/* zBizReport — Migdalor Ventures' portfolio reporting.
   Matched to migdalor.de: lime, navy, cream, lavender, ice blue.

   THREE RULES this sheet keeps, because the app looked bulky and uneven
   before they existed:

   1. ONLY the five brand colours. No greens, no reds, no greys. Where a
      lighter tone is needed it is one of the brand colours at a fixed
      opacity from the scale below — never a new hue, never an ad-hoc tint.
   2. ONE spacing scale (--s1…--s5) and ONE page gutter. Every card, band
      and row uses them, so edges line up down the whole page.
   3. LIGHT type. DM Sans 300/400/500 only. Nothing heavier than 500, and
      size + letter-spacing carry hierarchy instead of weight.
*/

@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500&display=swap');

:root {
  /* FOUR brand colours, and nothing else. The ice blue that used to be a
     fifth is gone: it was the "light blue" showing up on every focus ring
     and button hover, and it is not part of the brand. */
  --lime:     #d7e044;
  --navy:     #1c194b;
  --cream:    #eeeeea;
  --lavender: #ccb2d6;

  /* the only permitted softenings — the brand colours at fixed opacities,
     never a new hue */
  --navy-70: rgba(28, 25, 75, 0.70);   /* secondary text */
  --navy-45: rgba(28, 25, 75, 0.45);   /* hints, empty states */
  --navy-12: rgba(28, 25, 75, 0.12);   /* hairlines */
  --lime-18: rgba(215, 224, 68, 0.18); /* a success wash */
  --lime-45: rgba(215, 224, 68, 0.45); /* focus ring */
  --lav-30:  rgba(204, 178, 214, 0.30);/* hover wash */

  /* one spacing scale */
  --s1: 0.35rem;
  --s2: 0.7rem;
  --s3: 1.15rem;
  --s4: 2rem;
  --s5: 3.25rem;

  --gutter: var(--s4);
  --radius: 0.5rem;
  --hair: 1px solid var(--navy-12);

  /* zbase tokens, pointed at the brand */
  --z-primary: #1c194b;
  --z-primary-contrast: #d7e044;
  --z-secondary: #ccb2d6;
  --z-bg: #eeeeea;
  --z-accent: #ccb2d6;

  /* ── the framework's own SEMANTIC colours ────────────────────────────
     zbase ships #52B788 for success and #5CA9FF for info, and uses them
     for every confirmation message, focus ring and hover. That is where
     the stray green and light blue were coming from — not from this
     sheet. Repointing the tokens fixes them everywhere at once, including
     components this app has not styled by hand. */
  --color-success:        var(--lime);
  --color-success-light:  var(--lime-18);
  --color-info:           var(--lavender);
  --color-info-light:     var(--lav-30);
  --color-warning:        var(--lavender);
  --color-warning-light:  var(--lav-30);
  /* an error still has to read as an error, so it takes the darkest brand
     colour rather than a red from outside the palette */
  --color-error:          var(--navy);
  --color-error-light:    var(--navy-12);
  --color-primary:        var(--navy);
  --color-primary-light:  var(--navy-12);
  --color-secondary:      var(--lavender);
  --color-secondary-light:var(--lav-30);
}

* { box-sizing: border-box; }

body {
  background: var(--cream);
  /* the last row of a page used to sit hard against the bottom of the
     window, with the card's own border as the final pixel — it read as
     though the page had been cut off rather than ended */
  padding-bottom: var(--s5);
  color: var(--navy);
  font-family: 'DM Sans', -apple-system, Helvetica, sans-serif;
  font-weight: 400;
  font-size: 15px;
  line-height: 1.5;
  margin: 0;
  -webkit-font-smoothing: antialiased;
}

/* ── one shared page rhythm ───────────────────────────────────────────────
   ONE rule decides the left edge of everything: each visible band is inset
   from the window by the gutter, and NO container adds an inset of its own.

   The earlier version put the gutter on containers, which left every element
   that wasn't inside one — section headings, venture cards, report rows —
   sitting hard against the window edge while its neighbours were indented.
   Insetting the bands themselves means a heading lines up with the card
   below it no matter which block it happens to live in. */
.zbizreport-hero { padding-left: var(--gutter); padding-right: var(--gutter); }

.zbizreport-actions,
.zbizreport-nav,
.zbizreport-tiles,
.zbizreport-section-heading,
.zbizreport-completion,
.zbizreport-quarter-note,
.zbizreport-status-banner,
.zbizreport-selected-bar,
.zbizreport-venture-head,
.zbizreport-checklist,
.zbizreport-sc-head,
.zbizreport-sc-row,
.zbizreport-sc-answer-row,
.zbizreport-sc-simple-row,
.zbizreport-title-head,
.zbizreport-title-row,
.zbizreport-past-head,
.zbizreport-past-row,
.zbizreport-venture-card,
.zbizreport-attention-row,
.zbizreport-doc-row,
.zbizreport-comments,
.zbizreport-year,
.zbizreport-year-note,
.zbizreport-year-title-row,
.zbizreport-year-link,
.zbizreport-wizard-line {
  margin-left: var(--gutter);
  margin-right: var(--gutter);
}

/* containers now only carry vertical rhythm */
.zbizreport-scorecard,
.zbizreport-wizard-progress { padding-left: 0; padding-right: 0; }

/* ── headings everywhere, not just in the hero ────────────────────────────
   The hero rule alone left every heading OUTSIDE it (the sign-in title, for
   one) at the browser's bold default — heavy next to the light hero above
   it. Weight is set once, here, so nothing can drift back to bold. */
h1, h2, h3, h4, .zH1, .zH2, .zH3 {
  font-weight: 300;
  letter-spacing: -0.01em;
  color: var(--navy);
  margin: 0 0 var(--s2);
}
h1, .zH1 { font-size: 1.9rem; line-height: 1.15; }
h2, .zH2 { font-size: 1.3rem; }
h3, .zH3 { font-size: 1.05rem; font-weight: 400; }

/* ── standalone pages (sign in) ───────────────────────────────────────────
   A page that is only a form still needs the page gutter, or it sits a few
   pixels off from the hero band above it — and a login form stretched to
   the full window width reads as a wall, not an invitation.

   The gutter goes on the PAGE block only. Putting it on `form` as well
   indented the fields twice, so the title, the fields and the hero above
   them each sat at a different left edge.

   The real class is `.zDialog-container` — zLogin renders as a dialog, not
   as anything named "login". Measured in the browser rather than guessed:
   `.zLogin` matches nothing, so every rule hung on it did nothing. */
.zDialog-container {
  padding-left: var(--gutter) !important;
  padding-right: var(--gutter) !important;
  background: none !important;
  border: none !important;
  box-shadow: none !important;
}
.zDialog-form { max-width: 26rem; }

/* inside a modal the same markup IS the frame: keep its card, drop the
   page gutter, and let it use the full width of the overlay */
.zModal-overlay .zDialog-container,
.zModal-card .zDialog-container {
  padding: var(--s3) !important;
  background: #fff !important;
}
.zModal-overlay .zDialog-form, .zModal-card .zDialog-form { max-width: none; }

/* ── hero: a quiet lime band, not a shout ─────────────────────────────── */
.zbizreport-hero {
  background: var(--lime);
  padding-top: var(--s4);
  padding-bottom: var(--s4);
  margin-bottom: var(--s5);
}
.zbizreport-hero h1,
.zbizreport-hero .zH1 {
  font-weight: 300;              /* was 900 — the single biggest cause of "bulky" */
  font-size: 2.25rem;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--navy) !important;
  margin: 0 0 var(--s1);
  text-transform: none;          /* was lowercase, which fought the brand name */
}
.zbizreport-hero .zbizreport-lead {
  color: var(--navy-70);
  font-size: 0.95rem;
  max-width: 46rem;
  margin: 0;
}
.zbizreport-hero .zbizreport-hint {
  color: var(--navy-70);
  font-size: 0.85rem;
  max-width: 46rem;
  margin: var(--s1) 0 0;
}

/* ── buttons: outline by default, one filled emphasis ─────────────────── */
.zbizreport-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-bottom: var(--s4);
}
/* a row of buttons that belongs to the PAGE rather than to a card sits in
   the same gutter as everything else on it. Sign out stood alone against
   the left edge while the portfolio below it was indented — Yael drew a
   line down the margin, 2026-09-13. Not on `.zbizreport-actions` itself,
   which is also the class on the button rows inside cards, where a gutter
   would push them out of the card they belong to. */
.zbizreport-actions-page {
  margin-left: var(--gutter);
  margin-right: var(--gutter);
}
.zBtn {
  font-family: inherit !important;
  font-weight: 500 !important;
  font-size: 0.85rem !important;
  border-radius: var(--radius) !important;   /* was a 999px pill — heavy at this size */
  padding: 0.5rem 0.9rem !important;
  border: 1px solid var(--navy-12) !important;
  box-shadow: none !important;
  text-transform: none !important;
  letter-spacing: 0.01em;
}
.zBtn-primary { background: var(--navy) !important; color: var(--lime) !important; border-color: var(--navy) !important; }
.zBtn-primary:hover { background: #262260 !important; }
.zBtn-secondary { background: transparent !important; color: var(--navy) !important; }
.zBtn-secondary:hover { background: var(--lav-30) !important; }

/* ── section headings: a rule and a word, not a filled band ───────────── */
.zbizreport-section-heading {
  font-weight: 500;
  font-size: 0.8rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--navy-70);
  background: none;                       /* was a solid lavender block */
  border-bottom: 2px solid var(--lavender);
  border-radius: 0;
  /* a whole blank line above, and real air beneath the rule. A section is a
     separate thing from the one before it and has to LOOK separate — this
     gap is what makes a 42-row sheet scannable rather than a wall. */
  padding: 0 0 var(--s3);
  margin-top: 4rem;
  margin-bottom: var(--s4);
}
.zbizreport-scorecard > *:first-child .zbizreport-section-heading { margin-top: 0; }

/* ── the scorecard ────────────────────────────────────────────────────── */
.zbizreport-scorecard { padding-bottom: var(--s5); }

.zbizreport-quarter-note {
  display: inline-block;
  background: none;
  color: var(--navy);
  border: var(--hair);
  border-radius: var(--radius);
  font-weight: 500;
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: var(--s1) var(--s2);
  margin-bottom: var(--s2);
}
.zbizreport-completion {
  display: block;
  margin-bottom: var(--s3);
  font-size: 0.85rem;
  color: var(--navy-70);
}

/* the "there is no Save button, and that is fine" line. Quiet on purpose:
   it is reassurance, not an instruction, and it sits under the progress
   count where someone looks when wondering where they are. */
.zbizreport-saving-note {
  border-left: 3px solid var(--lavender);
  padding: var(--s1) 0 var(--s1) var(--s2);
  margin-left: var(--gutter);
  margin-right: var(--gutter);
  margin-bottom: var(--s3);
  font-size: 0.85rem;
  color: var(--navy-70);
  max-width: 52rem;
}
.zbizreport-saving-note p { margin: 0; }

/* row + header share one grid, so every column lines up all the way down */
.zbizreport-sc-head,
.zbizreport-sc-row,
.zbizreport-sc-answer-row,
.zbizreport-sc-simple-row {
  display: grid;
  /* seven tracks: label, four figures, comments, and the Fill in button.

     The button track is a FIXED width, not `auto`. Each grid sizes its own
     tracks, and the header's 7th cell is empty — so under `auto` it measured
     ~0 in the header and a button's width in every row. The six visible
     columns then divided different leftovers, and every heading sat right of
     the figures it named: "Comments" landed over the Fill in button, which is
     what Yael reported seeing. A fixed track reserves the same space in both,
     so the columns line up. Same fault, same fix, as the catalogue table. */
  grid-template-columns: minmax(0, 2.4fr) repeat(4, minmax(0, 0.62fr)) minmax(0, 1.4fr) 5.5rem;
  gap: var(--s3);
  padding: var(--s3) var(--s3) calc(var(--s3) + 0.15rem);
  align-items: start;
}
/* the header is a single line of small caps and needs less height than a
   row carrying a label plus its definition */
.zbizreport-sc-head { padding: var(--s2) var(--s3); }
/* NOT STICKY, and that is a correction rather than an omission.

   It was `position: sticky; top: 0`, which pins a header to the window for
   as long as its CONTAINING BLOCK is on screen — and its containing block
   is the whole scorecard. So each header stayed stuck all the way down the
   page, captioning the catalogue, the rights table and the worked-out
   figures in turn with the column names of a grid the reader had left
   several sections ago. Itai Aaronsohn, 2026-10-29: the headers "appear
   above unrelated sections instead of directly above the KPI table".

   The view had already worked this out and said so — the header is
   REPEATED per block of sections precisely "rather than made sticky",
   because one header cannot serve two grids with a different table sitting
   between them. The sticky went in afterwards and reintroduced the exact
   fault that comment exists to prevent. Bounding it instead would mean
   wrapping every header and its own rows in a container, and the rights
   table splits one of those runs in two — three wrappers and a fourth
   header to buy a convenience on a page that already names its columns
   three times. */
.zbizreport-sc-head {
  background: var(--navy);
  color: var(--lime);
  font-size: 0.67rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  border-radius: var(--radius);
  margin-bottom: var(--s2);
}
/* the four FIGURE headings follow their figures to the right edge. Comments
   is excluded here rather than overridden below: it heads a column of words,
   which are left-aligned like any prose, so a right-aligned heading floats
   at the far edge of the column instead of sitting over the text it names.
   Excluding it inline also sidesteps a specificity trap — three :not()s beat
   a plain :nth-child() override no matter which rule comes last. */
.zbizreport-sc-head > *:not(:first-child):not(:last-child):not(:nth-child(6)) {
  text-align: right;
}

.zbizreport-sc-row,
.zbizreport-sc-answer-row,
.zbizreport-sc-simple-row {
  background: #fff;
  border: var(--hair);
  border-radius: var(--radius);
  margin-bottom: var(--s2);
  box-shadow: none;                        /* the shadows made 42 rows feel heavy */
}

/* A row whose answer is words, not figures.

   It reuses the figure row's grid EXACTLY rather than defining a simpler
   three-track one of its own. Two grids with different track counts would
   put the KPI name in a different place on each, and the two shapes sit
   directly beneath one another in the same section — the labels have to form
   one straight column down the page or the section reads as two tables that
   got stapled together. So: same seven tracks, and the answer spans the five
   the figures would have used. */
.zbizreport-sc-answer {
  grid-column: 2 / 7;
  font-weight: 400;
  line-height: 1.5;
}
.zbizreport-sc-answer-note {
  display: block;
  margin-top: var(--s2);
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--navy-45);
}
.zbizreport-sc-answer-note:empty { display: none; }

/* A row that is just one value: a serial number, a date against a fixed
   deadline. Same seven-track grid as every other row so the KPI names form
   one straight column down the page, with the value spanning the tracks the
   figures would have used. */
