/* PORTED VERBATIM from vo-app-marketing-hub/apps/Marketing-Hub/marketing-hub-home.css, minus 5 rule(s)
   whose selectors name a class crm.css / ax-containers.css already own.
   DO NOT EDIT — node tools/port-marketing-hub.cjs */
/* training-materials.css:4865-4867 — real thumbnail wins over the SVG preview */
.tmr-thumb:has(.tmr-thumb-img:not([style*='display: none'])) .tmr-prev,
.tmr-thumb:has(.tmr-thumb-img:not([style*='display: none'])) svg {
  display: none;
}

/* training-materials.css:4875-4911 — list row thumbnail / preview block */
.tmr-row {
  align-items: center;
}

.tmr-row .tmr-badges {
  align-self: flex-start;
  margin-top: 0;
}

.tmr-thumb {
  position: relative;
  width: 60px; height: 44px;
  border-radius: 8px;
  flex-shrink: 0;
  background-size: cover;
  background-position: center top;
  border: 1px solid var(--border, #e5e7eb);
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}

.tmr-thumb--preview {
  border: 1px solid var(--border, #e8ecf0);
  position: relative;
}

.tmr-thumb-img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: top center;
  border-radius: inherit;
  z-index: 1;
  background: rgba(15, 23, 42, 0.06);
}

.tmr-thumb-img--fallback {
  object-fit: cover;
  object-position: top center;
  background: rgba(15, 23, 42, 0.08);
}

/* training-materials.css:5568-5595 — preview SVG + right-aligned badges */
.tmr-prev {
  width: 32px; height: 40px;
}

.tmr-badges {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 5px;
  flex-shrink: 0;
  min-width: 0;
  margin-left: auto;
}

.tmr-badge-type {
  font-size: 8px;
  font-weight: 600;
  padding: 1px 7px;
  border-radius: 4px;
  text-transform: uppercase;
  letter-spacing: .4px;
  white-space: nowrap;
  line-height: 1.4;
  opacity: .6;
}

/* ══════════════════════════════════════════════════════════════
   END VENDORED BLOCK — everything below is this hub's own.
   ══════════════════════════════════════════════════════════════ */

/* ── The two views. Exactly one is mounted at a time; the toggle swaps
      which container has content, so neither can leak layout into the other. */
.mh-home-list { display: flex; flex-direction: column; gap: 6px; padding: 4px 0 2px; }

.mh-home-grid { padding: 4px 0 2px; }

.mh-home-view[hidden] { display: none !important; }

/* ── View toggle — lives in .wk-focus-action-row beside the search box, so it
      reads as part of the Work focus header rather than a second toolbar. */
.mh-viewtog { display: inline-flex; gap: 2px; background: var(--surf-2, #f1f5f9);
  border: 1px solid var(--border, #e2e8f0); border-radius: 8px; padding: 2px; flex-shrink: 0; }

.mh-viewtog button {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 24px; border: 0; border-radius: 6px; cursor: pointer;
  background: transparent; color: var(--ink4, #94a3b8); transition: background .15s, color .15s;
}

.mh-viewtog button svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 2; }

.mh-viewtog button:hover { color: var(--ink2, #475569); }

.mh-viewtog button.is-active { background: var(--surf, #fff); color: var(--c-blue, #3b82f6);
  box-shadow: 0 1px 2px rgba(0,0,0,.08); }

/* ── Empty state, in the Work app's `.wk-empty` register. */
.mh-home-empty { padding: 34px 20px; text-align: center; }

.mh-home-empty-title { font-size: 13px; font-weight: 700; color: var(--ink2, #475569); }

.mh-home-empty-sub { font-size: 11px; color: var(--ink4, #94a3b8); margin-top: 4px;
  max-width: 52ch; margin-left: auto; margin-right: auto; line-height: 1.5; }

/* 🚨 IN CARD VIEW THE EMPTY STATE IS A GRID ITEM, SO IT SITS IN COLUMN 1.
   Tyler, 2026-08-07, looking at the card view: *"it says there's nothing here
   that should show in the middle still."*
   `.mh-home-grid` further down is `repeat(3, minmax(0,1fr))` and this block is
   written into it as an ordinary child — so `text-align:center` centres it
   inside the LEFT THIRD of the panel, and it reads as a stray label rather
   than the panel's answer. Nothing about it is data-dependent, which is why it
   looks like a rendering bug: it is one. It has to span every column.
   ⚠️ Scoped to `.mh-home-grid` because the same block is reused by the list
   view, which is not a grid — `grid-column` would be inert there anyway, but
   the min-height would not be. */
.mh-home-grid .mh-home-empty {
  grid-column: 1 / -1;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  min-height: 200px;
}

/* ── Support-rail rows. `.wk-up-row` is work.css's; these are the bits it
      cannot supply (the leading stage dot and the trailing timestamp). */
.mh-rail-row { display: flex; align-items: center; gap: 8px; padding: 7px 2px; cursor: pointer;
  border-radius: 8px; transition: background .15s; }

.mh-rail-row:hover { background: var(--hover-bg, #f8fafc); }

.mh-rail-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }

.mh-rail-main { flex: 1; min-width: 0; }

.mh-rail-title { font-size: 11.5px; font-weight: 600; color: var(--ink, #0f172a);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.mh-rail-sub { font-size: 10px; color: var(--ink4, #94a3b8); margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.mh-rail-when { font-size: 9.5px; font-weight: 700; color: var(--ink4, #94a3b8);
  flex-shrink: 0; text-transform: uppercase; letter-spacing: .3px; }

/* ══════════════════════════════════════════════════════════════
   PIECE POPUP — two panes: the piece as it will actually go out on
   the left, its facts and its stage actions on the right. Built on
   this hub's existing .mh-modal-overlay / .mh-modal skin so it is the
   same dialog the rest of the hub already opens.
   ══════════════════════════════════════════════════════════════ */
.mh-piece-modal { width: min(980px, 94vw); max-height: 88vh; display: flex; flex-direction: column; }

.mh-piece-body { display: grid; grid-template-columns: 1fr 300px; gap: 0;
  overflow: hidden; flex: 1; min-height: 0; }

@media (max-width: 820px) {
.mh-piece-body { grid-template-columns: 1fr; }
}

.mh-piece-preview { padding: 20px 22px; overflow: auto; background: var(--surf-2, #f8fafc);
  border-right: 1px solid var(--border, #e2e8f0); min-height: 260px; }

@media (max-width: 820px) {
.mh-piece-preview { border-right: 0;
  border-bottom: 1px solid var(--border, #e2e8f0); }
}

/* The preview "sheet" — a page-shaped card, so an email/post reads as the
   artifact rather than as more app chrome. */
.mh-piece-sheet { background: var(--surf, #fff); border: 1px solid var(--border, #e2e8f0);
  border-radius: 10px; padding: 20px 22px; box-shadow: 0 1px 3px rgba(0,0,0,.05);
  font-size: 13px; line-height: 1.6; color: var(--ink2, #334155); }

.mh-piece-sheet img { max-width: 100%; height: auto; border-radius: 8px; display: block; }

.mh-piece-sheet-subject { font-size: 15px; font-weight: 800; color: var(--ink, #0f172a);
  margin-bottom: 10px; line-height: 1.35; }

.mh-piece-sheet-body { white-space: pre-wrap; word-break: break-word; }

.mh-piece-sheet-hero { margin-bottom: 14px; }

.mh-piece-noprev { text-align: center; padding: 30px 16px; color: var(--ink4, #94a3b8); font-size: 12px; }

.mh-piece-side { padding: 18px; overflow: auto; display: flex; flex-direction: column; gap: 14px; }

.mh-piece-facts { display: flex; flex-direction: column; gap: 0; }

.mh-piece-fact { display: flex; gap: 10px; padding: 7px 0;
  border-bottom: 1px solid var(--border-s, #f1f5f9); }

.mh-piece-fact:last-child { border-bottom: 0; }

.mh-piece-fact-k { font-size: 10px; font-weight: 700; color: var(--ink4, #94a3b8);
  text-transform: uppercase; letter-spacing: .4px; width: 88px; flex-shrink: 0; padding-top: 1px; }

.mh-piece-fact-v { font-size: 11.5px; color: var(--ink, #0f172a); font-weight: 600;
  flex: 1; min-width: 0; word-break: break-word; }

.mh-piece-stage { display: inline-flex; align-items: center; gap: 6px; align-self: flex-start;
  font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .5px;
  padding: 5px 11px; border-radius: 999px; }

.mh-piece-stage-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }

.mh-piece-actions { display: flex; flex-direction: column; gap: 7px; margin-top: auto; padding-top: 8px; }

.mh-piece-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  font-size: 12px; font-weight: 700; padding: 9px 14px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--border, #e2e8f0); background: var(--surf, #fff); color: var(--ink2, #334155);
  transition: background .15s, border-color .15s, color .15s; }

.mh-piece-btn svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 2; }

.mh-piece-btn:hover { background: var(--hover-bg, #f8fafc); border-color: var(--ink4, #cbd5e1); }

.mh-piece-btn--primary { background: var(--c-blue, #3b82f6); border-color: var(--c-blue, #3b82f6); color: #fff; }

.mh-piece-btn--primary:hover { background: #2563eb; border-color: #2563eb; color: #fff; }

.mh-piece-btn[disabled] { opacity: .55; cursor: default; }

/* ══════════════════════════════════════════════════════════════
   "A ZERO IS A CLAIM" — the unreachable-source banner.
   Deliberately loud. A quiet version of this message is how a row
   of zeros gets read as "we have nothing scheduled" when the truth
   is "we could not reach the scheduler".
   ══════════════════════════════════════════════════════════════ */
.mh-src-down {
  display: flex; gap: 10px; align-items: flex-start;
  background: #fffbeb; border: 1px solid #fcd34d; border-left: 3px solid #f59e0b;
  border-radius: 10px; padding: 10px 14px; margin-bottom: 14px;
  font-size: 11.5px; line-height: 1.5; color: #78350f;
}

.mh-src-down svg { width: 15px; height: 15px; flex-shrink: 0; margin-top: 1px; color: #d97706; }

.mh-src-down b { font-weight: 800; }

@media (prefers-color-scheme: dark) {
.mh-src-down { background: #2a1f07; border-color: #78500a; color: #fde68a; }
}

/* ── THE ACTUAL PICTURE OF THE EMAIL ───────────────────────────
   A campaign that stored vo_htmlcontent gets rendered, scaled, in a
   sandboxed iframe on its card. Not a mockup — the bytes that went out.
   ⚠️ sandbox="" (empty) blocks scripts, forms, popups and same-origin
   access, so a marketing email cannot run anything inside our page.
   ⚠️ pointer-events:none — the card is the click target; an iframe
   would otherwise swallow the click that opens the receipt. */
.mh-mailshot {
  position: absolute; inset: 0; overflow: hidden; background: #fff;
  pointer-events: none;
}

.mh-mailshot iframe {
  position: absolute; top: 0; left: 0;
  width: 250%; height: 250%;
  transform: scale(.4); transform-origin: 0 0;
  border: 0; background: #fff; pointer-events: none;
}

/* A real image (a post's media, a briefing hero) fills the same frame. */
.mh-mailshot--img img { width: 100%; height: 100%; object-fit: cover; object-position: top center; display: block; }

/* ⚠️ THE DECORATIVE PATTERN MUST NOT PAINT OVER THE PRODUCT. work.css draws
   `.rp-card-thumb::before` as the type artwork; once there is a real preview
   underneath, that pattern is noise on top of the thing you came to look at. */
.rp-card-thumb:has(.mh-mailshot)::before { background: none; content: none; }

/* Once a real preview is showing, the type glyph stops being the picture and
   becomes a small corner marker — it still says "this is an email" without
   covering the email. */
.rp-card-thumb-icon.mh-thumb-chip {
  position: absolute; bottom: 7px; left: 7px; z-index: 3;
  width: 24px; height: 24px; border-radius: 7px;
  background: rgba(15, 23, 42, .55); backdrop-filter: blur(6px);
  border: 1px solid rgba(255, 255, 255, .18);
}

.rp-card-thumb-icon.mh-thumb-chip svg { width: 12px; height: 12px; }

/* ── HOVER. work.css lifts .rp-card 3px already; these are the parts that
      only make sense once the thumbnail is a live document: the preview
      brightens and the card's accent edge strengthens. */
.mh-card { will-change: transform; }

.mh-card:hover .mh-mailshot { filter: saturate(1.04) brightness(1.02); }

.mh-card:hover .rp-card-thumb-icon.mh-thumb-chip { background: rgba(15, 23, 42, .72); }

.mh-card:focus-visible { outline: 2px solid var(--c-blue, #3b82f6); outline-offset: 2px; }

/* ── THE ROW THUMBNAIL IS A PICTURE OF THE THING. Tyler, 2026-08-07:
      *"the list view is what's not matching. I want the list card to look at
      the picture on it."*

   🚫 What used to be here fought Training Materials instead of copying it: the
   thumb bottom-aligned its child, the SVG stand-in was blown up TALLER than
   the 44px box so its base would clip, and the rendered page was inset 4px a
   side and sunk 6px past the bottom with a drop shadow. That was built to
   reproduce a "preview rising from the bottom, base cut off" effect — and I
   MEASURED the real card: it does not do that. At 1280px its stand-in renders
   56x40 inside a 120px header and clears the bottom edge by 52px, unclipped.
   So the effect was invented, and inventing it is what made the row look
   wrong: 4px of grey border on every side of a 60x44 picture reads as a
   floating stamp, not a document.

   Training Materials' row is plain: `.tmr-thumb` is 60x44, the artefact COVERS
   it edge to edge, and it is cropped from the TOP — a document is recognised
   by its masthead. The stand-in is drawn only when there is no artefact. ── */
.mh-home-list .tmr-thumb .tmr-thumb-img { object-position: top center; }

/* A rendered page is the artefact here, so it covers the box exactly the way
   `.tmr-thumb-img` does — no inset, no shadow, read from the top edge. */
.mh-home-list .tmr-thumb .mh-rowshot { inset: 0; border-radius: inherit; }

/* Training Materials hides the stand-in from the <img>'s onload; a sandboxed
   iframe gives us no such hook, so CSS does it — otherwise the artwork flashes
   underneath before the frame paints. */
.tmr-thumb:has(.mh-rowshot) .tmr-prev { display: none; }

/* Only when there is nothing to show: the coloured stand-in, centred, at the
   size Training Materials draws it. */
.mh-home-list .tmr-thumb { align-items: center; justify-content: center; }

.mh-rowshot { position: absolute; inset: 0; overflow: hidden; background: #fff; pointer-events: none; }

.mh-rowshot iframe {
  position: absolute; top: 0; left: 0;
  width: 400%; height: 400%;
  transform: scale(.25); transform-origin: 0 0;
  border: 0; background: #fff; pointer-events: none;
}

/* ── TAGS. Every chip states a fact; the state chip leads and is the only
      one that carries weight, so the eye lands on "Needs review" first. */
.mh-tags { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }

.mh-tags .rp-card-tag { white-space: nowrap; }

.mh-tag--state { font-weight: 800; letter-spacing: .5px; }

/* In a list row the badge column must not push the title out; it wraps to two
   short lines instead of forcing the row wide. */
.mh-home-list .tmr-badges.mh-tags { max-width: 46%; justify-content: flex-end; row-gap: 3px; }

.mh-home-list .tmr-badges.mh-tags .rp-card-tag { font-size: 8.5px; padding: 2px 6px; }

/* the full-size email inside the detail popup */
.mh-piece-mail { border: 1px solid var(--border, #e2e8f0); border-radius: 8px; overflow: hidden; background: #fff; }

.mh-piece-mail iframe { display: block; width: 100%; height: 460px; border: 0; background: #fff; }

.mh-piece-note {
  background: #fef2f2; border: 1px solid #fecaca; color: #991b1b;
  border-radius: 8px; padding: 8px 11px; font-size: 11px; margin-bottom: 12px; line-height: 1.5;
}

/* ══════════════════════════════════════════════════════════════
   REPLIES — the only surface that lists individual emails, so the
   only one whose row leads with a PERSON rather than an artifact.
   The face is the CRM's real photo; initials only when there isn't one.
   ══════════════════════════════════════════════════════════════ */
.mh-reply-face {
  position: relative; width: 38px; height: 38px; border-radius: 50%;
  flex-shrink: 0; overflow: hidden; display: flex; align-items: center; justify-content: center;
  background: var(--surf-2, #eef2f7); color: var(--ink3, #64748b);
  font-size: 12px; font-weight: 800; letter-spacing: .3px;
}

.mh-reply-face img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 1; }

.mh-reply-face i { font-style: normal; }

.mh-reply-row .mh-rail-when { align-self: center; }

.mh-reply-about { color: var(--ink4, #94a3b8); }

.mh-reply-head { display: flex; align-items: center; gap: 12px; margin-bottom: 14px;
  padding-bottom: 14px; border-bottom: 1px solid var(--border-s, #f1f5f9); }

.mh-reply-big-face { width: 46px; height: 46px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }

.mh-reply-big-face--i { display: flex; align-items: center; justify-content: center;
  background: var(--surf-2, #eef2f7); color: var(--ink3, #64748b); font-size: 15px; font-weight: 800; }

.mh-reply-name { font-size: 14px; font-weight: 800; color: var(--ink, #0f172a); }

.mh-reply-addr { font-size: 11px; color: var(--ink4, #94a3b8); margin-top: 2px; }

/* The receipt kit draws its own faces; this is the wrapper our
   faceHtml() hook returns so a CRM photo lands round and cropped. */
.mh-rcpt-face { display: inline-block; position: relative; border-radius: 50%; overflow: hidden; flex-shrink: 0; }

.mh-rcpt-face img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ══════════════════════════════════════════════════════════════════════════
   `hidden` MUST HIDE. Restored for the vendored overlays.

   Tyler, 2026-08-07: *"I click into the daily briefing and I try to click out
   of the pop-up. It just blurs."*

   `.wk-rr-overlay` (marketing-hub-review.css:77) is declared `display:flex`
   with no `[hidden]` companion rule. An AUTHOR `display:flex` outranks the UA
   stylesheet's `[hidden]{display:none}`, so `ov.hidden = true` in
   marketing-hub-home.js hid nothing: closing the popup cleared the dialog's
   innerHTML but left a full-viewport violet scrim with a 1px blur sitting at
   z-index 1000000. Every click after that — the top nav included — landed on
   it. Measured in Chromium: `elementFromPoint(centre)` returned
   `#mhPieceOverlay` and the Campaigns tab was unreachable until reload.

   The rule lives HERE, not in marketing-hub-review.css, because that file is
   vendored verbatim from report-review.js's stylesheet and any edit there is
   lost at the next re-vendor. `.class[hidden]` (0,2,0) beats `.class` (0,1,0)
   on specificity, so load order does not matter — but `!important` is kept
   anyway because these overlays are the ones that brick the page when this
   fails, and a future vendored copy may well raise its own specificity.

   Same guard for the three siblings that share the defect
   (`.wk-rr-ask-ov`, `.wk-nr-tpick-ov`) plus the two hub overlays whose own
   stylesheets have the identical shape, so none of them can become the next
   version of this bug.
   ══════════════════════════════════════════════════════════════════════════ */
.wk-rr-overlay[hidden],
.wk-rr-ask-ov[hidden],
.wk-nr-tpick-ov[hidden],
.cb-overlay[hidden],
.mhpk-overlay[hidden] { display: none !important; }

/* ═══════════════════════════════════════════════════════════════════════
   THE BUNDLE CARD ON THE SIX SURFACES
   The `.tmb-*` rules themselves are the Resource Hub port and live in
   marketing-hub-campaigns.css, which this hub loads for every tab. These
   are only the home-side trims. Markup: marketing-hub-piece-tiles.js.
   ═══════════════════════════════════════════════════════════════════════ */
.mh-home-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 20px;
}

@media (max-width: 1180px) {
.mh-home-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 700px) {
.mh-home-grid { grid-template-columns: 1fr; }
}

/* ═══════════════════════════════════════════════════════════════════════
   THE SQUARE. Tyler, 2026-08-12: *"make it as tall as a square, or basically
   like an Instagram post… that way the thumbnail actually looks better,
   especially for social media content, and you can just see it better."*

   A 120px letterbox is a THUMBNAIL of a piece; a 1:1 frame is the piece. It
   is also the only shape that is honest about a social post, which is square
   at the source — cropping one into a 320×120 band throws away most of the
   artwork the person is here to judge.

   ⚠️ `aspect-ratio` needs `height:auto` AND `max-height:none` to take: the
   ported `.tmc-header` hard-sets both to 120px, and a fixed height beats a
   ratio every time.
   ⚠️ Both cards, in one rule. The bundle card and the material card land in
   the SAME grid — one square and one letterbox side by side reads as a
   rendering bug, not as two card types.
   ⚠️ These are scoped to the home-side `.mh-*` classes on purpose. The
   `.tmc-*`/`.tmb-*` blocks in marketing-hub-campaigns.css are the Resource
   Hub port and are shared with the list-row thumbnail, which must stay small.

   🚨 THE BOTTOM EDGE IS NOT DECORATION — IT IS WHAT MAKES THE SQUARE READABLE.
   Zeroing the ported `border-bottom` cost two rounds. Tyler, twice: *"this is
   not a square… they still don't look like perfect squares, they look tall in
   the thumbnail."* The frame measures 1.000 — but most pieces here are EMAILS,
   which are white, and the caption below them is white, so with no line between
   them the eye takes the picture and the caption as ONE region: 312 wide by 378
   tall. A measurably perfect square reads as a portrait rectangle when its
   bottom edge is invisible. Slightly darker than `--border` on purpose: this
   line has to survive against a white email, where the card's own outer border
   never has to.
   ⚠️ A BORDER, NOT AN INSET box-shadow. The artwork is `position:absolute;
   inset:0`, which resolves against the PADDING box — so a border sits outside
   it and stays visible, while an inset shadow paints under the children and the
   email covers it. The sides need nothing: the card's own 1px border bounds them.
   ⚠️ AND THE COMMENT LIVES OUT HERE, NOT INSIDE THE BLOCK. Written between two
   declarations, this same prose left an orphaned end-marker behind on an edit
   and took the whole rule down with it — the frame silently fell back to the
   ported 120px and measured 252×120. Comments above the selector, always.
   ═══════════════════════════════════════════════════════════════════════ */
.mh-mat-card .tmc-header,
.mh-tmb-card .tmb-card-header {
  aspect-ratio: 1 / 1;
  height: auto;
  max-height: none;
  min-height: 0;
  padding: 0;
  align-items: center;
  justify-content: center;
  border-bottom: 1px solid rgba(15, 23, 42, .12);
}

/* The stand-in artwork was drawn at 56×70 for a 120px band; in a square it is
   a stamp in the middle of an empty field. Sized off the frame instead, so it
   scales with the column count and keeps its own 4:5 proportions. */
.mh-mat-card .tmc-header .tmr-prev,
.mh-mat-card .tmc-header .tmc-prev-svg {
  width: 34%; height: auto; max-height: 52%;
}

.mh-tmb-card .tmb-mosaic-cell .tmc-prev-svg { width: 28%; height: auto; }

/* 🚨 THE ZOOM IS SET BY THE EMAIL'S WIDTH, NOT BY THE FRAME'S HEIGHT.
   I shipped 160%/.625 reasoning about the frame getting taller: a 306px square
   × 160% is a 490px viewport, which shows more of a short email than a 765px
   one does. It also CUTS A 600px EMAIL OFF ON THE RIGHT — and the cards this
   renders on MRG are 252px, not 306px, so the viewport there was 403px and a
   third of every email was simply missing. Tyler saw it as "this is not a
   square", which is exactly right: a picture sliced down its right edge reads
   as a fragment, not as a page.

   A marketing email is a fixed 600px table. The percentage has to keep the
   viewport at or above that on the NARROWEST card in the fleet, which is MRG's
   252px: 250% → 630px, 30px of margin. Upstream's 306px card lands at 765px,
   wider than it needs but only costing background either side.

   Filling the square is then the frame's job, not the zoom's: 630px of email
   scaled into a 252px box shows two and a half times what the 120px band did,
   and anything longer than one screen crops at the fold. A short email still
   ends in its own white — that white IS the email, and inventing a tighter
   crop to hide it would cut the width again. */
.mh-mat-card .mh-cardshot iframe { width: 250%; height: 250%; transform: scale(.4); }

/* Same arithmetic one level down: a mosaic cell in a 252px square header is
   ~124px, and 400% keeps its viewport at 496px — under 600, but a cell is a
   quarter-tile that is read as a thumbnail, not a page. */
.mh-tmb-card .tmb-mosaic-cell--shot .mh-mailshot iframe { width: 400%; height: 400%; transform: scale(.25); }

/* ── THE SHELF: the chips, over the bottom of the picture ─────────────────
   Its own gradient rather than the header's `::after`, because that scrim is
   only painted when there IS artwork — and the chips have to stay readable on
   the flat gradient of a piece that has none. Sitting ON the picture is what
   frees the white strip below to be nothing but the name. */
.mh-shelf {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 3;
  display: flex; flex-wrap: nowrap; align-items: center; gap: 5px;
  padding: 26px 10px 10px;
  background: linear-gradient(180deg, rgba(15, 23, 42, 0) 0%, rgba(15, 23, 42, .30) 46%, rgba(15, 23, 42, .62) 100%);
  pointer-events: none;
}

.mh-shelf .rp-card-tag {
  font-size: 9.5px; font-weight: 700; letter-spacing: .02em;
  padding: 3px 8px; border-radius: 999px;
  white-space: nowrap; line-height: 1.35;
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
  border: 1px solid rgba(255, 255, 255, .55);
  box-shadow: 0 1px 4px rgba(15, 23, 42, .28);
  /* The chips take the pointer back, only so a truncated one can show its
     `title`. The card's click is delegated off closest('[data-piece]'), so
     this costs nothing. */
  pointer-events: auto;
}

/* ⚠️ ONE LINE, ALWAYS — `nowrap` plus a shrink order, not a second row. Three
   chips do not fit across a 306px card at three columns, and a second row of
   chips eats a quarter of the picture. So they SHRINK instead, in reverse rank:
   shelfTags() sorts by `w`, which makes the LAST chip the least useful one, and
   it is the only one allowed to ellipsise. "Customers who have qu…" beside an
   intact "went out 7 times" is exactly the trade Tyler asked for.
   ⚠️ EXACTLY ONE CHIP MAY SHRINK. `flex-shrink:1` is the default, so leaving
   the middle chip on it let a 44px "Email" collapse to "Em…" next to an
   already-truncated audience — two damaged chips instead of one. */
   🚨 AND THE REAL CAP IS TWO, ENFORCED IN shelfTags() — NOT HERE. Three chips
   do not fit a 252px picture at all: "went out 7 times" + "Email" + an audience
   is ~263px of pill in 232px of shelf. Every CSS answer to that is a worse
   card. Letting the last one shrink puts it flush against the frame with no
   room to draw its ellipsis, so it reads as sliced rather than shortened.
   Capping its width instead pushes the FIRST TWO past the edge, where the
   header's overflow:hidden eats them — I shipped that for one round and it
   turned "WENT OUT 7 TIMES" into "WENT OUT 7 1". Wrapping costs a quarter of
   the picture. So the fix is to print two, and drop the one Tyler called the
   lowest. This rule stays only as the guard for a single very long chip. */
.mh-shelf .rp-card-tag { flex: 0 0 auto; }
.mh-shelf .rp-card-tag:last-child:not(:first-child) { flex: 0 1 auto; }

/* The existing scrim and the shelf's own gradient stack, and two darkenings
   of the same corner turn the bottom third of an email into mud. The shelf
   carries the contrast now, so the header's scrim only has to do depth. */
.mh-mat-card .tmc-header:has(.tmc-thumb-img)::after,
.mh-mat-card .tmc-header:has(.mh-cardshot)::after {
  background: linear-gradient(180deg, transparent 55%, rgba(0, 0, 0, .18) 100%);
}

/* ── EACH CARD ITS OWN OBJECT ────────────────────────────────────────────
   Tyler, same message: *"make each card feel like its own thing."* A 1px grey
   rectangle at 8px radius is a table cell. What separates an object from a
   cell is a soft ground shadow, a radius big enough to read as a corner, and
   a reaction of its own — so the artwork lifts under the pointer and the edge
   takes the piece's own state colour. */
.mh-mat-card,
.mh-tmb-card {
  border-radius: 14px;
  border-color: #eaeef3;
  box-shadow: 0 1px 2px rgba(15, 23, 42, .04), 0 10px 24px -18px rgba(15, 23, 42, .45);
  transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease;
}

.mh-mat-card:hover,
.mh-tmb-card:hover {
  transform: translateY(-4px);
  border-color: var(--rp-card-c, #cbd5e1);
  box-shadow: 0 2px 4px rgba(15, 23, 42, .05), 0 18px 34px -20px rgba(15, 23, 42, .55);
}

/* The picture, not the card, is what moves — a whole-card zoom would clip the
   caption. `overflow:hidden` on the card is what crops it. */
.mh-mat-card .tmc-header > .mh-mailshot,
.mh-mat-card .tmc-header > .tmc-thumb-img,
.mh-tmb-card .tmb-mosaic { transition: transform .35s ease; }

.mh-mat-card:hover .tmc-header > .mh-mailshot,
.mh-mat-card:hover .tmc-header > .tmc-thumb-img,
.mh-tmb-card:hover .tmb-mosaic { transform: scale(1.035); }

/* The caption. Tight against the square, and a fixed two-line title so a row
   of cards has one baseline instead of three. */
/* ⚠️ `gap:0`, because the sub line carries its own `margin-top` below — the
   ported body is a flex column and the bundle body is not, so a gap here makes
   the two captions 3px different and the row stops lining up. */
.mh-mat-card .tmc-body { padding: 11px 13px 13px; gap: 0; }

.mh-tmb-card .tmb-card-body { padding: 11px 13px 13px; }

.mh-mat-card .tmc-title,
.mh-tmb-card .tmb-card-title { font-size: 13px; line-height: 1.35; min-height: 2.7em; }

.mh-mat-card .tmc-sub,
.mh-tmb-card .tmb-card-meta { font-size: 11px; line-height: 1.4; color: var(--ink3, #94a3b8); margin-top: 3px; }

.mh-mat-card .tmb-mosaic,
.mh-tmb-card .tmb-mosaic { border-radius: 14px 14px 0 0; }

.mh-tmb-card { position: relative; }

.mh-tmb-card .tmb-card-desc {
  font-size: 11.5px; line-height: 1.45; color: var(--ink2, #525252);
  margin-top: 5px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

.mh-tmb-card .mh-tags { margin-top: 9px; display: flex; flex-wrap: wrap; gap: 4px; }

.mh-tmb-card .rp-card-stats { margin-top: 10px; }

/* A cell whose file has stored HTML shows the email itself. `.mh-mailshot`
   already pins and scales the sandbox; the cell just has to contain it. */
.tmb-mosaic-cell--shot { position: relative; }

.tmb-mosaic-cell--shot .mh-mailshot iframe { width: 400%; height: 400%; transform: scale(.25); }

/* ⚠️ THE PREVIEW RISES FROM THE BOTTOM EDGE in a list thumbnail too — same
   rule as `.mh-home-list .tmr-thumb`, so a mosaic row and a single-preview
   row sit at the same height. */
.mh-home-list .mc-list-thumb { border: 1px solid var(--border, #e5e7eb); }

/* The material card, home-side trims. `.mh-cardshot` is the stored HTML doing
   the job `.tmc-thumb-img` does for a picture: it covers the header and is
   read from its TOP edge, so the masthead is what you recognise. */
.mh-cardshot { z-index: 0; }

.mh-cardshot iframe { width: 250%; height: 250%; transform: scale(.4); transform-origin: 0 0; }

.mh-mat-card .mh-tags { margin-top: 8px; display: flex; flex-wrap: wrap; gap: 4px; }

.mh-mat-card .rp-card-stats { margin-top: 10px; }

/* ── "YOU ARE READING THIS, NOT EDITING IT" ─────────────────────
   Tyler, 2026-08-10: *"inside the piece pop-up where you're reviewing it, it
   should also say at the top: 'The piece that you selected is not going to let
   you edit it unless you're in edit mode.'"*

   A full-width strip directly under the tabs, so it reads as a property of the
   SCREEN rather than of the record in the left pane.

   ⚠️ IT IS DELIBERATELY NOT AMBER. `.mh-src-down` is the colour of "something is
   wrong and you should look at it"; this is the normal, correct state of a
   review screen. Amber here would make every piece look broken. Slate for the
   ordinary case, and only the admin-shut variant leans warm — that one IS a
   thing you cannot do.
   ⚠️ `flex:0 0 auto` for the same reason .wk-rr-tabs has it: the modal is a
   flex column and an auto-height strip would otherwise be squeezed by the
   scrolling body below it. */
/* ⚠️ `align-items:center` NOW THAT IT IS ONE LINE. It was flex-start because the
   sentence ran to two lines and a padlock centred against a two-line paragraph
   floats in the middle of it. One line, so centred is the correct alignment and
   flex-start would sit the icon a pixel high. */
.mh-rr-ro {
  display: flex; gap: 8px; align-items: center; flex: 0 0 auto;
  padding: 7px 24px 8px; border-bottom: 1px solid #eef2f7;
  background: #f8fafc; font-size: 11.5px; line-height: 1.45; color: #475569;
}

.mh-rr-ro svg { width: 13px; height: 13px; flex-shrink: 0; color: #94a3b8; }

.mh-rr-ro b { font-weight: 800; color: #334155; }

.mh-rr-ro--shut { background: #fffbeb; border-bottom-color: #fde68a; color: #78350f; }

.mh-rr-ro--shut svg { color: #d97706; }

.mh-rr-ro--shut b { color: #78350f; }

@media (prefers-color-scheme: dark) {
.mh-rr-ro { background: #131a26; border-bottom-color: #1f2937; color: #94a3b8; }

.mh-rr-ro b { color: #cbd5e1; }

.mh-rr-ro--shut { background: #2a1f07; border-bottom-color: #78500a; color: #fde68a; }

.mh-rr-ro--shut b { color: #fde68a; }
}

/* ══ THE ⋯ ON A CARD ════════════════════════════════════════════════════════
   Tyler, 2026-08-12: *"the three dots inside the piece card — when you hover
   over the piece you should be able to click those three dots as well."*

   ⚠️ IT IS IN THE MARKUP AT REST AND HIDDEN BY OPACITY, never injected on
   mouseenter. `visibility`/`display` would take it out of the tab order, and a
   control you cannot Tab to is one a keyboard user has no route to at all — so
   `:focus-within` on the card and `:focus-visible` on the button both reveal it.

   🚨 TOP-LEFT. `.tmc-badge-row` / `.tmb-card-status` are pinned top-RIGHT and
   carry the stage, which is what the card exists to say at rest. A kebab fading
   in over that square would cover the status at exactly the moment somebody is
   reading the card.

   ⚠️ `pointer-events: none` WHILE INVISIBLE. Without it the button still eats
   clicks in its corner — and the card's own click is delegated off
   `closest('[data-piece]')`, so the piece would silently stop opening from that
   8px square with nothing on screen to explain it. */
.mh-cardmenu {
  position: absolute; top: 6px; left: 6px; z-index: 4;
  opacity: 0; pointer-events: none;
  width: 26px; height: 26px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 7px;
  background: rgba(15, 23, 42, .62); color: #fff;
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
  cursor: pointer;
  transition: opacity .14s ease, background .14s ease;
}

.mh-cardmenu svg { width: 15px; height: 15px; }

.mh-mat-card:hover  > .tmc-header > .mh-cardmenu,
.mh-tmb-card:hover  > .tmb-card-header > .mh-cardmenu,
.mh-mat-card:focus-within > .tmc-header > .mh-cardmenu,
.mh-tmb-card:focus-within > .tmb-card-header > .mh-cardmenu,
.mh-cardmenu:focus-visible,
.mh-cardmenu.is-open {
  opacity: 1; pointer-events: auto;
}

/* 🚨 THREE CLASSES, AND THAT IS NOT PADDING. The component's base rules are
   wrapped in `:where()` so a consumer can restyle them — but
   `.wk-rmenu-btn.is-open` is NOT, and its <style> is injected into <head> at
   script load, i.e. AFTER this linked sheet. At equal specificity the later rule
   wins, so a two-class selector here would lose and an OPEN kebab would flip to
   the component's light grey, over the artwork, with a dark glyph on it. */
.mh-cardmenu:hover,
.mh-cardmenu.wk-rmenu-btn.is-open { background: rgba(15, 23, 42, .88); color: #fff; }

.mh-cardmenu:focus-visible { outline: 2px solid #6366f1; outline-offset: 2px; }

@media (hover: none) {
.mh-cardmenu { opacity: 1; pointer-events: auto; }
}

@media (prefers-reduced-motion: reduce) {
.mh-cardmenu { transition: none; }
}

/* ══ THE SEND QUEUE ROW ══════════════════════════════════════════════════════
   Tyler, 2026-08-12: *"when I schedule it, it should be scheduled, shows up in
   the history of scheduled … shows who it's scheduled to."*

   The row is a sentence read left to right: what state it is in (the dot), what
   it is and who it goes to, the faces, then WHEN — with every verb behind the ⋯
   on the right, so the row stays readable instead of becoming a toolbar. */
.mh-q-row { align-items: center; gap: 10px; position: relative; padding-right: 42px; }

.mh-q-main { flex: 1; min-width: 0; }

.mh-q-waves {
  margin-top: 3px; font-size: 11px; font-weight: 700; letter-spacing: .01em;
  color: #4338ca;
}

/* A hold always carries a reason — it is the one state on this screen that is
   waiting on a person, and "held" without "why" makes that person unfindable. */
.mh-q-why {
  margin-top: 3px; font-size: 11.5px; line-height: 1.4; color: #92400e;
}

.mh-q-right { align-items: flex-end; text-align: right; }

.mh-q-when {
  font-size: 11.5px; font-weight: 700; color: #334155; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* ⚠️ FACES, NOT A COUNT. Tyler on the same list of waiting sends: *"I need to
   see profile pictures."* Overlapped so four of them read as one audience. */
.mh-q-faces { display: flex; align-items: center; flex: 0 0 auto; }

.mh-q-face {
  position: relative; width: 26px; height: 26px; border-radius: 50%;
  overflow: hidden; flex: 0 0 auto;
  border: 2px solid #fff; background: #e2e8f0;
  display: flex; align-items: center; justify-content: center;
}

.mh-q-face + .mh-q-face { margin-left: -9px; }

.mh-q-face img { width: 100%; height: 100%; object-fit: cover; }

.mh-q-face i {
  font-style: normal; font-size: 9.5px; font-weight: 800; color: #475569;
  letter-spacing: .02em;
}

.mh-q-face--n { background: #cbd5e1; }

/* The ⋯ sits at the right edge of the row and is always present — unlike the
   card's, this list is the queue itself, so there is no artwork to keep clear
   and nothing gained by hiding the only way to cancel a send. */
.mh-qmenu {
  position: absolute; top: 50%; right: 6px; transform: translateY(-50%);
  width: 28px; height: 28px; flex: 0 0 auto;
}

@media (prefers-color-scheme: dark) {
.mh-q-when { color: #cbd5e1; }

.mh-q-waves { color: #a5b4fc; }

.mh-q-why { color: #fcd34d; }

.mh-q-face { border-color: #0f172a; background: #1e293b; }

.mh-q-face i { color: #94a3b8; }

.mh-q-face--n { background: #334155; }
}
