/* v3 additions. app.css owns the tokens, the shell, cards, tables and buttons —
   this file adds only what v3 introduces, and every class is prefixed `v3-` so a
   v2 page loading app.css alone is untouched.

   Everything here exists to make an honest number look deliberate: an absent
   figure, a qualified figure, a refusal and a negative take each get a
   treatment, because the alternative is a page where a dash reads as a bug. */

/* ---------- absence (fmt.js `absent`) ---------- */
.v3-absent { color: var(--ink-2); font-weight: 500; border-bottom: 1px dotted var(--edge);
             cursor: help; }

/* ---------- qualifiers (flags.js) ----------
   A note, not an alarm: `approximate` is a fact about the data, not a fault. */
.v3-flag { font-size: 12.5px; line-height: 1.55; color: var(--ink-2); margin: 10px 0 0;
           padding: 9px 12px; border-radius: var(--r-md); background: var(--card-2);
           border-left: 2px solid var(--line-2); }
.v3-flag b { color: var(--ink); font-weight: 600; }
.v3-flag-approx { border-left-color: var(--clay); }
.v3-flag-unpriced { border-left-color: var(--sage-deep); }
.v3-flag-incomplete { border-left-color: var(--down); }

.v3-chip { font-size: 11px; border-radius: 4px; padding: 2px 7px; border: 1px solid var(--edge);
           white-space: nowrap; font-weight: 500; color: var(--ink-2); background: var(--card-2);
           cursor: help; }
.v3-chip-approx { border-color: var(--clay); color: var(--clay-ink); background: transparent; }
.v3-chip-incomplete { border-color: var(--down); color: var(--down); background: transparent; }

/* ---------- refusals (§6.5) ----------
   settle() refusing is the useful output, so it is a row that needs attention. */
.v3-refuse { font-size: 13px; line-height: 1.55; color: var(--ink-2); background: var(--card-2);
             border: 1px dashed var(--line-2); border-radius: var(--r-md); padding: 10px 12px; }
.v3-refuse b { color: var(--ink); font-weight: 600; }
.v3-refuse-msg { display: block; color: var(--ink-3); margin-top: 3px; }
.v3-refuse-inline { color: var(--ink-2); font-weight: 500; font-size: 12.5px; cursor: help;
                    border-bottom: 1px dotted var(--edge); }

/* ---------- a take that can go under ---------- */
.v3-take { font-weight: 600; }
td .v3-take, .v3-take.v3-col { font-variant-numeric: tabular-nums; }
.v3-neg { color: var(--down); }
.v3-short { font-size: 11px; color: var(--down); margin-left: 6px; font-weight: 500;
            text-transform: uppercase; letter-spacing: .4px; }

.v3-warn { border-color: var(--clay); }
/* Neutral, not alarming. A sync in progress is the product working, and a shop
   that just connected would otherwise be greeted by a warning-coloured box
   telling them their brand new account has a problem. The warning treatment is
   reserved for data that is actually missing and is not coming. */