.zbizreport-sc-simple-value {
  /* starts under ACTUAL, not under TARGET. These rows have no target — the
     whole point of the shape — and on the flat tech sheet a raised figure
     sat in the target column directly above Revenue's real target, which
     reads as a plan number nobody planned. */
  grid-column: 3 / 7;
  font-weight: 400;
  line-height: 1.5;
}
/* the deadline, above the answer and quieter than it. The label is written
   here rather than in the content so that a row with no target renders an
   empty element — and an empty element takes the `:empty` rule below and
   disappears, prefix and all. */
.zbizreport-sc-simple-due {
  margin: 0 0 0.15rem;
  font-size: 0.75rem;
  color: var(--navy-45);
}
.zbizreport-sc-simple-due::before { content: "Target · "; }
.zbizreport-sc-simple-due:empty { display: none; }
.zbizreport-sc-simple-now {
  margin: 0;
  font-variant-numeric: tabular-nums;
}
/* who filed the report, on ScorecardSubmissionRow's one row. Sits under the
   date, same quiet treatment as the Target line above it — a byline, not a
   headline figure. */
.zbizreport-sc-simple-by {
  margin: 0.15rem 0 0;
  font-size: 0.75rem;
  color: var(--navy-45);
}
.zbizreport-sc-simple-by::before { content: "Filed by · "; }
.zbizreport-sc-simple-by:empty { display: none; }

/* the sources under an answer. Small and set apart, because they are where
   to go and read the thing, not part of the answer itself. */
.zbizreport-sc-answer-links {
  margin-top: var(--s2);
  font-size: 0.8rem;
  line-height: 1.6;
}
.zbizreport-sc-answer-links:empty { display: none; }
.zbizreport-sc-answer-links p { margin: 0; }
.zbizreport-sc-answer-links a {
  color: var(--navy);
  text-decoration: underline;
  /* a pasted URL is long and must not push the row wider than the page */
  overflow-wrap: anywhere;
}
/* The per-row Fill in button exists ONLY while the quarter is still a
   Draft. Once it is handed in the rows are locked — for Migdalor too, not
   just for the venture — and `save_scorecard_entry_by_label` refuses the
   write. Showing a button that can only be refused is worse than showing
   none, so the default here is hidden and the DraftMarker turns them on,
   the same way PublishedMarker turns the print button on. Keyed off the
   marker rather than a per-row flag because the lock is a property of the
   whole REPORT, not of any one row. */
.zbizreport-sc-edit { display: none; }
body:has(.zbizreport-is-draft) .zbizreport-sc-edit { display: block; }

.zbizreport-sc-edit .zBtn {
  font-size: 0.75rem !important;
  padding: 0.3rem 0.7rem !important;
  white-space: nowrap;
}
.zbizreport-sc-label { font-weight: 500; }
.zbizreport-sc-note {
  display: block;
  font-weight: 400;
  font-size: 0.75rem;
  line-height: 1.4;
  color: var(--navy-45);
  margin-top: var(--s2);
}
.zbizreport-sc-cell {
  font-variant-numeric: tabular-nums;
  text-align: right;
  font-weight: 400;
}
.zbizreport-sc-status {
  text-align: right;
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--navy-70);
}
.zbizreport-sc-comment { font-size: 0.8rem; color: var(--navy-70); }

/* ── tiles: equal width, equal air, one accent edge ───────────────────── */
.zbizreport-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--s2);
  margin-bottom: var(--s4);
}
.zbizreport-tile {
  background: #fff;
  border: var(--hair);
  border-left: 3px solid var(--lavender);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  font-size: 0.85rem;
  font-weight: 500;
}
/* the portfolio's mix, under its count. Quieter than the number above it:
   how many companies there are is the headline, how they split is the
   detail that qualifies it. */
.zbizreport-tile-split {
  margin-top: 0.15rem;
  font-size: 0.78rem;
  font-weight: 400;
  color: var(--navy-70);
}

/* the only two tiles that carry meaning get the two brand accents */
.zbizreport-tile-ontrack { border-left-color: var(--lime); }
.zbizreport-tile-below   { border-left-color: var(--navy); }

/* a company's own facts, on its own page. Quieter than Migdalor's panel —
   this is a card you read once and then get on with reporting, not the
   working surface of the page. */
.zbizreport-company-card {
  background: #fff;
  border: var(--hair);
  border-left: 3px solid var(--lavender);
  border-radius: var(--radius);
  padding: var(--s3);
  margin: 0 var(--gutter) var(--s4);
}
.zbizreport-company-name {
  margin: 0 0 var(--s2);
  font-size: 1.05rem;
  font-weight: 500;
}
.zbizreport-company-line {
  margin: 0 0 var(--s1);
  font-size: 0.85rem;
  color: var(--navy-70);
}
.zbizreport-company-line:last-child { margin-bottom: 0; }

/* ── type colours: cultural and tech, told apart at a glance ─────────────
   Yael, 2026-09-13: "let's give all tech companies one color, and another
   color for all cultural ventures" — and then, when I invented a teal for
   tech: "the green you chose for tech should be within our brand colors.
   so why not our brand's green?" She was right, and the palette above says
   so in as many words: "never a new hue". Both types now take a brand
   colour at brand opacities — lavender for cultural, lime for tech —
   with navy ink on each. */
:root {
  --cultural: var(--lavender);
  --cultural-wash: var(--lav-30);
  --tech: var(--lime);
  --tech-wash: var(--lime-18);
}
.zbizreport-type-chip {
  margin: 0;
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 500;
  padding: 0.12rem 0.45rem;
  border-radius: 0.25rem;
  white-space: nowrap;
  color: var(--navy);
}
.zbizreport-type-chip:empty { display: none; }
.zbizreport-type-cultural { background: var(--cultural); }
.zbizreport-type-tech     { background: var(--tech); }

/* and the card's own colouring follows the chip inside it. `_zClass`
   cannot be built from a token, so the type cannot put a class on the
   card — but `:has()` can read which chip rendered, which comes to the
   same thing.

   Three ways, not one: the edge, a wash behind the card's top band, and
   the chip. A 3px edge alone was too quiet to register, and on a cultural
   company it was the lavender the card already had, so nothing appeared
   to change. */
.zbizreport-board-row:has(.zbizreport-type-cultural:not(:empty)) { border-left-color: var(--cultural); }
.zbizreport-board-row:has(.zbizreport-type-tech:not(:empty))     { border-left-color: var(--tech); }
.zbizreport-glance-row:has(.zbizreport-type-cultural:not(:empty)) { border-left-color: var(--cultural); }
.zbizreport-glance-row:has(.zbizreport-type-tech:not(:empty))     { border-left-color: var(--tech); }

.zbizreport-board-row:has(.zbizreport-type-cultural:not(:empty)) .zbizreport-board-head,
.zbizreport-board-row:has(.zbizreport-type-tech:not(:empty)) .zbizreport-board-head {
  margin: calc(var(--s4) * -1) calc(var(--s4) * -1) var(--s3);
  padding: var(--s3) var(--s4);
  border-bottom: none;
  border-radius: var(--radius) var(--radius) 0 0;
}
.zbizreport-board-row:has(.zbizreport-type-cultural:not(:empty)) .zbizreport-board-head {
  background: var(--cultural-wash);
}
.zbizreport-board-row:has(.zbizreport-type-tech:not(:empty)) .zbizreport-board-head {
  background: var(--tech-wash);
}

/* ── the two grades ──────────────────────────────────────────────────────
   Yael, 2026-09-13: "more like a grading system (maybe like phillip tried
   to do with the traffic lights) for both economic and cultural — 3 stages
   / signs maybe? instead of text", and on the wording: "let's maybe stick
   to the same flag thing we have withing the reports? green red yellow".

   So the two ends are the report's OWN green and red (#2e7d5b / #a63d40,
   the same values the scorecard's verdicts use — a second green would be
   a second meaning). The middle is navy at hairline strength, not an
   amber: the palette forbids new hues, and "neither good nor bad" reads
   honestly as unmarked rather than as a third traffic light nobody has
   been taught. */
.zbizreport-grades {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s3);
  margin: var(--s2) 0 0;
}
.zbizreport-grade {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
}
.zbizreport-grade-label {
  margin: 0;
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--navy-45);
}
.zbizreport-grade-value {
  margin: 0;
  font-size: 0.82rem;
  font-weight: 500;
  padding: 0.1rem 0.45rem;
  border-radius: 0.25rem;
  white-space: nowrap;
}
.zbizreport-grade-value:empty { display: none; }
.zbizreport-grade-good  { color: #2e7d5b; background: var(--lime-18); }
.zbizreport-grade-watch { color: var(--navy-70); background: var(--navy-12); }
.zbizreport-grade-bad   { color: #a63d40; background: rgba(166, 61, 64, 0.12); }
/* a grade nobody has set takes no room — a company Migdalor has not judged
   should read as unjudged, not as a row of empty boxes.
   <
   NOT `:has(:empty)`, which was the first attempt and hid every grade on
   the page: a group holds three cells of which two are ALWAYS empty, so
   "has an empty cell" is true of all of them. It has to be "has no filled
   cell". The tests could not catch it — `textContent` reads hidden
   elements perfectly well, so all six assertions passed against pills
   nobody could see. Caught in the screenshot (2026-09-13). */
.zbizreport-grade:not(:has(.zbizreport-grade-value:not(:empty))) { display: none; }

/* in the strip the grades sit without their labels — the column position
   says which is which, and four companies' worth of repeated words is the
   noise the strip exists to avoid */
.zbizreport-glance-grades { margin: 0; gap: var(--s1); }
.zbizreport-glance-grades .zbizreport-grade-label { display: none; }
.zbizreport-glance-grades .zbizreport-grade-value { font-size: 0.72rem; }

/* ── the glance strip ────────────────────────────────────────────────────
   The whole portfolio, one line each, above the cards. Its job is to be
   read in one pass, so it carries a name, a type, a verdict and ONE
   figure — a strip with everything on it is the cards again in smaller
   type. */
.zbizreport-glance {
  margin: 0 var(--gutter) var(--s5);
}
/* the header sits in the same gutter as the strip it names, with no gap
   under it — it is the strip's first row, not a block of its own. */
.zbizreport-glance-wrap {
  margin: 0 var(--gutter);
}
/* the strip's column names. Same grid, quieter type — it reads as a
   table head rather than as a sixth company. */
.zbizreport-glance-head {
  background: none;
  border: none;
  border-left: none;
  color: var(--navy-45);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding-bottom: 0;
  margin-bottom: 0;
}
/* each heading is a bare zText, which renders as a <p> and so inherits the
   sheet's paragraph bottom margin. With `align-items: center` a child is
   centred INCLUDING its margin, so any heading that wrapped or sat in a
   taller track rode lower than its neighbours — "Impact" did, by about a
   third of a line, which is enough to look unfinished on the first thing
   on the board's page. Zeroing the margin and pinning the alignment puts
   all six on one baseline whatever their track does. */
.zbizreport-glance-head > * {
  margin: 0;
  align-self: center;
  line-height: 1.2;
}

.zbizreport-glance-row {
  display: grid;
  /* name · last published · type · this quarter · economic · impact ·
     site. SEVEN fixed tracks and seven children — each column is a
     container, even when what it holds is empty. Bare chips and flags
     (two and three elements of which one renders) used to land in
     whichever track came next, so the columns shifted company by company.
     The two grades were ONE cell until 2026-09-28; they are two now, so
     the header above can name each of them. */
  /* FIXED tracks, and the row hugs the LEFT. Two faults at once, both
     reported 2026-09-28 ("fix the alignment" / "put all info to the
     left side - the way it is now is very confusing"):

     1. `auto` tracks size to their own content, and every row is its
        OWN grid — so each company's chips landed in a different place
        and the header could not sit over anything. Widths have to be
        stated for separate grids to agree on a column.
     2. `minmax(0, 1fr)` on the name gave it all the slack, which shoved
        the other six columns against the right edge with a lake of
        white between. Now the name has a stated width like everything
        else, `justify-content: start` keeps the row packed left, and
        the leftover space falls off the right end where it belongs. */
  grid-template-columns:
    minmax(9rem, 15rem) 9.5rem 6.5rem 7.5rem 6.5rem 6.5rem auto;
  justify-content: start;
  align-items: center;
  gap: var(--s2) var(--s3);
  background: #fff;
  border: var(--hair);
  border-left: 3px solid var(--navy-12);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  margin-bottom: var(--s1);
}
/* one column of the strip. It exists to hold its track: what is inside
   may be one chip of three, or nothing at all. */
.zbizreport-glance-cell {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--s1);
  min-width: 0;
}
/* and every direct child of a row starts at its column's left edge —
   a chip or a flag centred in its track drifts away from the label
   above it, which is the confusion being fixed. */
.zbizreport-glance-row > * { justify-self: start; }

/* which quarter the line is reporting. Labelled by the stylesheet so that
   a company with nothing published shows neither the label nor a gap. */
.zbizreport-glance-published {
  margin: 0;
  font-size: 0.7rem;
  color: var(--navy-45);
  white-space: nowrap;
}
/* beside the name, where Yael put it: part of reading which company this
   is and how current the line is, rather than one more verdict */
.zbizreport-glance-row > .zbizreport-glance-cell:first-of-type { justify-self: start; }
.zbizreport-glance-published:empty { display: none; }
/* the words "LAST PUBLISHED " used to be drawn here, because the strip had
   no column headings and a bare quarter label said nothing. The headings
   arrived on 2026-09-28 and this was not removed with them, so every row
   read "LAST PUBLISHED 2030-Q4" under a column already headed LAST
   PUBLISHED. The heading is the better of the two: it is written once.
   The `:empty` rule above stays — it is what keeps a company with nothing
   published from leaving a gap. */

.zbizreport-glance-name {
  margin: 0;
  font-size: 0.92rem;
  font-weight: 500;
}
/* ONE GLYPH per company — "here, put only a flag please, no text and no
   %" (Yael, 2026-09-13). Bigger than the text it replaced, because a
   column of single marks is the thing the strip is read for. */
.zbizreport-glance-flag {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1;
  white-space: nowrap;
}
.zbizreport-glance-flag:empty { display: none; }
/* the figure reads as one unit: what it is, what it was, what was planned */
.zbizreport-glance-figure {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.35rem;
  justify-self: end;
  text-align: right;
}
.zbizreport-glance-figure-label {
  margin: 0;
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--navy-45);
}
.zbizreport-glance-figure-label:empty { display: none; }
.zbizreport-glance-figure-value {
  margin: 0;
  font-size: 0.92rem;
  font-weight: 500;
}
.zbizreport-glance-figure-value:empty { display: none; }
.zbizreport-glance-figure-plan {
  margin: 0;
  font-size: 0.72rem;
  color: var(--navy-45);
}
.zbizreport-glance-figure-plan:empty { display: none; }

