/* Chain Drain — homepage
   Standalone reproduction of the Figma homepage (node 17:3).
   Parallel to the legacy site/ build; nothing here touches build_dossiers.py.
   Rough-in stage: no animation, content/layout/behavior only. Refine later. */

* { box-sizing: border-box; }

html, body {
  margin: 0;
  color: var(--color-ink);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Kill the rubber-band overscroll so you can't pull past the fixed mesh canvas
     into the bare page colour at the top/bottom. (Honoured by Chrome/Firefox/
     Android + iOS 16.4+; the paper background below covers the rest.) */
  overscroll-behavior: none;
}
/* Fallback tone on <html> only — NOT <body>, or it would paint over the
   z-index:-1 mesh canvas that lives inside <body>. Paper (not the grey
   --color-bg) so the pre-paint flash on load, and any overscroll gap the browser
   still allows, read as the mesh's own near-white base instead of a stark grey. */
html { background: var(--color-paper); }

/* Static mesh-gradient backdrop (js/mesh-bg.js), fixed behind all content. */
#mesh-bg {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  z-index: -1;
  display: block;
  pointer-events: none;
}


/* 5-column grid: 20px margins, 20px gutters (Figma layout grid). */
.home {
  display: grid;
  grid-template-columns: repeat(var(--grid-cols), 1fr);
  column-gap: var(--gutter);
  align-content: start;
  min-height: 100vh;
  padding: var(--edge-top) var(--margin) 96px;
  /* Stacking context so the header's z-index:-1 water canvas sits behind the
     text but above the page background. */
  position: relative;
  z-index: 0;
}

.home__title {
  grid-column: 1 / 4;            /* columns 1–3 */
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-body);
  line-height: 1;
  letter-spacing: -0.01em;
  font-variation-settings: "XROT" 0, "YROT" 0;
}
/* "Last Updated on …" — subheading directly under the title, same ink colour. */
.home__updated {
  grid-column: 1 / 4;           /* aligns under the title */
  margin: 6px 0 0;
  font-size: 14px;
  line-height: 1.2;
  color: var(--color-ink);
}

/* Liquid "drain" title effect (js/header-fx.js). The maroon letters live on a
   canvas in the title cell; the striped drips live on a separate tall water
   canvas (a .home child, below) so they can drain the length of the intro. */
.home__title-fx {
  display: block;
  position: relative;
  width: 100%;
}
.home__title-fx .cd-header__title {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 2;                 /* letters above the drips */
  pointer-events: none;
}
/* The tall water/drip canvas: positioned to the title cell's top-left, sized by
   JS, sitting at z-index -1 inside .home so it's behind the essay text but above
   the page background. */
.cd-header__water {
  position: absolute;
  top: var(--edge-top);
  left: var(--margin);
  z-index: -1;
  pointer-events: none;
}

/* Visually-hidden but accessible (real <h1> text for SR / SEO). */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0);
  white-space: nowrap; border: 0;
}

.home__lede {
  grid-column: 1 / 4;            /* Figma body box = columns 1–3 (875px) */
  margin: var(--gap-title-body) 0 0;
  font-size: var(--fs-body);
  line-height: 1.25;
  /* Hidden until the DB count is injected (revealed by an inline script once
     CHART_DATA is available) so the text never flashes the uncounted version. */
  visibility: hidden;
}
html.cd-counted .home__lede { visibility: visible; }
.home__lede p { margin: 0 0 1em; }
.home__lede p:last-child { margin-bottom: 0; }

/* Inline links in the lede (e.g. the whitepaper link) — ink color, italic,
   underlined. */
.home__lede a {
  color: var(--color-ink);
  font-style: italic;
  text-decoration: underline;
}

/* Hoverable drawer triggers — Inter Black, thick underline.
   "107 products and companies", "Disconnection", "discoordination" all share
   this treatment. Each opens its drawer on hover / focus / tap. */
.home__count,
.term {
  font-weight: 900;
  text-decoration: underline;
  text-decoration-thickness: from-font;
  text-underline-position: from-font;
  cursor: pointer;
}
/* The DB count is hover-only (no click) on desktop, so it shouldn't advertise a
   click with the hand cursor — keep the normal arrow. */
