/* Avakode Hub: the board of the home page (lane 1.0a «Плата», plan §5, owner Q38). Home page only (inc/board.php).
   Prototype: forge/plans/hub-v2-mockups/trace-home.src.html. Markup: front-page.php, template-parts/family-zone.php,
   product-card.php, plugin-card.php, suite-card.php, card-vis/*.php; motion: assets/js/board.js.

   The default of every rule is the FINAL frame (every card powered, every scene drawn). The «unpowered» look is one block
   at the end, applied only while scripts run with motion allowed (html.hub-js, not .hub-static, reduced motion off): no JS,
   ?static and reduced motion all get the final frame with no extra rule.
   Only transform, opacity and stroke-dashoffset move. Colour changes of the prototype (card border and fill, pads, flagged
   lines, pins, silk) are lit layers faded by opacity; the prototype's box-shadow glow of a pad is a pseudo-element. */

body.hub-page {
  --hub-bg-3: #15121F;                    /* a powered card */
  --hub-trace: rgba(169,163,196,.16);     /* an unpowered trace */
  --hub-ok: #9FE8C3;                      /* «live», «ready to send» */
}

/* the plate: a faint dot grid under everything (behind the content, above the page colour) */
body.hub-page::before {
  content: ''; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background-image: radial-gradient(rgba(169,163,196,.10) .6px, transparent .7px); background-size: 28px 28px; opacity: .55;
}
html.hub-board-riding { scroll-behavior: auto !important; } /* Power all drives the scroll itself */

/* ---- bus, branches, pulse (svg.hub-trace is drawn by board.js) ---- */
.hub-trace { position: absolute; left: 0; top: 0; width: 100%; overflow: visible; pointer-events: none; z-index: 0; }
.hub-trace__bus { fill: none; stroke: var(--hub-trace); stroke-width: 2; }
.hub-trace__glow { fill: none; stroke: rgba(124,92,252,.22); stroke-width: 7; stroke-linecap: round; }
.hub-trace__lit { fill: none; stroke: var(--signal); stroke-width: 2; stroke-linecap: round; }
.hub-trace__br { fill: none; stroke: var(--hub-trace); stroke-width: 2; }
.hub-trace__br-lit {
  fill: none; stroke: var(--signal); stroke-width: 2; stroke-dasharray: 1; stroke-dashoffset: 1;
  transition: stroke-dashoffset .55s var(--ease); transition-delay: calc(var(--d, 0) * 1ms);
}
.hub-trace__br-lit.is-on { stroke-dashoffset: 0; }

.hub-pulse {
  position: absolute; left: 0; top: 0; z-index: 2; width: 12px; height: 12px; margin: -6px 0 0 -6px;
  border-radius: 50%; background: #fff; pointer-events: none; opacity: 0; transition: opacity .6s var(--ease);
}
.hub-pulse::before {
  content: ''; position: absolute; inset: -26px; border-radius: 50%;
  background: radial-gradient(circle, rgba(169,139,255,.55), rgba(124,92,252,.18) 45%, transparent 70%);
}
.hub-pulse::after {
  content: ''; position: absolute; left: 50%; bottom: 50%; width: 2px; height: 120px; margin-left: -1px;
  background: linear-gradient(to top, rgba(169,139,255,.9), transparent);
}
html.hub-board-live .hub-pulse { opacity: 1; }
html.hub-static .hub-pulse { display: none; }

/* No JavaScript (or static before board.js draws): a lit rail stands in for the bus, from the hero mark to the GND pad. */
.hub-board .hub-hero::after,
.hub-zone::before,
.hub-ground::before {
  content: ''; position: absolute; display: none; width: 2px; background: var(--signal); pointer-events: none;
  left: calc((100% - min(1240px, calc(100% - 32px))) / 2 + var(--hub-gut) / 2 - 1px);
}
.hub-board .hub-hero::after { top: calc(96px + 64px + 6px); bottom: 0; }
.hub-zone::before { top: 0; bottom: 0; }
.hub-ground::before { top: 0; height: calc(72px + 7px); }
html:not(.hub-js) .hub-board .hub-hero::after,
html:not(.hub-js) .hub-zone::before,
html:not(.hub-js) .hub-ground::before,
html.hub-static:not(.hub-board-drawn) .hub-board .hub-hero::after,
html.hub-static:not(.hub-board-drawn) .hub-zone::before,
html.hub-static:not(.hub-board-drawn) .hub-ground::before { display: block; }

/* ---- hero: Power all and its hint exist only while the board runs ---- */
.hub-power { display: none; }
.hub-power .hub-btn__ico svg { display: block; }
.hub-hero__hint { display: none; margin: 22px 0 0; color: var(--muted); font: 12px/1.5 var(--mono); }
.hub-hero__kbd { display: none; } /* Space: only where there is a keyboard */
@media (hover: hover) and (pointer: fine) { .hub-hero__kbd { display: inline; } }
.hub-hero__hint kbd { font: inherit; color: var(--fg-2); border: 1px solid var(--line); border-radius: 4px; padding: 0 6px; }
.hub-power--float {
  position: fixed; right: 22px; bottom: 22px; z-index: 6; background: var(--bg-2);
  opacity: 0; transform: translateY(12px); pointer-events: none;
  transition: opacity .6s var(--ease), transform .6s var(--ease);
}
.hub-power--float.is-shown { opacity: 1; transform: none; pointer-events: auto; }