/* narrow screens: the six columns stop being columns. A grid of fixed
   tracks on a phone gives each company a line of slivers, so the row
   becomes a wrapping line instead — name first, everything else flowing
   after it, the link last. (The old rule here still described the
   five-track layout with a figure in it, which no longer exists.) */
@media (max-width: 44rem) {
  .zbizreport-glance-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--s1) var(--s2);
  }
  .zbizreport-glance-name { flex: 1 0 100%; }
  .zbizreport-board-site { margin-left: auto; }
}

/* ── the board's view: one card per company ───────────────────────────────
   Designed with Yael 2026-09-09, rebuilt 2026-09-13 after she read it:
   "looks terrible graphicaly. please make it as clear as you can."

   What was wrong: every line was the same weight, so a card was a wall of
   small grey labels and the eye had nowhere to land. It is now four bands
   with a clear order of importance — WHO and how they are doing, THE
   FIGURES, MIGDALOR'S POSITION, and what you can do — separated by space
   and hairlines rather than by the reader's patience. */
.zbizreport-board {
  margin: 0 var(--gutter) var(--s5);
}
.zbizreport-board-row {
  background: #fff;
  border: var(--hair);
  border-left: 3px solid var(--lavender);
  border-radius: var(--radius);
  /* tighter than it was — Yael, 2026-09-13: "the card seems like it's
     using too much 'empty' space and could be a bit more denssed.
     without hurting the elegance". The hierarchy does the separating
     now (size, colour, one hairline per band), so the space between
     bands no longer has to. */
  padding: var(--s3);
  margin-bottom: var(--s2);
}
/* a company with nothing published: the same card, a quieter edge. It
   still carries Migdalor's position, which for those companies is the
   whole of what there is to read. */
.zbizreport-board-row-quiet { border-left-color: var(--navy-12); }

/* ── band one: who, and how they are doing ─────────────────────────────── */
.zbizreport-board-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s1) var(--s2);
  padding-bottom: var(--s2);
  border-bottom: var(--hair);
  margin-bottom: var(--s2);
}
/* the company name is the one thing a reader scans down the page for, so
   it is the only large type on the card */
.zbizreport-board-name {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 500;
  letter-spacing: -0.01em;
}
.zbizreport-board-what {
  margin: 0;
  font-size: 0.78rem;
  color: var(--navy-45);
}
/* the company's own site, as a ROUND button at the right edge — Yael,
   2026-09-13: "i think the website is missplaced... maybe a round bottun,
   something completely different." As a text link mid-band it read as one
   more fact about the company and broke the line of type between the
   sector and the quarter. Now it lives on the STRIP — one per company
   line, the same place every time, since the strip is the part of the
   page that names companies rather than quarters.
   Present when there is one, gone when there is not: a link to nowhere is
   worse than no link — and every company had one until today, because a
   missing site reached the column as the string "None". */
.zbizreport-board-site {
  align-self: center;
  justify-self: end;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* a PILL that says "website", not a bare arrow in a circle — Yael,
     2026-09-13: "no one could imagine they lead to the company's
     website. let's just add text into the circle - website". A glyph is
     only legible to whoever chose it. */
  gap: 0.25rem;
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--navy-12);
  border-radius: 999px;
  background: #fff;
  color: var(--navy-70);
  font-size: 0.72rem;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
}
.zbizreport-board-site:hover,
.zbizreport-board-site:focus-visible {
  background: var(--navy);
  border-color: var(--navy);
  color: var(--lime);
}
.zbizreport-board-site:not([href]),
.zbizreport-board-site[href=""],
/* and the three things "no website" turned into on its way through zData
   before the rebuild learned to leave the column alone: a Python None
   became the string "None", an empty string became the two characters
   '' , and an unresolved token stays as its own name. Fixed at the
   source; these stay because rows written by the old code are still out
   there, including in the hosted copy. */
.zbizreport-board-site[href="None"],
.zbizreport-board-site[href="''"],
.zbizreport-board-site[href="%website"],
/* and the one zURL itself produces: an empty href is documented as a
   PLACEHOLDER — "renders, no-op" — so it reaches the browser as `#`,
   which is neither missing nor empty. This is the rule that actually
   does the hiding; the four above are for values that should never have
   been written and still sit in rows written by the old code. */
.zbizreport-board-site[href="#"] { display: none; }

.zbizreport-board-which {
  /* everything after it is pushed to the right edge: the round site
     button and the way in */
  margin: 0 auto 0 0;
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--navy);
  background: #fff;
  padding: 0.15rem 0.5rem;
  border-radius: 0.25rem;
}
/* the verdict, pushed right so every company's sits in the same place —
   that column of glyphs is what makes the page scannable */
.zbizreport-board-flag {
  margin: 0 0 0 auto;
  font-size: 0.85rem;
  font-weight: 500;
  white-space: nowrap;
}
.zbizreport-board-flag:empty { display: none; }

/* ── band two: the four figures ────────────────────────────────────────── */
.zbizreport-board-figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--s2);
}
.zbizreport-board-slot:empty { display: none; }
.zbizreport-board-slot-label {
  margin: 0 0 0.2rem;
  font-size: 0.65rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--navy-45);
}
.zbizreport-board-slot-label:empty { display: none; }
/* the figure and its flag on one line — the flag reads as a mark ON the
   number, which is what it is */
.zbizreport-board-slot-value {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
}
/* the flag is now the variance PERCENTAGE, so it is a number beside a
   number — a step down in size keeps the actual the hero of the cell and
   leaves the colour to say which way it went. */
.zbizreport-board-slot-flag {
  margin: 0;
  font-size: 0.8rem;
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
}
.zbizreport-board-slot-flag:empty { display: none; }

/* the actual is the figure being reported: the biggest thing in the band,
   and the only one in full navy */
.zbizreport-board-slot-q {
  margin: 0;
  font-size: 1.1rem;
  font-weight: 500;
  line-height: 1.2;
}
.zbizreport-board-slot-q:empty { display: none; }
/* what it was measured against, and then the year — each a step quieter,
   so the eye reads down in order of importance rather than choosing */
.zbizreport-board-slot-plan {
  margin: 0.1rem 0 0;
  font-size: 0.75rem;
  color: var(--navy-70);
}
.zbizreport-board-slot-plan:empty { display: none; }
.zbizreport-board-slot-y {
  margin: 0.05rem 0 0;
  font-size: 0.7rem;
  color: var(--navy-45);
}
.zbizreport-board-slot-y:empty { display: none; }

/* the deal — the FACT the judgement is about, now the first line under
   "Migdalor's position" (Yael, 2026-09-13: "should come here, right
   before the recommendation"). No band and no rule of its own any more:
   inside the position it is one of its parts, and a hairline there would
   cut the block it belongs to in half. */
.zbizreport-board-deal {
  margin: 0 0 var(--s2);
  font-size: 0.85rem;
  color: var(--navy-70);
}
.zbizreport-board-deal:empty { display: none; }
.zbizreport-board-deal::before {
  content: "THE DEAL";
  display: block;
  font-size: 0.65rem;
  letter-spacing: 0.08em;
  color: var(--navy-45);
  margin-bottom: 0.15rem;
}

/* ── the way into the quarter, in the head band ───────────────────────────
   It used to be a band of its own at the foot: a hairline, a tinted
   ground and two paddings holding ONE button — Yael, 2026-09-13: "i think
   that last strip of the card is redundant, don't you?" It was. Beside
   the quarter it names, it costs nothing and says more, and the card now
   ends on Migdalor's position rather than on furniture. */
.zbizreport-board-go {
  align-self: center;
  background: var(--navy) !important;
  border-color: var(--navy) !important;
  color: var(--lime) !important;
  font-size: 0.82rem !important;
  padding: 0.4rem 0.8rem !important;
}

/* ── Migdalor's position, on the board's line ─────────────────────────────
   Adapted from Phillip's decision memo. Its whole idea is that a cultural
   investment has two values and only one of them is money, so the two
   columns sit side by side at equal weight — putting cultural underneath
   economic would answer the question the memo exists to reopen. */
.zbizreport-position {
  margin-top: var(--s2);
  padding-top: var(--s2);
  border-top: var(--hair);
}
/* the recommendation is the one line a board member must not miss */
.zbizreport-position-rec {
  margin: 0 0 var(--s2);
  font-size: 0.92rem;
  font-weight: 500;
}
.zbizreport-position-rec:empty { display: none; }

.zbizreport-position-columns {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--s3);
}
.zbizreport-position-col:empty { display: none; }

/* each column labelled by CSS rather than by a cell, so an empty column
   takes its heading with it when it collapses */
.zbizreport-position-economic:empty,
.zbizreport-position-cultural:empty,
.zbizreport-position-impact:empty,
.zbizreport-position-conditions:empty,
.zbizreport-position-deal:empty,
.zbizreport-position-risk:empty,
.zbizreport-position-when:empty { display: none; }

.zbizreport-position-economic::before { content: "ECONOMIC VALUE"; }
.zbizreport-position-cultural::before { content: "CULTURAL VALUE"; }
/* the same column for a startup, under the question that fits one —
   Yael, 2026-09-13: "'do good'/impact to the world kind'a question". */
.zbizreport-position-impact::before { content: "IMPACT"; }
.zbizreport-position-conditions::before { content: "CONDITIONS"; }
.zbizreport-position-deal::before { content: "THE DEAL"; }
.zbizreport-position-risk::before { content: "RETURN AND RISK"; }
.zbizreport-position-when::before { content: "POSITION LAST REVIEWED "; }

.zbizreport-position-economic,
.zbizreport-position-cultural,
.zbizreport-position-impact,
.zbizreport-position-conditions,
.zbizreport-position-deal,
.zbizreport-position-risk {
  margin: 0 0 var(--s2);
  font-size: 0.82rem;
  color: var(--navy-70);
  white-space: pre-wrap;
}
.zbizreport-position-economic::before,
.zbizreport-position-cultural::before,
.zbizreport-position-impact::before,
.zbizreport-position-conditions::before,
.zbizreport-position-deal::before,
.zbizreport-position-risk::before,
.zbizreport-position-when::before {
  display: block;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  color: var(--navy-45);
  margin-bottom: 0.15rem;
}
/* the conditions are what the recommendation DEPENDS on — an edge marks
   them as commitments rather than more prose */
.zbizreport-position-conditions {
  border-left: 2px solid var(--lavender);
  padding-left: var(--s2);
}
.zbizreport-position-when {
  margin: var(--s2) 0 0;
  font-size: 0.7rem;
  color: var(--navy-45);
}
.zbizreport-position-when::before { display: inline; }



/* Migdalor's words about the QUARTER, under their standing position. Two
   different voices about two different spans of time, so the band says
   which one this is rather than leaving an unattributed quote. */
/* HIGHLIGHTED, and named for what it is — Yael, 2026-09-13: "this part
   should be highlighted and called: mv review on this quarter". It was a
   quiet indented line; it is now a card of its own inside the quarter
   band, so Migdalor's words about the quarter carry the weight of the
   thing they are about. */
/* MV's review of the quarter, between the deal and the standing position
   — the card reads downward in widening time. Highlighted as its own
   block because it is Migdalor speaking, not another field. */
.zbizreport-board-review {
  margin: var(--s2) 0 0;
  padding: var(--s2) var(--s3);
  background: var(--cream);
  border-left: 3px solid var(--navy);
  border-radius: var(--radius);
}
.zbizreport-board-review::before {
  content: "MV REVIEW ON THIS QUARTER";
  display: block;
  font-size: 0.65rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  color: var(--navy-45);
  margin-bottom: 0.2rem;
}
.zbizreport-board-review-text {
  /* a real newline is a line break here. The tech card's review is two
     answers to two different questions (the valuation and the exit) and
     they arrived as one run-on sentence; a cultural review is one
     paragraph and is unaffected. */
  white-space: pre-line;
  margin: 0;
  font-size: 0.88rem;
  color: var(--navy-70);
  white-space: pre-wrap;
}
/* a company with no review written has no band at all — the label must
   not outlive the thing it labels */
.zbizreport-board-review:has(.zbizreport-board-review-text:empty) { display: none; }

/* the deal and the standing position on Migdalor's company panel. Quiet:
   it is context for the quarters below, not the subject of the page —
   the board card is where this argument is made at full volume. */

/* THE DEAL, carrying straight on from the purple header. Two blocks that
   read as one: the header rounds its top corners and this rounds its
   bottom ones, with no gap between them. Yael, 2026-09-22: "i want it
   imbeded on the top with all company's details."

   A separate block because the deal lives on VenturePosition while the
   header is drawn from the Ventures row — one pattern, one reel. */

/* A POSITION BLOCK WITH NOTHING IN IT PRINTS NOTHING. The heading is a
   sibling of the cells it introduces, so collapsing empty cells left
   "Migdalor's position" standing alone on every tech card — the position
   form was removed for tech on 2026-09-21, so there is never anything
   under it. Caught in a board screenshot, not by a test: `textContent`
   reads a heading whether or not it introduces anything.

   Hidden only when EVERY part is empty, so a company with just a
   recommendation still shows it. */
.zbizreport-position:not(:has(.zbizreport-position-rec:not(:empty))):not(:has(.zbizreport-grade-value:not(:empty))):not(:has(.zbizreport-position-conditions:not(:empty))):not(:has(.zbizreport-position-risk:not(:empty))) {
  display: none;
}

/* the company's standing facts, in the purple header — Yael, 2026-09-21:
   "make sure that all information from company details is shown at the
   deep purple part at the top". Quiet lines under the name: they are
   context for the quarters below, not the subject of the page. Each
   collapses when empty, so an unfilled company shows a name and a type
   rather than a column of blanks. */
.zbizreport-venture-headfact {
  margin: 0.15rem 0 0;
  font-size: 0.82rem;
  color: rgba(255, 255, 255, 0.72);
}
.zbizreport-venture-headfact:empty { display: none; }
.zbizreport-headfact-founded::before  { content: "Founded "; opacity: 0.6; }
.zbizreport-headfact-invested::before { content: "We have invested "; opacity: 0.6; }
.zbizreport-venture-headsite {
  display: inline-block;
  margin-top: 0.3rem;
  font-size: 0.82rem;
  color: var(--lime);
}
/* the same three guards the board card's link carries: an empty href
   reaches the browser as a placeholder, not as nothing */
.zbizreport-venture-headsite:not([href]),
.zbizreport-venture-headsite[href=""],
.zbizreport-venture-headsite[href="None"],
.zbizreport-venture-headsite[href="#"] { display: none; }

/* a row holding something destructive. Not red — red on a page of
   financial figures reads as "these numbers are bad". Set apart by space
   and a quiet rule instead, and the button itself is understated: this is
   a thing you go looking for, not one you meet. */