.home__count { cursor: default; }
/* In any narrow layout (touch OR a narrow desktop window ≤768px) there's no
   hover, so a CLICK/tap opens the companies marquee sheet — the count must read
   as tappable. The coarse case is also load-bearing on iOS Safari, which won't
   synthesize a click on a plain <span> unless it looks clickable (cursor:pointer);
   without it the tap never reaches the open handler. */
@media (hover: none), (pointer: coarse), (max-width: 768px) {
  .home__count { cursor: pointer; }
}
/* Fixed-width digit slot for the counting number: reserves the final target's
   width (via a JS-set min-width in ch) with tabular figures, so the count-up
   can't widen the span and shift the paragraph's line break. */
.home__count-num {
  display: inline-block;
  text-align: right;
  font-variant-numeric: tabular-nums;
  /* inline-block breaks the parent's underline, so re-apply it (matches .home__count). */
  text-decoration: underline;
  text-decoration-thickness: from-font;
  text-underline-position: from-font;
}
.home__count:focus-visible,
.term:focus-visible {
  outline: 2px solid var(--color-ink);
  outline-offset: 3px;
}

/* Companies overlay (Figma node 17:11) — column 5, appears on hover.
   Faithful to Figma: transparent panel, white Inter 24px, flush to the
   column's left edge. No animation (rough-in). */
/* Drawers are <dialog>: non-modal (open attribute) on desktop = hovercard;
   modal (showModal) on touch = bottom sheet (see the (hover: none) block). */
.drawer {
  display: none;
  position: fixed;
  top: var(--edge-top);         /* fallback — JS aligns this to the hovered term's top */
  bottom: 40px;
  left: auto;
  right: var(--margin);         /* right edge sits at the outer margin */
  width: var(--col);            /* companies list = one grid column */
  padding: 0 0 24px;            /* no top padding: text starts at the drawer top */
  overflow-y: auto;
  color: var(--color-ink);
  font-size: 24px;
  line-height: 1.35;
  margin: 0;                    /* reset <dialog> UA styles */
  border: 0;
  background: transparent;
  max-width: none;
  max-height: none;
  height: auto;                 /* override UA height:fit-content so top+bottom stretch it */
  /* Fade OUT on close instead of vanishing. display + overlay use discrete
     transitions (allow-discrete) so the box — and a modal sheet's top-layer
     membership — persist through the opacity fade. Opening stays instant: the
     [open] rule below zeroes the transition, so only the return to this base
     state (closing) animates. */
  opacity: 0;
  transition: opacity 0.18s ease,
              display 0.18s allow-discrete,
              overlay 0.18s allow-discrete;
}
.drawer[open] { display: block; opacity: 1; transition: none; }
.drawer::backdrop {
  background: rgba(20, 20, 20, 0.55);   /* only rendered when modal (touch) */
  opacity: 0;
  transition: opacity 0.18s ease,
              display 0.18s allow-discrete,
              overlay 0.18s allow-discrete;
}
.drawer[open]::backdrop { opacity: 1; transition: none; }
@media (prefers-reduced-motion: reduce) {
  .drawer, .drawer[open], .drawer::backdrop, .drawer[open]::backdrop { transition: none; }
}
/* Close button + commit action — injected by home.js; visible only on touch. */
.drawer__close, .drawer__action { display: none; }
/* Swipe pager (chart drawer). Passthrough on desktop — must NOT create a
   containing block, or the chart drawer's absolutely-positioned content would
   anchor to it (0-height) instead of the dialog. Touch adds transform/perf. */
.drawer__pager { display: contents; }

/* Thesis / archetype drawers span two columns (cols 4–5) and center their
   content vertically in the viewport (the chart drawer has its own centering). */
.drawer--2col {
  width: calc(2 * var(--col) + var(--gutter));
}
.drawer--2col:not(.drawer--center) {
  top: 0;
  bottom: 0;
  /* Keep the flex centering on the always-applied selector (it's inert until the
     box is display:flex) so it SURVIVES the close: display lingers as flex through
     the 0.18s fade (allow-discrete), and if justify-content were scoped to [open]
     the content would snap to the top of the window for that fade. */
  flex-direction: column;
  justify-content: center;
}
.drawer--2col:not(.drawer--center)[open] {
  display: flex;
}