.v3-note { border-color: var(--accent, #93B0CB); }
.v3-transparent { font-size: 12.5px; color: var(--ink-3); line-height: 1.55; margin: 12px 0 0; }

/* ---------- the estimated-revenue explainer (once per page) ---------- */
.v3-explain { font-size: 12.5px; color: var(--ink-3); line-height: 1.6; margin: 18px 0 0;
              border-top: 1px solid var(--line); padding-top: 12px; }
.v3-explain b { color: var(--ink-2); font-weight: 600; }
.v3-info { display: inline-flex; align-items: center; justify-content: center; width: 16px;
           height: 16px; border-radius: 50%; border: 1px solid var(--edge); color: var(--ink-2);
           font-size: 10px; font-weight: 700; margin-left: 5px; cursor: help; vertical-align: 1px; }

/* ---------- range / period bars ---------- */
.v3-range { flex-wrap: wrap; row-gap: 8px; }
/* [hidden] LOSES TO .controls, and that is not a detail.
   `hidden` is a UA stylesheet rule — `[hidden] { display: none }` at the lowest
   possible specificity — so any author rule that sets `display` beats it.
   .controls sets `display: flex`, so `<div class="controls" hidden>` rendered
   open: the shop view's custom date range was on screen from the first paint
   with aria-expanded="false" beside it, saying the opposite. Restated here so
   the attribute means what it says. */
.controls[hidden], .v3-range[hidden] { display: none; }
.v3-lab { font-size: 12px; color: var(--ink-3); font-weight: 500; }
.v3-range input.ctl { min-width: 0; }
.v3-week { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; row-gap: 8px; }
.v3-week .v3-wk { font-size: 13px; font-weight: 600; color: var(--ink);
                  font-variant-numeric: tabular-nums; }

/* ---------- barber list / selector ---------- */
/* ONE card, hairline rows — not a stack of separate boxes.
   Each row used to carry its own border and radius with an 8px gap between, so
   a four-barber roster read as four floating cards and the team list did not
   look like anything else in the product. Every other list in app.css (the
   payroll table, Recent Activity, Goals) is rows inside one bordered container
   separated by hairlines, and this is now that. The row keeps its own hover and
   its selected ring; it just no longer draws its own box. */
.v3-rows { background: var(--card); border: 1px solid var(--line);
           border-radius: var(--r-xl); overflow: hidden; }
.v3-row .sub { color: var(--ink-2); }
.v3-row { display: grid; grid-template-columns: 1fr auto; gap: 4px 14px; align-items: center;
          width: 100%; text-align: left; background: transparent; border: 0;
          border-top: 1px solid var(--line); border-radius: 0;
          padding: 15px 20px; font: inherit; color: var(--ink);
          cursor: pointer; min-height: 48px; }
.v3-row:first-child { border-top: 0; }
.v3-row:hover { background: var(--card-2); }
.v3-row .nm { font-size: 14px; font-weight: 600; }
.v3-row .sub { font-size: 12px; color: var(--ink-3); margin-top: 3px; }
.v3-row .fig { display: block; border: 0; margin: 0; text-align: right; }
.v3-row .amt { font-size: 15.5px; font-weight: 600; letter-spacing: -.02em;
               font-variant-numeric: tabular-nums; }
.v3-row .amtsub { font-size: 12px; color: var(--ink-3); margin-top: 3px; }
/* `.v3-row.on` used to live here; nothing ever set it. A selected state, if
   one is ever wanted here, should be box-shadow only. */
.v3-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 6px; }

/* ---------- assistant sentences: verbatim, unlabelled ----------
   No badge, no colour band, no ranking. docs/coach-v3.md §6. */
.v3-said { list-style: none; margin: 0; padding: 0; }
.v3-said li { font-size: 13.5px; line-height: 1.6; color: var(--ink); padding: 10px 0;
              border-top: 1px solid var(--line); }
.v3-said li:first-child { border-top: 0; }
.v3-said .cav { display: block; font-size: 12.5px; color: var(--ink-3); margin-top: 4px; }

/* ---------- forms (plan, invite) ---------- */
.v3-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px 14px;
           align-items: end; }
.v3-field { display: grid; gap: 6px; min-width: 0; }
.v3-field label { font-size: 12px; color: var(--ink-3); font-weight: 500; }
.v3-field input, .v3-field select, .v3-field textarea {
  background: var(--card); border: 1px solid var(--edge); border-radius: var(--r-md);
  color: var(--ink); font: inherit; font-size: 13px; padding: 9px 11px; min-height: 40px;
  width: 100%; min-width: 0; }
.v3-field textarea { min-height: 62px; resize: vertical; }
.v3-field .hint { font-size: 11.5px; color: var(--ink-3); line-height: 1.45; }
.v3-wide { grid-column: 1 / -1; }
.v3-check { display: flex; align-items: flex-start; gap: 10px; font-size: 13px; color: var(--ink);
            line-height: 1.5; }
/* The one native control left on these pages. A default checkbox draws its own
   grey box from the operating system, which on the near-black ground read as a
   pale smudge and was the last thing on the Team page that looked like it came
   from somewhere else. Same border, radius and accent as every other control in
   app.css; the box still draws at 18px.

   The hit target actually GREW, which is worth stating because it is a side
   effect rather than an intention. Two things carry it: the .v3-check label,
   which has min-height 44px further down this file, and a 44x44 ::after on the
   input itself. That pseudo-element has been in the file all along but never
   rendered — a native checkbox is a replaced element and draws no pseudo. With
   appearance:none it is an ordinary box, so the ::after is now live and the
   input has the 2.5.5 target the label was previously providing on its own. */