.zbizreport-act-danger {
  margin-top: var(--s5);
  padding-top: var(--s3);
  border-top: var(--hair);
}
.zbizreport-clear-quarter {
  font-size: 0.78rem !important;
  color: var(--navy-45) !important;
}
.zbizreport-clear-quarter:hover {
  /* the same #a63d40 the below-plan figures use — the palette's only
     warning colour, rather than a new hue invented for one button */
  color: #a63d40 !important;
  border-color: #a63d40 !important;
}

/* ── who on the board has read this quarter ───────────────────────────────
   Yael, 2026-09-13: "so uli could see that jan saw everything and
   viseversa. and of course mv should see this confirmation as well." The
   band is quiet on purpose — it is a record of who has been here, not
   part of the report — and it closes the card, which is where you are
   when you have finished reading it. */
.zbizreport-readbox {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s1) var(--s3);
  margin-top: var(--s2);
  padding-top: var(--s2);
  border-top: var(--hair);
}
.zbizreport-read-by,
.zbizreport-read-pending {
  margin: 0;
  font-size: 0.75rem;
}
/* who HAS read it carries the lime tick's colour family; who has not is
   grey, not red — an unread report is a normal state, not a fault */
.zbizreport-read-by {
  color: var(--navy-70);
  font-weight: 500;
}
.zbizreport-read-by::before {
  content: "\2713  ";
  color: var(--navy);
}
.zbizreport-read-pending { color: var(--navy-45); }
.zbizreport-read-by:empty,
.zbizreport-read-pending:empty { display: none; }
/* the tick sits at the end of the line, away from the two statements it
   is about */
.zbizreport-read-confirm {
  margin-left: auto;
  font-size: 0.78rem !important;
  padding: 0.35rem 0.75rem !important;
}

/* the divider over the companies with nothing published. They are full
   cards now — the earlier one-line treatment was what Yael read as them
   being missing — so this marks a section rather than a lesser class of
   company. */
.zbizreport-board-silent-heading {
  margin-top: var(--s5);
  color: var(--navy-45);
}

/* ── attention list: emphasis by weight and edge, never by red ────────── */
.zbizreport-attention-row {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 0.6fr) minmax(0, 1.6fr);
  gap: var(--s3);
  align-items: baseline;
  background: #fff;
  border: var(--hair);
  border-left: 3px solid var(--navy);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  margin-bottom: var(--s1);
}
.zbizreport-attention-gap {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}
.zbizreport-attention-numbers { font-size: 0.8rem; color: var(--navy-70); }

/* ── comments ─────────────────────────────────────────────────────────── */
.zbizreport-comments {
  background: #fff;
  border: var(--hair);
  border-radius: var(--radius);
  padding: var(--s3);
  margin-bottom: var(--s3);
}
.zbizreport-comments-text { white-space: pre-wrap; }

/* the evaluation reads as TEXT but behaves as a button — clicking it opens
   the editor. Everything that makes a .zBtn look like a control is stripped
   back out, because a paragraph of prose in a button-shaped box reads as
   chrome rather than as the review itself. Only the hover and the focus
   ring survive, so it is still discoverable and still reachable by
   keyboard. */
.zbizreport-comments-btn {
  display: block !important;
  width: 100%;
  background: transparent !important;
  border: none !important;
  padding: 0 !important;
  text-align: left !important;
  font-family: inherit !important;
  font-size: 0.95rem !important;
  font-weight: 400 !important;
  line-height: 1.5;
  color: var(--navy) !important;
  white-space: pre-wrap;
  cursor: pointer;
}
.zbizreport-comments-btn:hover {
  background: transparent !important;
  text-decoration: underline;
  text-decoration-color: var(--lavender);
  text-underline-offset: 0.25em;
}

/* ── report status ────────────────────────────────────────────────────── */
.zbizreport-status-banner {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
  margin-bottom: var(--s3);
}
.zbizreport-status-pill {
  display: inline-block;
  background: var(--navy);
  color: var(--lime);
  font-weight: 500;
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: var(--s1) var(--s2);
  border-radius: var(--radius);
}

/* ── portfolio ────────────────────────────────────────────────────────── */
.zbizreport-selected-bar {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
  background: var(--navy);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  margin-bottom: var(--s3);

  /* STICKY to the foot of the window. Picking a report cannot travel to it
     — a dialog never can — so the second press has to be findable from
     wherever you happen to be in the list. Parked above the rows it went
     off-screen the moment the list was long enough to scroll, and the whole
     interaction read as a dead button. */
  position: sticky;
  bottom: var(--s2);
  z-index: 5;
  box-shadow: 0 6px 20px rgba(28, 25, 75, 0.18);
}
/* the name that used to sit here is gone — the page title carries it now.
   The bar is a numbered path: step 1 opens the company, step 2 opens its
   scorecard, so they sit together at the left rather than being pushed to
   opposite ends by a label that no longer exists. */
.zbizreport-selected-name { color: var(--lime); font-weight: 500; }

.zbizreport-venture-card {
  display: grid;
  /* name · sector · the two years · open. Four columns, not three: the
     years went in as a FOURTH child, and with three tracks defined they
     wrapped the Open button onto a second row on every card. */
  grid-template-columns: minmax(0, 2fr) minmax(0, 1.3fr) minmax(0, 1.4fr) auto;
  gap: var(--s3);
  align-items: center;
  background: #fff;
  border: var(--hair);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  margin-bottom: var(--s1);
}
/* every card's button the same width, whatever the company is called —
   "Open Musical Beings" and "Open Altneuland Press" are very different
   lengths, and ragged button edges down a list read as sloppy */
.zbizreport-venture-card .zBtn {
  width: 100%;
  min-width: 14rem;
  text-align: center;
}
.zbizreport-venture-card > *:last-child { min-width: 14rem; }

.zbizreport-venture-name { font-weight: 500; }
.zbizreport-venture-sector { color: var(--navy-70); font-size: 0.85rem; }

/* column three stacks the investment stage over the year it went in. It is
   a stack rather than a fifth grid track because five tracks pushed the
   Open button onto a second line at 1280px. */
.zbizreport-venture-stage {
  color: var(--navy);
  font-size: 0.8rem;
  font-weight: 500;
  letter-spacing: 0.02em;
}
.zbizreport-venture-years { color: var(--navy-45); font-size: 0.8rem; }

/* The type-specific edit form is its OWN block (it has to be — it is
   shuttled over a type-filtered reel), so it lands as a second flex row
   below the venture panel's buttons with a full --s4 gap above it, reading
   as orphaned rather than as one more button. Pulling it up by the
   difference makes the two rows sit at the normal button gap, so they read
   as a single wrapped group. */
.zbizreport-actions-joined {
  margin-top: calc(var(--s2) - var(--s4));
}

/* the opened venture, as one continuous card: header + checklist */
.zbizreport-venture-head {
  background: var(--navy);
  border-radius: var(--radius) var(--radius) 0 0;
  margin-bottom: 0;
  padding: var(--s3) var(--s3) var(--s2);
  margin-top: var(--s3);
}
.zbizreport-venture-headname {
  display: block;
  color: var(--lime);
  font-weight: 300;
  font-size: 1.5rem;
  letter-spacing: -0.01em;
}
.zbizreport-venture-headmeta {
  display: block;
  color: var(--lavender);
  font-size: 0.85rem;
  margin-top: var(--s1);
}
.zbizreport-checklist {
  background: #fff;
  border: var(--hair);
  border-top: none;
  padding: var(--s2) var(--s3);
  font-size: 0.85rem;
}
.zbizreport-checklist:last-of-type {
  border-radius: 0 0 var(--radius) var(--radius);
  margin-bottom: var(--s4);
}

/* ── a TECH company's header: one bar, everything in it ───────────────── */
/* Yael, 2026-09-23: "i want all the main info in one place... shape the
   information in that 1 main bar look better and clearer."

   The old header stacked seven sentences of equal weight. This is three
   registers instead: the NAME, a grid of LABELLED FACTS, and then the deal
   and the position under a hairline — the same navy, so it reads as one
   block rather than three that happen to touch. */
.zbizreport-vhead-tech {
  padding-bottom: var(--s3);
}
.zbizreport-vhead-top {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s1) var(--s3);
}

/* the type and sector sit BESIDE the name, not under it — they qualify it,
   and a line of their own gave them the weight of a fact. */
.zbizreport-vhead-tech .zbizreport-venture-headmeta { margin-top: 0; }

.zbizreport-factgrid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s4);
  margin-top: var(--s3);
  padding-top: var(--s3);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
}
.zbizreport-fact { min-width: 0; }
/* founders used to take all the slack (`flex: 1 1 14rem`), which pushed
   stage/invested/since hard against the right edge and split one row of
   facts into two clusters. Yael, 2026-09-28: "the ux of right and left
   isn't working for me." Everything now flows from the left in the order
   it is written; founders may simply be the widest cell. */
.zbizreport-fact-wide { max-width: 22rem; }
/* the site is now the last fact in the row, left-flowing with the rest
   — no corner, no right alignment. */
.zbizreport-fact-site .zbizreport-venture-headsite { margin-top: 0; }
.zbizreport-fact-label {
  display: block;
  color: var(--lavender);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: 0.2rem;
}
.zbizreport-fact-value {
  display: block;
  color: #fff;
  font-size: 0.95rem;
  line-height: 1.35;
}
/* a fact nobody has filled in takes its LABEL with it. Without this the
   grid shows "FOUNDED" over nothing, which reads as a missing figure
   rather than as a question not yet answered. */
.zbizreport-fact:not(.zbizreport-fact-site):not(:has(.zbizreport-fact-value:not(:empty))) {
  display: none;
}
/* the same for the site pill: an empty href renders as "#" (a documented
   placeholder), so the link cannot be hidden by its target — only by
   whether it has anything to say. */
.zbizreport-fact-site:not(:has(a:not(:empty))) { display: none; }

/* the bar's lower half is now THREE blocks — deal, the company's own
   total, our position — so that a view-level line (the only thing that
   can read a session value) can sit between two patterns in the order
   Itai asked for. They must read as one continuous block: only the last
   one carries the rounded bottom, and none of them carry a gap. */
.zbizreport-vhead-band + .zbizreport-vhead-band { border-radius: 0; }
.zbizreport-vband-top { border-radius: 0; }
/* the middle slice of the bar. Not `.zbizreport-vhead-band`, because a
   view block renders for every company and that class means "this is one
   of the tech bar's patterns". */
.zbizreport-vband-raised {
  background: var(--navy);
  margin: 0 var(--gutter);
  padding: 0 var(--s3) var(--s3);
  border-radius: 0;
}
/* an empty total takes its whole block with it — a company nobody has
   reported a raise for shows the deal and stops. */
.zbizreport-vband-raised:not(:has(.zbizreport-vband-value:not(:empty))) {
  display: none;
}

/* the deal and the position, continuing the bar */
.zbizreport-vhead-band {
  background: var(--navy);
  /* the same inset as the header above it. Set to 0 first, on the
     reasoning that the two are DOM siblings — and the screenshot showed
     the band running edge to edge under an inset header. The header's
     own wrapper carries the page gutter; this one's does not. */
  margin: 0 var(--gutter);
  padding: 0 var(--s3) var(--s3);
  border-radius: 0 0 var(--radius) var(--radius);
}
.zbizreport-vband-row {
  padding-top: var(--s2);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
}
.zbizreport-vband-row + .zbizreport-vband-row { margin-top: var(--s2); }
.zbizreport-vband-value {
  display: block;
  color: #fff;
  font-size: 0.95rem;
  line-height: 1.4;
}
.zbizreport-vband-when {
  display: block;
  color: var(--lavender);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-top: var(--s1);
}
.zbizreport-vband-when:not(:empty)::before { content: "position last reviewed "; }
.zbizreport-vband-grades {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1) var(--s3);
  margin-top: var(--s2);
}
.zbizreport-vband-grades .zbizreport-grade-label {
  color: var(--lavender);
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
/* a grade with nothing in it takes its label with it — same rule, same
   reason, as the facts above. */
.zbizreport-vband-grades .zbizreport-grade:not(:has(.zbizreport-grade-value:not(:empty))) {
  display: none;
}
/* and an empty ROW — no deal written, no position taken — collapses whole,
   so a company nobody has judged yet shows facts and stops. */
.zbizreport-vband-row:not(:has(.zbizreport-vband-value:not(:empty))) { display: none; }
/* …and with both rows gone the band would still draw its own padding. */
.zbizreport-vhead-band:not(:has(.zbizreport-vband-row:not(:empty))) { display: none; }

/* ── tables of reports ────────────────────────────────────────────────── */
.zbizreport-past-head,
.zbizreport-past-row {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s3);
  padding: var(--s2) var(--s3);
  align-items: center;
}
.zbizreport-past-head {
  background: var(--navy);
  color: var(--lime);
  font-size: 0.67rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  border-radius: var(--radius);
  margin-bottom: var(--s2);
}
.zbizreport-past-row {
  background: #fff;
  border: var(--hair);
  border-radius: var(--radius);
  margin-bottom: var(--s1);
  font-size: 0.88rem;
}
.zbizreport-past-row > *:first-child { font-weight: 500; }

/* ── documents ────────────────────────────────────────────────────────── */
.zbizreport-doc-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1.5fr);
  gap: var(--s3);
  align-items: baseline;
  background: #fff;
  border: var(--hair);
  border-left: 3px solid var(--lavender);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  margin-bottom: var(--s1);
}
.zbizreport-doc-kind { font-weight: 500; font-size: 0.85rem; }
/* the filename is a link to the real file — brand navy, not browser blue */
.zbizreport-doc-row a {
  color: var(--navy);
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--lavender);
  text-decoration-thickness: 2px;
}
.zbizreport-doc-row a:hover { text-decoration-color: var(--navy); }
.zbizreport-doc-when,
.zbizreport-doc-note { font-size: 0.8rem; color: var(--navy-70); }

/* an attachment whose file is no longer on the server.
   Sits directly above the list it is about, and takes the same gutters and
   radius as a document row so it reads as part of that block rather than a
   floating alert. The red is the sheet's existing "bad" red — a warning is
   not an excuse for a new hue.
   It replaces a transient toast: six filenames in a signal that flushes was
   unreadable (Yael, 2026-10-25, "hard to read"), and the thing being
   reported does not go away on its own, so neither should the message.

   Hidden until DocsMissingMarker says otherwise — the same body:has()
   idiom the three-document cap uses above. It matters that the DEFAULT is
   hidden: the band's text comes from a session zVar, so on any page
   reached without the check having run there is nothing to resolve, and
   nothing to see either, because there is no marker on such a page. */
.zbizreport-docs-missing { display: none; }
body:has(.zbizreport-docs-gone) .zbizreport-docs-missing {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
  margin: 0 var(--gutter) var(--s2);
  padding: var(--s2) var(--s3);
  background: rgba(166, 61, 64, 0.12);
  border: 1px solid rgba(166, 61, 64, 0.35);
  border-left: 3px solid #a63d40;
  border-radius: var(--radius);
}
.zbizreport-docs-missing-text {
  flex: 1 1 18rem;
  min-width: 0;
  margin: 0;
  color: #a63d40;
  font-size: 0.9rem;
  line-height: 1.45;
}