/* Chart drawer — full height, content centered vertically in the viewport
   (rather than aligned to the hovered bar). */
.drawer--center {
  top: 0;
  bottom: 0;
  padding-top: 0;
  overflow: visible;       /* let large aggregate bubbles spill past the plot */
}

/* The matrix sits at a fixed vertical position (viewport middle); the text
   block grows upward from just above it, so the matrix never shifts as the
   company name / description length changes. */
.drawer--center[open] { display: block; }
.drawer--center .drawer__text {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(50% + 20px);
}
.drawer--center .mmx {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  margin-top: 0;                /* override default; position is absolute now */
}

/* Companies drawer: full viewport height, clipped, with an endless slow
   vertical scroll. home.js repeats the list until one copy is at least a
   viewport tall, then translates the track up at a constant px/s, wrapping
   modulo one copy's height so it loops with no visible start or end. */
.drawer--marquee {
  top: 0;
  bottom: 0;
  height: 100vh;
  width: calc(2 * var(--col) + var(--gutter)); /* columns 4–5 */
  padding: 0;
  overflow: hidden;
}
.marquee {
  display: flex;
  flex-direction: column;
  will-change: transform;   /* transform driven per-frame by home.js */
}
.marquee .drawer__list { flex: none; }
/* Dossier filter: hide un-edited names in EVERY loop copy so one copy's height
   (the wrap unit) stays correct and the scroll stays seamless. */
.drawer--marquee.is-edited-only .drawer__list > li:not([data-edited]) { display: none; }
@media (prefers-reduced-motion: reduce) {
  .drawer--marquee { overflow-y: auto; } /* fall back to a normal scroll list */
}
/* Touch has no wheel to drive the marquee and the rAF skips animating on coarse
   pointers, so let the panel scroll natively instead of clipping the list. Covers
   both the phone bottom-sheet and the tablet full-height side panel. */
@media (hover: none), (pointer: coarse) {
  .drawer--marquee { overflow-y: auto; }
}

.drawer__list { margin: 0; padding: 0; list-style: none; }
.drawer__list li { margin: 0 0 0.15em; }
.drawer__list a { color: inherit; text-decoration: none; }
.drawer__list a:hover, .drawer__list a:focus-visible { text-decoration: underline; }

/* "All Companies" — an A–Z list shown on every viewport (desktop keeps the
   companies drawer too; this is the always-visible roster). Column count is
   responsive via a column *width*, so it fills more columns on wider screens
   and collapses toward two/one on narrow ones. */
.all-companies { display: block; }
.all-companies__list {
  list-style: none;
  margin: 0;
  padding: 0;
  columns: 160px;
  column-gap: 20px;
  font-size: var(--fs-chart-label);   /* small, dense roster */
  line-height: 1.3;
}
.all-companies__list li { break-inside: avoid; margin: 0 0 3px; }
.all-companies__list a { color: var(--color-ink); text-decoration: none; }
.all-companies__list a:hover,
.all-companies__list a:focus-visible { text-decoration: underline; }
@media (hover: none), (pointer: coarse) {
  .all-companies__list { columns: 2; }   /* fixed two-up on touch */
}

/* Thesis drawers — headline + short body */
.drawer__title {
  margin: 0 0 0.5em;
  font-weight: 900;
  font-size: 24px;
  line-height: 1.1;
}
.drawer__body {
  margin: 0;
  font-size: 18px;
  line-height: 1.4;
}
/* Bold lead-in label on a drawer body (e.g. "Phase Rationale:" on the phases
   chart's per-company hover copy). */
.drawer__body-label { font-weight: 900; }
/* Big-matrix drawer: level label matches the axis-drawer's LOW/MED/HIGH rubric
   size, and stays on one line so the layout below never reflows (constant
   body-text top → smoother fade). */