.v3-check input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  width: 18px; height: 18px; margin: 2px 0 0; flex: 0 0 18px;
  border: 1px solid var(--edge); border-radius: 4px; background: var(--card-2);
  cursor: pointer; display: grid; place-content: center;
  transition: background .15s var(--ease), border-color .15s var(--ease); }
.v3-check input[type="checkbox"]::before {
  content: ""; width: 4.5px; height: 9px; margin-top: -2px;
  border: solid var(--on-accent); border-width: 0 2px 2px 0;
  transform: rotate(45deg) scale(0); transition: transform .15s var(--ease); }
.v3-check input[type="checkbox"]:checked {
  background: var(--accent); border-color: var(--accent-deep); }
.v3-check input[type="checkbox"]:checked::before { transform: rotate(45deg) scale(1); }
.v3-check input[type="checkbox"]:focus-visible { outline: none; box-shadow: var(--focus); }
.v3-check input[type="checkbox"]:disabled {
  opacity: .45; cursor: default; background: var(--card); }
@media (prefers-reduced-motion: reduce) {
  .v3-check input[type="checkbox"],
  .v3-check input[type="checkbox"]::before { transition: none; }
}
.v3-check .hint { display: block; font-size: 11.5px; color: var(--ink-3); margin-top: 3px; }
button.v3-primary { background: var(--ink); color: var(--on-ink); border: 1px solid var(--ink);
  border-radius: var(--r-md); font: inherit; font-size: 13px; font-weight: 600; padding: 0 16px;
  min-height: 40px; cursor: pointer; }
button.v3-primary:disabled { background: var(--card-2); border-color: var(--line); color: var(--ink-3);
  cursor: default; }

/* A key/value table, not a column of money. */
.v3-defs td.num { font-variant-numeric: normal; }

/* ---------- plan history / old plan ---------- */
.v3-old { background: var(--card-2); border-radius: var(--r-md); padding: 11px 13px; font-size: 13px;
          color: var(--ink-2); line-height: 1.55; }
.v3-old b { color: var(--ink); font-weight: 600; }
.v3-gap { color: var(--down); font-weight: 500; }

/* ---------- results of a batch (invites, bulk plans) ---------- */
.v3-result { display: grid; gap: 7px; margin-top: 14px; }
.v3-result div { font-size: 13px; line-height: 1.5; padding: 9px 12px; border-radius: var(--r-md);
                 background: var(--card-2); color: var(--ink-2); }
.v3-result b { color: var(--ink); font-weight: 600; }
.v3-result .bad { border-left: 2px solid var(--down); }
.v3-result .skip { border-left: 2px solid var(--line-2); }
.v3-result .good { border-left: 2px solid var(--up); }

/* ---------- invite preview text ---------- */
.v3-msg { background: var(--card-2); border-radius: var(--r-md); padding: 13px 15px; font-size: 13px;
          line-height: 1.6; white-space: pre-wrap; color: var(--ink); margin: 0;
          font-family: var(--sans); overflow-wrap: anywhere; }
.v3-msg-h { font-size: 12px; color: var(--ink-3); margin: 0 0 6px; font-weight: 500; }

/* ---------- scrollable table on a phone ---------- */
/* No negative margin here: it made the scroller 4px wider than its parent, which
   is 2px of overflow on every ancestor and a sweep that reports a problem it
   does not have. */
.v3-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.v3-scroll table { min-width: 560px; }
/* A SCROLLABLE BOX HAS TO BE REACHABLE BY KEYBOARD. axe at 360 caught this on
   payroll as `scrollable-region-focusable`, and it was true everywhere one of
   these tables overflowed: a mouse or a thumb could scroll it, a keyboard
   could not, so the columns past the right edge were simply unreachable
   without a pointer. Every .v3-scroll now carries tabindex="0" and a named
   role, which is the fix axe asks for - and a focusable thing needs a visible
   focus ring, so it gets one: the product's own --focus, an OUTER box-shadow,
   the same ring every other control uses.
   It has to stay an outer ring and nothing may clip it. The first cut of the
   phone rules below put a mask-image on this element, which cut the ring off
   at the border box and left it invisible at exactly the widths where the
   table scrolls. An outline would have been cut the same way. The mask is
   gone; if anything like it comes back, it belongs on a child. */