/* ── the add-a-venture wizard's running summary ─────────────────────────
   Keyed on a class the .zolo puts there BY HAND, not on `.zText`. A zText
   renders as a bare <p> carrying no class of its own — measured in the
   browser — so every `.zText` selector here matched nothing, which is why
   these lines sat hard against the window edge with no card around them
   while the heading above them was correctly inset. */
.zbizreport-wizard-progress { padding-bottom: var(--s5); }
.zbizreport-wizard-line {
  display: block;
  background: #fff;
  border: var(--hair);
  border-left: 3px solid var(--lavender);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  margin-bottom: var(--s1);
  font-size: 0.85rem;
  word-break: break-word;
}

/* Once the venture exists the held details are cleared, so the summary
   would show five "not given" lines ABOVE the "created" confirmation —
   reading as if the company had been added with nothing filled in. The
   moment the created block appears, the summary has done its job. */
.zbizreport-wizard-progress:has(.zbizreport-wizard-created) .zbizreport-wizard-line {
  display: none;
}
.zbizreport-wizard-created .zbizreport-checklist {
  border-left: 3px solid var(--lime);
  margin-bottom: var(--s3);
}

/* ── an opened company's quarters, as a row of chips ──────────────────────
   Wraps rather than scrolls: a company with twelve quarters should show all
   twelve, since the whole point is answering "where does this stand" in one
   look. The quarter reads first and the state sits under it, so scanning
   down the column of states is what tells you what needs you. */
.zbizreport-quarter-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-left: var(--gutter);
  margin-right: var(--gutter);
  margin-bottom: var(--s5);
}
.zbizreport-quarter-chip {
  background: #fff;
  border: var(--hair);
  border-left: 3px solid var(--lavender);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  min-width: 9rem;
}
.zbizreport-quarter-chip-q {
  font-weight: 500;
  font-size: 0.9rem;
  margin: 0;
}
.zbizreport-quarter-chip-s {
  font-size: 0.8rem;
  color: var(--navy-70);
  margin: 0;
}

/* the SAME chip, as a real button — used where clicking it actually goes
   somewhere (the Scorecard's own past-quarters strip), as opposed to
   Home's identical-looking but purely informational one. Built on top of
   the base .zBtn rules, not replacing them, so it still gets a focus ring
   and the rest of the app's button behaviour for free. */
.zbizreport-quarter-chip-btn {
  background: #fff !important;
  border: var(--hair) !important;
  border-left: 3px solid var(--lavender) !important;
  min-width: 9rem;
  text-align: left !important;
  font-weight: 500 !important;
  color: var(--navy) !important;
  /* deliberately matched to .zbizreport-quarter-chip above, so the
     clickable strip on the Scorecard and the read-only one on Home read as
     the same object. The base .zBtn is smaller than a chip, so both the
     padding and the size have to be restated here to close the gap. */
  padding: var(--s2) var(--s3) !important;
  font-size: 0.9rem !important;
  line-height: 1.35;
}
.zbizreport-quarter-chip-btn:hover {
  background: var(--lav-30) !important;
  border-color: var(--navy-12) !important;
  border-left-color: var(--lavender) !important;
}

/* A read-only field is an EXPLANATION, not a box to type in. The KPI's
   definition rides in the row dialog as a readonly input (the zDialog
   grammar has no plain-text element), so it is stripped of everything that
   invites a cursor. */
.zModal-overlay input[readonly] {
  border: none !important;
  background: transparent !important;
  padding-left: 0 !important;
  color: var(--navy-70) !important;
  cursor: default;
}

/* ── the seam between an opened company and the portfolio below ───────────
   Everything above belongs to the ONE opened company; everything below is
   every company again. A section heading alone didn't say that — it read
   as one more heading in a list of headings. A visible line, with real air
   on both sides, marks it as a genuine break rather than the next section. */
.zbizreport-venture-divider {
  margin: var(--s5) var(--gutter) var(--s4);
  border-top: 2px solid var(--lavender);
}

/* ── "handed in — here is what happens next" ──────────────────────────────
   Submitting locks the whole quarter, and until now the only sign it had
   worked was a small word flipping from DRAFT to SUBMITTED. For a move
   that consequential the page has to say so plainly, and point at the one
   thing that comes next. Lime, because this is a good outcome — the
   quarter is done and on its way — not a warning. */
.zbizreport-nextstep {
  background: var(--lime-18);
  border: 1px solid var(--lime);
  border-radius: var(--radius);
  margin: var(--s3) var(--gutter) var(--s4);
  padding: var(--s3);
}
.zbizreport-nextstep-text {
  margin: 0 0 var(--s3);
  font-size: 0.9rem;
  color: var(--navy);
  max-width: 60rem;
}
/* the action row inside the banner already has the page gutter from the
   shared rhythm rule; inside a padded card that doubles up */
.zbizreport-nextstep .zbizreport-actions {
  margin: 0;
}

/* ── the review queue: what is sitting in Migdalor's court ────────────────
   Lavender, the brand's attention colour — this is "someone is waiting on
   you", not a success (lime) and not a refusal (navy). It renders only
   when something is actually waiting, so it can afford to be loud. */
.zbizreport-awaiting-row {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1.2fr) auto;
  gap: var(--s3);
  align-items: center;
  background: var(--lav-30);
  border: 1px solid var(--lavender);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  margin-left: var(--gutter);
  margin-right: var(--gutter);
  margin-bottom: var(--s2);
}
.zbizreport-awaiting-who { font-weight: 500; margin: 0; }
.zbizreport-awaiting-when { font-size: 0.85rem; color: var(--navy-70); margin: 0; }

@media (max-width: 860px) {
  .zbizreport-awaiting-row { grid-template-columns: 1fr; gap: var(--s1); }
}

/* ── which actions a quarter's STATE actually allows ──────────────────────
   Both rules key off the invisible markers (DraftMarker / PublishedMarker)
   the page already renders, so the buttons follow the report's real state
   rather than anyone's memory of it.

   Submit: only on a Draft. On a submitted or published quarter the server
   refuses it anyway, so offering it was a button that could only fail —
   and with nothing open there is nothing to submit either.

   Open a quarter: hidden once the open one is PUBLISHED — but only for a
   VENTURE account. A CFO looking at a finalised report is reading it. For
   Migdalor the same button is ordinary navigation between quarters, and
   hiding it for everyone left no way out of a published one at all. Safe
   for the CFO because "Open my company" now clears the quarter, so a blank
   slate is always one step away. */
.zbizreport-act-submit { display: none; }
body:has(.zbizreport-is-draft) .zbizreport-act-submit { display: inline-block; }
/* the rule that lived here hid "Open a quarter" from a venture on a
   published report. The button moved to the company panel on 2026-09-08,
   so there is nothing left here to hide. */

/* attaching evidence is part of filling the quarter in, so the button is
   there only while it is a Draft — matching the row edits and the server,
   which refuses an attach to a handed-in quarter. */
.zbizreport-act-attachdoc { display: none; }
body:has(.zbizreport-is-draft) .zbizreport-act-attachdoc { display: flex; }

/* three is the limit, so once three are attached the button goes rather
   than staying to be refused. QuarterDocsFullMarker is what says "full" —
   a reel with offset 2 that only returns a row when a third exists, since
   CSS cannot count. */
body:has(.zbizreport-qdocs-full) .zbizreport-act-attachdoc { display: none; }

/* removing is the same permission as attaching: the venture's, while the
   quarter is a draft. Migdalor and the board read this list, never edit it.

   NOTE the selector: `_zClass` on a block whose only child is a zBtn lands
   the class ON the button, not on a wrapper — so this IS the button, and
   `.zbizreport-qdoc-remove button` would match nothing. */
.zbizreport-qdoc-remove { display: none; }
body:has(.zbizreport-is-venture):has(.zbizreport-is-draft) .zbizreport-qdoc-remove { display: block; }

/* a COMPANY's own documents are Migdalor's to attach, so they are
   Migdalor's to remove — the other half, added 2026-10-25 after Yael
   uploaded a second business plan for LowSwing and found no way to drop
   the first. Same selector shape as the quarter's Remove above: _zClass on
   a block whose only child is a zBtn lands ON the button. */
.zbizreport-vdoc-remove { display: none; }
body:has(.zbizreport-is-migdalor) .zbizreport-vdoc-remove { display: block; }

/* ── figures the app works out, with their arithmetic beside them ──────
   The board's ask, Oct 2026: compute what is typed by hand, and show the
   inputs next to each result so the calculation can be followed. So each
   line is result-then-working, and the working is deliberately quiet —
   it is there to be checked, not read every time.

   Hidden until CalculationsMarker says there is something to work out,
   which is the same body:has() idiom the lost-attachment band and the
   three-document cap both use. The whole block is absent on a tech
   quarter, whose sheet does not carry the rows the arithmetic needs. */
.zbizreport-calc { display: none; }
body:has(.zbizreport-calc-present) .zbizreport-calc {
  display: block;
  margin-top: var(--s5);
}
.zbizreport-calc-row {
  display: grid;
  grid-template-columns: minmax(10rem, 16rem) minmax(0, 1fr);
  gap: var(--s1) var(--s3);
  align-items: baseline;
  background: #fff;
  border: var(--hair);
  border-left: 3px solid var(--lavender);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  margin: 0 var(--gutter) var(--s1);
}
.zbizreport-calc-label {
  margin: 0;
  font-weight: 500;
  font-size: 0.9rem;
}
.zbizreport-calc-value {
  margin: 0;
  font-size: 1.05rem;
}
/* the working, under the result and across both tracks: a second line
   rather than a third column, because it is a sentence and a column would
   squeeze it to four words a row. */
.zbizreport-calc-how {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 0.8rem;
  color: var(--navy-70);
}
/* the two CONDITIONAL lines, each with its own marker — a row reading "—"
   is not a hidden row.

   Budget used needs the plan to carry a cost line at all (a quarter with no
   budget has no utilisation, and 0% would be a lie); runway needs the
   company to be burning (a profitable quarter has none, and "infinite" is a
   worse answer than no line). */
.zbizreport-calc-3,
.zbizreport-calc-4 { display: none; }
body:has(.zbizreport-budget-present) .zbizreport-calc-3 { display: grid; }
body:has(.zbizreport-runway-present) .zbizreport-calc-4 { display: grid; }

/* the section that asks for them — the last thing before Submit */
.zbizreport-qdocs { margin-top: var(--s5); }
.zbizreport-qdocs-invite {
  margin: 0 var(--gutter) var(--s3);
  padding: var(--s2) 0 var(--s2) var(--s2);
  border-left: 3px solid var(--lavender);
  font-size: 0.88rem;
  color: var(--navy-70);
  max-width: 58rem;
}
.zbizreport-qdocs-invite p { margin: 0; }

/* the row gains a fourth cell for Remove */
.zbizreport-qdocs .zbizreport-doc-row {
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 2fr) minmax(0, 1fr) auto;
}

/* ── the archive ──────────────────────────────────────────────────────────
   Deliberately quieter than a venture card: this is a record of what is no
   longer being reported on, not something to work in. Muted ground, no
   white card, so it reads as a footnote to the portfolio above it. */
.zbizreport-archived-row {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 2fr) auto;
  gap: var(--s3);
  align-items: center;
  background: none;
  border: 1px dashed var(--navy-12);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  margin-left: var(--gutter);
  margin-right: var(--gutter);
  margin-bottom: var(--s1);
}
.zbizreport-archived-name { font-weight: 500; color: var(--navy-70); margin: 0; }
.zbizreport-archived-meta { font-size: 0.82rem; color: var(--navy-45); margin: 0; }

@media (max-width: 860px) {
  .zbizreport-archived-row { grid-template-columns: 1fr; gap: var(--s1); }
}

/* ── modals ───────────────────────────────────────────────────────────── */
.zModal-overlay { background: rgba(28, 25, 75, 0.5) !important; }
/* A DIALOG TALLER THAN THE WINDOW HAD NO BOTTOM. Sandra, 2026-11-03:
   "I can't save any comments. For example for the titles." She was right
   and it was not about comments: the overlay is fixed to the viewport and
   the card inside it had neither a height limit nor a scrollbar, so a tall
   dialog was simply cut off at the foot of the screen. The title dialog
   has eleven fields — Notes is the tenth and Save is below it, so on a
   short window both were unreachable. Nothing was broken about saving;
   there was no way to press the button.

   It passed every test because zRaven clicks through Playwright, which
   scrolls an element into view before clicking it. The walk could always
   reach a button a person could not. Caught only by taking a screenshot
   of the open dialog and looking at it. */
.zModal-card, .zModal-content {
  border-radius: var(--radius) !important;
  border: var(--hair) !important;
  box-shadow: 0 12px 40px rgba(28, 25, 75, 0.18) !important;
  max-height: calc(100vh - 2rem);
  overflow-y: auto;
}
.zModal-header, .zModal-title {
  background: var(--navy) !important;
  color: var(--lime) !important;
  font-weight: 400 !important;
  font-size: 1.05rem !important;
  letter-spacing: -0.01em;
}
/* ── form fields ──────────────────────────────────────────────────────────
   The BACKGROUND is the important line here. zbase ships dark inputs; this
   sheet previously restyled their border and type but never their fill, so
   every field showed as a near-black box that belongs to no brand colour.
   White field on cream page, navy text — nothing outside the palette. */
.zForm-label, label {
  display: block;
  font-size: 0.8rem !important;
  color: var(--navy-70) !important;
  font-weight: 400 !important;
  margin-bottom: var(--s1) !important;
  letter-spacing: 0.01em;
}
.zForm-control,
input[type="text"], input[type="number"], input[type="email"],
input[type="password"], input[type="date"], input[type="search"],
select, textarea {
  background: #fff !important;
  color: var(--navy) !important;
  border-radius: var(--radius) !important;
  border: var(--hair) !important;
  font-family: inherit !important;
  font-size: 0.9rem !important;
  font-weight: 400 !important;
  padding: var(--s2) !important;
  width: 100%;
}
.zForm-control:focus,
input:focus, select:focus, textarea:focus {
  outline: none !important;
  border-color: var(--navy) !important;
  box-shadow: 0 0 0 3px var(--lime-45) !important;
}
input::placeholder, textarea::placeholder { color: var(--navy-45) !important; }

/* ── room to breathe between fields, and before the submit ────────────────
   Every field is its own block with the same gap beneath it, and the submit
   sits a full step below the last one — it was landing tight under the
   password box, reading as part of the field rather than as an action. */
.zForm-group, .zForm-field, .zDialog-field, .zForm-row {
  margin-bottom: var(--s3) !important;
}
.zForm-control, input, select, textarea { margin-bottom: var(--s3); }
.zForm-group:last-of-type, .zForm-field:last-of-type { margin-bottom: 0 !important; }