#matrix-cell-drawer .drawer__title {
  white-space: nowrap;
  font-size: 15px;
  letter-spacing: 0.04em;
}
/* Level tokens cross-fade like the rest; a hidden "HIGH" sizer holds each slot
   at a fixed width so DIGITAL / CHAIN never shift as the words swap. */
.mxi-lvl { position: relative; display: inline-block; text-align: center; }
.mxi-lvl-sizer { visibility: hidden; }
.mxi-lvl-word {
  position: absolute;
  left: 0; right: 0; top: 0;
  opacity: var(--w, 0);
  filter: blur(calc((1 - var(--w, 0)) * 7px));
  transition: opacity 0.12s ease, filter 0.12s ease;
}
/* Thin divider between the level label and the transport name below it. */
.mxi-drawer__rule {
  height: 0;
  margin: 0.15em 0 0.5em;
  border: 0;
  border-top: 1px solid var(--color-ink);
}
/* Transport name: a stacked fade/blur stage like the body, so the quadrant name
   blends the same way. One line, always present → fixed height. */
.mxi-drawer__name {
  grid-area: 1 / 1;
  margin: 0;
  white-space: nowrap;
  font-weight: 900;
  font-size: 24px;
  line-height: 1.1;
  color: var(--color-ink);
  opacity: var(--w, 0);
  filter: blur(calc((1 - var(--w, 0)) * 7px));
  transition: opacity 0.12s ease, filter 0.12s ease;
  pointer-events: none;
}
/* Blend stage: the four corner descriptions stacked in place. JS gives each a
   proximity weight --w (0–1); nearer the corner = more opaque and sharper, so
   the "between" space reads as an intentional fade-and-blur. */
/* Let the blurred text's halo spill past the drawer's left edge instead of being
   clipped (base .drawer sets overflow-y:auto, which clips overflow-x too).
   `top` is set by JS to vertically centre the drawer on the matrix. */
#matrix-cell-drawer { overflow: visible; bottom: auto; }
.mxi-drawer__stage {
  display: grid;               /* stack all four panels in one cell (overlapping) */
}
.mxi-drawer__body { margin-top: 0.6em; }
.mxi-drawer__panel {
  grid-area: 1 / 1;
  margin: 0;
  font-size: 18px;
  line-height: 1.4;
  color: var(--color-ink);
  opacity: var(--w, 0);
  filter: blur(calc((1 - var(--w, 0)) * 7px));
  transition: opacity 0.12s ease, filter 0.12s ease;
  pointer-events: none;
}
.drawer__rubric {
  margin: 1em 0 0;
  padding: 0;
}
.drawer__rubric-row {
  display: grid;
  /* auto: the label column hugs the widest label (HIGH) instead of a fixed em
     width that resolves against the 24px drawer font, not the 12px label text. */
  grid-template-columns: auto 1fr;
  gap: 0 0.5em;
  padding: 0.55em 0;
  border-top: 1px solid color-mix(in srgb, var(--color-ink) 25%, transparent);
}
.drawer__rubric-row dt {
  font-weight: 900;
  font-size: var(--fs-chart-label);   /* 12px */
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: 1.4;
}
.drawer__rubric-row dd {
  margin: 0;
  font-size: var(--fs-chart-label);   /* 12px */
  line-height: 1.4;
}

/* ---- Narrow layout: every drawer becomes a modal bottom sheet ----
   The hover drawers live in the grid's right-hand columns; once the layout
   collapses to a single column (≤768px) those columns are gone, so there's
   nowhere to float a hovercard and every drawer is opened with showModal()
   instead (::backdrop, focus trap, Escape) and re-laid out as a bottom sheet with
   an explicit close + (for companies) a commit action. Pure width test — NOT
   touch — matching home.js's wide() predicate: wide touch tablets keep the
   hovercard (opened by tap), a narrow desktop window gets the sheet. */