.v3-scroll:focus-visible { outline: none; box-shadow: var(--focus); border-radius: var(--r-sm); }
/* THE INVITES TABLE ON A PHONE. Six columns shared 460px inside a 358px
   screen, which gave a barber's name 58px - about four characters - and put
   the State cell off the right edge with no sign that anything was there.
   Measured at 360, 390 and 430; the same at all three.
   Widening the table is what fixes it; squeezing it smaller only ever makes
   the columns worse.
   THE TABLE STAYS A TABLE. The tempting fix is the usual responsive trick -
   display:block every row into a little card with pseudo-element labels. It
   looks better than this does. It also destroys the table semantics, so a
   screen reader stops announcing "Barber, Ray" and starts reading six
   unlabelled cells in a row: a real loss traded for a cosmetic gain, on the one
   page where a shop owner checks who was invited and whether it worked. The
   stacked version is in the report as a proposal rather than done here. */
@media (max-width: 620px) {
  /* The table still scrolls, and it is now WIDER than it was rather than
     narrower - 520px instead of 460px - so the six columns have room to be
     read. The first attempt at this went the other way: min-width 0 and
     overflow-wrap:anywhere, which did make it fit the screen, and broke
     "never" into "neve/r", "pending" into "pendin/g" and "Revoke" into
     "Rev/oke". Fitting is not the goal; being readable is, and a table that
     has to break words in half to fit has not fitted, it has just stopped
     admitting that it does not.
     So: a comfortable width, a floor under the barber's name so it can never
     be squeezed to four characters again, and breaking allowed ONLY on the
     cell that holds an unbreakable token - a masked phone number or an
     email. */
  .v3-scroll table { min-width: 520px; }
  .v3-scroll th:first-child, .v3-scroll td:first-child { min-width: 96px; }
  .v3-scroll td.dim { overflow-wrap: anywhere; }
  /* THE EDGE FADE IS GONE, and it was mine. A mask-image on this element did
     signal the scroll, and it did two things I did not look for:
       - it clipped the FOCUS RING. --focus is an outer box-shadow, the mask
         cuts everything outside the border box, so the ring I had just added
         for keyboard users was invisible at exactly the widths where the table
         scrolls and the ring is the only thing telling you where you are. An
         outline would have been masked too.
       - at the end of the scroll it landed on the Revoke button - the one
         action in an invites row - and greyed out 40% of it.
     A signpost that hides the destination is worse than no signpost. What
     signals the scroll now is the thing that always did, and it is the
     conventional cue: a column is visibly cut off at the right edge, because
     the table is 520px in a 358px box. The region also announces itself as
     "scrolls sideways" to a screen reader.
     THERE IS NO TRAILING PADDING RULE HERE, and there was for one commit. It
     was `.v3-scroll table { padding-right: 20px }`, meant to keep the last
     cell off the edge, and it did nothing at all: app.css sets
     border-collapse:collapse on every table, and the collapsing border model
     ignores padding on the table box. Measured - the table's right edge, the
     last cell's right edge and the Revoke button's right edge were all 520px
     with the rule in place. Removed rather than left as a line that looks like
     it is doing something. */
}

/* ---------- accept page (no shell) ---------- */
.v3-centre { min-height: 100vh; display: flex; align-items: center; justify-content: center;
             padding: 24px 16px; }
.v3-centre .card { width: 100%; max-width: 460px; }
.v3-centre h1 { font-size: 21px; margin-bottom: 6px; }

/* Sections are divided by a hairline, the way the bands on the landing page are.
   These headings used to float in the gap above their own content with nothing
   holding them to it, so "Invites sent" and "Needs attention" read as loose text
   on the page rather than as the start of a section. The rule does the dividing;
   the heading stays sentence case, because several of these are sentences and
   tracked caps on a sentence is a label pretending to be a title. */
.v3-sec { margin-top: 14px; }
/* Only a section that announces itself gets the divider. A .v3-sec holding just
   a card (an invite panel) already has that card's own border, and a
   rule directly above it would read as a double line; an empty one (#planhost,
   until a pay plan is opened) would draw a stray rule across the page. */
