/* ==========================================================================
   Gee What A Planner  —  the tool behind Gee What a Wedding
   Brand: hot pink #e8197a for headings & accents
          teal    #009faa for labels & borders
          white backgrounds only. No gold. No dark surfaces.
   ========================================================================== */

:root {
  --pink:        #e8197a;
  --pink-deep:   #c31465;
  --pink-tint:   #fdeaf3;

  --teal:        #009faa;
  --teal-deep:   #007c85;
  --teal-tint:   #e7f6f7;

  /* ---- the wider palette -------------------------------------------------
     Pink on its own got shouty, so categories now carry their own hue. These
     are for LABELLING — which category, which section — and are deliberately
     kept clear of the traffic-light colours below, which mean something.
     Status is always a dot or a pill; a category is always tinted text. Two
     different shapes, so even a near-miss on hue can't be misread. */
  /* ---- CONFETTI, chosen by Gee on 30 August 2026 -------------------------
     Two goes at this. The originals were six unrelated hues at full
     saturation and read as a bag of highlighters; I then muted them into one
     family and over-corrected so far that Gee called them "grey-brown mud",
     which was fair — desaturated against her pink, they looked dead.

     Confetti keeps the saturation and picks the six to sit TOGETHER. Every
     one carries a meaning, so a colour is never decoration:

        magenta    you, and your brand
        tangerine  overdue, needs attention
        emerald    money — budget, invoices, what is owing
        mint       celebrancy
        sky        suppliers and people
        grape      the couple's own edits

     Emerald replaced a gold Gee rejected, and she was right about that too:
     it was the only one in the set that was not fully saturated, which is
     exactly why it looked dull beside the rest.

     The NAMED value is the text colour, because --cat is used as type on
     --cat-tint. So these are the readable dark ends of each hue, not the
     vivid mid-tone — the vivid version is what the tint is mixed from. */
  --c-teal:      #00695c;   --c-teal-tint:   #d9f7f2;   /* mint — celebrancy */
  --c-sky:       #0067a3;   --c-sky-tint:    #daf0ff;   /* sky — suppliers   */
  --c-indigo:    #4a00b0;   --c-indigo-tint: #eae0ff;   /* grape — couple    */
  --c-plum:      #b3145e;   --c-plum-tint:   #ffe3ef;   /* magenta — you     */
  /* 🛑 RED, NOT BROWN. Gee, 17 September: "anywhere you've got that ugly
     brown, it should be that red." This is the ATTENTION colour — a count you
     have not checked, a heading that wants an answer, a job still to do — and
     as a solid fill it read as brown, so the one thing on the page asking for
     something looked like decoration. Was #b34e00. */
  --c-coral:     #d4121f;   --c-coral-tint:  #ffe3e4;   /* red — needs you    */

  /* ⚠️ AND THE PEACH SHE CHOSE IS NOT A STATUS, SO IT KEEPS THE OLD VALUE.
     `band-peach` is a run sheet heading colour she picks by hand, `cat-5` is
     one of the six category hues, and `door-coral` is a door. Sweeping those
     to red would have turned a heading she chose into an alarm and made one
     whole supplier category look broken. A palette choice and a warning are
     two different jobs that happened to share one token. */
  --c-peach:     #b34e00;   --c-peach-tint:  #ffeeda;   /* tangerine — chosen */
  --c-sage:      #00733d;   --c-sage-tint:   #d6f7e2;   /* emerald — money   */
  /* Added 9 Sept for the section-heading bands. Gee: "maybe give us a few more
     pastel color options." These two are the hues the door set has no answer
     for — a yellow, and a quiet neutral for the stretches of the day nobody
     needs to look at twice (pack down, transfers). Both deep values are dark
     enough to hold 700-weight uppercase at .78rem on their own tint, which is
     what the six above were chosen against. */
  --c-butter:    #8a6100;   --c-butter-tint: #fff3c4;   /* butter — a yellow */
  --c-stone:     #5f5560;   --c-stone-tint:  #ece7ea;   /* stone  — quiet    */

  /* ---- COUPLE IDENTITY, on the Coming up board ---------------------------
     Gee, 3 Sep: "the colours that you use are very similar to the red overdue
     button, so it's making it very difficult to see that... we don't want to
     use any reds or any oranges there for the client colours."

     The board was borrowing the six tokens above, and that was wrong twice
     over. Tangerine MEANS overdue and emerald MEANS money — so a card outlined
     in tangerine was saying "late" in the same breath as the red flag beside
     it, and a card outlined in emerald was saying "budget" about a couple. The
     palette above is a vocabulary, not a set of six nice colours to reach for.

     So the couples get a set of their own, and it is deliberately COOL end to
     end — 150° through 300°, green to orchid, with nothing warm in it. That is
     the whole trick: overdue red is then the only warm thing on the board, and
     it separates at a glance without having to shout.

     Six hues is already crowded, so they are spaced ~30° apart rather than
     chosen one at a time. The named value is the readable dark end, as above;
     -soft is the very light pastel the card is filled with. */
  --cu-1: #00707d;   --cu-1-soft: #e8f6f7;   /* cyan-teal */
  --cu-2: #16609c;   --cu-2-soft: #e9f1fa;   /* ocean     */
  --cu-3: #3d4fa8;   --cu-3-soft: #eceefa;   /* indigo    */
  --cu-4: #6d3fae;   --cu-4-soft: #f2ecfa;   /* violet    */
  --cu-5: #93359e;   --cu-5-soft: #f8ecf9;   /* orchid    */
  --cu-6: #12775a;   --cu-6-soft: #e8f6f0;   /* green     */

  /* One hover for all six. A white wash lifts any of the pastels above without
     needing a hover colour per hue, and it cannot go warm by accident. */
  --cu-hover: rgba(255, 255, 255, .68);

  /* The vivid mid-tones, for borders and anything that is not type on tint. */
  --v-mint:      #00bfa5;
  --v-sky:       #0091ea;
  --v-grape:     #6200ea;
  --v-magenta:   #e8197a;
  --v-tangerine: #ff6f00;
  --v-emerald:   #00c853;

  /* ---- BUTTON FILLS -----------------------------------------------------
     Gee, 30 Aug: "I don't think that you've looked at the checklist in the
     buttons there and the colors there... I think you haven't looked at them
     to go in sync with those color palettes."

     She was right, and the reason is worth writing down. The toolbar buttons
     DID read the category tokens — but those tokens are the dark TEXT ends of
     each hue, meant for type on a tint. Used as a solid fill they come out
     muddy, which is exactly the "not vibrant" complaint.

     The vivid ends cannot be used either: white on #00c853 is 2.24:1 and
     unreadable. So these are the brightest value in each hue that still holds
     a white label at 4.5:1, found by walking each hue from its vivid end
     toward its dark one rather than by eye. Bright green buttons with white
     writing are not a thing that can exist; this is as close as the hue gets.
     ---------------------------------------------------------------------- */
  /* SECOND ATTEMPT, and the first one was wrong in a way worth keeping.
     Gee, 30 Aug, looking at three Copy buttons in a row: "are you sure that
     the amber color is correct? I just don't feel like the coloring is
     correct."

     She was right. Forcing a hue dark enough to carry WHITE is what turned
     orange into mud — #be5100 is not tangerine, it is brown, and three of
     them side by side is the ugliest thing on that screen.

     The mistake was assuming a coloured button must have white writing. Turn
     it round and the problem disappears: a VIVID fill with dark ink of its own
     family is both brighter and MORE readable. Orange goes from 4.81:1 muddy
     to 5.59:1 vivid. So the light-friendly hues get dark ink, and the
     naturally dark ones — grape, pink — keep white. The fills are one family
     either way, which is what makes it read as a system rather than a mix. */
  --btn-sky:       #0091ea;   --ink-sky:       #002336;
  --btn-emerald:   #00c853;   --ink-emerald:   #00301a;
  --btn-tangerine: #ff6f00;   --ink-tangerine: #3d1a00;
  --btn-mint:      #00bfa5;   --ink-mint:      #002e28;
  --btn-pink:      #d1176e;   /* the brand, one shade deeper, for white type */
  /* No longer used on a button. Kept because removing a token that something
     might still reference is how you get an invisible control — and because
     the reason it left is worth reading: magenta and the brand pink measured
     3.7 apart on Lab, and anything under 15 reads as the SAME colour. "Apply
     budget" and "+ Add line" were sitting side by side looking identical,
     which is what Gee kept pointing at. Pink now means one thing: the single
     main action on a screen. Nothing else is allowed near it. */
  --btn-magenta:   #dd1874;
  --btn-grape:     #6200ea;
  --btn-crimson:   #d50000;

  /* Structure — headings, rules, the spine of a table. */
  --blue:        #1e3f7d;
  --blue-soft:   #eef2fa;
  --blue-rule:   #c3d0e8;

  /* Warm-biased neutrals. A pure grey next to hot pink reads as dirty. */
  --ink:         #2a2126;
  --ink-soft:    #6c6067;
  --ink-faint:   #a0949a;

  /* --muted was used in 23 places and defined in none of them, so every one of
     those declarations was invalid and the browser threw it away — 23 pieces of
     secondary text rendering in full-strength ink instead of stepping back. A
     TBC supplier's name, the text on a declined change request, the Notion pull
     detail lines, the template descriptions, the empty cells in the supplier
     coverage grid.

     Defined rather than find-and-replaced: the name is a good one and reads
     better than --ink-soft at the point of use, so making it real is worth more
     than removing it. Found 31 Aug 2026 while verifying the colour sweep — the
     audit counts never showed it, because a colour that never renders is not a
     colour anybody counts. */
  --muted:       var(--ink-soft);

  --line:        #ece5e8;
  --line-soft:   #f5f0f2;
  --surface:     #ffffff;
  --surface-soft:#faf7f9;
  --surface-warm:#fdf6f9;

  --danger:      #b3001b;
  --danger-tint: #fdeeec;

  /* "There is no ceremony." Not a warning, not an error — a confirmed absence,
     and the one thing on a run sheet a supplier must not skim past. Vibrant
     rather than the deep --danger above, which is tuned to sit quietly next to
     body text and reads as brown at card size. Gee: "I want a big, vibrant red
     so people can easily distinguish it."

     ⚠️ BRIGHTENED AGAIN 11 Sept — "it needs to be a nicer red, a bit more of a
     brighter red". #ef0000 rather than pure #ff0000: pure red sits at about
     4.0:1 on white, under the 4.5 a normal-weight word needs to stay readable,
     and this line is printed and read at a venue. #ef0000 is ~4.85:1 — visibly
     brighter than the maroon it replaced, and still legible on paper. */
  --absent:      #ef0000;

  /* ------------------------------------------------------------------------
     Status. Three colours, one meaning each, everywhere.

     Gee, 28 Aug: "I think we should have green mean confirmed, and maybe
     orange or change. We should have consistent color coding on the platform."
     Then, seeing it applied to every run sheet line: "why would we have
     confirmed on the running sheet? I'm a little bit confused."

     She was right, and it is the rule that makes the rest work: a colour only
     appears when it wants something from you. Plain means settled. Sixty green
     badges on a run sheet is not reassurance, it is noise, and it teaches you
     to stop reading badges at all.

     So green earns its place only where a FINISH is worth seeing at a glance —
     Paid, Done — and never as "this line is fine".

     Chosen to sit with the pink and the teal rather than shout over them: a
     deep sage, a burnt amber, and a wine red that is close enough to --pink
     not to fight it.
     ---------------------------------------------------------------------- */
  /* The traffic lights, in Confetti terms. Gee, 30 Aug: "I actually don't
     think it's site-wide at all" — she was right. Changing the six category
     tokens did nothing for these, because "done", "due soon" and "overdue"
     were written as raw hex in a hundred places rather than read from here.

     Crimson is new. Confetti had no red: magenta is the brand and tangerine
     is a warning, so genuine danger had nowhere to go and the app had drifted
     into using four different reds for it. */
  --ok:        #00733d;   --ok-tint:   #d6f7e2;   /* emerald  — done, paid   */
  --warn:      #d4121f;   --warn-tint: #ffe3e4;   /* tangerine — due soon    */
  --late:      #b3001b;   --late-tint: #ffe0e3;   /* crimson  — overdue      */
  --late-vivid:#d50000;
  --warn-mid:  #ffb74d;   /* borders and dots, where a tint would vanish */

  /* Bloom: generous corners, and shadows tinted with the brand rather than
     black, so an elevated card still looks like it belongs to this palette. */
  --radius:      14px;
  --radius-sm:   10px;
  --radius-lg:   22px;
  --radius-pill: 999px;

  --shadow:      0 1px 2px rgba(74, 30, 52, .04), 0 8px 24px rgba(74, 30, 52, .06);
  --shadow-soft: 0 2px 10px rgba(74, 30, 52, .05);
  --shadow-lg:   0 18px 50px rgba(74, 30, 52, .16);

  --display: "Bricolage Grotesque", "Figtree", -apple-system, BlinkMacSystemFont,
             "Segoe UI", sans-serif;
  --sans:    "Figtree", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
             "Helvetica Neue", Arial, sans-serif;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--surface);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* Headings are ink, not pink. Pink stops being a shout when it stops being
   every heading on the page — it goes back to meaning "look here". */
h1, h2, h3 {
  color: var(--ink);
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.15rem;
  margin: 0;
  line-height: 1.2;
  letter-spacing: -.02em;
  text-wrap: balance;
}

a { color: var(--teal-deep); }

[hidden] { display: none !important; }


/* ---------- wordmark ------------------------------------------------------ */

.wordmark {
  color: var(--pink);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: .11em;
  text-transform: uppercase;
  margin: 0;
}


/* ---------- boot / loading ------------------------------------------------ */

.boot {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  color: var(--ink-faint);
  letter-spacing: .04em;
}


/* ---------- login --------------------------------------------------------- */

.login-view {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 24px;
}

.login-card {
  width: 100%;
  max-width: 380px;
  border: 1px solid var(--line);
  border-top: 3px solid var(--pink);
  border-radius: var(--radius);
  padding: 34px 30px 30px;
  box-shadow: var(--shadow);
}

.login-card .wordmark { text-align: center; }

.login-card h1 {
  font-size: 1.35rem;
  text-align: center;
  margin: 6px 0 26px;
}

.login-card label {
  display: block;
  margin-bottom: 6px;
  color: var(--teal);
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
}

.login-card input { margin-bottom: 18px; }


/* ---------- form controls ------------------------------------------------- */

input[type="text"],
input[type="email"],
input[type="password"],
input[type="search"],
input[type="url"],
input[type="tel"],
textarea,
select {
  width: 100%;
  padding: 9px 11px;
  border: 1px solid var(--teal);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font-family: inherit;
  font-size: .93rem;
  transition: box-shadow .12s ease, border-color .12s ease;
}

textarea {
  min-height: 78px;
  resize: vertical;
}

select {
  appearance: none;
  background-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'><path d='M1 1l5 5 5-5' fill='none' stroke='%23009faa' stroke-width='2' stroke-linecap='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 11px center;
  padding-right: 32px;
}

input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--pink);
  box-shadow: 0 0 0 3px var(--pink-tint);
}

input::placeholder { color: var(--ink-faint); }


/* ---------- buttons ------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px 16px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--ink);
  font-family: inherit;
  font-size: .9rem;
  font-weight: 500;
  line-height: 1.2;
  cursor: pointer;
  white-space: nowrap;
  transition: background .12s ease, color .12s ease, border-color .12s ease;
}

.btn:disabled { opacity: .55; cursor: not-allowed; }

/* The one filled button on a screen — + Add task, Save changes, Sign in.
   --btn-pink, not --pink. The brand pink is #e8197a and it stays that
   everywhere it is TYPE: the wordmark, a couple's name, a countdown. But
   white on it is 4.33:1, just under readable, and this is the button people
   press hardest and most often. #d1176e is the same pink a shade deeper —
   indistinguishable beside the brand at button size — and clears it at
   5.17:1. The brand is untouched; only the fill under white text moved. */
.btn-primary { background: var(--btn-pink); color: #fff; }
.btn-primary:hover:not(:disabled) { background: var(--pink-deep); }

.btn-secondary { border-color: var(--teal); color: var(--teal-deep); }
.btn-secondary:hover:not(:disabled) { background: var(--teal-tint); }

/* ---------- the toolbar actions ------------------------------------------

   Print, Export, Pull from Notion and Apply were four identical teal outlines
   in a row of six. Gee, 29 Aug: "I feel like they should be colourful to
   stand out, because they're really not, and I think it's a bit hard to see.
   They're on the checklist, the budget, timeline, and suppliers."

   A colour per ACTION rather than one loud colour for all of them, so the
   colour carries meaning the way the source chips on the emails and the place
   tags on the run sheet do — Print is the same blue whether you are on the
   budget or the run sheet, so you stop reading the row and start aiming at it.

   Filled tints rather than outlines: an outline on white is what these already
   were, and the whole complaint is that it does not register.

   Selected off the attributes the buttons ALREADY carry, so this colours all
   four tabs without touching a line of HTML — and any new Print button
   anywhere gets it for free. */

.btn[data-print],
.btn[data-export],
.btn[id^="copy-"],
.btn#pull-from-notion,
.btn[data-action="apply-template"],
.btn[id^="apply-"],
.btn[data-action="save-project-template"],
.btn#save-timeline-template,
.btn#add-invoice,
.btn#add-payment,
.btn#s-new-contact,
.btn#team-add,
.btn#link-add,
.btn#f-newcat-save,
.btn#set-remind-plus,
.btn#set-backup {
  border-width: 1px;
  font-weight: 600;
}

/* Solid, not tinted. A pale wash on white is barely different from the outline
   these already were, and "it's a bit hard to see" was the whole complaint —
   see the email chip, which took three attempts to learn the same thing. */

/* ---- OUTLINED, all of them, and only the primary is filled ---------------
   Gee, 30 Aug: "some of the buttons are still not looking good, so you can't
   see because it's black font."

   Right, and it ends a corner I had painted myself into. A coloured button
   with WHITE text needs a fill dark enough to carry it, which is what turned
   tangerine into brown. Giving it DARK text instead let the fill stay vivid —
   but navy on bright blue just reads as black writing on a colour, which
   measures fine and looks unfinished. Both roads were wrong because the
   premise was: a coloured button must be a filled button.

   Outlined, it is neither. The colour is PURE — real #0091ea sky, real
   #00c853 emerald, nothing darkened to accommodate type — and the label is
   that same colour on white, so nothing anywhere is black on a colour.

   It also fixes the thing underneath every round of this: a toolbar of six
   filled buttons has no hierarchy. Now one button on each screen is solid
   pink and it is the one that matters. Everything else is a quiet coloured
   outline that fills on hover, so the colour is still what you touch.
   ------------------------------------------------------------------------- */

/* Print — sky. Onto paper. */
.btn[data-print] {
  border-color: var(--btn-sky); background: transparent; color: #0067a3;
}
.btn[data-print]:hover:not(:disabled) {
  background: var(--btn-sky); border-color: var(--btn-sky); color: #fff;
}

/* Export and Download a backup — emerald. Out as a file. */
.btn[data-export],
.btn#set-backup {
  border-color: var(--btn-emerald); background: transparent; color: #00733d;
}
.btn[data-export]:hover:not(:disabled),
.btn#set-backup:hover:not(:disabled) {
  background: var(--btn-emerald); border-color: var(--btn-emerald); color: var(--ink-emerald);
}

/* Copy — tangerine, and OUTLINED rather than filled.
   Out to the clipboard: the Instagram table, the emails, the phone numbers.
   Matched on id^="copy-" because two of the three are ghost buttons rather
   than secondary ones.

   Gee, 30 Aug, on the Suppliers toolbar: "I just don't feel like the coloring
   is correct." The hue was right by then — the WEIGHT was not. There are
   three Copy buttons and they sit together, so filling all three put a block
   of solid orange across half the toolbar and made three minor actions the
   loudest thing on the screen.

   Outlined fixes the weight without losing the colour or the grouping: still
   obviously one family, still obviously buttons, no longer shouting over
   Print, Export and the one pink action that actually matters. They fill on
   hover, so the colour is still what you touch. */
.btn[id^="copy-"] {
  border-color: var(--btn-tangerine);
  background: transparent;
  color: #d4121f;
}
.btn[id^="copy-"]:hover:not(:disabled) {
  background: var(--btn-tangerine);
  border-color: var(--btn-tangerine);
  color: var(--ink-tangerine);
}

/* ---- bringing something IN ----------------------------------------------- */

/* Pull from Notion — indigo. Arriving from somewhere else. */
.btn#pull-from-notion,
.btn#pull-contacts-notion {
  border-color: var(--btn-grape); background: transparent; color: #4a00b0;
}
.btn#pull-from-notion:hover:not(:disabled),
.btn#pull-contacts-notion:hover:not(:disabled) {
  background: var(--btn-grape); border-color: var(--btn-grape); color: #fff;
}

/* ---- templates ------------------------------------------------------------ */

/* Apply and Save as template — plum, both of them. They are the two ends of
   the same thing, and the labels say which end, so one colour for the family
   reads better than two colours nobody can keep straight.
   Matched on patterns rather than each id by name: naming them one at a time
   missed "Apply timeline" on the first go, and would have missed the next. */
.btn[data-action="apply-template"],
.btn[id^="apply-"],
.btn[data-action="save-project-template"],
.btn#save-timeline-template {
  border-color: var(--btn-grape); background: transparent; color: #4a00b0;
}
.btn[data-action="apply-template"]:hover:not(:disabled),
.btn[id^="apply-"]:hover:not(:disabled),
.btn[data-action="save-project-template"]:hover:not(:disabled),
.btn#save-timeline-template:hover:not(:disabled) {
  background: var(--btn-grape); border-color: var(--btn-grape); color: #fff;
}

/* ---- adding one more of something ---------------------------------------- */

/* Teal, not pink. Pink is reserved for the ONE main action on a screen —
   + Add task, + Add line, + Add supplier. These sit beside it and add a
   smaller thing, so they need to be clearly a button without competing with
   the button. */
.btn#add-invoice,
.btn#add-payment,
.btn#s-new-contact,
.btn#team-add,
.btn#link-add,
.btn#f-newcat-save,
.btn#set-remind-plus,
/* The Overview's own "+ Add task" — a ghost button, unlike the pink one on the
   checklist, which is why colouring btn-secondary alone left it behind. */
.btn.btn-ghost[data-action="add-task"] {
  border-color: var(--btn-mint); background: transparent; color: #00695c;
}
.btn#add-invoice:hover:not(:disabled),
.btn#add-payment:hover:not(:disabled),
.btn#s-new-contact:hover:not(:disabled),
.btn#team-add:hover:not(:disabled),
.btn#link-add:hover:not(:disabled),
.btn#f-newcat-save:hover:not(:disabled),
.btn#set-remind-plus:hover:not(:disabled),
.btn.btn-ghost[data-action="add-task"]:hover:not(:disabled) {
  background: var(--btn-mint); border-color: var(--btn-mint); color: var(--ink-mint);
}

@media print {
  /* None of them belong on the printout they produce. */
  .btn.btn-secondary[data-print],
  .btn.btn-secondary[data-export] { display: none; }
}

.btn-ghost { color: var(--ink-soft); }
.btn-ghost:hover:not(:disabled) { background: #f5f0f2; color: var(--ink); }

.btn-danger { border-color: var(--danger); color: var(--danger); }
.btn-danger:hover:not(:disabled) { background: var(--danger-tint); }

.btn-danger-solid { background: var(--danger); color: #fff; }
.btn-danger-solid:hover:not(:disabled) { background: #b3001b; }

.btn-block { width: 100%; }

.btn-sm { padding: 5px 10px; font-size: .82rem; }


/* ---------- top bar ------------------------------------------------------- */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 14px 26px;
  border-bottom: 1px solid var(--line);
}

.topbar-left,
.topbar-right {
  display: flex;
  align-items: center;
  gap: 14px;
}

.biz-name {
  padding-left: 14px;
  border-left: 1px solid var(--line);
  color: var(--ink-soft);
  font-size: .88rem;
}

.user-email {
  color: var(--ink-soft);
  font-size: .84rem;
}


/* ---------- module nav ---------------------------------------------------- */

.modnav {
  display: flex;
  gap: 4px;
  padding: 0 26px;
  border-bottom: 1px solid var(--line);
}

.modnav-item {
  padding: 11px 4px;
  margin-right: 20px;
  border: none;
  border-bottom: 2px solid transparent;
  background: none;
  color: var(--ink-soft);
  font-family: inherit;
  font-size: .88rem;
  font-weight: 500;
  letter-spacing: .03em;
  cursor: pointer;
}

.modnav-item.is-active {
  border-bottom-color: var(--pink);
  color: var(--pink);
}


/* ---------- layout -------------------------------------------------------- */

.wrap {
  max-width: 1220px;
  margin: 0 auto;
  padding: 26px;
}

.toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 22px;
}

.toolbar .search-field { flex: 1 1 260px; max-width: 380px; }
.toolbar .filter-field { flex: 0 1 210px; }

.count {
  margin-left: auto;
  color: var(--ink-faint);
  font-size: .84rem;
  white-space: nowrap;
}


/* ---------- contact cards ------------------------------------------------- */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  gap: 16px;
}

.card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  border-top: 3px solid var(--teal);
  border-radius: var(--radius);
  padding: 16px 17px 13px;
  background: var(--surface);
  transition: box-shadow .14s ease, border-color .14s ease;
}

.card:hover {
  border-top-color: var(--pink);
  box-shadow: var(--shadow);
}

.card-name {
  color: var(--pink);
  font-size: 1.04rem;
  font-weight: 600;
  margin: 0 0 8px;
  word-break: break-word;
}

/* THE COUPLE'S NAME IS THE ONE THING ON THE CARD YOU READ FIRST.
   Gee, 10 Sept, looking at nine cards at once: "Too much pink happening here.
   It's very hard to read. The names of the couple should be in black."

   She is right and it is not only the name. Nine cards each carry a pink
   border, a pink status pill, a pink progress bar, a pink overdue pill and a
   pink heading — and when everything is emphasised, nothing is. The heading is
   the piece that has to win, so it stops competing and takes the ink.

   ⚠️ SCOPED TO .wedding-card DELIBERATELY. `.card-name` is shared with the
   contacts list (contacts.js:169), and Gee pointed at the weddings screen.
   Contacts is raised with her rather than swept along — if she wants it too it
   is one selector, but it is her call, not a tidy-up.

   Sits after the base rule rather than editing it, so the contacts list keeps
   the brand pink with no second definition to keep in step. */
.wedding-card .card-name { color: var(--ink); }

.pill {
  display: inline-block;
  padding: 2px 10px;
  border: 1px solid var(--teal);
  border-radius: 999px;
  color: var(--teal-deep);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
}

.pill-none {
  border-color: var(--line);
  color: var(--ink-faint);
}

/* Cards are flex columns, so a bare pill would stretch to the full width. */
.card > .pill { align-self: flex-start; }

.card-rows {
  margin: 13px 0 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: .87rem;
}

.card-row {
  display: flex;
  gap: 9px;
  align-items: baseline;
}

.card-row .k {
  flex: 0 0 68px;
  color: var(--teal);
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding-top: 2px;
}

.card-row .v {
  flex: 1 1 auto;
  min-width: 0;
  word-break: break-word;
}

.card-row .v a { text-decoration: none; }
.card-row .v a:hover { text-decoration: underline; }

.card-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.card-actions .spacer { margin-left: auto; }

.file-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.file-badge .n {
  min-width: 17px;
  height: 17px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--pink-tint);
  color: var(--pink);
  font-size: .7rem;
  font-weight: 700;
  line-height: 17px;
  text-align: center;
}


/* ---------- project shell ------------------------------------------------- */

.backlink {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-bottom: 16px;
  padding: 0;
  border: none;
  background: none;
  color: var(--teal-deep);
  font-family: inherit;
  font-size: .86rem;
  cursor: pointer;
}

.backlink:hover { color: var(--pink); }

.project-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  padding-bottom: 16px;
}

.project-head h1 {
  font-size: 1.6rem;
  margin: 0 0 5px;
  word-break: break-word;
}

.project-meta {
  display: flex;
  gap: 7px;
  flex-wrap: wrap;
  color: var(--ink-soft);
  font-size: .9rem;
}

.project-head-side {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.countdown {
  padding: 4px 13px;
  border: 1px solid var(--teal);
  border-radius: 999px;
  color: var(--teal-deep);
  font-size: .82rem;
  font-weight: 600;
  white-space: nowrap;
}

.countdown.is-soon { border-color: var(--pink); color: var(--pink); background: var(--pink-tint); }
.countdown.is-past { border-color: var(--line); color: var(--ink-faint); }

.subnav {
  display: flex;
  gap: 24px;
  margin-bottom: 22px;
  border-bottom: 1px solid var(--line);
}

.subnav-item {
  padding: 9px 2px;
  border: none;
  border-bottom: 2px solid transparent;
  background: none;
  color: var(--ink-soft);
  font-family: inherit;
  font-size: .87rem;
  font-weight: 500;
  cursor: pointer;
}

.subnav-item.is-active { border-bottom-color: var(--pink); color: var(--pink); }


/* ---------- status pills -------------------------------------------------- */

.pill.status-booked,
.pill.status-in_progress { border-color: var(--pink); color: var(--pink); }
.pill.status-completed   { border-color: var(--line); color: var(--ink-faint); }
.pill.status-cancelled   { border-color: var(--danger); color: var(--danger); }


/* ---------- progress bar -------------------------------------------------- */

.progress-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}

.progress-bar > span {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--pink);
  transition: width .25s ease;
}


/* ---------- wedding cards ------------------------------------------------- */

.wedding-card { cursor: pointer; }
.wedding-card:focus-visible { outline: 2px solid var(--pink); outline-offset: 2px; }
.card-row .v.is-soon { color: var(--pink); font-weight: 600; }

.card-progress {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.card-progress-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 7px;
  color: var(--ink-faint);
  font-size: .78rem;
}


/* ---------- overview ------------------------------------------------------ */

.ov-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 16px;
}

.ov-card {
  border: 1px solid var(--line);
  border-top: 3px solid var(--teal);
  border-radius: var(--radius);
  padding: 16px 17px;
}

/* Recent activity had NO styling whatsoever — not one rule for .act-list,
   .act-who, .act-what, .act-when or .ov-wide. It rendered as a raw unstyled
   <ul>, which is most of why Gee said "I can't see anything on this screen...
   this is a terrible way of looking at it". Everything below is new. */

/* Two columns wide where there is room. It is the busiest card on the page. */
.ov-wide { grid-column: span 2; }
@media (max-width: 780px) { .ov-wide { grid-column: 1 / -1; } }

/* Recent activity now sits ABOVE the grid rather than as its last cell, so it
   is no longer a grid child and grid-column means nothing to it. Full width
   and its own bottom margin, otherwise it would sit flush against the checks
   panel below it. */
.ov-activity-first {
  grid-column: auto;
  margin-bottom: 16px;
}

.ov-card h3 .act-badge {
  margin-left: 8px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--pink-tint);
  color: var(--pink);
  font-size: .68rem;
  letter-spacing: 0;
  text-transform: none;
}

/* The header carries a badge, a filter and a button. Let them sit on one line
   and wrap rather than stretching the heading. */
.ov-card h3:has(.act-badge) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.act-filter {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: .72rem;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-faint);
  cursor: pointer;
}

/* THE important one. Uncapped, forty entries stretched the whole grid row to
   about three screens and pushed the checks panel off the bottom of the page.
   A column that can push the page around decides what everything else is
   worth. It scrolls inside itself now. */
.act-list {
  margin: 0;
  padding: 0;
  list-style: none;
  max-height: 340px;
  overflow-y: auto;
}

.act {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2px 12px;
  padding: 9px 2px;
  border-top: 1px solid var(--line);
  font-size: .86rem;
  line-height: 1.45;
}
.act:first-child { border-top: 0; }

.act-who  { font-weight: 700; color: var(--ink); }
.act-what { grid-column: 1; color: var(--ink-soft); }
.act-when { grid-column: 2; grid-row: 1; color: var(--ink-faint); font-size: .76rem; white-space: nowrap; }
.act-detail {
  grid-column: 1 / -1;
  color: var(--ink-faint);
  font-size: .8rem;
  /* A pasted comment can be a paragraph. Two lines, then stop. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The couple's own actions are the reason to read this at all. */
.act.is-client .act-who { color: var(--pink); }
.act.is-new { background: var(--pink-tint); border-radius: 6px; }
.act.is-empty { color: var(--ink-faint); border-top: 0; display: block; }

.ov-card h3 {
  margin: 0 0 12px;
  color: var(--teal);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.ov-big {
  margin: 11px 0 3px;
  color: var(--pink);
  font-size: 1.25rem;
  font-weight: 600;
}

.ov-sub { margin: 0; color: var(--ink-faint); font-size: .82rem; }

.ov-actions {
  display: flex;
  gap: 7px;
  flex-wrap: wrap;
  margin-top: 15px;
}

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

.ov-list li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 0;
  border-bottom: 1px solid var(--line);
  font-size: .87rem;
}

.ov-list li:last-child { border-bottom: none; }
.ov-list .ov-task { flex: 1 1 auto; min-width: 0; }
.ov-list .ov-when { flex: 0 0 auto; color: var(--ink-faint); font-size: .78rem; white-space: nowrap; }
.ov-list .ov-when.is-overdue { color: var(--danger); font-weight: 600; }
.ov-list .ov-none { color: var(--ink-faint); }

.ov-dl { margin: 0; font-size: .87rem; }
.ov-dl dt {
  color: var(--teal);
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  margin-top: 10px;
}
.ov-dl dt:first-child { margin-top: 0; }
.ov-dl dd { margin: 2px 0 0; word-break: break-word; }


/* ---------- checklists ---------------------------------------------------- */

.filter-field.wide { flex: 0 1 300px; }

.check-inline {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--ink-soft);
  font-size: .85rem;
  white-space: nowrap;
  cursor: pointer;
}

.check-inline input { width: 15px; height: 15px; accent-color: var(--teal); }

/* A group of fields a tick has just answered for — the ceremony boxes under
   "No ceremony on the wedding day".

   Faded, NOT disabled. Gee can tick it, learn there is a ceremony after all,
   and type straight back in without hunting for the tick to reopen the boxes.
   `opacity` alone also leaves whatever she typed readable, which matters:
   nothing here is ever wiped, so a stale 2:00 pm must stay visible rather than
   greyed into nothing. Full strength returns the moment she hovers or lands in
   one, so a field she is actually working in is never the dim one. */
.is-not-applicable {
  opacity: .45;
  transition: opacity .15s ease;
}

.is-not-applicable:hover,
.is-not-applicable:focus-within { opacity: 1; }

.task-group { margin-bottom: 26px; }

.task-group-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0 0 10px;
  padding-bottom: 7px;
  border-bottom: 1px solid var(--line);
  color: var(--pink);
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.task-group-count {
  color: var(--ink-faint);
  font-size: .75rem;
  font-weight: 500;
  letter-spacing: .03em;
}

.task-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.task-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 9px 10px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
}

.task-row:hover {
  border-color: var(--line);
  background: #faf7f9;
}

.task-check {
  flex: 0 0 auto;
  width: 17px;
  height: 17px;
  margin-top: 2px;
  accent-color: var(--teal);
  cursor: pointer;
}

.task-main { flex: 1 1 auto; min-width: 0; }

.task-title {
  display: block;
  font-size: .92rem;
  word-break: break-word;
}

.task-row.is-done .task-title {
  color: var(--ink-faint);
  text-decoration: line-through;
}

.task-meta {
  display: block;
  margin-top: 2px;
  color: var(--ink-faint);
  font-size: .78rem;
}

.task-meta .dot { margin: 0 7px; }

.task-meta .due.is-overdue { color: var(--danger); font-weight: 600; }
.task-meta .due.is-soon    { color: var(--pink);   font-weight: 600; }

.task-meta .task-wedding { color: var(--teal-deep); }

/* .task-actions — the right-hand strip that held the clip and Details — is
   gone. Both buttons moved into the left gutter on 28 Aug, at Gee's request:
   "I'd much rather have the details and also the attachment clip on the
   left-hand side where we've got delete plus and stuff." */

.overdue-pill {
  display: inline-block;
  margin-left: 7px;
  padding: 1px 9px;
  border-radius: 999px;
  background: var(--danger-tint);
  color: var(--danger);
  font-size: .74rem;
  font-weight: 700;
}

/* The Suppliers tab's missing-details pills (19 Oct). The same pink note as
   ever, now a button: press one to show only those suppliers. The pressed one
   gets a ring, so she can see why the table is short. */
#supplier-warnings { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-left: 7px; }
#supplier-warnings .overdue-pill { margin-left: 0; }
.sup-gap, .sup-gap-all {
  border: 0;
  font-family: inherit;
  line-height: 1.6;
  cursor: pointer;
}
.sup-gap:hover { filter: brightness(.96); }
.sup-gap[aria-pressed="true"] { outline: 2px solid var(--danger); outline-offset: 1px; }
.sup-gap-ok {
  display: inline-block;
  padding: 1px 9px;
  border-radius: var(--radius-pill);
  background: var(--ok-tint);
  color: var(--ok);
  font-size: .74rem;
  font-weight: 700;
}
.sup-gap-all {
  padding: 1px 9px;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--danger);
  font-size: .74rem;
  font-weight: 700;
  text-decoration: underline;
}
.sup-gap:focus-visible, .sup-gap-all:focus-visible { outline: 2px solid var(--danger); outline-offset: 2px; }

/* No hover on a phone, so the gutter has to be there all the time or the
   buttons are unreachable. */
/* 🛑 MOVED, 18 SEPTEMBER. It used to sit here and never applied — 0,1,0 against
   the 0,2,0 `.task-row > .row-gutter { opacity: .38 }` written 6,400 lines
   below it. It now lives directly under that rule, carrying `.task-list`.
   Kept as a note rather than deleted silently: the comment above it was
   correct for a month while the rule under it did nothing. */


/* ---------- empty state --------------------------------------------------- */

.empty {
  padding: 60px 20px;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  text-align: center;
  color: var(--ink-soft);
}

.empty strong {
  display: block;
  margin-bottom: 6px;
  color: var(--pink);
  font-size: 1.05rem;
}


/* ---------- modal --------------------------------------------------------- */

.modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 5vh 16px;
  background: rgba(44, 44, 44, .38);
  overflow-y: auto;
}

.modal-box {
  width: 100%;
  max-width: 620px;
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

.modal-box.narrow { max-width: 430px; }

.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 18px 22px;
  border-bottom: 1px solid var(--line);
}

.modal-head h2 { font-size: 1.12rem; }
/* Done at the top of a form that saves itself (budget line, supplier —
   20 Oct, CHECK 213). margin-left: auto keeps it beside the ×, not centred
   in the head's space-between. */
.modal-head .modal-head-done { margin-left: auto; }

.modal-close {
  border: none;
  background: none;
  color: var(--ink-faint);
  font-size: 1.5rem;
  line-height: 1;
  padding: 0 4px;
  cursor: pointer;
}

.modal-close:hover { color: var(--pink); }

.modal-body { padding: 22px; }

.modal-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 15px 22px;
  border-top: 1px solid var(--line);
}

.modal-foot .spacer { margin-left: auto; }

/* "Saved 4:12 pm" in the footer of a form that saves itself.
   A form with no Save button and no sign of saving asks you to take it on
   faith — which is the exact feeling the button was there to prevent. */
.save-stamp {
  font-size: .78rem;
  color: var(--ink-faint);
  margin-right: 4px;
  white-space: nowrap;
}
.save-stamp.is-busy { color: var(--ink-faint); }
.save-stamp.is-ok   { color: var(--teal-deep); }
.save-stamp.is-bad  { color: var(--danger); font-weight: 600; }


/* ---------- form grid ----------------------------------------------------- */

.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 15px;
}

.field { display: flex; flex-direction: column; }
.field.full { grid-column: 1 / -1; }

/* Estimate · Invoice amount · Due date · Terms — four short values that do not
   each need half the width of the modal. One row, two on a narrow screen. */
.money-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
}
@media (max-width: 620px) {
  .money-row { grid-template-columns: 1fr 1fr; }
}

/* ---------------------------------------------------------------------------
   THE FIELD LABEL STOPS SHOUTING. 3 September.

   Gee, on the supplier card: "it's very messy. It's all over the place."

   Measured before it was touched: 2063px tall, 22 inputs, and 22 labels — every
   one of them in this same ALL-CAPS teal. That is the fault. When every label
   shouts equally nothing is important, so the supplier's NAME read exactly as
   loud as "where these hours came from".

   ⚠️ Counted before changing, as she has asked: `.field` appears 147 times
   across 22 modals. The supplier card is not badly built — it is the biggest
   card built in the app's normal way, so this is a change to the form language
   and it lands everywhere at once. Fixing one card would have made it the only
   card that looked different, which is the inconsistency she keeps flagging.

   Sentence case, ink rather than teal, and no letter-spacing. The caps are kept
   for GROUP headings, which is what they were always right for: a heading names
   a section, a label names a box, and the two must not look the same.
   -------------------------------------------------------------------------- */
.field label {
  margin-bottom: 4px;
  color: var(--ink);
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
}

.field .hint {
  margin-top: 4px;
  color: var(--ink-faint);
  font-size: .75rem;
}

/* The "?" that the hint now lives behind — see initFieldHints in ui.js. Small,
   quiet, and beside the label rather than under the box, so a row of fields
   reads as a row of fields. */
.field label .why,
.field .form-section .why {
  margin-left: 6px;
  padding: 0;
  width: 15px;
  height: 15px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: none;
  color: var(--ink-faint);
  font: inherit;
  font-size: .68rem;
  font-weight: 700;
  line-height: 1;
  vertical-align: 1px;
  cursor: help;
}

.field label .why:hover,
.field label .why.is-on,
.field .form-section .why:hover,
.field .form-section .why.is-on {
  border-color: var(--teal);
  background: var(--c-teal-tint);
  color: var(--teal);
}

.form-error {
  margin: 14px 0 0;
  padding: 9px 12px;
  border: 1px solid var(--danger);
  border-radius: var(--radius-sm);
  background: var(--danger-tint);
  color: var(--danger);
  font-size: .85rem;
}

.login-card .form-error { margin-top: 16px; }


/* ---------- files --------------------------------------------------------- */

.upload-zone {
  padding: 22px;
  border: 1px dashed var(--teal);
  border-radius: var(--radius);
  background: var(--teal-tint);
  text-align: center;
}

.upload-zone.is-over {
  border-color: var(--pink);
  background: var(--pink-tint);
}

.upload-zone p {
  margin: 0 0 12px;
  color: var(--ink-soft);
  font-size: .87rem;
}

.upload-zone .hint {
  margin: 10px 0 0;
  color: var(--ink-faint);
  font-size: .75rem;
}

.file-list {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.file-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}

.file-thumb {
  flex: 0 0 42px;
  width: 42px;
  height: 42px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  object-fit: cover;
  background: #faf7f9;
}

/* A VIDEO IS THE ONE ATTACHMENT YOU HAVE TO WATCH, so it is not a 42px square.
   Migration 132, 21 September — Gee's couples send Instagram inspo and a
   thumbnail the size of a postage stamp answers nothing about a video.

   ⚠️ (0,2,0) BEATS `.file-thumb` (0,1,0), so this works wherever it sits in
   the file and does not depend on being later. `checks.css` and `bloom.css`
   were both checked for a competing rule; neither touches this class.

   Black ground on purpose: letterboxing a portrait phone video against the
   page's off-white reads as a broken image. */
.file-thumb.file-video {
  flex: 0 0 168px;
  width: 168px;
  height: auto;
  max-height: 120px;
  object-fit: contain;
  background: #000;
}

@media (max-width: 620px) {
  /* On a phone it takes the width rather than sitting beside the name. */
  .file-thumb.file-video { flex-basis: 100%; width: 100%; max-height: 190px; }
}

.file-icon {
  flex: 0 0 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--pink-tint);
  color: var(--pink);
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .05em;
}

.file-meta { flex: 1 1 auto; min-width: 0; }

.file-meta .fname {
  display: block;
  font-size: .88rem;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-meta .fsub {
  color: var(--ink-faint);
  font-size: .76rem;
}

.file-row-actions {
  flex: 0 0 auto;
  display: flex;
  gap: 6px;
  align-items: center;
  flex-wrap: wrap;
}

/* "on their supplier card" sits among the buttons, outside .file-meta, so the
   rule above never reached it and it drew at full body size beside a faint
   Save — 1 Oct, La Belle Venues. Same small grey as the line under the name. */
.file-row-actions .fsub {
  color: var(--ink-faint);
  font-size: .76rem;
}

/* "Where does this belong?"

   The two questions a document on the wedding's Files tab cannot answer for
   itself. Everything in the app hangs off a wedding and a supplier; a file
   here carries only the wedding, so it asks for the other one and then takes
   the same path a supplier's contract takes.

   Deliberately plain and deliberately small: this is a step on the way to the
   offers below it, not a feature of its own. */
.ar-where {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin: 10px 0 4px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--c-plum);
  border-radius: var(--radius-sm);
  background: var(--surface-soft, #faf7f9);
}

.ar-where-q {
  margin: 0;
  font-size: .84rem;
  font-weight: 700;
  color: var(--c-plum);
}

.ar-where-f {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: .8rem;
  color: var(--ink-soft);
}

.ar-where-f select { font: inherit; padding: 5px 8px; }

.ar-money:empty { display: none; }
.ar-money { margin-top: 8px; }

/* The controls on a document.

   Gee, 16 September: "I can't see it, I can't read it, and it's very hard to
   find it... maybe more of a proper button like we have in other places."

   Measured before changing anything: 12.5px type in a 28px-high control, and
   bloom.css restyles EVERY .btn-secondary as a white pill with a 1px
   var(--line) border - the same colour as a divider. On a white card that is
   a button you have to already know is there.

   ⚠️ bloom.css's rule is app-wide and deliberate, so it is NOT touched. This
   is scoped to a document row, where four small controls sit together and the
   neutral treatment stops working.

   Bigger type, a real target, and - the actual point - the one control that
   DOES something stops looking like the three that do not. Read it, and every
   apply, is now filled. Open and Save stay quiet; Delete stays quietest of
   all, which checks.css already handles. */
.file-row-actions .btn {
  font-size: .84rem;
  padding: 8px 14px;
  min-height: 34px;
}

.file-row-actions .btn-secondary {
  border-color: var(--teal);
  color: var(--teal-deep);
}

.file-row-actions [data-read-bf],
.file-row-actions [data-read-as-invoice],
.file-row-actions [data-apply-read],
.file-row-actions [data-apply-bank] {
  background: var(--btn-pink);
  border-color: var(--btn-pink);
  color: #fff;
  font-weight: 600;
}

.file-row-actions [data-read-bf]:hover:not(:disabled),
.file-row-actions [data-read-as-invoice]:hover:not(:disabled),
.file-row-actions [data-apply-read]:hover:not(:disabled),
.file-row-actions [data-apply-bank]:hover:not(:disabled) {
  background: var(--pink-deep);
  border-color: var(--pink-deep);
}

/* The same treatment where the couple's attachment is promoted, so "this
   document can be worked on" looks the same wherever it appears. */
.comment .bc-file {
  background: var(--btn-pink);
  border-color: var(--btn-pink);
  color: #fff;
  font-weight: 600;
  font-size: .82rem;
  padding: 7px 13px;
  margin-left: 8px;
}

.comment .bc-file:hover:not(:disabled) {
  background: var(--pink-deep);
  border-color: var(--pink-deep);
}

.muted-note {
  color: var(--ink-faint);
  font-size: .85rem;
  text-align: center;
  padding: 18px 0 4px;
}


/* ---------- progress ------------------------------------------------------ */

.progress {
  height: 4px;
  margin-top: 14px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}

.progress span {
  display: block;
  height: 100%;
  width: 0;
  background: var(--pink);
  transition: width .2s ease;
}


/* ---------- toasts -------------------------------------------------------- */

.toasts {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.toast {
  min-width: 220px;
  max-width: 380px;
  padding: 11px 15px;
  border: 1px solid var(--teal);
  border-left: 3px solid var(--teal);
  border-radius: var(--radius-sm);
  background: var(--surface);
  box-shadow: var(--shadow);
  font-size: .87rem;
  animation: toast-in .18s ease;
}

.toast.is-error {
  border-color: var(--danger);
  border-left-color: var(--danger);
  color: var(--danger);
}

@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}


/* ---------- responsive ---------------------------------------------------- */

@media (max-width: 620px) {
  .form-grid { grid-template-columns: 1fr; }
  .wrap      { padding: 18px 15px; }
  .topbar    { padding: 12px 15px; }
  .modnav    { padding: 0 15px; }
  .count     { margin-left: 0; width: 100%; }
  .toolbar .search-field,
  .toolbar .filter-field { flex-basis: 100%; max-width: none; }
}


/* ---------- budget ---------------------------------------------------------- */

.modal-box.wide { max-width: 860px; }

/* ---------- the two big forms --------------------------------------------

   Not every form is the same size, and until now every box was.

   The supplier form carries FORTY controls — roles, describe, where, driver,
   terms, contact, coverage, access, start, finish, where the hours came from,
   dietary, notes, messages — in the same 620px box as a two-field one. Gee,
   29 Aug: "when you go to edit the supplier, it's real full on and it would
   be good to be able to do that in a full page. Not just the screen like
   that... to open up in full view."

   .full is that page: it takes the screen rather than floating on it.
   .xwide is the budget line, which is smaller but still has a payment
   schedule that has to be read across.

   Deliberately separate from .wide, which the wedding form uses and which is
   the right size for what it holds — widening that one to fix this one would
   be changing a screen nobody complained about. */

.modal-box.full {
  max-width: min(1320px, 96vw);
  min-height: 88vh;
  display: flex;
  flex-direction: column;
}

/* ⚠️ A SIZE, NOT A CHANGE TO `.full`.
   Gee, 7 Sept: "Do you think that screen has to be that large now that we've
   moved everything and fixed up the spacing?"

   No — but `.modal-box.full` is shared by every large modal, and the supplier
   card genuinely needs 1320: its widest row is 1,050px of boxes. Narrowing the
   shared class would have squeezed that one to fix this one.

   The contact card's widest row is now 1,090px, so 1160 holds it with air.
   `min-height` goes too — the card is roughly 600px of content and was being
   held open at 88vh, which is where all the empty space below Notes came from.

   To narrow another modal: measure its widest .tight-row, add ~70, add the
   class. Do not reach for `.full`. */
.modal-box.full.snug {
  max-width: min(1160px, 94vw);
  min-height: 0;
}

/* ⚠️ `min-height: 0` IS NOT ENOUGH ON ITS OWN, and this cost an attempt.
   `.modal:has(> .modal-box.full)` sets `align-items: stretch`, which pulls the
   box to the full height of the screen whatever its min-height says. On the
   project form that left ~250px of white under Notes even with `snug` — Gee
   photographed it: "it's not using the best space. And it looks overwhelming."

   `align-self: start` lets it be as tall as its content. `max-height` keeps the
   head and foot pinned and the body scrolling on a long form, which is the job
   `stretch` was doing.

   Scoped to this modal by id ON PURPOSE. The contact card and Key details share
   `.snug` and both are stretched the same way, so both are probably carrying
   some of this too — but Gee signed those off on 7 Sept and they are not what
   she asked about. Raised, not changed. */
/* Measured on 8 Sept, empty, at a 1000px viewport — the white space each of
   these was holding open below its own content:

     Contact card   524px of form in a 960px box   436px of nothing
     Budget line    536px of form in a 960px box   424px of nothing
     Key details    743px of form in a 960px box   217px of nothing
     New wedding    739px of form in a 744px box     5px  (already fixed)

   `.snug` alone does not do it — two of these already had `.snug`. The height
   comes from `align-items: stretch` on the modal, which pulls the box to the
   full screen whatever its min-height says.

   ⚠️ WIDTHS ARE UNTOUCHED. This removes empty space and changes nothing else,
   which is why it is safe on three cards Gee has already signed off. The
   budget line is deliberately NOT given `.snug`: that class also narrows to
   1160, and its widest row is four 307px boxes. Narrowing it is a layout
   decision and hers to make. */
#wedding-modal .modal-box.full.snug,
#timeline-event-modal .modal-box.full.snug,
#contact-modal .modal-box.full,
#timeline-details-modal .modal-box.full,
#budget-modal .modal-box.full,
#supplier-modal .modal-box.full {
  align-self: start;
  max-height: 96vh;
  /* TWO separate rules hold a card open, and align-self only answers one.
     `.modal-box.full` also carries `min-height: 88vh`, and the other cards
     escape it through `.snug` — which the budget line must not have, because
     `.snug` narrows to 1160 as well and its widest row is four 307px boxes.
     Measured after fixing only the stretch: still 880px for 536px of form,
     because 88vh of a 1000px screen is 880. So the min-height is zeroed here
     on its own, and the width is left exactly as it was.

     The SUPPLIER CARD is here for the same reason and with the same care: §34
     records that it genuinely needs 1320 — its widest row is 1,050px of boxes —
     so it must not be given `.snug` either. It was holding about 100px, the
     smallest of the four, and it is the card Gee signed off first. Height only;
     the 1320 stays. */
  min-height: 0;
}

.modal-box.xwide { max-width: min(1080px, 96vw); }

/* A page, not a card sitting in the middle of one. */
.modal:has(> .modal-box.full) { padding: 2vh 2vw; align-items: stretch; }

.modal-box.full > form {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

/* The head and foot stay put; only the fields scroll. On a form this long,
   losing the Save button off the bottom of a 2000px page is the difference
   between a form and a scavenger hunt. */
.modal-box.full .modal-head,
.modal-box.full .modal-foot { flex: 0 0 auto; }
.modal-box.full .modal-body { flex: 1 1 auto; overflow-y: auto; padding: 24px 28px; }

/* Room means columns. Three across on a laptop, four on a wide screen —
   a 240px floor so nothing is ever squeezed into a slot too small to read. */
.modal-box.full .form-grid,
.modal-box.xwide .form-grid {
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

/* Compressed. Gee, 29 Aug: "it just needs to be compressed."
   Most of the height in this form is not the boxes, it is the sentence under
   each one. They stay — they are the difference between a field you can fill
   in and one you have to ask about — but they get smaller and tighter, and
   the whole form loses about a third of its height. */
.modal-box.full .form-grid { gap: 12px 14px; }
.modal-box.full .hint {
  margin-top: 3px;
  font-size: .71rem;
  line-height: 1.4;
}
/* .68rem was right for an all-caps label, which reads bigger than it measures.
   In sentence case it came out at 10.5px — smaller than the hint underneath it.
   Caught by measuring the computed style rather than reading the rule. */
.modal-box.full .field label { margin-bottom: 3px; font-size: .78rem; }
.modal-box.full .form-section { margin: 0 0 2px; font-size: .8rem; }

/* Gee, 7 Sept: "I don't feel like we need that much of a gap like we did
   previously." Six headings on this form, each one carrying 16px of padding
   above its rule — 96px of air on a form that was already scrolling.
   Scoped to this modal by id ON PURPOSE. `.modal-box.full .form-section` is
   shared with the supplier card and the contact card, and both of those she
   has already signed off; this is not the moment to nudge them. */
#timeline-details-modal .form-section { padding-top: 10px; }

/* Several short values on one line rather than one each. */
.tight-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px 14px;
}
@media (min-width: 720px) {
  /* ⚠️ FIXED WIDTHS, NOT FRACTIONS — this is the whole fix.
     `1fr` shares the row out equally, so a phone number got the same room as a
     street address and the group ended up a hand-span wide. Measured on
     Signature Chauffeurs, 7 Sept: 5,561px of empty box across 18 fields, with
     Instagram holding 616px to show eight characters.
     Gee: "There's so much space there that we can really constrict this even
     more… it's all over the shop."
     `justify-content: start` is what makes the leftovers collect at the END of
     the row instead of being shared out between the boxes. Without it every
     width below is ignored the moment the row is wider than their sum. */
  .tight-row { justify-content: start; }
  .tight-row > .field { min-width: 0; }

  .tight-row.two   { grid-template-columns: 1fr 1fr; }
  .tight-row.three { grid-template-columns: 150px 320px 260px; }
  .tight-row.four  { grid-template-columns: 170px 150px 160px 160px; }

  /* Every way to reach a supplier, on one line. Contact · Email · Phone ·
     Instagram · Website = 1040px of boxes in a 1320px card. */
  .tight-row.reach { grid-template-columns: 170px 280px 150px 190px 250px; }

  /* What they do and where they turn up. The venue gets the room because it
     holds "Ceremony — St Paul's Cathedral, 209 Flinders Lane, Melbourne VIC
     3000" — measured at 498px, and it has been clipping at 300 for weeks. */
  .tight-row.what { grid-template-columns: 260px 280px 510px; }

  /* The hours, the paperwork they came off, whose money it is. The checkbox
     takes what it needs; "Who's paying" only appears once it is ticked, which
     is why the row is declared with five columns and usually shows four. */
  .tight-row.booked { grid-template-columns: 150px 300px 240px auto 200px; }

  /* ---- the contact card, 7 Sept ------------------------------------------
     Same treatment, same three rules. Fifteen fields, seven of them claiming a
     whole row in a two-column grid. */

  /* The business, the person you deal with, and what they do. */
  .tight-row.who  { grid-template-columns: 280px 240px 340px; }

  /* Phone · Email · Website · Instagram. Its own class rather than reusing
     .reach: the supplier card's are a different set in a different order, and
     one row quietly inheriting the other's widths is the kind of thing that
     only shows up on the longest value somebody ever types.
     Facebook was removed on 7 Sept — nothing ever read it. */
  .tight-row.ways { grid-template-columns: 150px 280px 250px 190px; }

  /* The address and the bank block, side by side.
     The address was a full-width textarea — about 1,800px of empty box for a
     street address — and it was the only reason the bank details sat so far
     down the card. `align-items: start` matters: without it the textarea and
     the bank block stretch to match each other's height. */
  .tight-row.where-and-bank {
    grid-template-columns: 340px minmax(0, 1fr);
    align-items: start;
  }
  .tight-row.where-and-bank > .field > textarea { resize: vertical; }

  /* ⚠️ A BSB IS SIX DIGITS. It was being handed a third of the card.
     These are the numbers she types into a banking app and checks against an
     invoice — sized to be read at a glance, not stretched to fill a row.
     Leading zeros matter here (013013 is not 13013), which is why they are all
     text inputs and why the widths are generous enough to never truncate. */
  .tight-row.bank { grid-template-columns: 210px 110px 160px 190px; }

  /* ---- Key details, 7 Sept -----------------------------------------------
     Nineteen fields, six of them clocks, and 7,621px of empty box — worse than
     the supplier card's 5,561. `repeat(auto-fit, minmax(240px, 1fr))` handed a
     2:00 pm the same 301px as a street address, and the four fields carrying
     `.field.full` each took a 1,245px row to show a date, a word and two
     addresses. Gee: "you should be able to compress this so that they're on
     similar lines. I don't feel like we need that much of a gap."
     Same rules as everywhere else here: real widths, justify-content: start.
     Widest row is .ready at 1,012px inside the 1,089 that `snug` leaves. */

  /* Both people, both places, one line. */
  .tight-row.ready    { grid-template-columns: 175px 310px 175px 310px; }

  /* The address, then the two times it runs between. St Paul's full address
     measures 413px; 470 holds it with room for a longer one. */
  .tight-row.ceremony { grid-template-columns: 470px 130px 130px; }

  /* Where the reception is. "Upper Level, Southgate Shopping Precinct,
     Southbank VIC 3006" measures 444px. */
  .tight-row.venue    { grid-template-columns: 250px 150px 470px; }

  /* Canapes, start, entrance, end. 130px shows "12:00 am" and the little clock
     with room to spare — they had 301 each. */
  /* Canapé start moved out to its own section in 94; guest arrival came in
     with 95. Four again, but four RECEPTION times now — guests in, reception
     starts, couple announced, carriages. */
  .tight-row.clocks   { grid-template-columns: 130px 130px 130px 130px; }
  .tight-row.canapes  { grid-template-columns: 130px 130px; }

  /* ⚠️ THE VENUE ROW HAS THREE BOXES AND BORROWED A TWO-COLUMN PRESET.
     Migration 104 added Room reveal beside the two bump times and reused
     `.canapes` for its widths — so the third box wrapped onto a second line,
     sitting alone under Venue bump-in. Gee asked twice where the room reveal
     was: it had been on the page the whole time, one line below where anyone
     would look. `.canapes` is left alone because the real canapé row still
     uses it and still has two boxes. */
  .tight-row.bump     { grid-template-columns: 130px 130px 130px; }

  /* The question and its two counts. */
  .tight-row.party    { grid-template-columns: 240px 120px 120px; }

  /* One date, sized like a date. */
  .tight-row.lock     { grid-template-columns: 190px; }
  /* The five hour fields. One dropdown that needs words, then four times,
     which are narrow — so the first column gets the room and the clocks take
     what they need. Below this width they stack rather than being squeezed
     into five columns nobody can read. */
  .tight-row.five  { grid-template-columns: 1.5fr 1fr 1.1fr 1fr 1fr; }

  /* ⚠️ A GRID KEEPS ITS COLUMNS WHETHER OR NOT ANYTHING IS IN THEM.
     syncTeamTimesFold() in suppliers.js sets this class in the same breath as
     it hides the four supplier-level time boxes, so the row cannot be emptied
     and full-width at the same time.

     Not `.five` any more: Coverage booked moved out to sit with the invoice it
     was read off (7 Sept), so every field left in this row hides together and
     the row goes entirely. Written class-agnostically so it keeps working if
     the row is renumbered again. */
  .tight-row.is-solo-hidden { display: none; }

  /* ---- The new-wedding form, 8 Sept -------------------------------------
     Gee: "How come there's space after service type and budget type? Why can't
     you put guest count and budget up there?" — so the row that held three
     boxes in five auto-fit columns now holds five real ones.

     Second pass the same day: Status joined it — "move the status up next to
     maybe budget." Six boxes now, so every width came in a notch to keep the
     row inside the 1,089px this card actually has. */
  .tight-row.setup    { grid-template-columns: 180px 210px 210px 100px 125px 150px; }

  /* A couple's names, not a paragraph. This was `.full` — a whole 1,320px row. */
  .tight-row.name     { grid-template-columns: 420px; }

  /* One partner per row: role · first · last · email · phone · Instagram. See
     the note in index.html — the per-person grouping used to depend on the
     auto-fit grid landing on exactly five columns, and at Gee's own width it
     did not. Instagram joined 20 Oct (migration 162); last name and email gave
     up 10px and 40px so the six still fit the 1,089px card (1,080 with gaps). */
  .tight-row.person   { grid-template-columns: 120px 170px 170px 240px 160px 150px; }

  /* Event date · Booked date · Planner · All day event · Start time · Venue ·
     Location. Status left this row for the top one on 8 Sept; the tick, the
     clock and the two places joined it, because Gee asked for them "after
     planner".

     ⚠️ FLEX, NOT GRID — the only row in the app that is, and for a reason.
     Splitting these across two rows left 392px dead at the end of one and
     530px at the end of the other; Gee: "I still think this can be done
     better." A grid cannot wrap, so the two rows could not be merged into one
     while Start time still appears and disappears — the seven boxes fit on one
     line only while All day is ticked. Flex wraps only what does not fit, so
     the usual state is one line and unticking the box pushes Location down
     rather than leaving a hole.

     Widths are stated per field for the same reason as everywhere else, and
     they are sized to the longest value each holds: measured with nothing
     clipped, and 44px spare on the line. */
  .tight-row.when { display: flex; flex-wrap: wrap; gap: 10px 14px; align-items: end; }
  .tight-row.when > .field { flex: 0 0 auto; }
  #wedding-modal #w-date-field     { width: 135px; }
  #wedding-modal #w-booked-field   { width: 135px; }
  #wedding-modal #w-planner-field  { width: 180px; }
  #wedding-modal #w-allday-field   { width: 125px; }
  #wedding-modal #w-time-wrap      { width: 125px; }
  #wedding-modal #w-venue-field    { width: 255px; }
  #wedding-modal #w-location-field { width: 160px; }

  /* ---- The run sheet line, 8 Sept ---------------------------------------
     Nine visible fields, EIGHT of them forced full width: 5,844px of empty box
     and 960px of height, with "Visibility" holding 1,264px to show the word
     "Everyone". The worst ratio left in the app, and the form she opens most.

     ⚠️ FLEX, like the project details row, and for the same reason: half these
     fields hide by item type — a section heading has no times and no
     Responsible — and a display:none GRID item drops its column, shifting
     everything after it left. Widths on the fields; the row only flows them. */
  #timeline-event-modal .tight-row {
    display: flex; flex-wrap: wrap; gap: 10px 14px; align-items: end;
  }
  #timeline-event-modal .tight-row > .field { flex: 0 0 auto; }

  #tl-e-type-field    { width: 230px; }
  #tl-e-time-field,
  #tl-e-end-field     { width: 130px; }
  #tl-e-vis-field     { width: 290px; }
  #tl-e-place-wrap    { width: 420px; }
  #tl-e-title-field   { width: 600px; }
  /* The style bar could not be measured signed out — its swatches are drawn
     from the business's own colours. Sized from the Notes toolbar instead,
     which has the same shape and measures 442px of content. */
  #tl-e-style-field   { width: 420px; }
  #tl-e-contact-wrap  { width: 460px; }
  #tl-e-who-wrap      { width: 320px; }
  /* The one field that genuinely wants the room. */
  #tl-e-notes-field   { width: 100%; }

  /* Gee, 8 Sept: "make the enabled features all on one line instead of on two
     separate lines, like the guests and seating to go up."
     `repeat(auto-fit, minmax(160px, 1fr))` needed 1,228px for seven boxes and
     had 1,104, so "Guests & seating" wrapped alone onto a second line. Sized by
     their own labels they need about 640 and fit with room to spare. Scoped to
     this modal — `.feature-grid` is the shared class. */
  #wedding-modal .feature-grid { grid-template-columns: repeat(7, max-content); }
}
/* Narrower than the full card, the fixed widths stop fitting. Fractions are
   right here — there is no spare room to waste, which was the whole complaint. */
@media (min-width: 720px) and (max-width: 1180px) {
  .tight-row.five   { grid-template-columns: repeat(3, 1fr); }
  .tight-row.reach  { grid-template-columns: 1fr 1.4fr 1fr; }
  .tight-row.three  { grid-template-columns: 1fr 1.6fr 1.3fr; }
  .tight-row.four   { grid-template-columns: repeat(2, 1fr); }
  .tight-row.what   { grid-template-columns: 1fr 1fr; }
  .tight-row.booked { grid-template-columns: repeat(2, 1fr); }
  .tight-row.who    { grid-template-columns: 1fr 1fr; }
  .tight-row.ways   { grid-template-columns: 1fr 1.4fr; }
  .tight-row.bank   { grid-template-columns: repeat(2, 1fr); }
  .tight-row.where-and-bank { grid-template-columns: 1fr; }

  .tight-row.ready    { grid-template-columns: 1fr 1.6fr; }
  .tight-row.ceremony { grid-template-columns: 1fr 1fr; }
  .tight-row.venue    { grid-template-columns: 1fr 1fr; }
  .tight-row.clocks   { grid-template-columns: repeat(4, 1fr); }
  .tight-row.party    { grid-template-columns: 1.4fr 1fr 1fr; }

  /* The new-wedding form. Rule 4 of the pattern: below the width the stated
     pixels fit in, fractions are right — there is no spare room to waste,
     which was the whole complaint in the first place. */
  .tight-row.setup    { grid-template-columns: repeat(3, 1fr); }
  .tight-row.name     { grid-template-columns: 1fr; }
  .tight-row.person   { grid-template-columns: repeat(3, 1fr); }
  /* Still flex, but the stated widths no longer fit — so the boxes share what
     there is and wrap as they need to. */
  #wedding-modal #w-date-field,
  #wedding-modal #w-booked-field,
  #wedding-modal #w-planner-field,
  #wedding-modal #w-allday-field,
  #wedding-modal #w-time-wrap,
  #wedding-modal #w-venue-field,
  #wedding-modal #w-location-field { width: auto; flex: 1 1 190px; }

  /* Four across rather than seven — seven would be unreadable at this width. */
  #wedding-modal .feature-grid { grid-template-columns: repeat(4, max-content); }

  /* The run sheet line, narrower: the stated widths stop fitting, so the boxes
     share what there is and wrap as they need to. Notes keeps the full width. */
  #tl-e-type-field, #tl-e-time-field, #tl-e-end-field, #tl-e-vis-field,
  #tl-e-place-wrap, #tl-e-title-field, #tl-e-style-field,
  #tl-e-contact-wrap, #tl-e-who-wrap { width: auto; flex: 1 1 200px; }
  #tl-e-notes-field { width: 100%; flex: 1 1 100%; }
}

/* ---- The run sheet line, Look A — 18 Oct ---------------------------------
   Gee: "Give me colors or something. I can't see anything. Let's make it look
   nice as well." Three looks were drawn IN the live form on her Bec & Aleks
   Marnong line; she picked A, colour panels. Each of the four rows is its own
   soft panel, its name in the panel's colour, labels in that colour, boxes
   kept WHITE so a filled box still looks different from an empty one.

   ⚠️ THE NAMES ARE ::before ON THE ROW, so no markup moved and nothing in
   timeline.js changed. A row is flex, so the name takes the whole first line.
   ⚠️ A ROW WITH EVERY FIELD HIDDEN IS HIDDEN TOO. A heading or a text line has
   no Responsible, so the Who row would draw as an empty grape panel.
   ⚠️ SCOPED TO THIS MODAL ONLY. Supplier, task and budget forms are unchanged
   until she says otherwise. */
/* TIGHTER, 18 Oct — Gee: "I think it's a bit too spacious." Tried in the live
   form first: 886px of content became 726px and the whole form fits one screen
   with Save. The section name sits ON the panel's top edge as a tab instead of
   taking a line; Notes says its name once (its own label, styled as the tab
   would be); Documents' "Email" box and Choose Files sit side by side. */
#timeline-event-modal .modal-body { display: grid; gap: 18px; padding-top: 22px; padding-bottom: 16px; }
#timeline-event-modal .modal-body > .tight-row {
  --sec: var(--v-sky); --sec-tint: var(--c-sky-tint);
  position: relative;
  margin: 0; padding: 14px 16px 12px; row-gap: 6px; border-radius: 16px;
  background: var(--sec-tint);
  border: 2px solid color-mix(in srgb, var(--sec) 20%, transparent);
}
#timeline-event-modal .modal-body > .tl-e-what      { --sec: var(--v-magenta); --sec-tint: var(--pink-tint); }
#timeline-event-modal .modal-body > .tl-e-who-row   { --sec: var(--v-grape);   --sec-tint: var(--c-indigo-tint); }
#timeline-event-modal .modal-body > .tl-e-notes-row { --sec: #00a68f;          --sec-tint: var(--c-teal-tint); }
#timeline-event-modal .modal-body > .tight-row::before {
  position: absolute; top: -10px; left: 14px;
  padding: 1px 10px; border-radius: 999px; line-height: 1.4;
  background: var(--sec-tint);
  border: 2px solid color-mix(in srgb, var(--sec) 20%, transparent);
  color: var(--sec); font-weight: 800; font-size: .78rem;
  letter-spacing: .08em; text-transform: uppercase;
}
/* Notes is the one row whose first label already says its name. */
#timeline-event-modal .modal-body > .tl-e-notes-row::before { content: none; }
#timeline-event-modal #tl-e-notes-label {
  font-size: .78rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
}
#timeline-event-modal #tl-e-files-field .s-contract-add {
  display: flex; flex-wrap: wrap; gap: 8px 12px; align-items: center;
}
#timeline-event-modal #tl-e-files-field .s-contract-add > * { margin: 0; }
#timeline-event-modal #tl-e-files-field .s-contract-add select { width: auto; min-width: 200px; }
#timeline-event-modal .tl-e-when::before      { content: "When & where"; }
#timeline-event-modal .tl-e-what::before      { content: "What happens"; }
/* Gee, 18 Oct: "line Item style up beside the Event box." The row lines boxes
   up by their BOTTOMS, and the style bar is two swatch rows tall, so its label
   sat 41px above "Event *". Tops instead: both labels on one line, the Event
   box level with the first row of swatches. Measured live before building. */
#timeline-event-modal .modal-body > .tl-e-what { align-items: flex-start; }
#timeline-event-modal .tl-e-who-row::before   { content: "Who"; }
#timeline-event-modal .modal-body > .tight-row:not(:has(> .field:not([hidden]))) { display: none; }
#timeline-event-modal .modal-body > .tight-row .field > label:not(.check-inline) {
  color: var(--sec); font-weight: 700;
}
#timeline-event-modal .modal-body > .tight-row input:not([type="checkbox"]),
#timeline-event-modal .modal-body > .tight-row select,
#timeline-event-modal .modal-body > .tight-row .rt-editor {
  background: #fff;
  border-color: color-mix(in srgb, var(--sec) 40%, transparent);
}
@media (min-width: 1181px) {
  /* Narrower than the 420 and 290 above, so Visibility sits on the times'
     line even on a time span, with the place's description folded away. */
  #tl-e-place-wrap { width: 300px; }
  #tl-e-vis-field  { width: 200px; }
}

/* ---- The budget line, the same look — 18 Oct -----------------------------
   Gee: "This page is a real shit layout. It should be more compressed… You
   don't need that big of a space for the due date." Measured first: every box
   was 302px whatever it held ("Cash", "$850"), and the window was 1,246px of
   content in a 576px view. Mocked up in the live window and she said "that's
   so much nicer": 924px. Six panels named on their top edge, as the run sheet
   line above. Fields keep their ids; the wrappers are .b-panel in index.html.
   ⚠️ SCOPED TO #budget-modal. Widths are real pixels with justify-content:
   start (tight-row-layout-pattern) and only on a wide screen — under 900px
   every field takes the line, as before. */
#budget-modal .modal-box.full { width: 1100px; max-width: calc(100vw - 40px); }
#budget-modal .modal-body { display: grid; gap: 20px; padding-top: 24px; }
#budget-modal .b-panel {
  --sec: var(--v-sky); --sec-tint: var(--c-sky-tint);
  position: relative; margin: 0; padding: 16px 16px 12px; border-radius: 16px;
  background: var(--sec-tint);
  border: 2px solid color-mix(in srgb, var(--sec) 20%, transparent);
  display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: flex-start; justify-content: start;
}
#budget-modal .b-panel::before {
  content: attr(data-name);
  position: absolute; top: -11px; left: 14px;
  padding: 1px 10px; border-radius: 999px; line-height: 1.4;
  background: var(--sec-tint);
  border: 2px solid color-mix(in srgb, var(--sec) 20%, transparent);
  color: var(--sec); font-weight: 800; font-size: .78rem;
  letter-spacing: .08em; text-transform: uppercase;
}
#budget-modal .b-p-money { --sec: var(--v-magenta); --sec-tint: var(--pink-tint); }
#budget-modal .b-p-paper { --sec: #00a68f;          --sec-tint: var(--c-teal-tint); display: block; }
#budget-modal .b-p-pay   { --sec: var(--v-grape);   --sec-tint: var(--c-indigo-tint); }
#budget-modal .b-p-talk  { --sec: #e8590c;          --sec-tint: #fff1e6; display: block; }
#budget-modal .b-p-sched { display: block; }
#budget-modal .b-panel .field { margin: 0; }
#budget-modal .b-panel .field > label:not(.check-inline) { color: var(--sec); font-weight: 700; }
#budget-modal .b-panel input:not([type="checkbox"]),
#budget-modal .b-panel select,
#budget-modal .b-panel textarea {
  background: #fff;
  border-color: color-mix(in srgb, var(--sec) 40%, transparent);
}
/* The three ticks on one line; their explanations already fold behind a ?. */
#budget-modal .b-p-money > .field.full:not(:has(.money-row)) {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 18px;
}
#budget-modal .b-p-money > .field.full:not(:has(.money-row)) .check-inline { margin: 0; }
/* Measured live: .field is flex-direction COLUMN elsewhere in this file, so
   "No invoice needed" sat under "Invoice on file". A row, and the two ticks
   never need to wrap. */
#budget-modal .b-p-money > .field.full:not(:has(.money-row)) { flex-direction: row; }
#budget-modal .b-p-money > .field.full:has(#b-no-invoice) { flex-wrap: nowrap; }
#budget-modal .b-p-money > .field.full:not(:has(.money-row)) > .hint { display: none; }
#budget-modal #b-pay-why-wrap { flex: 0 0 100%; }
#budget-modal #b-pay-why { max-width: 420px; }
#budget-modal .money-row { display: flex; flex-wrap: wrap; gap: 8px 14px; justify-content: start; }
/* Paperwork: the empty "No invoice yet" box on one line, not 152px tall. */
#budget-modal .b-p-paper .b-files { margin: 0; }
#budget-modal .b-p-paper .bf-drop {
  flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: start;
  gap: 6px 14px; padding: 8px 12px; min-height: 0; text-align: left;
}
#budget-modal .b-p-paper .bf-drop-sub { max-width: 46ch; }
#budget-modal .b-p-paper .bf-drop .btn { padding: 6px 16px; margin: 0; }
/* Bank transfer and Pay by card side by side, without their own boxes. */
#budget-modal .b-p-pay > #b-bank-hint { flex: 0 0 100%; margin: 0; }
#budget-modal .b-p-pay .pay-way { margin: 0; border: 0; background: none; padding: 0; }
#budget-modal .b-p-pay .pay-way-head { padding: 0 0 4px; background: none; color: var(--sec); font-weight: 700; }
#budget-modal .b-p-pay .pay-way-body { padding: 0; }
#budget-modal .b-p-pay .pay-way .form-grid { display: flex; flex-wrap: wrap; gap: 8px 10px; }
#budget-modal .b-p-pay .pay-way .hint { display: none; }
#budget-modal .b-p-sched > h4 { display: none; }
#budget-modal .b-p-sched .pay-foot { margin-top: 4px; }
#budget-modal .b-p-talk .comment-list { min-height: 0; padding: 0; margin: 0 0 8px; background: none; }
#budget-modal .b-p-talk .comment-list .empty { padding: 2px 0; margin: 0; text-align: left; font-size: .85rem; }
@media (min-width: 900px) {
  #budget-modal .b-p-line > .field:nth-child(1) { width: 230px; }
  #budget-modal .b-p-line > .field:nth-child(2) { width: 120px; order: 4; }
  #budget-modal .b-p-line > .field:nth-child(3) { width: 290px; }
  #budget-modal .b-p-line > .field:nth-child(4) { width: 260px; }
  #budget-modal .b-p-money > .field.full:has(.money-row) { flex: 0 0 100%; }
  #budget-modal .money-row > .field { width: auto; }
  #budget-modal #b-estimate { width: 120px; }
  #budget-modal #b-invoiced { width: 150px; }
  #budget-modal #b-due      { width: 165px; }
  #budget-modal .money-row > .field:has(#b-terms-pick) {
    display: grid; grid-template-columns: 190px 280px; column-gap: 8px;
  }
  #budget-modal .money-row > .field:has(#b-terms-pick) > label { grid-column: 1 / -1; }
  #budget-modal .b-p-pay .pay-way .form-grid > .field { width: auto; }
  #budget-modal #b-acct-name { width: 190px; }
  #budget-modal #b-bsb       { width: 100px; }
  #budget-modal #b-acct-no   { width: 130px; }
  #budget-modal #b-payid     { width: 170px; }
  #budget-modal #b-pay-link  { width: 300px; }
}
/* ---- The supplier card, the same look — 18 Oct ----------------------------
   Gee said yes to the mock-up drawn in the live card on Oguzhan & Amal's Focus
   Films (1,183px → 1,091px — mostly easier to read; the card was already
   tightened in September and keeps its 1320px for its widest row). Five
   panels, wrappers in index.html. ⚠️ THE GREY HINTS STAY: the mock-up hid
   three that are NOT behind a ?, and a hint is the difference between a field
   she can fill and one she has to ask about (hints-already-fold-behind-a-?).
   ⚠️ Teams, Who to call and the couple's messages already carry their own
   heading (and its ? sits inside it), so those panels use THAT heading as the
   tab instead of a second name on the edge. */
#supplier-modal .modal-body > .form-grid { gap: 18px 14px; padding-top: 10px; }
#supplier-modal .s-panel {
  --sec: var(--v-sky); --sec-tint: var(--c-sky-tint);
  grid-column: 1 / -1; position: relative; margin: 0;
  padding: 14px 14px 10px; border-radius: 16px;
  background: var(--sec-tint);
  border: 2px solid color-mix(in srgb, var(--sec) 20%, transparent);
  display: grid; gap: 6px; min-width: 0;
}
/* Measured live after the first ship: 1,286px — TALLER than the 1,183 it
   replaced, because the hints stayed and the three panels with their own
   heading spent a line on it. Their heading now IS the tab on the panel's
   edge (its ? inside it still opens the hint — pressed on Teams): 1,134px. */
#supplier-modal .s-p-crews > .field:first-child > h4.form-section,
#supplier-modal .s-p-call > .field:first-child > h4.form-section,
#supplier-modal .s-p-talk > label {
  position: absolute; top: -11px; left: 14px; margin: 0 !important; z-index: 1;
  padding: 1px 10px; border-radius: 999px; line-height: 1.4;
  background: var(--sec-tint);
  border: 2px solid color-mix(in srgb, var(--sec) 20%, transparent);
  color: var(--sec); font-weight: 800; font-size: .78rem;
  letter-spacing: .08em; text-transform: uppercase;
}
#supplier-modal .s-panel::before {
  content: attr(data-name);
  position: absolute; top: -11px; left: 14px;
  padding: 1px 10px; border-radius: 999px; line-height: 1.4;
  background: var(--sec-tint);
  border: 2px solid color-mix(in srgb, var(--sec) 20%, transparent);
  color: var(--sec); font-weight: 800; font-size: .78rem;
  letter-spacing: .08em; text-transform: uppercase;
}
#supplier-modal .s-p-money { --sec: var(--v-magenta); --sec-tint: var(--pink-tint); }
#supplier-modal .s-p-crews { --sec: var(--v-grape);   --sec-tint: var(--c-indigo-tint); }
#supplier-modal .s-p-call  { --sec: #00a68f;          --sec-tint: var(--c-teal-tint); }
#supplier-modal .s-p-talk  { --sec: #e8590c;          --sec-tint: #fff1e6; }
#supplier-modal .s-p-crews::before,
#supplier-modal .s-p-call::before,
#supplier-modal .s-p-talk::before { content: none; }
#supplier-modal .s-panel .form-section,
#supplier-modal .s-p-talk > label { color: var(--sec); }
#supplier-modal .s-panel .field { margin: 0; }
#supplier-modal .s-panel .tight-row { margin: 0; }
#supplier-modal .s-panel label:not(.check-inline) { color: var(--sec); font-weight: 700; }
#supplier-modal .s-panel input:not([type="checkbox"]):not([type="file"]),
#supplier-modal .s-panel select,
#supplier-modal .s-panel textarea {
  background: #fff;
  border-color: color-mix(in srgb, var(--sec) 40%, transparent);
}
/* The contract "kind" picker was 1,170px wide for one word. */
#supplier-modal #s-contract-kind { width: 220px; display: inline-block; }
#supplier-modal .s-p-talk .comment-list li.comment.is-empty { padding: 0; min-height: 0; font-size: .85rem; }
@media (min-width: 1181px) {
  /* The green Budget / Paid by / Files box laid out wide: four lines → two. */
  /* Still five columns: "Who's paying" appears once Not the couple's budget
     is ticked (the rule this narrows, ~line 1972). */
  #supplier-modal .tight-row.booked { grid-template-columns: 150px 260px 220px minmax(0, 1fr) auto; }
  #supplier-modal .tight-row.booked > .field:nth-child(4) { width: auto; }
  #supplier-modal .tight-row.booked .sbh-panel { display: flex; flex-wrap: wrap; gap: 6px 18px; align-items: center; width: auto; }
  #supplier-modal .tight-row.booked .sbh-row { display: flex; gap: 8px; align-items: center; width: auto; }
  #supplier-modal .tight-row.booked .sbh-row select { width: auto; }
}

/* ---- Budget and Suppliers tables: frozen names, less gap — 18 Oct ---------
   Gee: "I don't love that I've got all that gap in between when it goes
   across the screen. It would be nice if the pane was frozen because you
   can't see… who you're looking at." Measured: the budget's Supplier column
   was 698px (nowrap, sized by its longest row) in a 1,723px table inside a
   1,168px page. Mocked up live, then "just fix everything".
   - Both tabs get more of the screen (the page stays 1,220 everywhere else).
   - Budget: Supplier is 470px and pinned left; long "for what" notes trim
     with … ; the category heading (.bg-cat-in) and the subtotal label pin too.
   - Suppliers: Role and Supplier pinned left (170 + 220).
   ⚠️ THE COUPLE'S BUDGET GETS THE FROZEN NAMES TOO. Their portal shows this
   same #wtab-budget, moved (§36.1) — checked in "Preview as couple" on
   18 Oct: names pinned, the page itself stays its normal width. Wide screens
   only, so a phone is unchanged. */
@media (min-width: 1181px) {
  main.wrap:has(#wtab-budget:not([hidden])),
  main.wrap:has(#wtab-suppliers:not([hidden])) { max-width: min(1600px, calc(100vw - 40px)); }

  #wtab-budget .bg-table thead th:first-child,
  #wtab-budget .bg-table td.bg-supplier,
  #wtab-budget .bg-table tr.bg-subtotal > td:first-child {
    position: sticky; left: 0; z-index: 2;
    background: var(--surface, #fff);
    box-shadow: 6px 0 6px -6px rgba(80, 30, 60, .3);
  }
  #wtab-budget .bg-table tr.bg-subtotal > td:first-child { background: var(--pink-tint); }
  #wtab-budget .bg-table td.bg-supplier { white-space: normal; width: 470px; min-width: 470px; max-width: 470px; }
  #wtab-budget .bg-table td.bg-supplier .bg-name { white-space: nowrap; }
  #wtab-budget .bg-table td.bg-supplier .bg-forwhat {
    display: inline-block; max-width: 150px; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom;
  }
  #wtab-budget .bg-table tr.bg-cat .bg-cat-in { display: inline-block; position: sticky; left: 10px; }

  #wtab-suppliers .sup-table .sup-c-role {
    position: sticky; left: 0; z-index: 2; background: var(--surface, #fff);
    width: 170px; min-width: 170px; max-width: 170px;
  }
  #wtab-suppliers .sup-table .sup-c-name {
    position: sticky; left: 170px; z-index: 2; background: var(--surface, #fff);
    width: 220px; min-width: 220px; max-width: 220px;
    box-shadow: 6px 0 6px -6px rgba(80, 30, 60, .3);
  }
}

/* 18 Oct, later — Gee: "it doesn't look like you did the freeze… on the
   left-hand side." The freeze above only switched on at 1181px and wider,
   and it was marked by a faint shadow alone. Her screen could not be read
   (her tab did not answer), so both causes are covered:
   - FROM 761px UP, with the frozen width shrinking on a smaller screen
     (a 470px column would eat a laptop), so any screen that scrolls sideways
     keeps the names;
   - A SOLID PINK EDGE down the frozen column, so it reads as frozen at a
     glance, like the line Excel draws under Freeze Panes. */
@media (min-width: 761px) {
  #wtab-budget .bg-table thead th:first-child,
  #wtab-budget .bg-table td.bg-supplier,
  #wtab-budget .bg-table tr.bg-subtotal > td:first-child,
  #wtab-suppliers .sup-table .sup-c-role,
  #wtab-suppliers .sup-table .sup-c-name {
    position: sticky; z-index: 2; background: var(--surface, #fff);
  }
  #wtab-budget .bg-table thead th:first-child,
  #wtab-budget .bg-table td.bg-supplier,
  #wtab-budget .bg-table tr.bg-subtotal > td:first-child,
  #wtab-suppliers .sup-table .sup-c-role { left: 0; }
  #wtab-budget .bg-table tr.bg-subtotal > td:first-child { background: var(--pink-tint); }
  #wtab-budget .bg-table thead th:first-child,
  #wtab-budget .bg-table td.bg-supplier,
  #wtab-budget .bg-table tr.bg-subtotal > td:first-child,
  #wtab-suppliers .sup-table .sup-c-name {
    /* 19 Oct — Gee: "the pink is a bit overpowering… the line should be a
       different color". SKY, her pick from three drawn on Tiffany's pages
       (sky · mint · lavender). Mint ran into the green stage chips. The
       budget's pink total rows were left pink, as she said. CHECK 202. */
    box-shadow: inset -3px 0 0 var(--v-sky, #0091ea), 6px 0 8px -6px rgba(80, 30, 60, .35);
  }
  #wtab-budget .bg-table td.bg-supplier,
  #wtab-budget .bg-table thead th:first-child {
    white-space: normal; width: min(470px, 34vw); min-width: min(470px, 34vw); max-width: min(470px, 34vw);
  }
  #wtab-budget .bg-table td.bg-supplier .bg-name { white-space: nowrap; }
  #wtab-budget .bg-table td.bg-supplier .bg-forwhat {
    display: inline-block; max-width: 150px; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom;
  }
  #wtab-budget .bg-table tr.bg-cat .bg-cat-in { display: inline-block; position: sticky; left: 10px; }
  #wtab-suppliers .sup-table .sup-c-role { width: 150px; min-width: 150px; max-width: 150px; }
  #wtab-suppliers .sup-table .sup-c-name { left: 150px; width: 200px; min-width: 200px; max-width: 200px; }
}
@media (min-width: 1181px) {
  #wtab-suppliers .sup-table .sup-c-role { width: 170px; min-width: 170px; max-width: 170px; }
  #wtab-suppliers .sup-table .sup-c-name { left: 170px; width: 220px; min-width: 220px; max-width: 220px; }
}

/* ---- The obvious how-to notes, gone — 18 Oct ------------------------------
   Gee, on the supplier card: "Put their Instagram handle, put their
   website… All of that muffle stuff doesn't really have to be there… I know
   that an email address needs to get added… That's just like children."
   Swept every window (supplier, budget line, to-do, wedding, contact) for grey
   notes that are ALWAYS on show: five were how-to sentences. Two were deleted
   from index.html, the hours one from suppliers.js; these two are written by
   suppliers.js on every open, so they are switched off here rather than torn
   out of code that still sets them.
   KEPT: lines that say something about THIS row — "On the supplier team.",
   "Nothing filed against this supplier yet.", "… has no bank details saved
   yet…", "Confirmed 12 Oct…". Notes already folded behind a ? are untouched.
   NOT TOUCHED: Key details' note ("Every place this wedding touches…") — it is
   the run sheet's window and she said leave the run sheet alone. */
#s-location-hint,
#s-solo-note,
#s-coverage-confirmed:empty { display: none !important; }

/* §124.9 — the second sweep, her yes to "the 21": every page and window, and
   the empty "new" version of each window. Switched off here (still written by
   code): the supplier arrival and coverage how-tos, and the 11 Settings
   section intros. Backup's intro is kept on purpose (#set-backup-intro: "no
   undo" is a warning). The rest were taken out of the code itself:
   suppliers.js (one-crew line, "usually filled in the day before"),
   checklists.js (due-date note, "star the few things…"), budget.js ("No
   supplier linked…"), activity.js ("Changes will show up here…"),
   weddingfiles.js (the Files paragraph), index.html (Client portal intro).
   LEFT ON HER SAY ("your call"): the findings group lines, the on-hold
   reason note, Backup's warning, the long findings explanations. */
#s-arrival-hint,
#s-coverage-hint,
#module-settings .set-intro { display: none !important; }
#b-bank-hint:empty,
#s-driver-note:empty { display: none; }

/* ---- Findings cards: "Not now" beside the card, not under it — 18 Oct -----
   Gee: "We don't need that much gap on the bottom next to the 'not now'."
   Every card spent a whole line on a full-width Not now bar. Now it sits on
   the right of the card's first line. Measured on Oguzhan & Amal: Overview
   cards 95 → 60px (list 1,685 → 1,164); the Budget box 1,510 → 1,270.
   🛑 NOT THE RUN SHEET'S BOX (#checks-timeline) — she asked for the run sheet
   to be left alone. Same cards, same words, same menu. */
@media (min-width: 761px) {
  #checks .check {
    grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; row-gap: 4px;
  }
  #checks .check > .check-body  { grid-column: 1; grid-row: 1; }
  #checks .check > .check-later { grid-column: 2; grid-row: 1; width: auto; justify-self: end; position: relative; }
  #checks .check > .check-goto  { grid-column: 3; grid-row: 1; }

  :is(#checks-budget, #checks-checklist, #checks-suppliers) .rsr-card:has(> .check-later) {
    display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 14px; align-items: center;
    padding-top: 8px; padding-bottom: 8px; row-gap: 2px;
  }
  :is(#checks-budget, #checks-checklist, #checks-suppliers) .rsr-card > .rsr-line,
  :is(#checks-budget, #checks-checklist, #checks-suppliers) .rsr-card > .rsr-body { grid-column: 1; }
  :is(#checks-budget, #checks-checklist, #checks-suppliers) .rsr-card > .check-later {
    grid-column: 2; grid-row: 1 / span 2; width: auto; margin: 0; position: relative;
  }

  :is(#checks, #checks-budget, #checks-checklist, #checks-suppliers) .check-later > summary { width: auto; white-space: nowrap; }
  :is(#checks, #checks-budget, #checks-checklist, #checks-suppliers) .check-later > .check-later-menu { right: 0; left: auto; }
}

/* ---- The to-do window, in panels — 18 Oct --------------------------------
   Gee: "That's a mess… terrible layout." Mocked up live, then "just fix
   everything". 1,320 → 1,060px wide; six panels (wrappers in index.html):
   The to-do · Who and when · Notes · Email to send · Task style · With the
   couple. Every hint and ? kept. Where a panel's first field already has its
   own heading (Notes, Email to send, Task style — the last two carry a ?),
   that heading IS the tab, as on the supplier card.
   ⚠️ A panel whose fields are all hidden hides itself: the email, style,
   suggestions, answer and comments boxes are each hidden until needed. */
#task-modal .modal-box.full { width: 1060px; max-width: calc(100vw - 40px); }
#task-modal .modal-body > .form-grid { gap: 20px 14px; padding-top: 10px; }
#task-modal .tk-panel {
  --sec: var(--v-sky); --sec-tint: var(--c-sky-tint);
  grid-column: 1 / -1; position: relative; margin: 0;
  padding: 14px 14px 10px; border-radius: 16px;
  background: var(--sec-tint);
  border: 2px solid color-mix(in srgb, var(--sec) 20%, transparent);
  display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: flex-start; min-width: 0;
}
#task-modal .tk-panel:not(.field):not(:has(> .field:not([hidden]))) { display: none; }
#task-modal .tk-panel::before,
#task-modal .tk-notes > .field > label:first-child,
#task-modal .tk-email > label:first-child,
#task-modal .tk-look > #t-style-wrap > label:first-child {
  position: absolute; top: -11px; left: 14px; margin: 0; z-index: 1;
  padding: 1px 10px; border-radius: 999px; line-height: 1.4;
  background: var(--sec-tint);
  border: 2px solid color-mix(in srgb, var(--sec) 20%, transparent);
  color: var(--sec); font-weight: 800; font-size: .78rem;
  letter-spacing: .08em; text-transform: uppercase;
}
#task-modal .tk-panel::before { content: attr(data-name); }
#task-modal .tk-notes::before,
#task-modal .tk-email::before,
#task-modal .tk-look:has(> #t-style-wrap:not([hidden]))::before { content: none; }
#task-modal .tk-look::before { content: "Suggestions"; }
#task-modal .tk-when   { --sec: var(--v-magenta); --sec-tint: var(--pink-tint); }
#task-modal .tk-notes  { --sec: #00a68f;          --sec-tint: var(--c-teal-tint); }
#task-modal .tk-email  { --sec: var(--v-grape);   --sec-tint: var(--c-indigo-tint); }
#task-modal .tk-look   { --sec: #7a5af8;          --sec-tint: #f1edff; align-items: center; }
#task-modal .tk-couple { --sec: #e8590c;          --sec-tint: #fff1e6; }
#task-modal .tk-panel .field { margin: 0; }
#task-modal .tk-panel > .field.full { flex: 1 1 100%; }
#task-modal .tk-look > .field.full { flex: 0 1 auto; }
#task-modal .tk-panel label:not(.check-inline) { color: var(--sec); font-weight: 700; }
#task-modal .tk-panel input:not([type="checkbox"]):not([type="file"]),
#task-modal .tk-panel select,
#task-modal .tk-panel textarea {
  background: #fff;
  border-color: color-mix(in srgb, var(--sec) 40%, transparent);
}
/* Email to send: To and Subject side by side. */
#task-modal .tk-email.field { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 12px; }
#task-modal .tk-email > :not(input) { grid-column: 1 / -1; }
@media (min-width: 900px) {
  #task-modal .tk-todo > .field:nth-of-type(2) { width: 300px; }
  #task-modal .tk-todo > .field:nth-of-type(3) { width: 340px; }
  #task-modal .tk-todo > .field:nth-of-type(4) { width: 180px; }
  /* 21 Oct: the names take the line, the date sits small at the end of it. */
  #task-modal .tk-when > .field:nth-of-type(1) { flex: 1 1 520px; min-width: 0; }
  #task-modal .tk-when > .tk-due { flex: 0 0 auto; }
}

/* ---- Who and when, tidied — 21 Oct ----------------------------------------
   Gee: "This layout's a bit stupid… The 'Type a name' and 'Enter' can be
   smaller… the due date can be smaller and a bit more in alignment."
   One line: the names (a lit button = on the to-do), a chip only for a name
   with no button, then a small type box. The date is the same height as the
   names, so the two read as one row. "For the couple" (Remind couple ·
   Recommend suppliers · Add saved link) moved up here out of Task style.
   CHECK 216. */
#task-modal .who-line { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; }
#task-modal .who-line > .chips:empty { display: none; }
#task-modal .who-line > .chips { margin: 0; }
#task-modal .who-line > .quick-names { display: contents; }
#task-modal .who-line > #t-assignee {
  width: 160px; flex: 0 0 160px; margin: 0;
  padding: 3px 10px; height: 28px; min-height: 0; font-size: .8rem; border-radius: 20px;
}
/* margin-top: measured 21 Oct, the date's top sat 6px above the names'
   (the "Assigned to ?" label is taller than "Due date"). */
#task-modal .tk-due > #t-due {
  width: 140px; height: 28px; min-height: 0; padding: 2px 8px; font-size: .82rem; margin-top: 6px;
}
#task-modal .tk-for-couple {
  border-top: 1px dashed color-mix(in srgb, var(--sec) 35%, transparent);
  padding-top: 10px;
}
#task-modal .tk-for-couple > label { display: block; margin-bottom: 6px; }
#task-modal .tk-for-couple .rcm-box { margin: 0; }
@media (max-width: 899px) {
  #task-modal .tk-panel > .field { flex: 1 1 100%; }
  #task-modal .tk-email.field { grid-template-columns: 1fr; }
}

/* ---- Weddings list, smaller — 18 Oct --------------------------------------
   Gee: "a bit too big, and it's a bit hard to scroll through… I don't care
   about the 'how many tasks to do'… I've never even looked at that."
   Measured: cards 249px, three across, page 2,464px. Now four across, no task
   count or progress bar, no "In progress" pill (every live wedding says it);
   date, venue, countdown and the red "overdue" chip stay. ~1,364px.
   And "Arrived by email" is one quiet line while nothing is waiting
   (.is-quiet, set in inbox.js) — the red box comes back when something lands. */
@media (min-width: 1181px) {
  #weddings-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
}
#weddings-grid .wedding-card { padding: 14px 16px; }
#weddings-grid .wedding-card .card-name { padding: 9px 16px; margin: -14px -16px 8px; }
#weddings-grid .wedding-card .card-rows { margin-top: 0; gap: 3px; }
#weddings-grid .wedding-card .pill.status-booked,
#weddings-grid .wedding-card .card-progress .progress-bar,
#weddings-grid .wedding-card .card-progress-meta > span:not(.overdue-pill) { display: none; }
#weddings-grid .wedding-card .card-progress { margin: 0; padding: 0; min-height: 0; }
#weddings-grid .wedding-card .card-progress:not(:has(.overdue-pill)) { display: none; }
#invoice-inbox.is-quiet { padding: 8px 16px; margin-bottom: 16px; background: var(--surface, #fff); border-color: #f3c6d6; }
#invoice-inbox.is-quiet .inbox-head { margin: 0; }
#invoice-inbox.is-quiet .inbox-aside-toggle { display: none; }
#invoice-inbox.is-quiet .inbox-aside:has(> .inbox-aside-list[hidden]) { display: none; }

/* ---- Settings: "On this page" down the right — 18 Oct ---------------------
   27,637px of Settings with nothing past the left two-thirds. The list is
   drawn by drawSettingsNav() in settings.js; it stays in view as you scroll.
   Wide screens only — on a narrow one it would push the settings down. */
#set-nav { display: none; }
@media (min-width: 1181px) {
  #module-settings:has(> #set-nav) {
    display: grid; grid-template-columns: minmax(0, 1fr) 230px; column-gap: 40px; align-items: start;
  }
  #module-settings:has(> #set-nav)[hidden] { display: none; }
  #module-settings > :not(#set-nav) { grid-column: 1; }
  #set-nav {
    grid-column: 2; grid-row: 1 / span 200; position: sticky; top: 16px;
    display: grid; gap: 2px; padding: 14px; border-radius: 16px;
    background: var(--pink-tint);
    border: 2px solid color-mix(in srgb, var(--v-magenta) 20%, transparent);
  }
  #set-nav .sn-t { font-weight: 800; font-size: .75rem; letter-spacing: .08em; text-transform: uppercase; color: var(--v-magenta); margin-bottom: 4px; }
  #set-nav button {
    all: unset; cursor: pointer; color: var(--ink); padding: 4px 8px; border-radius: 8px; font-size: .9rem;
  }
  #set-nav button:hover { background: #fff; }
  #set-nav button:focus-visible { outline: 2px solid var(--v-magenta); outline-offset: 1px; }
}

@media (max-width: 899px) {
  #budget-modal .b-panel > .field, #budget-modal .b-p-pay .pay-way { flex: 1 1 100%; }
  #budget-modal .money-row > .field { flex: 1 1 140px; }
}

/* Anything marked .full still spans the whole row — those are the fields that
   genuinely need the width (notes, the roles grid, the payment schedule). */
.modal-box.full .field.full,
.modal-box.xwide .field.full { grid-column: 1 / -1; }

/* The payment schedule is the reason the budget box is wider — the extra
   width is the whole fix. The scroller stays as overflow-x:auto: it shows a
   bar only when the table genuinely does not fit, so on a wide window it is
   invisible and on a laptop it still saves the layout. Forcing it visible
   would push the entire page sideways on a narrow screen instead. */

@media (max-width: 780px) {
  .modal-box.full { min-height: 0; }
  .modal:has(> .modal-box.full) { padding: 5vh 16px; align-items: flex-start; }
  .modal-box.full .modal-body { overflow-y: visible; padding: 22px; }

  /* ⚠️ AND THE BOX HAS TO LET GO OF ITS HEIGHT, OR THE LINE ABOVE IS A
     GUILLOTINE. This is the single worst bug the app has had.

     Gee, 11 September, on an iPhone and a Samsung: "when I go to Key Run Sheet
     Details, Getting Ready, Canapés Time, and Reception Time, I can't move
     down. It doesn't give me the down option, so I can't view the full screen
     on my phone… That's a huge issue and a huge bug."

     Measured at 375×812 before this rule existed:

         .modal-box   max-height 779px · overflow hidden
         content      1948px

     1,168 pixels of the form — SIXTY PERCENT — simply unreachable. Not
     scrolled off: clipped, with nothing to scroll.

     The mobile plan was right and half-finished. Turning the BODY's scrolling
     off is deliberate: the whole overlay is meant to scroll instead, which is
     why the rule above sets `align-items: flex-start`. But the box still
     carried `max-height: 96vh` and `overflow: hidden` from the DESKTOP rules,
     where they are correct precisely because the body scrolls there. With the
     body no longer scrolling, a cap on the box has nothing behind it.

     ⚠️ THE ID LIST IS NOT DECORATION. `max-height: 96vh` is set by
     `#timeline-details-modal .modal-box.full` and friends — 1,2,0. A media
     query adds no specificity, so a plain `.modal-box.full { max-height: none }`
     LOSES to it and changes nothing. The same six ids have to be named again.

     Proven at 375px before shipping: box height 1948 = full content, nothing
     clipped, overlay scrollHeight 2029 against a 812 viewport. */
  #wedding-modal .modal-box.full.snug,
  #timeline-event-modal .modal-box.full.snug,
  #contact-modal .modal-box.full,
  #timeline-details-modal .modal-box.full,
  #budget-modal .modal-box.full,
  #supplier-modal .modal-box.full,
  .modal-box.full {
    max-height: none;
    overflow-y: visible;
  }

  /* ---- AND SOMETHING TO GRAB ON ANYTHING THAT GOES SIDEWAYS ----------------
     Gee: "Anything that goes across the page needs to have the arrow and the
     line so I can move it."

     A touch browser hides a scrollbar until you are already scrolling, which
     is no use for telling you there is somewhere to scroll TO. On a phone
     these get a permanent bar: the line is the affordance. `.table-scroll`
     already fades its right edge; this is the other half. */
  /* The bar itself — colour, thickness, always-visible on a phone — now lives
     in ONE place for every screen size: "ONE MOVING LINE, EVERYWHERE" in
     checks.css, soft plum, 27 September. It was grey here and teal on the top
     bar, which is exactly the inconsistency Gee pointed at. */
}

.bg-summary {
  display: flex;
  align-items: center;
  gap: 26px;
  flex-wrap: wrap;
  padding: 15px 18px;
  margin-bottom: 18px;
  border: 1px solid var(--line);
  border-top: 3px solid var(--teal);
  border-radius: var(--radius);
}

.bg-stat { display: flex; flex-direction: column; gap: 2px; }

.bg-stat .k {
  color: var(--teal);
  font-size: .67rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
}

.bg-stat .v { font-size: 1.05rem; font-weight: 600; }
.bg-stat.is-balance .v { color: var(--pink); }
.bg-stat-flag { margin-left: 4px; }
.bg-summary > .btn:last-child { margin-left: auto; }

.table-scroll { width: 100%; overflow-x: auto; }

.bg-table {
  width: 100%;
  border-collapse: collapse;
  font-size: .87rem;
}

.bg-table thead th {
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
  color: var(--teal);
  font-size: .67rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
  text-align: left;
  white-space: nowrap;
}

.bg-table td, .bg-table th { padding: 8px 10px; }

/* JUST SUPPLIERS. Gee, 7 Sept: "I don't want to remove the supplier type. I
   want to have an option to press a button to just have the suppliers there...
   category headings where we can then have them there or not have them there."

   So it is a toggle, not a deletion — and it hides rather than rebuilds. The
   rows are still written, still counted and still correct; they are simply not
   drawn. That means pressing it twice cannot lose anything, and the totals it
   is hiding are the same totals it shows again.

   It rides on the existing "due date order" path, which already collapses the
   whole budget into one band — so the only thing left to take away is the band
   heading and its subtotal. Her reason for wanting it was "so we can see what
   payment needs to be made", which is due order by definition. */
.bg-table.is-flat .bg-cat,
.bg-table.is-flat .bg-subtotal { display: none; }

/* ...and the category comes back ON THE ROW when the bands go, because with
   the headings gone nothing else on the line says what a supplier is for.
   Gee, 7 Sept: "we didn't want to get rid of the categories on the left-hand
   side." Hidden in the grouped view, where the heading above already says it. */
.bg-cat-tag { display: none; }
.bg-table.is-flat .bg-cat-tag {
  display: inline-block;
  margin-right: 8px;
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--surface-soft);
  color: var(--ink-soft);
  font-size: .7rem;
  font-weight: 600;
  vertical-align: middle;
}
.bg-table .num { text-align: right; white-space: nowrap; }
.bg-table .strong { font-weight: 600; }

.bg-table tr.bg-cat th {
  padding-top: 18px;
  border-bottom: none;
  color: var(--pink);
  font-size: .72rem;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.bg-table tbody tr[data-line] {
  border-top: 1px solid var(--line);
  cursor: pointer;
}

.bg-table tbody tr[data-line]:hover { background: #faf7f9; }
.bg-table tbody tr[data-line]:focus-visible { outline: 2px solid var(--pink); outline-offset: -2px; }

/* A supplier's name is what she is looking for on this screen. 700, matching
   the run sheet and the checklist, so a name reads the same weight wherever
   she is. ⚠️ AND `.bg-table .bg-name` BELOW HAS TO MOVE TOO — it is more
   specific and it is the rule that applies in the actual budget table, so
   changing only this one would leave the main view untouched. */
.bg-name { font-weight: 700; }

/* ONE LINE, not three. Chosen from a rendered mock-up on 7 September.

   This used to read `display: block`, with a comment saying that side by side
   the button "kept getting squeezed onto its own line anyway". That was true
   when the button sat in a narrow cell — but the supplier column is the widest
   in the table, and stacking the name, the invoice button and the ⋮ made every
   row two lines tall whether or not it needed to be. Down a twenty-line budget
   that is most of a screen.

   Gee, 7 Sept, on the budget: "where a row wraps onto two lines, I'd rather it
   be one line." The row was never wrapping — it was stacking. The picture is
   what settled it.

   `white-space: nowrap` on the cell keeps the three parts together; the cell
   is already the widest column and the table already scrolls sideways, so a
   very long supplier name pushes the scroll rather than folding the row. */
/* ---------------------------------------------------------------------------
   Ticking several budget lines to delete them together.

   ⚠️ `.bg-table tr.is-picked`, NOT a bare `.is-picked`. bloom.css:2180 already
   owns `.cmp-flag.is-picked` for a comparison flag, and the two files are
   loaded together — an unscoped rule here would reach into it. Same trap as
   `.home-card`, which existed twice and let the later file win silently.
   --------------------------------------------------------------------------- */
/* The run sheet's half of the same control. Deliberately the same shape as the
   budget's: two screens doing one job differently is what has cost most here.
   Scoped `.tl-row.is-picked` for the reason in the budget block below —
   bloom.css already owns a bare `.cmp-flag.is-picked`. */
/* ---- find and replace on the run sheet ---------------------------------- */
.tl-find-where {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  margin: 8px 0 4px;
}

.tl-replace-list {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  max-height: 46vh;
  overflow-y: auto;
}

.tl-replace-list li {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 8px 4px;
  border-bottom: 1px solid var(--line);
}

.tl-replace-list input[type="checkbox"] {
  margin-top: 3px;
  accent-color: var(--pink);
  flex: 0 0 auto;
}

.tl-replace-list .rp-field {
  display: inline-block;
  min-width: 46px;
  font-size: .68rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* Before above, after below — one under the other rather than side by side,
   because a run sheet title can be sixty characters and two of them on one
   line wrap into each other and stop being comparable. */
.tl-replace-list .rp-was,
.tl-replace-list .rp-now { display: block; font-size: .88rem; }
.tl-replace-list .rp-was { color: var(--ink-soft); text-decoration: line-through; }
.tl-replace-list .rp-now { color: var(--c-sage-deep, #0f6e56); }

.tl-replace-list li.is-risky .rp-now { color: #b3001b; }

.tl-replace-list .rp-why {
  margin-left: auto;
  flex: 0 0 auto;
  align-self: center;
  font-size: .7rem;
  color: #b3001b;
  background: #fdeaee;
  border-radius: 999px;
  padding: 2px 9px;
}

.tl-pick {
  align-self: center;
  /* ⚠️ NEGATIVE, because .tl-row is a flex row with `gap: 16px`. Left at 6px
     the tick sat 22px from the controls beside it and read as belonging to
     nothing. This pulls it back to 8px, which is the same air the budget's has
     against its grip. */
  margin-right: -8px;
  accent-color: var(--pink);
  cursor: pointer;
  flex: 0 0 auto;
}

/* The picked tint itself lives at the BOTTOM of this file — see the note
   there. Two hover rules would otherwise beat it on a tie. */

.tl-picked-bar {
  position: sticky;
  bottom: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 10px;
  padding: 9px 14px;
  border: 1px solid var(--pink);
  border-radius: 10px;
  background: var(--pink-tint);
}

.tl-picked-bar b { color: var(--pink-deep); font-size: .9rem; }

.tl-picked-del {
  margin-left: auto;
  border: 1px solid #b3001b;
  background: #fff;
  color: #b3001b;
  font-weight: 700;
}
.tl-picked-del:hover { background: #b3001b; color: #fff; }

.bg-pick {
  margin-right: 7px;
  vertical-align: middle;
  accent-color: var(--pink);
  cursor: pointer;
}

/* On the heading band the tick sits against a tinted ground, so it needs the
   same optical nudge the grip beside it gets. */
.bg-cat .bg-pick-cat { margin-right: 9px; }

/* The picked tint itself lives at the BOTTOM of this file — see the note
   there. `.bg-table tbody tr[data-line]:hover td.bg-status` would otherwise
   beat it on the status cell. */

/* Below the table, because she ticks her way DOWN it — see the note in
   index.html. Sticky so a long strip-down does not leave the button off the
   bottom of the screen at the exact moment she wants it. */
.bg-picked-bar {
  position: sticky;
  bottom: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 10px;
  padding: 9px 14px;
  border: 1px solid var(--pink);
  border-radius: 10px;
  background: var(--pink-tint);
}

.bg-picked-bar b { color: var(--pink-deep); font-size: .9rem; }

/* The destructive one carries the weight; Clear stays a ghost beside it. */
/* ⚠️ NOT `margin-left:auto` ANY MORE. Set aside sits between Clear and Delete,
   so the auto margin moved onto the pair — otherwise Set aside would be pushed
   right and Delete would sit beside it with no separation, and the gentle
   option has to be the easier reach of the two. */
.bg-picked-aside {
  margin-left: auto;
  border: 1px solid var(--pink);
  background: #fff;
  color: var(--pink-deep);
  font-weight: 600;
}
.bg-picked-aside:hover { background: var(--pink); color: #fff; }

/* 🛑 AN ACTION IN A SELECTION BAR THAT WORE NO TIER AT ALL, AND FOUR OF THEM
   DID. Found 17 September by screenshotting the checklist's new bar: "Assign
   to…" and "Due date…" rendered as bare text beside a pill, so the two things
   she had just asked for looked like labels and Set aside looked like the only
   button on the row.

   ⚠️ IT WAS NOT NEW. `#tl-picked-place` — the run sheet's "Set the place",
   shipped the night before — had the same bare `btn btn-sm`, and so did the
   BUDGET's "Set aside", which predates both. §41.2 is this exact fault one
   component over: Open had no tier class while Delete was a red btn-danger, so
   the destructive button was the loudest thing on the card.

   ⚠️ A SHARED CLASS, NOT A STRUCTURAL SELECTOR. `.bar .btn:not(.btn-ghost)…`
   was the obvious way to write this and it would have been wrong: at five
   classes' specificity it beats `.bg-picked-del` and `.tl-picked-del` at one,
   and Delete selected would have turned pink. That is this morning's lesson —
   the values agree and the cascade does not — so the rule names what it
   styles. */
.picked-act {
  border: 1px solid var(--pink);
  background: #fff;
  color: var(--pink-deep);
  font-weight: 600;
}
.picked-act:hover { background: var(--pink); color: #fff; }

/* The payment labels for a whole selection (3 Oct). Outlined in the SAME
   colour as the row's own filled label — blue Ready, magenta Deposit, red On
   hold — so she recognises them, and filled only on hover so they never shout
   louder than Delete. Paid is green, the colour of money that has gone. */
#bg-picked-bar, #bg-picked-bar-top { flex-wrap: wrap; }
/* The same bar ABOVE the table too (Gee, 20 Oct: "It'll be good to have one
   on the top"). A clone made in initBudget. Not sticky — the bottom one
   already follows her down the table, and two sticky bars would stack. */
.bg-picked-top { position: static; margin: 0 0 10px; }
.bg-bulk-pay { background: #fff; font-weight: 600; border: 1px solid currentColor; }
.bulk-ready   { color: #1d4ed8; }
.bulk-deposit { color: #c026d3; }
.bulk-hold    { color: #be123c; }
.bulk-cash    { color: #f43f5e; }
.bulk-paid    { color: #047857; }
.bg-bulk-pay:hover { color: #fff; }
.bulk-ready:hover   { background: #1d4ed8; }
.bulk-deposit:hover { background: #c026d3; }
.bulk-hold:hover    { background: #be123c; }
.bulk-cash:hover    { background: #f43f5e; }
.bulk-paid:hover    { background: #047857; }
/* Who sees it (22 Oct, CHECK 239): teal like the couple-facing chips, violet for hers. */
.bg-bulk-vis { background: #fff; font-weight: 600; border: 1px solid currentColor; }
.bulk-show { color: #00796b; }
.bulk-hide { color: #6d28d9; }
.bg-bulk-vis:hover { color: #fff; }
.bulk-show:hover { background: #00796b; }
.bulk-hide:hover { background: #6d28d9; }

/* The same four labels INSIDE the line's card (20 Oct). The one the line has
   is filled, in the hover colour, so the card reads like the row's chip. */
.b-pay-labels { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0 4px; }
.bg-bulk-pay.is-on { color: #fff; }
.bulk-ready.is-on   { background: #1d4ed8; }
.bulk-deposit.is-on { background: #c026d3; }
.bulk-hold.is-on    { background: #be123c; }
.bulk-cash.is-on    { background: #f43f5e; }

/* The Undo strip borrows the selection bar's look; mint, so it reads as
   "done, and here is the way back" rather than as another selection. */
.bg-undo-bar { border-color: #10b981; background: #ecfdf5; }
.bg-undo-bar #bg-undo-text { color: #065f46; font-weight: 600; font-size: .9rem; }

.bg-picked-del {
  border: 1px solid #b3001b;
  background: #fff;
  color: #b3001b;
  font-weight: 700;
}
.bg-picked-del:hover { background: #b3001b; color: #fff; }

/* The drawer. Quiet by design — it is a note about what is not here, not a
   control competing with the budget above it. */
/* ⚠️ STICKY, AND IT SHIPPED WITHOUT IT. The selection bar above is sticky and
   this was not, so on a budget of forty lines "3 set aside · Show" sat at the
   very bottom of the DOCUMENT — below every line and every subtotal — and Gee
   set something aside and reported, correctly, "I can't see Show anywhere".

   A way back that you have to go looking for is not a way back. It sits just
   above the selection bar so the two stack rather than overlap. */
.bg-aside-bar {
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 10px;
  padding: 8px 14px;
  border: 1px dashed var(--ink-faint);
  border-radius: 10px;
  background: var(--surface, #fff);
  font-size: .86rem;
  color: var(--ink-soft);
}

/* Open, it is describing the screen you are looking at rather than one you
   are not — so it stops being a footnote and says so. */
.bg-aside-bar.is-open {
  border-style: solid;
  border-color: var(--pink);
  background: var(--pink-tint);
  color: var(--pink-deep);
}

/* The run sheet's and the checklist's drawers are the budget's, by design —
   three surfaces doing one job differently is what has cost this project most.
   Kept in one block so they cannot drift apart. */
/* Sticky, for the reason in the budget's block above — a drawer you have to
   scroll to the bottom of a sixty-line run sheet to find is not a way back. */
.tl-aside-bar,
.task-aside-bar {
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 10px;
  padding: 8px 14px;
  border: 1px dashed var(--ink-faint);
  border-radius: 10px;
  background: var(--surface, #fff);
  font-size: .86rem;
  color: var(--ink-soft);
}
.tl-aside-bar.is-open,
.task-aside-bar.is-open {
  border-style: solid;
  border-color: var(--pink);
  background: var(--pink-tint);
  color: var(--pink-deep);
}

/* ---------------------------------------------------------------------------
   THE COUNT, IN THE TOOLBAR. 13 September.

   Gee asked where "Show" was TWICE. By §39.18's own rule that means the control
   is not findable, and the answer written down that day was to put the count
   beside Print and Export — where she actually looks. This is that.

   ⚠️ MINT, MATCHING A QUESTIONNAIRE-PARKED ROW, AND NOT PINK. Pink on these
   three tabs means SELECTION — the checkbox, the selection bar, "Set aside 3".
   A pink chip in the toolbar would read as "3 rows are selected", which is a
   different fact and the one thing this must not be confused with.

   It is `hidden` when nothing is parked, so a wedding that has never used set
   aside carries no permanent chip — the same rule the Imported badge follows.
   --------------------------------------------------------------------------- */
.aside-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: inherit;
  font-size: .8rem;
  font-weight: 700;
  color: var(--c-teal);
  background: var(--c-teal-tint);
  border: 1px solid var(--c-teal);
  border-radius: 9px;
  padding: 4px 10px;
  cursor: pointer;
}
.aside-chip:hover { background: var(--c-teal); color: #fff; }
.aside-chip:focus-visible { outline: 2px solid var(--pink); outline-offset: 2px; }

/* Open, it is the way BACK, so it stops being a quiet marker and becomes the
   active control — the same flip the drawer bar does with .is-open. */
.aside-chip.is-open { background: var(--c-teal); color: #fff; }

.tl-picked-aside {
  margin-left: auto;
  border: 1px solid var(--pink);
  background: #fff;
  color: var(--pink-deep);
  font-weight: 600;
}
.tl-picked-aside:hover { background: var(--pink); color: #fff; }

/* `margin-left:auto` moved onto Set aside above, so Delete sits beside it. */
.tl-picked-del { margin-left: 0; }

/* ---------------------------------------------------------------------------
   WHY A ROW IS PARKED — one pill, three tabs.

   The budget has had this since 106. The run sheet and the checklist WROTE the
   reason and never showed it, so a parked line in either drawer looked exactly
   like a live one and said nothing about why it was there. Gee found that on
   13 September by looking at Aisle Planner's "Pre-Assignment: add note" and
   asking for the same thing here.

   ⚠️ ONE RULE, THREE SELECTORS, DELIBERATELY. Copying the declarations into a
   .tl- and a .task- block is how the couple's phase headings drifted away from
   the planner's — see the warning at bloom.css:770. If this changes, it changes
   for all three at once because there is only one of it.

   The run sheet's and the checklist's wrap; the budget's does not, because a
   budget row is a table cell and a wrapping pill there pushes the columns.
   --------------------------------------------------------------------------- */
.bg-aside-why,
.tl-aside-why,
.task-aside-why {
  margin-left: 8px;
  font-size: .72rem;
  color: var(--ink-soft);
  background: var(--line-soft);
  border-radius: 999px;
  padding: 2px 9px;
  white-space: nowrap;
}

/* When a task was ticked. Same pill as the parked reason above it — one shape
   for "here is a fact about this row's state", rather than a second look to
   learn. Quieter, because a done row is already dimmed and this must not be
   the loudest thing on it.

   Gee, 20 September: "I can't see what has been ticked… so then I can be like,
   oh shit, they shouldn't have been ticked." `completed_at` was on the table
   all along with nothing showing it. */
.task-ticked {
  margin-left: 8px;
  font-size: .7rem;
  color: var(--ink-faint);
  background: var(--line-soft);
  border-radius: 999px;
  padding: 2px 9px;
  white-space: nowrap;
  display: inline-block;
}

/* Nothing on paper needs to know when it was ticked. */
@media print { .task-ticked { display: none; } }

/* WHAT A LINE IS ACTUALLY FOR, when the supplier's name is not the answer.
   15 September — three Ritz-Carlton rows at $500, $2,000 and $1,710 with no
   way to tell them apart, while "Confetti clean-up fee" sat in a field nothing
   drew.

   ⚠️ NOT `white-space: nowrap`, unlike its neighbour above. A set-aside reason
   is a few words; "Security — 3 guards, minimum 6 hours each at $95/hour as per
   RSA" is a sentence, and holding it on one line would push the money columns
   off the side of the table. It wraps and the row grows, which is the right way
   round. */
.bg-forwhat {
  margin-left: 8px;
  font-size: .74rem;
  color: var(--ink-soft);
  font-style: italic;
}
.bg-forwhat::before { content: "· "; font-style: normal; opacity: .6; }

.tl-aside-why,
.task-aside-why {
  display: inline-block;
  margin: 5px 0 0;
  white-space: normal;
}

/* ---------------------------------------------------------------------------
   "THEY SAID NO" AGAINST "I DECIDED THIS". 13 September.

   Gee: "can it go in the backgrounds, with the highlight being green or
   something? Then it says from VIP questionnaire."

   ⚠️ THE MINT IS ONLY ON THE QUESTIONNAIRE'S ROWS, AND THAT IS THE WHOLE
   POINT. Drawn four ways first. Tinting every parked row green was the literal
   request and the drawing killed it: parked rows appear ONLY inside the drawer,
   where everything is parked, so a tint on all of them says nothing — and a row
   SHE parked would have carried a label claiming the couple said no.

   ⚠️ MINT (--c-teal), NOT EMERALD (--c-sage). Emerald is done-and-paid
   everywhere else in this file, and `band-sage` is a heading band that can sit
   directly above a parked line. Measured before choosing, not guessed.
   --------------------------------------------------------------------------- */
.tl-row.is-aside .tl-title,
.task-row.is-aside .task-title {
  text-decoration: line-through;
  text-decoration-color: var(--ink-faint);
  color: var(--ink-soft);
}

.tl-row.is-aside.from-vip,
.task-row.is-aside.from-vip {
  background: var(--c-teal-tint);
  border-left: 3px solid var(--c-teal);
  padding-left: 9px;
}

.tl-row.is-aside.from-vip .tl-aside-why,
.task-row.is-aside.from-vip .task-aside-why {
  color: var(--c-teal);
  background: var(--surface);
  font-weight: 600;
}

/* The tint has to survive a hover, or the row flickers between two meanings
   as the mouse crosses it. `.tl-row:hover` is a bare colour further up. */
.tl-row.is-aside.from-vip:hover,
.task-row.is-aside.from-vip:hover { background: var(--c-teal-tint); }

.bg-supplier { white-space: nowrap; }
.bg-supplier .bg-name { display: inline; margin-right: 8px; }
.bg-supplier .bg-clip,
.bg-supplier .row-dots,
.bg-supplier .bg-talk { vertical-align: middle; }

/* Invoices, quotes and receipts on a budget line.
   A bare paperclip glyph was far too easy to miss, so this is a labelled
   button that says what it does whether or not anything is attached yet. */
.bg-clip {
  display: inline-block;
  margin-top: 0;
  padding: 2px 9px;
  border: 1px solid var(--teal);
  border-radius: 999px;
  background: var(--surface);
  color: var(--teal-deep);
  font-family: inherit;
  font-size: .72rem;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
  cursor: pointer;
}

.bg-clip:hover { background: var(--teal-tint); }

.bg-clip.has-files {
  border-color: var(--teal);
  background: var(--teal-tint);
}

/* A second line under the status pill: which instalment is actually paid. */
.status-detail {
  display: block;
  margin-top: 3px;
  color: var(--ink-faint);
  font-size: .68rem;
  line-height: 1.3;
}

.bg-flag {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--danger-tint);
  color: var(--danger);
  font-size: .68rem;
  font-weight: 600;
}

.bg-table tr.bg-subtotal td {
  border-top: 1px solid var(--line);
  color: var(--ink-soft);
  font-weight: 600;
}

.bg-table .is-overdue { color: var(--danger); font-weight: 600; }

/* The three supporting figures under the balance on a supplier row.
   Deliberately quiet: the balance is the one that means "do something", and
   four numbers of equal weight is four numbers nobody reads. */
.sup-money-detail {
  display: block;
  margin-top: 2px;
  color: var(--ink-faint);
  font-size: .72rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* "not in budget" was a dead label; it is a door now. Sized like the other
   row buttons so it reads as something to press. */
.sup-add-budget { white-space: nowrap; }

.status {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .04em;
  white-space: nowrap;
}

.status-paid    { background: var(--teal-tint); color: var(--teal-deep); }
.status-partial { background: var(--pink-tint); color: var(--pink); }
.status-unpaid  { background: #f2f0f1;          color: var(--ink-soft); }


/* ---------- payment schedule ---------------------------------------------- */

.form-section {
  margin: 22px 0 4px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  color: var(--pink);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.pay-table { width: 100%; border-collapse: collapse; font-size: .85rem; }

.pay-table thead th {
  padding: 6px 6px;
  color: var(--teal);
  font-size: .64rem;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  text-align: left;
  white-space: nowrap;
}

.pay-table td { padding: 4px 6px; vertical-align: middle; }
.pay-table .pay-seq { color: var(--ink-faint); white-space: nowrap; }
.pay-table .pay-tick { text-align: center; }
.pay-table .pay-tick input { width: 16px; height: 16px; accent-color: var(--teal); }
.pay-in { padding: 6px 8px; font-size: .84rem; }
.pay-table .pay-del { width: 34px; }
/* 🛑 SCOPED TO `.pay-table` ON 18 SEPTEMBER, AND THE SCOPE IS THE WHOLE FIX.
   `pay-none` MEANS TWO DIFFERENT THINGS in this app and this rule was written
   for only one of them:

     in `.pay-table`  — the empty row, "No payments scheduled yet" (budget.js
                        3160). Grey is right: there is nothing there.
     on `.bg-table tr` — the payment STATUS "Unpaid" (budget.js 532). A real
                        supplier, a real amount, real money owed.

   Unscoped, this selector (0,1,1) caught both, so every unpaid budget row —
   the supplier's NAME included — rendered at `--ink-faint`, rgb(160,148,154),
   which is 2.92:1 on white and under the 4.5:1 a word needs.

   Gee found it on Legato and was right twice over: "a lot of people don't get
   a payment until the wedding day, so I don't think they should be grayed out."
   Unpaid is normal, not a lesser state — the status pill and the coloured left
   edge already say it. Measured the day it was fixed: 3 of Tiffany's 18 rows,
   21 of Solin & Rony's 28, and 33 of Pardesay & Haries' 35. A wedding uploaded
   with nothing paid yet would have drawn its ENTIRE budget at 2.92:1 on day one.

   ⚠️ The padding was already being overridden by a later rule on the budget
   table — measured identical (12px 12px 12px 8px) across pay-none, pay-part
   and pay-done before touching this — so scoping changes the COLOUR and
   nothing else. See `a-shorthand-wipes-the-longhand` and
   `css-class-name-collisions` in memory: one name, two meanings. */
.pay-table .pay-none td { padding: 12px 6px; color: var(--ink-faint); }

/* 📎 Receipt beside Paid, and the green bar after a couple ticks (Gee's
   mock-up, 15 Oct; migration 147). The tick cell stops wrapping so the
   button sits on the tick's line. */
.pay-table .pay-tick { white-space: nowrap; }
.pay-table .pay-receipt { margin-left: 8px; }
.pay-table .pay-receipt-remove { margin-left: 2px; padding: 2px 8px; color: var(--danger); font-weight: 800; }
.pay-ticked-row td { padding: 4px 6px 8px; }
.pay-ticked {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 12px;
  background: var(--ok-tint); border-radius: 10px; padding: 10px 14px;
  font-size: .86rem;
}
.pay-ticked b { color: var(--ok); }
.pay-ticked-hint { color: var(--ink-faint); font-size: .78rem; }
.pay-ticked-email { font-weight: 700; user-select: all; }
.pay-gee-paid {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  background: var(--ok-tint); color: var(--ok); font-weight: 700; font-size: .78rem;
}

.pay-warn td {
  padding: 2px 6px 8px;
  color: var(--danger);
  font-size: .76rem;
}

.pay-foot {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 12px;
}

.pay-summary { color: var(--ink-soft); font-size: .82rem; }
.pay-mismatch { color: var(--danger); font-weight: 600; }

/* ⚠️ THE `gap` LINE THAT WAS HERE HAS MOVED, 18 September. A later plain
   `.bg-summary { gap: 34px }` beat it — same weight, further down — so the
   summary strip kept a 34px gap on a phone while this said 16. It now sits
   directly under that rule. The button line below never collided and stays. */
@media (max-width: 620px) {
  .bg-summary > .btn:last-child { margin-left: 0; width: 100%; }
}

.feature-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 9px 18px;
  margin-top: 10px;
}

.feature-grid .check-inline {
  text-transform: none;
  letter-spacing: 0;
  font-size: .87rem;
  color: var(--ink);
}

.subnav-item[hidden] { display: none !important; }

.bg-table .is-under { color: var(--teal-deep); }
.bg-table .is-over  { color: var(--danger); font-weight: 600; }


/* ---------- timeline ------------------------------------------------------ */

/* Key details wears a filled teal bar and owns the top of the sheet.
   Gee, 27 Aug: "this key details section is not powerful enough... the
   supplier arrival times are more prominent than that, and I want that to
   really stand out. That really dictates everything that's happening on the
   day." It was the faintest thing on the page and the most important. */
.tl-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 0;
  padding: 9px 16px;
  background: var(--teal-deep);
  border-radius: var(--radius) var(--radius) 0 0;
}

/* Gee, 28 Aug: "the key details heading and the supply arrival times heading
   must be larger." They label the two blocks that dictate the day and they
   were set smaller than the body text under them. */
.tl-header h3 {
  margin: 0;
  color: #fff;
  font-size: .95rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
}

/* On the bar, not below it — a ghost button in ink would vanish into teal. */
.tl-header .btn-ghost {
  color: #fff;
  border-color: rgba(255, 255, 255, .45);
}
.tl-header .btn-ghost:hover {
  background: rgba(255, 255, 255, .16);
  border-color: #fff;
  color: #fff;
}

.tl-details {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 15px 22px;
  padding: 16px 17px;
  border: 1.5px solid var(--teal-deep);
  border-top: 0;
  border-radius: 0 0 var(--radius) var(--radius);
}

.tl-detail { display: flex; flex-direction: column; gap: 3px; }

/* Bold, and the deep teal rather than the light one. The pale --teal on a
   white card was the "too faint" Gee was pointing at. */
/* 🛑 800. Gee, 17 September, pointing at exactly these: "the stuff that's in
   the green, the teal colour — the bride's getting-ready location wording, the
   bride's address wording, the groom's getting-ready location wording. That's
   what I wanted you to go in bold."

   ⚠️ THEY WERE ALREADY 700, WHICH IS WHY MY FIRST ATTEMPT WENT TO THE WRONG
   HALF OF THE PANEL. Measuring said the labels were bold and the VALUES were
   600, so I bolded the values — and she had to send the panel back twice to
   say I had the wrong thing. The measurement was right and the inference from
   it was wrong: "already 700" is not "already as bold as it can look", and at
   10.85px in uppercase with letter-spacing, 700 reads light.

   Figtree loads 800, so that is where these go. If it still is not enough the
   next lever is SIZE or COLOUR — there is nothing above 800. */
/* 🛑 AND WHEN 800 STILL READ LIGHT, THE ANSWER WAS NOT MORE WEIGHT.
   Gee, after the third attempt: "The teal still reads light. It's still not
   bolded."

   800 is the heaviest Figtree loads, so weight was exhausted. Three other
   things were making these read thin, and all three are fixed here:

     SIZE          .7rem ≈ 11px. Small type reads light at ANY weight.  → .8rem
     CONTRAST      #007c85 on white is about 4.6:1 — a mid teal that greys out
                   at small sizes.                                      → #00565e ≈ 7.8:1
     LETTER-SPACE  .06em on uppercase pulls the strokes apart, which is exactly
                   what "not bold" looks like.                          → .035em

   ⚠️ THE COLOUR IS SET HERE, NOT ON `--teal-deep`. That token is used across
   the app and darkening it would silently restyle every other teal thing to fix
   one panel. */
.tl-detail .k {
  color: #00565e;
  font-size: .8rem;
  font-weight: 800;
  letter-spacing: .035em;
  text-transform: uppercase;
}

/* 🛑 THE ANSWER, NOT THE QUESTION. Gee, 17 September, pointing at Key details:
   "I want them bolded on the screen too."

   ⚠️ MEASURED BEFORE CHANGING ANYTHING, AND IT WAS NOT WHAT THE WORDS SAID.
   The teal labels — BRIDE GETTING READY LOCATION, VENUE BUMP-IN — were
   ALREADY 700. What read as unbolded was this: the values beneath them, at
   600. She was pointing at the panel; the only thing in it that was not bold
   was the half that carries the facts.

       BRIDE GETTING READY LOCATION   10.85px  700   ← already bold
       Bride's home                   15.0px   600   ← this one

   700 puts it with the run sheet line, the checklist task, the budget name and
   the supplier name. The label stays where it is: it is small teal capitals and
   it is the caption, not the content. */
/* ⚠️ 600, AND IT IS BACK AT 600 ON PURPOSE. It was briefly taken to 700 and
   then 800 chasing "I want them bolded" — wrong half of the panel. Gee meant
   the teal labels above; she asked for this reverted. The value is the answer
   and it sits at 600 under a heavier label, which is the hierarchy she wants.
   Do not "restore" this to 700 to match the other list screens: those are
   NAMES in a list, this is a value under its own caption. */
.tl-detail .v {
  font-size: .97rem;
  font-weight: 600;
  word-break: break-word;
}

/* AN ABSENCE SHE HAS CONFIRMED — "Ceremony: None on the wedding day",
   "Canapés: None", "Bridal party: None".

   Gee, 10 Sept, looking at the three of them scattered through the card in the
   same teal as a real address: "I think it should be in red."

   Both halves go red, not just the value. The teal label column is a strong
   rhythm down the card and a red value under a teal label reads as one cell
   half-changed; the whole cell turning is what makes it a different KIND of
   statement rather than an odd-looking fact.

   ⚠️ Red means "wrong" elsewhere in this app — overdue, TBC, cancelled. Here
   it deliberately means "there isn't one", and it is Gee's call: a supplier
   glancing at a run sheet should not be able to miss that there is no
   ceremony. Raised with her before building.

   ⚠️ IT WAS --danger (#b3001b) FIRST, AND THAT WAS WRONG. Gee, looking at it
   live: "It's a red that you can't really see properly... I want a big,
   vibrant red so people can easily distinguish it." She was right — #b3001b is
   a dark maroon chosen to sit QUIETLY beside body text as an overdue stamp,
   which is the opposite job. Against a white card at .97rem it reads as brown.

   --absent is #d50000, the same vibrant red the palette already keeps as
   --btn-crimson. Given its own name rather than borrowing the button token,
   because a button colour and a "this is not happening" colour are two
   different jobs that only happen to agree today.

   Weight 700 as well: "big AND vibrant" was two requests, and the value sits
   at 600 like every other fact on the card.

   ⚠️ AND THE WEIGHT CAME BACK OFF, 11 September. Gee: "the NA next to ceremony
   is a bigger font than the ones below… it doesn't have to be that big. I
   think the NA doesn't have to be bolded."

   The size never changed — .97rem, the same as every other value on the card.
   700 against its 600 neighbours READ as bigger, which is the more useful bug
   report: weight and size are the same signal at a glance, and "make it stand
   out" was already being done by the colour. */
.tl-detail.is-none .k,
.tl-detail.is-none .v { color: var(--absent); }

/* How many lines read backwards, and the one button that fixes them. Sits
   above the day rather than inside it: it is about the sheet, not a line. */
.tl-order {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin: 0 0 12px;
  padding: 11px 14px;
  background: var(--danger-tint);
  border: 1px solid var(--danger);
  border-radius: var(--radius-sm);
}

.tl-order-say { margin: 0; font-size: .92rem; color: var(--danger); }
.tl-order-say b { font-weight: 700; }
.tl-order .btn { margin-left: auto; }

/* The question a moved line asks about its own time. Indented to the width of
   the control gutter plus the time column, so it reads as belonging to the
   line above it rather than as a row of its own. */
.tl-timefix {
  list-style: none;
  margin: 0 0 4px 86px;
  padding: 12px 14px;
  background: var(--surface-warm);
  border: 1px solid var(--pink-deep);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
}

.tl-timefix-say { margin: 0 0 9px; font-size: .9rem; color: var(--ink); }

.tl-timefix-do {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  font-size: .9rem;
}

/* ⚠️ NOT `input[type="time"]` — that selector matched for about a second.
   initLooseTimeInputs() rewrites every time input to type="text" at boot, and
   this block is built long after that, so the rule stopped applying the moment
   the sweep touched it. Measured on the live build: the box went 122px → 764px
   and the prompt became 235px tall across six lines.
   This is `time-inputs-are-not-time-inputs` in the memory folder, and it has
   now cost an AM/PM change and this. NEVER style these by type. */
.tl-timefix-do input {
  width: 122px;
  flex: 0 0 auto;
  padding: 5px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font: inherit;
}

/* One line, not six. The label belongs to the box beside it, so they must not
   be separated by a wrap. */
.tl-timefix-do label { flex: 0 0 auto; color: var(--ink-faint); }
.tl-timefix { padding: 10px 14px; }
.tl-timefix-say { margin-bottom: 7px; }

.tl-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.tl-row {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: 11px 10px;
  border-bottom: 1px solid var(--line);
}

.tl-row:hover { background: #faf7f9; }

/* Black, not pink. Gee, 27 Aug: "there's just way too much pink happening on
   this running sheet. I think the times are not in pink, but in black."
   A time is data you read sixty times down a page, not an accent — and pink
   on every one of them was what made the whole sheet look decorated. */
.tl-time {
  flex: 0 0 86px;
  padding-top: 1px;
  color: var(--ink);
  font-size: .87rem;
  font-weight: 600;
  white-space: nowrap;
}

/* Not greyed out. A time that isn't locked in is a job still to do, and the
   faint grey read as "unimportant" — which is the opposite of true.

   Highlighted as well as coloured, using the same idea as Gee's own marks on
   the run sheet: on a page of fifty lines, red type alone still scans past.
   A block of colour does not. */
.tl-row.is-tbd .tl-time {
  color: #b3001b;
  font-weight: 700;
}

/* The planner's time column wraps its text in a span so a highlight hugs the
   time; the couple's copy puts the text straight in. Both need the block.
   ⚠️ NOT ON "Mark as checked". Since 13 Oct that button lives in this column
   too, and `> *` painted it pale pink with its white words on top — Gee
   spotted it on Annika & Ceyhun's Drinks + grazing line, 15 Oct; 13 lines on
   5 live weddings. CHECK 167. */
.tl-row.is-tbd .tl-time > :not(.yellow-checked),
.tl-readonly .tl-row.is-tbd .tl-time {
  background: #ffe0e3;
  border-radius: 3px;
  padding: 1px 5px;
}

.tl-readonly .tl-row.is-tbd .tl-time {
  display: inline-block;
  color: #b3001b;
  font-weight: 700;
}

/* A column so the title, the supplier pill and the notes stay stacked while
   each one only takes the width it needs. */
.tl-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

/* inline-block, not block: a highlighted title has to hug its words the way a
   marker pen would, rather than painting the whole row yellow. */
/* 🛑 THE LINE IS THE THING SHE READS, AND IT WAS THE LIGHTEST TEXT ON THE ROW.
   Gee, 17 September: "See how you've got 7:00 till 9:00? It's quite bolded,
   but the makeup artist arrives at the bride's house — on the screen it's not
   bolded. I need every line item site-wide to be bold... It's so hard to read,
   see, and decipher."

   Measured on her own row before changing anything, and she is right — it was
   backwards:

       7:00 - 9:00 am                       13.5px, weight 600
       Make-up artist arrives @ Bride's…    14.4px, weight 400

   The bigger text carried the lighter weight, so the clock shouted and the
   thing happening whispered. On a 69-line sheet that is the whole page.

   ⚠️ ONE RULE COVERS BOTH RUN SHEETS. `.tl-title` is written by timeline.js
   AND portal.js, so the couple's copy changes with hers and there is no second
   place to keep in step.

   ⚠️ AND `.is-quiet` STILL WINS. The arrivals card's hover-revealed label sets
   `font-weight: 400` at two classes' specificity, so it beats this at one and
   stays quiet — checked, not assumed. Headings are unaffected too: they set
   700 at three classes and were already bold. */
.tl-title {
  display: inline-block;
  max-width: 100%;
  font-size: .93rem;
  font-weight: 700;
  word-break: break-word;
}

.tl-notes {
  display: block;
  align-self: stretch;
  margin-top: 3px;
  color: var(--ink-soft);
  font-size: .8rem;
  line-height: 1.45;
}

/* .tl-actions is gone — every control for a line now lives in .tl-gutter on
   the left beside the time, so nothing has to be reached for across the row. */

@media (max-width: 620px) {
  .tl-time { flex-basis: 64px; }
}


/* ---------- timeline colours + responsible -------------------------------- */

/* Sits under the notes, not between them and the title — a supplier pill in
   the middle of a block of notes reads like the notes have ended. */
/* 🛑 A FILLED CHIP, NOT TEAL TEXT IN A NEARLY-WHITE RING. 19 September.
   Gee, looking at her own run sheet: *"there needs to be a different border
   around the supplier name because at the moment it's teal on teal, and it is
   very hard to distinguish."*

   She was exactly right, and reading the three things side by side on one line
   showed why:

     Gucci Paints   #007c85 text · border var(--line), near enough to white
     + supplier     #007c85 text — THE SAME COLOUR
     Bride's home   tint + matching border — reads as a solid object

   So the supplier's name was the only one of the three that was not a chip,
   and it sat against a button of its own colour.

   ⚠️ THE PATTERN IS THE ONE BESIDE IT, NOT A NEW ONE. `.who-place.is-set` two
   rules down is tint + matching border, and it has been the approved shape
   since it shipped. This is that shape in the supplier colour.
   `make-it-look-like-that-means-reuse-those-classes`.

   ⚠️ AND PRINT.CSS CARRIES THE SAME CHANGE. Gee: *"I thought it was all going
   to be on the same"* — the screen and the paper have to agree, and the print
   sheet had the identical pale ring. */
.tl-who {
  display: inline-block;
  margin-top: 6px;
  padding: 1px 9px;
  border: 1px solid var(--teal-deep);
  border-radius: 999px;
  background: var(--teal-tint);
  color: var(--teal-deep);
  font-size: .74rem;
  font-weight: 600;
}

/* MORE THAN ONE ON A LINE, since migration 83. Gee, 3 Sept: "we should be able
   to put multiple suppliers on one line if required as well."

   A chip each rather than one tag holding a comma list — she chose this looking
   at both, and it is right for a document that is scanned: three names run
   together read as one long supplier name. */
/* The colour key under the run sheet. Quiet on purpose — it is a reference you
   glance at once, not part of the day. `.c-*` already carry the six title
   colours, so the dot cannot drift from the thing it explains: it takes its
   background from the same class the title takes its text colour from. */
.tl-key {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 16px;
  margin: 14px 2px 0;
  padding-top: 11px;
  border-top: 1px solid var(--line-soft, var(--line));
  font-size: .78rem;
  color: var(--ink-soft);
}
.tl-key-lead { font-weight: 600; color: var(--ink); }
.tl-key-item { display: inline-flex; align-items: center; gap: 6px; }
.tl-key-dot {
  width: 11px; height: 11px; border-radius: 3px;
  background: currentColor;
}

/* The meaning, which she types. Looks like text until you hover it, because it
   is a label first and a control second. */
.tl-key-say {
  padding: 1px 5px;
  border: 1px solid transparent;
  border-radius: 5px;
  background: none;
  color: var(--ink);
  font-family: inherit;
  font-size: inherit;
  font-weight: 600;
  cursor: text;
}
.tl-key-say:hover { border-color: var(--line); background: var(--surface); }
/* Not yet named: quieter, and italic so it reads as a prompt rather than as a
   meaning somebody actually wrote. */
.tl-key-say.is-empty { color: var(--ink-faint); font-weight: 400; font-style: italic; }

.tl-key-input {
  padding: 1px 6px;
  border: 1px solid var(--teal);
  border-radius: 5px;
  font-family: inherit;
  font-size: inherit;
  min-width: 170px;
}

/* Paper gets the meanings and none of the machinery. */
@media print {
  .tl-key-say { border: 0; background: none; padding: 0; }
  .tl-key-say.is-empty { display: none; }
}

/* ⚠️ THE NAMES SIT ON ONE LINE.
   `.tl-main` is a flex COLUMN, so before this every chip dropped onto a line of
   its own — invisible with one supplier, three lines taller with three. Found
   7 September by rendering a line with three names rather than by reading the
   code. The row wraps only when it genuinely runs out of width. */
.who-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  margin-top: 6px;
  max-width: 100%;
}
.who-line .tl-who { margin-top: 0; }
.who-line .tl-who + .tl-who { margin-left: 0; }

/* Tag a second supplier without opening the line. Dashed, so it reads as an
   invitation rather than as another supplier already on the line. */
.who-add {
  padding: 1px 9px;
  border: 1px dashed var(--line);
  border-radius: 999px;
  background: none;
  color: var(--teal-deep);
  font-family: inherit;
  font-size: .78rem;
  font-weight: 600;
  cursor: pointer;
}
.who-add:hover { border-style: solid; background: var(--surface-soft); }

/* Say WHERE a line happens, next to who is on it. 12 September.

   Gee, 7 Sept, about tagging a supplier: "you have to go into it to be able to
   add. I'd rather it be up there." Then, 12 Sept, the identical complaint about
   the venue: "Do I need to go into the card file to then allocate where it's
   going? Is that right?" It was, and it should not have been.

   ⚠️ INDIGO, NOT TEAL. .who-add beside it is teal because teal means a person
   throughout this app; a place is not a person. Indigo is what the section
   bands already wear, which is where a place comes from when the line has none
   of its own — so the two controls on one row cannot be mistaken for each
   other at a glance.

   A quiet link while empty, a filled pill once she has chosen: the coloured
   tag on the far right looks identical whether the place was PICKED or guessed
   from the wording, so this chip is the only thing on the row that can tell
   her which. That is why it shows the venue name rather than a tick.

   ⚠️ SCOPED THROUGH .who-line, AND THAT IS NOT TIDINESS. `.tl-add-link` — the
   class every one of these buttons also carries — sets `border: 0` and teal at
   line 4664, nearly two thousand lines BELOW here. Same specificity (0,1,0),
   so the later rule wins and a bare `.who-place` loses its colour and its
   border silently. Found by asking the browser which rule won rather than by
   re-reading the file, which is the rule in CLAUDE.md.

   ⚠️ AND `.who-add` ABOVE HAS THE SAME FAULT, UNFIXED ON PURPOSE. Its dashed
   pill has never rendered — "+ supplier" has always been a plain teal text
   link. That is a real bug and it is Gee's to decide about, not something to
   change underneath her while she was asking for something else. Raised
   12 September. If she wants the pill back, scope it the same way this is. */
.who-line .who-place {
  color: var(--c-indigo);
  font-weight: 600;
}

/* Set, it stops being a link and becomes a value — it carries a venue name,
   not an instruction. The pill is what makes that read as a fact.
   (0,3,0) here, so it clears .tl-add-link without needing !important.) */
.who-line .who-place.is-set {
  padding: 1px 9px;
  border: 1px solid var(--c-indigo);
  border-radius: 999px;
  background: var(--c-indigo-tint);
}
.who-line .who-place:hover { text-decoration: underline; }

/* On a phone it is drawn the same — it is one small control and there is
   nothing to compact away, the same call made for .act-x on 31 August. */

.tl-who + .tl-who { margin-left: 5px; }

/* A TYPED NAME IS NOT A SUPPLIER, and must not look like one. Only a supplier
   picked from Contacts is counted by "is this supplier on the run sheet", by
   the coverage warning, or by their start time. Same shape, no fill, muted —
   present on the sheet, honestly not the same thing. */
.tl-who.is-typed {
  /* ⚠️ AND NO FILL, now that a picked supplier has one. 19 September: the fill
     went on `.tl-who` and would have been inherited here, which would have
     made a typed name look exactly like a counted one — the precise thing this
     rule exists to prevent. */
  background: none;
  color: var(--ink-soft);
  border-color: var(--line);
  border-style: dashed;
  font-weight: 500;
}

/* A typed name saying TBC / TBD — a supplier still to be found. Red, keeping
   the round dashed box. Gee, 18 October: "I like the round box, but red
   because it's a TBC." */
.tl-who.is-typed.is-tbc {
  color: var(--danger);
  border-color: var(--danger);
  background: var(--danger-tint);
  font-weight: 600;
}

/* ---- the picker in the Edit event modal ---- */
.who-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 7px;
}

.who-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 8px 3px 11px;
  border: 1px solid var(--c-teal);
  border-radius: 999px;
  background: var(--c-teal-tint);
  color: var(--c-teal);
  font: inherit;
  font-size: .78rem;
  font-weight: 600;
  cursor: pointer;
}

.who-chip span { font-size: .95rem; line-height: 1; opacity: .65; }
.who-chip:hover { border-color: var(--danger); background: var(--danger-tint); color: var(--danger); }
.who-chip:hover span { opacity: 1; }

/* A supplier taken out of Contacts after the line was written. Shown, and
   removable, rather than dropped silently — a name vanishing off a run sheet
   the first time somebody opens the line is not a thing to do quietly. */
.who-chip.is-gone {
  border-color: var(--line);
  background: var(--surface-soft);
  color: var(--ink-faint);
  font-style: italic;
}

/* ---------- text colours and highlighter pens ------------------------------

   These are deliberately plain, single-purpose classes. They sit on the time,
   on the title, and on spans inside notes, so the same red means the same
   thing wherever it appears on the run sheet. */

.c-red   { color: #b3001b; }   /* a proper red, not a warning-triangle orange */
.c-blue  { color: #0067a3; }
.c-pink  { color: var(--pink); }
.c-teal  { color: var(--teal-deep); }
.c-amber { color: #d4121f; }
.c-green { color: #00733d; }
.c-black { color: var(--ink); }

/* The pens are deliberately NOT the status tints. Until 31 Aug 2026 yellow was
   --warn-tint, green was --ok-tint and blue was --c-sky-tint, so a highlighted
   word wore the same colour as "due soon" or "done and paid". These sit one
   step more saturated than any tint, so a pen can never read as a status.
   Yellow leads because no status in the app is yellow — it is the one hue the
   pen owns outright. Grey is no longer offered in the picker (see HIGHLIGHTS
   in timeline.js) but is kept here so notes that already use it still draw. */
.h-yellow { background: #ffe89c; }
/* Added 9 Sept — Gee: "most likely, I'll highlight yellow or orange."
   Deliberately this side of orange rather than a strong one: on a black-and-
   white print a saturated orange drops to the same grey as the yellow, and two
   pens that print identically are one pen. */
.h-orange { background: #ffd0a1; }
.h-pink   { background: #ffc6dd; }
.h-blue   { background: #bcdcff; }
.h-green  { background: #aeecc6; }
.h-grey   { background: #e3dde1; }

/* The pen should look like a pen: it hugs the words, not the whole row. */
.h-yellow, .h-pink, .h-blue, .h-green, .h-grey {
  padding: 0 3px;
  border-radius: 2px;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

/* ⚠️ 800, NOT 700, AND THAT IS BECAUSE THE BASE MOVED. Every line title is 700
   now, so a 700 here would have silently turned her per-line Bold button into a
   control that does nothing — nine lines across her weddings use it, three of
   them on Tiffany & Liam. Figtree ships 800, so the emphasis survives.
   A toggle that stops changing anything is worse than one that was never
   built: she presses it, nothing moves, and she concludes it is broken. */
.is-bold      { font-weight: 800; }
.is-italic    { font-style: italic; }
.is-underline { text-decoration: underline; }

/* A coloured title still gets its left bar, so the run sheet scans the way
   the printed one does. */
.tl-row:has(.tl-title.c-red)   { border-left: 3px solid #b3001b;    padding-left: 12px; }
.tl-row:has(.tl-title.c-blue)  { border-left: 3px solid #0067a3;    padding-left: 12px; }
.tl-row:has(.tl-title.c-pink)  { border-left: 3px solid var(--pink); padding-left: 12px; }
.tl-row:has(.tl-title.c-teal)  { border-left: 3px solid var(--teal); padding-left: 12px; }
.tl-row:has(.tl-title.c-amber) { border-left: 3px solid #d4121f;    padding-left: 12px; }
.tl-row:has(.tl-title.c-green) { border-left: 3px solid #00733d;    padding-left: 12px; }

/* ---------- text-only items ------------------------------------------------

   No time column, no supplier — a heading or a block of detail that happens
   to live inside the run sheet. */

.tl-row.is-text {
  background: #faf7f9;
  border-left: 3px solid var(--line);
  padding-left: 12px;
}

.tl-row.is-text .tl-title {
  font-size: 1rem;
  letter-spacing: .01em;
}

/* ---------- section headings -----------------------------------------------

   Where the day divides — morning, ceremony, reception. Placed by hand and
   dragged like any other line, because only Gee knows where a day breaks.
   On screen it reads as a divider so she can see at a glance whether a line
   has landed in the right stretch of the day. */

/* Blue, asked for on 27 Aug: "that's pink everywhere, right? For all of those
   morning-of, for the grooms, for the brides, for the ceremony... I want that
   to be blue because it's too much pink happening."
   (These were turned grey earlier the same day WITHOUT being asked, and
   reverted. This time it is the request.)

   The sky already in the palette rather than a new blue, so the sheet keeps
   one set of colours. The band tint is light enough that the title needs a
   darker blue than the rule to stay readable — #0067a3 on #daf0ff is too
   close to call at .78rem. */
.tl-row.is-heading {
  background: var(--c-sky-tint, #daf0ff);
  border-left: 3px solid var(--c-sky, #0067a3);
  padding-left: 12px;
  margin-top: 14px;
}

.tl-row.is-heading .tl-title {
  color: #0067a3;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
}

/* 🛑 SLIM — "A", chosen 26 September. Gee: "It's too big. It doesn't have to
   be this thick… It's just to be able to break it up." Measured 83px on every
   heading on Solin & Rony: 11px padding, the title on one line, the place chip
   on the next, and an invisible "+ Add notes" row underneath still holding
   its space. Now the title, the chip and the hover links share ONE line with
   6px padding — about 38px. Same colours, words, chip and tag; her copy and
   the couple's copy both draw `.tl-row.is-heading`, and print.css has its own
   copy of this. Only headings — no other row is touched. */
.tl-row.is-heading {
  padding-top: 6px;
  padding-bottom: 6px;
  align-items: center;
}

.tl-row.is-heading .tl-main {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  column-gap: 10px;
  row-gap: 4px;
}

.tl-row.is-heading .who-line,
.tl-row.is-heading .tl-adds { margin-top: 0; }

/* A note on a heading still gets a line of its own. */
.tl-row.is-heading .tl-main > .tl-notes { flex-basis: 100%; }

/* ---------------------------------------------------------------------------
   Section heading bands — her choice, per heading. 9 September 2026.

   Gee, looking at RECEPTION · METROPOLIS: "we want to also be able to change
   the colors of that. Right now it's blue but we want to be able to choose
   from those beautiful pastel colors."

   Six of these are the door tints the sheet already uses, so a coloured
   heading agrees with the venue chips beside it. Butter and Stone are new.

   ⚠️ TINT AND DEEP TOGETHER, ALWAYS. The band is the pale one and the title is
   the dark one; setting only the background gives you #0067a3 lettering on a
   pink band, which is the state this had for about a minute while it was
   written. Every band below sets three things: background, left rule, title.

   No `.band-blue` rule exists on purpose — blank IS blue, and it is already
   drawn by the two rules above. Adding one would be a second copy to keep in
   step. The SWATCH needs a `.band-blue` (there is one further down) because a
   swatch has no blank state to fall back to.
   --------------------------------------------------------------------------- */
.tl-row.is-heading.band-mint     { background: var(--c-teal-tint);   border-left-color: var(--c-teal);   }
.tl-row.is-heading.band-lavender { background: var(--c-indigo-tint); border-left-color: var(--c-indigo); }
.tl-row.is-heading.band-rose     { background: var(--c-plum-tint);   border-left-color: var(--c-plum);   }
.tl-row.is-heading.band-peach    { background: var(--c-peach-tint);  border-left-color: var(--c-peach);  }
.tl-row.is-heading.band-sage     { background: var(--c-sage-tint);   border-left-color: var(--c-sage);   }
.tl-row.is-heading.band-butter   { background: var(--c-butter-tint); border-left-color: var(--c-butter); }
.tl-row.is-heading.band-stone    { background: var(--c-stone-tint);  border-left-color: var(--c-stone);  }

.tl-row.is-heading.band-mint     .tl-title { color: var(--c-teal);   }
.tl-row.is-heading.band-lavender .tl-title { color: var(--c-indigo); }
.tl-row.is-heading.band-rose     .tl-title { color: var(--c-plum);   }
.tl-row.is-heading.band-peach    .tl-title { color: var(--c-peach);  }
.tl-row.is-heading.band-sage     .tl-title { color: var(--c-sage);   }
.tl-row.is-heading.band-butter   .tl-title { color: var(--c-butter); }
.tl-row.is-heading.band-stone    .tl-title { color: var(--c-stone);  }

.swatches { display: flex; gap: 8px; flex-wrap: wrap; }

.swatch {
  width: 26px;
  height: 26px;
  padding: 0;
  border: 2px solid var(--line);
  border-radius: 50%;
  background: #fff;
  cursor: pointer;
  transition: transform .1s ease, border-color .1s ease;
}

.swatch:hover { transform: scale(1.08); }
.swatch.is-selected { border-color: var(--ink); }

.swatch.is-none {
  background:
    linear-gradient(to top left, transparent calc(50% - 1px), var(--ink-faint) 50%, transparent calc(50% + 1px));
}

/* The swatch shows the colour as a filled circle, so the colour classes above
   (which set text colour) have to be overridden here. */
.swatch.c-red   { background: #b3001b; }
.swatch.c-blue  { background: #0067a3; }
.swatch.c-pink  { background: var(--pink); }
.swatch.c-teal  { background: var(--teal); }
.swatch.c-amber { background: #d4121f; }
.swatch.c-green { background: #00733d; }

.swatch.h-yellow { background: #ffe89c; }
.swatch.h-orange { background: #ffd0a1; }
.swatch.h-pink   { background: #ffc6dd; }
.swatch.h-blue   { background: #bcdcff; }
.swatch.h-green  { background: #aeecc6; }
.swatch.h-grey   { background: #e3dde1; }

/* Band swatches — the section-heading colours. Drawn as the band itself: the
   pale fill with its own dark rule around it, so the circle in the menu is a
   small picture of the heading you are about to get rather than a dot in some
   related colour. `band-blue` is the no-choice default and needs a rule here
   even though the row does not — a swatch has no blank state to fall back to. */
.swatch.band-blue     { background: var(--c-sky-tint);    border-color: var(--c-sky);    }
.swatch.band-mint     { background: var(--c-teal-tint);   border-color: var(--c-teal);   }
.swatch.band-lavender { background: var(--c-indigo-tint); border-color: var(--c-indigo); }
.swatch.band-rose     { background: var(--c-plum-tint);   border-color: var(--c-plum);   }
.swatch.band-peach    { background: var(--c-peach-tint);  border-color: var(--c-peach);  }
.swatch.band-sage     { background: var(--c-sage-tint);   border-color: var(--c-sage);   }
.swatch.band-butter   { background: var(--c-butter-tint); border-color: var(--c-butter); }
.swatch.band-stone    { background: var(--c-stone-tint);  border-color: var(--c-stone);  }

/* The chosen one needs to beat the border-colour above, which the plain
   .is-selected rule earlier does not — it comes first and is no more specific. */
.swatch[class*="band-"].is-selected {
  border-color: var(--ink);
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 3px var(--ink);
}

/* Highlight swatches are squares — the same distinction Aisle Planner draws
   between the font colour and the pen. */
.swatch[class*="h-"] { border-radius: 5px; }

.swatch.sm { width: 20px; height: 20px; border-width: 1px; }


/* ---------- style bars ----------------------------------------------------- */

.style-bar,
.rt-toolbar {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  padding: 7px 9px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #faf7f9;
}

.rt-toolbar {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  border-bottom: 0;
}

.rt-btn {
  min-width: 28px;
  height: 28px;
  padding: 0 7px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--ink);
  font-size: .92rem;
  line-height: 1;
  cursor: pointer;
}

/* The bullet and the "1." are glyphs standing in for a whole idea, so they get
   a touch more size than the B and the I — at .92rem a dot point reads as a
   speck of dust. */
.rt-btn.rt-list { font-size: 1rem; font-weight: 700; }

.rt-btn:hover      { background: #fff; border-color: var(--line); }
.rt-btn.is-on      { background: var(--teal); border-color: var(--teal); color: #fff; }

/* "Just for me" is a word, not a letter, so it cannot be a 26px square like
   the B and the I. */
.rt-private {
  width: auto;
  padding: 0 10px;
  font-size: .78rem;
  font-weight: 600;
  white-space: nowrap;
}

/* Words that will not print on a supplier's copy.

   Marked on screen rather than left to memory — the whole feature is useless
   if she cannot tell at a glance which half of a note is private. A dashed
   underline and a faint tint: visible while reading the run sheet, quiet
   enough not to compete with the colours that mean something on the day. */
.is-private-note {
  background: var(--grey-tint, #f2f0f1);
  border-bottom: 1px dashed var(--ink-faint, #a0949a);
  border-radius: 2px;
  padding: 0 2px;
}

/* One line of a dot-pointed or numbered note.

   ⚠️ THIS RULE IS THE ONLY THING A LIST IS. The "•" and the "1." are real
   characters typed into the line — see markNoteLists() in ui.js — so a note
   still reads as a list with this stylesheet missing, in a plain-text export,
   or on a screen that has never heard of the class. All this does is hang the
   indent, so a line that wraps sits under its own words instead of falling
   back to the margin beside the marker.

   inline-block, not block: notes are a flat run of lines separated by <br>,
   and a block here would give every bullet a second empty line under it. */
.note-li {
  display: inline-block;
  vertical-align: top;
  max-width: 100%;
  padding-left: 1.15em;
  text-indent: -1.15em;
}
.rt-btn.is-on:hover{ background: var(--teal-deep); }

.rt-sep {
  width: 1px;
  height: 20px;
  background: var(--line);
  margin: 0 2px;
}

/* ---------- the notes editor ----------------------------------------------- */

.rt-editor {
  min-height: 92px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
  background: #fff;
  font: inherit;
  font-size: .92rem;
  line-height: 1.5;
  color: var(--ink);
  overflow-y: auto;
  max-height: 260px;
}

.rt-editor:focus {
  outline: 2px solid var(--teal);
  outline-offset: -1px;
}

.rt-editor:empty::before {
  content: attr(data-placeholder);
  color: var(--ink-faint);
}


/* ---------- budget: bank columns ------------------------------------------ */

.bg-table .mono {
  font-family: ui-monospace, "Cascadia Mono", "Segoe UI Mono", Menlo, monospace;
  font-size: .82rem;
  letter-spacing: .01em;
}

/* Details coming from the supplier record rather than typed on this line. */
/* Pay this one by card. Migration 102.

   Gee, 12 September: "some invoices are paid directly… They just have to click
   the button to go to the page to pay by credit card." And: "The payment link
   must be accessible to the couple, absolutely."

   ⚠️ IT SITS IN THE PAY METHOD CELL, beside the word "Card" that explains it,
   rather than in a column of its own — a whole column that is empty on every
   EFT and cash line is nine-tenths blank on a real budget.

   Teal because it is an action that takes you somewhere, not money and not a
   warning: green already means paid, crimson means late, orange means a
   finding. The couple sees this exact cell — their budget is this table MOVED,
   not a copy — so it has to read as a button to somebody who has never used
   the app. */
.bg-table .pay-link {
  display: inline-block;
  margin-left: 7px;
  padding: 1px 9px;
  border: 1px solid var(--teal-deep);
  border-radius: 999px;
  background: var(--surface);
  color: var(--teal-deep);
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .03em;
  text-decoration: none;
  white-space: nowrap;
}
.bg-table .pay-link:hover { background: var(--teal-deep); color: #fff; }
.bg-table .pay-link:focus-visible { outline: 2px solid var(--pink); outline-offset: 2px; }

/* It is a link to somewhere else, so it prints as nothing useful — a pill
   saying "Pay" on paper is a button nobody can press. */
@media print { .bg-table .pay-link { display: none; } }

/* (The `.bg-bank.is-inherited` rule that used to sit here has gone. It was the
   SECOND definition of a selector declared again 2,200 lines below, and the
   two disagreed — --ink-soft here, --ink-faint and italic there — so the
   later one silently won and this line had no effect at all. One definition,
   beside the other bg-bank rules. See one-idea-must-have-one-spelling. */

.bg-table .bg-date { white-space: nowrap; }

.bg-table.hide-bank .bg-bank { display: none; }

/* The Estimate and Difference columns, switched off from the toolbar. Same
   mechanism as the bank details above: a class on the table, not a rebuild of
   the rows, so nothing about the data changes when they are hidden. */
.bg-table.hide-estimate   .col-estimate   { display: none; }
.bg-table.hide-difference .col-difference { display: none; }


/* ---------- task assignees ------------------------------------------------ */

.who-bubble {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  margin-top: -1px;
  border: 1px solid var(--teal);
  border-radius: 50%;
  color: var(--teal-deep);
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .02em;
}

.who-bubble.is-empty { border-color: var(--line); color: var(--ink-faint); }

.who-bubble.lg {
  width: 32px;
  height: 32px;
  margin-right: 10px;
  border-color: var(--pink);
  color: var(--pink);
  font-size: .74rem;
}

.task-group.is-assignee .task-group-head {
  align-items: center;
  text-transform: none;
  letter-spacing: .02em;
  font-size: .95rem;
}

.task-link { font-size: .78rem; }

/* "Notion" on a task that was pulled from the board rather than typed here.
   A quiet chip, not a colour: it answers "where did this come from" and must
   not compete with the date or the person the task belongs to. */
.task-source {
  display: inline-block;
  padding: 1px 7px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--line-soft);
  color: var(--ink-soft);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-decoration: none;
  vertical-align: 1px;
}

.task-source:hover {
  border-color: var(--ink-faint);
  background: #fff;
  color: var(--ink);
}


/* ---------- suppliers on a wedding ---------------------------------------- */

/* The templates list in Settings. A name alone tells you nothing about whether
   a template is worth applying, so every row says what is in it. */
.tpl-group { margin-bottom: 22px; }
.tpl-group h3 {
  margin: 0 0 2px;
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--teal-deep);
}
.tpl-group .ov-sub { margin-bottom: 10px; }

.tpl-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #fff;
  margin-bottom: 6px;
}

.tpl-main { min-width: 0; flex: 1; }
/* Ticked by default on the sign-in card. Sits under the code button rather
   than beside it: a checkbox on the same line as a primary button gets
   half-pressed on a phone. */
.login-stay {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 12px 0 2px;
  font-size: .86rem;
  color: var(--ink-soft);
}

/* A modal that asks one thing does not want the width of one that holds a
   whole wedding. */
.modal-sm { max-width: 420px; }

/* ---------------------------------------------------------------------------
   The status pill.

   One shape, three colours, used on every tab. A pill and never a background,
   because the arrival cards already colour a whole card by VENUE — pink is the
   bride's, green is the church — and a green background meaning "confirmed"
   sitting next to a green card meaning "church" is exactly the confusion that
   made the run sheet unreadable in the first place.

   Different shape, different place: venue is a heading or a fill, status is a
   small rounded thing on the right. They can never be mistaken for each other.
   --------------------------------------------------------------------------- */
.pill {
  display: inline-block;
  padding: 2px 9px;
  border-radius: var(--radius-pill);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .01em;
  white-space: nowrap;
}

.pill.is-ok   { background: var(--ok-tint);   color: var(--ok); }
.pill.is-warn { background: var(--warn-tint); color: var(--warn); }
.pill.is-late { background: var(--late-tint); color: var(--late); }

/* Everything that was already saying "overdue" or "done" in its own colour now
   says it in the same three. Repointed rather than rewritten: these rules were
   scattered across the checklist, the budget, the overview and the supplier
   stages, each with its own green and its own red. */
.ov-list .ov-when.is-overdue,
.task-meta .due.is-overdue,
.task-due.is-overdue,
.bg-table .is-overdue { color: var(--late); font-weight: 600; }

/* Which door a line belongs to, on the line, in that door's own colour.
   This is what replaced the section it used to sit inside — the same colour
   the place already wears on its arrival card, so "pink means the bride's"
   holds from the top of the sheet to the bottom.
   Floated right so the day reads as time down the left and place down the
   right, and neither column interrupts the other. */
.tl-door {
  flex: 0 0 84px;
  align-self: flex-start;
  margin-top: 1px;
  padding: 2px 0;
  border-radius: var(--radius-pill);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .03em;
  white-space: nowrap;
  text-transform: none;
  text-align: center;
}

/* A line that names nowhere still holds the column open, so every tag on the
   page sits on the same vertical line. An empty cell that collapses would
   stagger the whole right edge and undo the point of having a column. */
.tl-door.is-empty { background: none; }

/* It is a BUTTON now (17 Sept) — Gee: "there is no way for me to be able to
   amend it or change it on the right-hand side, which I should be able to do."
   These four lines only undo what a browser does to a <button>, so the column
   looks exactly as it did before. */
button.tl-door {
  border: 0;
  font-family: inherit;
  cursor: pointer;
  line-height: inherit;
}
/* 🛑 THE "WHERE" BOX, ON SCREEN TOO — 30 SEPTEMBER. It shipped for paper in
   the morning; Gee, an hour later: *"It should be the same view because
   that's very clean and how we want the running sheet to look on screen as
   well."* CHECK 52 — one document, two renderers.

   The tag becomes a box, the same width on every line, with the building
   under it on every line that has one saved (her "A"). The chip for a
   place ALREADY SET leaves the who-line, because the box says it and pressing
   the box opens the same list of places (plus "Borrow it from the heading").

   ⚠️ THE UNSET "+ place" STAYS. On a phone the empty "Set a place" tag is
   hidden for room (below), so "+ place" is the only way to set a place there.
   ⚠️ `li.tl-row` ONLY — the couple's copy is `div.tl-row` and untouched. */
li.tl-row > button.tl-door {
  flex-basis: 124px;
  padding: 3px 8px;
  border-radius: 6px;
  line-height: 1.3;
  text-align: left;
  white-space: normal;
}
li.tl-row > button.tl-door.is-empty { border-radius: 6px; }
/* 🛑 THE BOX FITS ITS WORDS — 21 October, CHECK 227. Gee, once "Bride's Home"
   was said once (CHECK 224): "make this box a little bit smaller because
   there's a bit too much gap there." She picked A from two drawn on her sheet:
   each box hugs its words (up to the old 124px), right edges still in one line
   against the sheet's edge. B (all 100px) broke "Metropolis Events" onto a
   third line. SCREEN ONLY and not on a phone — print.css keeps its own box
   until she has seen it in print preview; under 620px the box sits under the
   title already. */
@media screen and (min-width: 621px) {
  li.tl-row > button.tl-door {
    flex: 0 0 auto;
    width: auto;
    max-width: 124px;
    margin-left: auto;
  }
}
.tl-door-place { display: block; font-weight: 700; }
/* 🛑 THE CHIP'S WRITING, NOT SMALLER. Gee, the same afternoon: *"I feel like
   the colours aren't as good as they were."* Measured: the colours were
   IDENTICAL — what had changed was the writing, 11.5px bold in the old chip
   against 10.5px and 9.9px/600 in the box, plus borrowed boxes faded to 82%.
   Pale type on a pale tint reads as a paler colour. Both lines now match the
   old chip (11.5px, 700), and a borrowed box keeps its dotted underline but
   not the fade. */
/* ⚠️ `@media screen`: at (0,3,2) this would out-weigh print.css's 7.5pt tag
   and blow the printed box up to screen size. Paper keeps its own point size. */
@media screen {
  /* .74rem, not .72: the root here is 15.5px, and .72 measured 11.16px
     against the old chip's 11.47px. .74 is 11.47. */
  li.tl-row > button.tl-door:not(.is-empty) { font-size: .74rem; font-weight: 700; }
}
li.tl-row > button.tl-door.is-lent { opacity: 1; }
li.tl-row .who-line .who-place.is-set { display: none; }
/* Her heading switch takes the WHOLE box — tag and building — on every line
   below it (CHECK 74). Gee, 30 Sept, on Tiffany's "Reception Commences":
   "where I've switched it from, I wanted to no longer show the reception."
   `visibility`, not `display`: the column keeps its width, so the lines below
   stay aligned with the lines above. */
li.tl-row.places-off > button.tl-door { visibility: hidden; }
button.tl-door:hover { filter: brightness(.95); }
button.tl-door:focus-visible { outline: 2px solid var(--calm, #007c85); outline-offset: 2px; }

/* 🛑 BORROWED, NOT SET. A dotted underline on a chip that came from the heading
   above rather than from this line.
   This is the thing she could not see: four reception lines read "National
   Gallery of Victoria" for weeks because they had no place of their own and
   took the nearest heading's. The colour still says WHERE; the dots say
   WHERE IT CAME FROM. */
button.tl-door.is-lent {
  text-decoration: underline dotted currentColor 1px;
  text-underline-offset: 3px;
  opacity: .82;
}

/* Nothing at all to show, and now something to press.
   🛑 NOT `opacity: 0` BEHIND A HOVER. That was written here first and deleted
   before it shipped: it is exactly how "Settled — don't ask again" failed Gee
   three times in one day, and `a-hover-reveal-she-has-to-be-told-about` is the
   whole note about it. A control she cannot see is not a control.
   Quiet, then — not hidden. It reads as a faint "Set a place" on every line
   that has none, which is also the honest signal that the line is borrowing. */
button.tl-door.is-empty {
  color: var(--ink-faint, #a0949a);
  font-weight: 600;
  font-size: .62rem;
  border: 1px dashed var(--line, #ece5e8);
  border-radius: var(--radius-pill);
}
button.tl-door.is-empty:hover { color: var(--ink-soft, #6c6067); border-style: solid; }

@media (max-width: 620px) {
  /* No room for a third column on a phone: it goes back under the title,
     where at least it is still attached to the line it describes. */
  .tl-door { flex-basis: auto; padding: 2px 9px; }
  .tl-door.is-empty { display: none; }
}

/* 🛑 ONE PALETTE PER PLACE, AND THESE FIVE LINES ARE THE SOURCE OF IT.
   Gee, 17 September: "the place being the same colour for everything… it's just
   too confusing," and then the part that mattered: "we do have to make sure the
   colours are still in alignment with the supplier arrival times."

   ⚠️ THEY WERE NOT ALIGNED, AND SHE WAS RIGHT TO ASK. Measured on her sheet:
   `.arr-card.tone-pink` used `--pink` (#e8197a) while `.tl-door.door-pink` used
   `--pink-deep` (#c31465) — the same place, two pinks, in two blocks a
   screen apart. Blue, green and plum already matched; pink did not, and the
   arrivals block had no coral at all, so a National Gallery arrival could never
   have worn the colour its run sheet tag does.

   `--pink-deep` wins because it is the darker of the two and everything else
   tonight has been about reading the thing at a glance.

   The `.door-*` classes below are now worn by THREE things — the tag on the
   right, the place chip in the line, and the heading's "at X" — so a place
   looks the same wherever it appears. See `one-question-must-have-one-matcher`:
   one answer, one colour, one place to change it.

   🛑 27 SEPTEMBER: THE WRITING IS BLACK NOW, AND ONLY THE BACKGROUND CARRIES
   THE PLACE. Gee, on Solin & Rony's "Groom's House" chip beside the "Groom's"
   tag: "it looks like the colors on the groom's house, and the color on the
   right-hand side is different… I am also happy for the font to be in black,
   and then the color in the background to be the same."

   ⚠️ THEY WERE NEVER DIFFERENT COLOURS. Measured: both rgb(238, 242, 250)
   behind rgb(30, 63, 125). The chip carried a ring in its writing colour and
   the tag did not, and on a background that pale the ring is what the eye
   reads. Black writing and no ring on all three — tag, chip, heading "at" —
   leaves the background as the one thing that says which place it is.

   ⚠️ THE BACKGROUNDS DID NOT MOVE, and that is what keeps them matched with
   the Supplier Arrivals boxes (`--tone-tint` in checks.css). CHECK 19 compares
   the BACKGROUND now, not the writing. A stronger blue was offered and turned
   down for exactly that reason: the Groom's arrivals box wears this same pale
   blue. CHECK 73 keeps the three place elements identical to each other. */
.tl-door.door-pink  { background: var(--pink-tint);   color: var(--ink); }
.tl-door.door-blue  { background: var(--blue-soft);   color: var(--ink); }
.tl-door.door-green { background: var(--c-sage-tint); color: var(--ink); }
.tl-door.door-plum  { background: var(--c-plum-tint); color: var(--ink); }
.tl-door.door-coral { background: var(--c-peach-tint); color: var(--ink); }

/* Print is handled in print.css, where the row is a grid and the place needs a
   column of its own — setting it here as well only fought with it. */

/* When a section runs, on its own heading. Pushed to the right so the eye can
   read the times down the page as a column and see the shape of the day —
   which is how the reception opening an hour before the photographs finish
   became visible at all. */
.tl-span {
  float: right;
  font-size: .76rem;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--blue);
  opacity: .8;
  font-variant-numeric: tabular-nums;
  text-transform: none;
}

@media print {
  .tl-span { color: #1e3f7d !important; opacity: 1; font-size: 8pt; }
}

/* "leaves" in front of a name on an arrival card. Small and amber rather than
   loud: it is a correction to what the line would otherwise mean, not a
   warning. A car at the bride's house at 12:40 is the bride going, and a
   supplier reading the card needs to know that is not a bump-in. */
.arr-out {
  display: inline-block;
  padding: 0 5px;
  border-radius: 3px;
  background: var(--warn-tint);
  color: var(--warn);
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
}

/* An arrivals line whose card already carries the heading. Kept in the DOM and
   still clickable to rename — just not shouting the same thing twice. It
   appears on hover and on focus, so the way to edit it is to go near it. */
.tl-title.is-quiet {
  font-size: .74rem;
  color: var(--ink-faint);
  font-weight: 400;
  opacity: 0;
  transition: opacity .12s ease;
}
.tl-row:hover .tl-title.is-quiet,
.tl-title.is-quiet:focus { opacity: 1; }

@media print { .tl-title.is-quiet { display: none; } }

/* Reminder points, as chips. A dropdown of six fixed options is exactly what
   Gee ran out of, so this is a list you add to in your own words. */
.rem-chips { display: flex; flex-wrap: wrap; gap: 6px; min-height: 30px; align-items: center; }

.rem-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px 4px 11px;
  background: var(--teal-tint);
  border: 1px solid var(--teal);
  border-radius: var(--radius-pill);
  font-size: .84rem;
  color: var(--teal-deep);
  font-weight: 600;
}

.rem-x {
  background: none;
  border: 0;
  padding: 0 3px;
  font-size: 1rem;
  line-height: 1;
  color: var(--teal-deep);
  cursor: pointer;
  opacity: .6;
}
.rem-x:hover { opacity: 1; color: #b3001b; }

.rem-none { font-size: .86rem; color: var(--ink-faint); font-style: italic; }

/* Who has to be fed differently. Under the coverage strip because it is the
   same kind of thing — one fact about the whole team, gathered rather than
   hidden one Edit button at a time. Amber, not red: it is a thing to hand the
   caterer, not a problem. */
.sup-diet {
  margin-top: 10px;
  padding: 10px 13px;
  background: #ffe3e4;
  border: 1px solid #ffb74d;
  border-left: 3px solid #d4121f;
  border-radius: var(--radius-sm);
}

.sup-diet-head {
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: #d4121f;
  margin-bottom: 6px;
}

.sup-diet-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.sup-diet-list li { display: flex; gap: 10px; font-size: .88rem; }
.sup-diet-who  { font-weight: 600; color: var(--ink); }
.sup-diet-what { color: #d4121f; }

/* ---------------------------------------------------------------------------
   A task, open where it sits.

   Indented to the width of the row's controls so it reads as belonging to the
   line above rather than as a new item in the list. Tinted, because the one
   thing worse than not being able to find the open task is not being able to
   tell where it ends.
   --------------------------------------------------------------------------- */
.task-row.is-open { border-bottom: 0; }

.task-panel {
  position: relative;          /* the close button hangs off this */
  list-style: none;
  margin: 0 0 8px;
  /* 40px on the right, not 16 — the close button sits up there and the
     Visibility dropdown would otherwise run underneath it. */
  padding: 10px 40px 12px 56px;
  background: var(--surface-soft);
  border: 1px solid var(--line);
  border-top: 0;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
}

/* The way out. Always visible, not on hover — the whole complaint was not
   being able to find one, and a control you have to discover by hovering is
   not findable. Top right, where a close has been for thirty years. */
.tp-close {
  position: absolute;
  top: 6px;
  right: 8px;
  width: 27px;
  height: 27px;
  padding: 0;
  /* Bordered and filled from the start, not on hover. A control you only see
     once you are already touching it does not answer "how do I get out". */
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink-soft);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  transition: background .12s ease, color .12s ease, border-color .12s ease;
}

.tp-close:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--surface);
}

@media print { .tp-close { display: none; } }

.tp-notes { margin: 0 0 10px; font-size: .88rem; color: var(--ink-soft); line-height: 1.6; }

/* Due, who and visibility on one line. Everything else stays in Details —
   this is what you change while working, not while building. */
.tp-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 16px;
  padding: 8px 11px;
  margin-bottom: 12px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}

.tp-bit { display: flex; align-items: center; gap: 6px; font-size: .8rem; }
.tp-bit > span:first-child { color: var(--ink-faint); font-size: .74rem; }
.tp-bit input, .tp-bit select { height: 28px; font-size: .82rem; padding: 2px 6px; }
.tp-bit em {
  font-style: normal; font-weight: 600; font-size: .78rem;
  padding: 1px 8px; border: 1px solid var(--line); border-radius: var(--radius-pill);
}
.tp-bit em.is-empty { color: var(--ink-faint); font-weight: 400; }
.tp-right { margin-left: auto; }

/* The thread. Nothing is truncated and nothing collapses — Gee's complaint
   about Aisle Planner was that a long comment gets minimised, and a comment is
   long because it mattered. */
.tp-thread { list-style: none; margin: 0 0 10px; padding: 0; display: flex; flex-direction: column; gap: 9px; }
.tp-thread li { font-size: .86rem; }
.tp-who  { font-weight: 700; margin-right: 7px; }
.tp-when { color: var(--ink-faint); font-size: .74rem; }
.tp-body { display: block; margin-top: 2px; white-space: pre-wrap; }
.tp-none { color: var(--ink-faint); font-style: italic; font-size: .82rem; }

/* The box, then its buttons underneath it.
   They used to sit to the RIGHT of the textarea, which on a panel as wide as
   the checklist meant crossing the whole screen to reach a paperclip. */
.tp-compose { display: flex; flex-direction: column; gap: 7px; }
.tp-compose textarea {
  width: 100%; min-width: 0; min-height: 34px; max-height: 130px;
  font-size: .86rem; resize: vertical;
}

.tp-tools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* A clip on the compose, so a comparison can arrive attached to the sentence
   that explains it rather than filed silently against the task. Labelled,
   not just an icon — a bare paperclip is a guess. */
.tp-clip,
.tp-send {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--ink-soft);
  font: inherit;
  font-size: .78rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color .12s ease, color .12s ease, background .12s ease;
}

.tp-clip:hover { border-color: var(--teal); color: var(--teal-deep); }

.tp-send {
  border-color: var(--teal-deep);
  background: var(--teal-deep);
  color: #fff;
  font-weight: 600;
}
.tp-send:hover { background: var(--teal); border-color: var(--teal); }

/* Enter is how it is actually sent; this says so once, quietly, instead of
   leaving it to be discovered. */
.tp-hint { font-size: .72rem; color: var(--ink-faint); }
@media (max-width: 620px) { .tp-hint { display: none; } }

.tp-picked {
  margin: 6px 0 0;
  font-size: .78rem;
  color: var(--ink-soft);
}

/* A file said in a conversation is styled in checks.css, NOT here.
   checks.css loads after this file, so a definition here loses silently —
   which is how a chip I wrote on 29 Aug never appeared. Look there. */
.tp-drop {
  margin-left: 8px;
  border: 0; background: none; padding: 0;
  color: var(--danger); font: inherit; font-size: .76rem;
  text-decoration: underline; cursor: pointer;
}

/* Reminders on a task — a list you add to, the shape Gee showed me in Aisle
   Planner. Sits under the date strip because that is what it hangs off. */
/* ✉️ Remind couple — one press, one email (20 Oct, CHECK 213/214). First on
   the recommendations line, the SAME pill as .rcm-open beside it, in the
   app's pink: purple = Recommend suppliers, sky = Add saved link. */
/* SOLID, full colour (Gee, 20 Oct: "it's not the full pink color"). The pale
   one read as already pressed. The app's bright magenta, white words. */
.tp-nudge-btn {
  border: 1.5px solid #e8197a; background: #e8197a; color: #fff; border-radius: 999px;
  padding: 6px 14px; font: inherit; font-weight: 800; font-size: .85rem; cursor: pointer;
}
.tp-nudge-btn:hover:not(:disabled) { background: #d0106b; border-color: #d0106b; }
.tp-nudge-btn:disabled { opacity: .5; cursor: default; }
.tp-nudge-note {
  font-size: .78rem;
  color: var(--ink-faint);
}

/* Remind me IN THE PANEL: a teal panel named on its top edge, like the budget
   window's (20 Oct, CHECK 214). Teal because sky is already Add saved link
   ("too similar… change the color of one or the other") and the picked days
   were teal already. Scoped to [data-name] so the Edit task window's copy
   (#t-reminders, also .tp-rem) is untouched. */
.task-panel .tp-rem[data-name] {
  position: relative;
  margin: 18px 0 12px;
  padding: 16px 14px 10px;
  background: #d9f7f2;
  border: 2px solid #9fe3d6;
  border-radius: 16px;
}
.task-panel .tp-rem[data-name]::before {
  content: attr(data-name);
  position: absolute; top: -11px; left: 14px;
  padding: 1px 10px; border-radius: 999px; line-height: 1.4;
  background: #d9f7f2; border: 2px solid #9fe3d6;
  color: #00806e; font-weight: 800; font-size: .72rem;
  letter-spacing: .08em; text-transform: uppercase;
}
.task-panel .tp-rem[data-name] .tp-rem-opt {
  background: #fff; border: 1.5px solid #7fd6c6; color: #00735f; font-weight: 700;
}
.task-panel .tp-rem[data-name] .tp-rem-opt:hover { border-color: #00a68f; }
.task-panel .tp-rem[data-name] .tp-rem-opt.is-on { background: #00a68f; border-color: #00a68f; color: #fff; }
.task-panel .tp-rem[data-name] .tp-rem-pick { color: #00735f; font-weight: 700; }
.tp-rem-says { margin: 8px 0 0; font-size: .82rem; color: #0b5a4f; }

.tp-rem {
  margin-bottom: 12px;
  padding: 9px 11px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}

/* The same control, in the Edit task window rather than the panel.
   The panel draws it as a little card because it floats among comments; in a
   form it already has a <label> above it, so a second frame would be a box
   inside a box. Everything below — the buttons, the picker, the dates — is
   shared, which is the whole point of moving it rather than rebuilding it. */
#t-reminders.tp-rem {
  margin-bottom: 0;
  padding: 0;
  background: none;
  border: 0;
}

.tp-rem-head {
  display: block;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 5px;
}

/* Buttons, not a box you type into. The first version asked her to type
   "a week, 3 days, on the day" and she said it was confusing — which it was.
   Five intervals, press to turn on, press again to turn off. Filled means on,
   so what is set is readable without opening anything. */
.tp-rem-opts { display: flex; flex-wrap: wrap; gap: 5px; }

.tp-rem-opt {
  padding: 4px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--ink-soft);
  font: inherit;
  font-size: .78rem;
  line-height: 1.2;
  cursor: pointer;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
}

.tp-rem-opt:hover { border-color: var(--teal); color: var(--teal-deep); }

.tp-rem-opt.is-on {
  background: var(--teal-deep);
  border-color: var(--teal-deep);
  color: #fff;
  font-weight: 600;
}

.tp-rem-note {
  margin: 6px 0 0;
  font-size: .76rem;
  color: var(--ink-faint);
}

/* Naming the day instead of counting backwards to it. Sits on the end of the
   chips, deliberately quieter than they are — it is the escape hatch for the
   date the five buttons don't cover, not the first thing to reach for. */
.tp-rem-pick {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding-left: 4px;
  font-size: .76rem;
  color: var(--ink-faint);
}

.tp-rem-pick input {
  height: 28px;
  padding: 2px 8px;
  font-size: .78rem;
}

/* .tp-send was a 34px square holding a bare return arrow, sitting to the
   right of the textarea. It moved under the box on 29 Aug and is styled with
   .tp-clip up above — this block was overriding that one and squashing it
   back into a square, so it is gone rather than left to fight. */

@media print { .task-panel { display: none; } }

/* The team. Deliberately plain rows — this is a list of who can read a
   couple's bank details, and it should read like a fact, not a feature. */
.team-list { display: flex; flex-direction: column; gap: 1px; margin-bottom: 12px; }

.team-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 12px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  font-size: .9rem;
}
.team-row:first-child { border-radius: var(--radius-sm) var(--radius-sm) 0 0; }
.team-row:last-child  { border-radius: 0 0 var(--radius-sm) var(--radius-sm); }
.team-row.is-pending  { background: var(--surface-soft); }

.team-who   { font-weight: 600; }
.team-role  { font-size: .74rem; letter-spacing: .06em; text-transform: uppercase; color: var(--teal-deep); }
.team-state { margin-left: auto; font-size: .82rem; color: var(--ink-faint); }
.team-row .team-del { margin-left: 8px; }
.team-row .team-del:hover { color: #b3001b; }

.tpl-name { display: block; font-weight: 600; font-size: .92rem; }

/* The name is the control. A template you can only rename is a box with no
   lid, which is exactly what Gee found on 28 Aug. */
.tpl-open {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--teal-deep);
  text-align: left;
  cursor: pointer;
}
.tpl-open:hover { text-decoration: underline; }
.tpl-row.is-open { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }

.tpl-panel {
  margin: 0 0 12px;
  padding: 12px 14px;
  background: var(--surface-soft);
  border: 1px solid var(--line);
  border-top: 0;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
}

.tpl-items { list-style: none; margin: 0 0 10px; padding: 0; display: flex; flex-direction: column; gap: 6px; }

/* Columns come from the kind — a checklist line and a budget line do not hold
   the same things, and forcing them into one shape would waste half the row on
   both. The template literal sets grid-template-columns per row. */
.tpl-item { display: grid; gap: 6px; align-items: center; }

.tpl-in {
  width: 100%;
  min-width: 0;
  padding: 5px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font: inherit;
  font-size: .86rem;
  background: #fff;
}
.tpl-in:focus { outline: 2px solid var(--teal); outline-offset: -1px; }

.tpl-item-acts { display: flex; gap: 2px; }

/* "14 days before" under the number, because the box holds days BEFORE the
   anchor and that reads backwards for anything after somebody signs. */
.tpl-item-hint {
  grid-column: 1 / -1;
  font-size: .74rem;
  color: var(--ink-faint);
  margin-top: -3px;
}

/* The budget half of a project template, opened under its checklist. */
.tpl-sub { margin: -8px 0 12px; padding-left: 14px; border-left: 2px solid var(--teal-tint); }
.tpl-sub-head {
  background: none;
  border: 0;
  padding: 4px 0;
  font: inherit;
  font-size: .84rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--teal-deep);
  cursor: pointer;
}
.tpl-sub-head:hover { text-decoration: underline; }
.tpl-sub .tpl-panel { border-radius: var(--radius-sm); border-top: 1px solid var(--line); }
.tpl-what { display: block; font-size: .78rem; color: var(--muted); }
.tpl-desc { display: block; font-size: .78rem; color: var(--muted); font-style: italic; }

.tpl-acts { display: flex; gap: 2px; flex: none; }
.tpl-acts .tpl-del:hover { color: #b3001b; }

@media (max-width: 620px) {
  .tpl-row { flex-direction: column; align-items: stretch; }
  .tpl-acts { justify-content: flex-end; }
}

/* Where a wedding goes. One row per place, as many as it needs. */
.place-row {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.3fr) minmax(0, 1fr) auto;
  gap: 6px;
  align-items: center;
  margin-bottom: 6px;
}

/* "Rename this venue everywhere" sits UNDER its row, spanning it, rather than
   becoming a fifth column — the three boxes are already tight at this width and
   a button beside them would squeeze the address that has to hold "65 Melton
   Hwy, Taylors Lakes VIC 3038".

   Quiet by design: it is a deliberate, business-wide action reached from a
   form about one wedding, so it should read as a link you go looking for, not
   a button sitting next to the delete cross. */
.place-rename {
  grid-column: 1 / -1;
  justify-self: start;
  margin: 0 0 2px;
  padding: 2px 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: .76rem;
  color: var(--ink-soft, #6b5a63);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.place-rename:hover,
.place-rename:focus-visible { color: var(--pink, #e8197a); }

.place-row input {
  min-width: 0;
  padding: 6px 9px;
  font-size: .86rem;
}

.place-move { display: flex; gap: 2px; }

.place-move button {
  width: 26px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: #fff;
  color: var(--muted);
  font-size: .82rem;
  line-height: 1;
  cursor: pointer;
}

.place-move button:hover:not(:disabled) { border-color: var(--ink-faint, #a0949a); color: var(--ink); }
.place-move button:disabled { opacity: .35; cursor: default; }
.place-move button[data-place-del]:hover { border-color: #ff8a95; color: #b3001b; }

@media (max-width: 720px) {
  .place-row { grid-template-columns: 1fr auto; }
  .place-row .place-addr, .place-row .place-label { grid-column: 1 / -1; }
}

/* "Is everyone accounted for" — above the table, never inside it. Ten columns
   is already a lot, and Gee's own objection to three more was "that's going to
   be a massive supplier tab." This costs no width at all. */
.sup-cover {
  margin: 0 0 12px;
}

.sup-cover:empty { display: none; }

/* ⚠️ SENTENCE CASE, NOT SHOUTING. Gee, 11 Sept: "They should not be capital,
   and maybe the text should be black, just to stand out a little bit more."

   It was 0.7rem uppercase teal with wide tracking — the styling of a field
   label, on what is actually a question worth reading. The orange is untouched;
   she was explicit that she likes it. */
.sup-cover-head {
  color: var(--ink);
  font-size: .86rem;
  font-weight: 700;
  letter-spacing: 0;
  margin-bottom: 7px;
}

.sup-cover-axes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 8px;
}

.sup-cover-axis {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #fff;
}

/* A number, not a colour, does the work — the count IS the finding. Green on a
   panel that already uses the traffic light for payments would be a second
   meaning for one colour (DECISIONS §4). */
.sup-cover-mark {
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: .78rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ALL CLEAR, and said properly.

   Gee, 30 Aug: "I think the others should be green, bolded, and a bit more out
   there." Then, when told green already means PAID on the budget traffic
   light: "Leave the understanding of green being paid, and then you figure out
   a different colour for it."

   So: BLUE. Not a compromise — it is the right one. Blue already means
   suppliers and people across this app, and "all 20 on the run sheet" is a
   statement about suppliers. Filled and bold, so it carries the same weight as
   the amber gap beside it: a tick you cannot see is a reassurance nobody gets. */
.sup-cover-axis.is-ok {
  background: #eef2fa;
  border: 2px solid #0091ea;
}

.sup-cover-axis.is-ok .sup-cover-mark { background: #0067a3; color: #fff; }
.sup-cover-axis.is-ok .sup-cover-text b { color: #1e3f7d; font-weight: 800; }

.sup-cover-axis.is-none .sup-cover-mark { background: var(--line-soft); color: var(--muted); }

/* A GAP IS LOUD NOW.

   Gee, 30 Aug, looking at "8 on no arrival card": "I need that to be full
   yellow and red text so it really stands out."

   Amber rather than the red she asked for, and this is the one place I went
   against the wording. Red already means OVERDUE everywhere else in this app —
   on payments, on tasks, on a TBC arrival time. Spending it here would make
   both meanings weaker, and an overdue payment is the one thing that must
   never have to compete for the eye. So: the loudest thing on the panel, in a
   colour that is still only ever "look at this". Say the word and it is red.

   Filled, not tinted — this used to be a white card with an amber border and
   it read as one of three, which is exactly why eight suppliers went unseen. */
.sup-cover-axis.is-gap {
  background: #ffe3e4;
  border: 2px solid #ff6f00;
}

.sup-cover-axis.is-gap .sup-cover-mark {
  background: #ff6f00;
  color: #fff;
}

/* The label stays orange — it IS the warning. The NAMES go to ink, because
   they are names: reading them as part of the warning is what made the strip
   hard to scan. */
.sup-cover-axis.is-gap .sup-cover-text b { color: #d4121f; font-weight: 800; }
.sup-cover-axis.is-gap .sup-cover-who    { color: var(--ink); }

/* ---- and it opens ---------------------------------------------------------
   Gee: "I want all of these to be clickable. I want to know, when I click on
   it, who they are in each box." "and 5 more" was a dead end — finding the
   other five meant reading fourteen table rows for what was absent, which is
   the job this strip exists to have already done. */
.sup-cover-axis.is-gap,
.sup-cover-axis.is-excused { display: block; padding: 0; }

/* NOT A GAP — AN ANSWER. Suppliers ticked "not required on the wedding day".
   Teal, the same colour this strip already uses for "all covered", because
   orange here is what made the arrivals box read 4 when the real number was 0:
   every name in it had already been answered. */
.sup-cover-axis.is-excused {
  background: var(--teal-tint, #e6f7f8);
  border: 2px solid var(--teal);
}

.sup-cover-axis.is-excused .sup-cover-mark   { background: var(--teal-deep); color: #fff; }
.sup-cover-axis.is-excused .sup-cover-text b { color: var(--teal-deep); font-weight: 800; }
.sup-cover-axis.is-excused .sup-cover-chev   { color: var(--teal-deep); }
.sup-cover-axis.is-excused .sup-cover-open:hover { background: rgba(0, 159, 170, .08); }
.sup-cover-axis.is-excused .sup-cover-list li { border-bottom-color: rgba(0, 124, 133, .14); }

.sup-cover-open {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  width: 100%;
  padding: 9px 11px;
  background: none;
  border: 0;
  text-align: left;
  font: inherit;
  cursor: pointer;
}

.sup-cover-open:hover  { background: rgba(255, 111, 0, .07); }
.sup-cover-open:focus-visible { outline: 2px solid #ff6f00; outline-offset: -2px; }

.sup-cover-chev { margin-left: auto; color: #d4121f; font-weight: 800; flex: 0 0 auto; }

.sup-cover-list {
  list-style: none;
  margin: 0;
  padding: 2px 11px 10px 42px;   /* lines up under the text, clear of the count */
  font-size: .85rem;
}

.sup-cover-list li {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 3px 0;
  border-bottom: 1px solid rgba(179, 78, 0, .14);
  color: var(--ink);
}

.sup-cover-list li:last-child { border-bottom: 0; }
.sup-cover-list .linkish { margin-left: auto; font-size: .78rem; }

.sup-cover-text { min-width: 0; font-size: .84rem; line-height: 1.4; }
.sup-cover-text b { font-weight: 600; }

.sup-cover-who {
  display: block;
  color: var(--muted);
  font-size: .78rem;
  margin-top: 1px;
}

.sup-table { width: 100%; border-collapse: collapse; font-size: .88rem; }

.sup-table thead th {
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
  color: var(--teal);
  font-size: .67rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
  text-align: left;
  white-space: nowrap;
}

.sup-table td { padding: 9px 10px; border-top: 1px solid var(--line); vertical-align: middle; }
.sup-table tbody tr:hover { background: #faf7f9; }

/* Sky since 19 Oct, to match the frozen line beside it (pink text AND a pink
   line was "a bit too pink"). --c-sky is the deep sky, readable on white. */
.sup-role { color: var(--c-sky, #0067a3); font-weight: 600; }

/* 19 Oct — Gee: "It's very rare that I'm going to have to delete any of
   that… really annoying having those buttons there." The tick boxes only
   feed "Remove N from this wedding", so they hide until she presses Select.
   The button lives in suppliers.js (toggleSupSelecting). The class sits on
   the TAB, which the table re-render never replaces. CHECK 202. */
#wtab-suppliers:not(.sup-selecting) [data-pick-sup],
#wtab-suppliers:not(.sup-selecting) #sup-select-all { display: none; }
/* The whole row opens the supplier, so the whole row says so. Controls inside
   it keep their own cursors — a stage button is still a button. */
.sup-table tbody tr[data-supplier] { cursor: pointer; }
.sup-table tbody tr[data-supplier]:hover { background: var(--line-soft); }

/* 700, with the run sheet, the checklist and the budget. Checked for a more
   specific weight rule before changing this one: `.sup-table .sup-name` sets
   wrapping only, so this is the rule that applies. */
.sup-name { font-weight: 700; }
.sup-missing { color: var(--ink-faint); font-size: .82rem; }

/* The supplier's name opens the supplier. Styled to look like the text it
   replaced rather than like a button — the point is that the obvious thing
   is clickable, not that there is another control to notice. It only announces
   itself on hover and focus. */
.sup-name-open {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  border-radius: 6px;
}
.sup-name-open:hover { color: var(--pink); text-decoration: underline; text-underline-offset: 2px; }
.sup-name-open:focus-visible { outline: 2px solid var(--pink); outline-offset: 2px; }
.sup-name-open .sup-person { text-decoration: none; }

/* Booked, paid, and deliberately not the couple's money — often somebody
   else's entirely. Teal rather than the faint grey of .sup-missing above,
   because the same empty budget line means opposite things depending on
   whether it was a decision, and a decision should not wear a warning's
   colour. */
.sup-outside {
  display: inline-block;
  padding: 1px 9px;
  border: 1px solid var(--teal);
  border-radius: 999px;
  color: var(--teal-deep);
  font-size: .74rem;
  font-weight: 600;
  white-space: nowrap;
}

.sup-actions { white-space: nowrap; text-align: right; }
.sup-actions .btn { margin-left: 2px; }

/* ⚠️ PINNED, BECAUSE THIS COLUMN HAS NEVER BEEN ON SCREEN.
   Measured on Solin & Rony, 10 Sept: the supplier table is 2039px wide inside
   a 1168px pane — 871px of overhang — and this cell sat 625px past the right
   edge of a 1280px window. Sixteen ↑ and ↓ buttons were being drawn, correctly,
   at coordinates nobody could reach. Gee: "suppliers don't have any of it to
   move around. I can't see that here."

   Folding four buttons into one ⋮ saves 194px of that 871, so it is NOT the
   fix on its own — sticky is. Every column is kept; the controls simply travel
   with her however far the table is scrolled. */
.sup-table th.sup-actions,
.sup-table td.sup-actions {
  position: sticky;
  right: 0;
  z-index: 3;
  width: 46px;
  min-width: 46px;
  padding-left: 6px;
  padding-right: 6px;
  text-align: center;
}

/* A sticky cell must be opaque or the columns scroll through underneath it.
   ⚠️ SCOPED WITH :not(.is-unchecked) RATHER THAN A HIGHER SPECIFICITY, which
   is the lesson from `.imp-list li` — `tr.is-unchecked > td` (0,1,2) paints
   every cell amber, and `.sup-table td.sup-actions` (0,2,1) would beat it and
   punch a white hole through the amber on exactly the rows that need reading.
   Excluding the state class keeps ONE definition of what amber looks like. */
.sup-table tr:not(.is-unchecked) > td.sup-actions,
.sup-table thead th.sup-actions {
  background: var(--surface);
}

/* The shadow is what says "there is more table under this", and it is the only
   cue once the row's own borders stop at the sticky edge. */
.sup-table th.sup-actions,
.sup-table td.sup-actions {
  box-shadow: -8px 0 8px -8px rgb(0 0 0 / 28%);
}


/* ---------- two-step delete ------------------------------------------------

   Nothing in here is recoverable, so a delete is two deliberate acts rather
   than one twitch of the mouse. */

.confirm-step {
  color: var(--ink-faint);
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
}

/* Who a printed run sheet is for.

   Two big targets rather than a dropdown and an OK: this is asked every single
   time the run sheet is printed, so it has to be answerable without reading.
   The description under each is not decoration — "Me" and "A supplier" alone
   do not say that driver mobiles are the difference. */
.print-for-option {
  display: block;
  width: 100%;
  margin-bottom: 10px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-left: 4px solid var(--v-pink, var(--pink, #e8197a));
  border-radius: 12px;
  background: var(--surface, #fff);
  text-align: left;
  cursor: pointer;
  font: inherit;
  transition: border-color .12s, background .12s;
}

.print-for-option:last-of-type { border-left-color: var(--v-sky, #0091ea); }

.print-for-option:hover,
.print-for-option:focus-visible {
  background: var(--tint, #faf7f9);
  border-color: var(--ink-faint);
}

.print-for-option strong {
  display: block;
  margin-bottom: 3px;
  color: var(--ink);
  font-size: 1rem;
  font-weight: 700;
}

.print-for-option span {
  display: block;
  color: var(--ink-soft);
  font-size: .85rem;
  line-height: 1.45;
}

.confirm-verify {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

.confirm-verify label {
  display: block;
  margin-bottom: 7px;
  color: var(--ink-soft);
  font-size: .84rem;
}

.confirm-verify strong {
  color: var(--danger);
  font-weight: 700;
}

.confirm-verify input {
  border-color: var(--danger);
}

.confirm-verify input:focus {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px var(--danger-tint);
}


/* ---------- checklist: Working on vs Full checklist -------------------------

   The full list runs to eighty-odd items. Reading that every time you open a
   project is exhausting, so the short curated list is what opens first and
   the star is how something gets into it. */

.segmented {
  display: inline-flex;
  border: 1px solid var(--teal);
  border-radius: 999px;
  overflow: hidden;
}

.segmented .seg {
  padding: 6px 15px;
  border: 0;
  background: var(--surface);
  color: var(--teal-deep);
  font-family: inherit;
  font-size: .84rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}

.segmented .seg + .seg { border-left: 1px solid var(--teal); }
.segmented .seg:hover:not(.is-active) { background: var(--teal-tint); }
.segmented .seg.is-active { background: var(--teal); color: #fff; }

.task-star {
  flex: 0 0 auto;
  padding: 0 4px;
  border: 0;
  background: none;
  color: #ece5e8;
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  transition: color .12s ease, transform .1s ease;
}

.task-star:hover  { color: var(--pink); transform: scale(1.15); }
.task-star.is-on  { color: var(--pink); }


/* ---------- run sheet: editing in place ------------------------------------

   Building a run sheet is mostly retyping a title or nudging a time. The
   gutter controls appear on hover so they don't clutter a sheet you're only
   reading, and the title and time are editable where they sit. */

/* Every control for a line, on the left beside the time. Four buttons in a
   2×2 block, so the gutter stays 46px wide: a run sheet is read straight down
   the time column, and a wide control gutter would shove the times off that
   scan line. Arrows on top, add and delete beneath. */
/* ⚠️ ONE LINE, NOT TWO COLUMNS — this is what makes a run sheet row 46px.
   As a 2-column grid the five buttons stacked three deep: a 71px gutter on a
   row whose text is one line, so every row was 94px and the controls were
   taller than the content. Over sixty lines that is ~2,900px of scrolling
   spent on buttons.
   Gee, 8 Sept: "it makes the line seem way too big instead of it being a bit
   more compressed." Three options were rendered in the live app and measured;
   she chose grip + arrows + ⋮, which is the checklist's `.row-gutter` shape
   with the arrows kept out. */
.tl-gutter {
  display: flex;
  flex: 0 0 auto;
  gap: 2px;
  align-items: flex-start;
  padding-top: 1px;
  opacity: 0;
  transition: opacity .12s ease;
}

/* They fade in on the line under the cursor — the Aisle Planner behaviour Gee
   asked for on 27 Aug after seeing them permanently on: "when you go and
   hover onto that line item, the plus comes up." Four black chips on every
   one of sixty lines turned out to read as clutter rather than as help.

   focus-within is not decoration: without it these are unreachable by
   keyboard, since there is no hover to trigger. */
.tl-row:hover .tl-gutter,
.tl-gutter:focus-within { opacity: 1; }

/* No hover on a phone, so they stay put there. */
@media (max-width: 620px) {
  .tl-gutter { opacity: 1; }
}

/* It used to be a wide bar across the top of the grid, because a grab handle
   has to look like something you take hold of and two columns of identical
   22px squares gave no clue which one carried the row.

   In a single line that reasoning is spent: it is the FIRST chip now, which is
   where the checklist puts its grip too, and the ≡ glyph carries the meaning on
   its own. `grid-column` goes with the grid — left in, it would have made the
   grip stretch across the whole flex row. */
.tl-grip {
  cursor: grab;
}
.tl-grip:active { cursor: grabbing; }

/* The row being carried stays visible — dropping something you cannot see is
   how a line ends up in the wrong section. */
.tl-row.is-dragging { opacity: .45; }

/* Where it will land. A rule, not a gap: the sheet must not jump about under
   the cursor while a wedding day is being rearranged. */
.tl-row.is-drop-above { box-shadow: inset 0 2px 0 0 var(--pink); }
.tl-row.is-drop-below { box-shadow: inset 0 -2px 0 0 var(--pink); }

/* The + that adds a line under this one. Slightly stronger than its
   neighbours: ≡ ↑ ↓ ⋮ all rearrange what is already there, and this is the one
   that makes something new. Gee had been scrolling to the top of a 39-line
   sheet to reach the only add button she could see. */
.tl-add-here {
  color: var(--pink-deep);
  font-weight: 800;
  font-size: 1rem;
}

.tl-add-here:hover:not([disabled]) {
  background: var(--pink-tint);
  border-color: var(--pink);
}

.tl-mini[disabled] { opacity: .3; cursor: default; }
.tl-mini[disabled]:hover { background: var(--line-soft); border-color: var(--line); }

/* A resting shape of their own — a flat glyph floating beside the time would
   read as decoration rather than as something to press. */
.tl-mini {
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--line-soft);
  color: var(--ink);
  font-size: .84rem;
  line-height: 1;
  cursor: pointer;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
}

/* All five are black at rest. Gee, 27 Aug: "I think it should be black, and
   it should stay on the screen and not only on hover."

   Colour arrives only under the cursor, on the two that do something you
   cannot undo by pressing the same button again. At rest the sheet keeps one
   colour vocabulary: a tint on this screen means "category of event", and
   nothing else gets to borrow it. */

.tl-mini:hover {
  background: #fff;
  border-color: var(--ink-faint);
  color: var(--ink);
}
.tl-mini[data-delete-event]:hover { background: var(--danger-tint); border-color: var(--danger); color: var(--danger); }
.tl-mini[data-add-after]:hover    { background: var(--teal-tint);   border-color: var(--teal);  color: var(--teal-deep); }
.tl-mini[data-delete-event][disabled]:hover,
.tl-mini[data-add-after][disabled]:hover { background: var(--line-soft); border-color: var(--line); }

/* ---------- the plus menu -------------------------------------------------- */

/* Positioned in page coordinates on <body>, not inside the row: a run sheet
   line clips its own overflow, and a menu opened inside one gets cut off at
   the row edge. */
.tl-menu {
  position: absolute;
  z-index: 200;
  min-width: 210px;
  padding: 5px 0;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(42, 33, 38, .12), 0 2px 6px rgba(42, 33, 38, .08);
}

.tl-menu-item {
  display: block;
  width: 100%;
  padding: 7px 14px;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: .86rem;
  text-align: left;
  cursor: pointer;
}

.tl-menu-item:hover,
.tl-menu-item:focus-visible { background: var(--line-soft); outline: none; }

/* The line under each one saying what it DOES. The three items were named
   after what they are — "line", "heading", "arrival card" — which tells you
   nothing about the only difference that matters: a line is written by you,
   an arrival card fills itself. */
.tl-menu-item small {
  display: block;
  margin-top: 1px;
  font-size: .74rem;
  line-height: 1.35;
  color: var(--ink-faint);
}
.tl-menu-item:hover small,
.tl-menu-item:focus-visible small { color: var(--ink-soft); }

.tl-menu-sep { height: 1px; margin: 5px 0; background: var(--line); }

.tl-menu-label {
  padding: 4px 14px 2px;
  color: var(--ink-faint);
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .04em;
}

.tl-menu-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  padding: 3px 14px 7px;
}

/* Only hint at editability on hover — a dotted underline on every line would
   make the whole sheet look like a form. */
.tl-edit { cursor: text; border-radius: 3px; }
.tl-row:hover .tl-edit:hover {
  background: #faf7f9;
  box-shadow: 0 0 0 3px #faf7f9;
}

.tl-inline {
  width: auto;
  min-width: 90px;
  max-width: 100%;
  padding: 1px 5px;
  border: 1px solid var(--pink);
  border-radius: 4px;
  font-family: inherit;
  font-size: .93rem;
}

.tl-time .tl-inline { width: 108px; font-size: .87rem; }


/* ---------- shared: hover gutter and inline editing -------------------------

   Aisle Planner puts a trash and a plus at the left of every row, revealed on
   hover. It keeps a sheet you're only reading clean, and puts the two things
   you do constantly a single click away. */

.row-gutter {
  display: flex;
  flex: 0 0 auto;
  gap: 2px;
  align-items: flex-start;
  padding-top: 2px;
  opacity: 0;
  transition: opacity .12s ease;
}

.task-row:hover .row-gutter,
.row-gutter:focus-within { opacity: 1; }

/* TWO buttons on a task row: the drag grip, and the ⋮ that holds the other six.

   It was seven, in a 4x2 grid 82px wide — four columns rather than three
   specifically so adding the grip did not push it to three rows, because a
   taller gutter costs height on every one of a hundred rows.

   The menu made the grid pointless. Two buttons are a flex row, one line tall,
   and the gutter is now 48px instead of 82px — 34px given back to every title
   on the page.

   The grip stayed OUT of the menu. You cannot drag a menu item, and Gee on
   1 Sept: "I definitely want the dragging option... especially on the running
   sheet." Sorted by date the grip is not drawn and only the ⋮ is left. */
.task-row > .row-gutter {
  display: flex;
  gap: 4px;
  align-items: center;
  padding-top: 0;
}

.row-mini {
  position: relative;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--ink-faint);
  font-size: .78rem;
  line-height: 1;
  cursor: pointer;
}

.task-row > .row-gutter .row-mini { width: 19px; height: 18px; }

.row-mini:hover { background: #f2f0f1; color: var(--ink); }
.row-mini[data-delete-task]:hover    { background: var(--danger-tint); color: var(--danger); }
.row-mini[data-add-after-task]:hover { background: var(--teal-tint);   color: var(--teal-deep); }
.row-mini[data-task-up]:hover,
.row-mini[data-task-down]:hover      { background: var(--teal-tint);   color: var(--teal-deep); }
.row-mini[data-edit-task]:hover      { background: var(--teal-tint);   color: var(--teal-deep); }

/* A file count on the clip. Pinned to the BOTTOM corner: the clip sits on the
   gutter's last row, so a badge hung off the top lands on the button above it
   and neither can be read. */
.row-mini.has-files { color: var(--teal-deep); }
.row-mini i {
  position: absolute;
  bottom: -2px; right: -1px;
  min-width: 11px;
  padding: 0 2px;
  border-radius: 999px;
  background: var(--teal-deep);
  color: #fff;
  font-style: normal;
  font-size: .55rem;
  font-weight: 700;
  line-height: 11px;
}

/* Grouped by person the order is alphabetical, so there is nothing for the
   arrows to change. Hiding them beats two buttons that do nothing. */
.lane-grid .row-mini[data-task-up],
.lane-grid .row-mini[data-task-down],
.lane-grid .row-grip { visibility: hidden; }

/* The grip. Only this starts a drag — the same rule as the run sheet, because
   a draggable row eats the text selection in the title and the date, and both
   of those edit in place. */
.row-grip {
  cursor: grab;
  letter-spacing: -3px;
  color: var(--ink-faint);
}
.row-grip:active { cursor: grabbing; }
.row-grip:hover  { background: var(--teal-tint); color: var(--teal-deep); }

.task-row.is-dragging { opacity: .45; }

/* Where it will land. A line, not a highlight: a highlighted row reads as
   "this one", and what you need to know is which side of it you are on. */
.task-row.is-drop-above { box-shadow: inset 0 2px 0 0 var(--pink); }
.task-row.is-drop-below { box-shadow: inset 0 -2px 0 0 var(--pink); }

/* ---------- contacts, as a plain list -------------------------------------

   Cards are right when you are reading one supplier. A list is right when you
   are hunting for one among sixty, which is what Gee asked for on 29 Aug. */

.contact-table-wrap { width: 100%; overflow-x: auto; }

.contact-table {
  width: 100%;
  min-width: 720px;
  border-collapse: collapse;
  font-size: .86rem;
}

.contact-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 8px 12px;
  border-bottom: 1px solid var(--ink);
  background: var(--surface);
  color: var(--ink-faint);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  text-align: left;
  white-space: nowrap;
}

.contact-table td {
  padding: 8px 12px;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: middle;
}

.contact-table tbody tr:hover { background: var(--surface-soft); }
.contact-table .c-name  { font-weight: 600; color: var(--ink); }
.contact-table .c-blank { color: var(--ink-faint); }
.contact-table .c-do    { text-align: right; white-space: nowrap; }

/* A phone number is one thing, not two.
   Gee, 29 Aug: "I'd love to be able to see the mobile number properly."
   The email column was taking the width it wanted and squeezing this one, so
   "0402 730 997" broke after the second group. It gets its own width and is
   never allowed to wrap; the email can be the one that gives, because a
   shortened address is still readable and half a phone number is not. */
.contact-table .c-phone {
  white-space: nowrap;
  width: 1%;
  font-variant-numeric: tabular-nums;
}
.contact-table .c-phone a { white-space: nowrap; }

/* A contact can be two or three things — a photographer who also films, hair
   and makeup. The chips wrap inside the cell rather than widening the column. */
.contact-table .c-cats { max-width: 200px; }
.contact-table .c-cats .cat-chip { margin: 1px 3px 1px 0; }

/* The Open button only when you are on the row — sixty of them down the right
   edge reads as sixty things wanting attention. */
.contact-table .c-do .btn { opacity: 0; transition: opacity .12s ease; }
.contact-table tr:hover .c-do .btn,
.contact-table tr:focus-within .c-do .btn { opacity: 1; }

@media (max-width: 620px) { .contact-table .c-do .btn { opacity: 1; } }

/* The order control in the toolbar. */
.inline-select {
  height: 26px;
  padding: 0 6px;
  margin-left: 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font: inherit;
  font-size: .8rem;
  color: var(--ink);
}

.row-edit { cursor: text; border-radius: 3px; }
.task-row:hover .row-edit:hover {
  background: #faf7f9;
  box-shadow: 0 0 0 3px #faf7f9;
}

.inline-edit {
  width: auto;
  min-width: 140px;
  max-width: 100%;
  padding: 1px 5px;
  border: 1px solid var(--pink);
  border-radius: 4px;
  font-family: inherit;
  font-size: .93rem;
}


/* ---------- run sheet: inline add links ------------------------------------

   The two things most often missing from a line, offered where the line is
   rather than behind a form. */

.tl-adds {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 4px;
  opacity: 0;
  transition: opacity .12s ease;
}

.tl-row:hover .tl-adds,
.tl-adds:focus-within { opacity: 1; }

.tl-add-link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--teal-deep);
  font-family: inherit;
  font-size: .74rem;
  cursor: pointer;
}

.tl-add-link:hover { text-decoration: underline; }

/* ⚠️ .tl-inline-select IS DELETED — 12 September. It styled a native dropdown
   of every contact, hanging off "+ supplier" on a run sheet line. You cannot
   type into a <select>: a keystroke jumps to the first name starting with that
   letter, so typing "Abra" landed on A, then B, then R. Gee: "I type something
   and it doesn't seem to save… you have to actually open up the card."

   It is the shared type-ahead now, the same one the card uses. Nothing emits
   the old class. */

/* The type-ahead, inline on a run sheet line. Narrow, because it sits between
   the supplier chips rather than in a form. */
.tl-inline-pick { position: relative; display: inline-block; }

.tl-inline-input {
  width: 190px;
  max-width: 100%;
  padding: 3px 8px;
  font-size: .8rem;
}

/* The list is drawn into the line, which has overflow of its own — so it is
   lifted out and above anything below it, the same way the modal's list is.

   ⚠️ THE WIDTH IS CLAMPED TO THE SCREEN, NOT SET TO A NUMBER. Gee hit the old
   version of this control on her phone — "the whole dropdown came up, and I
   couldn't select or try to search". A fixed 240px list, absolutely positioned
   part-way across a run sheet row, runs off the right of a 375px screen and
   takes the supplier names with it. min() keeps it readable on a laptop and
   inside the glass on a phone. */
.tl-inline-pick .vendor-list {
  position: absolute;
  top: calc(100% + 3px);
  left: 0;
  z-index: 60;
  min-width: min(240px, 78vw);
  max-width: min(340px, calc(100vw - 28px));
  max-height: 46vh;
  overflow-y: auto;
  /* 🛑 A FLOATING LIST NEEDS ITS OWN GROUND. `.vendor-list` has none — inside
     a modal the white card behind it was the ground. Lifted over the run
     sheet, the lines underneath read straight through it (Gee, 30 Sept:
     "Bride & groom to relax" printed across "Love & Other"). Covers both
     inline pickers, "+ supplier" and "+ place". */
  background: var(--surface);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .14);
}

/* On a phone the row is narrow and the box sat beside chips that pushed it off
   the edge. It gets the width it needs and the list lines up under it. */
@media (max-width: 620px) {
  .tl-inline-input { width: 100%; min-width: 150px; }
  .tl-inline-pick  { display: block; width: 100%; }
}

/* The time column is narrow; its editor should match rather than stretch. */
.tl-time .inline-edit { width: 108px; min-width: 0; font-size: .87rem; }

/* Editing a time opens both ends at once, which needs more than the 86px the
   column normally gets. */
.tl-time.is-editing { flex: 0 0 auto; }

.tl-time-edit {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}

.tl-time-edit .inline-edit {
  width: 100px;
  min-width: 0;
  font-size: .84rem;
}

.tl-dash { color: var(--ink-faint); }

/* Marks a row the couple cannot see. Deliberately quiet — it should be
   noticeable when you scan for it, not shouting on every line. */
/* The section's place, on the heading band. 12 September.

   Gee, an hour after the heading inheritance shipped: "I can't see the place
   card here, so something's not right." Setting a heading's place silently
   changed every tag beneath it, and the band said nothing — no way to tell
   which sections carried one, and no confirmation that it had worked. It was
   in the mock-up she approved and it did not get built.

   ⚠️ IT IS NOT THE .tl-door TAG, AND THE DIFFERENCE IS THE POINT. A heading
   draws that small colour-coded tag on the far right like every other row,
   and it appears whether the place was PICKED or guessed from the wording.
   This one names the venue and only ever shows when she actually set it — so
   a band with no chip means the lines under it are still guessing.

   Built from .vis-tag below, which is the app's existing inline tag on this
   exact row, in the heading band's own indigo rather than teal. Not uppercase:
   it carries a real venue name, and "at LAKESIDE RECEPTIONS" shouts. */
/* ⚠️ AND IT IS A BUTTON, INCLUDING WHEN THE SECTION HAS NO PLACE. Gee, looking
   at a heading with nothing set: "There is no place on the running sheet." A
   band that only shows a chip once a place exists gives her nothing to press to
   create one — the empty state is exactly the state that needs the control. */
.tl-at {
  display: inline-block;
  margin-left: 10px;
  padding: 2px 9px;
  border: 1.5px solid var(--c-indigo);
  border-radius: 999px;
  background: var(--surface);
  color: var(--c-indigo);
  font-family: inherit;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .01em;
  text-transform: none;
  vertical-align: middle;
  white-space: nowrap;
  cursor: pointer;
}
.tl-at:hover { background: var(--c-indigo-tint); }

/* Nothing set yet: dashed and quieter, the same grammar .who-add uses on a
   line — an invitation rather than a fact. */
.tl-at.is-unset {
  border-style: dashed;
  border-width: 1px;
  font-weight: 600;
  opacity: .8;
}
.tl-at.is-unset:hover { border-style: solid; opacity: 1; }

/* 🛑 PLACES HIDDEN FROM THE EVENING ONWARDS — 27 September, migration 135.
   Gee, on Solin & Rony: "Just the reception section onwards when the evening
   starts, but everything above that, I think it does need to have it."

   `.places-off` is put on every line AFTER a heading she marked, by
   renderTimeline. It takes the place chip beside the supplier and NOTHING
   else — CHECK 74, the same promise CHECK 53 holds the print tick to: the
   right-hand tag, the "arrives" chip, the supplier and the heading's own "at …"
   all stay. The place is still saved; this is ink.

   The pill is how she can see it is on. Pink, the colour her mock-up showed,
   and a button — pressing it turns the hiding off. */
.tl-row.places-off .who-place.is-set { display: none; }

.tl-places-off {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 9px;
  border: 1px solid var(--pink-deep);
  border-radius: 999px;
  background: var(--surface);
  color: var(--pink-deep);
  font-family: inherit;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .01em;
  text-transform: none;
  vertical-align: middle;
  white-space: nowrap;
  cursor: pointer;
}
.tl-places-off:hover { background: var(--pink-tint); }

.vis-tag {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 7px;
  border: 1px solid var(--teal);
  border-radius: 999px;
  color: var(--teal-deep);
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  vertical-align: middle;
}

/* 🛑 THE SAME CHIP, EXCEPT THIS ONE IS A BUTTON. "Not ready to pay", on a
   budget line Gee has held back. 18 September.

   It is the chip shape on purpose — she already reads "Planner only" in this
   position and this is the same kind of fact about the line. What is different
   is that pressing it does the thing: one press makes the line ready, which is
   what she went looking for and could not find.

   ⚠️ A BUTTON NEEDS THE RESET A SPAN DID NOT. `.vis-tag` was written for a
   `<span>`, so it sets no font-family, background or cursor — a bare button
   inherits the browser's 13px Arial and a grey bevel, which is exactly how a
   deliberate control ends up looking like a mistake.

   ⚠️ RED, AND NOT BECAUSE ANYTHING IS WRONG. Nothing is: she chose to hold it.
   It is red because the consequence is that money is not moving, and this chip
   is the only thing on the page that says so. `--danger` on `--danger-tint`,
   which is the pair already used for money that is late. */
/* 🛑 FILLED, LIKE ITS TWO SIBLINGS. Gee, 1 Oct: "there are inconsistencies
   with the on hold… it's not in the same format as the others, where it's
   more colored in." Blue = Ready to pay, magenta = Deposit required,
   red = On hold — all three filled, white writing, one shape. */
button.vis-tag.is-held {
  border-color: #be123c;
  background: #be123c;
  color: #fff;
  font-family: inherit;
  cursor: pointer;
  /* The chip is 0.64rem uppercase. On a phone it still has to be hittable, and
     padding is the only lever that does not change how the row reads. */
  padding: 3px 9px;
}
button.vis-tag.is-held:hover { background: #9f1239; }
button.vis-tag.is-held:focus-visible { outline: 2px solid var(--danger); outline-offset: 2px; }
@media (hover: none) { button.vis-tag.is-held { padding: 8px 11px; } }
@media print { button.vis-tag.is-held { background: none; color: #be123c; } }

/* "Ready to pay" — Gee's own press (1 Oct). Blue and filled = go, beside the
   red outline of "On hold" = stop. Same blue as the approved mock-up. */
.vis-tag.is-ready {
  border-color: #1d4ed8;
  background: #1d4ed8;
  color: #fff;
}
/* "Deposit required" — also her press (1 Oct). Magenta, her pick over violet
   ("Ready to Pay and Deposit Required are very similar in colour"). */
.vis-tag.is-deposit {
  border-color: #c026d3;
  background: #c026d3;
  color: #fff;
}
/* "Cash on day" — her press (20 Oct, migration 161). CORAL PINK, her pick the
   same morning after orange (it merged with the orange "new file" 📎 tag) and
   a run of others: teal, gold, purple (her 📎 tag is grape), sunshine. Filled
   like the other three so all four read as one family. Darker coral on paper. */
.vis-tag.is-cash {
  border-color: #f43f5e;
  background: #f43f5e;
  color: #fff;
}
/* All three are buttons that open the choice menu, so they dress like On hold. */
button.vis-tag.is-ready,
button.vis-tag.is-deposit,
button.vis-tag.is-cash { font-family: inherit; cursor: pointer; padding: 3px 9px; }
button.vis-tag.is-ready:hover   { background: #1e40af; }
button.vis-tag.is-deposit:hover { background: #a21caf; }
button.vis-tag.is-cash:hover    { background: #e11d48; }
button.vis-tag.is-ready:focus-visible,
button.vis-tag.is-deposit:focus-visible,
button.vis-tag.is-cash:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
@media (hover: none) { button.vis-tag.is-ready, button.vis-tag.is-deposit, button.vis-tag.is-cash { padding: 8px 11px; } }
@media print {
  .vis-tag.is-ready   { background: none; color: #1d4ed8; }
  .vis-tag.is-deposit { background: none; color: #c026d3; }
  .vis-tag.is-cash    { background: none; color: #e11d48; }
}

/* Same shape as the visibility tag, pink instead of teal: a task the couple
   put on the list themselves. Worth marking, because a task you did not write
   is a task you have not agreed to. */
.client-tag {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 7px;
  border: 1px solid var(--pink);
  border-radius: 999px;
  color: var(--pink-deep);
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  vertical-align: middle;
}

/* ---------- what the couple sent back ------------------------------------

   The answer sits open on the row, not behind a Details button. It is the one
   piece of information the task exists to collect, and making Gee click to
   find it would be making her hunt for the point of her own task.

   Capped and scrolling, because a guest list of 180 names must not push every
   other task off the screen.                                                */

.client-answer {
  margin: 7px 0 2px;
  padding: 9px 12px;
  border-left: 3px solid var(--teal-deep);
  border-radius: 0 8px 8px 0;
  background: var(--teal-tint, #e7f6f7);
}

.client-answer-head {
  color: var(--teal-deep);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
}

.client-answer-body {
  margin-top: 5px;
  max-height: 190px;
  overflow-y: auto;
  color: var(--ink);
  font-size: .85rem;
  line-height: 1.5;
  /* Their line breaks, kept. A song list is a list. */
  white-space: pre-wrap;
}

/* ---------- assignee chips -------------------------------------------------

   A task can belong to more than one person, so the field holds a row of
   names rather than a single value. */

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}

.chips:empty { margin-bottom: 0; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 5px 3px 4px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  font-size: .82rem;
}

.chip-x {
  width: 17px;
  height: 17px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--ink-faint);
  font-size: .92rem;
  line-height: 1;
  cursor: pointer;
}

.chip-x:hover { background: var(--danger-tint); color: var(--danger); }

.who-bubble.sm { width: 20px; height: 20px; font-size: .6rem; }

/* Several bubbles on one row overlap slightly, the way a shared task reads
   on Aisle Planner. */
.task-row .who-bubble + .who-bubble { margin-left: -7px; }


/* ---------- checklist: lanes by person -------------------------------------

   Grouped by person the list reads as columns side by side, one per owner,
   the way the Aisle Planner assignments overview does. Stacked sections make
   you scroll past your own work to find someone else's. */

.lane-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
  gap: 32px 40px;
  align-items: start;
}

.lane-head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
}

.lane-who { display: flex; flex-direction: column; min-width: 0; }

/* ---------------------------------------------------------------------------
   SELECT ALL, PER COLUMN. 18 September.

   Gee: "I should be able to do a Select All in a column. I've literally been
   individually touching and pressing all of these."

   ⚠️ `margin-left: auto` RATHER THAN A WIDTH, because `.lane-head` is declared
   in BOTH styles.css and checks.css — a real collision, and whichever wins,
   both are `display: flex`, so auto-margin puts this at the right-hand end
   either way. See `css-class-name-collisions`.

   ⚠️ QUIET. It sits above a column of work and must not compete with the
   name; --teal-deep on white measures 4.97:1, which clears 4.5 at this weight
   and size.
   -------------------------------------------------------------------------- */
.lane-pick {
  margin-left: auto;
  flex: 0 0 auto;
  min-height: 30px;
  padding: 5px 9px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: none;
  font-family: inherit;
  font-size: .7rem;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--teal-deep);
  cursor: pointer;
  white-space: nowrap;
}
.lane-pick:hover { background: var(--teal-tint); border-color: var(--teal-deep); }
.lane-pick:focus-visible { outline: 2px solid var(--teal-deep); outline-offset: 2px; }

/* The tap target is the thing. She found this on a laptop; the phone still has
   to be able to use it. */
@media (hover: none) { .lane-pick { min-height: 44px; padding: 8px 12px; } }

@media print { .lane-pick { display: none !important; } }

.lane-name {
  color: var(--ink);
  font-size: .84rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
}

.lane-sub {
  color: var(--ink-faint);
  font-size: .76rem;
  font-style: italic;
}

.lane-sub.has-overdue { color: var(--danger); font-style: normal; font-weight: 600; }

/* The due date gets its own column so a lane scans as a list of dates. */
.task-due {
  flex: 0 0 78px;
  padding-left: 12px;
  border-left: 1px solid var(--line);
  color: var(--ink-soft);
  font-size: .78rem;
  text-align: right;
  white-space: nowrap;
}

.task-due.is-overdue { color: var(--danger); font-weight: 600; }
.task-due.is-soon    { color: var(--ink); }

/* The date is editable in place, like the title. */
.task-due.row-edit { cursor: text; }

/* Same on the budget: the date you chase is the one you change. */
.bg-due.row-edit { cursor: text; border-radius: 3px; }
.bg-due.row-edit:hover { background: var(--line-soft); box-shadow: 0 0 0 3px var(--line-soft); }
.bg-due.is-empty { color: var(--ink-faint); }
.bg-date input.inline-edit, tr[data-line] > td input.inline-edit { min-width: 132px; }

/* A task with no date needs something to click, but eighty rows each carrying
   a permanent "+ date" prompt reads as eighty things undone. It appears when
   you are on the row and goes away again. */
.task-due.is-empty { color: transparent; }
.task-row:hover .task-due.is-empty { color: var(--ink-faint); }
.task-due.is-empty:focus-visible   { color: var(--ink-faint); }

/* editInPlace swaps the span for the input, so it lands as a direct child of
   the row. .inline-edit already gives it 140px — it just needs to stop being
   stretched by the row's flex. */
.task-row > input.inline-edit[type="date"] { flex: 0 0 auto; }

/* The tick box carries the urgency, like your coloured squares. */
.task-check.is-soon { accent-color: #ffb74d; box-shadow: 0 0 0 3px #ffe3e4; border-radius: 2px; }
.task-check.is-late { accent-color: var(--danger); box-shadow: 0 0 0 3px #ffe0e3; border-radius: 2px; }

@media (max-width: 900px) {
  .lane-grid { grid-template-columns: 1fr; }
}


/* ---------- assignee names, comments ---------------------------------------

   Names rather than initials: there are only ever a handful of people on a
   wedding, and "Tiffany" reads instantly where "TI" needs decoding. */

.who-name {
  flex: 0 0 auto;
  padding: 1px 9px;
  border: 1px solid var(--teal);
  border-radius: 999px;
  color: var(--teal-deep);
  font-size: .72rem;
  font-weight: 600;
  white-space: nowrap;
}

.who-name + .who-name { margin-left: 4px; }
.who-name.is-empty { border-color: var(--line); color: var(--ink-faint); font-weight: 500; }

.comment-flag {
  padding: 0 4px;
  border: 0;
  background: none;
  color: var(--ink-faint);
  font-size: .78rem;
  cursor: pointer;
  vertical-align: middle;
}

/* Faint until you want it, so fifty rows don't look like fifty notifications
   — but always there, because starting a conversation is the point. */
.comment-flag { opacity: .38; transition: opacity .12s, color .12s; }
.comment-flag:hover { color: var(--teal-deep); opacity: 1; }

/* A row that already has a conversation says so at full strength. */
.comment-flag.has-comments { opacity: 1; color: var(--teal-deep); }

.comment-flag span { margin-left: 2px; font-weight: 700; font-size: .68rem; }

/* Something said since you last looked. Gee, 29 Aug: "it would be nice for
   there to be a new comment or something like that to let us know... maybe
   like an orange or something."
   Teal is every conversation; orange is the handful that want you. It is a
   filled pill rather than coloured text so it carries across a page of
   eighty rows. */
.comment-flag.is-new { opacity: 1; color: var(--warn); }
.comment-flag.is-new span {
  margin-left: 4px;
  padding: 1px 6px;
  border-radius: var(--radius-pill);
  background: var(--warn);
  color: #fff;
  font-size: .64rem;
  letter-spacing: .02em;
}
.comment-flag.is-new:hover { color: var(--warn); }

/* Reaching for the icon on a row shouldn't need aim. */
.task-row:hover .comment-flag { opacity: 1; }

/* The paperclip on a task row. A task carrying three quotes should say so from
   the list — that is the whole point of keeping them here rather than in email
   — so the count sits on the button instead of hiding inside the modal. */
.btn.has-files {
  color: var(--teal-deep);
  font-weight: 700;
}

.btn.has-files span { margin-left: 3px; font-size: .7rem; }

.comment-list {
  list-style: none;
  margin: 0 0 10px;
  padding: 0;
  max-height: 220px;
  overflow-y: auto;
}

.comment {
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
  font-size: .86rem;
}

.comment:last-child { border-bottom: 0; }
.comment.is-empty { color: var(--ink-faint); font-style: italic; }

.comment-who  { font-weight: 700; }
.comment-when { margin-left: 8px; color: var(--ink-faint); font-size: .74rem; }
/* ✉ Email again, under her own comment (13 Oct). Small and quiet: a re-send
   is the rare case, never the thing the eye should land on first. */
.comment-again { display: inline-block; margin-top: 4px; border: 1.5px solid #d6c3f5; background: #fff;
  color: #5b2bb5; border-radius: 999px; padding: 2px 10px; font: inherit; font-size: .74rem;
  font-weight: 800; cursor: pointer; }
.comment-again:hover { background: #f6f1ff; }
/* ✎ Edit · ✉ Email again · 🗑 Delete in one row (17 Oct, CHECK 184). */
.comment-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 4px; }
.comment-tools[hidden], .comment-body[hidden], .tp-body[hidden] { display: none; }
.comment-tools .comment-again { margin-top: 0; }
.comment-again.comment-del { border-color: #f5b5c8; color: #b0124f; }
.comment-again.comment-del:hover { background: #fff0f5; }
.comment-edited { font-style: italic; }
.comment-edited[title] { cursor: help; text-decoration: underline dotted; }
.comment-hidden { font-style: italic; color: #b0124f; font-weight: 700; }
.comment-editing { margin-top: 4px; }
.comment-editing textarea { width: 100%; box-sizing: border-box; font: inherit; font-size: .9rem;
  border: 1.5px solid #d6c3f5; border-radius: 10px; padding: 8px 10px; resize: vertical; }
.comment-edit-note { color: var(--ink-faint); font-size: .74rem; }
.comment-body { display: block; margin-top: 3px; white-space: pre-wrap; }

/* Attach and Post sit UNDER the box you just typed in, at the left, not out
   on the right-hand edge. Gee: "it would be good for anywhere that we've got
   the send button to be closer to the bottom of the beginning... it's really
   annoying being all the way on the side, and it's like that in a few
   sections."

   She is right that it is in a few sections — there are six of these
   composers, two in index.html, one in budget.js and three in portal.js, and
   they all share this one rule. flex-basis:100% on the textarea pushes the
   buttons onto their own row rather than needing a wrapper in six places. */
/* The box, the paperclip and Post on ONE line.
   Gee, 28 Aug: "very hard to leave a comment and press post". The textarea was
   flex-basis 100%, so it took the whole row and pushed both buttons onto a
   second one — which meant typing a comment, then hunting downward for a
   button that had moved. They sit together now, and the box grows with the
   typing instead of starting tall. */
/* UNDER the box, not beside it. Gee asked for this twice on the 30 Aug
   walkthrough — "move this poster and this attach maybe straight under that"
   and, at the supplier card, "I'll move the attach down there".

   They were beside the box because an earlier version had them wrapping onto a
   second row unpredictably, which meant hunting for a button that had moved.
   That was flex-wrap doing it, not the position. A deliberate column with the
   buttons in their own row cannot wrap: the box keeps its max-height, the
   buttons stay exactly where they were put, and the row below the box is where
   your hand already is when you stop typing. */
.comment-compose {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: flex-end;
  align-items: center;
}

/* Takes the whole row, which puts everything else on the next one. That IS
   the behaviour the old note was complaining about — it is just the behaviour
   she has now asked for twice. */
.comment-compose textarea {
  flex: 1 0 100%;
  min-width: 0;
  min-height: 60px;
  max-height: 160px;
  resize: vertical;
}

/* Attach and Post, side by side, ending where the box ends. */
.comment-compose .btn { flex: 0 0 auto; }

@media (max-width: 620px) {
  /* A phone has no room for a box and two buttons side by side, and the box
     is the part that needs the width. */
  .comment-compose { flex-wrap: wrap; }
  .comment-compose textarea { flex: 1 1 100%; }
}


/* ============================================================================
   Budget — the traffic light
   ============================================================================

   Two earlier attempts at this are gone. The first tinted every money column
   and used green for two different meanings; the second stripped all the
   colour out and was unreadable. This is the version we settled on.

   Green, amber, red. Nothing else on the table is coloured, so the status is
   the only thing competing for attention — which is the point, because it is
   the thing you scan for.

   Amber rather than pink for a part-paid line matters: pink is the brand
   colour and sits three shades from red, so a healthy half-paid line looked
   like a warning. Amber reads as "in progress" the way it does at a junction.
   ========================================================================== */

/* The budget's own three were a different green, a different amber and a
   different red from everywhere else — same three meanings, three sets of
   values, because each was picked where it was needed. They are aliases now,
   so "paid" here and "done" on the checklist are the same colour, and there is
   one place to change all of it. */
:root {
  --pay-done:      var(--ok);
  --pay-done-soft: var(--ok-tint);
  --pay-part:      var(--warn);
  --pay-part-soft: var(--warn-tint);
  --pay-late:      var(--late);
  --pay-late-soft: var(--late-tint);
  --pay-none:      #6b6167;
  --pay-none-soft: #f2f0f1;
}

/* Numbers stay black; weight separates the columns. */
.bg-table td.col-estimate,
.bg-table td.col-actual,
.bg-table td.col-paid,
.bg-table td.col-balance,
.bg-table td.col-balance.is-clear { background: none; }

.bg-table td.col-estimate { color: var(--ink-soft); font-weight: 400; }
.bg-table td.col-actual   { color: var(--ink); font-weight: 600; }
.bg-table td.col-paid     { color: var(--ink); font-weight: 400; }
.bg-table td.col-balance  { color: var(--ink); font-weight: 700; }
.bg-table td.col-balance.is-clear { color: var(--ink-faint); font-weight: 500; }

/* Headers name the money columns without tinting the cells beneath. */
.bg-table thead th.col-head {
  color: var(--pink);
  border-bottom: 2px solid var(--pink-tint);
}

/* The status edge. Run your eye down the left and the state of every line
   reads without a single number being involved. */
.bg-table tbody tr[data-line] td:first-child {
  border-left: 4px solid transparent;
  padding-left: 8px;
}

.bg-table tr.pay-done td:first-child { border-left-color: var(--pay-done); }
.bg-table tr.pay-part td:first-child { border-left-color: var(--pay-part); }
.bg-table tr.pay-late td:first-child { border-left-color: var(--pay-late); }
.bg-table tr.pay-none td:first-child { border-left-color: var(--pay-none-soft); }

/* Late is the one number allowed to shout. */
.bg-table tr.pay-late td.col-balance,
.bg-table tr.pay-late .bg-date { color: var(--pay-late); font-weight: 700; }

/* Categories on the left, heavier, with a rule beneath. */
.bg-table tr.bg-cat th {
  padding: 26px 10px 8px;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-align: left;
  text-transform: uppercase;
}

/* Subtotals close a category with a firm rule rather than a coloured band. */
.bg-table tr.bg-subtotal td {
  padding-top: 10px;
  padding-bottom: 10px;
  border-top: 2px solid var(--ink);
  background: none;
  color: var(--ink);
  font-weight: 700;
}

.bg-table tr.bg-subtotal td:first-child {
  border-left: 4px solid transparent;
  font-size: .7rem;
  letter-spacing: .08em;
  text-transform: uppercase;
}

/* Outlined pills — the pale fills disappeared against white at the far
   right of the table, which is exactly where the eye lands last. */
.status {
  display: inline-block;
  padding: 2px 12px;
  border: 1.5px solid var(--ink);
  border-radius: 999px;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .04em;
  white-space: nowrap;
}

.status-paid    { background: var(--pay-done-soft); color: var(--pay-done); }
.status-partial { background: var(--pay-part-soft); color: var(--pay-part); }
.status-overdue { background: var(--pay-late-soft); color: var(--pay-late); }
.status-unpaid  { background: var(--pay-none-soft); color: var(--pay-none); }


/* ============================================================================
   Budget — the design pass
   ============================================================================

   The traffic light was right but the table around it was still a wall. The
   real problem is thirteen columns of equal weight: your supplier names, your
   money, and a BSB you only need on the day you pay all shouted equally.

   Three moves. Money is the subject and gets the room. Bank details are
   reference and get demoted to small mono behind a divider. Categories and
   their subtotals are pink, so the eye has a rhythm to follow down the page.
   ========================================================================== */

.bg-table { font-size: .88rem; }

.bg-table thead th { padding: 10px 12px 8px; }
.bg-table tbody td { padding: 12px; }

/* Numbers that line up. Without this the columns wobble on every row. */
.bg-table .num { font-variant-numeric: tabular-nums; }

/* The supplier is what you look for first, so it gets the weight. */
/* ⚠️ THE ONE THAT ACTUALLY APPLIES IN THE BUDGET TABLE. Two classes beats the
   one-class `.bg-name` above, so this is what she sees. The balance column is
   700 and the supplier's name was 600 — the money out-shouted the name of who
   it was for. Both are 700 now; the money keeps the lead through colour and
   position rather than weight. */
.bg-table .bg-name {
  font-size: .95rem;
  font-weight: 700;
  letter-spacing: -.005em;
}

/* Categories back in pink, with a hairline of the same colour under them. */
.bg-table tr.bg-cat th {
  padding: 30px 12px 8px;
  border-bottom: 1px solid var(--pink-tint);
  color: var(--pink);
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-align: left;
  text-transform: uppercase;
}

/* The bottom layer, coloured — a category closes on a pink band rather than
   a bare rule, so the page reads as a set of blocks instead of one long list. */
.bg-table tr.bg-subtotal td {
  padding-top: 11px;
  padding-bottom: 11px;
  border-top: 1px solid var(--pink);
  background: var(--pink-tint);
  color: var(--pink-deep);
  font-weight: 700;
}

.bg-table tr.bg-subtotal td:first-child {
  border-left: 4px solid transparent;
  font-size: .7rem;
  letter-spacing: .1em;
  text-transform: uppercase;
}

/* Bank details are reference, not money. Small, mono, muted, and set behind a
   divider so it's obvious where "what it costs" ends and "how to pay" begins. */
.bg-table th.bg-bank,
.bg-table td.bg-bank {
  font-family: ui-monospace, "Cascadia Mono", "Segoe UI Mono", Menlo, monospace;
  font-size: .76rem;
  color: var(--ink-soft);
}

.bg-table th.bg-bank { font-family: inherit; }

.bg-table td.bg-bank-first,
.bg-table th.bg-bank-first {
  border-left: 1px solid var(--line);
}

/* Inherited from the supplier's contact card rather than typed on this line.
   ITALIC SAYS SO; THE INK DOES NOT CHANGE. It used to drop to --ink-faint,
   the palette's quietest grey — so the account a payment is actually going to
   was the hardest thing on the row to read, purely because nobody had retyped
   it onto the line. An inherited BSB is not a less real BSB. */
.bg-table .bg-bank.is-inherited { font-style: italic; }

/* The due date earns a little prominence — it's the only other thing on the
   row you act on. */
.bg-table .bg-date { font-weight: 500; white-space: nowrap; }

/* A quiet hover, so a thirteen-column row is trackable across its width. */
.bg-table tbody tr[data-line]:hover { background: #faf7f9; }


/* ============================================================================
   Budget — contrast
   ============================================================================

   Pink text on a pink band failed the readability test, and pink on white was
   borderline at small sizes. Colour still marks the structure; the words are
   dark enough to actually read.

   Rule of thumb applied throughout: a tint may be the background OR the text,
   never both.
   ========================================================================== */

/* --- the summary strip --- */

.bg-summary { padding: 18px 20px; gap: 34px; }

/* 34px between four figures is a desktop measurement. On a phone it wraps the
   strip into a ladder — moved here from line 2925, where it was written and
   then beaten by the line directly above. Found by CHECK 30. */
@media (max-width: 620px) {
  .bg-summary { gap: 16px; }
}

.bg-stat .k {
  color: var(--ink-soft);
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .1em;
}

.bg-stat .v {
  color: var(--ink);
  font-size: 1.32rem;
  font-weight: 700;
  letter-spacing: -.01em;
  font-variant-numeric: tabular-nums;
}

.bg-stat.is-balance .v { color: var(--pink-deep); }

/* --- column headers: the labels you navigate by --- */

.bg-table thead th {
  padding: 12px;
  border-bottom: 2px solid var(--ink);
  color: var(--ink);
  font-size: .71rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.bg-table thead th.col-head { color: var(--ink); border-bottom-color: var(--ink); }

/* --- category headings: pink, but a pink you can read --- */

.bg-table tr.bg-cat th {
  padding: 32px 12px 9px;
  border-bottom: 2px solid var(--pink);
  color: var(--pink-deep);
  font-size: .84rem;
  font-weight: 700;
  letter-spacing: .16em;
}

/* --- subtotals: dark words on the pink band, not pink on pink --- */

.bg-table tr.bg-subtotal td {
  border-top: none;
  background: var(--pink-tint);
  color: var(--ink);
  font-weight: 700;
}

.bg-table tr.bg-subtotal td:first-child {
  color: var(--pink-deep);
  font-size: .72rem;
  letter-spacing: .1em;
}


/* ============================================================================
   Budget — structure in blue
   ============================================================================

   Pink was doing two jobs at once: brand accent and section marker. Handing
   the structural work to a deep blue frees the pink to mean something again,
   and blue holds far more contrast against white than pink ever will at
   heading sizes.

   The division now: blue is structure, the traffic light is status, pink is
   the one brand highlight on the balance.
   ========================================================================== */

:root {
  --blue:       #1e3f7d;   /* deep enough to read at .84rem on white */
  --blue-soft:  #eef2fa;
  --blue-rule:  #c3d0e8;
}

.bg-table tr.bg-cat th {
  border-bottom: 2px solid var(--blue);
  color: var(--blue);
}

.bg-table tr.bg-subtotal td {
  background: var(--blue-soft);
  border-bottom: 1px solid var(--blue-rule);
  color: var(--ink);
}

.bg-table tr.bg-subtotal td:first-child { color: var(--blue); }

/* The summary strip picks up the same blue on its top edge, so the block and
   the table below it read as one thing. */
.bg-summary { border-top-color: var(--blue); }
.bg-stat.is-balance .v { color: var(--pink-deep); }


/* ============================================================================
   Budget — heading blue, total pink
   ============================================================================

   Two different jobs, so two different colours:

     the category heading   says where a section STARTS  -> blue, bold
     the subtotal row       says what the section COSTS  -> pink, and the
                                                            numbers pink too

   The pink figures on the subtotal band were the part that worked; the last
   pass turned them black along with everything else, which took the punch out
   of the one row you actually look for when you're totting up.
   ========================================================================== */

.bg-table tr.bg-cat th {
  border-bottom: 2px solid var(--blue);
  color: var(--blue);
  font-weight: 700;
}

.bg-table tr.bg-subtotal td {
  background: var(--pink-tint);
  border-bottom: 1px solid var(--pink);
  color: var(--pink-deep);
  font-weight: 700;
}

.bg-table tr.bg-subtotal td:first-child { color: var(--pink-deep); }

/* The summary strip goes back to pink on top, matching the totals rather than
   the section headings. */
.bg-summary { border-top-color: var(--pink); }


/* ---------- what they have to spend ----------------------------------------

   Three segments, not one. "80% of the budget spent" means something very
   different depending on whether that money has left the account or is still
   owed — so paid is green, committed-but-unpaid is amber, and anything past
   the line is red. Only red can push the bar past full.                     */

.bg-target {
  margin-bottom: 18px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper, #fff);
}

.bg-target-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 10px;
}

.bg-target-label {
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.bg-target-value {
  color: var(--ink);
  font-family: inherit;
  font-size: 1.5rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* Only Gee's copy is clickable. The couple's is the same number, stated. */
button.bg-target-value {
  padding: 0;
  border: 0;
  border-bottom: 1px dashed transparent;
  background: none;
  cursor: pointer;
}

button.bg-target-value:hover { border-bottom-color: var(--pink); }

.bg-target-bar {
  display: flex;
  height: 12px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--line);
}

.bg-target-bar span { display: block; height: 100%; transition: width .3s ease; }

/* THE VIVID ENDS, not the dark writing ends.
   ---------------------------------------------------------------------------
   Gee, 1 Sept: "I don't love the green, I don't love the orange, and I don't
   love the red. You just can't really see it properly."

   She was right, and it is the SAME FAULT that was found and fixed for the
   toolbar buttons on 30 August — see the long note at the top of this file.
   Every hue in this palette has two ends: a dark one for TYPE, and a vivid one
   for FILLING A SHAPE. This bar was painted in the type ends, which is why it
   read muddy and flat.

   The buttons were corrected then and the bar was missed. Now it uses the same
   vivid ends the Export and Print buttons already carry, so nothing new was
   invented — the bar simply starts using the half of the palette it should
   always have used.

   ⚠️ The WRITTEN figure keeps the dark red. "$36,075.11 over budget" is type,
   and #d50000 at that size is hard to read. Bright for the shape, dark for the
   words — which is the rule, not an exception to it. */
.bar-paid { background: var(--v-emerald); }
.bar-owed { background: var(--v-tangerine); }
.bar-over { background: var(--late-vivid); }

.bg-target-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 9px;
  font-size: .82rem;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

.leg-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 5px;
  border-radius: 50%;
}

/* The dots are the KEY to the bar, so they have to be the bar's colours. Left
   on the old dark ones they would have quietly stopped matching the thing they
   label — a legend that disagrees with its own chart. */
.leg-dot.is-paid { background: var(--v-emerald); }
.leg-dot.is-owed { background: var(--v-tangerine); }
.leg-dot.is-over { background: var(--late-vivid); }

.bg-target-legend strong { color: var(--danger); }
.leg-room { color: var(--ink-faint); }

/* No target set yet. An invitation, not a bar reading zero — a wedding whose
   budget has never been discussed is not a wedding that is 0% spent. */
.bg-target-set {
  margin-bottom: 18px;
  padding: 9px 15px;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  background: none;
  color: var(--ink-soft);
  font: inherit;
  font-size: .88rem;
  cursor: pointer;
}

.bg-target-set:hover { border-color: var(--pink); color: var(--pink-deep); }


/* ---------- who is this invoice from? --------------------------------------

   A list of names you scan, not a dropdown you hunt through. Each row says
   the same three things Aisle Planner's does — who they are, what kind of
   supplier, and whether they're already on this budget — because those are
   the three things that decide whether it's the right row.                  */

.vendor-list {
  max-height: 46vh;
  overflow-y: auto;
  margin-top: 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

/* Adding a supplier who is not in Contacts yet, in the Add invoice window
   rather than in two browser prompt boxes.

   Kadija, 1 Sept: "It asked me to select a category, but it didn't let me
   click on the category. It just showed the whole list in a text box."

   It borrows .tp-rem-opt for the category buttons — the same pill the task
   reminders use — so there is one "pick one of these" shape in the app rather
   than a third invention. */
/* ---------------------------------------------------------------------------
   THE CLOCK ON A TIME BOX.

   Every time field in the app is turned into a plain text box at boot, so that
   Gee sees "9:30 am" instead of 09:30. That fixed the army time she asked
   about twice — and quietly took away the PICKER, because a text box has none.

   Kadija, 1 Sept, on a supplier's team times: "I couldn't change their times.
   It didn't show the options." The field worked. It did not look like it did.

   The clock is DRAWN INSIDE the box, not added beside it. Twelve of these sit
   in twelve different layouts — a form grid, a flex row of three, a table
   cell — and a new sibling element in each is twelve chances to break one.
   A background image cannot disturb a layout. The whole box is the trigger,
   so the target ends up bigger than a button would have been.
   --------------------------------------------------------------------------- */
/* ⚠️ The rule that paints the clock is in BLOOM.CSS, not in this file.
   `bloom.css` loads after this one and restyles every text input, setting both
   the `padding` and `background` shorthands at the same specificity as
   `input[data-loose-time="1"]`. Later file wins, so a clock declared here is
   erased no matter where in this file it sits — I moved it to the end of this
   file first and it still lost. It has to live in the last sheet that touches
   inputs. See the note at the bottom of bloom.css. */

/* On <body> in page coordinates, like every other menu here — a form field
   inside a modal clips its own overflow. */
.clock-list {
  position: absolute;
  z-index: 200;
  max-height: 232px;
  overflow-y: auto;
  padding: 4px 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(42, 33, 38, .12), 0 2px 6px rgba(42, 33, 38, .08);
}

.clock-opt {
  display: block;
  width: 100%;
  padding: 6px 14px;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: .86rem;
  text-align: left;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
}

.clock-opt:hover,
.clock-opt:focus-visible { background: var(--line-soft); outline: none; }

.clock-opt.is-on {
  background: var(--teal-deep);
  color: #fff;
  font-weight: 600;
}

/* Room for a thumb. */
@media (hover: none) {
  .clock-opt { padding: 10px 16px; font-size: .92rem; }
  .clock-list { max-height: 260px; }
}

@media print { .clock-list { display: none !important; } }

/* A filter over 48 buttons. Full width and quiet — it is a way through the
   list, not a second thing to fill in. */
.vendor-new-filter {
  width: 100%;
  margin-bottom: 8px;
  padding: 7px 11px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-soft);
  color: var(--ink);
  font: inherit;
  font-size: .85rem;
}
.vendor-new-filter:focus {
  outline: none;
  border-color: var(--teal);
  background: var(--surface);
}

/* Categories that look like the same thing twice. Reports only — see the note
   in settings.js. */
.cat-dupes:not(:empty) { margin-top: 14px; }

.cat-dupe-head {
  margin: 18px 0 4px;
  font-size: .95rem;
  font-weight: 600;
  color: var(--ink);
}
.cat-dupes .cat-dupe-head:first-child { margin-top: 4px; }

.cat-dupe-group {
  margin: 8px 0 0;
  padding: 10px 14px;
  list-style: none;
  border: 1px solid var(--line);
  border-left: 3px solid var(--warn-mid);
  border-radius: 10px;
  background: var(--surface-soft);
}
.cat-dupe-group li {
  padding: 3px 0;
  font-size: .9rem;
  color: var(--ink-soft);
}
.cat-dupe-group li strong { color: var(--ink); font-weight: 600; }

.cat-empty { color: var(--warn); font-style: normal; font-weight: 600; }

.cat-dupe-foot { margin-top: 16px; }

.vendor-new { padding: 18px 18px 16px; }
.vendor-new .field { margin-bottom: 16px; }
.vendor-new .field:last-of-type { margin-bottom: 14px; }
.vendor-new .toolbar { justify-content: flex-end; gap: 8px; }
.vendor-new .tp-rem-opts { margin-top: 4px; }

.vendor-opt {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: baseline;
  gap: 12px;
  width: 100%;
  padding: 10px 14px;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.vendor-opt:last-child { border-bottom: 0; }
.vendor-opt:hover      { background: var(--pink-tint); }

.vendor-name { font-weight: 600; }
.vendor-cat  { color: var(--ink-faint); font-size: .8rem; }

.vendor-state {
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  white-space: nowrap;
}

/* Already on the budget: this is the common case and the reassuring one. */
.vendor-state.has-line { color: var(--teal-deep); }

/* Adding somebody without leaving the wedding — the trip that used to mean
   going out to Contacts and coming back. */
.vendor-opt.is-new {
  position: sticky;
  bottom: 0;
  border-top: 1px solid var(--line);
  background: var(--teal-tint, #e7f6f7);
  color: var(--teal-deep);
}

.vendor-opt.is-new .vendor-name { font-weight: 700; }
.vendor-opt.is-new:hover { background: var(--teal-tint, #e7f6f7); filter: brightness(.98); }

.modal-narrow { max-width: 520px; }

/* ---------- when something breaks, say so ----------------------------------

   A bar across the top of the app, not a toast: a toast disappears, and the
   whole lesson of 25 August is that a failure nobody sees is worse than the
   failure. It stays until it is dismissed.

   Red, because it is a fault — but the second line says the data is safe,
   because the first thing a person thinks when a screen goes wrong is that
   they have lost their work.                                               */

.crash-bar {
  position: sticky;
  top: 0;
  z-index: 60;
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 12px 18px;
  background: var(--danger);
  color: #fff;
  font-size: .9rem;
  line-height: 1.5;
}

.crash-bar-main { flex: 1 1 auto; }
.crash-bar strong { display: block; }

.crash-bar-detail {
  display: block;
  margin-top: 2px;
  font-family: ui-monospace, "Cascadia Mono", Menlo, monospace;
  font-size: .78rem;
  opacity: .85;
  word-break: break-word;
}

.crash-bar-safe {
  display: block;
  margin-top: 6px;
  font-size: .84rem;
  opacity: .95;
}

.crash-bar-close {
  flex: 0 0 auto;
  padding: 0 4px;
  border: 0;
  background: none;
  color: #fff;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  opacity: .8;
}

.crash-bar-close:hover { opacity: 1; }

/* Never on paper. */
@media print { .crash-bar { display: none !important; } }


/* A select with an escape hatch beside it: pick someone, or add someone. */
/* ⚠️ CAPPED. Gee, 7 Sept: "you don't need 'Metropolis Events' as a supplier to
   be that long, where it's got 'Supplier', then 'Metropolis Events', then
   'New edit Metropolis Events'. That's too fucking long."
   The input was `flex: 1 1 auto` inside a full-width field, so a 17-character
   business name was handed 1,014px and the "Edit ⟨name⟩" link was pushed to the
   far side of the card, reading as a third thing rather than as a link about
   the box beside it. A supplier name is a supplier name; 560px holds the
   longest one she has with room to spare. */
.pick-row { display: flex; gap: 8px; align-items: stretch; max-width: 720px; }

/* The supplier picker's results, which replaced a <datalist> on 10 Sept —
   with 330 contacts the browser's own dropdown would not offer a match and
   there was nothing on this side that could make it. Reuses .vendor-list
   wholesale so it is the same control as the budget's "Add invoice" picker;
   this rule only matches it to .pick-row's width so the edges line up, and
   caps the height so a broad search cannot push the Save button off screen. */
#s-contact-results {
  max-width: 720px;
  max-height: 260px;
  overflow-y: auto;
  margin-top: 6px;
}
/* input as well as select: the supplier picker became a typeable box on
   29 Aug and would otherwise collapse to its default width beside + New. */
.pick-row select,
.pick-row input { flex: 1 1 auto; min-width: 0; }
.pick-row .btn { flex: 0 0 auto; white-space: nowrap; }

/* Read-only while editing — you cannot swap the supplier on a booking that
   already exists. Looks like a field, not like something you can change. */
.pick-row input[readonly] {
  background: var(--surface-soft);
  color: var(--ink-soft);
  cursor: default;
}


/* ---------- what the document says -----------------------------------------

   The gap is the headline, because agreeing with the budget is the only
   reason to read an invoice you have already filed. Green when it matches,
   red when it doesn't, and the figures underneath as supporting evidence.

   Nothing here writes anything. The caveat under the button says so, and it
   stays there rather than being a thing you learn once.                     */

/* .file-row is a flex ROW, so this has to be told to take a whole line of its
   own — otherwise it becomes another item squeezed in beside the buttons and
   an error message renders as a tall thin column of single words. */
.bf-read {
  flex: 1 0 100%;
  margin-top: 10px;
  padding: 14px 16px;
  border-radius: 10px;
  background: var(--paper-sunk, #f5f0f2);
  border: 1px solid var(--line);
}

.file-row { flex-wrap: wrap; }

.bf-read-wait  { margin: 0; color: var(--ink-soft); font-size: .88rem; }
.bf-read-error { margin: 0; color: var(--danger); font-size: .88rem; }
.bf-read-done  { margin: 0; color: var(--pay-done, #00733d); font-size: .88rem; font-weight: 600; }

.bf-read-gap {
  font-size: 1.02rem;
  font-weight: 700;
  margin-bottom: 8px;
}

.bf-read-gap.is-match { color: var(--pay-done, #00733d); }
.bf-read-gap.is-off   { color: var(--danger); }
.bf-read-gap.is-new   { color: var(--ink-soft); font-weight: 600; }

/* A deposit or a receipt covering part of the line. Teal, not red: this is the
   ordinary way a booking is paid, and it was reading as three errors on a line
   where nothing was wrong. See gapGist in budgetfiles.js. */
.bf-read-gap.is-part  { color: var(--teal-deep); }

/* ---------- the empty state on a budget line's documents ------------------
   Gee: the attach button "doesn't look like it was meant to be more
   prominent". It was the smallest class in the app, sitting UNDER the sentence
   saying there was nothing here, so it read as a footnote to a "no". */
.bf-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  text-align: center;
  padding: 19px 16px;
  border: 2px dashed var(--line);
  border-radius: 11px;
  background: var(--surface-soft);
}
.bf-drop-big  { font-weight: 700; font-size: .95rem; }
.bf-drop-sub  { font-size: .8rem; color: var(--ink-soft); max-width: 36ch; line-height: 1.45; }

/* ---------- how the couple pays them -------------------------------------
   Two blocks, not one grid, because a bank transfer and a card payment are
   different answers to one question — and the card half had never been filled
   in once on 336 contacts, because an empty box in a grid says nothing. */
.pay-way {
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
  margin-top: 10px;
}
.pay-way-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  background: var(--surface-soft);
  border-bottom: 1px solid var(--line);
  font-weight: 700;
  font-size: .78rem;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.pay-way-body { padding: 12px; }

.pay-way-state {
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 20px;
  border: 1px solid;
  white-space: nowrap;
}
/* Amber, not red. Nothing is WRONG with a supplier you pay another way —
   it is a blank worth noticing, not a fault. */
.pay-way-state.is-off { background: var(--warn-tint); color: var(--warn);
                        border-color: var(--warn); }
.pay-way-state.is-on  { background: var(--teal-tint); color: var(--teal-deep);
                        border-color: var(--teal); }

/* "Not needed" — a way of paying this line does not use.
   Gee, 17 September, on a Cash line: "we don't need their bank details."
   Grey and quiet ON PURPOSE. It is the ANSWER to the question, not a blank
   worth noticing, so it must not wear the amber `is-off` wears — the whole
   complaint was a warning colour on a line where nothing is wanted. */
.pay-way-state.is-moot { background: var(--grey-tint, #f2f0f1);
                         color: var(--ink-faint, #a0949a);
                         border-color: var(--line, #ece5e8); }

/* An INHERITED detail is real data wearing a placeholder.
   Gee, 16 September, twice: the supplier's BSB is supposed to reach the budget
   line. It did, and nothing showed it — the boxes sat empty because blank MEANS
   "use theirs", so the only place the number appeared was an 11px hint. It is
   the placeholder now, and a placeholder is --ink-faint by default, which is
   correct for the example "633 123" and much too quiet for the account this
   money is going to. Darker and heavier, but still not --ink: it must stay
   visibly untyped, or she will not know it can be overridden. */
.pay-way-body input.is-inherited::placeholder {
  color: var(--ink-soft);
  font-weight: 600;
  opacity: 1;               /* Firefox dims placeholders by default. */
}

/* ---------- a reading, folded --------------------------------------------
   Three filed documents drew three full readings, so the files themselves
   scrolled off the screen. The summary carries the GIST, not the word
   "Reading", so all three can be read shut. Native details: no handler to
   bind and it survives a re-render. */
.bf-fold { margin-top: 6px; }

.bf-fold-sum {
  cursor: pointer;
  list-style: none;
  padding: 7px 10px 7px 26px;
  border-radius: 8px;
  background: var(--surface-soft);
  font-weight: 600;
  font-size: .85rem;
  position: relative;
  transition: background .12s ease;
}
.bf-fold-sum::-webkit-details-marker { display: none; }

/* The triangle, drawn rather than inherited, so it turns with the fold. */
.bf-fold-sum::before {
  content: "";
  position: absolute;
  left: 10px;
  top: 50%;
  width: 0;
  height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transform: translateY(-50%);
  transition: transform .14s ease;
}
.bf-fold[open] > .bf-fold-sum::before { transform: translateY(-50%) rotate(90deg); }

.bf-fold-sum:hover { background: var(--line-soft, var(--surface-soft)); }
.bf-fold-sum:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

/* Same colour language as the headline inside, so shut and open agree. */
.bf-fold-sum.is-match { color: var(--pay-done, #00733d); }
.bf-fold-sum.is-off   { color: var(--danger); }
.bf-fold-sum.is-part  { color: var(--teal-deep); }
.bf-fold-sum.is-new   { color: var(--ink-soft); }

/* A wrong supplier matters more than a few dollars — it means the document
   may be filed against the wrong business entirely. */
.bf-read-warn {
  margin-bottom: 10px;
  padding: 9px 12px;
  border-left: 3px solid var(--danger);
  background: var(--danger-tint, #fdeeec);
  border-radius: 0 8px 8px 0;
  font-size: .88rem;
}

.bf-read-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: 10px 16px;
  margin: 0 0 10px;
}

.bf-read-facts dt {
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.bf-read-facts dd {
  margin: 2px 0 0;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.bf-read-note {
  margin: 0 0 12px;
  padding: 9px 12px;
  border-left: 3px solid var(--teal);
  background: var(--teal-tint, #e7f6f7);
  border-radius: 0 8px 8px 0;
  font-size: .88rem;
  color: var(--ink);
}

.bf-read-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.bf-read-caveat { font-size: .78rem; color: var(--ink-faint); }


/* ---------- the invoice inbox ----------------------------------------------

   Above the weddings on the front door, because an unfiled invoice is the
   thing to deal with before going anywhere. It disappears completely when
   empty — a permanent empty box on the front door is furniture.

   Every guess is presented AS a guess: a dropdown already set to it, never a
   fact stated. Confirming is a button, correcting is a dropdown.           */

.inbox {
  margin-bottom: 26px;
  padding: 18px 20px 20px;
  border: 1px solid var(--amber, #d4121f);
  border-left-width: 4px;
  border-radius: var(--radius);
  background: var(--amber-tint, #ffe3e4);
}

.inbox-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 14px;
  /* The count used to be "5 waiting" and is now "1 contract · 2 conversations
     · 2 photos", which on a phone is longer than the heading beside it. */
  flex-wrap: wrap;
}

/* WHAT SHE PUT ASIDE, AND THE WAY BACK. 15 September.

   "Done with it" wrote a status nothing read, and the list only ever loaded
   pending — so a dismissed card left her view for good and the only way back
   was somebody running SQL. It cost her the Ritz conversation carrying an
   $8,750 promise.

   Deliberately quiet: a line of text, not a second panel. This is a door, not
   a place she is meant to spend time in, and a drawer that shouts competes
   with the cards that actually need her. */
.inbox-aside {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--amber, #d4121f);
}

.inbox-aside-toggle {
  font: inherit;
  font-size: .84rem;
  font-weight: 600;
  color: var(--amber, #d4121f);
  background: none;
  border: 0;
  padding: 2px 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.inbox-aside-list { margin-top: 12px; }

/* THE SECOND DOOR, IN THE HEADING. 15 September.

   Same words and the same amber as the toggle at the bottom, deliberately —
   it is one control in two places, and a different colour or a different
   sentence would read as a different feature.

   `margin-left: auto` pushes it to the far end of the flex row, so it never
   crowds the count, and the row already wraps on a phone. A pill rather than
   a bare link because it is sitting beside a heading and has to survive being
   glanced past. */
.inbox-aside-chip {
  margin-left: auto;
  font: inherit;
  font-size: .8rem;
  font-weight: 600;
  color: var(--amber, #d4121f);
  background: var(--warn-tint, #ffe3e4);
  border: 1px solid var(--amber, #d4121f);
  border-radius: 999px;
  padding: 3px 12px;
  cursor: pointer;
  white-space: nowrap;
}
.inbox-aside-chip:hover { filter: brightness(.97); }

/* The card keeps its own look; only the button beside it changes. Reusing
   .inbox-card means a put-aside conversation reads exactly as it did in the
   inbox, which is the whole point — she cannot decide about a row she cannot
   read. */
/* SELECT SEVERAL, 20 Oct. The tick sits OUTSIDE the card, in a row of its
   own, because the cards are three different layouts (a photo card is a grid)
   and a box inside each would have been three placements to keep right.
   ⚠️ CHECK 69: measured live at 16 × 16 — the app-wide checkbox base at the
   end of this file sets every tick's size, so the box is the budget's own. */
.inbox-pick-row { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 10px; }
.inbox-pick-row > .inbox-card { flex: 1 1 auto; min-width: 0; margin-bottom: 0; }
.inbox-pick { flex: none; padding-top: 18px; cursor: pointer; }
.inbox-pick input { margin: 0; cursor: pointer; }
#inbox-select-all { margin-left: auto; }
#inbox-select-all + .inbox-aside-chip { margin-left: 8px; }
/* MINIMISE, 22 Oct — solid pink so it is found without being told about it
   (memory a-control-you-have-to-be-told-about). Shut, only the heading line
   stays: the count still says something is waiting. View only. */
.inbox-min { margin-left: 8px; background: var(--pink, #c2185b); color: #fff; border: 1px solid var(--pink, #c2185b); }
.inbox-min:hover { filter: brightness(.95); }
#invoice-inbox.is-min { padding-top: 10px; padding-bottom: 10px; }
#invoice-inbox.is-min .inbox-head { margin: 0; }
#invoice-inbox.is-min #inbox-list,
#invoice-inbox.is-min #inbox-aside,
#invoice-inbox.is-min #inbox-picked-bar,
#invoice-inbox.is-min #inbox-select-all,
#invoice-inbox.is-min #inbox-aside-chip { display: none !important; }
#invoice-inbox.is-min #inbox-min { margin-left: auto; }
.inbox-picked-bar { flex-wrap: wrap; }
/* `width: auto` — the global select is 100% wide, which put the chooser on a
   line of its own and the buttons under it. */
.inbox-picked-bar select { width: auto; flex: 0 1 300px; min-width: 200px; }

.inbox-aside-row + .inbox-aside-row { margin-top: 12px; }
.inbox-aside-row .inbox-card { opacity: .82; }
.inbox-aside-act { margin-top: 8px; }

.inbox-head h2 {
  margin: 0;
  font-size: 1.05rem;
}

.inbox-count {
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--amber-deep, #d4121f);
}

.inbox-card {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  align-items: flex-start;
  justify-content: space-between;
  padding: 16px 18px;
  margin-bottom: 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper, #fff);
}

.inbox-card:last-child { margin-bottom: 0; }
.inbox-card.is-unread  { border-left: 3px solid var(--danger); }

.inbox-main { flex: 1 1 340px; min-width: 0; }

.inbox-who {
  font-weight: 700;
  font-size: 1.02rem;
  display: flex;
  align-items: baseline;
  gap: 9px;
  flex-wrap: wrap;
}

.inbox-kind {
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 1px 8px;
}

.inbox-from { margin-top: 3px; font-size: .78rem; color: var(--ink-faint); }

.inbox-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: 9px 16px;
  margin: 12px 0 10px;
}

.inbox-facts dt {
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.inbox-facts dd {
  margin: 2px 0 0;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------------------
   THE PAYMENT SCHEDULE AN INVOICE SETS OUT. 14 September.

   Gee: "sometimes it already has the payment schedule on those invoices…
   can you add those payment schedules below where we've got that payment
   schedule section."

   ⚠️ SHOWN HERE BEFORE IT IS WRITTEN. Filing creates a real payment row per
   instalment, and nothing an AI read may land without her having seen it
   proposed. Sage, because these are dated money — the same family the budget
   uses for a figure that is real rather than estimated.
   --------------------------------------------------------------------------- */
.inbox-sched {
  margin: 0 0 10px;
  padding: 9px 12px;
  border-left: 3px solid var(--c-sage);
  background: var(--c-sage-tint);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.inbox-sched > b { display: block; font-size: .84rem; color: var(--c-sage); margin-bottom: 5px; }
.inbox-sched ul { margin: 0 0 5px; padding: 0; list-style: none; }
.inbox-sched li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 10px;
  padding: 3px 0;
  font-size: .85rem;
}
.sch-what { font-weight: 600; }
.sch-when { color: var(--ink-soft); white-space: nowrap; }
/* Tabular, because two amounts one above the other are read as a column. */
.sch-amt  { font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.inbox-sched .hint { font-size: .78rem; }

/* ---------------------------------------------------------------------------
   A contract that came in by email, folded shut.

   Four quoted clauses and four lists is the length of a page, and the inbox is
   a list of things waiting — a card that tall buries the invoices underneath
   it. So the panel is the SAME `.fr-panel` markup as on a booking (checks.css
   styles it, and that is deliberate: an emailed contract and an uploaded one
   must not look like two different features), only behind a fold.

   ⚠️ The `.fr-panel` inside carries its own margin and border, so this row adds
   neither — nesting a bordered panel inside a bordered box is the double frame
   that made the findings drawer look broken in §39.1.
   --------------------------------------------------------------------------- */
.inbox-contract { margin: 0 0 10px; }
.inbox-contract > summary {
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  font-size: .84rem;
  font-weight: 700;
  color: var(--c-sky);
  padding: 6px 10px;
  background: var(--c-sky-tint);
  border-radius: var(--radius-sm);
}
.inbox-contract > summary:hover { filter: brightness(.97); }
/* ⚠️ `display: flex` on a <summary> DELETES the browser's disclosure triangle,
   with no warning — so the row above loses the one thing that says it opens,
   and a contract's clauses sit behind a pill nobody presses. Drawn back by
   hand. `list-style: none` as well, because Safari uses a ::-webkit marker
   that flex does not remove. */
.inbox-contract > summary { list-style: none; }
.inbox-contract > summary::-webkit-details-marker { display: none; }
.inbox-contract > summary::before {
  content: '';
  flex: 0 0 auto;
  width: 6px;
  height: 6px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform .12s ease;
}
.inbox-contract[open] > summary::before { transform: rotate(45deg); }
/* The panel already sits in a card; it does not need a second warm ground. */
.inbox-contract .fr-panel { margin: 8px 0 0; background: transparent; }

/* "Record $1,000 as already paid" — sage, because it is dated money that is
   real rather than estimated, the same family the schedule below it uses. */
.inbox-paid {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0 0 10px;
  padding: 8px 12px;
  border-left: 3px solid var(--c-sage);
  background: var(--c-sage-tint);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: .86rem;
  cursor: pointer;
}
.inbox-paid input { width: 15px; height: 15px; accent-color: var(--c-sage); flex: 0 0 auto; }
.inbox-paid b { font-variant-numeric: tabular-nums; }

.inbox-note  { margin: 0 0 8px; font-size: .86rem; color: var(--ink-soft); }

/* WHAT THE APP ACTUALLY HAS, against a reading that says it lost it.
   Teal because it is good news and it is CORRECTING the grey summary above
   it — in the same grey it would read as more of the same paragraph, which is
   exactly the sentence it exists to contradict. Only a rule and a colour: the
   shape stays .inbox-note so there is one definition of what a note looks
   like. */
.inbox-note.is-good {
  color: var(--teal-deep, #007c85);
  font-weight: 600;
  border-left: 3px solid var(--teal-deep, #007c85);
  padding-left: 10px;
}
.inbox-terms {
  margin: 0 0 10px;
  padding: 8px 12px;
  border-left: 3px solid var(--teal);
  background: var(--teal-tint, #e7f6f7);
  border-radius: 0 8px 8px 0;
  font-size: .84rem;
}

.inbox-pickers {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 10px;
}

.inbox-pickers label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1 1 200px;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.inbox-pickers select { font-size: .9rem; }

.inbox-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: flex-start;
}

/* An invoice that has been here before, or is already on the budget. Red
   rather than the panel's amber, because the two mean different things: amber
   is "deal with this", red is "you may be about to pay twice". */
.inbox-card.is-twin {
  border-left: 3px solid var(--danger);
  background: var(--danger-tint, #fdeeec);
}

.inbox-twin-tag {
  padding: 1px 8px;
  border: 1px solid var(--danger);
  border-radius: 999px;
  color: var(--danger);
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
}

.inbox-card.is-twin .inbox-note { color: var(--danger); font-weight: 600; }


/* ---------- the instalments behind a line ----------------------------------

   A sub-row under the item, not extra columns: a couple reads this on a phone,
   and the table already has five. Deliberately quiet — the money columns above
   are the answer, and this is the detail you drop to when you want the dates.

   Three states, and only two of them are coloured. Paid is green, overdue is
   red, and a payment simply coming up is plain grey, because a deposit due in
   October is not a warning and should not wear the colour of one.            */

.bg-table tr.bg-instalments td {
  padding: 0 12px 9px;
  border-bottom: 1px solid var(--line);
  background: none;
}

.ins {
  display: inline-block;
  margin: 3px 14px 0 0;
  color: var(--ink-soft);
  font-size: .76rem;
  white-space: nowrap;
}

.ins + .ins::before {
  content: '·';
  margin-right: 14px;
  color: var(--line);
}

.ins-done { color: var(--pay-done); }
.ins-late { color: var(--danger); font-weight: 600; }

/* ---------------------------------------------------------------------------
   "WE'VE PAID THIS" — migration 123.

   It exists because 121 chases an overdue payment every three days and a chase
   only stops on `is_paid`, which a couple cannot set. Without it, somebody who
   had already transferred the money had no way to say so.

   ⚠️ IT IS A CLAIM AND IT MUST NOT LOOK LIKE A TICK. `--pay-part`, not
   `--pay-done` — Gee's confirmation is still what "paid" means, and a green
   row here would say the opposite on the one screen where money must not be
   ambiguous.
   -------------------------------------------------------------------------- */
.ins-claimed { color: var(--pay-part); font-weight: 600; }

.ins-paid {
  margin-left: 8px;
  padding: 3px 9px;
  border: 1px solid var(--danger);
  border-radius: 999px;
  background: none;
  font-family: inherit;
  font-size: .72rem;
  font-weight: 700;
  color: var(--danger);
  cursor: pointer;
  white-space: nowrap;
}
.ins-paid:hover { background: var(--warn-tint); }
.ins-paid:focus-visible { outline: 2px solid var(--danger); outline-offset: 2px; }

/* The target is the thing — they will be on a phone. */
@media (hover: none) { .ins-paid { min-height: 40px; padding: 8px 12px; } }
@media print { .ins-paid { display: none !important; } }

/* ---------- payments coming up, on the couple's To do tab -----------------

   Every unpaid instalment across every line they can see, in one block above
   the checklist. Reads the schedule live rather than creating tasks, so it
   cannot drift out of step and a payment leaves it the moment it is ticked.

   Deliberately quiet: a plain list, one accent colour, and red reserved for
   genuinely overdue. It sits above a to-do list and must not shout over it. */
/* ⚠️ NO LONGER QUIET, and the note above is kept because it records what was
   tried. Gee, 1 Sept, looking at the couple's page as Tiffany: "the payment
   coming up, you can't really tell that a payment is coming up. It's quite
   lost in the background. I think it should have that full orange background."
   Her words, and she was right — a white card with a thin pink edge reads as
   another panel, not as money leaving her clients' account this month.

   Tinted, not filled: the rows inside carry their own amber and red pills for
   soon and overdue, and a solid orange behind them would swallow both. This is
   --warn-tint, the same wash the findings panel uses for "check this", so it is
   a colour the app already means something by. */
.pay-due {
  margin: 0 0 18px;
  padding: 16px 18px;
  border: 1px solid var(--warn-mid);
  border-left: 3px solid var(--warn);
  border-radius: 12px;
  background: var(--warn-tint);
}

/* The heading has to carry the tint too, or it looks pasted on. */
.pay-due-head h3 { color: var(--warn); }

/* On a tint, --ink-faint is nearly invisible. Both of these step up one. */
.pay-due { --pay-quiet: var(--ink-soft); }

.pay-due-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 12px;
  margin-bottom: 10px;
}
.pay-due-head h3 { margin: 0; font-size: 1rem; }
.pay-due-total { font-size: .8rem; color: var(--pay-quiet, var(--ink-faint)); }

.pay-due-list { margin: 0; padding: 0; list-style: none; }

.pay-due-row {
  display: grid;
  grid-template-columns: 8.5rem 1fr auto;
  gap: 10px;
  align-items: baseline;
  padding: 7px 0;
  /* --warn-mid, not --line: a warm near-white rule is invisible on the tint
     this card now carries. */
  border-top: 1px solid var(--warn-mid);
  font-size: .9rem;
}
.pay-due-row:first-child { border-top: 0; }

.pay-due-when { color: var(--pay-quiet, var(--ink-faint)); font-size: .82rem; }
.pay-due-who  { color: var(--ink); min-width: 0; }
.pay-due-amt  { font-weight: 700; white-space: nowrap; }

/* The platform's traffic light, doing the job it already does on the budget's
   status pills — red is overdue, amber is close. No green: everything in this
   block is unpaid, and a green outstanding amount would say it was fine.
   The date carries a tinted pill so the colour is a shape as well as a hue,
   which is DECISIONS §4 — a near miss on colour still cannot be misread. */
.pay-due-row.is-soon .pay-due-when,
.pay-due-row.is-late .pay-due-when {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-weight: 700;
}
/* ⚠️ WHITE, not the tint. --pay-part-soft IS --warn-tint, which is now the
   colour of the card behind it — the "due soon" pill would have disappeared
   completely the moment the background went orange. Found by checking what the
   pill used before changing what sat under it, rather than after.

   White reads as a chip ON the tint, keeps the amber and red as the thing that
   carries the meaning, and works for both states without inventing a colour. */
.pay-due-row.is-soon .pay-due-when {
  background: var(--surface);
  color: var(--pay-part);
}
.pay-due-row.is-late .pay-due-when {
  background: var(--surface);
  color: var(--pay-late);
}
.pay-due-row.is-late .pay-due-amt { color: var(--pay-late); }

.pay-due-later {
  margin: 10px 0 0;
  padding-top: 12px;
  /* Same reason as the row rule above — invisible on the tint otherwise. */
  border-top: 1px solid var(--warn-mid);
  font-size: .92rem;
  color: var(--ink);
}

/* Both of these were too small and too faint to read. Gee: "you can't really
   see that... it should be in better text, better font, it's very hard to
   read." They carry the two things a couple most needs off this block — where
   the account actually is, and that anything asking them to pay elsewhere is
   a scam — so they cannot be the quietest thing on the page. */
.pay-due-note {
  margin: 12px 0 0;
  font-size: .88rem;
  line-height: 1.6;
  color: var(--ink-soft);
}

/* The Budget link is the action in both lines. Make it read as one. */
.pay-due-note a,
.pay-due-later a {
  color: var(--pink);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* "We've paid this", on its own line under the amount. Full width in both
   layouts, so it never squeezes the supplier name or the figure. */
.pay-due-act { grid-column: 1 / -1; }
.pay-due-act .ins-paid { margin-left: 0; }

@media (max-width: 560px) {
  .pay-due-row { grid-template-columns: 1fr auto; }
  .pay-due-when { grid-column: 1 / -1; }
}

/* ---------- the couple's paperwork ----------------------------------------

   The invoices and quotes on a line, as chips under the supplier name. They
   say what the document IS rather than what it is called: "Invoice" beats
   "SCAN_0042_final_v3.pdf" for a person deciding whether to open it.        */

.bg-files {
  display: block;
  margin-top: 4px;
}

.bg-file {
  display: inline-block;
  margin: 3px 6px 0 0;
  padding: 2px 9px;
  border: 1px solid var(--teal);
  border-radius: 999px;
  color: var(--teal-deep);
  font-size: .7rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}

.bg-file:hover { background: var(--teal-tint, #e7f6f7); }

/* A file whose signed link didn't come back. Shown rather than hidden, so
   "where's my invoice?" is answerable instead of mysterious. */
.bg-file.is-dead {
  border-color: var(--line);
  color: var(--ink-faint);
}

/* Thirteen columns is a lot for a phone. The table scrolls sideways inside
   its own box — the page itself never does — and the supplier name holds its
   ground so you always know which row you are reading. */
#portal-body .bg-table { min-width: 900px; }

/* ---------------------------------------------------------------------------
   THE COUPLE'S BUDGET ON A PHONE — A CARD A LINE, NOT A TABLE. 18 September.

   🛑 Gee: *"the view right now on the phone, when you go to the Budget (and the
   Budget is the main one), is not the best."*

   The note above this one said it out loud and settled for less: **"Thirteen
   columns is a lot for a phone"** — and the answer was a **900px table you
   scroll sideways**, on the tab she says matters most. Measured: 13 columns,
   40 rows.

   ⚠️ CSS ONLY, AND SCOPED TO `#portal-body`. This is HER budget screen moved
   into the portal, not a copy (§36.1). Changing the markup would change her
   own Budget tab; changing it here cannot.

   ⚠️ THE LABELS COME FROM THE COLUMN CLASSES, not from `data-label` attributes
   that would have to be added to every cell — so a column that gets renamed in
   her table cannot leave a stale label here. There is no attribute to forget.

   WHAT A COUPLE NEEDS, IN THIS ORDER: who, what is left, when, and only then
   how to pay. Estimate and Difference are her bookkeeping and are hidden —
   they are on her own screen, untouched, a tap away for her.
   -------------------------------------------------------------------------- */
@media (max-width: 700px) {
  #portal-body .bg-table,
  #portal-body .bg-table tbody,
  #portal-body .bg-table tr,
  #portal-body .bg-table td { display: block; width: auto; }

  #portal-body .bg-table { min-width: 0; }
  #portal-body .bg-table thead { display: none; }

  /* 🛑 A CARD YOU CAN SEE THE EDGE OF. Gee, after the first version: *"you
     can't tell each card because there's no border or anything like that."*
     She is right — a white card on a near-white page, separated by nothing but
     a 1px shadow, is not separated at all.

     ⚠️ THIS BENDS bloom.css's "surfaces are tinted, not outlined" ON PURPOSE,
     and the reason is the phone. That rule was written about a desktop where a
     whole table reads as one object; here every line has to read as its own
     thing on a 375px screen, and she has now told me twice which way she wants
     it. The outline is soft, not a spreadsheet's. */
  #portal-body .bg-table tbody tr:not(.bg-cat):not(.bg-subtotal):not(.bg-thread) {
    background: var(--surface, #fff);
    border: 1px solid var(--line);
    /* 🛑 AND THE LEFT EDGE MEANS SOMETHING. The row already wears pay-done /
       pay-part / pay-late / pay-none — "the class the row wears, so the
       coloured edge matches the pill", and the first version of this card
       overrode it with a flat grey, throwing the meaning away. The rules below
       put it back, so a phone scan shows which suppliers are settled without
       reading a word. */
    border-left: 4px solid var(--pay-none-soft);
    border-radius: 12px;
    padding: 14px 15px;
    margin-bottom: 12px;
    box-shadow: 0 1px 3px rgba(28, 37, 35, .08), 0 4px 14px rgba(28, 37, 35, .05);
  }

  /* ⚠️ THE `:not()` CHAIN IS REPEATED, AND IT HAS TO BE. The base rule above
     carries three `:not(.class)` selectors, and `:not()` counts its argument's
     specificity — so `tr.pay-done` alone LOSES to it and every card came out
     the same grey. Measured, not assumed: all three edges read
     rgb(242,240,241) before this. Same family as
     `specificity-ties-go-to-the-later-file`. */
  #portal-body .bg-table tbody tr.pay-done:not(.bg-cat):not(.bg-subtotal):not(.bg-thread) {
    border-left-color: var(--pay-done);
  }
  #portal-body .bg-table tbody tr.pay-part:not(.bg-cat):not(.bg-subtotal):not(.bg-thread) {
    border-left-color: var(--pay-part);
  }
  #portal-body .bg-table tbody tr.pay-late:not(.bg-cat):not(.bg-subtotal):not(.bg-thread) {
    border-left-color: var(--pay-late);
  }
  #portal-body .bg-table tbody tr.pay-none:not(.bg-cat):not(.bg-subtotal):not(.bg-thread) {
    border-left-color: var(--pay-none-soft);
  }

  #portal-body .bg-table td {
    border: none;
    padding: 2px 0;
    white-space: normal;
    text-align: left;
  }

  /* 🛑 THE SUPPLIER'S NAME IS THE HEADING OF THE CARD. Gee: *"I just feel like
     you need to make it a bit more prominent so you can tell the different
     suppliers."*

     ⚠️ AND WEIGHT ALONE COULD NOT DO IT — it was already 700, sitting the same
     size as the money under it. `bold-is-not-only-weight`: when weight hits the
     ceiling the levers are SIZE, CONTRAST and SPACE. So it goes up to 1.15rem
     at 800, in full --ink, with a rule under it — the name now reads as the
     top of a card rather than the first of nine lines. */
  #portal-body .bg-table .bg-supplier {
    font-size: 1.15rem;
    font-weight: 800;
    line-height: 1.25;
    letter-spacing: -.01em;
    color: var(--ink);
    padding: 0 0 10px;
    margin-bottom: 10px;
    border-bottom: 1px solid var(--line-soft);
    min-width: 0;
  }

  /* The drag grip is a planner's control and has no business on their card. */
  #portal-body .bg-table .bg-supplier .row-grip,
  #portal-body .bg-table .bg-supplier [draggable="true"] { display: none; }

  /* Estimate and Difference USED to be hidden here as "her bookkeeping" — a
     session's own call on 17 September. Reversed 27 September by Gee: "I want
     the clients to be able to see everything that I see." They show on a
     phone now, labelled like every other card line. */

  /* Everything else says what it is, because the header row is gone. */
  #portal-body .bg-table td[class*="col-"]::before,
  #portal-body .bg-table .bg-date::before,
  #portal-body .bg-table .bg-bank::before {
    display: inline-block;
    min-width: 108px;
    font-size: .66rem;
    font-weight: 800;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--ink-faint);
  }
  #portal-body .bg-table .col-estimate::before   { content: "Estimate"; }
  #portal-body .bg-table .col-difference::before { content: "Difference"; }
  #portal-body .bg-table .col-actual::before  { content: "Agreed"; }
  #portal-body .bg-table .col-paid::before    { content: "Paid"; }
  #portal-body .bg-table .col-balance::before { content: "Still to pay"; }
  #portal-body .bg-table .bg-date::before     { content: "Due"; }

  /* The one number they are actually looking for. */
  #portal-body .bg-table .col-balance { font-size: 1.05rem; font-weight: 800; }

  /* How to pay, together and last — it is only wanted at the moment of paying. */
  #portal-body .bg-table .bg-bank-first {
    margin-top: 9px;
    padding-top: 9px;
    border-top: 1px solid var(--line-soft);
  }
  /* 🛑 EACH BANK LINE SAYS WHICH NUMBER IT IS. Without this the card reads
     "EFT / Robbie Polack / 733 058 / 62 4113 / 0430 693 636" — five values and
     no way to tell the BSB from the account number while standing in a banking
     app. That is the one moment this screen exists for.

     ⚠️ BY POSITION, BECAUSE EVERY BANK CELL CARRIES THE SAME CLASS. The order
     is Method · Account name · BSB · Account no. · PayID, fixed in budget.js.
     **If a column is ever inserted before these, these labels shift with it** —
     there is no class to key on, and adding one would mean touching her own
     table. Checked against the live render on 18 September. */
  #portal-body .bg-table tbody tr > td:nth-child(8)::before  { content: "How to pay"; }
  #portal-body .bg-table tbody tr > td:nth-child(9)::before  { content: "Account name"; }
  #portal-body .bg-table tbody tr > td:nth-child(10)::before { content: "BSB"; }
  #portal-body .bg-table tbody tr > td:nth-child(11)::before { content: "Account no."; }
  #portal-body .bg-table tbody tr > td:nth-child(12)::before { content: "PayID"; }

  /* Status reads as a state, not a bare word at the bottom of a card. */
  #portal-body .bg-table .bg-status {
    margin-top: 10px;
    padding-top: 9px;
    border-top: 1px solid var(--line-soft);
    font-weight: 700;
  }
  #portal-body .bg-table .bg-status::before {
    content: "Status";
    display: inline-block;
    min-width: 108px;
    font-size: .66rem;
    font-weight: 800;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--ink-faint);
  }

  /* The category bands and totals stay as bands — they are not lines. */
  #portal-body .bg-table tr.bg-cat,
  #portal-body .bg-table tr.bg-subtotal { margin: 14px 0 8px; }

  /* A conversation under a line keeps the full width it already asked for. */
  #portal-body .bg-table tr.bg-thread td { padding: 0 0 10px; }
}

/* The thread on a line, in the couple's own budget table. Full width under
   the row rather than a sixth column: this is a conversation, and squeezing
   it into a money table would make both unreadable. */
.bg-table tr.bg-thread td {
  padding: 0 12px 12px;
  border-bottom: 1px solid var(--line);
  background: none;
}

/* Same fix as the supplier card: the shared compose row is box + paperclip +
   Send on one line, which collapses the box inside a narrow column. */
.bg-table .portal-thread .comment-compose {
  display: grid;
  grid-template-columns: 1fr auto auto;
  grid-template-areas: 'box box box' '. clip send';
  gap: 8px;
  margin-top: 8px;
  max-width: 560px;
}

.bg-table .portal-thread .comment-compose textarea {
  grid-area: box;
  width: 100%;
  min-height: 58px;
  resize: vertical;
}

.bg-table .portal-thread .comment-compose [data-bg-attach] { grid-area: clip; }
.bg-table .portal-thread .comment-compose [data-bg-send]   { grid-area: send; }


/* ---------- suppliers: where each one is up to ------------------------------

   Six steps from your email SOP. One click marks a step done, a second marks
   it not needed for this supplier — the Intro Zoom rarely applies to a cake
   maker, and a step that will never happen shouldn't sit there looking late. */

.stepper { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; }

.step {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 3px 5px;
  border: 0;
  border-radius: 6px;
  background: none;
  cursor: pointer;
}

.step:hover { background: #f5f0f2; }

.step-dot {
  width: 12px;
  height: 12px;
  border: 2px solid var(--ink-faint);
  border-radius: 50%;
  background: #fff;
}

.step-name {
  color: var(--ink-faint);
  font-size: .58rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.step.is-done .step-dot  { border-color: var(--ok); background: var(--ok); }
.step.is-done .step-name { color: var(--ok); }

/* Not needed: hollow, faint, struck through. Unmistakably different from a
   step that simply hasn't happened yet. */
.step.is-skipped .step-dot  { border-style: dotted; border-color: var(--line); }
.step.is-skipped .step-name { color: var(--line); text-decoration: line-through; }

.step-count {
  margin-left: 6px;
  color: var(--ink-soft);
  font-size: .7rem;
  font-weight: 700;
}

.sup-owing { white-space: nowrap; }
.owe { font-weight: 700; font-variant-numeric: tabular-nums; }
.owe.is-late { color: var(--danger); }
.owe-clear { color: var(--ok); font-size: .78rem; font-weight: 600; }

/* Six steps on one line: wrapped onto two they read as two half-finished
   rows rather than one sequence. */
.sup-stages { min-width: 320px; }
.stepper { flex-wrap: nowrap; }
.step { flex: 0 0 auto; padding: 3px 4px; }
.step-name { font-size: .55rem; white-space: nowrap; }

/* The progress column hides the same way the bank details do — one column of
   six dots is the difference between a working list and a contact sheet. */
.sup-table.hide-progress .sup-progress-col,
.sup-table.hide-progress .sup-stages,
/* Coverage rides with it. It's the same kind of information — where a
   supplier is up to — and together the two columns are 500px, which is the
   difference between the contact details being on screen and being off it. */
.sup-table.hide-progress .sup-cover-col,
.sup-table.hide-progress .sup-cover { display: none; }

/* The Paid step is read from the budget, so it isn't a button — no hover, no
   pointer, and a ring to say "this one reports rather than records". */
.step.is-derived { cursor: default; }
.step.is-derived:hover { background: none; }
.step.is-derived .step-dot { box-shadow: 0 0 0 2px #fff, 0 0 0 3px var(--line); }


/* ---------- budget: status stays put ---------------------------------------

   With bank details showing, the table is wider than the screen and Status —
   the one column that tells you whether a line needs chasing — was the first
   thing to fall off the right edge. It now pins there while everything else
   scrolls underneath. */

.bg-table th.bg-status,
.bg-table td.bg-status {
  position: sticky;
  right: 0;
  z-index: 2;
  background: var(--surface);
  border-left: 1px solid var(--line);
  box-shadow: -6px 0 8px -6px rgba(0, 0, 0, .12);
}

/* The header and the subtotal band have their own grounds; a transparent
   sticky cell would let the rows scroll through them. */
.bg-table thead th.bg-status { background: var(--surface); }
.bg-table tbody tr[data-line]:hover td.bg-status { background: #faf7f9; }

/* 🛑 ON HER BUDGET STATUS IS FIRST AFTER SUPPLIER NOW (22 Oct, CHECK 272), so
   it does not pin to the right. The couple's copy still uses the rule above.
   Same specificity as it, and later, so this wins. */
.bg-table th.bg-status-lead,
.bg-table td.bg-status-lead {
  position: static;
  border-left: 0;
  box-shadow: none;
  white-space: nowrap;
  padding-left: 10px;
  padding-right: 10px;
}
/* 🛑 A DOT AND THE WORD, NOT A PILL (22 Oct, her "Build C"). Filled chips beside
   Ready to pay read as a double-up: her presses are the pills, the facts are
   dots. Colours from the app's own bright set, none of the four the press chips
   use (royal blue, magenta, red, coral): Paid mint, Partial sunshine, Unpaid
   brand pink, Overdue bright red. She turned down purple, amber and lime. */
.bg-table td.bg-status-lead .status {
  border: 0;
  background: none;
  padding: 0;
  font-size: .72rem;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.bg-table td.bg-status-lead .status::before {
  content: '';
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  margin-right: 6px;
  vertical-align: -1px;
  background: currentColor;
}
.bg-table td.bg-status-lead .status-paid    { color: #00a08a; }
.bg-table td.bg-status-lead .status-partial { color: #c99700; }
.bg-table td.bg-status-lead .status-unpaid  { color: #e8197a; }
.bg-table td.bg-status-lead .status-overdue { color: #d50000; }
@media print {
  /* print.css outlines every .status in grey; a dot needs none. */
  .bg-table td.bg-status-lead .status { border: 0 !important; padding: 0 !important; }
  .bg-table td.bg-status-lead .status::before { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}


/* ---------- suppliers: a done step should look done -------------------------

   A hollow dot turning into a small filled one wasn't emphatic enough — the
   point of the row is telling at a glance what has been actioned. A completed
   step now sits on a green chip and keeps it. */

.step.is-done {
  background: var(--pay-done-soft);
  border: 1px solid var(--pay-done);
  padding: 2px 6px;
}

.step.is-done:hover { background: #d6f7e2; }

.step.is-done .step-dot {
  border-color: var(--pay-done);
  background: var(--pay-done);
  position: relative;
}

/* A tick inside the dot, drawn rather than an image so it stays crisp. */
.step.is-done .step-dot::after {
  content: '';
  position: absolute;
  left: 3px;
  top: 0px;
  width: 3px;
  height: 6px;
  border: solid #fff;
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
}

.step.is-done .step-name { color: var(--pay-done); font-weight: 700; }

/* To-do stays deliberately quiet, so done reads as the signal. */
.step.is-todo .step-dot { border-color: #cfcbcd; }


/* ---------- suppliers: Certificate of Currency (142, 7 Oct) -----------------

   Beside the seven steps, after a thin line, never one of them — the x/7 count
   does not include it. GRAPE, Gee's pick: green already means "done" on the
   dots beside it. Faint until a venue needs it. */

.coc-sep {
  align-self: stretch;
  width: 1px;
  margin: 0 4px 0 6px;
  background: var(--line);
}

.step.coc-step { position: relative; }
.step.is-coc-off .step-dot  { border-style: dotted; border-color: #cfcbcd; }
.step.is-coc-off .step-name { color: #b9b2b6; }

.step.is-coc-need {
  border: 1px solid var(--c-indigo);
  background: #fff;
  padding: 2px 6px;
}
.step.is-coc-need .step-dot  { border-color: var(--c-indigo); }
.step.is-coc-need .step-name { color: var(--c-indigo); font-weight: 700; }

.step.is-coc-got {
  border: 1px solid var(--c-indigo);
  background: var(--c-indigo-tint);
  padding: 2px 6px;
}
.step.is-coc-got:hover { background: #ddd0ff; }
.step.is-coc-got .step-dot {
  border-color: var(--v-grape);
  background: var(--v-grape);
  position: relative;
}
/* The same drawn tick as a done step. */
.step.is-coc-got .step-dot::after {
  content: '';
  position: absolute;
  left: 3px;
  top: 0px;
  width: 3px;
  height: 6px;
  border: solid #fff;
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
}
.step.is-coc-got .step-name { color: var(--c-indigo); font-weight: 700; }

.coc-clip {
  flex: 0 0 auto;
  margin-left: 2px;
  padding: 3px 5px;
  border: 1px dashed var(--c-indigo);
  border-radius: 6px;
  background: #fff;
  color: var(--c-indigo);
  font-size: .72rem;
  font-weight: 700;
  cursor: pointer;
}
.coc-clip.is-filed { border-style: solid; background: var(--c-indigo-tint); }
.coc-clip:hover { background: var(--c-indigo-tint); }

/* The venue button in the toolbar. */
#sup-coc-venue { display: inline-flex; flex-wrap: wrap; gap: 6px; }
#sup-coc-venue[hidden] { display: none; }
.coc-venue-btn {
  padding: 4px 12px;
  border: 1.5px solid var(--c-indigo);
  border-radius: 999px;
  background: var(--c-indigo-tint);
  color: var(--c-indigo);
  font: inherit;
  font-size: .8rem;
  font-weight: 700;
  cursor: pointer;
}
.coc-venue-btn:hover { background: #ddd0ff; }


/* ---------- suppliers: contact columns --------------------------------------

   A phone number broken across two lines reads as two numbers. Same for an
   email address. They get the room to stay on one line. */

.sup-table td a[href^="tel:"],
.sup-table td a[href^="mailto:"] { white-space: nowrap; }

/* These were nth-child(5) and (6) — Owing and Phone by position. Adding a
   "Where they're going" column after Coverage silently moved both rules onto
   the wrong columns. Positional selectors on a table break the moment anyone
   inserts one, and nothing tells you: the layout just quietly goes wrong.
   Targeted by what they ARE now, so the next column costs nothing. */
.sup-table .sup-owing { white-space: nowrap; min-width: 140px; }
.sup-table .sup-phone { white-space: nowrap; }

/* Where each supplier is going. "not set" is the whole point of the column —
   it has to be findable at a glance down seventeen rows, without shouting
   loudly enough to read as an error. Nothing is wrong; something is simply
   not filled in yet, which is amber's job here as it is on Coverage. */
.sup-where { white-space: nowrap; }
.sup-where-set  { color: var(--ink); }
.sup-where-none { color: var(--pay-part); font-style: italic; font-size: .86rem; }

/* Supplier and role can wrap — they're words, not identifiers. */
.sup-table .sup-role,
.sup-table .sup-name { white-space: normal; min-width: 150px; }


/* ==========================================================================
   Client portal

   The couple's half of the platform. Softer than the planner side — this is
   somebody looking forward to their wedding, not somebody at work — but it
   uses the same tokens so the two never drift apart.
   ========================================================================== */

.portal-topbar { border-bottom: 1px solid var(--line); }

.portal-head {
  padding: 34px 0 22px;
  text-align: center;
}

.portal-head h1 {
  margin: 0;
  font-size: 2rem;
  font-weight: 600;
  letter-spacing: -.01em;
}

.portal-date {
  margin: 6px 0 0;
  color: var(--pink);
  font-size: .95rem;
  letter-spacing: .04em;
}

/* Something on the couple's page did not load.
   Deliberately NOT the danger red: nothing is broken for them and nothing is
   lost — a part of the page is incomplete and refreshing usually fixes it. A
   red alarm on a wedding portal reads as "your wedding has a problem", which
   is both untrue and the last thing a couple needs to see. */
.portal-warn {
  max-width: 640px;
  margin: 14px auto 0;
  padding: 9px 14px;
  border: 1px solid var(--pink);
  border-radius: var(--radius-sm);
  background: var(--surface-warm);
  color: var(--ink);
  font-size: .86rem;
  line-height: 1.5;
  text-align: center;
}

/* The couple's budget toolbar — search, Just suppliers, Export. Their side of
   the three controls Gee has on hers. Right-aligned so it sits over the table
   rather than competing with the totals above it. */
.portal-bg-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 14px 0 6px;
}

.portal-bg-search {
  flex: 1 1 220px;
  max-width: 340px;
  padding: 7px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font: inherit;
  font-size: .88rem;
}

.portal-bg-tools .btn { flex: 0 0 auto; }

/* Pressed reads as pressed, or a toggle is a button that does nothing. */
.portal-bg-tools .btn[aria-pressed="true"] {
  background: var(--pink);
  border-color: var(--pink);
  color: #fff;
}

/* None of it belongs on paper — a search box and an Export button printed on
   a budget are two controls nobody can press. */
@media print { .portal-bg-tools { display: none !important; } }

#portal-view .subnav { justify-content: center; }

.portal-hint {
  margin: 0 0 18px;
  padding: 11px 14px;
  border-left: 3px solid var(--teal);
  background: var(--teal-tint);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: .85rem;
  line-height: 1.5;
}


/* ---------- overview cards ------------------------------------------------ */

.ov-countdown {
  background: var(--pink-tint);
  border-color: transparent;
  text-align: center;
}

.ov-countdown .ov-big { color: var(--pink-deep); font-size: 3rem; }

.ov-planner-name {
  margin: 4px 0 0;
  font-size: 1.05rem;
  font-weight: 600;
}


/* ---------- read-only run sheet ------------------------------------------- */

/* No gutter, no hover affordances — nothing here suggests it can be dragged
   or edited, because it can't be. */
.tl-readonly .tl-row { cursor: default; }
.tl-readonly .tl-row:hover { background: none; }
.tl-readonly .tl-notes { margin-top: 3px; }


/* ---------- suppliers ----------------------------------------------------- */

.portal-suppliers {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 14px;
}

.portal-supplier {
  padding: 15px 17px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}

.portal-supplier-role {
  color: var(--ink-faint);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
}

.portal-supplier-name {
  margin: 3px 0 7px;
  font-size: 1rem;
  font-weight: 600;
}

.portal-supplier-bits {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  font-size: .84rem;
}

/* Contact details must never break mid-number. */
.portal-supplier-bits a { white-space: nowrap; }


/* ---------- budget -------------------------------------------------------- */

.portal-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 14px;
  margin-bottom: 20px;
}

.portal-summary > div {
  padding: 13px 15px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.portal-summary strong {
  display: block;
  margin-top: 2px;
  font-size: 1.2rem;
}


/* ---------- comments on a task -------------------------------------------- */

.portal-thread { margin-top: 8px; }

.portal-thread > summary {
  color: var(--teal-deep);
  font-size: .8rem;
  cursor: pointer;
  list-style: none;
}

.portal-thread > summary::-webkit-details-marker { display: none; }
.portal-thread > summary::before { content: '💬 '; }


/* ---------- tagging someone in --------------------------------------------

   A tag has to be visible inside a sentence without shouting over it. Blue,
   matching the names in the notification emails, so "@Liam" means the same
   thing on screen as it does in the inbox.

   Deliberately not pink: pink is the brand colour and already carries "this
   is Gee's", which is the wrong thing for a name that is usually not hers.  */

.mention {
  padding: 1px 5px;
  border-radius: 5px;
  background: #daf0ff;
  color: #0067a3;
  font-weight: 600;
  white-space: nowrap;
}

/* One floating list shared by every comment box on the page — eighty threads
   on a long checklist would otherwise be eighty dropdowns, all listening. */
.mention-menu {
  position: absolute;
  z-index: 3000;
  overflow: hidden;
  min-width: 200px;
  max-width: 300px;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 10px 28px rgba(30, 20, 26, .16);
}

.mention-option {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding: 7px 10px;
  border: 0;
  border-radius: 7px;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.mention-option:hover,
.mention-option.is-on { background: #daf0ff; }

.mention-name { color: var(--ink); font-size: .87rem; font-weight: 600; }

.mention-role {
  color: var(--ink-faint);
  font-size: .7rem;
  letter-spacing: .04em;
  text-transform: uppercase;
}


/* ---------- the couple writing back ----------------------------------------

   Three controls under a task now, where there was one. They share a look and
   a size on purpose: comment, answer and reminder are three ways of replying
   to the same task, and making one of them louder would be a claim about
   which one matters, which depends entirely on the task.

   The answer is the exception once it has something in it — a filled-in
   entrance list is content, not a control, so it gets a tinted panel and a
   left rule the way a comment does.                                          */

/* ACROSS THE PAGE, NOT DOWN IT.

   Gee, 30 Aug: "for this to do list in the client view can we go across the
   page instead of down for the 3 things remind me ask etc."

   Three stacked <details> cost every task six lines of height before one was
   even opened, so a list of eighty items was mostly its own controls. As a row
   they are three small chips on one line.

   An OPEN one still takes the full width. A comment thread or an entrance list
   in a third of a column is unreadable, so the open panel claims the whole row
   and the two closed chips sit above it — which also means only one thing is
   ever wide, and it is the thing being used. */
.task-does {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 18px;
  margin-top: 8px;
}

.task-does > details { margin-top: 0; }
.task-does > details[open] { flex: 1 0 100%; margin-top: 4px; }

/* On a phone there is no room for three across; they stack again rather than
   wrapping into an awkward two-and-one. */
@media (max-width: 560px) {
  .task-does { flex-direction: column; align-items: stretch; gap: 6px; }
}

.portal-answer,
.portal-remind { margin-top: 8px; }

.portal-answer > summary,
.portal-remind > summary {
  color: var(--teal-deep);
  font-size: .8rem;
  cursor: pointer;
  list-style: none;
}

.portal-answer > summary::-webkit-details-marker,
.portal-remind > summary::-webkit-details-marker { display: none; }

.portal-answer > summary::before { content: '✍️ '; }
.portal-remind  > summary::before { content: '⏰ '; }

/* Open, it is the couple's own words — panelled like a comment so the eye
   reads it as content rather than as another empty form. */
.portal-answer[open] {
  padding: 10px 12px 12px;
  border-left: 3px solid var(--teal-deep);
  border-radius: 0 8px 8px 0;
  background: var(--teal-tint, #e7f6f7);
}

.answer-by {
  margin: 6px 0 0;
  color: var(--ink-faint);
  font-size: .74rem;
}

.answer-box {
  width: 100%;
  margin-top: 8px;
  min-height: 92px;
  resize: vertical;
  /* A song list is a list. Honour the line breaks they typed rather than
     reflowing nine songs into a paragraph. */
  white-space: pre-wrap;
}

.answer-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}

.answer-saved {
  color: var(--pay-done, #00733d);
  font-size: .76rem;
}

.remind-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}

/* A reminder that already exists, since migration 74 allows several. Reads as
   a set fact with a way to undo it, rather than as another empty field —
   otherwise three reminders look like three things still to fill in. */
.remind-row.is-set {
  gap: 8px;
  padding: 4px 0;
}

.remind-when {
  font-size: .82rem;
  font-weight: 600;
  color: var(--teal-deep);
}

.remind-when::before {
  content: '•';
  margin-right: 7px;
  color: var(--ink-faint);
}

/* Only appears once there is already one, so the empty box below the list
   reads as "add another" rather than as an unfilled duplicate. */
.remind-add-hint {
  font-size: .74rem;
  color: var(--ink-faint);
}

.remind-note {
  margin-top: 6px;
  color: var(--ink-faint);
  font-size: .74rem;
  line-height: 1.45;
}

/* Something the couple added themselves. A quiet tag, not a badge: it marks
   ownership so Gee knows who put it there, and it is not news. */
.task-ours-tag {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 8px;
  border: 1px solid var(--teal-deep);
  border-radius: 999px;
  color: var(--teal-deep);
  font-size: .68rem;
  letter-spacing: .06em;
  text-transform: uppercase;
}

.task-remove {
  margin-top: 8px;
  padding: 0;
  border: 0;
  background: none;
  font-size: .76rem;
  cursor: pointer;
}

.link-danger { color: var(--danger); }

/* Adding your own item — closed by default. This list is mostly Gee's, and
   the couple's job on most days is to work down it, not add to it. */
.task-add { margin-bottom: 14px; }

.task-add > summary {
  color: var(--teal-deep);
  font-size: .85rem;
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}

.task-add > summary::-webkit-details-marker { display: none; }
.task-add > summary::before { content: '＋ '; }

.task-add-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.task-add-form #portal-task-title { flex: 1 1 220px; }

.task-add-when {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--ink-faint);
  font-size: .78rem;
}

/* Whose job it is.

   Gee, 15 September: the couple should be able to put things on each other's
   lists, on hers, and she on theirs. The chip below is the whole of it on the
   reading side - it LABELS a row and never hides one, because she was explicit
   that both partners see everything either of them can see.

   Four hues, chosen to be told apart at a glance rather than to mean anything
   borrowed: the two partners, the pair, and the planner. Deliberately NOT the
   run sheet's mint and emerald, which already carry "they said no" and "done
   and paid" on that screen.

   color-mix gives each chip its own tint without adding eight more tokens; the
   flat background underneath is what shows if it is ever unsupported. */
.task-for {
  --for: var(--ink-soft);
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  border: 1px solid var(--for);
  background: var(--surface, #fff);
  background: color-mix(in srgb, var(--for) 12%, transparent);
  color: var(--for);
  font-size: .7rem;
  font-weight: 700;
  white-space: nowrap;
}

.task-for.is-one     { --for: var(--c-plum); }
.task-for.is-two     { --for: var(--c-sky); }
.task-for.is-both    { --for: var(--c-sage); }
/* All three of us — 18 September. Grape, which the palette already calls "the
   couple" and which nothing else in this set uses, so the sixth chip is not a
   near-miss of one of the five beside it. */
.task-for.is-all     { --for: var(--c-indigo); }
.task-for.is-planner { --for: var(--c-butter); }
.task-for.is-other   { --for: var(--c-stone); }

/* The picker on the couple's add form.

   It takes a whole row of the flex form rather than sitting between the title
   and the date, because four buttons wrapping mid-row is how the Attach and
   Send controls went wrong on this same screen this morning. */
.task-add-for {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.task-add-for-label {
  color: var(--ink-faint);
  font-size: .78rem;
}

.pick-for-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.pick-for {
  padding: 5px 12px;
  border: 1.5px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--ink-soft);
  font: inherit;
  font-size: .8rem;
  font-weight: 700;
  cursor: pointer;
}

.pick-for:hover { border-color: var(--ink-faint); }

.pick-for.is-on {
  border-color: var(--c-plum);
  color: var(--c-plum);
  background: color-mix(in srgb, var(--c-plum) 12%, transparent);
}

.pick-for:focus-visible {
  outline: 2px solid var(--c-plum);
  outline-offset: 2px;
}

/* The compose box inside a supplier card.

   The shared .comment-compose is a flex row: box, paperclip, Send. That works
   on a full-width checklist task and looked wrong here — a supplier card is a
   third the width, so the two buttons ate it and left a stub of a textarea
   with its own scrollbar after four words. Here the box gets the whole width
   and the buttons drop underneath it, where there is room for them. */
/* ⚠️ THE BUTTONS SIT UNDER THE LEFT EDGE OF THE BOX, NOT THE RIGHT.
   Gee, 15 September: *"This should be directly under, not on the right-hand
   side, directly under the box on the left-hand side, which is easier when
   you're doing the work."*

   The empty column moved from the front of the row to the end — `'clip send .'`
   rather than `'. clip send'`. That is the whole change.

   ⚠️ AND IT ENDS A CONTRADICTION THAT WAS ALREADY IN THIS FILE. Further down,
   `.comment-compose { justify-content: flex-start }` carries the note "LEFT, to
   match Save. One place for the main action in a compose box." This grid was
   overriding that for every thread it wraps, so the same two buttons sat left
   in one place and right in another. */
.portal-thread .comment-compose {
  display: grid;
  grid-template-columns: auto auto 1fr;
  grid-template-areas: 'box box box' 'clip send .';
  gap: 8px;
  margin-top: 8px;
}

.portal-thread .comment-compose textarea {
  grid-area: box;
  width: 100%;
  min-height: 62px;
  resize: vertical;
}

/* 🛑 NAME EVERY BUTTON THAT CAN LAND IN THIS GRID, NOT JUST THE ONE YOU BUILT
   IT FOR.

   This grid was written for a supplier card, so only `[data-sup-attach]` and
   `[data-sup-send]` were given areas. But `.portal-thread` also wraps the
   CHECKLIST task's compose box, whose buttons are `[data-portal-attach]` and
   `[data-portal-comment-send]` — different attributes, no areas, so the
   browser auto-placed them into the first free cells: Attach into the wide
   empty `.` column and Send into `clip`.

   That is precisely what Gee saw on 15 September: *"I don't like the way your
   Attach and Send are on the screen. That doesn't make sense to me."* Attach
   stranded in the middle of the row, Send beside it and short of the edge.

   ⚠️ A GRID WITH NAMED AREAS SILENTLY AUTO-PLACES ANYTHING YOU DID NOT NAME.
   It never errors and it never looks broken in the case you built it for. If
   you add a control to a compose box, give it an area here in the same edit. */
.portal-thread .comment-compose [data-sup-attach],
.portal-thread .comment-compose [data-portal-attach],
.portal-thread .comment-compose [data-bg-attach]   { grid-area: clip; }

.portal-thread .comment-compose [data-sup-send],
.portal-thread .comment-compose [data-portal-comment-send],
.portal-thread .comment-compose [data-bg-send]     { grid-area: send; }

.portal-thread .comment-list:empty { display: none; }


/* ---------- the couple's own notes ---------------------------------------- */

.portal-notes {
  display: grid;
  gap: 9px;
  margin-bottom: 20px;
}

.portal-note {
  display: grid;
  grid-template-columns: 1fr 1fr auto auto;
  align-items: center;
  gap: 12px;
  padding: 12px 15px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.portal-note-label { font-weight: 600; font-size: .9rem; }

.portal-note-value {
  font-family: var(--mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
  font-size: .9rem;
  word-break: break-all;
}

/* Masked rather than encrypted. It keeps a code off the screen in a cafe;
   it is not security, which is why the copy says not to keep logins here. */
.portal-note-value.is-secret {
  color: transparent;
  background: repeating-linear-gradient(
    90deg, var(--ink-faint) 0 7px, transparent 7px 11px);
  background-size: 100% 3px;
  background-position: 0 center;
  background-repeat: no-repeat;
  user-select: none;
}

.portal-note-add {
  display: grid;
  grid-template-columns: 1fr 1fr auto auto;
  align-items: center;
  gap: 10px;
  padding: 15px;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
}

@media (max-width: 640px) {
  .portal-note,
  .portal-note-add { grid-template-columns: 1fr; }
  .portal-head h1 { font-size: 1.5rem; }
  #portal-view .subnav { gap: 16px; overflow-x: auto; }
}


/* ---------- who can reach a project (planner side) ------------------------ */

.portal-access { display: grid; gap: 9px; }

.access-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: 14px;
  padding: 12px 15px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.access-name  { font-weight: 600; font-size: .92rem; }
.access-email { color: var(--ink-soft); font-size: .82rem; }

/* ✎ Add / Edit name on an invited person (1 Oct). A real button, always
   visible — never a hover-reveal (memory a-hover-reveal-she-has-to-be-told-about). */
.access-name-line { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.access-name.is-unnamed { color: var(--ink-soft); font-style: italic; }
.access-name-btn {
  border: 1.5px solid var(--pink);
  color: var(--pink);
  background: #fff;
  border-radius: 999px;
  padding: 2px 11px;
  font: inherit;
  font-size: .8rem;
  font-weight: 700;
  cursor: pointer;
  min-height: 28px;
}
.access-name-btn:hover,
.access-name-btn:focus-visible { background: var(--pink); color: #fff; outline: none; }
.access-name-edit { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.access-name-edit input { min-width: 180px; }

.access-status {
  display: flex;
  align-items: center;
  gap: 9px;
}

@media (max-width: 640px) {
  .access-row { grid-template-columns: 1fr; }
}

/* ---------- their cover photo (planner side) ------------------------------
   Borrows `.access-row`'s geometry deliberately: it sits directly under that
   list on the same tab, and its own look would read as a second feature
   rather than one more thing about this couple's portal. Same border, same
   radius, same padding, same single column on a phone.
   ⚠️ The name of the thing is bold — `.cover-title` is 700, and never
   lighter than the line under it. Checks 17 and 22. */
.cover-card {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 14px;
  margin-top: 14px;
  padding: 12px 15px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

/* A fixed box whether or not there is a photo, so the card does not jump
   height the moment one is chosen. The dashed edge is the empty state. */
.cover-shot {
  width: 104px;
  height: 62px;
  border-radius: calc(var(--radius) - 3px);
  border: 1px dashed var(--line);
  /* ⚠️ --surface-soft, a token that EXISTS. `--paper-2` was written here
     first and is defined nowhere — the same shape as `--muted`, which was
     used in 23 places and defined in none, so all 23 declarations were
     thrown away silently. A fallback in the var() would have hidden it. */
  background-color: var(--surface-soft);
  background-size: cover;
  background-position: center;
}

.cover-shot.has-shot { border-style: solid; }

.cover-title { font-weight: 700; font-size: .92rem; }
.cover-state { color: var(--ink-soft); font-size: .82rem; }

.cover-acts { display: flex; align-items: center; gap: 8px; }

@media (max-width: 640px) {
  .cover-card { grid-template-columns: 1fr; }
  .cover-shot { width: 100%; height: 132px; }
}

/* The global input rule stretches these to the full row; the invite form is a
   single line of three controls, so pin them back. */
#wtab-portal .toolbar { flex-wrap: wrap; align-items: center; }
#wtab-portal .toolbar input[type="email"] { flex: 0 1 280px; width: auto; }
#wtab-portal .toolbar input[type="text"]  { flex: 0 1 200px; width: auto; }


/* ---------- settings ------------------------------------------------------ */

.set-block {
  max-width: 760px;
  margin-bottom: 30px;
  padding-bottom: 26px;
  border-bottom: 1px solid var(--line);
}

.set-block h2 { margin: 0 0 4px; font-size: 1.05rem; }
.set-block .ov-sub { margin: 0 0 16px; max-width: 62ch; line-height: 1.5; }

.set-grid { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }

.set-logo-preview img {
  max-width: 120px;
  max-height: 80px;
  margin-top: 12px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  object-fit: contain;
}


/* ---------- the planner's mark in the portal ------------------------------ */

#portal-brand { display: flex; align-items: center; gap: 10px; }

.portal-logo {
  width: auto;
  max-width: 110px;
  height: 24px;
  object-fit: contain;
}

/* ⚠️ THE NAME IN TYPE IS A FALLBACK, NOT A LABEL — and it only appears when
   the logo has taken itself off the page.

   The original note here was right: "the logo already says the name, repeating
   it in type beside the mark reads as a placeholder nobody removed." So the
   wordmark ships beside the logo for the case the logo cannot load, and hides
   itself the moment the logo is actually there.

   `[hidden]` is what logoImgHtml's onerror sets, so this is a pure CSS
   consequence of the image failing — no second handler, nothing to keep in
   step. */
#portal-brand .portal-logo:not([hidden]) + .wordmark.is-fallback { display: none; }

.ov-planner-logo {
  display: block;
  width: 44px;
  height: 44px;
  margin-bottom: 8px;
  border-radius: 8px;
  object-fit: contain;
}

/* --------------------------------------------------------------------------
   The comment you followed a link to.

   Emails now deep-link to one exact comment rather than to the app's front
   door. Landing mid-thread with no marker leaves you re-reading four notes
   working out which one the email meant, so the target gets a sunflower wash
   and a bar down its edge for six seconds, then fades back to normal. It has
   to fade: a highlight still sitting there tomorrow is a puzzle, not a hint.
   -------------------------------------------------------------------------- */
.comment.is-linked {
  background: #ffe3e4;
  border-left: 3px solid #ffb74d;
  padding-left: 9px;
  border-radius: 4px;
  animation: linked-settle 6s ease-out forwards;
}

@keyframes linked-settle {
  0%, 70% { background: #ffe3e4; }
  100%    { background: transparent; border-left-color: transparent; }
}

@media (prefers-reduced-motion: reduce) {
  .comment.is-linked { animation: none; }
}

/* --------------------------------------------------------------------------
   The same marker, on anything that isn't a comment.

   Added 11 September, when deep links started working on the couple's side.
   A link to a task, a run sheet line or a supplier now lands them ON that row
   — and a row that looks exactly like the fourteen around it is no better than
   the front page they used to get.

   ⚠️ box-shadow, NOT the border-left + padding-left the comment rule uses.
   These are grid and flex rows with their own padding; adding a border to one
   shifts its content three pixels sideways relative to every other row on the
   page, which reads as a rendering fault rather than a highlight.

   ⚠️ AND NO `forwards`. The class is removed by markIt() at exactly 6s; a
   filled-forwards animation ending on `background: transparent` would race it
   and could strip a row's own background instead of the wash.
   -------------------------------------------------------------------------- */
.is-linked:not(.comment) {
  border-radius: 5px;
  box-shadow: inset 3px 0 0 #ffb74d;
  animation: linked-settle-row 6s ease-out;
}

@keyframes linked-settle-row {
  0%, 70% { background: #ffe3e4; box-shadow: inset 3px 0 0 #ffb74d; }
  100%    { background: transparent; box-shadow: inset 3px 0 0 transparent; }
}

@media (prefers-reduced-motion: reduce) {
  .is-linked:not(.comment) { animation: none; background: #ffe3e4; }
}

/* A FIELD a link landed in, 17 September.
   The wash above is drawn for a ROW — a full-width band with a bar down its
   left edge. On a form box it is nearly invisible: the box has its own white
   background and its own border, and the animation's tint sits behind both.
   An outline reads at a glance and does not move the field a pixel, which
   matters here because these sit in a four-across grid.
   The cursor is already in it, so this only has to say WHICH. */
input.is-linked,
select.is-linked,
textarea.is-linked {
  outline: 2px solid var(--c-coral);
  outline-offset: 1px;
  border-color: var(--c-coral);
}

/* ASKED AND NEVER ANSWERED — the panel on a supplier's card.
   Quiet by default: this is a list to read, not a list to act on, and every
   row of it is a sentence somebody else wrote. */
.s-open-row {
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  margin-top: 6px;
}

.s-open-q {
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink);
}

.s-open-from {
  margin-top: 3px;
  font-size: 11px;
  color: var(--ink-faint);
}

/* An amount that only applies sometimes is money that may not be owed at all,
   so it carries the money colour rather than the quiet one. */
.s-open-if .s-open-q { color: var(--c-sage); }

/* The printed checklist lives in the page but never shows on screen.
   Building it here rather than at print time means it is already in the DOM
   when Ctrl+P opens the dialog — a block rendered inside beforeprint arrives
   too late in Safari and prints blank. */
#task-print { display: none; }

/* ---------------------------------------------------------------------------
   Back to the top, and down to the bottom.

   Gee, 30 Aug: "maybe we need a top button and a bottom button, a back button
   and an arrow to take you to the top."

   Bottom right, above the toasts, and only on screen once you are a screen and
   a half down. Small: this is a way back, not a feature.
   --------------------------------------------------------------------------- */

.page-jump {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 60;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.jump-btn {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: #fff;
  color: var(--ink);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(36, 29, 33, .16);
  transition: background .12s, border-color .12s, transform .12s;
}

.jump-btn:hover {
  background: var(--tint, #faf7f9);
  border-color: var(--ink-faint);
  transform: translateY(-1px);
}

.jump-btn:focus-visible { outline: 2px solid var(--v-pink, #e8197a); outline-offset: 2px; }

/* Never on paper, and out of the way on a phone where the keyboard and the
   browser chrome are already fighting for that corner. */
@media print { .page-jump { display: none !important; } }
@media (max-width: 560px) { .page-jump { right: 10px; bottom: 76px; } }

/* 🔒 THE RUN SHEET LOCK (19 Oct, runsheetlock.js, CHECK 205). Sky blue, which
   she chose over raspberry and tangerine on 15 Oct ("that's so much better").
   White with a sky edge when open; solid sky when locked. The floating copy
   sits above the ↑ ↓ (.page-jump, 18px + two buttons). */
.tl-lock-btn {
  font: inherit;
  font-weight: 700;
  font-size: 14px;
  line-height: 1.2;
  border-radius: 999px;
  padding: 6px 14px;
  border: 2px solid var(--v-sky, #0091ea);
  background: #fff;
  color: #0077c2;
  cursor: pointer;
  white-space: nowrap;
}
.tl-lock-btn.is-on { background: var(--v-sky, #0091ea); color: #fff; }
.tl-lock-float {
  position: fixed;
  right: 18px;
  bottom: 118px;
  z-index: 61;
  padding: 8px 14px;
  box-shadow: 0 2px 8px rgba(0, 145, 234, .35);
}
@media (max-width: 560px) { .tl-lock-float { right: 10px; bottom: 176px; } }
@media print { .tl-lock-btn { display: none !important; } }

/* Locked: the controls FADE and stay exactly where they are — her choice, so
   nothing on the screen jumps. The gutter is normally invisible until hover;
   locked, hover brings it only to faint. Pressing is stopped in JS, not here. */
#wtab-timeline.tl-locked .tl-gutter,
#wtab-timeline.tl-locked .tl-row:hover .tl-gutter,
#wtab-timeline.tl-locked .tl-gutter:focus-within { opacity: .25 !important; }
#wtab-timeline.tl-locked :is(.tl-pick, [data-clear-note], [data-add-who], [data-add-notes],
  #add-timeline-event, #paste-timeline, #tl-replace-open, #apply-timeline-template,
  #tl-select-all, #tl-edit-details, #tl-add-supplier-table) { opacity: .25 !important; cursor: default; }
#wtab-timeline.tl-locked :is([data-edit-title], [data-edit-time], [data-door-pick],
  [data-set-place], [data-row]) { cursor: default; }

/* An Overview card that opens a tab. The whole card is the target, so it needs
   to look like one — but quietly, because the three cards sit side by side and
   a loud hover on each would make the row twitch. */
.ov-card.is-jump { cursor: pointer; transition: border-color .12s, box-shadow .12s; }

.ov-card.is-jump:hover {
  border-color: var(--v-pink, #e8197a);
  box-shadow: 0 2px 14px rgba(36, 29, 33, .09);
}

.ov-card.is-jump:focus-visible {
  outline: 2px solid var(--v-pink, #e8197a);
  outline-offset: 2px;
}

/* The controls inside keep their own jobs and their own cursor. */
.ov-card.is-jump a,
.ov-card.is-jump button { cursor: pointer; }

/* ---------------------------------------------------------------------------
   The couple's list, in the couple's phases.

   Gee, 30 Aug: "Why is this not in the order of the phase 1, phase 2, phase 3?"
   It had no sections at all — eighty-one rows sorted by date. These are her
   sections, in her order, with the two facts a couple actually wants from a
   heading: how many are left, and how many are late.
   --------------------------------------------------------------------------- */

.portal-phase { margin-top: 26px; }
.portal-phase:first-of-type { margin-top: 16px; }

.portal-phase-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin: 0 0 6px;
  padding-bottom: 7px;
  border-bottom: 2px solid var(--line, #ece5e8);
}

.portal-phase-name {
  font-size: .82rem;
  font-weight: 800;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--teal-deep, #007c85);
}

.portal-phase-count {
  font-size: .78rem;
  color: var(--ink-faint, #a0949a);
  font-weight: 600;
}

/* Only when there is something late. A permanent zero on every heading is
   eighty-one reassurances nobody reads. */
.portal-phase-late {
  font-size: .68rem;
  font-weight: 800;
  padding: 1px 8px;
  border-radius: 20px;
  background: #ffe0e3;
  color: #b3001b;
}

/* A reply the couple has not necessarily seen. The thread opens itself when
   the last word is Gee's and folds away when it is theirs, so this chip marks
   the ones that are open for a reason. */
/* How many comments have arrived since this browser last opened the thread.

   Sits beside "reply" and means something different: reply says the last word
   was Gee's and is waiting on them, this says there is something they have not
   read. A thread can be both.

   Amber rather than the brand pink, because pink is already "you need to
   answer this" two millimetres away and two pinks would read as one thing. */
.portal-thread > summary .thread-unread {
  display: inline-block;
  margin-left: 6px;
  padding: 0 7px;
  border-radius: 20px;
  background: var(--c-butter, #8a6100);
  color: #fff;
  font-size: .62rem;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
}

.portal-thread > summary .thread-new {
  display: inline-block;
  margin-left: 6px;
  padding: 0 7px;
  border-radius: 20px;
  background: var(--v-pink, #e8197a);
  color: #fff;
  font-size: .62rem;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  vertical-align: 1px;
}

/* Print, sitting at the end of the couple's tab strip. Quieter than a tab —
   it is not a place you can be, it is a thing you do to wherever you are. */
.portal-print {
  margin-left: 10px;
  padding: 6px 14px;
  border: 1px solid var(--line, #ece5e8);
  border-radius: 20px;
  background: transparent;
  color: var(--ink-soft, #6c6067);
  font: inherit;
  font-size: .84rem;
  font-weight: 600;
  cursor: pointer;
}

.portal-print:hover {
  border-color: var(--v-pink, #e8197a);
  color: var(--v-pink, #e8197a);
}

@media print { .portal-print { display: none !important; } }

/* ---------------------------------------------------------------------------
   The couple's due dates, in a column on the right.

   Gee, 30 Aug: "Where are the due dates, though? That's not good. That should
   be on the right-hand side. You can't tell that it's a due date."

   It was a pill under the title, sitting beside the notes, so a date read as
   part of the sentence above it. Here it is a column you scan down — and it
   always carries the word Due or Overdue, because a bare date next to a task
   is ambiguous and this is the audience least likely to guess right.
   --------------------------------------------------------------------------- */

/* ⚠️ IT IS ON THE LEFT NOW, AND THIS REVERSES 30 AUGUST ON PURPOSE.
   The note directly above is her 30 August instruction and it was right at the
   time — the date had been a pill under the title, unreadable as a deadline.
   Gee, 15 September, looking at the same list again: *"The due date, I feel
   like, should be on the left-hand side."*

   Nothing about the treatment changed — still its own column, still carrying
   the word Due or Overdue, still tabular figures so the dates line up. Only
   the side moved, by `order`, so the DOM keeps title-then-date and a screen
   reader still hears the task before its deadline.

   🛑 THE ROW ALREADY HAS AN ORDER SCALE. DO NOT USE A NEGATIVE HERE.
   Asked the browser rather than read the file: `.task-check` computes to
   `order: 1` and `.task-main` to `order: 6`, set elsewhere in this stylesheet.
   The first attempt used `order: -1`, which jumped BOTH of them — the date
   landed at the far left of the row, in front of the tick, and dragged the
   reminder button along with it because that button's default 0 also beat
   check's 1. Sit inside the existing scale instead: after the tick, before the
   title.

   ⚠️ AND THE REMINDER BUTTON NEEDS A NUMBER TOO. Left at the default 0 it
   sorts ahead of `.task-main`'s 6 and lands mid-row. Anything added to this
   row from now on needs an order, because 0 is not "leave me where I am". */
/* ---------------------------------------------------------------------------
   THE DATE CELL IS A BUTTON NOW. Migration 120, 18 September.

   Gee, in her own portal on her brother's phone: *"Why can't I, when I'm
   acting as a bride, change the due date on one of my checklist items?"*

   ⚠️ IT MUST NOT LOOK LIKE A BUTTON AT REST. This column is scanned down a
   list of 85; a border or a fill on every row turns a checklist into a form.
   The affordance is the hover and the cursor, and the label on the empty ones.

   ⚠️ EVERY ONE OF THESE RESETS IS LOAD-BEARING. A <button> brings its own
   font, its own centring, a border and a background, and without `font: inherit`
   the dates come out in the browser's UI font — which is how one cell ends up
   looking like a completely different component from the div it replaced.
   -------------------------------------------------------------------------- */
.task-when {
  flex: 0 0 auto;
  order: 2;
  min-width: 104px;
  text-align: left;
  line-height: 1.25;
  padding: 1px 4px 2px;
  margin-left: -4px;           /* the padding must not shift the column */
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  border-radius: 6px;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

.task-when:hover { background: rgba(0, 124, 133, .09); }

.task-when:focus-visible {
  outline: 2px solid var(--teal-deep, #007c85);
  outline-offset: 1px;
}

/* An undated task now says so, because an empty cell is not a control. 68 of
   her 85 carry no date, so this is the common case, not the edge one. */
.task-when-add {
  display: block;
  font-size: .78rem;
  font-weight: 700;
  color: var(--teal-deep, #007c85);
  white-space: nowrap;
}

/* The picker and its way out, in place of the cell. */
.task-when-wrap {
  flex: 0 0 auto;
  order: 2;
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 104px;
}

/* 🛑 THE CHILDREN NEED THEIR OWN ORDER, AND FORGETTING IT PUT "No date" ABOVE
   THE DATE FIELD. `.task-when-edit` carries `order: 2` for its place in the
   ROW; inside this wrapper that made it come after a button whose order
   defaults to 0, so the way-out rendered first and the field second. Ordering
   both explicitly is the fix — `order-zero-is-not-leave-me-where-i-am`, again,
   and caught by looking at it rather than by reading it. */
.task-when-wrap .task-when-edit  { order: 1; flex: 1 1 auto; min-width: 0; }
.task-when-wrap .task-when-clear { order: 2; }

/* 🛑 "No date" — the only way to clear one on a phone, and it lives ON THE ROW.
   A phone's date picker can hand back a date and nothing else; there is no way
   to empty it. It was first put inside the date editor, and Gee caught that:
   *"it comes there later once you press the button, so they're not going to
   pick it up."* A way out you only meet after pressing something else is one
   nobody finds. It sits beside the date now, always, on the rows that have
   one — 17 of Tiffany's 85, so seventeen buttons, not eighty-five. */
.task-when-clear {
  flex: 0 0 auto;
  order: 3;                 /* after .task-when, which is order 2 */
  align-self: center;
  margin-left: 8px;
  min-height: 30px;
  padding: 5px 9px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--surface, #fff);
  font-family: inherit;
  font-size: .7rem;
  font-weight: 700;
  /* --ink-soft on white is 6.1:1 — quiet, because it is the lesser of the two
     things this control does, but never faint. */
  color: var(--ink-soft);
  cursor: pointer;
  white-space: nowrap;
}
.task-when-clear:hover { background: var(--sunk, #f4efe9); border-color: var(--ink-faint); }
.task-when-clear:focus-visible { outline: 2px solid var(--teal-deep); outline-offset: 2px; }

@media (hover: none) { .task-when-clear { min-height: 44px; padding: 8px 12px; } }

/* The picker, in place of the cell. Sized to the column so nothing jumps. */
.task-when-edit {
  flex: 0 0 auto;
  order: 2;
  min-width: 104px;
  max-width: 148px;
  font: inherit;
  font-size: .82rem;
  font-weight: 700;
  padding: 2px 4px;
  border: 1px solid var(--teal-deep, #007c85);
  border-radius: 6px;
  background: #fff;
  color: var(--ink, #2a2126);
}

.task-when-label {
  display: block;
  font-size: .62rem;
  font-weight: 800;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-faint, #a0949a);
}

.task-when-date {
  display: block;
  font-size: .82rem;
  font-weight: 700;
  color: var(--ink, #2a2126);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Late is the one state that has to carry across the row. */
.task-when.is-overdue .task-when-label { color: #b3001b; }
.task-when.is-overdue .task-when-date  { color: #b3001b; }

/* A task with no date keeps the column so the dates that exist stay aligned
   down the page.
   ⚠️ IT IS NO LONGER EMPTY. It used to hold nothing, on the reasoning that an
   empty cell says "no date" better than words do — true when it was a label,
   and wrong the moment it became the way to ADD one. */
.task-when.is-none { min-width: 104px; }

.task-row.is-done .task-when { opacity: .45; }

/* On a phone the row is already tight; the date drops under the title rather
   than squeezing the title into a third of the width. */
@media (max-width: 560px) {
  .task-when, .task-when.is-none { min-width: 0; text-align: left; padding-top: 4px; }
  .task-when-label { display: inline; }
  .task-when-date  { display: inline; margin-left: 5px; }

  /* 🛑 AN EMPTY CELL STAYS HIDDEN; A CONTROL DOES NOT.
     Gee found this on her brother's PHONE, and 68 of her 85 tasks carry no
     date — so hiding the dateless cell would have hidden the "Add a date"
     button on four rows in five, on the one device she was actually using,
     while it worked perfectly on a laptop. That is
     "a-control-you-have-to-be-told-about" all over again.

     ⚠️ SCOPED BY ELEMENT ON PURPOSE. Before migration 120 this cell is still a
     <div> with nothing in it, and that one must stay hidden — an empty block
     eating 44px on eighty rows of a phone is not an improvement. */
  div.task-when.is-none    { display: none; }
  button.task-when.is-none { display: flex; }

  /* The tap target is the thing, not the words. 44px is the floor. */
  button.task-when { min-height: 44px; display: flex; align-items: center; gap: 5px; }
  /* ⚠️ NOT `width: 100%`. Inside .task-when-wrap that forced the field onto a
     line of its own and pushed "No date" off it. It flexes instead, so the
     two sit side by side and still fill the row when the field is alone. */
  .task-when-edit  { min-height: 40px; max-width: none; flex: 1 1 auto; min-width: 0; }
}

/* ---------------------------------------------------------------------------
   THE COUPLE'S CHECKLIST ON A PHONE. 18 September.

   🛑 Gee, testing her own portal on her phone: *"It just doesn't look nice on
   the phone… very hard to understand that there's a checklist, the dots, the
   alignment. I feel like maybe it needs some boxes or something to show
   individual items more easily and clearly."*

   She was right, and rendering it at 375×812 with these stylesheets found
   something worse than the spacing: **the date was ABOVE the title.**
   `flex-wrap` plus `order: 2` on `.task-when` put "Overdue 15 Sept 2026" on
   the line before the name of the task, so she read the deadline of a thing
   she had not been told about yet. Items were 174–196px tall with a **2px**
   gap between them.

   Measured after: **738px → 582px** over four items, gap 2px → 10px. Roomier
   AND shorter, which is the tell that the old version was spending height on
   nothing.

   ⚠️ A SURFACE, NOT AN OUTLINE, AND THAT IS bloom.css's OWN RULE: *"Surfaces
   are tinted, not outlined. A 1px grey border round everything made the app
   read as a spreadsheet."* So: a white card and a soft shadow. The only line
   is the left edge, and it is there to carry the overdue red.

   ⚠️ PHONE ONLY, AND THE COUPLE'S SCREEN ONLY. Scoped to `.portal-phase` and
   560px — Gee's own checklist uses `.task-row` too and is untouched.

   🛑 AND IT CAME BACK THE SAME DAY. Gee, on the version above: *"the layout is
   still a bit shit on the phone for the checklist, please. Can you amend that
   and make it better?"*

   Rendered again at 375×812 against these same stylesheets, and she was right
   four separate times. None of it was taste:

   | what | measured |
   |---|---|
   | The date was BELOW "Remind me" | `.task-does` 224px, button 251px, date 284px |
   | Every note was three dots | `white-space: nowrap`, and the hover that undid it does not exist on a phone |
   | The comment count printed twice | "💬 2" and "2 comments", one above the other |
   | A task they added was indented differently | its title at x=81, every other title at x=56 |
   | The tick was 13×13 | on a list whose whole purpose is ticking |

   🛑 THE FIRST ONE IS THE ONE WORTH READING TWICE, BECAUSE THE BLOCK ABOVE
   CLAIMED IT WAS FIXED. It said the order is *"what it is, whose it is, WHEN,
   and only then the button"* — and `.task-does` is a child of `.task-main`,
   not of `.task-row`, so `grid-row: 3` on it never applied to anything. A
   grid property on a node that is not a grid item is a comment that looks like
   code. The rule was written, shipped, described in prose, and inert.

   ⚠️ `.task-main` DISSOLVES NOW — `display: contents` — so the title, the
   chips, the note and the actions are all real items of the card's own grid
   and can be put in the order she asked for. That is the whole structural
   change; everything else here is placement.

   Measured after, 375×812, five cards: order correct on all five, both notes
   readable in two lines (`scrollHeight === clientHeight`, so nothing is
   clipped), every title at x=71, tick target 38×38 around a 20px box, and no
   two elements overlapping. 836px → 911px — it is 75px TALLER, and that is the
   two notes coming back from "…" to words. Height was never the complaint.
   -------------------------------------------------------------------------- */
/* 🛑 SCOPED TO `#portal-body`, NOT TO `.portal-phase`, AND THAT IS THE SECOND
   THING THIS BLOCK GOT WRONG.

   Gee, 18 September, still on her phone: *"I don't think that the checklist
   part for the couples is fixed and done… The square next to the overdue is
   not in alignment. It just doesn't look good."*

   She is describing the tick on the cards under the **Overdue** heading, and
   those cards were getting NONE of this. The couple's checklist draws
   **three** `.task-list`s from the same `rowHtml()`:

     portal.js 2153   inside `<section class="portal-phase">`   ← styled
     portal.js 2187   inside `<section class="portal-attention">` — Overdue
     portal.js 2193   inside the same section — Due this week

   **Two of the three, and they are the two at the top of the page.** So the
   first thing she met on her own checklist was the old flex layout — a 13px
   tick, no card, the date out of place — directly above the new cards, which
   is exactly why it read as "odd" and "not in alignment" rather than as one
   thing being unstyled.

   ⚠️ THE COMMENT BELOW USED TO SAY *"PHONE ONLY, AND THE COUPLE'S SCREEN
   ONLY. Scoped to `.portal-phase`"* — treating one section as if it were the
   whole screen. `#portal-body` IS the couple's screen: her own checklist lives
   in `#task-groups`, outside it, and stays untouched. A fourth list added
   tomorrow is covered for free.

   ⚠️ AND AN ID ENDS THE CASCADE PROBLEM BELOW PERMANENTLY. 1,x,x beats every
   competitor in this file, so the `.task-row` on each selector is now belt and
   braces rather than the only thing holding it up.

   🛑 EVERY SELECTOR IN HERE CARRIES `.task-row`, AND THAT IS LOAD-BEARING.
   Written without it, five of these rules were beaten by rules further down
   this same file — equal specificity, later in the cascade, so the later one
   wins every property they share. Measured on the rendered page, not guessed:

     .portal-phase .task-row      { padding: 12px 14px }  lost to line 9848's 6px 10px
     .portal-phase .task-meta     { display: flex }       lost to `.task-row .task-meta`'s block
     .portal-phase .task-meta .muted-note                 lost display, white-space,
                                                          overflow and max-width to line 9852
     .portal-phase .task-does     { gap, margin-top }     lost to line 9863

   So the card shipped with the wrong padding and the note stayed a row of
   dots, while the stylesheet read as though both were fixed. The version
   before this one had the same fault and nobody could see it.

   ⚠️ IT IS NOT ONLY IDENTICAL SELECTORS. `.task-row .task-meta` and
   `.portal-phase .task-meta` are different text and the same weight — which is
   why CHECK 30 (a static comparison) cannot see all of these, and why the
   control for this block is the browser one: computed styles, at 375px.

   See `a-duplicate-rule-wins-the-cascade` in memory. Adding `.task-row` puts
   every rule here at 0,3,0 or better, above every competitor in this file.  */
@media (max-width: 560px) {
  /* 🛑 AND THE OVERDUE BLOCK STOPS BEING A BOX, OR IT IS A CARD INSIDE A CARD.
     `.portal-attention` is a white panel with a 1px border, a red left edge and
     18px of side padding. The moment its rows became cards — white, shadowed,
     with their own red left edge — it was the same object drawn twice, and its
     padding pushed those titles to x=92 while every other title on the page
     sat at x=71. Measured. That 21px is what "not in alignment" looks like.

     The heading keeps the signal: red, uppercase, with its count pill. Same
     shape as the phase sections above — a heading, then cards. */
  #portal-body .portal-attention {
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    margin-bottom: 20px;
  }

  #portal-body .task-list { display: grid; gap: 10px; }

  #portal-body .task-list .task-row {
    /* The ⋮ is pinned to this. */
    position: relative;
    display: grid;
    /* 🛑 THREE COLUMNS, AND THE MIDDLE ONE IS `auto` FOR ONE REASON.
       Gee: *"I would bring the 'no date' closer to the date."* She is right —
       measured, they were **91px apart**, because both sat in one cell with the
       date pinned left and "No date" pinned right, so the whole width of the
       card sat between a deadline and the button that clears it.

       Two items cannot sit side by side in one grid cell. So the date takes
       column 2, "No date" takes column 3, and column 2 is `auto` — it shrinks
       to the width of the date itself. Everything else spans `2 / -1` and is
       unaffected. Making column 2 `1fr` instead would push column 3 back to the
       far edge and change nothing. */
    grid-template-columns: auto auto 1fr;
    column-gap: 10px;
    row-gap: 5px;
    align-items: start;
    background: var(--surface, #fff);
    border: none;
    border-left: 3px solid var(--line);
    border-radius: 12px;
    padding: 12px 14px;
    box-shadow: 0 1px 2px rgba(28, 37, 35, .06), 0 1px 8px rgba(28, 37, 35, .04);
  }

  /* Late is findable while scrolling, which is the whole point of an edge. */
  #portal-body .task-row.is-late,
  #portal-body .task-row:has(.task-when.is-overdue) { border-left-color: var(--warn); }

  /* 🛑 THE WRAPPER STOPS EXISTING AS A BOX. Without this its four children are
     stuck inside one grid cell in DOM order — title, chips, summary, actions —
     and the date, which IS a grid item, can only ever land after all of them.
     That is exactly how "Remind me" ended up above the deadline. */
  #portal-body .task-row .task-main { display: contents; }

  /* A real target round a real box. It was a 13px checkbox with no padding on
     the one control this screen exists for, while every button beside it had
     44px. The label is the target; the box inside it is what you see. */
  #portal-body .task-row .task-check {
    grid-column: 1;
    grid-row: 1;
    width: 38px;
    height: 38px;
    margin: -8px 0 -8px -9px;   /* the target grows, the layout does not move */
    display: grid;
    place-items: center;
  }
  #portal-body .task-row .task-check input { width: 20px; height: 20px; }

  #portal-body .task-row .task-title {
    grid-column: 2 / -1;
    grid-row: 1;
    min-width: 0;               /* .task-main carried this before it dissolved */
    align-self: center;
    font-size: 1rem;
    line-height: 1.3;
  }

  /* Row 2: whose it is, then the note under it on a line of its own. */
  #portal-body .task-row .task-meta {
    grid-column: 2 / -1;
    grid-row: 2;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 6px;
    margin-top: 0;
  }

  /* 🛑 THE NOTE IS WORDS AGAIN. It inherits a one-line ellipsis meant for the
     desktop list, undone on `:hover` — and a phone has no hover, so what Gee
     read on her own portal was the supplier chip followed by "…". The same
     shape as the × whose explanation lived in a `title` attribute.

     ⚠️ ALL OF IT, NOT TWO LINES. The first version clamped to two with
     `-webkit-line-clamp`, and that rule was INERT: the note is a flex item of
     `.task-meta` now, a flex item blockifies its display, and the computed
     value came back `flow-root` rather than `-webkit-box`. Clamping needs the
     box. Read off the page, not assumed — it rendered three lines while the
     stylesheet said two.

     Left as a full wrap on purpose rather than rebuilt with `max-height`:
     silently cutting the end off a note is the fault above wearing a shorter
     coat. Cards on a phone are allowed to be different heights. */
  #portal-body .task-row .task-meta .muted-note {
    flex: 1 1 100%;
    display: block;
    white-space: normal;
    text-align: left;
    overflow: visible;
    text-overflow: clip;
    max-width: 100%;
    line-height: 1.35;
  }

  /* 🛑 ROW 3: WHEN. Above the actions, which is what she asked for.
     ⚠️ THE EDITOR TAKES THE SAME CELL, or the card jumps the moment she
     presses the date. */
  #portal-body .task-row .task-when,
  #portal-body .task-row .task-when-wrap {
    grid-column: 2;
    grid-row: 3;
    align-self: center;
    /* ⚠️ THE EDITOR TAKES ALL THREE COLUMNS. `.task-when-wrap` replaces the
       date with a real date input and its own Clear; squeezed into the `auto`
       column sized to the words "Overdue 15 Sept 2026" it would be unusable. */

    flex: initial;
    width: auto;
    min-width: 0;
    /* ⚠️ A REAL TARGET. The date is a button — pressing it is how a couple
       changes it — and it was drawn 19px tall, the shortest thing on the card
       by half. "No date" beside it already makes this row 40px, so on a dated
       row this costs nothing; on an undated one it is "Add a date" that gets
       the target, which is the control those 68 rows exist for. */
    min-height: 36px;
    margin: 0;
    padding: 0;
    display: flex;
    align-items: center;
    gap: 6px;
  }

  /* Both controls need a real target on a phone; they sit side by side. */
  #portal-body .task-row .task-when-wrap {
    grid-column: 2 / -1;
    align-items: center;
    flex-wrap: wrap;
  }

  /* 🛑 AND THE DATE MUST NOT STRETCH, OR IT SITS UNDER THE BUTTON.
     A grid item defaults to `justify-self: stretch`, so `.task-when` filled the
     whole cell and its right edge ran 66px PAST the left edge of "No date" —
     measured, after I had written a comment claiming they would not meet.
     `start` makes it take only the width of the words. */
  #portal-body .task-row .task-when { justify-self: start; }

  /* ⚠️ "No date" SHARES ROW 3 WITH THE DATE, pinned to the far end. Grid lets
     two items sit in one cell: the date is start-aligned and this is
     end-aligned, so they read as one line instead of costing the card a whole
     extra row on seventeen rows.

     ⚠️ AND IT IS NOT A BOX ANY MORE. `@media (hover: none)` gives it a 40px
     height for the finger, which drew a 66×44 outlined button weighing exactly
     as much as the deadline beside it. The target stays; the border goes. */
  #portal-body .task-row .task-when-clear {
    grid-column: 3;
    grid-row: 3;
    justify-self: start;
    align-self: center;
    margin-left: 0;
    border: 0;
    background: none;
    padding: 6px 2px;
    min-height: 40px;
    font-size: .72rem;
    text-decoration: underline;
    text-underline-offset: 3px;
  }
  #portal-body .task-row .task-when-label { display: inline; font-size: .62rem; }
  #portal-body .task-row .task-when-date  { display: inline; margin-left: 0; }

  /* Rows 4 and 5: what the row already knows, then what you can do to it. */
  #portal-body .task-row .task-sum { grid-column: 2 / -1; grid-row: 4; margin-top: 0; }

  /* ⚠️ ACROSS, NOT STACKED. The rule at 560px turns every `.task-does` into a
     column with `align-items: stretch`, which made "Remind me" a full-width
     bar — the loudest thing on a card about something else entirely. */
  #portal-body .task-row .task-does {
    grid-column: 2 / -1;
    grid-row: 5;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 12px;
    margin-top: 2px;
  }
  /* ⚠️ THE LAST CONTROL ON THE CARD UNDER TARGET. Measured at 27px while the
     tick, the date, "No date" and the ⋮ were all 36–40. It is scoped to the
     couple's phone card deliberately: `.task-remind-btn` is drawn on Gee's own
     rows too, and growing it there would change a list she has not complained
     about. */
  #portal-body .task-row .task-remind-btn {
    flex: 0 0 auto;
    width: auto;
    min-height: 36px;
    padding: 6px 12px;
  }

  /* 🛑 THE COMMENT CHIP WAS FAILING CONTRAST, AND IT WAS FAILING TWICE OVER.
     Gee: *"where it's got the three dots and the talking mark, that's too faint
     or not."* She was unsure. Measured, at 375px, on white:

       the comment chip in a dated section   opacity .78, 11.5px  → 3.38:1  ✗
       the same chip in the Overdue block    opacity 1,   12.4px  → 4.97:1  ✓
       the ⋮                                                      → 5.99:1  ✓
       the task's note                                            → 2.92:1  ✗

     So the SAME control was two different faintnesses on one page, because the
     `opacity: .78` rule further down this file is scoped to `.portal-phase` and
     the Overdue block is not one. **The fainter of the two fails AA**, and it
     is the one on eighty-odd rows.

     ⚠️ THE ⋮ SHE ASKED ABOUT IS FINE — 5.99:1. Left alone and said so, rather
     than "tidied" along with the one beside it.

     ⚠️ THE NOTE WAS THE WORST OF THE FOUR AND SHE DID NOT ASK ABOUT IT. 2.92:1
     is `--ink-faint` on white, and it is the sentence that says what the task
     actually means. Fixed here because it is the same card and the same
     complaint; `--ink-faint` is untouched everywhere else in the app. */
  #portal-body .task-row .task-does > details > summary {
    opacity: 1;
    font-size: .78rem;
    color: var(--teal-deep);   /* 4.97:1 on white, and still reads as an action */
  }
  #portal-body .task-row .task-meta .muted-note { color: var(--ink-soft); }   /* 5.99:1 */

  /* 🛑 THE ⋮ WAS BREAKING THE CARD IT SAT ON. With no placement it auto-filled
     column 1 row 2, and a 38px button in a column sized to a checkbox pushed
     that card's title 25px right of every other card's. Out of the flow and
     into the corner; the two rows it could reach reserve its width. */
  #portal-body .task-row .row-dots { position: absolute; top: 4px; right: 4px; }
  #portal-body .task-row.is-ours .task-title,
  #portal-body .task-row.is-ours .task-meta { padding-right: 38px; }
}

/* ---------------------------------------------------------------------------
   The couple's supplier cards.

   Gee, 30 Aug: "I don't really love the setup of this. I think it's really
   shit the way it's set up, to be honest. I think you're using the same colour
   everywhere. I think if it's an ask about this supplier, it should be a
   different colour everywhere so it stands out more."

   Three things were wrong and they were all the same thing — nothing on the
   card told you what kind of card it was. Twenty grey panels, contact details
   run together into one sentence of links, and the one thing you can DO on the
   card looking exactly like the four things you can only read.
   --------------------------------------------------------------------------- */

.portal-supplier {
  border-left: 4px solid var(--cat, var(--line, #ece5e8));
  border-radius: 0 12px 12px 0;
  background: var(--surface, #fff);
  box-shadow: 0 1px 2px rgba(36, 29, 33, .05);
}

/* The role wears the colour, so the eye finds "Photography" before it reads
   any of the names. */
.portal-supplier-role {
  color: var(--cat, var(--ink-faint));
  font-weight: 800;
  letter-spacing: .09em;
  text-transform: uppercase;
  font-size: .68rem;
}

.portal-supplier-name { font-weight: 700; }

/* Labelled rows. A phone number you can find without reading the line. */
.portal-supplier-rows { margin: 10px 0 0; display: grid; gap: 4px; }

.ps-row {
  display: grid;
  grid-template-columns: 74px 1fr;
  gap: 10px;
  align-items: baseline;
  font-size: .85rem;
}

.ps-row dt {
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-faint, #a0949a);
}

.ps-row dd { margin: 0; min-width: 0; word-break: break-word; }
.ps-row dd a { color: var(--ink, #2a2126); text-decoration: none; border-bottom: 1px solid var(--line); }
.ps-row dd a:hover { color: var(--cat, var(--v-pink, #e8197a)); border-bottom-color: currentColor; }

.portal-supplier-none { margin: 10px 0 0; font-size: .84rem; color: var(--ink-faint); }

/* THE ONE THING YOU CAN DO ON THE CARD, and it no longer looks like the four
   things you can only read. Filled pink — the only pink on the page, so it is
   unmistakably the action. */
.portal-thread.is-ask { margin-top: 12px; }

.portal-thread.is-ask > summary {
  display: inline-block;
  padding: 5px 13px;
  border-radius: 20px;
  background: var(--v-pink, #e8197a);
  color: #fff;
  font-size: .78rem;
  font-weight: 700;
  cursor: pointer;
  list-style: none;
}

.portal-thread.is-ask > summary::-webkit-details-marker { display: none; }
.portal-thread.is-ask > summary:hover { filter: brightness(1.08); }
.portal-thread.is-ask[open] > summary { margin-bottom: 10px; }

/* On the pink chip the reply flag has to invert or it disappears. */
.portal-thread.is-ask > summary .thread-new { background: #fff; color: var(--v-pink, #e8197a); }

/* Blue, and smaller. Gee, seeing the pink one: "I do not like the way you made
   it so bold and pink about the 'Ask about this supplier'. Maybe do a blue for
   that and maybe a bit smaller... I do like the reply button. I think it needs
   to be blue in the supplies tab."

   Still filled, because standing out was the original point — just not
   shouting. Blue is also the honest colour here: it is what suppliers and
   people wear everywhere else in this app, and pink is her brand, which should
   not be spent on a comment box. */
.portal-thread.is-ask > summary {
  padding: 4px 11px;
  background: #0067a3;
  font-size: .72rem;
  font-weight: 600;
}

.portal-thread.is-ask > summary .thread-new {
  background: #fff;
  color: #1e3f7d;
  font-size: .58rem;
}

/* ---------------------------------------------------------------------------
   "Ask about this" on a budget row.

   A button on the row, and nothing more. The conversation opens in a box over
   the page — see #portal-thread-modal. Gee looked at the version that rendered
   the thread into the table twice and said "the look of this is not good" and
   then "this doesn't look right at all", and she was right both times: a
   budget table is a grid of numbers and a chat thread in a cell of it breaks
   the rows apart whatever it is dressed in.
   --------------------------------------------------------------------------- */

.bg-ask {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 2px 0 6px;
  padding: 4px 12px;
  border: 1px solid #c3d0e8;
  border-radius: 20px;
  background: #eef2fa;
  color: #1e3f7d;
  font: inherit;
  font-size: .74rem;
  font-weight: 600;
  cursor: pointer;
}

.bg-ask:hover { background: #daf0ff; border-color: #0091ea; }
.bg-ask:focus-visible { outline: 2px solid #0067a3; outline-offset: 2px; }

/* A line that has a conversation on it already reads a shade stronger. */
.bg-ask.has-thread { background: #daf0ff; border-color: #0091ea; }

.bg-ask .thread-new {
  padding: 0 7px;
  border-radius: 20px;
  background: #0067a3;
  color: #fff;
  font-size: .58rem;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
}

/* The box itself. Nothing exotic — it is the app's own modal, so it behaves
   the way every other one does. */
.portal-thread-panel .comment-list { margin-bottom: 12px; }
.portal-thread-panel .comment { padding: 8px 0; }
.portal-thread-panel .comment + .comment { border-top: 1px solid var(--line-soft, #f5f0f2); }
.portal-thread-panel .comment-compose textarea { min-height: 68px; }

@media print { .bg-ask { display: none !important; } }

/* ===========================================================================
   THE CHECKLIST ROW, TIGHTENED.  (Option A, chosen 30 Aug.)

   Gee: "I think we can make this so much more beautiful, nicer, cleaner, and
   neater... more compact, where the new message shows up more prominently."

   Four faults, four fixes, nothing removed:

   1. The title began a third of the way across, behind an 82px button gutter.
      The gutter moved to the RIGHT, into the 400px of nothing that was already
      sitting between the end of a title and the date.
   2. It was two rows of four buttons, so it cost a second line of height on
      every one of 119 rows. One row of seven now.
   3. Notes wrapped, so no two rows were the same height. Clamped to one line —
      the whole note is still in the tooltip and in the open task.
   4. A message looked like a footnote. Unread is a filled chip now.
   =========================================================================== */

.task-row { padding: 5px 10px; align-items: center; }

/* =============================================================================
   LEFT AGAIN — everything you DO to a row, gathered by the tick.

   Gee and Kadija, 2 Sept, on a call: "we want all of that moved to the
   left-hand side where we've got the tick, because we think that would be
   easier, user-experience-wise, to be on the left instead of all the way where
   the date is."

   This is the third position these have had, so the history matters:

     28 Aug  Gee: "I'd much rather have the details and the attachment clip on
             the left-hand side." They were — as TWO ROWS OF FOUR. That cost
             every one of 119 rows an 82px column AND a second line of height.
     30 Aug  Gee: "cleaner, neater, more compact." So they moved right, into
             the empty space between the title and the date.
      1 Sept The seven buttons collapsed behind one ⋮ menu.

   Which is what makes the move back safe: TWO controls, not seven. One line
   tall, about 52px wide. The objection that drove them right no longer exists.

   The date is deliberately NOT part of this — it stays last, in its own
   column, so dates line up down the page.

   Order is set on every child, because a flex item left at the default 0
   would jump in front of anything numbered.
   ========================================================================== */
.task-row > .task-check   { order: 1; }
.task-row > .task-star    { order: 2; }
.task-row > .row-gutter   { order: 3; }
.task-row > .comment-flag { order: 4; }
.task-row > .who-name     { order: 5; }
.task-row > .task-main    { order: 6; }

.task-row > .row-gutter {
  grid-template-columns: repeat(7, 19px);
  grid-auto-rows: 18px;
  width: auto;
  padding-top: 0;
  margin-left: 0;   /* the row's own 12px gap is enough beside the star */
}

/* PRESENT AT REST, not invisible.
   Both of these were opacity 0 until you hovered. That was defensible out on
   the right where nobody was looking; beside the tick it is the exact thing
   Gee objected to on the mock-up — "how are we going to know that we add
   something in there?" So they sit back at rest and come up on the row.
   Faint, because 119 rows of full-strength buttons is a wall. */
.task-row > .row-gutter { opacity: .38; }

.task-row:hover > .row-gutter,
.task-row > .row-gutter:focus-within { opacity: 1; }

/* 🛑 A TICK YOU CANNOT SEE IS NOT A SELECTION.

   Gee, 20 September, trying to group three band tasks: *"how do I select them
   individually without pressing Select All… There's a flaw here for me to be
   able to group the bands. I want to group that, but I can't."*

   She was right, and this is the flaw. Picking several rows means moving
   between them — and the gutter only comes up on the row you are ON. So the
   row you ticked a second ago drops back to .38 the moment the mouse leaves
   it, and reads as untouched. The only evidence left is the counter at the
   bottom of the page.

   ⚠️ IT CANNOT BE FIXED ON THE CHILD. Opacity on a parent cannot be undone by
   anything inside it, so `.task-pick:checked { opacity: 1 }` does nothing —
   the tick already computes to 1 and is dimmed by the gutter around it.
   `ask-what-the-parent-is-doing`.

   ⚠️ AND NOT "ALWAYS ON" EITHER. The note above is still right: 119 rows of
   full-strength buttons is a wall. Only a row that IS selected keeps its
   gutter up, so the page stays quiet until she starts picking and then shows
   her exactly what she has picked.

   🛑 IT HANGS OFF `.is-picked`, AND THE FIRST VERSION USED `:has()` AND DID
   NOT WORK. `.task-row > .row-gutter:has(.task-pick:checked)` matched the
   element — `matches()` returned true, the rule was in the cascade, the
   browser supported `:has()` — and the computed opacity stayed .38 even with
   `!important`. The style engine had not re-resolved the row after the
   checkbox's `checked` was set from JS rather than by a real click.

   `syncPickedTasks` toggles `.is-picked` on the row two lines after it sets
   `box.checked`, so the class was there the whole time. A class the app
   already maintains beats a selector that has to infer the same thing.

   Caught by pressing it. Every automated check said the rule was fine —
   `walk-new-code-against-a-real-row`. */
.task-row.is-picked > .row-gutter { opacity: 1; }

/* 🛑 AND ON A PHONE THEY ARE FULL STRENGTH, BECAUSE THERE IS NO HOVER TO
   BRING THEM UP. This was already written, at line 1273, as
   `@media (max-width: 620px) { .row-gutter { opacity: 1 } }` with the comment
   *"No hover on a phone, so the gutter has to be there all the time or the
   buttons are unreachable."* **It never applied.** `.row-gutter` on its own is
   0,1,0 and the rule above is 0,2,0, so the .38 won and Gee's grip and ⋮ sat
   at 38% on the only device where nothing can reveal them.

   The rule was RIGHT and could not fire — which the gate calls the real bug.
   It lives here now, beside what it has to beat, and carries `.task-list` so
   it is 0,3,0. `.row-gutter` is rendered in exactly one place,
   `checklists.js` `taskRowHtml()`, and every task row sits in a `.task-list`.

   Found by CHECK 30, 18 September. See `a-duplicate-rule-wins-the-cascade`. */
@media (max-width: 620px) {
  .task-list .task-row > .row-gutter { opacity: 1; }
}
/* The comment flag's resting state is set further down, where its own
   :not(.has-comments) rule lives — putting it here would lose the cascade to
   it, which is exactly how the clock picker went wrong on 1 Sept. */

/* 🛑 THE DATE COLUMN, FIRST ON THE ROW (22 Oct, CHECK 238).
   Gee: "when I go into your due date, it is on the right-hand side, but to
   change the date … it is on the left-hand side. It's just a stupid user
   experience… I want that on the left side. I want the reminder on the left
   side as well, and I want the dropdown for the date on the left-hand side."
   The date was `order: 7` (last) and the typing box editInPlace swapped in had
   no order, so it landed first. Now the date and ✉️ Remind share one column,
   first, at ONE fixed width so the ticks line up whether or not a row has a
   Remind. Print keeps it last (print.css). */
.task-row > .row-when {
  order: -2;
  flex: 0 0 116px;
  width: 116px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  margin-right: 10px;
  align-self: flex-start;
  margin-top: 2px;
}

/* ✉️ Remind on the row (Gee, 20 Oct, CHECK 215), now UNDER the date (CHECK 238).
   Full pink — a pale one "looks like it's already been pressed". After a send
   it turns to an outline reading "Reminded 20 Oct". */
.row-when > .row-remind {
  border: 1.5px solid #e8197a; background: #e8197a; color: #fff;
  border-radius: 999px; padding: 1px 10px;
  font: inherit; font-weight: 800; font-size: .76rem; line-height: 1.5;
  white-space: nowrap; cursor: pointer;
}
.row-when > .row-remind:hover { background: #d0106b; border-color: #d0106b; }
.row-when > .row-remind.is-sent { background: #fff; color: #b0125e; }
.row-when > .row-remind.is-sent:hover { background: #fde7f1; }

/* ⚠️ `.task-due` (line ~6836) is `flex: 0 0 78px` with a left rule — a WIDTH
   on a row, but a HEIGHT inside this column, which pushed Remind 78px down. */
.row-when > .task-due { flex: 0 0 auto; padding-left: 0; border-left: 0; text-align: left; }
.row-when > .inline-edit { width: 116px; max-width: 100%; }

/* 🛑 ONE LINE A ROW (22 Oct, Gee's option B, CHECK 243). Gee: "it would be
   awesome if [Remind] was on the right-hand side of the date… Notion… on the
   side too, so we can sort of constrict the spacing… when you've got 30
   weddings going on, it just feels so overwhelming." Measured on Solin & Rony
   before: 70px a row with Remind, 47px without.
   - Date and Remind SIDE BY SIDE in one fixed-width column, so ticks still
     line up whether or not a row has a Remind (the zig-zag of CHECK 238).
   - Title, its tags and the note on ONE line; the note greys out and is cut
     short (open the to-do for all of it). The WhatsApp and prepared-email
     cards stay full width underneath. Screen only — print is untouched. */
@media screen {
  .task-row > .row-when { flex-direction: row; align-items: center; flex-basis: 210px; width: 210px; gap: 8px; }
  .task-row > .task-main {
    display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 8px; row-gap: 4px; min-width: 0;
  }
  .task-row > .task-main > .task-title { flex: 0 1 auto; min-width: 0; }
  .task-row > .task-main > .vis-tag,
  .task-row > .task-main > .thread-chip { flex: none; }
  .task-row > .task-main > .task-meta { flex: 1 1 80px; min-width: 0; }
  .task-row > .task-main > .wa-card,
  .task-row > .task-main > .tem-card { flex: 1 0 100%; }
  .task-row.is-open > .task-main > .task-meta { flex-basis: 100%; }
}
/* 🛑 THE DATE IN YOUR FACE, THE ROW COMPRESSED (22 Oct, CHECK 244). Gee: "you've
   lost the date… the due date should be a little bit more in your face…
   make that big square box smaller. Condense the message box… planner only can
   be just a P." Her yes to the drawn mock-up, the same morning.
   ⚠️ #task-groups, because bloom.css greys every date with a (1,3,0) rule. */
@media screen {
  .task-row > .row-when { flex-basis: 112px; width: 112px; gap: 5px; }
  #task-groups .task-row .row-when > .task-due:not(.is-empty) {
    font-weight: 700; font-size: .8rem; border-radius: 6px; padding: 1px 7px;
    white-space: nowrap; color: #4a2040; background: #f6eef7;
  }
  #task-groups .task-row .row-when > .task-due.is-overdue { background: #fcebeb; color: #a32d2d; }
  #task-groups .task-row .row-when > .task-due.is-soon    { background: #faeeda; color: #854f0b; }
  .row-when > .row-remind { width: 24px; height: 19px; padding: 0; display: inline-flex;
    align-items: center; justify-content: center; font-size: .8rem; line-height: 1; }
  .task-row > .task-check { width: 14px; height: 14px; }
  .task-row > .task-check:checked::after { left: 3px; top: -1px; width: 4px; height: 8px; }
  .task-check.is-soon, .task-check.is-late { box-shadow: 0 0 0 2px #ffe3e4; }
  .task-row > .vis-p { order: 1; flex: 0 0 16px; width: 16px; height: 16px; border-radius: 50%;
    background: #e1f5ee; color: #085041; font-size: 10px; font-weight: 800;
    display: inline-flex; align-items: center; justify-content: center; align-self: flex-start; margin-top: 2px; }
  .task-row > .vis-p.is-none { background: none; }
  .task-row > .comment-flag { padding: 2px 5px; }
}
@media print { .task-row > .vis-p { display: none; } }
/* A phone has no room for a 210px column: the date and Remind sit as wide as they are. */
@media screen and (max-width: 620px) {
  .task-row > .row-when { flex-basis: auto; width: auto; flex-wrap: wrap; }
}

/* One line. A note is context for the title, not a paragraph — and every row
   being a different height is what stops the list having a rhythm to scan. */
.task-row .task-meta {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

/* THE NEW MESSAGE. Filled, so it is the one thing on the row that is a
   colour rather than a shade. A read thread stays quiet — the point is not
   that there IS a conversation, it is that somebody is waiting. */
.task-row .comment-flag.is-new {
  background: var(--v-pink, #e8197a);
  border-color: var(--v-pink, #e8197a);
  color: #fff;
  font-weight: 700;
}

.task-row .comment-flag.is-new span { color: #fff; }

/* An open task keeps its full note — you opened it to read. */
.task-row.is-open .task-meta { white-space: normal; overflow: visible; }

@media (max-width: 900px) {
  /* Not hidden — wrapped. Taking the controls away on a narrow screen would
     mean no reordering and no delete on a laptop in a venue car park. */
  .task-row { flex-wrap: wrap; }
  .task-row > .row-gutter { flex: 1 0 100%; margin-left: 0; justify-content: flex-start; }
  .task-row > .task-due { min-width: 0; }
}

/* ---------------------------------------------------------------------------
   The same list, on a phone.

   Gee, 31 Aug: "when you view this on your phone, it's not compacted and it
   looks really weird and odd."

   The seven controls in .row-gutter are opacity:0 until you HOVER the row. A
   phone has no hover, so they never appeared — but the rule above still gave
   them a full-width line of their own under every single task. Measured on
   Tiffany & Liam: 7 buttons of 19x18px, invisible, adding about 30px to each of
   41 rows. Roughly one and a half phone screens of nothing.

   The run sheet already fixed its half of this at 620px by simply showing
   .tl-gutter — but that one is a 2x2 grid of 22px buttons sitting INSIDE the
   row. This gutter is seven buttons pushed onto their own line, so showing it
   would make the list taller still, which is the opposite of what was asked.

   The first fix, that morning, was to draw nothing until you tapped the row —
   .is-open already existed, so it needed no new gesture. It worked, but it
   still meant seven 38px buttons unfolding onto their own line.

   THE ⋮ MENU LATER THAT DAY MADE THAT UNNECESSARY. The gutter is now one
   button, so it costs 38px on the row it is already on and nothing underneath.
   It is drawn all the time rather than on tap — same reasoning as the budget
   row's: a control you have to discover by tapping is a control two testers
   did not find.

   Keyed on (hover: none) rather than a width, deliberately: a NARROW LAPTOP
   window still has a mouse, so it keeps the old behaviour and the venue car
   park case above still works.
   --------------------------------------------------------------------------- */
@media (hover: none) {
  .task-row > .row-gutter {
    display: flex;
    flex: 0 0 auto;
    opacity: 1;
    gap: 6px;
    padding-top: 0;
    align-items: center;
  }

  /* The × that clears a line of Recent activity was opacity:0 until the row was
     hovered, so on a phone the "activity you can actually clear" built on
     31 Aug could not be cleared at all. It costs one small × per row to just
     show it. Unlike .row-gutter this is a single control, so there is nothing
     to compact away. */
  .act-x { opacity: 1; }

  /* THE GRIP CANNOT WORK HERE, so it should not take a slot.
     Reordering is HTML5 drag-and-drop — `dragstart` / `dragover` / `drop` in
     timeline.js and checklists.js — and there is not a single touchstart,
     touchmove or pointerdown handler anywhere in assets/js. HTML5 drag does not
     fire on a phone at all, so on touch this button has never been able to do
     anything; it has only ever been a hole in the layout.

     On the run sheet that matters more than it sounds. .tl-gutter is a 2-wide
     grid, so five buttons take THREE rows — 71px, measured live, on a line
     whose whole height is 97px. The controls nobody can see are the tallest
     thing in the row. Dropping to four buttons makes it two rows and takes
     about 25px off every line.

     Reordering on touch is still there: the up and down arrows sit right beside
     it and work by tapping. */
  .tl-gutter [data-drag],
  .row-gutter .row-grip { display: none; }

  /* ONE BUTTON PER LINE ON THE RUN SHEET, not four.
     Gee, 31 Aug: the run sheet has to work on a phone, and the control block
     was the tallest thing in a 97px line. Four 22px targets stacked two-wide
     is 47px of height and 46px of width taken off the title, and none of them
     is big enough for a thumb anyway.

     So on touch only the menu button survives, at 30px. Move earlier, Move
     later and Delete moved INTO that menu (openLineMenu in timeline.js), which
     already carried colour, highlight, TBC and details — so nothing became
     unreachable, and both routes call the same moveEvent/deleteEvent. */
  /* 8 Sept: the gutter is a flex strip now, not a grid, so the old
     `grid-template-columns: 30px` here did nothing. Width comes from the one
     button that is left. The height problem this block describes above is
     gone anyway — the strip is 23px on every device. */
  .tl-gutter { opacity: 1; }

  /* [data-delete-event] is no longer emitted — the bin came off the row on
     8 Sept and lives in the menu. Left listed: it costs nothing and it is
     accurate about intent if one ever comes back. */
  .tl-gutter [data-move-up],
  .tl-gutter [data-move-down],
  .tl-gutter [data-delete-event] { display: none; }

  .tl-gutter [data-add-after] {
    width: 30px;
    height: 30px;
    border-radius: 8px;
    background: var(--surface-soft);
    font-size: 1rem;
  }
}

/* Delete reads as delete wherever it is offered. */
.tl-menu-danger { color: var(--late); }

/* MOVE EARLIER / MOVE LATER ARE NOT IN THE MENU WHERE THE ARROWS ARE.
   Gee, 8 Sept: "now that I've got the arrows up and down, I don't need a move
   earlier or move later option when I do the dropdown on the three dots" — and,
   told they are the only route on a phone: "I'm not going to do that on my
   phone. I won't amend it on my phone."

   So they are hidden where the arrows are showing, rather than deleted. It is
   the smaller change: the menu she actually uses loses two duplicate rows, and
   the phone — where the arrows ARE hidden, because HTML5 drag does not fire on
   touch and 22px targets are not thumb-sized — keeps the only way it has to
   reorder a line. Deleting them outright would have been the bigger edit and
   would have taken reordering off touch entirely.

   Keyed on (hover: hover) to mirror the (hover: none) block above: a narrow
   laptop window still has a mouse, so it keeps the arrows and loses the rows. */
@media (hover: hover) {
  .tl-menu-item[data-menu-move] { display: none; }
}

/* "Done", not "×". Gee hit this twice: pressing Enter posted her comment and
   handed the cursor back to the box, so she had no reason to believe she had
   finished, and a bare × on a panel that just swallowed your Enter key reads
   as "cancel" rather than "close". A word cannot be misread. */
.tp-close {
  width: auto;
  height: auto;
  padding: 5px 14px;
  border-radius: 20px;
  font-size: .78rem;
  font-weight: 700;
  font-family: inherit;
}

.tp-close:focus-visible {
  outline: 2px solid var(--v-pink, #e8197a);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------------------
   The couple's to-do rows, compacted.

   Gee, 30 Aug: "THE LAST IMAGE TO DO SHOULD ALSO BE COMPACTED AS WELL."

   Same faults as her own list had, and the same fixes: a note wrapping to two
   lines so no two rows match, and three action chips sitting on a line of
   their own under every single task whether or not anybody ever presses them.
   --------------------------------------------------------------------------- */

.portal-phase .task-row { padding: 6px 10px; }

/* One line, with the whole note still in the tooltip. */
.portal-phase .task-meta .muted-note {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

/* The three actions get quieter and closer. They are always available and
   almost never used on any given row — they should not weigh the same as the
   task itself. */
.portal-phase .task-does { gap: 4px 14px; margin-top: 3px; }

.portal-phase .task-does > details > summary {
  font-size: .74rem;
  opacity: .78;
}

.portal-phase .task-does > details[open] > summary { opacity: 1; font-weight: 700; }
.portal-phase .task-does > details > summary:hover { opacity: 1; }

/* A row being worked on gets its full note back. */
.portal-phase .task-row:hover .task-meta .muted-note { white-space: normal; }

/* How long a line takes, under the clock it belongs to.

   Gee, of four options: "I AGREE WITH YOUR PICK 1." It sits in the time column
   because it is a fact about the clock, it leaves the title alone, and on the
   day that column is the one being read down the left of the page.

   Quiet on purpose. The time is what you act on; the duration is what you plan
   with, and it must never compete with the number above it. */
.tl-dur {
  display: block;
  margin-top: 1px;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .04em;
  color: var(--ink-faint, #a0949a);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* A line whose time is not settled has an unsettled length too. */
.tl-row.is-tbd .tl-dur { color: var(--danger, #b3001b); opacity: .75; }

/* WHEN THEY ACTUALLY TURN UP, under the time they start. 17 September.
   Gee: "there should be an arrival time, then the start time and the end time."
   Pink rather than grey, because it is a different FACT from the duration
   beside it — that is arithmetic on this line, this is a time off the
   supplier's card — and the two must not read as one number. */
.tl-arr {
  display: block;
  margin-top: 1px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--pink-tint, #fdeaf3);
  color: var(--pink-deep, #c31465);
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .03em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* "Open the checklist →", on the cards that open something.

   Gee, 31 Aug: "the checklist progress, the coming up next, how do you know to
   click that?" She did not. Both had opened the checklist since that morning
   and nothing on them said so — and a hover cursor is no answer, because you
   have to already suspect a thing is clickable to discover that it is.

   A line, not a button: the whole card is still the target, exactly as before.
   This only says so out loud. The Details card gets nothing, because it does
   not go anywhere, and a card that looks clickable and is not is worse than
   one that says nothing at all. */
.ov-opens {
  display: block;
  margin-top: 14px;
  padding-top: 11px;
  border-top: 1px solid var(--line);
  color: var(--v-pink, #e8197a);
  font-size: .76rem;
  font-weight: 700;
}

.ov-card.is-jump:hover .ov-opens { text-decoration: underline; }

/* On paper it is an instruction to click something. */
@media print { .ov-opens { display: none !important; } }

/* ---------------------------------------------------------------------------
   Clearing an activity entry.

   Gee, 31 Aug, for the second time: "How do I hide that? How do I mark that
   off that I've read them?" The first time I answered it by hiding the "Mark
   all read" button when there was nothing to mark — which fixed the button and
   left the real fault alone. This was a LOG being read as an INBOX, and a log
   never empties.

   The × stamps seen_at on that one row. Nothing is deleted: "Everything" still
   shows the lot, dimmed, because the audit trail is the reason this exists.
   --------------------------------------------------------------------------- */

.act { position: relative; padding-right: 30px; }

.act-x {
  position: absolute;
  top: 7px;
  right: 0;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink-faint);
  font-size: .82rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity .12s, color .12s, border-color .12s;
}

/* On the row you are pointing at, or the one you have tabbed to. Twenty of
   these showing at once is a column of crosses down the card. */
.act:hover .act-x,
.act-x:focus-visible { opacity: 1; }

.act-x:hover {
  color: var(--v-pink, #e8197a);
  border-color: var(--v-pink, #e8197a);
}

/* Read, in the Everything view. Present, and plainly behind you. */
.act.is-read { opacity: .55; }

/* "All caught up" — a sentence, not a blank box. */
.act.is-empty { padding: 18px 2px; text-align: center; }
.act-clear-head {
  display: block;
  font-family: var(--display, inherit);
  font-size: 1rem;
  font-weight: 800;
  color: var(--c-green, #00733d);
  margin-bottom: 3px;
}

/* Disabled rather than gone. Hiding it is what made it unfindable the first
   time she went looking. */
#activity-mark-seen:disabled { opacity: .4; cursor: default; }

@media print { .act-x { display: none !important; } }

/* The message chip, in its own column beside the date.

   Gee, on what actually shipped: "this layout was never proposed either." She
   approved option A, and in that picture the chip sat on the right next to the
   date. I moved the row's controls and left this inside the title block, where
   it wrapped onto a line of its own underneath the words and read as a
   footnote — which is the exact fault option A existed to fix. */
.task-row > .comment-flag {
  flex: 0 0 auto;
  align-self: center;
  margin-left: 0;   /* beside the dots now, not floated off the title */
}

/* A row with no conversation yet keeps the chip quiet until you are on it —
   119 empty speech bubbles down a page is noise, and the point of showing it
   on every row is that starting a conversation is one click. */
.task-row > .comment-flag:not(.has-comments) { opacity: .38; }
.task-row:hover > .comment-flag:not(.has-comments),
.task-row > .comment-flag:focus-visible { opacity: 1; }

/* ---------------------------------------------------------------------------
   Grouped by person: you on the left, the couple on the right.

   Gee, 31 Aug: "I want the bride and the groom to be on the right-hand side
   and only me on the left-hand side." The lanes were alphabetical, so which
   column was hers changed with the names on the wedding.
   --------------------------------------------------------------------------- */

.lane-col { min-width: 0; display: grid; gap: 18px; align-content: start; }

/* Nothing assigned to her on this wedding is a fact, not a hole — the column
   simply has no lanes in it and the couple's still sit on the right. */
.lane-col:empty { display: none; }

/* ---------------------------------------------------------------------------
   A meeting is not a phase of the wedding.

   Gee: "for the things outside of the meetings, I want it to really just show
   the heading — that way we can really tell that that's a part of the wedding
   itself, not a part of the meetings."

   Read off the section name, so a heading she renames tomorrow follows the
   rule without anybody re-tagging anything.
   --------------------------------------------------------------------------- */

.task-group.is-meeting > .task-group-head {
  /* GRAPE, not plum. --c-plum is #b3145e and the palette comment beside it
     says "magenta — you": my purple rail shipped as her own brand pink, so a
     meeting looked like everything else and read as a wide pink slab. Gee:
     "the way that you have done this is so messy... it looks nothing like
     what you proposed."

     --c-indigo is the only genuinely purple hue in the palette and nothing on
     the checklist uses it. */
  background: var(--c-indigo-tint);
  border-left: 3px solid var(--c-indigo);
  border-radius: 0 8px 8px 0;
  padding: 6px 12px;
  color: var(--c-indigo);
}

.sec-kind {
  display: inline-block;
  margin-right: 8px;
  padding: 1px 8px;
  border-radius: var(--radius-pill, 20px);
  background: var(--c-indigo);
  color: #fff;
  font-size: .58rem;
  font-weight: 800;
  letter-spacing: .07em;
  text-transform: uppercase;
  vertical-align: 1px;
}

/* ---------------------------------------------------------------------------
   Dragging a whole section.
   --------------------------------------------------------------------------- */

.sec-grip { cursor: grab; letter-spacing: -3px; }
.sec-grip:active { cursor: grabbing; }

.task-group.sec-dragging { opacity: .45; }

.task-group.sec-drop-above > .task-group-head { box-shadow: 0 -3px 0 var(--v-pink, #e8197a); }
.task-group.sec-drop-below { box-shadow: 0 3px 0 var(--v-pink, #e8197a); }

@media print {
  .sec-kind { background: none !important; color: #2a2126 !important; font-weight: 800; }
  .task-group.is-meeting > .task-group-head { background: none !important; }
}

/* ---------------------------------------------------------------------------
   The chip and the date sit level with the TITLE, not floating in the middle.

   Gee, looking at what shipped: "the way that you've got one new message in
   the meeting in there, you can't see that clearly. It looks really messy and
   ugly, and it's like the new message section is out of the box."

   She is right and my mockup is what misled us both. It drew single-line rows.
   Her real rows carry a PLANNER ONLY tag, a Notion tag and a note, so they run to
   three lines — and with align-items:center the chip and the date centred
   themselves against all three and hung in white space beside nothing.

   Top-aligned, with a hair of padding so they sit on the title's own line.
   --------------------------------------------------------------------------- */

.task-row { align-items: flex-start; }

.task-row > .task-check,
.task-row > .task-star,
.task-row > .who-name,
.task-row > .comment-flag,
.task-row > .task-due,
.task-row > .row-gutter { align-self: flex-start; margin-top: 2px; }

/* The tick is a shade taller than the text it sits beside. */
.task-row > .task-check { margin-top: 3px; }

/* An unread chip is the one thing on the row that means somebody is waiting,
   so it keeps its fill — but it stops being a lozenge adrift in a gap by
   sitting on the same line as the words it belongs to. */
.task-row > .comment-flag.is-new { align-self: flex-start; }

/* ---------------------------------------------------------------------------
   A bigger meeting heading.

   Gee: "the meeting round box that you've got, say, 16th of July, is just too
   small. I think you can make it larger so it doesn't look so messy."

   It was carrying the same 0.78rem as a phase heading while doing more work —
   a chip, a date, a count and three controls — so everything in it was
   cramped and the bar read as clutter rather than a divider.
   --------------------------------------------------------------------------- */

.task-group.is-meeting > .task-group-head {
  padding: 11px 16px;
  font-size: .92rem;
  letter-spacing: .06em;
  gap: 10px;
}

.task-group.is-meeting .sec-kind {
  padding: 3px 11px;
  font-size: .64rem;
  letter-spacing: .09em;
}

.task-group.is-meeting .task-group-count { font-size: .78rem; }

/* ---------------------------------------------------------------------------
   A pill inside a pill.

   The unread chip on a checklist row is a filled magenta lozenge, and the
   COUNT inside it kept its own amber fill from the older design — so it read
   as a yellow blob sitting in a pink one, which is the mess in Gee's close-up.
   The outer pill already carries the colour; the number just needs to be
   white on it.
   --------------------------------------------------------------------------- */

.task-row .comment-flag.is-new span {
  background: none;
  padding: 0;
  margin-left: 5px;
  color: #fff;
  font-weight: 800;
}

/* ---------------------------------------------------------------------------
   The unread chip goes back to tangerine.

   Gee: "when you receive a message, I still don't love that it's pink and it's
   one new message, and I think that that needs to be different. Maybe orange."

   It WAS tangerine until this morning — I made it pink when I moved it into
   its own column, on my own reasoning about it needing to stand out. Pink is
   her brand and it is already on the stars, the buttons and the bands; one
   more use of it is one more thing that does not stand out.
   --------------------------------------------------------------------------- */

.task-row .comment-flag.is-new {
  background: var(--warn);
  border-color: var(--warn);
}

/* ---------------------------------------------------------------------------
   Phase headings get the same weight as meeting headings — in blue.

   Gee: "Just trying to find the full checklist... I can't see that that has
   come through nicer, but also it doesn't have to be pink. There's just too
   much pink."

   Both halves are fair. I gave the meeting heading a bar and left the phase
   heading as thin text, so Full checklist got worse by comparison — not
   because anything changed on it, but because the thing beside it improved.

   Blue, which is the colour these headings have always been, so this adds
   weight without adding a fifth pink to a page that already has the stars,
   the buttons and the bands.
   --------------------------------------------------------------------------- */

.task-group:not(.is-meeting) > .task-group-head {
  background: var(--c-sky-tint);
  border-left: 3px solid var(--c-sky);
  border-radius: 0 8px 8px 0;
  padding: 11px 16px;
  font-size: .92rem;
  letter-spacing: .06em;
  gap: 10px;
  color: var(--c-sky);
}

.task-group:not(.is-meeting) .task-group-count { font-size: .78rem; }

/* By date the headings are months, which are not sections and cannot be
   dragged or renamed — they stay quiet rather than pretending to be one. */
#task-groups.by-date .task-group > .task-group-head {
  background: none;
  border-left: 0;
  padding: 6px 0;
}

@media print {
  .task-group > .task-group-head {
    background: none !important;
    border-left: 0 !important;
    padding: 6px 0 !important;
  }
}

/* ---------------------------------------------------------------------------
   A NEW MESSAGE: a badge on the bubble, on a tangerine row.

   Options 4 and 3 of five, chosen together. Gee: "I like that, but I would
   also like it with the yellowy orangey background as well, like you've got in
   the number three."

   The pair does two different jobs. The tint is visible from the top of a page
   of 119 without reading anything; the badge says which row and how many once
   you are looking at it.

   Three versions of this failed before it, all for the same reason: they ADDED
   an object to the row — a lozenge that then had nowhere to live and hung in
   the gap between the note and the date. Nothing is added here. The bubble was
   always on every row; it gains a number.
   --------------------------------------------------------------------------- */

.task-row > .comment-flag.is-new {
  position: relative;
  background: none;
  border: 0;
  color: var(--ink-soft);
  overflow: visible;
  opacity: 1;
}

.task-row > .comment-flag.is-new:hover { background: none; }

/* Specificity: the "pill inside a pill" fix above sets
   `.task-row .comment-flag.is-new span { background: none }`, which is three
   classes and beat a bare .flag-badge — so the badge shipped with no fill at
   all. Caught by measuring it rather than by looking at it. */
.task-row .comment-flag.is-new .flag-badge {
  position: absolute;
  top: -5px;
  right: -7px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: var(--radius-pill);
  /* The vivid tangerine, not the dark type colour: this is a filled shape at
     10px, and the readable-on-tint end of the hue goes muddy that small. */
  background: #ff6f00;
  color: #fff;
  font-size: .58rem;
  font-weight: 800;
  line-height: 16px;
  text-align: center;
  /* A ring in the row's own colour, so the badge reads as sitting ON the
     bubble rather than behind it — and it has to follow the tint, or it cuts a
     white hole in the orange row. */
  box-shadow: 0 0 0 2px var(--warn-tint, #ffe3e4);
}

/* The row itself. */
.task-row.is-unread { background: var(--warn-tint, #ffe3e4); }

/* Hover still has to read as hover on an already-tinted row. */
.task-row.is-unread:hover { background: #ffe3e4; }

/* Off the tint, the ring goes back to the page. */
.task-row:not(.is-unread) .comment-flag.is-new .flag-badge { box-shadow: 0 0 0 2px var(--surface); }

/* Read it and the row goes quiet again — no tint, no badge, the plain count. */
.task-row.is-done.is-unread { background: none; }

@media print {
  .task-row.is-unread { background: none !important; }
  .flag-badge { display: none !important; }
}

/* ---------------------------------------------------------------------------
   The unread row goes HIGHLIGHTER YELLOW, and the badge stays orange.

   Gee: "do you think it should be orange because there is a bit of orange
   already... maybe it should be like a yellow? More of the highlight in the
   back. I do like the orange number one."

   She is right, and by more than she knew. #ffe3e4 is used NINE times in this
   app — the "to check" pill, the Check two numbers heading, the coverage
   panel, the change request card, and the highlighter that calls itself
   h-yellow while actually being orange. A row in that colour was saying the
   same thing as five other things.

   This yellow is used nowhere else. It reads as a highlighter pen, which is
   exactly what an unread row is: something marked to come back to. And the
   orange badge sits on it far better than it sat on orange.
   --------------------------------------------------------------------------- */

.task-row.is-unread { background: #fff8c4; }
.task-row.is-unread:hover { background: #fff2a8; }

/* The ring has to follow the row, or it cuts a hole in the highlight. */
.task-row.is-unread .comment-flag.is-new .flag-badge { box-shadow: 0 0 0 2px #fff8c4; }
.task-row.is-unread:hover .comment-flag.is-new .flag-badge { box-shadow: 0 0 0 2px #fff2a8; }

/* "6 without an invoice" — pressed, it filters the budget to exactly those.

   Amber rather than pink: this is a chase, the same job the "still an
   estimate" finding sits under, and it should read as the same kind of thing
   in both places. */
#bg-no-invoice {
  border-color: #ffb74d;
  color: #d4121f;
  background: var(--warn-tint, #ffe3e4);
  font-weight: 700;
}

#bg-no-invoice:hover:not(:disabled) { border-color: #d4121f; }

#bg-no-invoice.is-on {
  background: #d4121f;
  border-color: #d4121f;
  color: #fff;
}

/* Nothing outstanding is worth saying once, quietly, rather than hiding the
   button and leaving her wondering whether it ran. */
#bg-no-invoice:disabled {
  background: var(--line-soft);
  border-color: var(--line);
  color: var(--ink-faint);
  cursor: default;
}

@media print { #bg-no-invoice { display: none !important; } }

/* ---------------------------------------------------------------------------
   The comment box — three of Gee's findings from the couple test, 1 Sept.

   > "The post is on the right-hand side again. It'll be good if it was right
   >  below, just where the Save is... but the add a comment doesn't also say
   >  Add a file. When that happens, you can't tell that it has been done."

   Three separate faults in one small box:

     1. Send sat bottom-RIGHT while Save in the answer box sits bottom-LEFT.
        Two identical jobs, two places. This is the consistency point she has
        made repeatedly, and it is fixed by picking a side: LEFT, matching Save.

     2. Nothing said the box takes a file until you found the paperclip.

     3. .compose-file-name had NO CSS AT ALL. The filename was written into a
        bare span, so attaching a document produced almost no visible change —
        which is exactly why she could not tell it had worked.
   --------------------------------------------------------------------------- */

/* LEFT, to match Save. One place for the main action in a compose box. */
.comment-compose { justify-content: flex-start; }

/* ---------------------------------------------------------------------------
   "REMIND ME" AS A BUTTON. 15 September.

   Gee, in her own couple's portal: *"It would be good if the reminder can be a
   button. It doesn't have to be three dots because there's nothing else
   there."*

   The ⋮ was a menu of ONE on every task she wrote — its second item, Remove,
   is pushed in only for a task the couple added themselves. Two clicks and a
   popup to reach a control that could just be the control.

   Same size and weight as the ⋮ it replaces so the right-hand edge of the list
   does not change shape, and quiet enough that eighty of them down a page do
   not read as eighty calls to action.
   --------------------------------------------------------------------------- */
/* ⚠️ NO `order` ANY MORE — it moved out of `.task-row` and into `.task-does`,
   beside "Add a comment or a file". It used to need `order: 9` to stop the
   default 0 landing it mid-row; inside .task-does it simply follows its DOM
   position, which is where it should be. */
.task-remind-btn {
  flex: 0 0 auto;
  padding: 4px 10px;
  border: 1px solid var(--line, #ece5e8);
  border-radius: 20px;
  background: transparent;
  color: var(--ink-soft, #6c6067);
  font: inherit;
  font-size: .74rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}

.task-remind-btn:hover {
  border-color: var(--v-pink, #e8197a);
  color: var(--v-pink, #e8197a);
}

.task-row.is-done .task-remind-btn { opacity: .45; }

@media print { .task-remind-btn { display: none !important; } }

/* The sentence the panel never had. Gee: *"it just says 'Remind me', but then
   there's nothing else that is saying 'at a comment' or 'for'."* */
.remind-lead {
  margin: 2px 0 8px;
  color: var(--ink, #2a2126);
  font-size: .84rem;
  font-weight: 700;
}

/* The confirmation that a file is on. A chip, because it has to be noticed:
   it is the only thing that says the attach happened. */
.compose-file-name:not(:empty) {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  padding: 4px 11px;
  border: 1px solid var(--c-sky);
  border-radius: var(--radius-pill, 999px);
  background: var(--c-sky-tint);
  color: var(--c-sky);
  font-size: .78rem;
  font-weight: 600;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Sky, not green: green means done, and this is attached-but-not-sent. The
   paperclip repeats the button that produced it, so the two read as a pair. */
.compose-file-name:not(:empty)::before { content: '\1F4CE'; }

/* ---------------------------------------------------------------------------
   THE ROW MENU — one ⋮ per row, everything you can do to that row inside it.

   Gee, 1 Sept, showing Aisle Planner: "it's like three dots on the side."

   It replaced two things built earlier the same day:

     - a bare bin on the budget row, added because BOTH testers had failed to
       find delete (Kadija: "add the delete button icon on the budget line
       front page instead of clicking on the line"; Gee: "I can't see that
       there is a delete"). It fixed delete and left edit still meaning
       "click the row somewhere."

     - six identical grey squares in the checklist gutter, which meant reading
       six tooltips to find the one you wanted.

   QUIET UNTIL WANTED, not hidden until hovered. The row controls that hide
   until hover are exactly what could not be found on a phone this morning, so
   the dots are always drawn on the budget row and simply sit back until you go
   near them. Red only on hover, and only on the delete line: a bin glowing red
   on every line of a budget reads as forty warnings.

   The menu itself is the run sheet's menu, to the pixel — same white, same
   border, same shadow, same 10px radius. That menu already existed and Gee
   already knows it. A second look would be a second thing to learn.
   --------------------------------------------------------------------------- */

.row-dots {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  margin-left: 4px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 8px;
  background: none;
  color: var(--ink-faint);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  vertical-align: middle;
}

.row-dots:hover,
.row-dots[aria-expanded='true'] {
  border-color: var(--line);
  background: var(--surface-soft);
  color: var(--ink);
}

.row-dots:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

/* On a phone there is no hover, so it has to read as pressable already, and
   it has to be big enough to hit with a thumb. */
@media (hover: none) {
  .row-dots {
    width: 38px;
    height: 38px;
    border-color: var(--line);
    border-radius: 9px;
    color: var(--ink-soft);
  }
}

/* Never on paper — a printed budget or checklist is a document, not a control
   panel. The menu can't print anyway; it lives on <body> and is removed on
   close, but the dots would. */
@media print { .row-dots { display: none !important; } }

/* Positioned in page coordinates on <body>, not inside the row: a table cell
   and a task row both clip their own overflow, and a menu opened inside one
   gets cut off at the row edge. Same reason the run sheet's does. */
.row-menu {
  position: absolute;
  z-index: 200;
  min-width: 210px;
  padding: 5px 0;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(42, 33, 38, .12), 0 2px 6px rgba(42, 33, 38, .08);
}

.row-menu-item {
  display: flex;
  align-items: baseline;
  gap: 9px;
  width: 100%;
  padding: 7px 14px;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: .86rem;
  text-align: left;
  cursor: pointer;
}

.row-menu-item:hover,
.row-menu-item:focus-visible { background: var(--line-soft); outline: none; }

/* Fixed width so the labels line up in a column rather than stepping in and
   out as the icons change width — ✎ and 📎 are not the same size. */
.row-menu-icon {
  flex: 0 0 16px;
  color: var(--ink-faint);
  font-size: .9rem;
  text-align: center;
}

/* The line under each one saying what it DOES, or how many are on it. */
.row-menu-item small {
  display: block;
  margin-top: 1px;
  font-size: .74rem;
  line-height: 1.35;
  color: var(--ink-faint);
  font-weight: 400;
}
.row-menu-item:hover small,
.row-menu-item:focus-visible small { color: var(--ink-soft); }

.row-menu-sep { height: 1px; margin: 5px 0; background: var(--line); }

/* Delete is red IN THE MENU, where it is one line among five and the red is
   doing the job of telling them apart — not on the row, where it would be the
   loudest thing on a page of forty. */
.row-menu-item.is-danger { color: var(--late); }
.row-menu-item.is-danger .row-menu-icon { color: var(--late); }
.row-menu-item.is-danger:hover,
.row-menu-item.is-danger:focus-visible { background: var(--late-tint); }

/* Room for a thumb, and for the fact that a mis-hit here deletes something. */
@media (hover: none) {
  .row-menu { min-width: 240px; }
  .row-menu-item { padding: 11px 16px; font-size: .92rem; }
}

/* A .file-thumb.is-doc rule lived here for one afternoon, sizing an <object>
   that was meant to render a PDF's first page. The object never rendered — see
   the warning on filePreviewHtml() in budgetfiles.js — so the rule styled
   nothing and is gone with it. Do not re-add either half without testing
   against a real PDF. */

/* Inside the Edit budget line box the list is a reference, not a workbench —
   the attaching, reading and deleting all stay in the files modal. So it is
   tighter, and its top margin goes, because a form-section heading is already
   sitting directly above it. */
.b-files .file-list.is-compact { margin-top: 0; gap: 6px; }
.b-files .file-row { padding: 7px 9px; }
.b-files > .btn { margin-top: 10px; }
.b-files .muted-note { margin: 0 0 4px; }


/* ---------------------------------------------------------------------------
   WHAT IS TICKED, AND WHY IT IS THE LAST THING IN THIS FILE.
   Run sheet and budget, 12 September.

   ⚠️ SPECIFICITY TIES GO TO WHATEVER COMES LATER, so these are last on purpose.
   Written next to their own components, both of them LOST:

     .tl-row.is-picked          (0,2,0) ties with .tl-row:hover        (0,2,0)
     .bg-table tr.is-picked>td  (0,2,1) loses to
                    .bg-table tbody tr[data-line]:hover td.bg-status   (0,3,2)

   So hovering a selected run sheet line dropped the tint entirely, and hovering
   a selected budget line turned just its Status cell grey while the rest stayed
   pink. Both are the same fault the `.home-card` collision recorded: the rule
   was right and the cascade decided otherwise.

   They are kept together rather than beside each component for the same reason
   the two screens share one design — one place to read, one place to change.
   --------------------------------------------------------------------------- */
.tl-row.is-picked,
.tl-row.is-picked:hover { background: var(--pink-tint); }

.bg-table tbody tr.is-picked > td,
.bg-table tbody tr.is-picked:hover > td,
.bg-table tbody tr.is-picked:hover td.bg-status,
/* ⚠️ AND THE SUPPLIER TABLE, which is `.sup-table` and not `.bg-table`. The
   tick reuses the budget's class name so one rule covers both — but the SCOPE
   had to be widened too, or a ticked supplier row would stay white while the
   bar below it counted it. Added here rather than as a second rule, for the
   same reason the three drawers share one block. */
.sup-table tbody tr.is-picked > td,
.sup-table tbody tr.is-picked:hover > td { background: var(--pink-tint); }

/* ⚠️ THE CHECKLIST'S SELECT TICK IS NOT ITS DONE TICK. The row already opens
   with `.task-check`, which marks a task DONE — so this one lives in the
   gutter with the other controls and is deliberately smaller and quieter. Two
   identical squares side by side meaning opposite things is how somebody ticks
   "finished" when they meant "not happening". */
/* ⚠️ A DIFFERENT COLOUR, BECAUSE TWO PINK TICKS MEANT OPPOSITE THINGS.
   Gee, on first use: "it's very hard to distinguish between the one square and
   the other square. Maybe you need that to be a different colour."
   She is right — the done tick fills PINK when ticked, and so did this one, so
   two identical pink squares on one row meant "I finished it" and "this is not
   happening". Selection is SKY BLUE here, which is already the heading colour
   on this screen, and it is round rather than square so the two are told apart
   before colour even registers. */
/* ⚠️ DRAWN, NOT ACCENT-COLOURED — because `accent-color` and `border-radius`
   do NOTHING to a native checkbox's shape, which is why the first attempt came
   back `radius: 0px` when measured.

   And colour alone was never going to fix it. The done tick is TEAL and this
   was already PINK — two different colours — and Gee still said "it's very hard
   to distinguish between the one square and the other square". The problem is
   that they are both SQUARES, the same size, on the same row.

   So this one is a ROUND dot. Round against square is read before colour is,
   and there is a gap and a hairline between them so they cannot be mistaken
   for a pair. */
.task-row .row-gutter .task-pick {
  appearance: none;
  -webkit-appearance: none;
  align-self: center;
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  margin: 0 8px 0 0;
  padding: 0;
  /* ⚠️ WAS `--ink-faint`, WHICH IS 2.92:1 AND IS EXACTLY WHAT GEE COULD NOT
     SEE — the shared base at the end of this file could not reach it, because
     this selector is (0,3,0). Fixed here rather than by weakening that rule.
     🛑 SKY, NOT TEAL. This dot turns `--c-sky` when you tick it, for the
     reason written above: it has to be told apart from the pink done-tick
     beside it. An empty teal ring that fills blue would be two colours for
     one control. 6.05:1 on white. The ROUND shape and the blue fill are
     untouched — only the colour of the empty ring moved. */
  border: 1.5px solid var(--c-sky, #0067a3);
  border-radius: 50%;
  background: var(--surface, #fff);
  cursor: pointer;
  position: relative;
}

/* ⚠️ BLUE, NOT PINK, AND THIS IS THE ONE PLACE THAT BREAKS THE PATTERN.
   Selection is pink on the budget, the run sheet and the suppliers tab — but
   on THIS row the done tick is already a pink rounded square, and measuring
   proved the select tick was filling the identical pink. Two pink controls
   side by side meaning "I finished it" and "this is not happening" is what Gee
   was pointing at.
   Nowhere else has a competing tick, so nowhere else has to give this up. Here,
   telling two adjacent controls apart beats matching three other screens. */
.task-row .row-gutter .task-pick:checked {
  background: var(--c-sky, #0067a3);
  border-color: var(--c-sky, #0067a3);
}

/* The dot inside, so a selected one reads as filled rather than merely darker. */
.task-row .row-gutter .task-pick:checked::after {
  content: '';
  position: absolute;
  left: 4px;
  top: 4px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #fff;
}

.task-row .row-gutter .task-pick:focus-visible {
  outline: 2px solid var(--c-sky, #0067a3);
  outline-offset: 2px;
}

/* Sky, matching the tick — a pink row behind a blue tick would put the
   confusion straight back. */
.task-row.is-picked,
.task-row.is-picked:hover { background: var(--c-sky-tint, #daf0ff); }



/* ---------------------------------------------------------------------------
   AN INSPIRATION PHOTO IN THE INBOX. 14 September, migration 109.

   Gee: "photos and images uploaded into the supplier… that'll be like
   inspiration, and I think that will help me a lot."

   ⚠️ THE PICTURE IS THE CARD. Every other inbox card leads with a supplier
   name because a PDF has nothing to look at; a photo has nothing BUT something
   to look at, and a filename is the least useful thing about it.
   --------------------------------------------------------------------------- */
.inbox-card.is-photo {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 14px;
  align-items: start;
}

.inbox-shot {
  width: 104px;
  height: 104px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--surface-soft);
  flex: none;
}

/* The kind chip on a photo is grape, not the sky the documents use — an
   inspiration photo is a different KIND of arrival, not a different document,
   and the colour is doing that job rather than decoration. */
.inbox-card.is-photo .inbox-kind {
  background: var(--c-indigo-tint);
  color: var(--c-indigo);
}

/* A receipt that arrived as a PNG comes down the photo route, because the route
   is chosen by the file type. It is not inspiration, so it must not wear
   inspiration's grape — it goes emerald, which is this app's colour for money,
   and a document that carries no amount goes stone, which is its colour for
   quiet. A NAB transfer screenshot was filed as "Make Up inspiration" before
   this, and the chip was half of why that looked plausible. */
.inbox-card.is-photo.is-doc .inbox-kind {
  background: var(--c-stone-tint);
  color: var(--c-stone);
}
.inbox-card.is-photo.is-doc.has-money .inbox-kind {
  background: var(--c-sage-tint);
  color: var(--c-sage);
}

/* A SCREENSHOTTED CONVERSATION WEARS THE FORWARDED ONE'S COLOUR, deliberately.
   Gee: "how are we reading all those photos that I sent across that were just
   conversations?" A WhatsApp exchange and a forwarded email thread are the same
   thing arriving two ways, and a third colour would say they were two features.
   Listed after the two above so it wins the tie on source order rather than on
   a specificity trick — all three selectors weigh the same. */
.inbox-card.is-photo.is-chat .inbox-kind {
  background: var(--c-teal-tint);
  color: var(--c-teal);
}

/* What was actually said, folded away. It is the longest thing on any card and
   the findings above it are the part she acts on — but a transcript she cannot
   reach is a summary she has to take on trust.
   ⚠️ NO display:flex ON THE SUMMARY. It deletes the disclosure triangle, which
   cost a change on 14 September. */
.inbox-transcript { margin-top: 10px; }
.inbox-transcript > summary {
  cursor: pointer;
  font-size: .82rem;
  font-weight: 600;
  color: var(--teal-deep);
}
.inbox-transcript > pre {
  margin: 8px 0 0;
  padding: 10px 12px;
  background: var(--surface-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-family: var(--sans);
  font-size: .84rem;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 320px;
  overflow-y: auto;
}

@media (max-width: 640px) {
  /* The thumbnail keeps its place and the buttons drop below, rather than the
     picture shrinking to a stamp on the screen where it matters most. */
  .inbox-card.is-photo { grid-template-columns: auto minmax(0, 1fr); }
  .inbox-card.is-photo .inbox-actions { grid-column: 1 / -1; }
  .inbox-shot { width: 78px; height: 78px; }
}


/* ---------------------------------------------------------------------------
   A FORWARDED EMAIL THREAD IN THE INBOX. 14 September.

   Gee: "Are we able to create an email instead of me having to PDF all of the
   emails to you like this, which takes a lot of time?"

   ⚠️ THIS CARD IS A BRIEFING, NOT A FILING FORM. Every other inbox card asks
   which wedding and which supplier and then gets out of the way. A thread's
   value is what it SAYS — reading one by hand found a promise worth $8,750 and
   a deposit unacknowledged for a fortnight — so the findings ARE the card.
   --------------------------------------------------------------------------- */
.inbox-card.is-thread .inbox-kind {
  background: var(--c-teal-tint);
  color: var(--c-teal);
}

.th-summary {
  margin: 0 0 10px;
  font-size: .9rem;
  color: var(--ink);
  max-width: 70ch;
}

.th-block {
  margin: 0 0 9px;
  padding: 9px 12px;
  border-left: 3px solid var(--b, var(--line));
  background: var(--b-tint, var(--surface-soft));
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.th-block > b { display: block; font-size: .82rem; color: var(--b); margin-bottom: 5px; }
.th-block ul { margin: 0; padding: 0; list-style: none; }
.th-block li { padding: 4px 0; font-size: .85rem; }

/* A promise is the most valuable thing a thread holds and it is the one thing
   a contract may not repeat — so it wears the colour of money, not of a note. */
.th-block.is-promise { --b: var(--c-sage);   --b-tint: var(--c-sage-tint); }
/* Something nobody has answered is a thing to chase, which is what tangerine
   means everywhere else in this app. */
.th-block.is-waiting { --b: var(--c-coral);  --b-tint: var(--c-coral-tint); }
.th-block.is-extra   { --b: var(--c-butter); --b-tint: var(--c-butter-tint); }
/* A document that arrived as a link and could not be read here wears magenta,
   which is this app's colour for "you". It is not late and it is not money —
   it is the one thing on the card that only Gee can finish, by clicking it. */
.th-block.is-link    { --b: var(--c-plum);   --b-tint: var(--c-plum-tint); }
.th-block.is-link a.th-what { color: var(--b); }

.th-what  { font-weight: 600; }
.th-worth { float: right; font-weight: 700; font-variant-numeric: tabular-nums; }
/* Their words, indented and quiet, so it reads as evidence rather than as the
   app's own claim about what was agreed. */
.th-quote {
  display: block;
  margin: 2px 0 0 0;
  padding-left: 10px;
  border-left: 2px solid var(--line);
  color: var(--ink-soft);
  font-size: .83rem;
}
.th-who { display: block; font-size: .76rem; color: var(--ink-faint); margin-top: 2px; }

/* ---------------------------------------------------------------------------
   THE PLACE CHIP AND THE HEADING'S "at X", IN THE PLACE'S OWN COLOUR
   17 September. They were `--indigo` for every venue, sitting inches from a tag
   that was already pink / blue / green / plum for the same place — which is
   exactly the "too confusing with all the colours" Gee described.

   ⚠️ SET ONLY. An unset "+ place" keeps the indigo and stays an ACTION, not a
   place. 61 of those on her three live weddings; colouring them would claim a
   venue on every line that has not got one.

   🛑 27 SEPTEMBER: BLACK WRITING, NO RING — the same as the tag on the right.
   See the note above `.tl-door.door-pink`. The ring is made TRANSPARENT rather
   than removed so the chip keeps its exact size and nothing on the row moves.
   Weight 700 to match the tag, which was already 700.
   --------------------------------------------------------------------------- */
.who-place.is-set.door-pink,  .tl-at.door-pink  { background: var(--pink-tint);    color: var(--ink); border-color: transparent; font-weight: 700; }
.who-place.is-set.door-blue,  .tl-at.door-blue  { background: var(--blue-soft);    color: var(--ink); border-color: transparent; font-weight: 700; }
.who-place.is-set.door-green, .tl-at.door-green { background: var(--c-sage-tint);  color: var(--ink); border-color: transparent; font-weight: 700; }
.who-place.is-set.door-plum,  .tl-at.door-plum  { background: var(--c-plum-tint);  color: var(--ink); border-color: transparent; font-weight: 700; }
.who-place.is-set.door-coral, .tl-at.door-coral { background: var(--c-peach-tint); color: var(--ink); border-color: transparent; font-weight: 700; }


/* The paperclip on a run sheet line. Quiet until there is something on it —
   then it carries the count, so a line holding a floor plan says so on the
   sheet rather than only once you open it. 17 September. */
.tl-add-link.has-files {
  color: var(--teal-deep);
  font-weight: 700;
}


/* ===========================================================================
   EVERY TICK BOX YOU HAVE TO FIND — 18 September 2026
   ---------------------------------------------------------------------------
   Gee: "anything that's got these buttons that I have to tick, I want them to
   be a different colour on the whole platform because I can't see it. It's all
   lost with all of the things that we've got going on."

   🛑 THE THING THAT MAKES THIS WORTH A SHARED RULE: `accent-color` PAINTS ONLY
   A BOX THAT IS ALREADY TICKED. It was set in eleven places, in five different
   colours, and NONE of it reaches an empty box — which is the one she is
   hunting for. Measured on her live page before writing this: 227 checkboxes,
   nine treatments, four sizes, and 91 of them native with no styling at all.

   So the EMPTY state is what this rule fixes, and it fixes it once.
   `--teal-deep` is 4.98:1 on white; the faint borders it replaces were 2.92:1
   and the browser default is fainter still.

   ⚠️ DELIBERATELY AT THE END OF styles.css, WHICH LOADS FIRST. That makes this
   a BASE every later file can still override — and two of them should:

     `input.task-check`  (checks.css) keeps its 19px and its PINK fill when
                         ticked. That is documented and deliberate: "finishing
                         something feels like finishing something." Its empty
                         border is fixed there, not here.
     `.is-soon` / `.is-late`  keep amber and red. Those carry meaning.

   ⚠️ NO SIZE FIGHT. Width and height are set to a modest 16px rather than
   forced, so anything that declares its own size later still wins and nothing
   on the page moves. `.task-row .row-gutter .task-pick` (0,3,0) and
   checks.css's 19px both beat this on purpose.

   Check 32 in CHECKS.md is the guard.
   ========================================================================= */
input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  border: 2px solid var(--teal-deep);
  border-radius: 4px;
  background: #fff;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease;
}

input[type="checkbox"]:checked {
  border-color: var(--teal-deep);
  background: var(--teal-deep);
}

/* The tick itself, drawn rather than a glyph so it lands the same everywhere.
   ⚠️ `content` must stay on the ::after or Safari renders nothing at all. */
input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  left: 4px;
  top: 0;
  width: 4px;
  height: 9px;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(43deg);
}

input[type="checkbox"]:disabled {
  border-color: var(--line);
  background: var(--surface-soft);
  cursor: default;
}

input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--pink);
  outline-offset: 2px;
}

/* The confirm dialog's message, and the one declaration that makes a confirm
   worth reading. Every bulk action in the app now NAMES the rows it is about
   to touch, one per line; without this they arrive as a single run-on
   paragraph, which is most of the way back to "6 hidden" — the message that
   told Gee nothing on 18 September. It lives here rather than inline on the
   element because index.html is in the service worker's shell and is the
   slowest file in the app to reach her browser. */
#confirm-message { white-space: pre-line; }

/* What is attached to a line of the day, on the couple's run sheet — 18 Sept.
   Gee: "Any attachment that's attached to the running sheet itself, they
   should be able to view it."

   ⚠️ NO NEW LOOK. The chips themselves are `.bg-file`, the same ones the
   couple's budget already uses, so a paperclip means the same thing on both
   of their screens. This rule only gives the row somewhere to put them —
   under the title and the names, above anything they can ask about. */
.tl-files {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
}

/* The paper-only copy of a paperclip count — see print.css and the note in
   timeline.js. It exists so the printed sheet has something that is NOT a
   <button> to show; on screen the real button is the one you press. */
/* Superseded 18 Sept by .tl-clip-tag - a visible tag prints, a hidden one
   never reliably did. Kept as a no-op so an old cached page does not show a
   stray count. */
.print-clip { display: none; }

/* ---------------------------------------------------------------------------
   "📎 1 file" ON A RUN SHEET LINE — 18 September.

   Gee, after four attempts at making a print-only paperclip appear in her
   PDF: "I do want a colour. If you don't want it to be coloured in, maybe a
   border, or the border saying '1 file attached', because I need to be able
   to tell."

   🛑 SO IT IS A VISIBLE TAG, NOT A HIDDEN ONE. Every previous attempt relied
   on something being display:none on screen and display:something on paper,
   and every one of them failed somewhere between the two. An ordinary
   visible element has nothing to toggle and therefore nothing to get wrong.

   ⚠️ TEAL IS NOT A NEW COLOUR. It is already what this app means by "there
   is a file" — .bg-file hovers teal and this count has been teal text since
   17 September. The fill is what separates it from the outlined supplier
   chip sitting next to it.
   --------------------------------------------------------------------------- */
/* 🛑 SOLID, NOT OUTLINED — AND THAT IS THE ANSWER TO "I CAN'T SEE IT".
   Gee, once it finally printed: *"I feel like it needs a different colour.
   You can't tell that it's there. The paperclip. You can't see it."*

   ⚠️ GRAPE, AND IT IS THE ONLY HUE ON THIS LINE THAT IS NOT ALREADY TAKEN.
   Teal was tried first and she was right to reject it: *"I can't see the
   colour clearly. It's too mixed up with everything."* The supplier chip is
   teal, and the place chips and the door tag use pink, plum, blue, green and
   coral — six of the app's colours are already on one row. `--c-indigo` is
   the seventh, it is hers, no door uses it, and it is dark enough to survive
   a mono printer. Her rule was "match every other colour there and just not
   go completely tangent": this is from the same set, not a new invention.

   ⚠️ TINT, NOT SOLID. It was solid grape for one deploy and her word for it
   was *"too harsh"* — a filled block of #4a00b0 shouts next to five soft
   outlined chips. The tint is how every other chip on this line is drawn:
   pale fill, coloured text, matching border. Keeping the HUE is what makes
   it findable; keeping the TREATMENT is what stops it shouting. */
.tl-clip-tag {
  display: inline-block;
  padding: 2px 10px;
  border: 1px solid var(--c-indigo);
  border-radius: 999px;
  background: var(--c-indigo-tint, #eae0ff);
  color: var(--c-indigo);
  font-size: .73rem;
  font-weight: 800;
  letter-spacing: .01em;
  white-space: nowrap;
  cursor: pointer;
}
.tl-clip-tag:hover { filter: brightness(1.12); }
.tl-clip-tag:focus-visible { outline: 2px solid var(--pink); outline-offset: 2px; }

/* The couple's copy of the same tag. An <a> rather than a span, because on
   their side each document opens directly instead of opening a drawer — but
   it must LOOK identical, or one file means two things depending on whose
   screen it is on. Gee, 18 September: "that colour doesn't come through on
   the couples." */
a.tl-clip-tag { text-decoration: none; }
a.tl-clip-tag:hover { filter: brightness(.97); text-decoration: none; }

/* A file whose link could not be signed. Visible as a problem rather than
   quietly absent — see the note in portal.js. */
.tl-clip-tag.is-dead {
  border-color: var(--line, #ece5e8);
  background: var(--surface-soft, #faf7f8);
  color: var(--ink-faint, #877f8b);
  cursor: default;
}

/* ---------- what to include on the supplier sheet -------------------------
   18 September. Gee: "Can you do it so that I can select what I want to show
   and what I don't want to show… so I don't have to constantly keep deleting
   things if I don't need that information?"

   🛑 THE PRINT-ONLY CELLS ARE HIDDEN HERE, UNCONDITIONALLY. They exist so the
   printed sheet can carry a column the screen table never had — Arrives,
   Notes, Driver and so on. The Suppliers tab she looks at must be exactly
   what it was, and she said so: "That must stay the way it is." print.css
   lets one through only on a supplier copy, and only when she has ticked it. */
.sup-print-only { display: none !important; }

.sup-cols-panel {
  margin: 0 0 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-soft);
  overflow: hidden;
}

.sc-head {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 16px;
  align-items: flex-start;
  justify-content: space-between;
  padding: 13px 16px;
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}

/* The name of the thing is bold — checks 17 and 22. */
.sc-title { font-weight: 700; font-size: .95rem; }
.sc-sub   { color: var(--ink-soft); font-size: .82rem; margin-top: 2px; }
.sc-sub.is-warn { color: var(--danger); font-weight: 600; }

.sc-presets { display: flex; flex-wrap: wrap; gap: 6px; }

.sc-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}

.sc-group {
  padding: 12px 16px;
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

/* ⚠️ The four she must not send are tinted as a group rather than marked one
   by one. A row of identical tickboxes with one quiet warning above them is
   how the wrong column goes out at eleven at night. */
.sc-group.is-private { background: var(--danger-tint); }
.sc-group.is-private .sc-group-name { color: var(--danger); }

.sc-group-name {
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 7px;
}

.sc-opt {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 4px 0;
  font-size: .86rem;
  cursor: pointer;
}

/* ⚠️ A WIDTH AND A HEIGHT, NOT accent-color ALONE. An unticked box painted
   only with accent-color is invisible — that is recorded in memory as
   `accent-color only paints a ticked box`, and this panel is nothing but
   empty boxes until she starts ticking. */
.sc-opt input {
  width: 16px;
  height: 16px;
  flex: none;
  accent-color: var(--pink);
  cursor: pointer;
}

.sc-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 16px;
  align-items: center;
  justify-content: space-between;
  padding: 11px 16px;
}

.sc-note { color: var(--ink-soft); font-size: .8rem; max-width: 62ch; }

@media (max-width: 640px) {
  .sc-group { border-right: 0; }
}

/* ---------------------------------------------------------------------------
   A checklist template, read in sections.

   Gee, 19 September, on the Settings template list: "It is unreadable."
   139 lines four boxes wide, with the heading retyped on every single row.
   Grouped, the same list is 17 headings that fit on one screen. Mocked up
   first and approved.

   ⚠️ `tpl-sec`, NOT `tpl-group` — that name is already taken by the
   "PROJECT TEMPLATES" heading blocks higher up this file, which style an
   `h3` and add a 22px margin. `css-class-name-collisions`.

   ⚠️ CHECKLIST TEMPLATES ONLY. A budget template is category and line; a
   timeline template is one day in time order. Neither has sections and
   neither is touched.
--------------------------------------------------------------------------- */
.tpl-sec {
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
  margin-bottom: 8px;
  background: var(--surface);
}

.tpl-sec-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 13px;
  background: var(--teal-tint);
  cursor: pointer;
  list-style: none;
}
.tpl-sec-head::-webkit-details-marker { display: none; }

.tpl-sec-head::before {
  content: '\25B8';
  font-size: .7rem;
  color: var(--teal-deep);
  transition: transform .15s ease;
}
.tpl-sec[open] > .tpl-sec-head::before { transform: rotate(90deg); }

/* The name of the thing is bold, and never lighter than the count beside it. */
.tpl-sec-name {
  font-weight: 700;
  font-size: .92rem;
  color: var(--ink);
}

.tpl-sec-count {
  margin-left: auto;
  font-size: .74rem;
  font-weight: 600;
  color: var(--ink-faint);
  white-space: nowrap;
}

/* The rows keep their own styling; this only insets them from the band. */
.tpl-sec .tpl-items {
  margin: 0;
  padding: 9px 11px 11px;
}

/* ---------------------------------------------------------------------------
   Shutting a budget category.

   Gee, 19 September, after the checklist template was grouped: "I think the
   budget would be good with headings and then the information below, in the
   same style that you've done the checklist."

   🛑 THE SUBTOTAL ROW STAYS WHEN THE GROUP IS SHUT, and that is the whole
   point of the feature rather than a detail. Nineteen categories shut is not
   nineteen hidden things — it is the budget on one screen, each category
   showing what it costs. Hiding the subtotal too would just be hiding.

   ⚠️ AND IT NEVER APPLIES TO PAPER. A category she shut while reading on
   Tuesday must not vanish from a printed budget on Wednesday. The print rule
   below puts every row back.

   ⚠️ `is-shut`, not `is-folded`: `data-checks-fold` and `foldedSections()`
   already mean something else in this app, and one idea must have one
   spelling.
--------------------------------------------------------------------------- */
tr.bg-row-shut { display: none; }

.bg-cat-fold {
  background: none;
  border: 0;
  cursor: pointer;
  padding: 0 4px;
  margin-right: 2px;
  font-size: .72rem;
  line-height: 1;
  color: inherit;
  opacity: .72;
}
.bg-cat-fold:hover { opacity: 1; }
.bg-cat-fold:focus-visible { outline: 2px solid var(--teal-deep); outline-offset: 2px; }

@media print {
  /* Everything prints, shut or not. */
  tr.bg-row-shut { display: table-row; }
  .bg-cat-fold { display: none; }
}

/* ---------------------------------------------------------------------------
   The quiet half of a section heading.

   Gee, 19 September, on a receipt filed under "Invoices and quotes": "if an
   invoice is in the budget line, why is the receipt in there?… Something is
   not right with this layout." The heading named two of the five kinds the
   list actually holds, so filing a receipt there looked like a mistake.

   ⚠️ IT NEEDS ITS OWN CLASS RATHER THAN `.hint`. `.form-section` is uppercase,
   pink and letter-spaced, and every one of those inherits — a plain hint would
   have rendered "INVOICES, QUOTES, RECEIPTS, CONTRACTS" shouting in pink.
   `.hint` is also only ever styled inside `.field`, so it would have arrived
   with no styling of its own to push back with.
--------------------------------------------------------------------------- */
.form-section-sub {
  display: inline;
  margin-left: 8px;
  color: var(--ink-faint);
  font-size: .72rem;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}

/* ---------------------------------------------------------------------------
   A note on a checklist task has to look like a note.

   Gee, 19 September: "when there are the details in the notes, I want that to
   be visible… I want it to be visible where you know that there's a note in
   there."

   It was already being printed. The fault was that it wore the SAME 12px
   --ink-faint as the assignee, the section and the due date, in the same
   dotted meta line — so on Tiffany & Liam, 87 tasks carrying a note looked
   identical to tasks with none.

   ⚠️ --ink-soft, THE SAME INK THE RUN SHEET USES FOR ITS NOTES (.tl-notes).
   Not a new colour: the app already had an answer for "this is a note, not
   metadata", and the two screens must not disagree about what a note looks
   like.

   ⚠️ THE MARKER IS A ::before, SO NOTHING CHANGES IN THE DATA OR THE MARKUP
   of a task without one. A row with no note is byte-for-byte what it was.

   ⚠️ AND IT STAYS INLINE. Giving every note its own line would add 87 lines
   to her list on one wedding — see .tl-notes if she asks for the fuller
   treatment later; the hook (.has-note) is already there.
--------------------------------------------------------------------------- */
.task-meta .task-notes {
  color: var(--ink-soft);
}

.task-meta .task-notes::before {
  content: "\01F4DD";
  margin-right: 5px;
  /* Quieter than the text it introduces: it is a signpost, not the message. */
  opacity: .75;
  font-size: .9em;
}

/* ---------------------------------------------------------------------------
   Assign a task with one press.

   Gee, 19 September: "I just want to be able to press the button of that
   couple." The box and its type-ahead stay — these sit above it, built from
   knownAssignees(), which had been working this out all along and never
   showing her.
--------------------------------------------------------------------------- */
.quick-names {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-bottom: 6px;
}

.quick-name {
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink-soft);
  border-radius: 20px;
  padding: 3px 11px;
  font: inherit;
  font-size: .78rem;
  cursor: pointer;
}
.quick-name:hover { border-color: var(--teal-deep); color: var(--ink); }

/* Already on the task. Pressing it again takes it off, so it has to look
   pressed rather than disabled. */
.quick-name.is-on {
  background: var(--teal-tint);
  border-color: var(--teal-deep);
  color: var(--teal-deep);
  font-weight: 600;
}

/* ---------------------------------------------------------------------------
   The task window's buttons sit at the left, under the form.

   Gee, 19 September: "I think the Done button and the Close button should be
   below, so we don't have to go to the right-hand side."

   ⚠️ SCOPED TO #task-modal. `.spacer` appears in 28 modals in index.html and
   every one of them right-aligns its footer; changing the class itself would
   move the buttons on all of them, which nobody asked for.
--------------------------------------------------------------------------- */
#task-modal .modal-foot .spacer { display: none; }
#task-modal .modal-foot { justify-content: flex-start; }
/* The "Saved 4:12 pm" stamp keeps its distance from the buttons now that it
   is no longer pushed against them by the spacer. */
#task-modal .modal-foot .save-stamp { order: 2; }

/* ---------------------------------------------------------------------------
   THREADS — the same job, asked for at five different meetings

   Gee, 19 September: "It would be awesome if there's a way of being able to
   combine it so that it's all still there, and then I can see that I've asked
   5 times for something, and it's still not done."

   🛑 A THREAD ROW IS A CONTAINER, NOT A TASK, AND IT HAS TO LOOK LIKE ONE.
   It carries no done-tick and no star, because it cannot be ticked — it is
   done when every line in it is done. So it opens with a fold arrow where the
   tick would be, and sits on a faint band, so the eye reads "there is more
   under here" before it reads the words.

   ⚠️ EVERY CHILD IS GIVEN AN `order`, INCLUDING 0. The block at line 9916
   says why in its own words: *"a flex item left at the default 0 would jump in
   front of anything numbered."* Here that is what is wanted for the arrow and
   ruinous for anything else.

   ⚠️ NOTHING HERE TOUCHES `.task-row` ON ITS OWN. Every selector carries
   `.is-thread` or `.thread-kids`, so a checklist with no threads on it is
   identical to the character — `add-it-do-not-rebuild-it`.
--------------------------------------------------------------------------- */
.task-row.is-thread {
  background: var(--surface-soft);
  align-items: center;
}
.task-row.is-thread:hover { background: var(--c-sky-tint); }

/* Where the done-tick would be. Explicitly 0, not "left alone" — see above. */
/* 🛑 A BUTTON SHE CAN SEE, 22 Oct (CHECK 270). It was a 12px grey triangle,
   and once the date column moved left it sat UNDER the group's date span.
   Gee: "You can't see where the line is to open it up." Her pick, B: bigger,
   in the light sky box the groups already use. */
.task-row.is-thread > .thread-fold {
  order: 0;
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: var(--c-sky-tint);
  color: var(--c-sky);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.task-row.is-thread > .thread-fold:hover {
  background: var(--c-sky);
  color: #fff;
}
.task-row.is-thread > .thread-fold:focus-visible {
  outline: 2px solid var(--c-sky);
  outline-offset: 1px;
}

/* The name is the row. Clicking anywhere on it folds, so the control is the
   whole line rather than a 12px triangle. */
.task-row.is-thread .task-title { cursor: pointer; }

/* 🛑 IT IS A FRACTION NOW, AND NOTHING ELSE. 21 September 2026.
   ~~"asked 5 times" beside "still not done" is the whole sentence she asked
   for; either half alone is the number without the point of it.~~ STRUCK OUT,
   not deleted, so the next reader knows it was overturned rather than missed.

   Gee, 21 September, reading "asked 2 times - 1 of 2 done": "that's like us
   2 x 1 or 2 done. I don't like that." She was right — members.length was
   printed TWICE in one sentence, so one fact arrived as two numbers and the
   line scanned as arithmetic.

   Then: "I think maybe having the 1 of 2 or whatever is good, but besides
   that, I don't think we need to have it like that."

   NOTHING IS LOST. The "2" in "1 of 2" IS the number of asks — one line per
   ask — so the fraction already says everything the long sentence said.
   Her intent from 19 September is intact; only the wording is overturned.

   ⚠️ AND IT IS NO LONGER A WARNING COLOUR. It was --danger on --warn-tint,
   which is crimson — the colour that, since 21 September, means "already
   late" on this very row. Progress is not urgency.

   🛑 NAVY, AND IT IS THE ONLY COLOUR THAT WAS FREE. Gee asked for something
   other than grey, and every other slot in this palette is spoken for:
   crimson is late, amber is due this week, emerald is done and paid, magenta
   is the brand and a ticked box, and mint, sky and grape are the celebrancy,
   suppliers and couple CATEGORIES. Borrowing any of them would put two
   meanings on one colour, which DECISIONS.md section 4 forbids.

   Blue was free because blue is STRUCTURE — "headings, rules, the spine of a
   table" — and this chip is structure: it says how many lines the row stands
   for, not how the work is going.
   `the-app-already-has-a-colour-for-that`.

   🛑 AND A CORRECTION, MEASURED AFTER IT SHIPPED. I told Gee navy would match
   the section band above it. IT DOES NOT, and the band was never --blue: a
   later rule wins, `.task-group:not(.is-meeting) > .task-group-head`, which
   paints it --c-sky #0067a3, and the Meetings band is --c-indigo #4a00b0.
   The bands are CATEGORY-coloured; the chip is not. Reading the rule I had
   grepped rather than the rendered colour is what produced the wrong claim —
   `ask-the-app-what-it-draws`.

   ⚠️ SO THERE ARE NOW THREE BLUES ON THIS SCREEN: sky bands, grape bands and
   navy chips. Checked on the rendered page rather than assumed, and they do
   not collide — the bands are full-width filled bars in uppercase and the
   chip is an 40px pill on a white row, which is the shape rule DECISIONS.md
   section 4 relies on. **If a fourth blue is ever added, this stops being
   true.**

   ⚠️ MEASURED BEFORE IT WAS CHANGED: 7 threads live, FIVE of them two asks.
   Seven chips all shouting is why Styling Reception — four asks, nothing
   done — looked identical to the five that were fine.

   Contrast measured, not guessed: #1e3f7d on #eef2fa is 9.08:1 and the done
   state #00695c on #d9f7f2 is 5.84:1. Both clear the 4.5:1 that 11px bold
   needs.

   ⚠️ THE DONE STATE IS STILL MINT, AND MINT IS THE CELEBRANCY CATEGORY
   COLOUR RATHER THAN A "DONE" ONE — emerald, --ok, is the real one. Raised
   with her 21 September and she asked only for the blue, so it stays. It has
   never appeared on screen: none of her seven threads is finished. */
.thread-chip {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 20px;
  background: var(--blue-soft);
  color: var(--blue);
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
  vertical-align: 1px;
  font-variant-numeric: tabular-nums;
}
.thread-chip.is-done {
  background: var(--c-teal-tint);
  color: var(--c-teal);
}

/* 🛑 ON THE SAME LINE AS THE NAME. Gee, 21 September: "That part's a bit
   messy. I'd rather that be somewhere else."

   The chip is inline-block, but .task-title is display:block, so the chip
   always wrapped underneath it. Measured on Pardesay: a thread row was
   59.5px against 36.7px for every other row — 62% taller, on SEVEN of her
   21 rows, so one row in three broke the rhythm of the page.

   Making .task-main a flex row puts them side by side and brings the row
   back to 37px. The margin-left above is gone because the gap does it now.

   ⚠️ SCOPED TO .is-thread, so an ordinary task row is untouched.
   ⚠️ min-width:0 and the ellipsis are load-bearing: without them a long
   thread name refuses to shrink and pushes the chip off the row. */
.task-row.is-thread .task-main {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}

.task-row.is-thread .task-main > .task-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.task-row.is-thread .task-main > .thread-chip { flex: 0 0 auto; }

/* The span of the asking, in the date column, so it lines up with every other
   date down the page. */
.task-row.is-thread .thread-span {   /* inside .row-when since 22 Oct (CHECK 238) */
  color: var(--ink-faint);
  font-size: 11.5px;
  min-width: 118px;
}

/* The lines themselves, stepped in under their thread and held by one rule
   down the left so it is obvious where the thread ends. */
.thread-kids {
  list-style: none;
  margin: 0;
  padding: 0 0 0 26px;
  border-left: 2px solid var(--c-sky, #0067a3);
  margin-left: 14px;
  background: var(--surface-soft);
}
.thread-kids > .task-list { margin: 0; padding: 0; }

/* ⚠️ A LINE INSIDE A THREAD IS STILL A WHOLE TASK — its tick, star, gutter,
   comment flag and date all work exactly as they do anywhere else. The only
   thing that changes is where it sits. */
.thread-kids .task-row:last-child { border-bottom: 0; }

/* On a phone the step-in has to give way to the row, which is already tight. */
@media (max-width: 620px) {
  .thread-kids { padding-left: 10px; margin-left: 6px; }
  .task-row.is-thread .thread-span { min-width: 0; }
}

/* ===========================================================================
   THE RUN SHEET ON A PHONE — 21 September 2026

   Gee, looking at Pardesay on her phone: "why does my phone layout look like
   this now?" It was not new. The row has been one flat flex line since
   20 August and the only phone rule it has ever had is `.tl-time` dropping
   from 86px to 64px, further up this file.

   MEASURED, on touch, at a 390px phone — every one of these is a fixed width
   that never yields:

       tick box                    16
       the gutter (+ and the menu) 54
       the venue tag               84
       three 16px gaps             48
       row padding                 20
       the time column             64
       ------------------------------
       spoken for                 286   →  about 104px left for the job title

   A hundred pixels is eight or nine characters, which is why "Make-Up start"
   came out as three lines and the supplier pills wrapped one word at a time.

   ⚠️ THE FIRST VERSION OF THIS NOTE SAID 118px AND 40px. That was the DESKTOP
   gutter — all five buttons. `@media (hover: none)` above already drops the
   grip, the arrows and the bin, so her phone carries two buttons, not five.
   Measure the state the fault is IN.

   So: the row wraps. Line one is the tick, the time, the venue tag and the
   menu; the whole of `.tl-main` — title, supplier, place, notes — goes onto
   line two with the full width of the phone.

   ⚠️ ORDER IS SET ON ALL FIVE CHILDREN, not on the two being moved. Nothing in
   `.tl-row` set `order` before, so an unnumbered child would jump the queue —
   that is `order-zero-is-not-leave-me-where-i-am`, which cost an afternoon.

   ⚠️ `.tl-door` NEEDS `button.` TO WIN. `button.tl-door` further up is (0,1,1)
   and sets its own properties; the scoped `.tl-row > button.tl-door` here is
   (0,2,1) and beats it.

   Nothing changes on the desktop, and nothing changes on the printed sheet —
   print.css hides the gutter entirely and never sees this block.
   =========================================================================== */
@media (max-width: 620px) {
  .tl-row {
    flex-wrap: wrap;
    row-gap: 5px;
    column-gap: 10px;
    align-items: center;
  }

  .tl-pick   { order: 1; }
  .tl-time   { order: 2; flex: 0 1 auto; }
  .tl-gutter { order: 4; align-items: center; }

  /* Pushed to the right-hand end of the first line, where it has always sat. */
  .tl-row > button.tl-door,
  .tl-row > .tl-door {
    order: 3;
    margin-left: auto;
    align-self: center;
  }

  /* The whole point: the words get the width of the phone. */
  .tl-row > .tl-main { order: 5; flex: 1 0 100%; }
}

/* ---------------------------------------------------------------------------
   Couple's details, from the wedding header — 21 September 2026

   Gee asked twice: "I asked you to create it so that I can access it very
   quickly as well", then "I want it easier to find the couple's details."

   The block has existed since 12 September, but only on the Suppliers toolbar
   as the 7th of 12 buttons at 12.5px, which is why she went looking and could
   not see it. This is a second door, not a replacement — that button and that
   toolbar are untouched.

   ⚠️ NOT A MODAL, DELIBERATELY. She opens this mid-task from whatever tab she
   is on; a dialog would take the screen away from the thing she is copying it
   FOR. It drops under the header and leaves the page where it was.

   ⚠️ EVERY COLOUR IS A TOKEN. `--surface`, `--line`, `--ink*`, `--radius-sm`
   and `--shadow` already exist; a literal here would be the one thing that
   looks wrong the day the palette moves.
--------------------------------------------------------------------------- */
.cd-panel {
  flex: 1 0 100%;              /* its own row under the header, which wraps */
  margin-top: 12px;
  max-width: 460px;
  padding: 14px 16px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
}

.cd-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}

.cd-title {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.cd-actions { display: flex; gap: 6px; }

/* "Not all of this couple's details are here" — 15 October. Pink, as on the
   mock-up she said yes to (claude.ai/artifact/Px7zKjHBiNtMMji755DZ9L). */
.cd-missing {
  margin: 0 0 10px;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  background: var(--c-plum-tint);
  color: var(--c-plum);
  font-size: 13px;
  font-weight: 700;
  line-height: 1.45;
}

/* ⚠️ `pre` INHERITS NOTHING — it arrives with a monospace default, its own
   margin and no wrapping. All three are set here, and `white-space: pre-wrap`
   is what stops a long billing address running off the side on a phone. */
.cd-body {
  margin: 0;
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 12.5px;
  line-height: 1.65;
  color: var(--ink);
  white-space: pre-wrap;
  word-break: break-word;
}

@media (max-width: 620px) {
  .cd-panel { max-width: none; }
}

/* ---------------------------------------------------------------------------
   The three numbers, across the top of Key details — 21 September 2026

   Gee: "I just want the bride's, mine, and the groom's phone numbers easily
   accessible. I think Key Details would be good."

   ⚠️ IT LIVES INSIDE `.tl-details`, WHICH IS A GRID, so it has to span the
   whole row or it becomes a fourth column and the strip breaks apart.
--------------------------------------------------------------------------- */
/* 🛑 GOLD, AND THE APP'S OWN FONT — 27 September 2026.
   Gee: "I don't like the font" (it was Consolas, the code font, and nothing
   else in Key details used it), then "a different color so it stands out.
   It's a little washed out." Five colours were put in front of her; she
   picked the BUDGET GOLD — the exact three values of the balance box in
   checks.css (`.bg-stat.is-balance`), so it is not a new colour.
   Purple was offered first and rejected: "the purple doesn't go with it."

   THEN, THE SAME DAY, ONE SLIM LINE AT THE BOTTOM, ALL IN BLACK. Gee, seeing
   it live on Solin & Rony: "Phone number is too big, and the other
   information looks like it's gotten smaller… the information is much more
   important than the phone numbers." Then: "it can go on the bottom… my name
   first… just have contact numbers there… the yellow box, yes, but the font
   should be a different color, maybe black."

   So: smaller than the details (.88rem against their .97rem), weight 600
   against their 600, and every word in it black — no gold-brown, no teal. */
.tl-kd-phones {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 24px;
  padding: 7px 12px;
  border-left: 4px solid #e8c84a;
  border-radius: var(--radius-sm);
  background: #fff7d6;
  color: #000;
  font-size: .88rem;
}

.tl-kd-phones .kd-lead,
.tl-kd-phones .kd-who {
  font-size: .72rem;
  font-weight: 800;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: #000;
}

.tl-kd-phones .kd-who { margin-right: 6px; }

/* 🛑 TWO STRAIGHT COLUMNS — 21 October, CHECK 230. Gee, on Tiffany's PDF:
   "you can see it looks very messy, right?" — the numbers ran along the line
   and started somewhere different on every row. She picked B from three
   drawn on her page: two columns, every number starting in the same line.
   Print AND computer screens (her rule: the PDF matches the screen); a phone
   keeps the flowing line, two columns do not fit there. `display: contents`
   lets each name and number sit in the grid's own columns. */
/* ⚠️ AND COMPRESSED, THE SAME MORNING. On paper the Reception arrival card
   fell to page 2 (7px short). Gee: "compress the golden phone number area".
   "Contact numbers" moves to its own left-hand column instead of a row of its
   own, and the spacing tightens: 97px → 68px on Tiffany's page. Each pair is
   pinned to column 2–3 or 4–5 by position (the lead is child 1, so pairs are
   children 2, 3, 4…), so the lead never gets a second row's pair under it. */
@media print, screen and (min-width: 621px) {
  .tl-kd-phones {
    display: grid;
    grid-template-columns: auto auto 1fr auto 1fr;
    gap: 2px 12px;
    padding: 5px 12px;
    align-items: baseline;
  }
  .tl-kd-phones .kd-lead { grid-column: 1; grid-row: 1; padding-right: 6px; }
  .tl-kd-phones .kd-one { display: contents; }
  .tl-kd-phones .kd-who { margin-right: 0; }
  .tl-kd-phones .kd-one:nth-child(even) .kd-who { grid-column: 2; }
  .tl-kd-phones .kd-one:nth-child(even) .kd-num { grid-column: 3; }
  .tl-kd-phones .kd-one:nth-child(odd) .kd-who  { grid-column: 4; }
  .tl-kd-phones .kd-one:nth-child(odd) .kd-num  { grid-column: 5; }
}

/* 🛑 NO "NEXT DAY" ON THE ARRIVAL CARDS — 21 October, her word: "I'm happy for
   you to get rid of the 'next day' line under 'signature chauffeur' if that
   helps." It was a whole line under every after-midnight time on the cards at
   the top, and it pushed Tiffany's Reception card onto page 2. The in-sheet
   blue bar card keeps its inline "next day" (print.css .arr-card.is-bar). */
.arrivals-groups .arr-next { display: none !important; }

/* 🛑 THE BLUE "SUPPLIER ARRIVALS" BOX, TWO SIDES — 21 October, CHECK 231.
   Gee, on Tiffany's Metropolis box (10 arrivals) and Bride's home (11): "we
   can split that to two sides… same as the bride's house… compact things a
   little bit more." She said yes to the drawing: five or more arrivals read
   down the left, then down the right; fewer stay one list (three split reads
   oddly). Screen and paper alike; a phone keeps one list. Metropolis on
   Tiffany's paper: 233px → 131px. */
@media print, screen and (min-width: 621px) {
  .arr-card.is-bar:has(.arr-card-list > li:nth-child(5)) .arr-card-list {
    columns: 2;
    column-gap: 24px;
  }
  .arr-card.is-bar .arr-card-list > li { break-inside: avoid; }
}

.tl-kd-phones .kd-num {
  font-family: var(--sans);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: #000;
  text-decoration: none;
}

.tl-kd-phones .kd-num:hover { text-decoration: underline; }

/* One tap on a phone, and 44px is the real target size — a 17px number was
   shipped once before and could not be hit. See `a-control-must-be-visible-
   and-hittable`. */
@media (hover: none) {
  .tl-kd-phones { gap: 4px 20px; }
  .tl-kd-phones .kd-num { display: inline-block; min-height: 30px; line-height: 30px; }
}

/* ---------------------------------------------------------------------------
   Venue key details — the night's moments, 27 September 2026

   Gee: "we definitely need a venue one because people won't always refer back
   to the top." It borrows `.tl-header` and `.tl-details` whole, so it is the
   same teal bar and the same grid as Key details and reads as its pair.
   `make-it-look-like-that-means-reuse-those-classes`.

   NA is Key details' own red (`.is-none`, already styled). TBC is new: a line
   that is on the run sheet without a time yet is not "not happening", so it
   must not wear the same red. Tangerine, the app's `--c-peach`.
--------------------------------------------------------------------------- */
/* 🛑 HOT PINK, HER BRAND. Gee, 27 September, from five options: "the hot
   pink for your brand's number one". It must NOT look like Key details — "it
   should be a bit different to the other one" — so the bar, the frame and the
   labels all take the brand's deep pink, the same `--pink-deep` the Add
   buttons wear. Teal (Key details), navy (arrivals), red (requests), orange
   (findings) and gold (contact numbers) were already taken on this tab. */
/* 🛑 ROYAL BLUE (#1d4ed8), 30 SEPTEMBER, AFTER A BRIEF INDIGO. On the live sheet the indigo read purple and "a bit hard to read": "I'd much rather it be a royal blue". Chosen against the measured neighbours — the in-day arrival boxes are #0067a3 and the top arrivals block navy #1e3f7d; royal is brighter than both. EARLIER NOTE: INDIGO SINCE 30 SEPTEMBER. Gee: "this pinky colour is not the best colour because there's a lot of pink" — then chose the app's own indigo off a mock-up beside Tiffany's real neighbours ("It's like a blue, right?"). The only bright colour left with no meaning on the run sheet. Was --pink-deep. */
.tl-header-night { margin-top: 0; background: #1d4ed8; }
/* ↑ ↓ Put back Edit sit together on the right (30 Sept) — `space-between`
   alone would spread four buttons across the bar. */
.tl-header-night { justify-content: flex-start; gap: 8px; }
.tl-header-night h3 { margin-right: auto; }

/* Drawn INSIDE the run sheet list, above the first evening moment (Gee,
   27 September). An <li> with room above and below so it reads as a block the
   day runs through, not as one more line. */
.tl-night-li { list-style: none; padding: 18px 0 14px; }

/* 🛑 KEY DETAILS TIMES FROM THE RUN SHEET — 30 September. A time the app only
   SUGGESTED draws grey and italic with "suggested — confirm in Edit" under it:
   never presented as fact. Where the line differs from what she typed, the
   typed time sits under it in tangerine (the app's --c-peach) so nothing moves
   silently. */
#tl-details .tl-detail.is-suggested .v { color: #8a7d84; font-style: italic; font-weight: 600; }
#tl-details .kd-sug,
#tl-details .kd-was { display: block; font-size: .68rem; line-height: 1.3; }
#tl-details .kd-sug { color: #8a7d84; }
#tl-details .kd-was { color: var(--c-peach, #b34e00); font-weight: 700; }

/* The picker rows at the top of the Key details form. */
.kd-lines { display: grid; gap: 6px; margin: 6px 0 12px; }
.kd-line { display: grid; grid-template-columns: 150px minmax(0, 1fr) auto auto; gap: 4px 10px; align-items: center;
  padding: 6px 8px; border: 1px solid var(--line, #ece5e8); border-radius: 8px; }
.kd-line select { min-width: 0; width: 100%; }
.kd-line-k { font-weight: 700; font-size: .85rem; }
.kd-line-status { font-size: .75rem; font-weight: 700; white-space: nowrap; }
.kd-line.is-ok .kd-line-status { color: var(--c-sage, #00733d); }
.kd-line.is-sug select { color: #8a7d84; font-style: italic; }
.kd-line.is-sug .kd-line-status { color: #8a7d84; }
.kd-line.is-bad .kd-line-status { color: #c4002f; }
.kd-line.is-off .kd-line-status { color: var(--muted, #6c6067); }
.kd-line-typed { grid-column: 2 / -1; font-size: .72rem; color: var(--muted, #6c6067); }
.kd-line.is-na { grid-template-columns: 150px 1fr; }
.kd-line-na { font-size: .8rem; color: var(--muted, #6c6067); }
@media (max-width: 620px) {
  .kd-line { grid-template-columns: 1fr auto; }
  .kd-line select { grid-column: 1 / -1; }
}
#tl-night { border-color: #1d4ed8; }
/* 🛑 THE WORDS BLACK, THE TIMES IN THE BOX'S COLOUR — BOTH BOXES. Gee,
   27 September: "the times have to be in that pink, but the words need to be
   in the black… Same above, because I think it's just too hard to read this
   information with too much blue and too much pink in the heading details for
   both of the key sections."

   Scoped by ID to her run sheet's two boxes. The couple's portal draws its own
   Key details with the bare classes and is left exactly as it was.
   ⚠️ NA and TBC keep their meaning-colours: an ID rule would otherwise
   outrank `.tl-detail.is-none` and paint a confirmed absence teal or pink. */
#tl-details .tl-detail .k,
#tl-night .tl-detail .k { color: #000; }
#tl-details .tl-detail .v { color: #00565e; }
#tl-night .tl-detail .v { color: #1d4ed8; }
#tl-details .tl-detail.is-none .k, #tl-details .tl-detail.is-none .v,
#tl-night .tl-detail.is-none .k, #tl-night .tl-detail.is-none .v { color: var(--absent); }
#tl-night .tl-detail.is-tbc .v { color: var(--c-peach, #b34e00); }

.tl-detail.is-tbc .v { color: var(--c-peach, #b34e00); }

.tl-night-rows {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  margin-top: .6rem;
}
/* One row per moment, in the order of the night — Gee, 1 October: the old two
   columns of cut-off drop-downs were "way too hard to read". */
.tl-nr {
  display: grid; grid-template-columns: 150px 70px minmax(0, 1fr) auto;
  gap: 4px 12px; align-items: center;
  padding: 9px 0; border-bottom: 1px solid var(--line, #e3e8f7);
}
.tl-nr[hidden] { display: none; }
.tl-nr-name { font-weight: 800; font-size: .78rem; letter-spacing: .05em; text-transform: uppercase; color: #000; margin: 0; }
.tl-nr-time { color: #1d4ed8; font-weight: 700; font-variant-numeric: tabular-nums; }
.tl-nr-time.is-na { color: var(--absent, #ef0000); }
.tl-nr-time.is-tbc { color: var(--c-peach, #b34e00); }
.tl-nr-from { min-width: 0; font-size: .9rem; color: var(--ink, #121a33); overflow-wrap: anywhere; }
.tl-nr-from small { display: block; font-size: .72rem; color: var(--muted, #6c6067); }
.tl-nr-chg {
  border: 1.5px solid #1d4ed8; color: #1d4ed8; background: #fff;
  border-radius: 8px; padding: 4px 12px; font-weight: 700; font-size: .78rem; white-space: nowrap; cursor: pointer;
}
.tl-nr-chg:hover, .tl-nr-chg:focus-visible { background: #1d4ed8; color: #fff; }
.tl-nr-pick { grid-column: 1 / -1; background: #eef3ff; border-radius: 8px; padding: 8px 10px; }
.tl-nr-pick[hidden] { display: none; }
.tl-nr-pick select { width: 100%; }
.tl-nr-sect { font-weight: 800; font-size: .76rem; letter-spacing: .06em; text-transform: uppercase; color: #db2777; padding: 14px 0 6px; }
.tl-nr-gone { display: flex; flex-wrap: wrap; gap: 8px; padding-bottom: 4px; }
@media (max-width: 560px) {
  .tl-nr { grid-template-columns: 1fr auto; }
  .tl-nr-from { grid-column: 1 / -1; }
}
.tl-night-row select { width: 100%; }
.tl-night-row .hint-static { font-size: .88rem; color: var(--muted); padding: 6px 0; }

/* The ✕ on each Venue key details moment, and the "Removed from this box"
   strip under it — Gee, 1 October, Option A. Screen only (print.css). */
/* ⚠️ THE ✕ SITS STRAIGHT AFTER ITS OWN NAME. Pinned to the right edge of the
   cell (first version), it landed beside the NEXT moment's name — Gee: "why is
   there one X on its own? It's just not right." */
.tl-night-x {
  display: inline-grid; place-items: center; vertical-align: middle;
  width: 20px; height: 20px; padding: 0; margin-left: 6px; border-radius: 50%;
  border: 1.5px solid #db2777; background: #fff; color: #db2777;
  font-size: .65rem; font-weight: 800; line-height: 1; cursor: pointer;
}
.tl-night-x:hover, .tl-night-x:focus-visible { background: #db2777; color: #fff; }
.tl-night-removed {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 8px; font-size: .85rem; color: var(--muted);
}
.tl-night-back {
  border: 1.5px solid #db2777; background: #fff; color: #db2777;
  border-radius: 999px; padding: 3px 12px; font-size: .82rem; font-weight: 700; cursor: pointer;
}
.tl-night-back:hover, .tl-night-back:focus-visible { background: #db2777; color: #fff; }

/* ---------------------------------------------------------------------------
   Key details on her run sheet: the places as a list, the times underneath
   27 September 2026 — option B

   Gee rejected the card layout (1A) on sight: "way too squashy… it's
   confusing". Its mock-up had been drawn wider than her real box (1,168px,
   measured). Option B was drawn AT that width and she chose it: each place on
   one line — label, the place in teal, the address in dark text beside it —
   then the times in the four-across grid she already knew. `.is-ww` turns the
   old grid off for the box itself; the times bring their own grid.
--------------------------------------------------------------------------- */
#tl-details.is-ww { display: block; }

.kd-rows { border-top: 1px solid #edf2f3; }
.kd-row {
  display: grid;
  grid-template-columns: 230px minmax(0, 1fr);
  gap: 4px 16px;
  align-items: baseline;
  padding: 10px 0;
  border-bottom: 1px solid #edf2f3;
}
.kd-k {
  font-size: .8rem;
  font-weight: 800;
  letter-spacing: .035em;
  text-transform: uppercase;
  color: #000;
}
.kd-place-v { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 14px; }
.kd-name { font-size: .97rem; font-weight: 600; color: #00565e; }
.kd-addr { font-size: .97rem; font-weight: 500; color: #2a2126; }
.kd-row.is-none .kd-k,
.kd-row.is-none .kd-name { color: var(--absent); }

.kd-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 15px 22px;
  margin-top: 18px;
}

#tl-details.is-ww .tl-kd-contact { margin-top: 16px; }

/* A phone: the label goes above its place, and the times drop to two across. */
@media (max-width: 640px) {
  .kd-row { grid-template-columns: 1fr; }
  .kd-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---------- Files box: new in orange, read in purple ----------------------
   Gee, 2 October 2026: "when the paper comes through first and it hasn't been
   read… like new… then when it gets read and ticked through, it changes to
   purple." Mock-up approved the same morning. budgetfiles.js docState() decides
   the state; Tiffany & Liam carry none of these classes. Bright only — her
   no-grey rule. CHECK 119. */
.file-row.bf-st-new,
.file-row.bf-st-todo { border: 2px solid #ff8a1f; background: #fff0dc; }
.file-row.bf-st-read { border: 2px solid #8b3dff; background: #f1e8ff; }

/* The supplier card's rows, same states (2 Oct, CHECK 122). */
/* ⚠️ THREE CLASSES DEEP: `.sf-row.is-internal` in checks.css loads later and
   would win a tie on every "Planner only" document. */
.sf-item .sf-row.bf-st-new,
.sf-item .sf-row.bf-st-todo { border: 2px solid #ff8a1f; background: #fff0dc; border-radius: 8px; }
.sf-item .sf-row.bf-st-read { border: 2px solid #8b3dff; background: #f1e8ff; border-radius: 8px; }

.bf-chips { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.bf-chips:empty { display: none; }
.bf-chip {
  display: inline-block;
  padding: 1px 9px;
  border-radius: 999px;
  font-size: .72rem;
  font-weight: 800;
  letter-spacing: .03em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #fff;
}
.bf-chip.is-new  { background: #ff8a1f; }
.bf-chip.is-todo { background: #ff8a1f; }
.bf-chip.is-read { background: #8b3dff; }

/* The paperclip on the budget row, while a document on it waits for her. */
.tl-clip-tag.is-new { border-color: #ff8a1f; background: #fff0dc; color: #c25a00; }

/* The payment plan read off the document. */
.bf-plan {
  margin-top: 12px;
  padding: 10px 12px;
  border: 2px solid #ff8a1f;
  border-radius: 10px;
  background: #fff;
}
.bf-plan-head { margin: 0 0 6px; font-weight: 800; font-size: .88rem; color: #c25a00; }
.bf-plan-table { border-collapse: collapse; width: 100%; font-size: .86rem; font-variant-numeric: tabular-nums; margin-bottom: 8px; }
.bf-plan-table td { padding: 3px 6px; border-bottom: 1px solid #ffd9b0; }

/* Only a different NUMBER is a warning (2 Oct, CHECK 122): a different account
   NAME on the same BSB and account is quiet lavender, never red. */
.bf-bank-tag.is-name { background: #f1e8ff; color: #6a2bd9; }

/* Key details places look like a drop-down (2 Oct, CHECK 125): a caret drawn
   inside the box, never a sibling element. Not on Tiffany & Liam.
   ⚠️ [type="text"] ON PURPOSE: bloom.css loads later with input[type="text"]
   and a `background:` shorthand, and wins a tie — measured on the live box. */
input.kd-place-pick[type="text"] {
  border-color: #3f7b80;
  padding-right: 28px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%233f7b80'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  cursor: pointer;
}
.kd-place-results { margin-top: 4px; }

/* ---------- Edit details as ONE LIST (2 Oct, CHECK 126) --------------------
   Places first, then every time, one row each, one look. Only under
   .kd-onelist, which the finished weddings never get. Orange = still needed,
   purple = set (the Files box colours); a suggestion keeps its grey + Confirm. */
#timeline-details-modal.kd-onelist .kd-old { display: none; }
/* PURPLE = DONE, ORANGE = NEEDS YOU, said four ways (2 Oct, mock-up
   PcnTQmzQ2z34ikEtKkXjVH, "I love this one"): two counts on top, a ✓ / ! on
   every row, words under an orange row's name, and one solid orange button
   for "do this". Purple rows sit back; orange rows stand forward. */
.kd-prog { display: grid; gap: 8px; margin-bottom: 4px; }
.kd-sum { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; }
.kd-tile { display: flex; align-items: center; gap: 12px; padding: 10px 14px; border-radius: 14px; border: 1.5px solid; }
.kd-tile b { font-size: 1.7rem; font-weight: 900; line-height: 1; font-variant-numeric: tabular-nums; }
.kd-tile strong { display: block; font-size: .92rem; }
.kd-tile small { display: block; font-size: .78rem; color: var(--ink-soft, #5b5f7a); }
.kd-tile.is-done { background: #f3ecff; border-color: #cdb4ff; }
.kd-tile.is-done b { color: #8b3dff; }
.kd-tile.is-todo { background: #fff1df; border-color: #ffc58a; }
.kd-tile.is-todo b { color: #e8730c; }
.kd-track { display: block; height: 10px; background: #fff1df; border: 1px solid #ffc58a; border-radius: 99px; overflow: hidden; }
.kd-track i { display: block; height: 100%; width: 0; background: linear-gradient(90deg, #a66bff, #8b3dff); transition: width .2s ease; }
.kd-grp { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; font-weight: 800; font-size: .78rem;
  letter-spacing: .08em; text-transform: uppercase; color: #c31465; margin: 12px 0 6px; }
.kd-grp small { text-transform: none; letter-spacing: 0; font-weight: 600; color: var(--ink-soft, #5b5f7a); }
.kd-gc { font-size: .76rem; font-weight: 700; letter-spacing: 0; text-transform: none; padding: 1px 9px; border-radius: 999px; }
.kd-gc:empty { display: none; }
.kd-gc.is-done { background: #f3ecff; color: #6d28d9; }
.kd-gc.is-todo { background: #fff1df; color: #a84f00; }
/* 🛑 .kd-row AND .kd-rows ARE ALSO THE KEY DETAILS CARD ON THE RUN SHEET PAGE
   (timeline.js ~1654, the block above this one). Unscoped, these rules reached
   that card on 2 Oct and squeezed its labels into a 30px column ("GETTINGWhiteman
   St"). Every .kd-row / .kd-rows rule for the Edit details list lives under
   #tl-kd-lines. CHECK 130. */
#tl-kd-lines .kd-rows { display: grid; gap: 8px; border-top: 0; }
#tl-kd-lines .kd-row { display: grid; grid-template-columns: 30px 180px minmax(0, 1fr) 150px; gap: 8px 12px; align-items: center;
  border: 1.5px solid #e3eeed; border-radius: 14px; padding: 9px 12px; background: #fff; }
#tl-kd-lines .kd-row.is-set  { border-color: #cdb4ff; background: #f3ecff; }
#tl-kd-lines .kd-row.is-need,
#tl-kd-lines .kd-row.is-sug  { border-color: #e8730c; background: #fff1df; box-shadow: 0 2px 0 #ffc58a; }
#tl-kd-lines .kd-row.is-sug select { color: #7b8090; font-style: italic; border: 1.5px dashed #e8730c; }
#tl-kd-lines .kd-row.is-need input:placeholder-shown { border: 1.5px dashed #e8730c; }
#tl-kd-lines .kd-row.is-na   { opacity: .7; }
.kd-ico { width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center;
  font-weight: 900; font-size: .8rem; color: #fff; background: #cfd3de; }
#tl-kd-lines .kd-row.is-set .kd-ico { background: #8b3dff; }
#tl-kd-lines .kd-row.is-set .kd-ico::before { content: "\2713"; }
#tl-kd-lines .kd-row.is-need .kd-ico, #tl-kd-lines .kd-row.is-sug .kd-ico { background: #e8730c; }
#tl-kd-lines .kd-row.is-need .kd-ico::before, #tl-kd-lines .kd-row.is-sug .kd-ico::before { content: "!"; }
#tl-kd-lines .kd-row.is-na .kd-ico { background: #0f7a55; }
#tl-kd-lines .kd-row.is-na .kd-ico::before { content: "\2013"; }
.kd-row-l { display: grid; gap: 1px; min-width: 0; }
.kd-how { font-size: .74rem; font-weight: 700; color: #a84f00; line-height: 1.3; }
.kd-how:empty { display: none; }
#tl-kd-lines .kd-row.is-set .kd-row-k { color: #3b2a66; }
.kd-row-k { font-weight: 800; font-size: .9rem; }
.kd-row-c { display: grid; gap: 6px; min-width: 0; }
.kd-row-c select, .kd-row-c .kd-slot > input { width: 100%; min-width: 0; }
.kd-row-ext { display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: center; }
.kd-ext-f { display: flex; align-items: center; gap: 6px; flex: 1 1 220px; min-width: 0; }
.kd-ext-l { font-size: .75rem; font-weight: 700; color: var(--ink-soft, #5b5f7a); white-space: nowrap; }
.kd-ext-f .kd-slot { flex: 1; min-width: 0; }
.kd-row-ext > .kd-slot { flex: 0 1 140px; }
.kd-dash { color: var(--ink-soft, #5b5f7a); }
/* The linked line's own times, typed here (2 Oct, CHECK 128). */
.kd-row-ext > .kd-lt { flex: 0 1 120px; min-width: 90px; }
/* One line per time (2 Oct): drop-down + from – to side by side. .kd-one-ln
   never wraps; only "Add to the run sheet" drops below when there is no room.
   Time rows get a narrower status column; the Where rows keep 150px. */
#tl-kd-lines .kd-row[data-kd-row="time"] { grid-template-columns: 30px 180px minmax(0, 1fr) 104px; }
.kd-one { display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: center; min-width: 0; }
.kd-one-ln { display: flex; flex-wrap: wrap; gap: 6px 8px; align-items: center; flex: 1 1 380px; min-width: 0; }
/* ⚠️ min 220px: without it a narrow window squeezed the drop-down to 45px.
   Below that the from – to pair drops under it, together (.kd-times). */
.kd-row-c .kd-one-ln > select { flex: 1 1 220px; width: auto; min-width: 0; }
.kd-times { display: flex; gap: 6px; align-items: center; flex: none; }
/* 112px: at 96 the time box's clock icon cut "11:59 pm" to "11:59 pr". */
.kd-times .kd-lt, .kd-times .kd-slot > input { width: 112px; min-width: 0; }
.kd-addline { flex: none; }
.kd-addline { border: 1.5px solid #8b3dff; color: #8b3dff; background: #fff; font-weight: 800; }
#tl-kd-lines .kd-row.is-need .kd-addline { border-color: #e8730c; background: #e8730c; color: #fff; }
.kd-tick label { margin: 0; font-size: .82rem; }
.kd-row-na { font-size: .85rem; color: var(--ink-soft, #5b5f7a); }
.kd-row-s { justify-self: end; }
.kd-chip { display: inline-block; padding: 2px 10px; border-radius: 999px; font-size: .7rem; font-weight: 800;
  letter-spacing: .03em; text-transform: uppercase; color: #fff; background: #ff8a1f; white-space: nowrap; }
#tl-kd-lines .kd-row.is-set .kd-chip { background: #8b3dff; }
#tl-kd-lines .kd-row.is-na .kd-chip  { background: #0f7a55; }
.kd-confirm { border: 1.5px solid #e8730c; color: #fff; background: #e8730c; font-weight: 800; }

/* "At <ceremony venue>" / "↓ Moves to <reception>" — 2 Oct, her option E: a
   SOLID line ("it should not be dashes") through a white outlined pill. Pink
   for the ceremony, sky blue for the reception — never purple, which means
   "done" in this box. Drawn only when the ceremony is somewhere else
   (ceremonyElsewhere), on the card AND in Edit details. */
/* 2 Oct, later: "more in-your-face" — her option C, a SOLID pill, white words,
   with "more spacing within the word… the letters are too close together". */
.kd-move { display: flex; align-items: center; gap: 14px; margin: 20px 0 8px; }
.kd-move::before, .kd-move::after { content: ""; flex: 1; border-top: 3px solid currentColor; }
.kd-move.is-cer { color: #d6337a; }
.kd-move.is-rec { color: #1a8fe0; }
.kd-move span { background: currentColor; border-radius: 999px; padding: 7px 22px;
  white-space: nowrap; max-width: 80%; overflow: hidden; text-overflow: ellipsis; }
/* The words are a child so they can be white while the pill takes currentColor. */
.kd-move span b { color: #fff; font-weight: 800; font-size: .95rem; letter-spacing: .06em; word-spacing: .12em; }
#tl-kd-lines .kd-rows > .kd-move { margin: 10px 0 0; }

/* What kind of place (141, CHECK 132): House / Hotel, Church / Ceremony
   location / At the reception. Purple when chosen (= done); a suggestion is
   dashed orange (= fill this in), the same language as the boxes above. */
#tl-kd-lines .kd-kinds { display: flex; flex-wrap: wrap; gap: 6px; }
#tl-kd-lines .kd-kind { border: 1.5px solid #cdb4ff; background: #fff; color: #6d28d9; border-radius: 999px;
  padding: 4px 14px; font: inherit; font-size: .82rem; font-weight: 700; cursor: pointer; }
#tl-kd-lines .kd-kind:hover { border-color: #8b3dff; }
#tl-kd-lines .kd-kind.is-on { background: #8b3dff; border-color: #8b3dff; color: #fff; }
#tl-kd-lines .kd-kind.is-sug { border: 1.5px dashed #e8730c; color: #a84f00; }
#tl-kd-lines .kd-kind:focus-visible { outline: 2px solid #8b3dff; outline-offset: 2px; }

/* "This wedding still says Bride's house" — under the place row after Hotel is
   pressed (17 Oct, CHECK 183). Sky, never purple (purple = done in this box)
   and never orange (= fill this in). NOW struck through, SHOULD BE in green,
   as in the mock-up she said yes to (ASnYhVSmGBcfx8GWdkKdLG). */
#tl-kd-lines .kd-hotel-offer { margin: -2px 0 10px 38px; padding: 12px 14px; border-radius: 14px;
  border: 2px dashed #0091ea; background: #e6f4ff; display: grid; gap: 8px; }
#tl-kd-lines .kd-ho-head { margin: 0; font-size: .88rem; color: #0b4f8a; }
#tl-kd-lines .kd-ho-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
#tl-kd-lines .kd-ho-list li { display: grid; grid-template-columns: 76px minmax(0, 1fr) 18px minmax(0, 1fr);
  gap: 8px; align-items: baseline; font-size: .85rem; padding: 4px 0; border-bottom: 1px solid #b9defc; }
#tl-kd-lines .kd-ho-list li:last-child { border-bottom: 0; }
#tl-kd-lines .kd-ho-where { font-size: .72rem; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; color: #0062b8; }
#tl-kd-lines .kd-ho-now { text-decoration: line-through; color: #d6337a; min-width: 0; overflow-wrap: anywhere; }
#tl-kd-lines .kd-ho-arr { color: #0062b8; }
#tl-kd-lines .kd-ho-next { color: #00733d; font-weight: 800; min-width: 0; overflow-wrap: anywhere; }
#tl-kd-lines .kd-ho-btns { display: flex; flex-wrap: wrap; gap: 8px; }
#tl-kd-lines .kd-ho-go { background: #0091ea; border: 1.5px solid #0091ea; color: #fff; font-weight: 800; }
#tl-kd-lines .kd-ho-no { background: #fff1c2; border: 1.5px solid #f5c542; color: #8a5a00; font-weight: 800; }
@media (max-width: 640px) {
  #tl-kd-lines .kd-hotel-offer { margin-left: 0; }
  #tl-kd-lines .kd-ho-list li { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  #tl-kd-lines .kd-ho-arr { display: none; }
}

/* Where this wedding goes, in the same look — only under .kd-onelist, so the
   finished weddings keep theirs. Every place written down is purple ✓; a row
   with no name yet is orange !. Same inputs, same buttons, same handlers. */
.kd-onelist .place-row { grid-template-columns: 30px minmax(0, 1.1fr) minmax(0, 1.3fr) minmax(0, 1fr) auto;
  gap: 6px 8px; padding: 8px 12px; border-radius: 14px; border: 1.5px solid #cdb4ff; background: #f3ecff; margin-bottom: 8px; }
.kd-onelist .place-row::before { content: "\2713"; width: 26px; height: 26px; border-radius: 50%; display: grid;
  place-items: center; font-weight: 900; font-size: .8rem; color: #fff; background: #8b3dff; }
.kd-onelist .place-row:has(.place-name:placeholder-shown) { border-color: #e8730c; background: #fff1df; box-shadow: 0 2px 0 #ffc58a; }
.kd-onelist .place-row:has(.place-name:placeholder-shown)::before { content: "!"; background: #e8730c; }
.kd-onelist .place-row input { border-radius: 9px; background: #fff; padding: 7px 10px; }
.kd-onelist .place-row:has(.place-name:placeholder-shown) .place-name { border: 1.5px dashed #e8730c; }
.kd-onelist .place-move { gap: 5px; }
.kd-onelist .place-move button { width: 30px; height: 30px; border-radius: 8px; border-color: #cdb4ff; color: #6d28d9; background: #fff; }
.kd-onelist .place-rename { grid-column: 2 / -1; color: #6d28d9; font-weight: 700; }
.kd-onelist #tl-place-add { color: #8b3dff; font-weight: 800; }

@media (max-width: 760px) {
  #tl-kd-lines .kd-row, #tl-kd-lines .kd-row[data-kd-row="time"] { grid-template-columns: 30px minmax(0, 1fr); }
  #tl-kd-lines .kd-row > .kd-row-c, #tl-kd-lines .kd-row > .kd-row-s { grid-column: 1 / -1; }
  .kd-row-s { justify-self: start; }
  .kd-onelist .place-row { grid-template-columns: 30px minmax(0, 1fr) auto; }
  .kd-onelist .place-row .place-addr, .kd-onelist .place-row .place-label { grid-column: 2 / -1; }
}

/* ---- Key details, compressed — 18 Oct -------------------------------------
   Gee asked for Key details "the same way" as the budget line, and said yes to
   the mock-up drawn in the live window on Oguzhan & Amal: 1,782px → 1,435px.
   NOT colour panels: this box already uses colour to MEAN something (purple
   done, orange needs you, green not on), and panels would fight it. Instead:
   the four places two to a row (name and chip on top, the boxes full card
   width under them, 521 → 317px), and every row a little tighter.
   ⚠️ #tl-kd-lines ONLY, so the run sheet page's own Key details card is
   untouched (CHECK 130), and the finished weddings never draw these rows.
   Wide screens only — under 760px the rules above stack every row already. */
@media (min-width: 761px) {
  #tl-kd-lines .kd-rows { gap: 6px; }
  #tl-kd-lines .kd-row { padding: 6px 12px; row-gap: 4px; }
  #tl-kd-lines .kd-row.is-na { padding: 3px 12px; }
  #tl-kd-lines .kd-row[data-kd-row="time"] { grid-template-columns: 30px 230px minmax(0, 1fr) auto; }
  #tl-kd-lines .kd-rows:has(> [data-kd-row="place"]) { grid-template-columns: 1fr 1fr; }
  #tl-kd-lines .kd-rows:has(> [data-kd-row="place"]) > .kd-move { grid-column: 1 / -1; }
  /* The "still says Bride's house" offer (CHECK 183) draws right after its
     row inside this list — across both columns, or it would fill a card slot. */
  #tl-kd-lines .kd-rows:has(> [data-kd-row="place"]) > .kd-hotel-offer { grid-column: 1 / -1; margin: 0 0 4px; }
  #tl-kd-lines .kd-row[data-kd-row="place"] {
    grid-template-columns: 28px minmax(0, 1fr) auto; align-items: start;
    padding: 8px 10px; row-gap: 6px;
  }
  #tl-kd-lines .kd-row[data-kd-row="place"] > .kd-row-l { grid-column: 2; grid-row: 1; align-self: center; }
  #tl-kd-lines .kd-row[data-kd-row="place"] > .kd-row-s { grid-column: 3; grid-row: 1; }
  #tl-kd-lines .kd-row[data-kd-row="place"] > .kd-row-c { grid-column: 1 / -1; grid-row: 2; }
  #tl-kd-lines .kd-row[data-kd-row="place"] .kd-row-ext { flex-wrap: nowrap; width: 100%; }
  #tl-kd-lines .kd-row[data-kd-row="place"] .kd-ext-f { flex: 1 1 0; }
  /* The Room box beside the reception's address: narrower, so the address
     keeps the room (measured: 204 → 269px for "210 Hume Hwy, Somerton…"). */
  #tl-kd-lines .kd-row[data-kd-row="place"] .kd-ext-f + .kd-ext-f { flex: 0 1 190px; }
}

/* A deposit invoice: why "Put the total" is not offered (2 Oct, CHECK 120). */
.bf-read-deposit {
  margin: 10px 0 0;
  padding: 8px 12px;
  border-left: 4px solid #ff8a1f;
  border-radius: 0 8px 8px 0;
  background: #fff0dc;
  font-size: .86rem;
}

/* ==========================================================================
   Supplier recommendations on a to-do (143) — recommendations.js
   ⚠️ HER COLOURS, CHOSEN 13 Oct 2026 AFTER SIX ROUNDS (CFkLwULykfHktdiqpMixns
   version 2). One colour per job, never mixed inside one thing:
     purple  heading band        pink   All / Choose switch
     blue    Instagram + count   mint   the tick
     yellow  a chosen row        blue   the Add button
   The couple's circles and her ranked list use the 1·2·3·+ colours she
   loved in the mock-up (pink, violet, sky, mint). Do not "tidy" either set
   into the theme tokens.
   ========================================================================== */
/* ── A prepared email under its to-do (153, Gee's Option 1, 16 Oct) ──────────
   Pale sky card, the honey "not sent" square from the ★ tag, mint once sent.
   The buttons are the mock-up's own pills. taskemail.js draws it. */
.tem-card { margin: 8px 0 4px; padding: 10px 12px; display: flex; flex-direction: column; gap: 5px;
  border: 1.5px solid #9fd4ff; background: #f3f9ff; border-radius: 12px; font-size: .86rem; cursor: auto; }
.tem-card.is-sent { border-color: #9be8d2; background: #f2fcf8; }
.tem-top { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.tem-tag { display: inline-flex; align-items: center; height: 22px; padding: 0 8px; border: 1.5px solid #b58100;
  background: #fff3c4; color: #6b4f00; border-radius: 6px; font-weight: 800; font-size: .74rem; white-space: nowrap; }
.tem-tag.is-sent { border-color: #00a07a; background: #d9fbef; color: #006b52; }
.tem-line { display: flex; gap: 8px; min-width: 0; }
.tem-k { flex: 0 0 58px; color: #0067a3; font-weight: 800; }
.tem-v { flex: 1; min-width: 0; color: #2e1f2a; font-weight: 700; overflow-wrap: anywhere; }
.tem-body { white-space: pre-wrap; overflow-wrap: anywhere; background: #fff; border: 1px solid #d6ecff;
  border-radius: 10px; padding: 8px 10px; color: #2e1f2a; line-height: 1.45; user-select: text; }
.tem-btns { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 2px; }
.tem-b { font: inherit; font-size: .78rem; font-weight: 800; line-height: 1.2; border-radius: 999px;
  padding: 4px 12px; border: 1.5px solid; cursor: pointer; background: #fff; text-decoration: none; }
.tem-b:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
.tem-b:disabled { opacity: .55; cursor: default; }
.tem-show { color: #c22a63; border-color: #e8197a; background: #ffeaf3; }
.tem-copy { color: #0067a3; border-color: #0091ea; background: #e3f3ff; }
.tem-ol   { color: #4a00b0; border-color: #6200ea; background: #eae0ff; }
.tem-sent { color: #006b52; border-color: #00bfa5; background: #d9fbef; }
.tem-fields { display: flex; flex-direction: column; gap: 6px; }
/* The email shown the way it will paste into Outlook (bold headings, real
   bullets) — so not pre-wrap, which the reply text still uses. */
.tem-body.tem-body-mail { white-space: normal; }
.tem-body.tem-body-mail ul { padding-left: 22px; }
/* 155: waiting on a reply (sky), their reply under the email (mint), and the
   inbox's offer to put a forwarded reply on its to-do. */
.tem-tag.is-wait { border-color: #0091ea; background: #e3f3ff; color: #0067a3; }
.tem-reply { border: 1.5px solid #9be8d2; background: #fff; border-radius: 10px; padding: 8px 10px;
  display: flex; flex-direction: column; gap: 5px; align-items: flex-start; }
.tem-reply > b { color: #006b52; font-size: .82rem; }
.tem-reply-sum { margin: 0; color: #2e1f2a; line-height: 1.45; }
.tem-reply .tem-body { align-self: stretch; }
.tem-offer { display: flex; flex-direction: column; gap: 6px; margin: 6px 0; padding: 8px 10px;
  border: 1.5px solid #9fd4ff; background: #f3f9ff; border-radius: 10px; font-size: .86rem; }
.tem-offer-row { display: flex; gap: 8px; align-items: center; justify-content: space-between; flex-wrap: wrap; }
.tem-offer-why { color: #7a5a6c; font-size: .8rem; }

.rcm-box { margin: 10px 0 4px; display: flex; flex-direction: column; gap: 8px; }
.rcm-head { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.rcm-head b { font-size: .95rem; color: #4a00b0; }
.rcm-pill { display: inline-flex; align-items: center; font-size: .74rem; font-weight: 800;
  padding: 2px 9px; border-radius: 999px; white-space: nowrap; }
.rcm-pill-in   { background: #ffd6e5; color: #c22a63; }
.rcm-pill-warn { background: #ffeeda; color: #b34e00; }
.rcm-pill-wait { background: #eae0ff; color: #4a00b0; }
/* The ★ square at the end of a checklist title (Gee, 14 Oct, option A made
   square). Inline, on the title line, nowrap — it never sits over the
   description underneath. Purple = sent, pink = choices in, honey = not sent. */
.rcm-rowtag { display: inline-flex; align-items: center; height: 22px; margin-left: 8px;
  padding: 0 7px; border: 1.5px solid; border-radius: 6px; font-family: inherit;
  font-size: .72rem; font-weight: 800; line-height: 1; white-space: nowrap;
  vertical-align: middle; cursor: pointer; }
.rcm-rowtag.is-sent { background: #eae0ff; color: #4a00b0; border-color: #4a00b0; }
.rcm-rowtag.is-in   { background: #ffd6e5; color: #c22a63; border-color: #c22a63; }
.rcm-rowtag.is-wait { background: #fff3c4; color: #6b4f00; border-color: #b58100; }
.rcm-rowtag:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
.rcm-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.rcm-row { display: flex; gap: 10px; align-items: center; padding: 8px 10px; border-radius: 12px;
  background: #fff; border: 1.5px solid #efe6ec; }
.rcm-row.rcm-k1 { border-color: #ffd6e5; background: #fff5f9; }
.rcm-row.rcm-k2 { border-color: #e6dcff; background: #faf7ff; }
.rcm-row.rcm-k3 { border-color: #d6ecff; background: #f5faff; }
.rcm-row.rcm-k4 { border-color: #d7f5e6; background: #f3fcf7; }
.rcm-who { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.3; }
.rcm-who b { font-weight: 800; color: #2e1f2a; overflow-wrap: anywhere; }
.rcm-link, .rcm-handle { font-size: .8rem; font-weight: 700; color: #0067a3; text-decoration: none;
  overflow-wrap: anywhere; }
.rcm-link:hover { text-decoration: underline; }
.rcm-nolink { color: #b34e00; }
.rcm-guide { font-size: .78rem; font-weight: 700; color: #5b3fb0; }
.rcm-badge { width: 30px; height: 30px; flex: none; border-radius: 50%; display: grid; place-items: center;
  font-weight: 800; border: 2px dashed #ead7e1; background: #fff; color: #c9b3bf; }
.rcm-b1 { background: #ffd6e5; color: #c22a63; border: none; }
.rcm-b2 { background: #e6dcff; color: #5b3fb0; border: none; }
.rcm-b3 { background: #d6ecff; color: #245d93; border: none; }
.rcm-b4 { background: #d7f5e6; color: #1f7a4f; border: none; }
.rcm-status { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; justify-content: flex-end; }
.rcm-chip { border: 1.5px solid #d7f5e6; background: #fff; color: #1f7a4f; border-radius: 999px;
  padding: 3px 11px; font: inherit; font-size: .78rem; font-weight: 800; cursor: pointer; }
.rcm-chip.is-on { background: #d7f5e6; }
.rcm-chip-quote { border-color: #d6ecff; color: #245d93; }
.rcm-chip-quote.is-on { background: #d6ecff; }
.rcm-x { border: none; background: none; color: #c22a63; font-size: 1.15rem; line-height: 1;
  cursor: pointer; padding: 2px 4px; }
/* ↑ ↓ on an unsent supplier (13 Oct). Same small bordered squares as her
   checklist section arrows, in the box's purple so they read as its own. */
.rcm-move { border: 1.5px solid #d6c3f5; background: #fff; color: #5b2bb5; border-radius: 6px;
  width: 26px; height: 26px; font-size: .9rem; font-weight: 800; line-height: 1; cursor: pointer; padding: 0; }
.rcm-move:hover { background: #f6f1ff; }
.rcm-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.rcm-open { border: 1.5px dashed #b89cf0; background: #f6f1ff; color: #4a00b0; border-radius: 999px;
  padding: 6px 14px; font: inherit; font-weight: 800; font-size: .85rem; cursor: pointer; }
.rcm-send { border: none; background: #0091ea; color: #fff; border-radius: 999px; padding: 6px 14px;
  font: inherit; font-weight: 800; font-size: .85rem; cursor: pointer; display: inline-flex; gap: 8px; align-items: center; }
.rcm-send span { background: rgba(255,255,255,.25); border-radius: 999px; padding: 0 8px; font-size: .76rem; }
.rcm-note { margin: 0; font-size: .82rem; color: #7a5a6c; }
/* Saved links in the same box (154, Gee's option A, 16 Oct): sky, the colour
   the mock-up she picked used, beside the box's own purple and blue. */
.rcm-open-link { border-color: #7cc4f5; background: #e3f3ff; color: #0067a3; }
.rcm-badge.rcm-lk-mark { background: #e3f3ff; color: #0067a3; border: none; font-size: .8rem; }
.rcm-lk-sent { background: #d9fbef; color: #006b52; }
.rcm-lk-what { font-size: .84rem; color: #5a4452; }
.rcm-lk-title { color: #0067a3; font-size: .88rem; }
.rcm-lk-new { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.rcm-lk-new .rcm-line { min-width: 140px; }
.rcm-lk-watch { flex: none; font-weight: 800; font-size: .84rem; color: #0067a3; background: #fff;
  border: 1.5px solid #0091ea; border-radius: 999px; padding: 4px 12px; text-decoration: none; white-space: nowrap; }
.rcm-lk-watch:focus-visible { outline: 2px solid #0091ea; outline-offset: 2px; }

/* her picker — screen 1 */
.rcm-pick { border: 2px solid #eae0ff; border-radius: 14px; background: #fff; overflow: hidden;
  display: flex; flex-direction: column; gap: 8px; padding: 0 12px 12px; }
.rcm-pick-head { background: #eae0ff; color: #4a00b0; font-weight: 800; margin: 0 -12px;
  padding: 9px 12px; display: flex; gap: 8px; align-items: center; }
.rcm-box-mark { width: 15px; height: 15px; border: 2px solid #4a00b0; border-radius: 4px; flex: none; }
.rcm-subject { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; font-weight: 700; color: #4a00b0; font-size: .85rem; }
.rcm-subject select { font: inherit; padding: 5px 8px; border-radius: 10px; border: 1.5px solid #eae0ff; min-width: 0; max-width: 100%; }
.rcm-bar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: space-between; }
.rcm-seg { display: inline-flex; background: #ffe3ef; border-radius: 999px; padding: 3px; }
.rcm-seg button { border: none; background: none; color: #d1176e; font: inherit; font-weight: 800;
  font-size: .82rem; padding: 4px 12px; border-radius: 999px; cursor: pointer; }
.rcm-seg button.is-on { background: #d1176e; color: #fff; }
.rcm-count { background: #daf0ff; color: #0067a3; font-size: .76rem; font-weight: 800; padding: 2px 10px; border-radius: 999px; }
/* The search at the top of the picker (13 Oct) — her list AND her Contacts. */
.rcm-search { display: flex; align-items: center; gap: 8px; border: 1.5px solid #d6c3f5; border-radius: 10px;
  padding: 6px 10px; background: #fff; }
.rcm-search-icon { color: #5b2bb5; font-size: 1rem; line-height: 1; }
.rcm-search input { flex: 1; min-width: 0; border: none; outline: none; font: inherit; font-size: .92rem;
  background: transparent; color: #2e1f2a; padding: 2px 0; }
.rcm-search:focus-within { border-color: #8b5cf6; box-shadow: 0 0 0 3px #ede4ff; }
.rcm-from { display: block; font-size: .74rem; color: #7a5a6c; font-weight: 600; }
.rcm-pickrow { cursor: pointer; user-select: none; }
.rcm-pickrow b { color: #6b5a66; font-weight: 700; }
.rcm-pickrow.is-picked { background: #fff3c4; border: 2px solid #f2d36b; }
.rcm-pickrow.is-picked b { color: #2e1f2a; font-weight: 800; }
.rcm-tick { width: 32px; height: 32px; flex: none; border-radius: 50%; border: 2px solid #d9cdd5; background: #fff;
  display: grid; place-items: center; color: #fff; font-weight: 800; }
.rcm-tick.is-on { background: #2fc9a8; border-color: #2fc9a8; }
.rcm-pick-foot { display: flex; gap: 8px; align-items: center; justify-content: flex-end; flex-wrap: wrap; }
.rcm-link-btn { border: none; background: none; color: #7a5a6c; font: inherit; font-weight: 700; cursor: pointer; }
.rcm-add { border: none; background: #0091ea; color: #fff; border-radius: 999px; padding: 7px 16px;
  font: inherit; font-weight: 800; font-size: .86rem; cursor: pointer; }
.rcm-add:disabled { opacity: .45; cursor: default; }

/* their side — screen 2, circles on each row */
.rcm-cbox { margin: 8px 0 4px; display: flex; flex-direction: column; gap: 8px; }
.rcm-cintro { margin: 0; font-size: .88rem; color: #5a4452; }
.rcm-circles { display: flex; gap: 5px; flex: none; }
.rcm-c { width: 30px; height: 30px; border-radius: 50%; border: 1.5px solid #e7d7e0; background: #fff;
  display: grid; place-items: center; font: inherit; font-weight: 800; font-size: .82rem; color: #7a5a6c; cursor: pointer; padding: 0; }
.rcm-c:disabled { cursor: default; opacity: .6; }
.rcm-c.is-on:disabled { opacity: 1; }
.rcm-on1 { background: #ffd6e5; border-color: #ffd6e5; color: #c22a63; }
.rcm-on2 { background: #e6dcff; border-color: #e6dcff; color: #5b3fb0; }
.rcm-on3 { background: #d6ecff; border-color: #d6ecff; color: #245d93; }
.rcm-on4 { background: #d7f5e6; border-color: #d7f5e6; color: #1f7a4f; }
.rcm-cfoot { display: flex; justify-content: flex-end; }
.rcm-csend { border: none; background: #e0457b; color: #fff; border-radius: 999px; padding: 7px 16px;
  font: inherit; font-weight: 800; font-size: .86rem; cursor: pointer; }
.rcm-csend:disabled { opacity: .45; cursor: default; }
/* 🛑 THE COUPLE'S ROWS ARE COLOURED FROM THE START (Gee, 13 Oct, option A):
   one strong fill per row, in the EMAIL's order — pink, mint, sky, honey,
   violet — so the email and the screen match. The row keeps its colour when
   picked (three classes beat .rcm-row.rcm-kN); the tapped circle fills SOLID
   instead, so the choice still shows on a coloured row. The handle is bold
   and darker: "too washed" at #0067a3. Her own (planner) rows are untouched. */
/* 🛑 OPTION B, 13 Oct ("love it"): the colour moved off the whole card onto a
   BAND across the top (name + circles, or name + her buttons); the handle and
   the Available / price pills sit on white underneath, where they can be read.
   Both screens, so hers and theirs match. Three classes, so a picked row's
   .rcm-row.rcm-kN tint never overrides the white body. */
.rcm-row.rcm-card { display: block; padding: 0; overflow: hidden; background: #fff; }
.rcm-card .rcm-band { display: flex; align-items: center; gap: 8px; padding: 8px 12px; flex-wrap: wrap; }
.rcm-card .rcm-name { flex: 1; min-width: 0; font-weight: 800; color: #2e1f2a; overflow-wrap: anywhere; }
.rcm-card .rcm-body { display: flex; flex-direction: column; gap: 6px; padding: 8px 12px 10px; }
.rcm-row.rcm-card.rcm-p1 { border-color: #f9a8cc; background: #fff; } .rcm-card.rcm-p1 .rcm-band { background: #ffcfe4; }
.rcm-row.rcm-card.rcm-p2 { border-color: #7fdcc4; background: #fff; } .rcm-card.rcm-p2 .rcm-band { background: #bff0e2; }
.rcm-row.rcm-card.rcm-p3 { border-color: #93c2f7; background: #fff; } .rcm-card.rcm-p3 .rcm-band { background: #c9e2ff; }
.rcm-row.rcm-card.rcm-p4 { border-color: #f9c74f; background: #fff; } .rcm-card.rcm-p4 .rcm-band { background: #ffe8a8; }
.rcm-row.rcm-card.rcm-p5 { border-color: #b9a2f5; background: #fff; } .rcm-card.rcm-p5 .rcm-band { background: #e2d6ff; }
.rcm-card .rcm-band .rcm-pill-wait { background: #fff; }
.rcm-card .rcm-band .rcm-c:not(.is-on) { background: #fff; }
.rcm-infos { display: flex; flex-wrap: wrap; gap: 6px; }
.rcm-info { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 10px;
  font-size: .78rem; font-weight: 800; }
.rcm-info-ok   { background: #c9f5e8; color: #0f6b55; }
.rcm-info-line { background: #fff3c4; color: #6b4f00; }
.rcm-extras { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.rcm-avail { border: 1.5px solid #2fc9a8; background: #fff; color: #0f6b55; border-radius: 999px;
  padding: 4px 12px; font: inherit; font-size: .8rem; font-weight: 800; cursor: pointer; }
.rcm-avail.is-on { background: #c9f5e8; }
.rcm-line { flex: 1; min-width: 160px; font: inherit; font-size: .85rem; padding: 5px 10px;
  border: 1.5px solid #f2d36b; background: #fffbea; border-radius: 8px; color: #2e1f2a; }
.rcm-line:focus { outline: none; border-color: #e0a800; box-shadow: 0 0 0 3px #fff3c4; }
.rcm-crow .rcm-link { font-weight: 800; color: #0a4f80; font-size: .84rem; }
.rcm-crow .rcm-c.rcm-on1 { background: #e0457b; border-color: #e0457b; color: #fff; }
.rcm-crow .rcm-c.rcm-on2 { background: #7c5cd6; border-color: #7c5cd6; color: #fff; }
.rcm-crow .rcm-c.rcm-on3 { background: #1668c4; border-color: #1668c4; color: #fff; }
.rcm-crow .rcm-c.rcm-on4 { background: #2fa36b; border-color: #2fa36b; color: #fff; }
@media (max-width: 480px) {
  .rcm-crow { flex-wrap: wrap; }
  .rcm-crow .rcm-circles { width: 100%; justify-content: flex-end; }
  .rcm-row .rcm-status { width: 100%; }
  /* A card's band keeps the four circles beside the name when they fit. */
  .rcm-card .rcm-circles { width: auto; }
}

/* 🛑 "✓ Checked" — one press takes the yellow off a run sheet line or a task.
   Gee, 13 October: clearing yellow meant the ⋮ menu, the ⊘ swatch, close and
   F5. 🛑 PURPLE, FILLED — her pick, 13 Oct, off a mock-up: the first cut was
   mint and sat under the teal supplier chips looking like one of them. This is
   the Files box's "read" purple (#8b3dff, 2 Oct), the one colour that already
   means "read" and that nothing else on the run sheet uses. A real button
   reset (font, background, cursor) because a bare <button> gets the browser's
   grey bevel. Hidden on print by .no-print in print.css. */
.yellow-checked {
  display: inline-block;
  margin-left: 8px;
  padding: 2px 10px;
  border: 1.5px solid #8b3dff;
  border-radius: 999px;
  background: #8b3dff;
  color: #fff;
  font-family: inherit;
  font-size: .75rem;
  font-weight: 800;
  line-height: 1.4;
  vertical-align: middle;
  cursor: pointer;
  white-space: nowrap;
}
.yellow-checked:hover { background: #6d28d9; border-color: #6d28d9; }
.yellow-checked:focus-visible { outline: 2px solid var(--pink); outline-offset: 2px; }
.yellow-checked:disabled { opacity: .5; cursor: progress; }
@media (hover: none) { .yellow-checked { padding: 6px 12px; } }

/* "✕ Clear note" — right under the line's title (her option D) in solid
   BRIGHT BLUE, 15 Oct (Annika & Ceyhun), her pick after three tries. Not the
   pale pink — that is the place tags' colour. Not raspberry — "a really ugly
   colour"; no dark colours, ever. Not tangerine — "way too close to the
   yellow" title highlight right above it. Not purple — that is "Mark as
   checked".
   The bar's "Show Clear note buttons" tick puts .hide-clear-note on the tab.
   CHECK 164. */
.tl-clear-note-row {
  display: block;
  margin: 3px 0 2px;
}
#wtab-timeline.hide-clear-note .tl-clear-note-row { display: none; }
.tl-clear-note {
  display: inline-block;
  padding: 1px 10px;
  border: 1.5px solid var(--v-sky);
  border-radius: 999px;
  background: var(--v-sky);
  color: #fff;
  font-family: inherit;
  font-size: .75rem;
  font-weight: 800;
  line-height: 1.4;
  cursor: pointer;
  white-space: nowrap;
}
.tl-clear-note:hover { background: #33a7f0; border-color: #33a7f0; }
.tl-clear-note:focus-visible { outline: 2px solid var(--pink); outline-offset: 2px; }
.tl-clear-note:disabled { opacity: .5; cursor: progress; }
@media (hover: none) { .tl-clear-note { padding: 6px 12px; } }
/* "From the Zoom" — the orange Zoom words drawn in their own box under her
   notes, ✓ Keep / ✕ on each and ✕ Clear Zoom notes. Her yes, 16 Oct
   (Annika & Ceyhun). .no-print in the markup keeps it off paper. CHECK 174. */
.tl-zoom {
  display: block;
  margin-top: 6px;
  max-width: 680px;
  padding: 6px 10px;
  border-left: 3px solid var(--v-tangerine);
  border-radius: 6px;
  background: #fff4e8;
  color: #4a2a10;
  font-size: .8rem;
}
.tl-zoom-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 2px; }
.tl-zoom-head b { font-size: .7rem; letter-spacing: .08em; text-transform: uppercase; color: var(--c-peach); }
.tl-zoom-row { display: flex; align-items: flex-start; gap: 8px; padding: 3px 0; }
.tl-zoom-row + .tl-zoom-row { border-top: 1px dashed #f5c79a; }
.tl-zoom-text { flex: 1; min-width: 0; }
.tl-zoom-btns { display: flex; gap: 4px; white-space: nowrap; }
.tl-zoom-clear, .tl-zoom-keep, .tl-zoom-drop {
  padding: 1px 9px;
  border-radius: 999px;
  color: #fff;
  font-family: inherit;
  font-size: .7rem;
  font-weight: 800;
  line-height: 1.4;
  cursor: pointer;
}
.tl-zoom-clear { border: 1.5px solid var(--v-sky); background: var(--v-sky); }
.tl-zoom-keep  { border: 1.5px solid #2fc9a8; background: #2fc9a8; }
.tl-zoom-drop  { border: 1.5px solid #ff5a64; background: #ff5a64; }
.tl-zoom-clear:disabled, .tl-zoom-keep:disabled, .tl-zoom-drop:disabled { opacity: .5; cursor: progress; }
.tl-zoom button:focus-visible { outline: 2px solid var(--pink); outline-offset: 2px; }
@media (hover: none) { .tl-zoom-clear, .tl-zoom-keep, .tl-zoom-drop { padding: 5px 11px; } }
/* Ask before removing, on the spot (ui.js armOrGo, CHECK 176): the pressed
   button turns into a red "Yes, …" with the question before it and a
   "Keep it" after. Gee's option A, 16 Oct. */
button.is-armed {
  width: auto !important;
  min-width: 0 !important;
  padding: 1px 10px !important;
  border: 1.5px solid #ff5a64 !important;
  border-radius: 999px !important;
  background: #ff5a64 !important;
  color: #fff !important;
  font-family: inherit;
  font-size: .72rem !important;
  font-weight: 800;
  white-space: nowrap;
  cursor: pointer;
}
.arm-ask {
  margin: 0 6px 0 4px;
  color: #0067a3;
  font-size: .72rem;
  font-weight: 800;
  white-space: nowrap;
}
.arm-keep {
  margin-left: 6px;
  padding: 1px 10px;
  border: 1.5px solid var(--v-sky);
  border-radius: 999px;
  background: #fff;
  color: #0067a3;
  font-family: inherit;
  font-size: .72rem;
  font-weight: 800;
  white-space: nowrap;
  cursor: pointer;
}
.tl-undo-clear {
  margin-left: 10px;
  padding: 2px 10px;
  border: 1.5px solid #8b3dff;
  border-radius: 999px;
  background: #8b3dff;
  color: #fff;
  font-family: inherit;
  font-size: .78rem;
  font-weight: 800;
  cursor: pointer;
}
/* Under the time, below "30 min" — her option A, 13 Oct. The column is ~98px
   on her screen, so the words wrap onto two lines inside it. */
.tl-time .yellow-checked {
  display: block;
  margin: 8px 0 0;
  max-width: 100%;
  white-space: normal;
  text-align: center;
}
/* The checklist's twin: under the names, her option A. Only a yellow task has
   .who-col; it takes the names' place in the row (order 5) and the names
   inside keep the 84px pill bloom.css gives them as direct children. */
.task-row > .who-col {
  order: 5;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  align-self: flex-start;
  margin-top: 2px;
}
.task-row > .who-col .who-names { display: flex; }
.task-row > .who-col .who-name {
  flex: 0 0 84px;
  max-width: 84px;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: center;
}
.task-row > .who-col .who-name + .who-name { flex-basis: auto; }
/* ⚠️ THE BUTTON MUST NOT WIDEN THE COLUMN. On one line it is 113px against the
   84px name, and measured live it pushed that row's title 29px right of every
   other row's. min-width: 0 + wrapping keeps the column the names' width. */
.task-row > .who-col .yellow-checked {
  margin-left: 0;
  width: min-content;
  min-width: 100%;
  max-width: 84px;
  white-space: normal;
  text-align: center;
}
@media (max-width: 900px) {
  .task-row > .who-col .who-name { flex-basis: 62px; max-width: 62px; }
  .task-row > .who-col .yellow-checked { max-width: 62px; }
}

/* ==========================================================================
   SEE A DOCUMENT ON SCREEN (viewer.js) — Gee, 16 October 2026.
   Above every modal (100, stacked 140) and below the mention menu (3000), so
   it opens over the run sheet line's form that is usually behind it.
   ========================================================================== */
.fv {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3vh 16px;
  background: rgba(232, 25, 122, .18);
}
.fv[hidden] { display: none; }
.fv-box {
  display: flex;
  flex-direction: column;
  width: min(1100px, 100%);
  height: min(92vh, 100%);
  border: 1.5px solid var(--pink-tint, #fdeaf3);
  border-radius: 14px;
  background: #fff;
  box-shadow: 0 18px 50px rgba(232, 25, 122, .18);
  overflow: hidden;
}
.fv-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  background: var(--pink-tint, #fdeaf3);
}
.fv-name {
  min-width: 0;
  font-weight: 800;
  color: var(--pink, #e8197a);
  overflow-wrap: anywhere;
}
.fv-count { font-size: .85rem; font-weight: 700; color: var(--pink-deep, #c31465); }
.fv-gap { flex: 1 1 auto; }
.fv-stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px;
  overflow: auto;
  background: #fffafc;
}
.fv-img, .fv-video { max-width: 100%; max-height: 100%; object-fit: contain; }
.fv-frame { width: 100%; height: 100%; border: 0; background: #fff; }
.fv-msg { text-align: center; color: var(--pink-deep, #c31465); font-weight: 600; line-height: 1.6; }
/* 19 Oct — a spreadsheet, CSV or Word file drawn by docpreview.js.
   ⚠️ align-self: stretch, because the stage CENTRES its child — a tall table
   centred in a flex box loses its top rows off-screen where no scroll reaches. */
.fv-doc { align-self: stretch; width: 100%; overflow: auto; text-align: left; }
.fv-sheet-name { margin: 4px 0 8px; font-size: 1rem; font-weight: 800; color: var(--pink, #e8197a); }
.fv-sheet-name:not(:first-child) { margin-top: 18px; }
.fv-sheet-scroll { overflow-x: auto; }
.fv-sheet-table { border-collapse: collapse; background: #fff; font-size: .85rem; }
.fv-sheet-table td {
  border: 1px solid var(--pink-tint, #fdeaf3);
  padding: 4px 8px;
  vertical-align: top;
  white-space: pre-wrap;
  min-width: 2.5em;
  color: var(--ink, #3b1830);
}
.fv-sheet-cut { margin: 8px 0 0; font-size: .85rem; font-weight: 700; color: var(--pink-deep, #c31465); }
.fv-docx { max-width: 760px; margin: 0 auto; background: #fff; padding: 18px 22px; border-radius: 10px; }
.fv-docx-p { margin: 0 0 8px; line-height: 1.55; white-space: pre-wrap; }
.fv-foot {
  display: flex;
  justify-content: center;
  gap: 10px;
  padding: 10px;
  border-top: 1.5px solid var(--pink-tint, #fdeaf3);
}
.fv-foot[hidden] { display: none; }
html.fv-open { overflow: hidden; }
@media print { .fv { display: none !important; } }
/* ── WhatsApp on the Checklist (158, 18 Oct) ──────────────────────────────────
   Gee's Option C: every message is a WhatsApp bubble — the person's initial,
   their name, their own words, the photo inside, the time in the corner — on
   WhatsApp's own pale green chat background, with a tag underneath naming the
   to-do it went onto. The strip sits above the list; the card sits under a
   to-do, beside the email card (.tem-card). whatsapp.js draws both.
   Pastels only — no dark or neutral fills. */
.wa-strip { margin: 0 0 12px; }
.wa-strip-in, .wa-card { border: 1.5px solid #8fdc9f; background: #effbe9; border-radius: 14px;
  padding: 8px 10px; display: flex; flex-direction: column; gap: 6px; font-size: .84rem; cursor: auto; }
.wa-card { margin: 8px 0 4px; }
.wa-top { display: flex; justify-content: space-between; align-items: center; gap: 8px; flex-wrap: wrap; }
.wa-title, .wa-head { font-weight: 800; color: #1d6b32; font-size: .9rem; }
.wa-sec { font-weight: 800; color: #1d6b32; font-size: .8rem; margin-top: 0; }
.wa-sec.is-ask { color: #b0224f; }
.wa-dim { color: #7a5a6c; font-size: .82rem; font-weight: 600; }

/* The chat itself */
.wa-chatbg { background: #e4f5dc; border-radius: 12px; padding: 8px; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.wa-msg { display: grid; grid-template-columns: 24px minmax(0, 1fr); gap: 6px; align-items: start; }
.wa-msg.is-dim { opacity: .5; }
.wa-av { width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center; font-weight: 800; font-size: .72rem; }
.wa-bwrap { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; min-width: 0; }
.wa-bubble { position: relative; background: #fff; color: #2e1f2a; border-radius: 0 9px 9px 9px;
  padding: 4px 9px 15px; min-width: 120px; max-width: min(460px, 100%); display: flex; flex-direction: column; gap: 3px;
  box-shadow: 0 1px 1.5px rgba(46, 31, 42, .12); }
.wa-bubble::before { content: ""; position: absolute; left: -6px; top: 0; border-top: 8px solid #fff; border-left: 6px solid transparent; }
.wa-bname { font-weight: 800; font-size: .74rem; }
.wa-btext { font-size: .86rem; font-weight: 600; line-height: 1.32; white-space: pre-wrap; overflow-wrap: anywhere; user-select: text; }
.wa-btext.is-deleted { font-style: italic; font-weight: 500; color: #7a5a6c; }
.wa-edited { font-size: .72rem; font-weight: 600; color: #7a5a6c; }
.wa-btime { position: absolute; right: 8px; bottom: 2px; font-size: .64rem; color: #6f8a77; white-space: nowrap; }
.wa-bphoto { display: block; width: 110px; max-width: 100%; border-radius: 6px; overflow: hidden; background: #dcf8c6; }
.wa-bphoto img { display: block; width: 100%; height: auto; max-height: 90px; object-fit: cover; }
/* FILE IT under a photo, 22 Oct — copies it onto a supplier or the Files tab */
.wa-fileit { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 2px 0 12px; }
.wa-fileit select { font: inherit; font-size: .74rem; font-weight: 700; color: #0067a3; background: #fff;
  border: 1.5px solid #0091ea; border-radius: 999px; padding: 2px 8px; max-width: 220px; cursor: pointer; }
.wa-filed { align-self: flex-start; margin: 2px 0 12px; }
.wa-bvid { display: flex; flex-direction: row; gap: 6px; align-items: baseline; align-self: flex-start; max-width: 100%; border-radius: 6px;
  background: #eae0ff; color: #4a00b0; font-weight: 800; padding: 4px 9px; font-size: .78rem; text-align: left; }
.wa-bvid small { font-weight: 700; font-size: .7rem; }
.wa-bvid.is-missing { background: #fff3c4; color: #6b4f00; }
.wa-link, .wa-doc { color: #0067a3; font-weight: 700; text-decoration: underline; overflow-wrap: anywhere; }

/* Who it is — the same person, the same colour, every time */
.wa-av.is-rose,   .wa-bname.is-rose   { color: #b0224f; } .wa-av.is-rose   { background: #ffd6e7; }
.wa-av.is-sky,    .wa-bname.is-sky    { color: #0067a3; } .wa-av.is-sky    { background: #dcefff; }
.wa-av.is-violet, .wa-bname.is-violet { color: #5b3a9e; } .wa-av.is-violet { background: #efe4ff; }
.wa-av.is-mint,   .wa-bname.is-mint   { color: #006b52; } .wa-av.is-mint   { background: #d9fbef; }
.wa-av.is-sun,    .wa-bname.is-sun    { color: #8a5a00; } .wa-av.is-sun    { background: #fff3c4; }

/* Where it went */
.wa-onto { display: inline-flex; align-items: center; font-weight: 800; font-size: .76rem; border-radius: 999px;
  padding: 1px 10px; background: #fff; color: #00513e; border: 1.5px solid #00a07a; }  /* white, deep green — "very hard to read that green on the green", 18 Oct */
.wa-onto.is-ask { background: #ffeaf3; color: #b0224f; border-color: #f48fb8; }
.wa-off { font: inherit; font-size: .76rem; font-weight: 700; color: #b0224f; background: none; border: 0; padding: 0;
  text-decoration: underline; cursor: pointer; }

/* Her buttons */
.wa-offer { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px;
  border: 1px solid #9fd4ff; background: #f3f9ff; border-radius: 10px; padding: 8px 10px; }
.wa-btns { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.wa-b { font: inherit; font-size: .74rem; font-weight: 800; line-height: 1.2; border-radius: 999px;
  padding: 3px 10px; border: 1.5px solid #25a244; background: #dcf8c6; color: #1d6b32; cursor: pointer; }
.wa-b:focus-visible, .wa-off:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
.wa-b:disabled { opacity: .55; cursor: default; }
.wa-b-go { border-color: #0091ea; background: #e3f3ff; color: #0067a3; }
.wa-b-plain { border-color: #f48fb8; background: #ffeaf3; color: #b0224f; }
/* MINIMISE, 22 Oct — solid pink like Arrived by email's (CHECK 237). */
.wa-b-min { border-color: var(--pink, #e8197a); background: var(--pink, #e8197a); color: #fff; }
.wa-min-count { font-weight: 600; font-size: .85em; opacity: .85; }
.wa-card .wa-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.wa-card.is-min { padding-top: 6px; padding-bottom: 6px; }
/* width: auto — the app's own `select` rule is full width, which put "Not
   needed" on a line of its own (seen in the made-up render, 18 Oct). */
.wa-put, .wa-pick select { font: inherit; font-size: .76rem; width: auto; flex: 0 1 280px; min-width: 0;
  max-width: 100%; height: auto; border-radius: 8px;
  border: 1.5px solid #8fdc9f; padding: 2px 6px; background: #fff; color: #2e1f2a; }
.wa-pick { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; font-weight: 700; color: #1d6b32; }

/* The whole chat, by day */
.wa-chat { max-height: 560px; overflow: auto; }
.wa-day { position: sticky; top: 0; z-index: 1; align-self: center; background: #fff; color: #1d6b32; font-weight: 800;
  font-size: .74rem; border-radius: 8px; padding: 3px 10px; box-shadow: 0 1px 1px rgba(46, 31, 42, .1); }
/* The jobs found in the chat (159) — her approved mock-up, 18 Oct. One card a
   job: the job in bold, the details as written, who said it, the to-do tag,
   the messages behind it one press away, and her buttons. Pink rail = to do,
   sky rail = booked or decided, mint rail = dealt with. */
.wa-job { background: #fff; border: 1px solid #c7efcf; border-left: 5px solid #e8197a; border-radius: 10px;
  padding: 7px 11px; display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.wa-job.is-fyi { border-left-color: #0091ea; }
.wa-job.is-closed { border-left-color: #00bfa5; opacity: .7; }
.wa-act { font-weight: 800; font-size: .92rem; color: #2e1f2a; line-height: 1.3; overflow-wrap: anywhere; }
.wa-det { font-size: .84rem; font-weight: 600; color: #2e1f2a; white-space: pre-wrap; overflow-wrap: anywhere; user-select: text; }
.wa-said { font-size: .74rem; color: #7a5a6c; }
.wa-show { font: inherit; font-size: .76rem; font-weight: 700; color: #0067a3; background: none; border: 0; padding: 0;
  text-decoration: underline; cursor: pointer; }
.wa-show:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
.wa-b-done { border-color: #00bfa5; background: #d9fbef; color: #006b52; }
.wa-closed { font-size: .76rem; font-weight: 800; color: #006b52; }
.wa-job .wa-chatbg { margin-top: 2px; }
/* One click from the chat — + Supplier (violet), + Budget (honey), "Is this
   X?" (pink). Each line says exactly what its click saves; mint once done.
   Her approved mock-up, 18 Oct. */
.wa-adds { display: flex; flex-direction: column; gap: 5px; border-top: 1px dashed #8fdc9f; padding-top: 6px; }
.wa-sub { font-weight: 800; font-size: .74rem; color: #1d6b32; }
.wa-add { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 8px; align-items: center; border-radius: 9px;
  padding: 5px 9px; border: 1.5px solid #f6d3e3; background: #fffafc; }
.wa-add.is-done { border-color: #00bfa5; background: #d9fbef; color: #006b52; font-weight: 800; font-size: .78rem; }
.wa-add.is-done .wa-what { font-weight: 600; }
.wa-what { font-size: .82rem; color: #2e1f2a; overflow-wrap: anywhere; }
.wa-what b { font-weight: 800; }
.wa-sum { color: #7a5a6c; font-size: .76rem; }
.wa-gap { color: #b0224f; font-weight: 800; }
.wa-go { font: inherit; font-size: .76rem; font-weight: 800; border-radius: 999px; padding: 3px 11px; border: 1.5px solid;
  cursor: pointer; white-space: nowrap; }
.wa-go:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
.wa-go:disabled { opacity: .55; cursor: default; }
.wa-go.is-sup { background: #efe4ff; color: #5b3a9e; border-color: #b69cf0; }
.wa-go.is-bud { background: #fff3c4; color: #6b4f00; border-color: #e0b400; }
.wa-go.is-ask { background: #ffeaf3; color: #b0224f; border-color: #f48fb8; }
@media (max-width: 560px) { .wa-add { grid-template-columns: 1fr; } }
/* 22 Oct (CHECK 253): where a job stands, the chat ≠ budget line, and the
   always-there "+ Supplier… / + Budget…" (her approved mock-up). */
.wa-add.is-differs { border-color: #f48fb8; background: #ffeaf3; color: #b0224f; font-weight: 800; font-size: .78rem; }
.wa-more { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.wa-sup-top { display: flex; gap: 12px; align-items: flex-start; margin-bottom: 8px; }
#ws-photo { max-width: 160px; max-height: 200px; border-radius: 10px; border: 1.5px solid #bfe6cf; object-fit: contain; }
.wa-sup-match { border-radius: 9px; padding: 7px 10px; border: 1.5px solid #00bfa5; background: #d9fbef; color: #006b52; font-size: .85rem; }
.wa-sup-match.is-stop { border-color: #f48fb8; background: #ffeaf3; color: #b0224f; }
/* Update contact (CHECK 254): NOW struck through beside SHOULD BE. */
.wa-add.is-update { border-color: #7cb6f2; background: #e8f3ff; }
.wa-go.is-con { background: #dcecff; color: #1d4f9a; border-color: #7cb6f2; }
.wa-now { color: #8a6f80; }
.wa-should { color: #b0224f; font-weight: 800; }
.wa-sup-ups { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; color: #2e1f2a; }
.wa-sup-up { display: flex; align-items: center; gap: 7px; font-size: .85rem; cursor: pointer; }
.wa-sup-up input { width: 18px; height: 18px; flex: none; accent-color: #00bfa5; }
@media print { .wa-strip, .wa-card { display: none !important; } }

/* ---------------------------------------------------------------------------
   THE COUPLE'S TO-DO ROW: GEE'S NOTE IN ITS OWN BOX, TWO BUTTONS YOU CAN SEE.
   Gee, 18 Oct, from Ceyhun's photo of Annika & Ceyhun's list: "Add a comment
   or a file is very small, and people can't see it… it's quite lost." Then,
   over six mock-ups: E2 with option 1 teal, "the color of the fonts should
   be black". The note gets a pale lavender box so it reads first; Comment /
   file is pale teal and Remind me pale blue, both with black words.
   ⚠️ COUPLE'S SCREEN ONLY (#portal-body). .task-remind-btn and .task-meta are
   drawn on Gee's own checklist too, and that list is not part of this ask.
   The "Gee" whose-job chip (.task-for) is untouched — it is butter, not pink.
   --------------------------------------------------------------------------- */
#portal-body .task-row .task-meta:has(.muted-note) {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 4px 10px;
  white-space: normal;
  overflow: visible;
  margin-top: 4px;
  padding: 8px 12px;
  background: #f4efff;
  border-left: 4px solid #c3aef2;
  border-radius: 8px;
}
#portal-body .task-row .task-meta:has(.muted-note) .muted-note {
  flex: 1 1 0;
  min-width: 0;
  display: block;
  padding: 0;
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  text-align: left;
  color: var(--ink, #2b2226);
  font-size: .92rem;
  line-height: 1.4;
}
#portal-body .task-row .task-does > .portal-thread:not(.is-ask) > summary {
  display: inline-block;
  opacity: 1;
  padding: 4px 13px;
  border: 1px solid #8fd8d1;
  border-radius: 999px;
  background: #d2f3f0;
  color: #000;
  font-size: .86rem;
  font-weight: 700;
}
#portal-body .task-row .task-does > .portal-thread:not(.is-ask) > summary::before { content: '📝 '; }
#portal-body .task-row .task-remind-btn {
  padding: 4px 13px;
  border: 1px solid #b9dcf5;
  background: #dbeeff;
  color: #000;
  font-size: .86rem;
  font-weight: 700;
}
#portal-body .task-row .task-remind-btn:hover { border-color: #0067a3; color: #000; }

/* 👥 TEAM VIEW (21 Oct, teamview.js). Gee: "they just see the running sheet and
   suppliers… I don't want them to be able to see the progress or the owing
   amount." Chosen from a mock-up on Solin & Rony's real wedding. They DO see
   planner-only lines and the Couple's details ("They should be able to see
   everything"). HIDDEN, not faded, except the run sheet's own lock, which
   fades its controls as she chose on 19 Oct. A screen, not a database lock. */
.team-view-btn {
  font: inherit;
  font-weight: 700;
  font-size: 13px;
  border-radius: 999px;
  padding: 5px 12px;
  border: 2px solid var(--v-sky, #0091ea);
  background: #fff;
  color: #0077c2;
  cursor: pointer;
  white-space: nowrap;
  margin-right: 10px;
}
.team-view-btn.is-on { background: var(--v-sky, #0091ea); color: #fff; }
.team-view-btn[hidden] { display: none; }
@media print { .team-view-btn { display: none !important; } }

/* Only Weddings in the top nav: Home lists every payment. */
body.team-view .modnav-item:not([data-module="weddings"]),
/* Only Run sheet and Suppliers, and no finding counts on them. */
body.team-view #app-view .subnav-item:not([data-tab="timeline"]):not([data-tab="suppliers"]),
body.team-view #app-view .subnav-item > *,
/* The wedding header: no Edit, Imported or Questionnaire. Couple's details stays. */
body.team-view #edit-wedding,
body.team-view #wd-imports,
body.team-view #wd-questionnaire,
/* No findings or import strips on either tab. */
body.team-view #app-view [id^="checks-"],
body.team-view #app-view [id^="imports-"],
/* The Weddings list: no New wedding, no checklist progress on a card. */
body.team-view #new-wedding,
body.team-view .wedding-card .card-progress,
/* Run sheet: nothing that adds, pastes, replaces or unlocks. */
body.team-view :is(#tl-lock, #tl-lock-float, #add-timeline-event, #paste-timeline,
  #tl-replace-open, #apply-timeline-template, #tl-select-all, #tl-edit-details,
  #save-timeline-template, #tl-add-supplier-table),
/* Suppliers: no adding, selecting, progress, coverage, findings or money. */
body.team-view :is(#add-supplier, #paste-suppliers, #sup-select-mode, #sup-select-all,
  #sup-cols-open, #sup-cols-panel, #supplier-coverage, #supplier-warnings, #sup-coc-venue),
body.team-view .check-inline:has(#show-progress),
body.team-view .sup-table :is(.sup-progress-col, .sup-c-progress, .sup-stages,
  .sup-cover-col, .sup-cover, .sup-owing-col, .sup-c-owing, .sup-owing, .sup-add-budget),
body.team-view .sup-table [data-sup-menu] { display: none !important; }

/* 👥 Which weddings a team member is on (21 Oct, 165, team.js). Per person in
   Settings → Your team, her choice. Sky like the Team view button, so the two
   read as one feature. */
.team-wed-btn {
  font: inherit; font-size: .82rem; font-weight: 700;
  border: 2px solid var(--v-sky, #0091ea); background: #fff; color: #0077c2;
  border-radius: 999px; padding: 3px 10px; cursor: pointer; margin-left: 10px;
}
.team-wed-btn.is-none { border-color: #f4a58f; color: #b3001b; }
.team-wed-need { margin-left: 10px; font-size: .8rem; color: #b3001b; }
.team-wed-panel {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 2px 14px; padding: 10px 14px; margin: 0 0 6px;
  background: #e3f0fd; border: 2px solid #9cc5ef; border-radius: var(--radius-sm, 8px);
}
.team-wed-row { display: flex; align-items: center; gap: 8px; padding: 3px 0; cursor: pointer; }
.team-wed-row input { width: 16px; height: 16px; flex: none; }
.team-wed-name { font-weight: 600; }
.team-wed-date { margin-left: auto; font-size: .8rem; color: var(--ink-faint, #7a6a8c); }

/* 👥 Planners on this wedding — Client portal tab (21 Oct, team.js). */
.portal-team { margin: 18px 0 6px; padding-top: 14px; border-top: 1px solid var(--line, #f0dce6); }
.portal-team .pt-head { margin: 0 0 8px; font-size: 1rem; }
.pt-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 12px; margin: 4px 0; background: #fff;
  border: 1px solid var(--line, #f0dce6); border-radius: var(--radius-sm, 8px);
}
.pt-who { font-weight: 600; }
.pt-state { margin-left: auto; font-size: .82rem; color: var(--ink-faint, #7a6a8c); }
.pt-row .team-wed-btn { margin-left: 0; }
.pt-add { display: flex; align-items: center; gap: 8px; margin: 8px 0 4px; font-weight: 600; flex-wrap: wrap; }

/* 👀 Previewing as one person — sky, the Team view colour. */
.preview-as-bar {
  position: sticky; top: 0; z-index: 70;
  background: #e3f0fd; border-bottom: 2px solid var(--v-sky, #0091ea); color: #0b4f86;
  padding: 8px 16px; text-align: center; font-size: 14px;
}
.preview-as-bar[hidden] { display: none; }
.preview-as-bar .pv-back {
  font: inherit; font-weight: 700; margin-left: 10px; cursor: pointer;
  border: 2px solid var(--v-sky, #0091ea); background: var(--v-sky, #0091ea); color: #fff;
  border-radius: 999px; padding: 3px 12px;
}
@media print { .preview-as-bar { display: none !important; } }

/* A team member's name (166) — borrows the couple's .access-name-* look. */
.team-name-wrap { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

/* 👥 Team view, second pass (21 Oct). Gee, after previewing as Amber: no tick
   boxes on the run sheet, no Set aside, no Clear note, no "Hide planner-only
   lines" ("They should be able to see everything"), no "No supplier · N", and
   on Suppliers no Copy emails / Copy phones / Copy Instagram table. */
body.team-view #wtab-timeline .tl-pick,
body.team-view #tl-picked-bar,
body.team-view :is(#tl-aside-chip, #tl-aside-bar),
body.team-view #wtab-timeline [data-clear-note],
body.team-view #wtab-timeline label:has(#tl-show-clear),
body.team-view #tl-hide-internal,
body.team-view #wtab-timeline label:has(#tl-only-orphans),
body.team-view #tl-keep-arrivals-wrap,
body.team-view :is(#copy-emails, #copy-phones, #copy-handles) { display: none !important; }