/* ---- zones (one per family) ---- */
.hub-zone { position: relative; padding: 88px 0; border-top: 1px solid var(--line); }
.hub-zone .hub-grid { position: relative; }
.hub-zone__body { grid-column: 2; min-width: 0; }
.hub-silk {
  position: relative; grid-column: 1; grid-row: 1; justify-self: start; align-self: start; margin-left: 2px; padding-top: 4px;
  writing-mode: vertical-rl; transform: rotate(180deg);
  font: 500 10.5px/1.5 var(--mono); letter-spacing: .2em; text-transform: uppercase; color: var(--muted); white-space: nowrap;
}
.hub-silk b { font-weight: 500; color: var(--fg-2); }
.hub-silk__txt, .hub-silk__hot { transition: opacity .6s var(--ease); }
.hub-silk__hot { position: absolute; top: 4px; left: 0; right: 0; bottom: 0; color: var(--signal-hot); opacity: 0; }
.hub-silk__hot b { color: var(--signal-hot); }
.hub-silk.is-on .hub-silk__hot { opacity: 1; }
.hub-silk.is-on .hub-silk__txt { opacity: 0; }

.hub-zone__head { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); gap: 28px; align-items: end; margin-bottom: 28px; }
body.hub-page .hub-zone__title { margin: 10px 0 0; font-size: clamp(28px, 3vw, 38px); line-height: 1.08; letter-spacing: -.03em; }
.hub-zone__intro { max-width: 56ch; margin: 12px 0 0; color: var(--fg-2); font-size: 16px; }
.hub-zone__cnt { margin: 0; color: var(--muted); font: 12px/1.7 var(--mono); text-align: right; }
.hub-zone__cnt b { color: var(--fg-2); font-weight: 500; }
.hub-zone__cnt a { color: var(--fg-2); }
.hub-zone__cnt a:hover { color: var(--fg); }

.hub-cards { display: grid; gap: 14px; }
.hub-cards--suite { margin-bottom: 14px; }
/* Lane 5.4 (round 2): the number of columns follows from the card's minimum width — the width at which every row of
   its sample, its head line and its foot stand in one line (measured: a plugin card 344 px, a product card 368 px,
   the bridges' «from → to» rows the widest). --hub-cols is the most a zone shows side by side; auto-fill keeps the
   empty tracks, so a last card alone in its row is as wide as the ones above it. On a screen narrower than one card
   the card is the width of the screen (min(…, 100%)). */
.hub-cards { --hub-cols: 1; --hub-card-min: 368px; }
.hub-cards--c2 { --hub-cols: 2; }
.hub-cards--c3 { --hub-cols: 3; }
.hub-cards--c4 { --hub-cols: 4; --hub-card-min: 344px; }
.hub-cards--c2, .hub-cards--c3, .hub-cards--c4 {
  grid-template-columns: repeat(auto-fill, minmax(max(min(var(--hub-card-min), 100%), calc((100% - (var(--hub-cols) - 1) * 14px) / var(--hub-cols))), 1fr));
}

/* ---- a card = a chip with a contact pad on its left edge ---- */
.hub-card {
  position: relative; isolation: isolate; display: flex; flex-direction: column; gap: 12px; min-width: 0;
  padding: 22px; border: 1px solid var(--line); border-radius: 8px; background: var(--bg-2);
}
.hub-card::before, /* powered fill */
.hub-card::after { /* powered border */
  content: ''; position: absolute; border-radius: inherit; pointer-events: none;
  transition: opacity .7s var(--ease); transition-delay: calc(var(--hub-card-d, 0) * 1ms);
}
.hub-card::before { inset: 0; z-index: -1; background: var(--hub-bg-3); }
.hub-card::after { inset: -1px; border: 1px solid rgba(169,139,255,.42); }
.hub-card__pad {
  position: absolute; z-index: 1; left: -7px; top: 22px; width: 12px; height: 12px;
  border: 2px solid var(--hub-trace); border-radius: 50%; background: var(--bg);
}
.hub-card__pad::before, /* glow ring */
.hub-card__pad::after { /* lit pad */
  content: ''; position: absolute; border-radius: 50%;
  transition: opacity .5s var(--ease); transition-delay: calc(var(--hub-card-d, 0) * 1ms);
}
.hub-card__pad::before { inset: -6px; background: rgba(124,92,252,.18); }
.hub-card__pad::after { inset: -2px; background: var(--signal); }