form button[type="submit"],
.zForm-submit, .zDialog-submit, .zBtn-submit {
  margin-top: var(--s3) !important;
}

/* a dialog's body needs the same air as the page does */
.zModal-body, .zDialog-body, .zModal-content > form { padding: var(--s3) !important; }

/* ── narrow windows: stack, don't squeeze ─────────────────────────────── */
@media (max-width: 900px) {
  :root { --gutter: var(--s3); }
  .zbizreport-sc-head, .zbizreport-past-head { display: none; }
  .zbizreport-sc-row,
  .zbizreport-sc-answer-row,
  .zbizreport-sc-simple-row,
  .zbizreport-past-row,
  .zbizreport-attention-row,
  .zbizreport-doc-row,
  .zbizreport-venture-card { grid-template-columns: 1fr; gap: var(--s1); }
  /* the span is meaningless once the row is a single column, and left in
     place it would stretch the answer across tracks that no longer exist */
  .zbizreport-sc-answer,
  .zbizreport-sc-simple-value { grid-column: auto; }
  .zbizreport-venture-card .zBtn { min-width: 0; }
  .zbizreport-sc-cell, .zbizreport-sc-status, .zbizreport-attention-gap { text-align: left; }
  .zbizreport-hero h1, .zbizreport-hero .zH1 { font-size: 1.75rem; }
}

/* ── print: the actual deliverable ────────────────────────────────────── */
@media print {
  .no-print { display: none !important; }
  body { background: #fff; }
  .zbizreport-hero { background: none; border-bottom: 2px solid var(--lavender); }
  .zbizreport-sc-row, .zbizreport-sc-answer-row, .zbizreport-sc-simple-row,
  .zbizreport-past-row, .zbizreport-doc-row,
  .zbizreport-attention-row, .zbizreport-tile {
    border: var(--hair);
    break-inside: avoid;
  }
  .zbizreport-sc-head, .zbizreport-past-head,
  .zbizreport-section-heading, .zbizreport-status-pill {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}

/* the print button's rules are gone with the button (2026-09-08). What
   remains of that idea is `.no-print` and the @media print block further
   down, which still matter: a browser's own Print command works on any
   page, and the app should look right when someone uses it. */

/* ── the back/forward nav row ─────────────────────────────────────────────
   Same gutter and gap as the action bar. Without a layout class it sat
   flush against the window edge while every other band was inset — the
   most visible misalignment on the page. Sits tight under the hero, since
   it belongs to it. */
.zbizreport-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  /* the hero already ends with its own padding; pull up only half a step
     so the buttons sit close to their heading without touching it */
  margin-top: calc(var(--s4) * -1);
  margin-bottom: var(--s5);

  /* STICKY, so the way back is reachable from anywhere on the page. The
     scorecard is 42 rows long and the review page longer; a back button
     only at the top means scrolling all the way up to leave, which is what
     made it feel like there was no way back at all. Sticky rather than
     fixed so it still occupies its own space in the flow and nothing has
     to be padded to sit under it. */
  position: sticky;
  top: 0;
  z-index: 20;
  /* it now sits over content as it scrolls, so it needs its own ground */
  background: var(--cream);
  padding-top: var(--s3);
  padding-bottom: var(--s3);
  /* the page gutter is a MARGIN on this element (see the shared rhythm
     block), so a full-width background would stop short of the window
     edges and show a seam. The shadow spreads it back out. */
  box-shadow: 0 0 0 var(--gutter) var(--cream), 0 1px 0 var(--gutter) var(--navy-12);
}

/* ── messages the app says back ───────────────────────────────────────────
   "Welcome back Yael", "Attached to LowSwing Records", a refusal — all of
   these render through zSignal/zAlert/zToast. Left alone they arrive in
   zbase's own green (#52B788), which belongs to no brand.

   Repointing the tokens above fixes the accent, but a message also has to
   be READABLE: lime type on cream is nearly invisible. So the colour goes
   into a wash and a left edge, and the words stay navy. */
.zSignal, .zAlert, .zToast {
  background: #fff;
  border: var(--hair);
  border-left: 3px solid var(--lavender);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  color: var(--navy) !important;
  font-size: 0.9rem;
}
.zSignal-success, .zAlert-success, .zToast-success {
  background: var(--lime-18);
  border-left-color: var(--lime);
  color: var(--navy) !important;
}
.zSignal-error, .zAlert-error, .zToast-error {
  background: #fff;
  border-left-color: var(--navy);
  color: var(--navy) !important;
}
.zSignal-info, .zSignal-warning,
.zAlert-info, .zAlert-warning { border-left-color: var(--lavender); }
.zSignal-close { color: var(--navy); }

/* ── a refusal inside a modal goes ABOVE the button that was pressed ──────
   zOS appends a plugin's `error:` return to the END of the dialog's form,
   after the submit button:

       <div class="zDialog-actions"><button type="submit">Submit</button></div>
       <div class="zDialog-feedback">
         <div class="zSignal zSignal-error zAlert" role="alert">…</div>
       </div>

   On a short dialog that reads perfectly well — the submit-refusal shot
   shows it right where the eye already is. On a TALL one it is invisible.
   The title edit dialog is fourteen fields deep; the card clips after
   "List price per unit" and the message renders below the fold inside the
   card's own scroll. Press Save with a price the app cannot read and the
   dialog just sits there: a refused save and a dead button look identical.
   That would make a validation message worse than no validation at all,
   which is why this rule ships in the same breath as the refusals it
   carries.

   `.zDialog-feedback` IS the flex child; `.zSignal` is its child and one
   level too deep for `order` to reach. Two rounds were spent on that — the
   flex took (the dialog's spacing visibly moved) and the order did not,
   which is exactly how a wrong guess about compiled markup presents. The
   class names above are read off the live DOM, not inferred. */
.zModal-overlay form {
  display: flex;
  flex-direction: column;
}
.zModal-overlay .zDialog-feedback {
  order: -1;
  margin-bottom: var(--s3);

  /* AND PINNED, because first-in-the-form is not the same as on-screen.
     After a refused submit the card is scrolled near the field that was
     being typed in, so a message resting at the top of a fourteen-field
     form is simply off the top instead of off the bottom.

     Sticky works here on purpose: `.zModal-card` above already carries
     `max-height: calc(100vh - 2rem); overflow-y: auto`, so it is the
     scrollport this sticks against, and the `overflow-x: clip` fix further
     down keeps <body> from being a scroll container (finding 31 — a base
     rule there silently disables every sticky in the app). Both were
     already true; this rule only depends on them. */
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--cream);
}

/* the dialog's own heading stays first — a refusal must not jump above
   "Hand this quarter in to Migdalor?" and leave the question it is
   answering underneath it. Harmless where the heading sits outside the
   form, which is where most of them are. */
.zModal-overlay form > .zDialog-title,
.zModal-overlay form > .zModal-title { order: -2; }

/* zbase colours some text by semantic name too */
.zText-success { color: var(--navy) !important; }
.zBadge-success { color: var(--navy) !important; background: var(--lime-18) !important; }

/* ── downloading the finished report ──────────────────────────────────────
   A filled navy button, not a text link: this is the thing most people open
   a published quarter to do, and it should look like the primary action on
   the page rather than a footnote under the rows. */