@media (max-width: 768px) {
  .drawer,
  .drawer--2col,
  .drawer--2col:not(.drawer--center),   /* (0,2,0): must out-specify the desktop top:0 rule to peg the sheet to the bottom */
  .drawer--center,
  .drawer--marquee {
    top: auto;
    bottom: 0;
    left: 0;
    right: 0;
    width: 100%;
    height: 50vh;                /* fixed sheet height on mobile */
    max-height: 50vh;
    box-sizing: border-box;
    padding: 44px 20px calc(24px + env(safe-area-inset-bottom, 0px));
    overflow-y: auto;
    background: var(--color-paper);      /* opaque sheet, same tone as the footer bar */
    color: var(--color-ink);
    border-radius: 16px 16px 0 0;
    box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.25);
  }
  /* Desktop centers 2-col drawer content vertically (justify-content:center); in a
     fixed-height sheet that overflows, centering pushes the top of the content past
     the scroll origin where it can't be reached, clipping it. Top-align so content
     starts at the sheet top and the whole thing scrolls. Not gated on [open] — so it
     survives the close fade (same reason as the desktop rule above). */
  .drawer--2col:not(.drawer--center) { justify-content: flex-start; }
  /* The chart drawer's matrix is absolutely-positioned on desktop; in a sheet it
     flows normally under the text. */
  .drawer--center[open] { display: block; }
  .drawer--center .drawer__text,
  .drawer--center .mmx {
    position: static;
    left: auto; right: auto; top: auto; bottom: auto;
    margin: 0;
  }
  .drawer--center .mmx { margin-top: 24px; }
  /* The pager translates horizontally during the swipe; clip its overflow and
     let vertical pans scroll the sheet while horizontal pans go to the drag JS.
     display:block (overriding the desktop display:contents) so it's a real,
     translatable box; position:relative anchors the ghost peek panels to it. */
  .drawer--center { overflow-x: hidden; }
  .drawer__pager {
    display: block;
    position: relative;
    touch-action: pan-y;
    will-change: transform;
  }
  /* Ghost peek panels — the prev/next cards shown sliding in during a drag. */
  .drawer__ghost {
    position: absolute;
    top: 0; left: 0;
    width: 100%;
  }
  .drawer--marquee { overflow-y: auto; }
  .drawer--marquee .marquee { animation: none; }
  .drawer--marquee .drawer__list[aria-hidden="true"] { display: none; }  /* hide the loop clone */

  .drawer__close {
    display: block;
    /* STICKY, not absolute. The sheet is the scroll container, and an
       absolutely-positioned button scrolls away with its content: after a short
       scroll of a long drawer the X is off-screen and a link sits exactly where
       it looked like it was, so "tapping the X" navigates instead of closing.
       Sticky keeps it pinned to the top of the sheet however far you scroll.
       `margin-left: auto` + a negative bottom margin hold it at the top-right
       without reserving a row of its own. */
    position: sticky;
    z-index: 3;
    top: 10px;
    float: right;              /* block-flow sheets */
    align-self: flex-end;      /* the .is-names sheet is a flex column, where
                                  float is ignored */
    /* `margin-right`, not `right`: on a sticky box `right` is a stick
       constraint, not an offset. The negative bottom margin keeps the button
       from reserving a row of its own. */
    margin: 0 14px -44px auto;
    width: 34px; height: 34px;
    padding: 0;
    font-size: 22px; line-height: 34px;
    color: var(--color-ink); background: rgba(0, 0, 0, 0.08);
    border: 0; border-radius: 50%;
    cursor: pointer;
  }
  .drawer__action {
    margin-top: 22px;
    padding: 12px 18px;
    font-size: 18px; font-weight: 900;
    color: #fff; background: var(--chart-fill);
    border-radius: 8px;
    text-decoration: none;
  }
  .drawer__action.is-on { display: inline-block; }   /* shown only when a company is previewed */
}

/* Archetype 2×2 matrix (e.g. the Play drawer). A square split into 4 cells by a
   1px crosshair; a cell can carry a 1px-line arrow pointing to its outer corner.
   Axis labels flank it (rotated left + bottom), like the dossier matrix. */