.v3-sec:has(> h2) { margin-top: 30px; padding-top: 24px; border-top: 1px solid var(--line); }
.v3-sec:empty { display: none; }
.v3-sec > h2 { font-size: 15px; font-weight: 600; margin: 0 0 14px; letter-spacing: -.016em; }
.v3-sub { font-size: 12.5px; color: var(--ink-3); margin: -6px 0 12px; line-height: 1.55; }
.v3-mode { font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-2);
           border: 1px solid var(--edge); border-radius: var(--r-pill); padding: 3px 9px;
           font-weight: 600; white-space: nowrap; }
.v3-err { border-left: 2px solid var(--down); }

/* The health band is a sentence about a person, not a tracked-caps label, so it
   keeps its own casing: "Steady 5.8/10". Barber side only. */
.v3-score { font-size: 13px; font-weight: 600; color: var(--ink); white-space: nowrap;
            border: 1px solid var(--edge); border-radius: var(--r-pill); padding: 4px 11px; }


/* ---------- touch targets, last word ----------
   app.css states the 44px floor for the shared controls, but these three are
   declared in THIS file, which loads after it - so the floor has to be restated
   here or the later declaration wins and nothing moves. Measured before this
   existed: v3-primary 40px, the roster checkbox 18x18. */
@media (max-width: 860px) {
  button.v3-primary { min-height: 44px; }
  .v3-field input, .v3-field select { min-height: 44px; }
  .v3-week button.ghost, .v3-range button.ghost { min-width: 44px; }
}

/* AT EVERY WIDTH, NOT JUST ON A PHONE.
   The floor above was written as a phone rule, which left "Apply", "Sync now"
   and every other ghost button on the v3 screens at 36px on a desktop — and a
   desktop is a touch surface on any laptop with a touchscreen, or a tablet wide
   enough to still be showing the sidebar. 36px clears the 24px WCAG 2.5.8 asks
   at AA, so this is 2.5.5's target rather than a failure being repaired, and it
   costs 8px of height on a control that already had padding around it.
   Scoped to v3.css, so the v2 pages stay exactly as they shipped.

   Unqualified rather than a list of ancestors: the first attempt named .card,
   .controls, .v3-sec and #ownerroot, and missed the "Pay plan" buttons in the
   roster rows on Team — which sit in a .v3-rows directly in the page, in none of
   those. A floor with holes in it is worse than no floor, because the holes are
   the bits nobody measured. */
button.ghost, button.v3-primary { min-height: 44px; }
/* A checkbox cannot be padded - it is a replaced element - so the row it sits in
   carries the target instead, at every width. The box still draws at 18px. */
.v3-check { min-height: 44px; align-items: center; }
.v3-check input, .v3-row input[type="checkbox"] { position: relative; }
.v3-check input::after, .v3-row input[type="checkbox"]::after {
  content: ""; position: absolute; left: 50%; top: 50%; width: 44px; height: 44px;
  transform: translate(-50%, -50%); }


/* ===========================================================================
   THE SHOP VIEW, ABOVE THE PHONE BREAKPOINT
   ===========================================================================
   The owner screen used to open on four equal tiles and a pair of date inputs.
   Four numbers the same size is no hero — the reader has to rank them, which is
   the job the layout was supposed to have done — and a date input is a control
   you fill in rather than one you press. Below 768px mobile.css had already
   fixed both; above it nothing had.

   So these are the two missing desktop pieces, and they are deliberately the
   SAME two ideas the phone layout uses rather than new ones: a period control
   you press that sits directly under the figure it scopes, and one lead figure
   carrying a comparison with a named basis.

   Everything else on that screen is already in app.css — .mrev for the hero
   tile, .fig for the supporting row, .card for the surfaces, .ctile/.tdisc for
   the expandable tile, .v3-rows for the team list — and is reused rather than
   restyled, so the shop reads as the same product as the chair.
   =========================================================================== */