.zbizreport-download {
  margin: var(--s4) var(--gutter) var(--s2);
}
a.zbizreport-download-link,
.zbizreport-download a {
  display: inline-block;
  background: var(--navy);
  color: var(--lime) !important;
  text-decoration: none;
  padding: var(--s2) var(--s4);
  border-radius: var(--radius);
  font-size: 0.92rem;
}
.zbizreport-download a:hover { background: #2a2668; }

/* ── the catalogue table ──────────────────────────────────────────────────
   Altneuland's Acquisition section, as a table of titles. Deliberately the
   SAME visual language as the scorecard grid below it — one header strip,
   one row per thing, an action at the right edge — so the page reads as one
   document rather than two bolted together. Six columns, not ten: author,
   tier, market and format sit under the title as a quiet meta line, because
   they rarely change and a ten-column grid is unreadable on anything
   narrower than a desk monitor. */
.zbizreport-title-head,
.zbizreport-title-row {
  display: grid;
  /* title | tier | stage | sold this q | sold to date | cost | price | edit.
     Tier is deliberately narrow — it is a 1/2/3, but it has to be SCANNABLE,
     which is why it earned a column of its own rather than staying in the
     meta line under the name. */
  /* the last track is a FIXED width, not `auto`. With `auto` the header
     reserved ~0px for its empty Edit cell while a row reserved ~66px for the
     button — the fr tracks then shared different amounts of leftover space in
     each, so every value sat progressively further left than its own heading.
     A fixed track makes both grids identical, which is the only way a header
     and its rows can actually line up. */
  /* "Earns per unit" (track 7) is widened and "Current balance" (track 8)
     with it. The two carry the money, they sit side by side at the end of
     the row, and at their old widths the headings very nearly touched — two
     different figures reading as one block. Space between them is what keeps
     "what a copy earns" and "where the title stands" legible as separate
     answers. The width comes off the title column, which had room. */
  /* ELEVEN tracks since 2026-10-29: "Cost per copy sold" after the cost
     it is derived from, and "Notes" after the balance. Both were asked for
     in the Altneuland brief (C4, C5). The width for them comes off Title
     and Stage, which had the most to give — the title still wraps to two
     lines where it needs to, and the author/market/format meta line under
     it was already doing that. */
  grid-template-columns:
    minmax(0, 1.4fr) minmax(0, 0.3fr) minmax(0, 0.95fr)
    repeat(2, minmax(0, 0.55fr)) minmax(0, 0.75fr) minmax(0, 0.7fr)
    minmax(0, 0.7fr) minmax(0, 0.85fr) minmax(0, 0.95fr) 5rem;
  gap: var(--s3);
  padding: var(--s3);
  align-items: start;
}

.zbizreport-title-head {
  background: var(--navy);
  color: var(--lime);
  font-size: 0.67rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  border-radius: var(--radius);
  margin-bottom: var(--s2);
}

.zbizreport-title-row {
  background: #fff;
  border: var(--hair);
  border-radius: var(--radius);
  margin-bottom: var(--s1);
  font-size: 0.9rem;
}

/* a title that has come off the active list. Quieter than a live row, and
   deliberately NOT struck through or greyed to the point of illegibility:
   its sales are still real and still counted, it is simply not something
   anyone has to act on this quarter. */
.zbizreport-title-row-archived {
  background: transparent;
  border-style: dashed;
  opacity: 0.72;
}
.zbizreport-title-archived-since {
  margin: var(--s1) 0 0;
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  color: var(--navy-45);
}
/* the section heading for those rows, set apart from the live catalogue
   above it without shouting */
.zbizreport-archived-heading {
  margin-top: var(--s4);
  color: var(--navy-45);
}

.zbizreport-title-label { font-weight: 500; margin: 0; }

/* author · market · format · tier — present but never competing with the
   title itself for attention */
.zbizreport-title-meta {
  margin: var(--s1) 0 0;
  font-size: 0.78rem;
  color: var(--navy-45);
}

/* the stage is the column people scan for, so it reads as a state rather
   than as another cell of text */
.zbizreport-title-stage {
  display: inline-block;
  margin: 0;
  padding: 0.1rem 0.5rem;
  border-radius: 0.3rem;
  background: var(--lav-30);
  font-size: 0.8rem;
}

.zbizreport-title-row p { margin: 0; }

/* the tier reads as a marker, not as another number in a row of numbers */
.zbizreport-title-tier {
  font-weight: 500;
  color: var(--navy-70);
}

/* the running total is the quieter of the two sales figures — this
   quarter's number is the news, the total is context for it */
.zbizreport-title-todate { color: var(--navy-70); }
.zbizreport-addtitle { margin-top: var(--s3); }

@media (max-width: 900px) {
  .zbizreport-title-head { display: none; }
  .zbizreport-title-row {
    grid-template-columns: 1fr;
    gap: var(--s1);
  }
}

/* the cost-recovery verdict. Green and red are the only two places this app
   uses a colour that is not in the brand palette — deliberately, because
   "am I losing money on this title" is the one thing on the page that has to
   read at a glance without being decoded. Kept muted rather than alarming. */
.zbizreport-title-recouped { font-weight: 500; }
.zbizreport-verdict-good { color: #2e7d5b; margin: 0; }
.zbizreport-verdict-bad  { color: #a63d40; margin: 0; }

/* exactly one of the two ever carries the figure; the other must take up no
   room at all, or every row gains a blank line where the unused half sits */
.zbizreport-verdict-good:empty,
.zbizreport-verdict-bad:empty { display: none; }

/* what the balance was worked out from, under the number. Deliberately quiet
   — it is a qualifier, not a second figure — but never hidden: "at list
   price" is the difference between a rough signal and a number a board would
   take for a per-title P&L. */
.zbizreport-verdict-basis {
  margin: 0.1rem 0 0;
  font-size: 0.7rem;
  font-weight: 400;
  line-height: 1.25;
  color: #7b7f8a;
}
.zbizreport-verdict-basis:empty { display: none; }

/* ── the rights table ─────────────────────────────────────────────────────
   Six tracks: what was sold, type, buyer, amount, status, and the Edit
   button. The button track is FIXED, not `auto` — with `auto` the header's
   empty sixth cell measures ~0 while a row's measures a button, and the two
   grids then divide different leftovers so every heading drifts left of its
   column. Same fault, same fix, as the catalogue and the scorecard. */
.zbizreport-rights-head,
.zbizreport-rights-row {
  display: grid;
  grid-template-columns:
    minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1.3fr)
    minmax(0, 0.9fr) minmax(0, 1fr) 5rem;
  gap: var(--s3);
  padding: var(--s3);
  align-items: start;
  margin-left: var(--gutter);
  margin-right: var(--gutter);
}
.zbizreport-rights-head {
  background: var(--navy);
  color: var(--lime);
  font-size: 0.67rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  border-radius: var(--radius);
  margin-bottom: var(--s2);
  padding: var(--s2) var(--s3);
}
.zbizreport-rights-row {
  background: #fff;
  border: var(--hair);
  border-radius: var(--radius);
  margin-bottom: var(--s2);
}
.zbizreport-rights-what { min-width: 0; }
.zbizreport-rights { margin-bottom: var(--s4); }

@media (max-width: 900px) {
  .zbizreport-rights-head { display: none; }
  .zbizreport-rights-row { grid-template-columns: 1fr; gap: var(--s1); }
}
@media print {
  .zbizreport-rights-row { border: var(--hair); break-inside: avoid; }
  .zbizreport-rights-head {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}

/* ── a big miss on the rows the board reads first ─────────────────────────
   Only Hard Criteria rows are ever split into these three, and only past
   ±10% (see EMPHASIS_PCT). Below that the figure stays plain, so the colour
   keeps meaning something rather than lighting up every quarter.

   The same two brand-adjacent tones the catalogue's balance uses, so red and
   green mean the same thing in both places on the page. */
.zbizreport-var-plain,
.zbizreport-var-good,
.zbizreport-var-bad { margin: 0; }
.zbizreport-var-good { color: #2e7d5b; font-weight: 500; }
.zbizreport-var-bad  { color: #a63d40; font-weight: 500; }
.zbizreport-var-plain:empty,
.zbizreport-var-good:empty,
.zbizreport-var-bad:empty { display: none; }

/* The verdict as a flag, in the Hard Criteria section only — Yael asked for
   red/green there instead of the words "Positive" / "Negative".

   Same two tones as the variance above it, so a red flag and a red figure on
   the same row are saying one thing, not two. Sized up a little because a
   single glyph at body size reads as a typo rather than a signal; the arrow
   is what carries the meaning into a greyscale print (see STATUS_FLAG_GOOD). */
.zbizreport-status-plain,
.zbizreport-status-good,
.zbizreport-status-bad { margin: 0; }
.zbizreport-status-good { color: #2e7d5b; font-size: 1.15rem; line-height: 1; }
.zbizreport-status-bad  { color: #a63d40; font-size: 1.15rem; line-height: 1; }
.zbizreport-status-plain:empty,
.zbizreport-status-good:empty,
.zbizreport-status-bad:empty { display: none; }

/* ── the year view ──────────────────────────────────────────────────────
   Eleven columns: a label, four quarters of plan/actual, and the year's
   pair. That does not fit a phone and should not try to — squeezing eleven
   money columns into 375px makes every figure unreadable, which is worse
   than asking someone to scroll. The table scrolls sideways INSIDE its own
   box, so the page itself never does. */
.zbizreport-year {
  overflow-x: auto;
  margin-bottom: var(--s5);
}
.zbizreport-year-head,
.zbizreport-year-row {
  display: grid;
  /* the label column is wide and FIXED; the ten figure columns are equal,
     so plan and actual sit directly under their own headings and a figure
     can be compared straight down the column. min-content on the whole grid
     is what makes it scroll rather than compress. */
  grid-template-columns: 16rem repeat(10, minmax(5.5rem, 1fr));
  gap: var(--s2);
  padding: var(--s2) var(--s3);
  min-width: 72rem;
  align-items: start;
}
.zbizreport-year-head {
  background: var(--navy);
  color: #fff;
  border-radius: var(--radius) var(--radius) 0 0;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.zbizreport-year-head > * { color: var(--lime); margin: 0; }
/* every figure heading right-aligned over its own column, the label left */
.zbizreport-year-head > *:not(:first-child),
.zbizreport-year-row > *:not(:first-child) { text-align: right; }

.zbizreport-year-row {
  background: #fff;
  border-bottom: var(--hair);
  font-size: 0.9rem;
}
.zbizreport-year-row:last-child {
  border-radius: 0 0 var(--radius) var(--radius);
}
.zbizreport-year-name { text-align: left; }
.zbizreport-year-name p { margin: 0; }
.zbizreport-year-section {
  color: var(--navy-70);
  font-size: 0.72rem;
  margin-top: 2px !important;
}
/* an actual carries slightly more weight than its plan — the plan is the
   reference, the actual is the news. */
.zbizreport-year-actual { font-weight: 500; }
/* the year's own pair, set apart from the quarters it sums */
.zbizreport-year-total {
  font-weight: 600;
  border-left: var(--hair);
  padding-left: var(--s2);
}
.zbizreport-year-note { margin-bottom: var(--s3); }

/* ── the catalogue, as bars ─────────────────────────────────────────── */
.zbizreport-year-titles { margin-bottom: var(--s5); }
.zbizreport-year-title-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 12rem;
  gap: var(--s3);
  align-items: center;
  padding: var(--s2) var(--s3);
  background: #fff;
  border-bottom: var(--hair);
}
.zbizreport-year-title-meta { text-align: right; }
.zbizreport-year-title-meta p { margin: 0; }
.zbizreport-year-title-stage {
  color: var(--navy-70);
  font-size: 0.78rem;
}

@media (max-width: 768px) {
  /* the bars stack; the wide table keeps scrolling rather than reflowing,
     because a plan and its actual only mean anything side by side. */
  .zbizreport-year-title-row { grid-template-columns: 1fr; gap: var(--s1); }
  .zbizreport-year-title-meta { text-align: left; }
}

/* ── year view, second pass (Yael, 2026-09-08) ────────────────────────────
   "highlight all 'planned q' columns in one shade + all 'actual' columns in
   a different one to make it easier to read."

   Eleven columns of figures with nothing separating them meant counting
   across to work out which number was the plan. The tint does that work:
   plan columns sit in a faint lavender wash, actuals stay on white, so the
   eye reads them as two interleaved sets rather than one undifferentiated
   grid.

   Columns 2,4,6,8 are the plans (1 is the label; 10/11 are the year pair);
   nth-child is the only handle available — the cells carry no per-column
   class, and adding one would mean eleven near-identical blocks in the
   pattern. */
.zbizreport-year-head > *:nth-child(2),
.zbizreport-year-head > *:nth-child(4),
.zbizreport-year-head > *:nth-child(6),
.zbizreport-year-head > *:nth-child(8),
.zbizreport-year-row  > *:nth-child(2),
.zbizreport-year-row  > *:nth-child(4),
.zbizreport-year-row  > *:nth-child(6),
.zbizreport-year-row  > *:nth-child(8) {
  background: var(--lav-30);
}
/* the actual columns take the faintest possible navy so they read as a
   band too, without competing with the lavender beside them */
.zbizreport-year-row  > *:nth-child(3),
.zbizreport-year-row  > *:nth-child(5),
.zbizreport-year-row  > *:nth-child(7),
.zbizreport-year-row  > *:nth-child(9) {
  background: rgba(28, 25, 75, 0.04);
}
/* the tint has to reach the cell edges, so the gap moves onto padding */
.zbizreport-year-head,
.zbizreport-year-row { gap: 0; }
.zbizreport-year-head > *,
.zbizreport-year-row  > * { padding: var(--s2) var(--s2); }

/* the year's own pair, set apart from the quarters it sums */
.zbizreport-year-head > *:nth-child(10),
.zbizreport-year-head > *:nth-child(11),
.zbizreport-year-row  > *:nth-child(10),
.zbizreport-year-row  > *:nth-child(11) {
  background: var(--lime-18);
}

/* ── the titles table: name, date, bar, units, balance ─────────────────── */
.zbizreport-year-title-row {
  /* name+date | bar | units+stage | balance */
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.4fr) 9rem 9rem;
}
.zbizreport-year-title-name p { margin: 0; }
/* zProgress draws its own label at a colour this sheet does not control,
   and it came out nearly invisible. The name is rendered as real text
   instead, under this app's own type rules. */
.zbizreport-year-title-label {
  font-weight: 500;
  color: var(--navy);
}
.zbizreport-year-title-date,
.zbizreport-year-title-stage {
  color: var(--navy-70);
  font-size: 0.78rem;
}
.zbizreport-year-title-units { font-weight: 500; }
.zbizreport-year-title-balance { text-align: right; }
.zbizreport-year-title-balance p { margin: 0; }
/* the bar itself sits centred in its column with no label above it */
.zbizreport-year-title-row .zProgress { margin: 0; }

@media (max-width: 900px) {
  .zbizreport-year-title-row {
    grid-template-columns: 1fr 1fr;
    gap: var(--s2);
  }
  .zbizreport-year-title-balance { text-align: left; }
}

/* the year link sits with the quarter chips it summarises, so it takes the
   page gutter like every other band and a little air above the note below */
.zbizreport-year-link { margin-top: var(--s2); margin-bottom: var(--s2); }
/* the two dates under a title read as one quiet block, with the one that
   matters for sales (in the market since) leading */
.zbizreport-year-title-market {
  color: var(--navy-70);
  font-size: 0.78rem;
}

/* the year's flag: a KPI more than 10% off plan, coloured and arrowed.
   Same two tones as the scorecard's own flags, so a red on the year page
   and a red on a quarter mean the same thing. An empty cell collapses so
   the three variants never leave a gap. */
.zbizreport-year-flag-plain,
.zbizreport-year-flag-good,
.zbizreport-year-flag-bad { margin: 0; }
.zbizreport-year-flag-good { color: #2e7d5b; font-weight: 600; }
.zbizreport-year-flag-bad  { color: #a63d40; font-weight: 600; }
.zbizreport-year-flag-plain:empty,
.zbizreport-year-flag-good:empty,
.zbizreport-year-flag-bad:empty { display: none; }

/* ── the quarter strip: chips, start-a-quarter, yearly view ──────────────
   One row — Yael, 2026-09-08: "yearly view should sit here, next to the
   quarters, not separated." The two actions take the chips' own shape so
   the strip reads as one set of things about this year, with the actions
   distinguishable by their lighter fill rather than by sitting apart. */
.zbizreport-quarter-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
}
/* The chips and the two actions each arrive inside their own wrapper — the
   page needs three separate shuttles to build them — and those wrappers
   were stacking the strip vertically, one group per line. `display:
   contents` dissolves them so their children join the strip's own flex row,
   which is what puts Q1 Q2 Q3 side by side again with the actions after
   them (Yael, 2026-09-08: "it was nicer when it was horizontally laid-out,
   not stacked one above the other"). */
.zbizreport-quarter-strip > * { display: contents; }
/* SOLID, not a wash. --lav-30 is 30%-opaque lavender, which is a pale tint
   on the page and a muddy near-black on the CFO's navy bar — where "+ Start
   a quarter" also sits. Navy text on it was unreadable ("too dark and hard
   to read", Yael 2026-09-08). This is the same colour the wash resolves to
   over white, but opaque, so it looks identical wherever it is used and
   never takes on what is behind it. */
.zbizreport-strip-action {
  background: #f0e9f3 !important;
  border-style: dashed !important;
  border-color: var(--lavender) !important;
  color: var(--navy) !important;
}
.zbizreport-strip-action:hover {
  background: #e3d5ea !important;
}

/* the status pill and the year link, side by side under the quarter note */
.zbizreport-status-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s3);
  margin-left: var(--gutter);
  margin-right: var(--gutter);
  margin-bottom: var(--s3);
}
.zbizreport-status-line .zbizreport-status-banner {
  margin-left: 0;
  margin-right: 0;
  margin-bottom: 0;
}

/* ── the scorecard's column header ──────────────────────────────────────
   Yael, 2026-09-08: it "should be closer to it's related lines (below) and
   not so close to the on above, which is unconnected." It labels the rows
   underneath it, but sat tight against whatever section ended above — the
   catalogue table, most often — so it read as that table's footer. */
.zbizreport-sc-head {
  margin-top: var(--s5);
  margin-bottom: var(--s1);
}

/* the year switcher, under the strip it switches. Quieter than the quarter
   chips — it is a way of changing what you are looking at, not one of the
   things being looked at. */
.zbizreport-year-switcher {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
  /* a little more air above than the strip's own gap: this row now sits
     under its own "All reports" heading rather than floating loose beneath
     the quarters. */
  margin-left: var(--gutter);
  margin-right: var(--gutter);
  margin-top: var(--s2);
}
.zbizreport-year-switcher > * { display: contents; }
.zbizreport-year-switch {
  font-size: 0.82rem !important;
  opacity: 0.85;
}

/* the company's three actions on one row — import, attach, edit. They come
   from three different places in the markup (two written here, Edit from
   whichever type-filtered pattern matched), so the wrappers dissolve and
   their buttons join this row directly. */
.zbizreport-venture-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-left: var(--gutter);
  margin-right: var(--gutter);
  margin-bottom: var(--s4);
}
/* dissolve the WRAPPERS only. A zBtn written straight into this row is
   itself the direct child — dissolving it would strip its own frame and
   the row's gap, which is exactly how import/attach lost their buttons
   and ran together as plain text (Yael, 2026-09-08). */
.zbizreport-venture-actions > *:not(.zBtn):not(button):not(a) { display: contents; }
/* the grouped patterns (import+attach, and each Edit form) carry their own
   row classes from when they stood alone. Inside this row they become
   flex ITEMS that lay their own buttons out with the same gap — rather
   than `display: contents`, which dissolved them and left whether the
   row's gap reached the buttons depending on how the collapse resolved.
   Yael, 2026-09-13: "missing space here between bottuns" — the import and
   attach pair, which are the two that share a wrapper. */
.zbizreport-venture-actions .zbizreport-actions,
.zbizreport-venture-actions .zbizreport-actions-joined {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin: 0 !important;
}

/* ── "Migdalor's current position" is not like its neighbours ────────────
   Yael, 2026-09-13: "the 'migdalor's position' bottun is the special one".
   It is special because of what it holds: the other buttons on this row
   file documents or correct details, and this one is Migdalor's judgement
   about whether the investment is working — the thing the board actually
   reads. Lavender-filled rather than white, and pinned to the end with
   `order` so it stays last however the blocks above it are rearranged. */
.zbizreport-venture-actions .zbizreport-act-position { order: 99; }
.zbizreport-venture-actions .zbizreport-act-position .zBtn {
  background: var(--lavender) !important;
  border-color: var(--lavender) !important;
  color: var(--navy) !important;
}
.zbizreport-venture-actions .zbizreport-act-position .zBtn:hover {
  background: #b99ac6 !important;
  border-color: #b99ac6 !important;
}

/* Migdalor's own review of a published quarter, on the report page. Navy
   rather than lavender: this is the house's verdict, not a request for the
   company to act on — and the company never sees it at all. */
.zbizreport-review-note {
  background: var(--navy);
  color: #fff;
  border-radius: var(--radius);
  padding: var(--s3);
  margin: var(--s4) var(--gutter) var(--s3);
}
.zbizreport-review-head {
  margin: 0 0 var(--s1);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--lime);
}
.zbizreport-review-body {
  margin: 0;
  font-size: 0.9rem;
  white-space: pre-wrap;
}

/* what Migdalor asked for, on the quarter it belongs to. Lavender rather
   than the error tone: it is a request to act on, not a failure — and it
   sits above the rows it is about so it is read before they are edited. */
.zbizreport-change-request {
  background: var(--lav-30);
  border-left: 3px solid var(--lavender);
  border-radius: var(--radius);
  padding: var(--s3);
  margin: 0 var(--gutter) var(--s3);
}
.zbizreport-change-request-head {
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--navy-70);
  margin: 0 0 var(--s1);
}
.zbizreport-change-request-body {
  margin: 0;
  white-space: pre-wrap;  /* a multi-line ask keeps its line breaks */
}

/* ── THE APP ON A PHONE ───────────────────────────────────────────────────
   Written 2026-10-13, the first time anyone looked. Three faults, all of
   them things that only exist at this width. */
@media (max-width: 640px) {

  /* 1 · A FIGURE ROW LOSES ITS COLUMN HEADINGS. At this width the row is a
     stack and the header row is hidden, so Revenue read as four anonymous
     em-dashes and an N/A. The heading comes back as a label on the cell
     itself — the same trick the simple row's "Target ·" prefix uses, and
     for the same reason: a cell is either a whole statement or nothing. */
  .zbizreport-sc-target::before,
  .zbizreport-sc-actual::before,
  .zbizreport-sc-variance::before,
  .zbizreport-sc-status::before {
    display: block;
    font-size: 0.6rem;
    font-weight: 500;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--navy-45);
  }
  .zbizreport-sc-target::before   { content: "Target"; }
  .zbizreport-sc-actual::before   { content: "Actual"; }
  .zbizreport-sc-variance::before { content: "vs plan"; }
  .zbizreport-sc-status::before   { content: "Status"; }
  /* the cells read from the left here, like everything else in the stack */
  .zbizreport-sc-cell { text-align: left; }

  /* 2 · THE OVERLAYS COVER THE PAGE. Both the framework's error box
     (`#bifrost-errors`) and its toasts (`#zToast-container`) are fixed
     400px panels pinned to the top right — which on a 390px phone is the
     whole header, including the button the page exists for. On the
     scorecard a success toast was sitting on top of "Publish to the
     board".

     They are moved to the BOTTOM rather than hidden: an error nobody can
     see is worse than one in the way. 56px clears the connection badge,
     which lives in the bottom-right corner. */
  #bifrost-errors,
  .bifrost-error-container,
  #zToast-container,
  .zToast-container {
    top: auto !important;
    bottom: 56px !important;
    left: 8px !important;
    right: 8px !important;
    width: auto !important;
    max-width: none !important;
    max-height: 45vh;
    overflow-y: auto;
  }

  /* 3 · THE BOARD STRIP'S COLUMN NAMES ARE A TABLE HEAD, and there is no
     table at this width — the strip collapses to a stack, so the six
     labels ran together as one line of word-soup above the companies.
     A heading with nothing beneath it to head is worse than none. */
  .zbizreport-glance-wrap { display: none; }
}

/* ── WHEN THE PAGE LOSES ITS CONNECTION ───────────────────────────────────
   Sandra, Altneuland's CFO, 2026-10-15: "Whenever I click on 'Edit' or
   'Fill in', nothing happens." Nothing was wrong with her report or her
   account — the page had been open long enough for the socket to drop, and
   from that moment every button silently does nothing.

   The app already knows: the client flips its badge to "Disconnected" and
   logs "Connection lost — retrying in the background. Reload if this
   persists." But that is an 80px badge in the bottom-right corner and a
   line in a panel, so the only thing the person actually experiences is an
   app that has stopped working. Reproduced exactly by killing the server
   with a scorecard open: the badge changed, and clicking Fill in opened
   nothing.

   So the badge stops being a badge while it is in that state: a bar across
   the foot of the page, in the colour this app uses for "something needs
   you", saying the one thing that fixes it. It returns to a quiet corner
   badge the moment the connection is back. */
.zConnection.zBadge-connection.zBadge-pending,
.zConnection.zBadge-connection.zBadge-error {
  position: fixed !important;
  left: 0 !important;
  right: 0 !important;
  bottom: 0 !important;
  top: auto !important;
  width: auto !important;
  max-width: none !important;
  margin: 0 !important;
  border: none !important;
  border-top: 3px solid var(--navy) !important;
  border-radius: 0 !important;
  background: var(--lime-18) !important;
  color: var(--navy) !important;
  font-size: 0.95rem !important;
  font-weight: 500 !important;
  text-align: center !important;
  padding: var(--s2) var(--s3) !important;
  z-index: 10001 !important;      /* above the error panel and the toasts */
  box-shadow: 0 -6px 24px rgba(28, 25, 75, 0.12) !important;
  /* the badge ships `white-space: nowrap`, which is right for the word
     "Connected" in a corner and wrong for a sentence across a page: the
     message ran 1111px wide inside a 1024px bar and simply fell off the
     end (measured, not guessed). */
  white-space: normal !important;
  line-height: 1.4 !important;
}
/* the badge's own word is "Disconnected", which says what happened and not
   what to do about it. */
.zConnection.zBadge-connection.zBadge-pending::after,
.zConnection.zBadge-connection.zBadge-error::after {
  content: " — this page lost its connection, so buttons will not respond. Reload to carry on. Nothing you entered is lost.";
  font-weight: 400;
}

/* ── a colour per book stage ──────────────────────────────────────────────
   Yael, 2026-10-29, and the Altneuland brief's C6: "review book-status
   colors for consistent, distinguishable meanings; define a legend". Every
   stage rendered the same pale lavender pill before this, so the column was
   a word you had to read rather than a state you could scan.

   The colour cannot be built from the data — `_zClass` does not resolve a
   `%token` (finding 16) — so each stage is its own gated cell and exactly
   one of them renders. See the Stage block in the title patterns.

   The ramp is deliberate and reads in order: a title starts cold (grey),
   warms as work goes into it (blue, then amber), and lands green when it is
   earning. Distinguishable in greyscale too, because these print: the
   lightness steps down as the stage advances, so the pills differ in tone
   and not only in hue. */
/* `align-items: flex-start` is load-bearing: without it the column's flex
   children stretch to its full width and the pill reads as a filled bar
   rather than a tag. The old single cell was `inline-block` and sized
   itself; five stacked cells do not. */
.zbizreport-stage-cell {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.15rem;
}

.zbizreport-stage-pipeline   { background: var(--navy-12); color: var(--navy-70); }
.zbizreport-stage-signed     { background: #dbe4f3; color: #2f4369; }
.zbizreport-stage-editing    { background: #fbeccd; color: #7a5712; }
.zbizreport-stage-production { background: #f7dcc0; color: #7c4a16; }
.zbizreport-stage-market     { background: #cfe8d8; color: #1f5c3c; font-weight: 500; }

/* the catch-all pill steps aside whenever a known stage rendered beside it.
   It cannot simply be left out: `stage` is deliberately not an enum, so a
   title in a state nobody anticipated still has to show what it says. */
.zbizreport-stage-cell:has(.zbizreport-stage-known) .zbizreport-stage-any {
  display: none;
}

/* ── the legend ───────────────────────────────────────────────────────────
   The brief asks for one, and a colour scheme without a key is a private
   joke. Sits under the catalogue, quiet enough to ignore once learned. */
.zbizreport-stage-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  align-items: center;
  margin: var(--s2) var(--gutter) var(--s4);
  font-size: 0.75rem;
  color: var(--navy-70);
}
.zbizreport-stage-legend p { margin: 0; }
.zbizreport-stage-legend .zbizreport-title-stage { font-size: 0.72rem; }

/* ── where a row's figures come from ──────────────────────────────────────
   Yael, 2026-10-29: "i can't see a mark for what is automated and what is
   to be inputed by cfo". Every row had said so since that morning, as a
   sentence under the label — and a sentence in 0.78rem grey on a forty-row
   sheet is not a mark. This is.

   It sits INSIDE the label cell rather than taking a grid column of its
   own. The row grid has seven fixed tracks shared with the header and with
   three other row shapes; an eighth would have had to be added to all of
   them in step, and the one time those drifted apart every heading sat one
   column right of the figures it named. */
/* the label cell becomes a two-track grid so the badge sits BESIDE the row
   name instead of above it. On its own line it cost every row an extra
   line of height, which is the density complaint this round is answering. */
.zbizreport-sc-label {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 0.4rem;
  align-items: baseline;
}
.zbizreport-sc-label > :first-child { grid-column: 1; grid-row: 1; }
.zbizreport-sc-source { grid-column: 2; grid-row: 1; justify-self: start; }
.zbizreport-sc-label > .zbizreport-sc-note { grid-column: 1 / -1; grid-row: 2; }

.zbizreport-sc-source {
  display: flex;
  gap: 0.3rem;
}
.zbizreport-src-badge {
  margin: 0;
  padding: 0.05rem 0.35rem;
  border-radius: 0.25rem;
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  align-self: flex-start;
}
/* AUTO is the one worth noticing — it is the row you must not type into */
.zbizreport-src-auto { background: #d8e8de; color: #1f5c3c; }
.zbizreport-src-plan { background: var(--lav-30); color: var(--navy-70); }
/* CFO is the default state of most rows, so it stays quiet: present for
   anyone checking, invisible to anyone scanning */
.zbizreport-src-cfo  { background: transparent; color: var(--navy-45); padding-left: 0; }

/* the catch-all steps aside whenever a specific badge rendered beside it */
.zbizreport-sc-source:has(.zbizreport-src-known) .zbizreport-src-plan,
.zbizreport-sc-source:has(.zbizreport-src-known) .zbizreport-src-cfo,
.zbizreport-sc-source:has(.zbizreport-src-plan) .zbizreport-src-cfo {
  display: none;
}

/* cost per copy sold — a derived figure, so it reads a shade quieter than
   the two a person typed either side of it */
.zbizreport-title-percopy { color: var(--navy-70); }

/* title-level notes: prose in a table of numbers, so it is sized down and
   allowed to wrap rather than forcing the column wider */
.zbizreport-title-notes {
  font-size: 0.78rem;
  color: var(--navy-70);
  line-height: 1.35;
}

/* ── the verdict word, coloured ───────────────────────────────────────────
   Yael, 2026-10-29: "let's color code the words positive / negative / on
   plan". Colour reached only Hard Criteria before this, as a red or green
   arrow; every other section printed its verdict in the same grey as the
   figures beside it.

   Same two tones as the variance percentage, so a red word and a red figure
   on one row are saying one thing rather than two. The word still carries
   the meaning on its own — these print, and in greyscale Positive and
   Negative are still two different words. */
.zbizreport-status-pos    { color: #2e7d5b; font-weight: 500; }
.zbizreport-status-neg    { color: #a63d40; font-weight: 500; }
/* on plan is the uneventful answer and reads as one — present, not loud */
.zbizreport-status-onplan { color: var(--navy-70); }
/* N/A is the quietest: nothing to compare is not a verdict at all */
.zbizreport-status-na     { color: var(--navy-45); }

/* ── a column header that follows its own table, and only its own ────────
   Yael, 2026-10-30, scrolling a long catalogue: "could you make it to be
   actually sticky? otherwise i get lost."

   This is NOT a reversal of removing the sticky earlier. Itai's complaint
   was that a header stuck to the WHOLE PAGE and followed him down over the
   catalogue, the rights table and the worked-out figures, captioning each
   with the column names of a grid he had left two sections ago. A sticky
   element is bounded by its containing block, and the containing block was
   the entire scorecard.

   So every grid on this page now sits in its own `.zbizreport-sc-table`
   wrapper, and the catalogue already had one. The header follows you down
   its own rows and stops at the last of them — which is what both of them
   were asking for, from opposite directions.

   `top` clears the back-bar, which is itself sticky at the top of the
   page; without the offset the column names slide underneath it and are
   simply gone. `z-index` sits below that bar (20) and above the rows. */
.zbizreport-sc-table { position: relative; }

.zbizreport-sc-head,
.zbizreport-title-head {
  position: sticky;
  /* 4.75rem clears the back-bar itself (measured at 73.8px) — and that is
     not enough, because the bar does not end where it ends. It paints a
     `box-shadow: 0 0 0 var(--gutter) var(--cream)`: a solid cream
     rectangle spreading a full gutter PAST its own box on every side, so
     its background reaches the window edges with no seam. At z-index 20
     that spread covers the top of anything parked underneath it.

     The first version parked the header at 4.75rem and the top ~30px of
     it sat behind that invisible cream band — the column names were there,
     stuck correctly, and cut in half. Yael, 2026-10-30: "the header is cut
     and you cant see the text".

     So the offset has to clear the bar AND what the bar paints. Written as
     a calc on the same `--gutter` the shadow uses, because that variable
     shrinks on narrow screens and the two must move together.

     5.5rem, not the 4.75rem that merely matches the bar's measured height:
     that left 2.5px of overlap in a standalone reproduction of these two
     rules, because the bar renders a few px taller under different button
     and font metrics. 5.5rem + gutter clears a bar up to 88px tall, which
     is ~14px of headroom on the real page. The gap is background; the
     overlap was unreadable column names. */
  top: calc(5.5rem + var(--gutter));
  z-index: 3;
}

/* below 900px the app hides these headers and writes each label into the
   row instead, so there is nothing to stick — and a sticky bar would eat
   a tenth of a phone screen to say nothing. */
@media (max-width: 900px) {
  .zbizreport-sc-head,
  .zbizreport-title-head { position: static; }
}

/* ── why the sticky headers above actually stick ──────────────────────────
   zOS's own base stylesheet sets `overflow: hidden auto` on <body>. That
   makes BODY the scroll container rather than the viewport, and a
   `position: sticky` element inside a scroll container that never itself
   scrolls simply never sticks. The rule applies, the browser computes
   `position: sticky`, and nothing moves — which is exactly what happened:
   the suite asserted the computed property and passed on a page where the
   feature did nothing (2026-10-30).

   `overflow-x: clip` keeps what that rule was for — no sideways scroll on
   a wide table — WITHOUT creating a scroll container, which is the whole
   difference between `clip` and `hidden`. Paired with `overflow-y: visible`
   so scrolling goes back to the viewport where sticky can see it. The two
   must be set together: per spec a `visible` axis beside a `hidden`/`auto`
   one computes to `auto`, and only `clip` leaves it alone. */
body {
  overflow-x: clip;
  overflow-y: visible;
}

/* ── and why the sticky headers need THIS as well ─────────────────────────
   The overflow fix above was necessary and not sufficient. zOS renders
   every zUI block as its own `<div>`, and a shuttled pattern adds another
   for the pattern's root — so the column header's real parent is an
   anonymous div EXACTLY as tall as the header itself:

       .zbizreport-sc-head    70.5px
       div (anonymous)        70.5px   <- the sticky element's parent
       div (anonymous)        70.5px
       .zbizreport-sc-table  925px

   A sticky element can only travel inside its containing block. With a
   parent the same height as the element there is nowhere to travel, so it
   sits still — no error, and `getComputedStyle().position` still reads
   `sticky`. That is the second reason the feature shipped twice looking
   implemented (2026-10-30).

   `display: contents` removes those two wrappers from the box tree without
   removing them from the DOM, so the header's containing block becomes the
   table wrapper and it has the full height of its own rows to move within —
   and stops at the last of them, which is the bound Itai asked for. The
   divs carry no styling of their own, so nothing is lost with their boxes.

   MEASURED, not assumed: at scroll positions 900 / 1200 / 1600 the header
   holds at exactly 76px, and releases once its table's bottom passes the
   viewport. Same for the catalogue's own header. */
.zbizreport-sc-table > div:has(.zbizreport-sc-head),
.zbizreport-sc-table > div:has(.zbizreport-sc-head) > div,
.zbizreport-catalogue > div:has(.zbizreport-title-head),
.zbizreport-catalogue > div:has(.zbizreport-title-head) > div {
  display: contents;
}