.arch-mx {
  /* Hidden: the big essay 2×2 is now the interactive matrix, so the drawer that
     opens on hover shows just the title + description (no mini 2×2). */
  display: none;
  margin-top: 32px;
  grid-template-columns: auto auto;
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: 8px;
  row-gap: 5px;
  width: max-content;
  color: var(--color-ink);
}
.arch-mx__ylab {
  grid-column: 1; grid-row: 1;
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  text-align: center;
  font-size: 15px;
}
.arch-mx__xlab {
  grid-column: 2; grid-row: 2;
  text-align: center;
  font-size: 15px;
}
.arch-matrix {
  grid-column: 2; grid-row: 1;
  width: 208px;
  height: 208px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  border: 1px solid var(--chart-outline);
}
.arch-cell { position: relative; }
.arch-cell:nth-child(odd) { border-right: 1px solid var(--chart-outline); }   /* vertical divider */
.arch-cell:nth-child(1),
.arch-cell:nth-child(2) { border-bottom: 1px solid var(--chart-outline); }    /* horizontal divider */
.arch-arrow {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  color: var(--color-ink);
  overflow: visible;
}

/* Per-company Chain-Drain mini-matrix (in the chart drawer). A crosshair
   square with a glow marking the company's digital × supply-chain cell; the
   glow glides between companies as the shared drawer is reused. */
.mmx { margin-top: 32px; }
.mmx[hidden] { display: none; }
/* Caption for the aggregate/distribution view only (per-cell company counts on a
   category-label hover). Hidden in the single-company placement view. */
.mmx-title {
  display: none;
  margin: 0 0 12px;
  font-weight: 900;
  font-size: 15px;
  line-height: 1.2;
  color: var(--mmx-line);
}
.mmx.is-aggregate .mmx-title { display: block; }
/* Axis labels flank the plot: "Digital" runs up the left (rotated), and
   "Supply-chain" sits under the bottom edge. */
.mmx-grid {
  display: grid;
  grid-template-columns: auto auto;
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: 8px;
  row-gap: 5px;
  width: max-content;
  color: var(--mmx-line);   /* labels share the graph's deep blue */
}
.mmx-ylab {
  grid-column: 1; grid-row: 1;
  align-self: stretch;         /* fill the plot's height so text-align can slide
                                  the label to the company's row (top/center/bottom) */
  writing-mode: vertical-rl;
  transform: rotate(180deg);   /* reads bottom→top, low→high */
  text-align: center;
  font-size: 15px;
}
.mmx-xlab {
  grid-column: 2; grid-row: 2;
  text-align: center;
  font-size: 15px;
}
/* Fixed-width slot for the level word (High/Med/Low) so the label holds still.
   `inline-size` maps to width on the x-label and to height on the rotated
   y-label, reserving the widest ("High") in both orientations. */
.mmx-lvl {
  display: inline-block;
  inline-size: 2.7em;
  text-align: start;
  white-space: nowrap;
}
.mmx-plot {
  grid-column: 2; grid-row: 1;
  position: relative;
  width: 208px;
  height: 208px;
  border: 1px dashed var(--mmx-line);   /* dashed outer frame */
  overflow: visible;   /* let the largest aggregate bubbles spill past the box */
}
/* Aggregate bubbles (one per populated cell) when hovering a value label. */
.mmx-cells { position: absolute; inset: 0; pointer-events: none; }
/* Weighted centre of the aggregate bubbles — the element is created and
   positioned by js/mini-matrix.js; every visual property is a --mmx-centroid-*
   token in css/tokens.css, so symbol, size, colour and opacity change in one
   place for every page. It sits UNDER the bubbles (first child of .mmx-cells)
   and slides between them on the glow ring's curve. */
.mmx-centroid {
  position: absolute;
  width: var(--mmx-centroid-size);
  height: var(--mmx-centroid-size);
  transform: translate(-50%, -50%);
  background-color: var(--mmx-centroid-color);
  -webkit-mask: var(--mmx-centroid-symbol) center / contain no-repeat;
  mask: var(--mmx-centroid-symbol) center / contain no-repeat;
  opacity: 0;
  pointer-events: none;
  transition: left 0.34s cubic-bezier(0.34, 1.56, 0.64, 1),
              top 0.34s cubic-bezier(0.34, 1.56, 0.64, 1),
              opacity 0.2s ease;
}
.mmx-centroid.is-on { opacity: var(--mmx-centroid-opacity); }
@media (prefers-reduced-motion: reduce) {
  .mmx-centroid { transition: opacity 0.2s ease; }
}