/* ---------- the period control ----------
   ONE native <select>, at the top of both breakpoints. This was a segment of
   four buttons plus a disclosure holding two date inputs and an Apply; Logan
   asked for a single dropdown, and the custom range went with the segment.

   A native select rather than a built one, so keyboard operation, the focus
   ring, the accessible name/role/value and the phone's own picker wheel are the
   browser's job. .ctl in app.css already gives it the surface, the --edge border
   at 1.4.11's 3:1, and the 44px floor at every width; the rules below are the
   handful of things .ctl does not know about this particular control.

   NOT styled as an open dropdown, and no custom arrow: the OS draws one, and on
   a phone the control is a wheel rather than a list, so painting a caret would
   be decoration competing with the real affordance. */
.v3-periodwrap { display: flex; align-items: center; gap: 9px; flex-wrap: nowrap; }
.v3-periodlab { font-size: 12px; font-weight: 600; letter-spacing: .04em;
                text-transform: uppercase; color: var(--ink-2); white-space: nowrap; }
/* 44px AT EVERY WIDTH. app.css gives .ctl that floor only inside its ≤860px
   block, so on a desktop this measured 132x36 — and "the dropdown is a 44px
   target" was the requirement, not "on a phone it is". A period control is also
   the single most-pressed thing on the screen. */
select.v3-period { font-size: 13.5px; font-weight: 600; min-width: 132px;
                   min-height: 44px; padding-right: 34px; cursor: pointer; }
select.v3-period:focus-visible { outline: none; box-shadow: var(--focus); }

/* THE PHONE COPY, in a row appended to the sticky header by paintMobileOwner.
   It is the same <select>; what differs is where it sits and that its label is
   read but not drawn. */
@media (max-width: 767px) {
  /* Appended to .m-top — the sticky element — rather than to .m-top-big, which
     mobile.css hides on scroll. So this row stays pinned to the top of the
     viewport for the whole page, collapsed header or not.

     It carries the header's own background, because a sticky row with a
     transparent ground has the page's content scrolling through it. */
  .m-periodbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
                 padding-top: 8px; padding-bottom: 10px; background: var(--bg);
                 border-bottom: 1px solid var(--line); }
  .m-periodbar select.v3-period { min-width: 132px; font-size: 14px; }
  /* The label is redundant here: the selected option reads "30 days" and the row
     sits under the shop's name at the top of the screen, so "PERIOD" in front of
     it is a word doing no work on the surface with the least room for one. The
     <label> stays in the markup for the accessible name and is taken off screen
     rather than removed — display:none would take it out of the tree with it. */
  .m-periodbar .v3-periodlab {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
  }
  /* The dates the choice resolves to, beside the control when they fit and under
     it when they do not. --ink-ground is 5.5:1 on --bg (app.css). */
  .m-periodwhen { font-size: 12.5px; color: var(--ink-ground, var(--ink-3));
                  font-variant-numeric: tabular-nums; }
  /* The compact bar already repeats the period as a label; with the dropdown now
     permanently on screen beneath it, that is the same fact twice in 52px. */
  .m-top.is-stuck .m-top-compact .cl { display: none; }
}

/* ---------- the hero's comparison ----------
   .mrev already carries the tile, the label and the figure. This is the line
   underneath: a direction, a magnitude, and the window it is measured against.
   A percentage with no named basis is noise, and this is where the basis goes. */
.v3-cmp { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
          font-size: 12.5px; color: var(--ink-2); line-height: 1.5; }
.v3-cmp .d { font-weight: 600; font-size: 13.5px; }
.v3-cmp .d.up { color: var(--up); }
.v3-cmp .d.dn { color: var(--down); }
.v3-cmp .basis { color: var(--ink-3); }

/* The lead tile gets the lead figure's size, matching .fig-lead's first cell. */
.topline .mrev.lead { flex: 1 1 340px; }
.topline .mrev.lead .v { font-size: 40px; letter-spacing: -.038em; }
@media (max-width: 1100px) { .topline .mrev.lead .v { font-size: 33px; } }

/* ---------- the client mix ----------
   Returning against new, as a ring, with the two categories separated by
   LIGHTNESS inside the brand's own blue family and by a ring-versus-disc
   swatch. Both cues, for the reason app.css's --cat-new comment sets out at
   length: two hues alone measured 1.01:1 in greyscale. Tokens are app.css's, so
   the day and night versions cannot drift apart from the barber view's. */