.hub-card__col { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
/* the head of a card: sign, name, badge in a row; when the name does not leave room for the badge (DemurrageCheck at 390: the badge was
   3 px past the window) the badge goes to the next line — the name is never broken or squeezed */
.hub-card__top { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 10px; }
body.hub-page .hub-card__name { font-size: 22px; line-height: 1.15; letter-spacing: -.02em; }
.hub-card__one { margin: 0; color: var(--fg-2); font-size: 14.5px; line-height: 1.5; }
.hub-card__meta { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 4px 10px; color: var(--muted); font: 12px/1.5 var(--mono); }
.hub-card__acts { display: flex; flex-wrap: wrap; gap: 8px; }
/* the foot (price, host, buttons) sits at the bottom of the card: a row of cards ends on one line (lane 5.4) */
.hub-card__foot { display: flex; flex-direction: column; justify-content: flex-end; gap: 12px; margin-top: auto; }
.hub-card > .hub-vis { flex: 1 0 auto; } /* the frame takes the free height of the card (the sample stays at its top) */
.hub-card--wide { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: 28px; }
.hub-card--mini { gap: 8px; padding: 16px; }
.hub-card--mini .hub-card__pad { top: 18px; }
body.hub-page .hub-card--mini .hub-card__name { font-size: 16px; letter-spacing: -.01em; }
.hub-card--mini .hub-card__one { font-size: 13px; }
.hub-card--mini .hub-card__meta { margin-top: auto; font-size: 11px; }
.hub-card__more { color: var(--fg-2); }
.hub-card__more:hover { color: var(--fg); }

.hub-badge {
  display: inline-block; padding: 2px 8px; border: 1px solid var(--line); border-radius: 999px; white-space: nowrap;
  color: var(--fg-2); font: 500 10px/1.6 var(--mono); letter-spacing: .12em; text-transform: uppercase;
}
.hub-badge--live { color: var(--hub-ok); border-color: rgba(159,232,195,.3); }

/* ---- the micro-scene frame (the powered part of a card) ---- */
.hub-vis {
  position: relative; min-height: 168px; padding: 14px; overflow: hidden;
  border: 1px solid var(--line); border-radius: 6px; background: var(--bg);
  color: var(--muted); font: 12px/1.4 var(--mono);
}
.hub-card--wide .hub-vis { min-height: 260px; }
.hub-vis__smp { position: absolute; top: 12px; right: 12px; color: var(--muted); font-size: 10px; letter-spacing: .14em; text-transform: uppercase; }
.hub-vis__head { padding-right: 100px; color: var(--fg-2); } /* clear of the «sample data» label */
.hub-vis svg { display: block; width: 100%; height: auto; overflow: visible; }
.hub-vis svg text { fill: var(--muted); font-family: var(--mono); font-size: 10px; }
.hub-vis svg .hub-t-hot { fill: var(--signal-hot); }
.hub-vis svg .hub-t-ok { fill: var(--hub-ok); }
.hub-vis svg .hub-t-fg { fill: var(--fg); }

/* every timed element of a scene: its own delay (--d) + its card's place in the row (--hub-card-d) + the branch (250 ms) */
.hub-vis [data-d] {
  transition: opacity .6s var(--ease), transform .6s var(--ease), stroke-dashoffset .9s var(--ease);
  transition-delay: calc((var(--d, 0) + var(--hub-card-d, 0) + 250) * 1ms);
}
.hub-grow, .hub-pop { transform-box: fill-box; transform-origin: center; }
.hub-draw { stroke-dasharray: 1; stroke-dashoffset: 0; }

/* invoice (WareAudit): the lines and the letter are two flex items; the letter has its own column on the right when
   the scene is wide enough for both, and goes under the lines (at the right) when it is not — it never lies over a line */
.hub-inv__body { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 14px; margin-top: 22px; }
.hub-inv { display: grid; flex: 1 1 320px; min-width: 0; gap: 5px; }
.hub-inv__row {
  /* A row is a flex line (lane 5.9): the label, then what, the amount stands at the right edge of the first line. Every piece is one
     line and is never torn («#48213 · 2 units», «bin B-12 · 31 days»); a row whose pieces do not fit in the card breaks after its
     label: the label and the amount on the first line, what on the second. Only a row that needs it breaks, the others keep one line. */
  --inv-gap: 8px; --inv-lab: 70px; --inv-amt: 54px;
  position: relative; display: flex; flex-wrap: wrap; align-items: baseline; column-gap: var(--inv-gap); row-gap: 2px;
  padding: 5px 8px; border-radius: 3px; color: var(--fg-2);
}
.hub-inv__row > span:not(.hub-inv__mark) { position: relative; min-width: 0; white-space: nowrap; }
.hub-inv__lab { flex: none; width: var(--inv-lab); }
/* the room of the amount is kept beside what (the margin), the amount itself stands over it at the right edge of the first line */
.hub-inv__what { flex: none; margin-right: calc(var(--inv-amt) + var(--inv-gap)); }
.hub-inv__row > span.hub-inv__amt { position: absolute; top: 5px; right: 8px; width: var(--inv-amt); }
.hub-inv__row.is-flag { color: var(--fg); }
.hub-inv__amt { text-align: right; }
.hub-inv__mark { position: absolute; inset: 0; border-left: 2px solid var(--signal); border-radius: 3px; background: rgba(124,92,252,.10); }
.hub-inv__row em {
  position: absolute; top: -9px; right: 8px; padding: 0 5px;
  background: var(--bg); color: var(--signal-hot); font-size: 10px; font-style: normal;
}
.hub-inv__doc {
  flex: 0 1 168px; min-width: 0; margin-left: auto; padding: 12px;
  border: 1px solid var(--line); border-radius: 4px; background: var(--bg-2); color: var(--fg-2);
}
.hub-inv__doc b { display: block; margin: 4px 0 2px; color: var(--fg); font-size: 18px; font-weight: 500; }


/* ---- lane 1.4: the colour of a plugin or a bridge (owner Q40–Q42) ----
   --brand is accent.500 of the plugin's palette (forge-theme/palettes/<slug>.json; the bridges: constants under test);
   --brand-ink colours text (4.5:1 on every card ground), --brand-line colours marks and lines (3:1). Inclusive's 500 is
   too dark for both on the dark card, so its ink and line are its 400. A card without data-product keeps the violet. */
.hub-card[data-product="clearwayforge"], .hub-suite-sign[data-product="clearwayforge"] { --brand: #06B6D4; --brand-ink: #06B6D4; --brand-line: #06B6D4; }
.hub-card[data-product="draftforge"], .hub-suite-sign[data-product="draftforge"] { --brand: #0D9296; --brand-ink: #0D9296; --brand-line: #0D9296; }
.hub-card[data-product="fieldforge"], .hub-suite-sign[data-product="fieldforge"] { --brand: #F43F5E; --brand-ink: #F43F5E; --brand-line: #F43F5E; }
.hub-card[data-product="flowforge"], .hub-suite-sign[data-product="flowforge"] { --brand: #DA76BB; --brand-ink: #DA76BB; --brand-line: #DA76BB; }
.hub-card[data-product="formforge"], .hub-suite-sign[data-product="formforge"] { --brand: #3B82F6; --brand-ink: #3B82F6; --brand-line: #3B82F6; }
.hub-card[data-product="guardforge"], .hub-suite-sign[data-product="guardforge"] { --brand: #2ABE55; --brand-ink: #2ABE55; --brand-line: #2ABE55; }
.hub-card[data-product="inclusiveforge"], .hub-suite-sign[data-product="inclusiveforge"] { --brand: #2456D6; --brand-ink: #4F7DF0; --brand-line: #4F7DF0; }
.hub-card[data-product="langforge"], .hub-suite-sign[data-product="langforge"] { --brand: #0EC49E; --brand-ink: #0EC49E; --brand-line: #0EC49E; }
.hub-card[data-product="profitforge"], .hub-suite-sign[data-product="profitforge"] { --brand: #C98A4B; --brand-ink: #C98A4B; --brand-line: #C98A4B; }
.hub-card[data-product="rankforge"], .hub-suite-sign[data-product="rankforge"] { --brand: #FF6B35; --brand-ink: #FF6B35; --brand-line: #FF6B35; }
.hub-card[data-product="renewforge"], .hub-suite-sign[data-product="renewforge"] { --brand: #6FA287; --brand-ink: #6FA287; --brand-line: #6FA287; }
.hub-card[data-product="velocityforge"], .hub-suite-sign[data-product="velocityforge"] { --brand: #A2C700; --brand-ink: #A2C700; --brand-line: #A2C700; }
.hub-card[data-product="ewsbridge"], .hub-suite-sign[data-product="ewsbridge"] { --brand: #E3B341; --brand-ink: #E3B341; --brand-line: #E3B341; }
.hub-card[data-product="hubspotbridge"], .hub-suite-sign[data-product="hubspotbridge"] { --brand: #5AB0F0; --brand-ink: #5AB0F0; --brand-line: #5AB0F0; }
.hub-card--brand::after { border-color: color-mix(in srgb, var(--brand-line) 45%, transparent); }
.hub-card--brand .hub-card__pad::before { background: color-mix(in srgb, var(--brand) 22%, transparent); }
.hub-card--brand .hub-card__pad::after { background: var(--brand); }

/* the sign: two copies of one sprite symbol (inc/board.php), a grey one under the colour one; the colour arrives by
   crossfading their opacity (Q41: grey before the pulse, colour after; the final frame is the colour) */
.hub-sign { position: relative; display: block; flex: none; width: 36px; height: 36px; }
.hub-sign > svg {
  position: absolute; inset: 0; width: 100%; height: 100%;
  transition: opacity .7s var(--ease); transition-delay: calc((var(--d, 0) + var(--hub-card-d, 0) + 250) * 1ms);
}
.hub-mono { opacity: 0; }
.hub-sign .hub-mono { filter: grayscale(1) brightness(.8); }
.hub-sign--line { width: 26px; height: 26px; }
.hub-sign--line .hub-mono { filter: none; color: var(--muted); }
.hub-sign--line .hub-tint { color: var(--brand-line, var(--signal)); }
.hub-card__top > .hub-sign + .hub-card__name { margin-right: auto; }

/* ---- lane 5.4 (owner 16.10): a row of cards is one height and its parts line up ----
   A card of a row has four parts in this order: head, phrase, sample frame (.hub-vis), foot. With subgrid each card
   takes four rows of its grid, so the parts of the cards of one row share their row heights: heads, phrases, the tops and
   bottoms of the frames and the feet are on one line, the frame stretches over the free height and its sample stays at
   the top (one rule for every frame), the foot keeps to the bottom. Without subgrid the card is the flex column above
   (equal heights by the grid's stretch, the frame grows, the foot at the bottom). A card alone in its zone is the wide one. */
@supports (grid-template-rows: subgrid) {
  .hub-cards > .hub-card:not(.hub-card--wide) { display: grid; grid-template-rows: subgrid; grid-row: span 4; row-gap: 12px; }
  .hub-cards > .hub-card--mini { row-gap: 8px; }
}
.hub-card__noframe { display: block; }

/* the «Preview · hidden on release» label of a product in development: a tab on the card's top edge, out of the flow */
.hub-card > .hub-preview-tag { position: absolute; z-index: 2; top: 0; left: 22px; margin: 0; transform: translateY(-50%); background: var(--bg); }
.hub-card--mini > .hub-preview-tag { left: 16px; }

/* the logo of a plugin (mark + word, avakode_hub_logo()) is the heading of its card: one height for all twelve, the width
   by its own proportion, at the left as the name was; a grey copy lies over the colour one until the pulse (Q41).
   Lane 5.8 (owner 16.10): the files share one frame (tools/plogo-build.mjs: viewBox height 125.097, the mark's anvil +
   hammer 100 of it), so one height draws one mark on every card. 52.08 px = the header's mark ink (41.63 px, chrome.css
   50 px) × 125.097 / 100; ≤ 480 px 4/5 of it, as the header (40 / 50). The card's head is a size container: 18.34cqi =
   100 × 125.097 / 681.777 (the widest file, Clearway) keeps the widest logo inside the narrowest card, so a logo is never
   squeezed (no max-width, no object-fit): it is smaller only where a card is narrower than the widest logo.
   tests/plogo.test.mjs checks these numbers against the files and the header. */
body.hub-page .hub-card__name--logo { margin: 0; line-height: 0; container-type: inline-size; }
.hub-plogo { position: relative; display: inline-block; vertical-align: top; }
.hub-plogo__img {
  display: block; width: auto; height: min(52.08px, 18.34cqi);
  transition: opacity .7s var(--ease); transition-delay: calc((var(--hub-card-d, 0) + 250) * 1ms);
}
@media (max-width: 480px) { .hub-plogo__img { height: min(41.66px, 18.34cqi); } }
.hub-plogo .hub-mono { position: absolute; left: 0; top: 0; filter: grayscale(1) brightness(.8); }
/* ---- lane 5.16 (owner 16.10): the product logos (Forge Suite's too) — the plugin logos' frame (tools/prodlogo-build.mjs)
   and their height (52.08 px, ≤ 480 px 41.66 px: the header's mark ink), under classes of their own, so the plugin logos'
   checks (twelve per page, one height) do not count them. The card's head is the size container; each logo is capped by
   its own file (round 2): 100cqi / --hub-prodlogo-ar (its viewBox width / height, printed by inc/board.php), so only a
   logo wider than its head is smaller. ≤ 480 px the board's plugin logos shrink below 41.66 px (18.34cqi of the mini
   card's name, under 337 px) and the product logos with them: the mini card's name is 2 × (22 − 16) = 12 px wider than
   the product card's head. Never squeezed (no max-width, no object-fit). tests/prodlogo.test.mjs checks these numbers. */
.hub-card__top--logo { container-type: inline-size; align-content: flex-start; } /* the row's heads are one height (subgrid): the logo stays at the top of a taller head */
/* round 2: the status goes on its own line under the logo in every product card — a row of cards keeps one place of the
   status whichever of its logos is widest (beside the logo it would sit in some cards and wrap in others) */
body.hub-page .hub-card__name--prodlogo { margin: 0; line-height: 0; flex: 0 0 100%; }
.hub-prodlogo { position: relative; display: inline-block; vertical-align: top; }
.hub-prodlogo__img {
  display: block; width: auto; height: min(52.08px, calc(100cqi / var(--hub-prodlogo-ar)));
  transition: opacity .7s var(--ease); transition-delay: calc((var(--hub-card-d, 0) + 250) * 1ms);
}
@media (max-width: 480px) { .hub-prodlogo__img { height: min(41.66px, calc((100cqi + 12px) * .1834), calc(100cqi / var(--hub-prodlogo-ar))); } }
.hub-prodlogo .hub-mono { position: absolute; left: 0; top: 0; filter: grayscale(1) brightness(.8); }
/* the phrase of a plugin: one height on all twelve — the most lines any of the twelve takes at this width (no cut:
   min-height, never a fixed height); --hub-one-lines by width below */
.hub-card--mini .hub-card__one { min-height: calc(var(--hub-one-lines, 2) * 13px * 1.5); line-height: 1.5; }
/* the most lines of the twelve phrases by width (measured 16.10, round 2 columns, every px from 320 to 1924: 2 under 440,
   1 at 440–777, 2 at 778–816 and 1188–1245 — right after a column is added, the cards at their narrowest — 1 elsewhere);
   the two-line ranges are widened by ~30 px so a slightly wider font never adds a line; tests/e2e/cards-v2.e2e.mjs
   sweeps 360–1920 */
@media (min-width: 470px) and (max-width: 747px), (min-width: 847px) and (max-width: 1157px), (min-width: 1276px) { .hub-cards { --hub-one-lines: 1; } }

/* the set: twelve signs, the names are in the scene's aria-label */
.hub-suite-signs { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 16px 10px; justify-items: center; margin-top: 26px; }
.hub-suite-sign .hub-sign { width: 44px; height: 44px; }

/* a plugin's micro-scene: a head, a few rows, one signal line (the helper avakode_hub_board_mvis()). Text stays grey and
   white; the colour is carried only by parts that are off before the pulse (a row's mark, its tag, dots, bars, the signal
   line), so an unpowered card shows no colour and the colour arrives by opacity (Q41). */
.hub-vis.hub-mvis { min-height: 0; padding: 12px; color: var(--fg-2); font: 11px/1.45 var(--mono); }
.hub-mvis .hub-vis__smp { top: 9px; right: 10px; font-size: 9px; }
.hub-mvis__hd { margin: 0 74px 8px 0; color: var(--muted); font-size: 10px; }
/* The head of a mock is ONE line and never breaks inside («3PL invoice · October», «quality gate · before publish», …; lane 5.9, round 2).
   The «sample data» label stands beside it at the right when both fit, and when they do not it goes to a line of its own under
   the head (still at the right): the frame is a flex-wrap line, the head and the label first, the rest on full lines. The label is
   in the flow here (the svg mocks keep it absolute over their drawing). The longest head is 29 characters (≈ 174 px at 10px), the
   frame is 210 px wide inside at a 320 px window: every head fits a line of its own, so no head needs a break after «·». */
.hub-vis--invoice, .hub-vis.hub-mvis { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 12px; row-gap: 4px; }
.hub-vis--invoice > *, .hub-vis.hub-mvis > * { flex: 0 0 100%; order: 2; }
.hub-vis--invoice > .hub-vis__head, .hub-vis.hub-mvis > .hub-mvis__hd { flex: 0 1 auto; order: 0; margin: 0 0 4px; padding-right: 0; white-space: nowrap; }
.hub-vis--invoice > .hub-vis__smp, .hub-vis.hub-mvis > .hub-vis__smp { position: static; flex: none; order: 1; margin: 0 0 4px auto; }
.hub-vis__head { white-space: nowrap; }
.hub-vis--suite > .hub-vis__head { padding-right: 0; } /* no label beside the name of the set */
/* the tape's drawing scales with the frame; on a 320 px window its «Pro» tag reached the label by 3 px: the drawing steps down a little */
@media (max-width: 380px) { .hub-vis--tape svg { margin-top: 6px; } }
.hub-mvis__pro {
  display: inline-block; margin-left: 6px; padding: 0 5px; border: 1px solid var(--line); border-radius: 999px;
  color: var(--fg-2); font-size: 9px; font-weight: 500;
}
.hub-mvis__body { display: grid; gap: 3px; }
.hub-mvis__row { position: relative; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 8px; padding: 3px 6px; border-radius: 3px; }
.hub-mvis__row > :not(.hub-mvis__mark) { position: relative; min-width: 0; overflow-wrap: anywhere; }
.hub-mvis__mark { position: absolute; inset: 0; border-left: 2px solid var(--brand-line, var(--signal)); border-radius: 3px; }
.hub-mvis__to { color: var(--muted); }
.hub-mvis__v { margin-left: auto; color: var(--fg); text-align: right; }
.hub-mvis--map .hub-mvis__row { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: baseline; }
.hub-mvis--map .hub-mvis__k { grid-column: 1 / -1; }
.hub-mvis--map .hub-mvis__v { margin-left: 0; text-align: left; }
.hub-mvis__ok { color: var(--hub-ok); }
.hub-mvis__tag { flex-basis: 100%; color: var(--brand-ink, var(--signal-hot)); font-size: 10px; font-style: normal; }
.hub-mvis__dots { display: inline-flex; align-items: center; gap: 3px; }
.hub-mvis__dot { display: block; width: 6px; height: 6px; border-radius: 50%; background: var(--brand-line, var(--signal)); }
.hub-mvis__barbox { display: inline-flex; align-items: center; gap: 6px; }
.hub-mvis__barbox b { color: var(--fg); font-weight: 500; }
.hub-mvis__bar { display: block; width: 44px; height: 4px; border-radius: 2px; background: var(--brand-line, var(--signal)); transform-origin: left center; transform: scaleX(var(--w, 1)); }
.hub-mvis__sig { display: flex; align-items: center; gap: 6px; margin: 8px 0 0; color: var(--brand-ink, var(--signal-hot)); }
.hub-nb { white-space: nowrap; }
.hub-mvis__led { flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--brand-line, var(--signal)); }
.hub-inv__doc i { color: var(--hub-ok); font-size: 10px; font-style: normal; letter-spacing: .12em; text-transform: uppercase; }

/* tree (Musterproof), page (Inclusive Forge), tape (Renew Forge), bridge (the bridges) */
.hub-e { fill: none; stroke: rgba(169,163,196,.45); stroke-width: 1.5; }
.hub-n { fill: var(--hub-bg-3); stroke: rgba(169,163,196,.45); stroke-width: 1.5; }
.hub-n.is-hot { fill: var(--signal); stroke: var(--signal-hot); }
.hub-ring { fill: none; stroke: var(--hub-trace); stroke-width: 3; }
.hub-ring-lit { fill: none; stroke: var(--signal); stroke-width: 3; stroke-linecap: round; stroke-dasharray: 1; stroke-dashoffset: .28; }
.hub-blk { fill: rgba(169,163,196,.14); }
.hub-blk-flag { fill: rgba(124,92,252,.18); stroke: var(--signal); stroke-width: 1.5; }
.hub-dot { fill: rgba(169,163,196,.4); }
.hub-dot-lit { fill: var(--fg-2); }
.hub-dot-rem { fill: var(--signal); }
.hub-pipe { fill: none; stroke: rgba(169,163,196,.35); stroke-width: 10; stroke-linecap: round; }
.hub-span { fill: none; stroke: var(--signal); stroke-width: 3; stroke-linecap: round; }
.hub-flow {
  fill: var(--signal-hot);
  animation: hub-flow 2.2s var(--ease) infinite; animation-delay: calc(var(--i, 0) * -.55s);
  transition: opacity .6s var(--ease);
}
@keyframes hub-flow { from { transform: translateX(0); } to { transform: translateX(260px); } }

/* chip (a plugin) and suite (the Forge Suite card) */
.hub-vis--chip { display: grid; place-items: center; min-height: 96px; padding: 10px; border: 0; background: transparent; }
.hub-vis--chip .hub-chip { display: block; width: 100%; max-width: 132px; height: auto; }
.hub-chip__body { fill: var(--hub-bg-3); stroke: rgba(169,163,196,.45); stroke-width: 1.5; }
.hub-pin { fill: rgba(169,163,196,.3); }
.hub-pin-lit { fill: var(--signal); }
.hub-led { fill: rgba(169,163,196,.25); }
.hub-led-lit { fill: var(--signal-hot); }
.hub-vis .hub-chip__nm { fill: var(--fg-2); font-size: 7px; letter-spacing: .08em; }

/* ---- GND: the pad at the foot of the board and the counter ---- */
.hub-ground { position: relative; padding: 72px 0 56px; border-top: 1px solid var(--line); }
.hub-ground:focus { outline: 0; } /* the anchor board.js moves the focus to when the floating button goes away */
.hub-ground__rail { display: flex; justify-content: center; }
.hub-ground__pad { position: relative; width: 14px; height: 14px; border: 2px solid var(--hub-trace); border-radius: 50%; background: var(--bg); }
.hub-ground__pad::before {
  content: 'GND'; position: absolute; top: 20px; left: 50%; transform: translateX(-50%);
  color: var(--muted); font: 10px/1.4 var(--mono); letter-spacing: .14em;
}
.hub-ground__pad::after { content: ''; position: absolute; inset: -2px; border-radius: 50%; background: var(--signal); transition: opacity .5s var(--ease); }
/* the number and the caption are two boxes of their own: when the row is too narrow for both, the caption goes under the number
   (never over it: the line is 1.3 tall, the glyph box of a line is 1.22) and on a phone it is a little smaller, so it stays one line */
.hub-ground__count { margin: 0; color: var(--fg); font-family: var(--display); font-size: clamp(32px, 4vw, 52px); letter-spacing: -.03em; line-height: 1.3; }
.hub-ground__count b { display: inline-block; color: var(--signal-hot); font-weight: 500; font-variant-numeric: tabular-nums; }
.hub-ground__cap { display: inline-block; font-size: min(1em, 7.4vw); white-space: nowrap; }
.hub-board-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }

/* ---- narrow: the bus along the left edge, one column ---- */
@media (max-width: 960px) {
  .hub-zone { padding: 64px 0; }
  .hub-silk { display: none; }
  .hub-zone__head,
  .hub-card--wide { grid-template-columns: minmax(0, 1fr); }
  .hub-zone__cnt { text-align: left; }
  .hub-board .hub-hero::after { top: calc(56px + 28px + 6px); }
  .hub-ground { padding: 56px 0 44px; }
  .hub-ground::before { height: calc(56px + 7px); }
  /* The floating Power all becomes a round bolt in the right corner, and the buttons of the cards keep clear of that corner
     (28 px to the right of the last one: the circle is 48 px at 12 px from the edge), so it never lies over a card's button. */
  .hub-power--float { right: 12px; bottom: 16px; padding: 10px; }
  .hub-power--float .hub-power__txt { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
  .hub-card__acts, .hub-card__more { margin-right: 28px; }
  .hub-suite-signs { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-width: 480px) {
  /* the narrowest card: the sign and one signal line of the scene (with its «sample data» mark) */
  .hub-card--mini .hub-mvis__hd,
  .hub-card--mini .hub-mvis__body { display: none; }
  .hub-card--mini .hub-mvis .hub-vis__smp { position: static; display: block; margin-bottom: 2px; }
  .hub-card--mini .hub-mvis__sig { margin: 0; }
}
@media (max-width: 520px) {
  .hub-inv__row { --inv-gap: 6px; padding: 5px 6px; }
  .hub-inv__row > span.hub-inv__amt { right: 6px; }
}

/* ---- the unpowered board: only while scripts run with motion allowed ---- */
@media (prefers-reduced-motion: no-preference) {
  html.hub-js:not(.hub-static) .hub-power { display: inline-flex; }
  html.hub-js:not(.hub-static) .hub-hero__hint { display: block; }

  html.hub-js:not(.hub-static) .hub-card:not(.is-on):not(:hover):not(:focus-within)::before,
  html.hub-js:not(.hub-static) .hub-card:not(.is-on):not(:hover):not(:focus-within)::after,
  html.hub-js:not(.hub-static) .hub-card:not(.is-on):not(:hover):not(:focus-within) .hub-card__pad::before,
  html.hub-js:not(.hub-static) .hub-card:not(.is-on):not(:hover):not(:focus-within) .hub-card__pad::after { opacity: 0; transition-delay: 0ms; }
  html.hub-js:not(.hub-static) .hub-card:not(.is-on):not(:hover):not(:focus-within) .hub-vis [data-d] { transition-delay: 0ms; transition-duration: .35s; }
  html.hub-js:not(.hub-static) .hub-card:not(.is-on):not(:hover):not(:focus-within) .hub-off { opacity: .18; transform: translateY(6px); }
  html.hub-js:not(.hub-static) .hub-card:not(.is-on):not(:hover):not(:focus-within) .hub-lit { opacity: 0; }
  html.hub-js:not(.hub-static) .hub-card:not(.is-on):not(:hover):not(:focus-within) .hub-pop { opacity: 0; transform: translateY(4px); }
  html.hub-js:not(.hub-static) .hub-card:not(.is-on):not(:hover):not(:focus-within) .hub-grow { opacity: 0; transform: scale(.4); }
  html.hub-js:not(.hub-static) .hub-card:not(.is-on):not(:hover):not(:focus-within) .hub-slide { opacity: 0; transform: translateX(24px); }
  html.hub-js:not(.hub-static) .hub-card:not(.is-on):not(:hover):not(:focus-within) .hub-draw,
  html.hub-js:not(.hub-static) .hub-card:not(.is-on):not(:hover):not(:focus-within) .hub-ring-lit { stroke-dashoffset: 1; }
  html.hub-js:not(.hub-static) .hub-card:not(.is-on):not(:hover):not(:focus-within) .hub-flow { opacity: 0; animation-play-state: paused; }
  /* lane 1.4: grey before the pulse, the colour after it (or while hovered / focused) */
  html.hub-js:not(.hub-static) .hub-card:not(.is-on):not(:hover):not(:focus-within) .hub-tint { opacity: 0; transition-delay: 0ms; }
  html.hub-js:not(.hub-static) .hub-card:not(.is-on):not(:hover):not(:focus-within) .hub-mono { opacity: 1; transition-delay: 0ms; }
  html.hub-js:not(.hub-static) .hub-card:not(.is-on):not(:hover):not(:focus-within) .hub-grow-x { opacity: 0; transform: scaleX(0); }
  html.hub-js:not(.hub-static) .hub-ground__pad:not(.is-on)::after { opacity: 0; }
}

/* ---- static and reduced motion: the final frame, nothing moves ---- */
html.hub-static .hub-board *,
html.hub-static .hub-board *::before,
html.hub-static .hub-board *::after,
html.hub-static .hub-ground *,
html.hub-static .hub-ground *::before,
html.hub-static .hub-ground *::after { transition: none !important; }
html.hub-static .hub-flow { animation: none; transform: translateX(calc(var(--i, 0) * 65px + 30px)); }
@media (prefers-reduced-motion: reduce) {
  .hub-pulse, .hub-power, .hub-hero__hint { display: none; }
  .hub-board *, .hub-board *::before, .hub-board *::after,
  .hub-ground *, .hub-ground *::before, .hub-ground *::after { transition: none !important; }
  .hub-flow { animation: none; transform: translateX(calc(var(--i, 0) * 65px + 30px)); }
}