.mmx-bubble {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  border: 1px solid var(--mmx-line);   /* hollow, no fill */
  color: var(--mmx-line);
  font-size: 12px;
  font-weight: 900;
  line-height: 1;
  /* Scale between label hovers (JS clears the transition on fresh mounts). */
  transition: width 0.36s cubic-bezier(0.34, 1.2, 0.64, 1),
              height 0.36s cubic-bezier(0.34, 1.2, 0.64, 1),
              opacity 0.3s ease;
}
.mmx-cross { position: absolute; inset: 0; }
.mmx-cross::before,
.mmx-cross::after { content: ""; position: absolute; background: var(--mmx-line); }
.mmx-cross::before { left: 50%; top: 0; bottom: 0; width: 1px; transform: translateX(-50%); }
.mmx-cross::after { top: 50%; left: 0; right: 0; height: 1px; transform: translateY(-50%); }
/* Open-chevron arrowheads on the crosshair ends — same style as the MXI
   interactive matrix (currentColor, thin non-scaling stroke, tip at the edge). */
.mmx-axis-arrow {
  position: absolute;
  width: 20px;
  height: 20px;
  color: var(--mmx-line);
  pointer-events: none;
  overflow: visible;
}
.mmx-axis-arrow--up    { top: -2px;    left: 50%; transform: translateX(-50%); }
.mmx-axis-arrow--right { right: -2px;  top: 50%;  transform: translateY(-50%); }
.mmx-axis-arrow--down  { bottom: -2px; left: 50%; transform: translateX(-50%); }
.mmx-axis-arrow--left  { left: -2px;   top: 50%;  transform: translateY(-50%); }
/* Label (aggregate) hovers show bubbles, not a single cell — drop the arrows
   and the centerlines so the hollow bubbles don't collide with the crosshair. */
.mmx.is-aggregate .mmx-axis-arrow,
.mmx.is-aggregate .mmx-cross { display: none; }
.mmx-glow {
  position: absolute;
  left: 50%; top: 50%;
  width: 54px; height: 54px;
  border-radius: 50%;
  border: 1.5px solid var(--mmx-line);   /* hollow ring marking the cell */
  transform: translate(-50%, -50%);
  pointer-events: none;
  transition: left 0.34s cubic-bezier(0.34, 1.56, 0.64, 1),
              top 0.34s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Taubman logo — pinned to the window (viewport), bottom-right, so it stays
   put as the page scrolls (Figma: 250×25, 36px right / 40px bottom). */
.home__logo {
  position: fixed;
  right: 36px;
  bottom: 40px;
  width: 250px;
  height: 25px;
  object-fit: contain;
  pointer-events: none;
  z-index: 30;            /* stay above the chart-drawer panel + content */
}

/* Responsive text-swap utilities. Put the desktop copy in .hide-on-mobile and
   the mobile copy in .show-on-mobile; they cross over at the 768px breakpoint —
   the same width where the MXI collapses and taps replace hover. Inline so a
   word/phrase can swap mid-sentence (e.g. "Hover over" ⇄ "Click on"). */
.show-on-mobile { display: none; }

/* Narrow screens: collapse the grid, drawer becomes a wider panel. */
@media (max-width: 768px) {
  .home {
    /* minmax(0, 1fr) caps the track at the viewport — a plain 1fr grows to the
       widest item's min-content, which was dragging the whole column (and the
       intro text) out past the screen edge. */
    grid-template-columns: minmax(0, 1fr);
    padding-bottom: 72px;
  }
  .home__title,
  .home__lede { grid-column: 1; min-width: 0; }
  /* Text-swap crossover: desktop copy hides, mobile copy shows. Below 768px the
     MXI goes full-width and touch replaces hover, so hover-specific copy (e.g.
     "move your cursor over the 2×2") gives way to the tap-oriented version. */
  .hide-on-mobile { display: none; }
  .show-on-mobile { display: inline; }

  .home__logo {
    position: static;
    display: block;
    margin-top: 48px;
    width: 200px;
    height: auto;
  }
}