/* app.css sets `.tdisc-p p { margin: 0 }`, which is right for the chair's
   one-paragraph note and wrong the moment there are two: the explanation ran on
   as a single block with a sentence boundary nobody could see. */
#mixpanel p + p { margin-top: 9px; }

.v3-mix { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; margin-top: 4px; }
.v3-mix .leg { display: grid; gap: 9px; min-width: 0; }
.v3-mix .leg .row { display: flex; align-items: center; gap: 9px; font-size: 13px;
                    color: var(--ink-2); }
.v3-mix .leg .sw { width: 13px; height: 13px; border-radius: 50%; flex: 0 0 13px; }
.v3-mix .leg .sw.solid { background: var(--cat-return); }
.v3-mix .leg .sw.ring { background: transparent; box-shadow: inset 0 0 0 3.5px var(--cat-new); }
.v3-mix .leg .nm { color: var(--ink-2); }
.v3-mix .leg .vl { margin-left: auto; font-weight: 600; color: var(--ink);
                   font-variant-numeric: tabular-nums; }

/* ---------- the trend ----------
   Same treatment the barber's chart has: recessive gridlines, one series, no
   legend, the card heading naming what is drawn. */
.v3-trend .gl { stroke: var(--track); stroke-width: 1; }
.v3-trend text { fill: var(--ink-3); font-size: 10.5px; }

/* ---------- the team list ----------
   Every row is a button that opens that barber's own dashboard, so it needs to
   look pressable and clear the touch floor. The order is the roster's, by name;
   there is no sort control here and no column that could be sorted by money. */
.v3-rows .v3-row { min-height: 60px; }
.v3-rows button.v3-row { width: 100%; text-align: left; background: transparent;
                         font: inherit; color: var(--ink); cursor: pointer; }
.v3-rows button.v3-row:focus-visible { outline: none; box-shadow: var(--focus); }
/* A row that opens something says so at rest. Three columns rather than two, so
   the chevron has its own track and the figure does not shift about as names and
   amounts change width. --ink-3 against the card is 5.8:1, well past the 3:1
   1.4.11 asks of a graphic that carries meaning. */
.v3-rows button.v3-open { grid-template-columns: 1fr auto auto; gap: 4px 12px; }
.v3-rows button.v3-open .cv { grid-column: 3; grid-row: 1 / span 2; display: flex;
                              align-items: center; color: var(--ink-3); }
.v3-rows button.v3-open:hover .cv { color: var(--ink); }
/* The phone list is a different element entirely — the .m-row, with
   its own grid and its own tap treatment — so nothing here needs a phone variant. */

/* The back control on the drill-in. A real button in the flow above the head,
   so it is the first thing in the tab order on that screen. */
.v3-back { display: inline-flex; align-items: center; gap: 7px; min-height: 44px;
           font-size: 13px; font-weight: 600; color: var(--accent-deep);
           background: none; border: 0; padding: 0; cursor: pointer; font-family: inherit; }
.v3-back:focus-visible { outline: none; box-shadow: var(--focus); border-radius: var(--r-sm); }

/* A desktop-only empty state, matching mobile.css's .m-empty so the two
   breakpoints say the same thing in the same shape. */
.v3-blank { background: var(--card); border: 1px solid var(--line);
            border-radius: var(--r-xl); padding: 26px 24px; max-width: 620px; }
.v3-blank h3 { font-size: 17px; font-weight: 600; letter-spacing: -.015em; margin: 0 0 8px; }
.v3-blank p { font-size: 13.5px; line-height: 1.6; color: var(--ink-2); margin: 0; }
.v3-blank p + p { margin-top: 10px; }
.v3-blank .hint { font-size: 12.5px; color: var(--ink-3); margin-top: 12px; }
.v3-blank .act { display: inline-flex; align-items: center; min-height: 44px; padding: 0 17px;
                 margin-top: 16px; border-radius: var(--r-md); background: var(--accent-deep);
                 color: var(--on-accent); font-size: 14px; font-weight: 600; text-decoration: none; }

/* Both caveats on one bar. The separator is a middot with breathing room, not a
   border: two sentences sharing a note need to be told apart, and a second rule
   line inside a bar that exists to reduce rule lines would defeat the point. */
.v3-flag-both .v3-flag-sep { opacity: .45; margin: 0 .5em; }
