/* The Hub (/) and the two free pages it shares a look with: the Free Stuff umbrella
   (/free-stuff/) and PBI Agentic Dev (/power-bi-agentic-dev/). Report Hacker's own page,
   /report-hacker/, came down on 18 Oct 2026 (KAN-58): the tool's front door at /mockup/ is
   its landing page now. A page loads this file through `stylesheets` in its front matter.

   Lifted from the prototype (docs/hub-prototype/prototype-site.html) with only the
   decided variants kept: the Constellation with the `model` motion, nothing below it, and
   the Umbrella as three cards (docs/site-hub-sections-design.md). The pages carry
   bodyClass "page-portfolio" as the prototype did, so all.css gives them the Portfolio's
   nav, footer and type; the Hub adds "page-hub", which the Constellation keys off. Page
   content sits in a .pt wrapper, so none of this reaches the nav or the footer. */

/* ================================================================
   Shared tokens
   ================================================================ */
body.page-portfolio {
  --indigo: #4f46e5;
  --violet: #7c6cf6;
  --indigo-soft: #eef0ff;
  --teal: #0fb5ae;
  --teal-ink: #0a7a75;
  --teal-soft: #e3f6f5;
}
body.page-portfolio .pt { font-family: 'Manrope', system-ui, sans-serif; color: #2a2f3a; }
body.page-portfolio .pt p { margin: 0 !important; }
body.page-portfolio .pt ul { margin: 0 !important; padding: 0 !important; list-style: none !important; }
body.page-portfolio .pt li { margin: 0 !important; }
body.page-portfolio .pt h1,
body.page-portfolio .pt h2,
body.page-portfolio .pt h3 { color: #0c0e14 !important; margin: 0 !important; letter-spacing: -0.02em; }
body.page-portfolio .pt .serif {
  background: linear-gradient(120deg, #4f46e5 0%, #7c6cf6 55%, #0fb5ae 100%);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent !important;
}
body.page-portfolio .pt a.pt-btn {
  display: inline-flex !important; align-items: center; gap: 0.5rem;
  background: #0c0e14 !important; color: #fff !important;
  border: 1px solid #0c0e14 !important; border-radius: 999px;
  padding: 0.72rem 1.3rem; font-size: 0.92rem; font-weight: 600;
  text-decoration: none !important; transition: transform .18s, box-shadow .18s;
}
body.page-portfolio .pt a.pt-btn:hover { transform: translateY(-1px); box-shadow: var(--shadow-md); color: #fff !important; }
body.page-portfolio .pt a.pt-btn.brand { background: linear-gradient(135deg, #4f46e5, #7c6cf6) !important; border-color: transparent !important; box-shadow: 0 6px 18px rgba(79,70,229,.3); }
body.page-portfolio .pt a.pt-btn.ghost { background: #fff !important; color: #0c0e14 !important; border-color: #e7e9ee !important; }
body.page-portfolio .pt a.pt-btn.ghost:hover { color: #0c0e14 !important; }
body.page-portfolio .pt a.pt-link { color: var(--indigo) !important; font-weight: 700; border-bottom: 1px solid transparent !important; text-decoration: none !important; }
body.page-portfolio .pt a.pt-link:hover { border-bottom-color: currentColor !important; }
/* Report Hacker is coming soon (Tim, 16 Oct 2026): its buttons and cards keep their shape but
   carry no href, so nothing leads to the tool until he is happy with it. */
body.page-portfolio .pt a.pt-btn.soon, body.page-portfolio .pt a.pt-btn.soon:hover { background: #f5f6f9 !important; color: #5f6677 !important; border-color: #e7e9ee !important; box-shadow: none; transform: none; cursor: default; }

/* ================================================================
   HUB
   ================================================================ */
body.page-portfolio .hub { padding: 2.6rem 0 1rem; }
body.page-portfolio .hub-intro { text-align: center; max-width: 900px; margin: 0 auto 1.4rem; }
body.page-portfolio .hub-intro h1 {
  font-size: clamp(2rem, 4.4vw, 3.15rem) !important; font-weight: 800 !important;
  line-height: 1.06 !important; letter-spacing: -0.035em !important; margin: 1rem 0 0 !important;
  text-wrap: balance;
}

/* The Doors */
body.page-portfolio .nexus { position: relative; max-width: 1140px; margin: 0 auto; }
body.page-portfolio .nexus .door { --acc: var(--indigo); --acc-soft: var(--indigo-soft); }
body.page-portfolio .nexus .door.free { --acc: var(--teal-ink); --acc-soft: var(--teal-soft); }
body.page-portfolio .pt a.door {
  position: relative; z-index: 1; display: flex; flex-direction: column;
  background: #fff; border: 1px solid #e7e9ee !important; border-radius: 18px;
  box-shadow: var(--shadow-sm); color: #0c0e14 !important; text-decoration: none !important;
  transition: transform .2s, box-shadow .2s, border-color .2s; overflow: hidden; text-align: left;
}
body.page-portfolio .pt a.door:hover,
body.page-portfolio .pt a.door:focus-visible {
  transform: translateY(-2px); box-shadow: var(--shadow-md);
  border-color: var(--acc) !important; color: #0c0e14 !important;
}
body.page-portfolio .pt a.door:focus-visible { outline: 2px solid var(--acc); outline-offset: 3px; }
body.page-portfolio .door-body { display: flex; gap: .8rem; align-items: flex-start; padding: .95rem 1rem 1rem; }
body.page-portfolio .door-icon {
  flex: 0 0 38px; height: 38px; border-radius: 11px; display: grid; place-items: center;
  background: var(--acc-soft); color: var(--acc);
}
body.page-portfolio .door-icon svg { width: 20px; height: 20px; }
body.page-portfolio .door-title { display: block; font-weight: 700; font-size: .98rem; line-height: 1.28; letter-spacing: -.01em; }
body.page-portfolio .door-sub { display: block; font-size: .8rem; line-height: 1.42; color: #565d6e; margin-top: .3rem; }
body.page-portfolio .door-go {
  position: absolute; right: .75rem; top: .75rem; width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center; background: var(--acc); color: #fff; font-size: .8rem;
  opacity: 0; transform: translateX(-4px); transition: opacity .2s, transform .2s;
}
body.page-portfolio a.door:hover .door-go,
body.page-portfolio a.door:focus-visible .door-go { opacity: 1; transform: none; }

/* ---- The Constellation (desktop) ---- */
body.page-hub .nexus {
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); grid-template-rows: repeat(3, 172px);
}
body.page-hub .nexus .door { place-self: center; width: min(100%, 236px); margin: 0 6px; }
body.page-hub .nexus .door[data-slot="tl"] { grid-area: 1 / 2; }
body.page-hub .nexus .door[data-slot="l"]  { grid-area: 2 / 1; }
body.page-hub .nexus .door[data-slot="bl"] { grid-area: 3 / 2; }
body.page-hub .nexus .door[data-slot="tr"] { grid-area: 1 / 4; }
body.page-hub .nexus .door[data-slot="r"]  { grid-area: 2 / 5; }
body.page-hub .nexus .door[data-slot="br"] { grid-area: 3 / 4; }

body.page-portfolio .spokes { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; z-index: 0; }

body.page-portfolio .core {
  grid-area: 2 / 3; place-self: center; position: relative; z-index: 1;
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: .55rem;
}
body.page-portfolio .core-orb {
  position: relative; width: 84px; height: 84px; border-radius: 26px;
  background: linear-gradient(135deg, #4f46e5 0%, #7c6cf6 100%);
  display: grid; place-items: center; color: #fff; font-weight: 800; font-size: 2.2rem; letter-spacing: -.04em;
  box-shadow: 0 14px 36px rgba(79,70,229,.38), inset 0 1px 0 rgba(255,255,255,.4);
}
body.page-portfolio .core-orb::before,
body.page-portfolio .core-orb::after {
  content: ""; position: absolute; inset: -14px; border-radius: 34px; border: 1.5px solid rgba(124,108,246,.35);
  animation: pt-halo 3.2s ease-out infinite;
}
body.page-portfolio .core-orb::after { animation-delay: 1.6s; border-color: rgba(15,181,174,.35); }
@keyframes pt-halo { from { transform: scale(.85); opacity: 1; } to { transform: scale(1.35); opacity: 0; } }

/* Two quiet hemisphere labels: Work on the left, Resources on the right */
body.page-portfolio .hemi { position: absolute; top: -.6rem; font: 800 .76rem/1.3 'Manrope', sans-serif; letter-spacing: .14em; text-transform: uppercase; }
body.page-portfolio .hemi.tim { left: 20%; color: #5148d8; }
body.page-portfolio .hemi.free { right: 20%; color: #0b7f7a; text-align: right; }

/* ---- The Constellation on a phone: the trunk ---- */
@media (max-width: 860px) {
  body.page-hub .nexus {
    display: flex; flex-direction: column; gap: .7rem; padding-left: 30px; margin-top: 1rem;
  }
  body.page-hub .nexus::before {
    content: ""; position: absolute; left: 13px; top: 36px; bottom: 34px; width: 2px; border-radius: 2px;
    background: linear-gradient(#7c6cf6 0%, #7c6cf6 48%, #0fb5ae 52%, #0fb5ae 100%);
  }
  body.page-hub .spokes, body.page-hub .hemi { display: none; }
  body.page-hub .core { flex-direction: row; margin: 0 0 .4rem -30px; gap: .8rem; text-align: left; place-self: auto; }
  body.page-hub .core-orb { width: 28px; height: 28px; border-radius: 9px; font-size: .95rem; }
  body.page-hub .core-orb::before, body.page-hub .core-orb::after { inset: -6px; border-radius: 13px; }
  body.page-hub .nexus .door { width: 100%; margin: 0; }
  body.page-hub .nexus .door::before {
    content: ""; position: absolute; left: -17px; top: 50%; width: 17px; height: 2px; background: var(--acc);
    opacity: .5;
  }
  /* Beats the card's own overflow: hidden, so the branch stub and the label show */
  body.page-portfolio.page-hub .pt .nexus a.door { overflow: visible; }
  body.page-hub .nexus .door.first-free { margin-top: 1rem; }
  body.page-hub .nexus .door.first-free::after {
    content: "Resources"; position: absolute; left: 0; top: -1.35rem;
    font: 800 .76rem/1.3 'Manrope', sans-serif; letter-spacing: .14em; text-transform: uppercase; color: #0b7f7a;
  }
}

/* ---- The Constellation on a narrow desktop (861 to 1100px): the Doors wrap to 187 to
   243px, taller than the 172px rows, and ran over the hemisphere labels. Equal rows sized
   to the tallest Door (minmax with 1fr in an auto-height grid), a row gap, and the labels
   lifted above the grid. ---- */
@media (min-width: 861px) and (max-width: 1100px) {
  body.page-hub .nexus { grid-template-rows: repeat(3, minmax(172px, 1fr)); row-gap: 1rem; margin-top: 1.6rem; }
  body.page-portfolio .nexus .hemi { top: -1.45rem; }
}

/* A drawn terminal stands in for a screenshot on the PBI Agentic Dev card */
body.page-portfolio .term {
  flex-direction: column; justify-content: center; gap: .45rem; padding: 1rem 1.1rem;
  background: #0c0e14 !important; color: #c9cdf8; font: 500 .76rem/1.5 'JetBrains Mono', monospace; overflow: hidden;
}
body.page-portfolio .term .p { color: #6ee7df; }
body.page-portfolio .term .d { color: #8b92a3; }

/* ================================================================
   The free pages: shared hero and sections
   ================================================================ */
body.page-portfolio .rp { max-width: 1140px; margin: 0 auto; padding: 2.6rem 0 0; }
body.page-portfolio .rp-hero { display: grid; grid-template-columns: 1.05fr 1fr; gap: 2.6rem; align-items: center; }
body.page-portfolio .rp-hero.solo { grid-template-columns: minmax(0, 820px); }
body.page-portfolio .rp-hero h1 { font-size: clamp(2rem, 4.2vw, 3rem) !important; font-weight: 800 !important; line-height: 1.06 !important; letter-spacing: -.035em !important; margin: 1rem 0 0 !important; }
body.page-portfolio .rp-hero .deck { font-size: 1.08rem; color: #565d6e; margin-top: 1rem !important; max-width: 60ch; }
body.page-portfolio .rp-actions { display: flex; flex-wrap: wrap; gap: .65rem; margin-top: 1.5rem; }
body.page-portfolio .rp-pills { display: flex; flex-wrap: wrap; gap: .45rem; margin-top: 1.2rem; }
body.page-portfolio .rp-pills span { font-size: .78rem; font-weight: 600; color: #2a2f3a; background: #fff; border: 1px solid #e7e9ee; padding: .35rem .75rem; border-radius: 999px; }
body.page-portfolio .browser { border-radius: 16px; overflow: hidden; background: #fff; border: 1px solid #e7e9ee; box-shadow: var(--shadow-lg); }
body.page-portfolio .browser .bar { display: flex; gap: .35rem; align-items: center; padding: .55rem .8rem; background: #f5f6f9; border-bottom: 1px solid #e7e9ee; font: 500 .76rem/1.3 'JetBrains Mono', monospace; color: #5f6677; }
body.page-portfolio .browser .bar i { width: 9px; height: 9px; border-radius: 50%; background: #dfe2ea; display: inline-block; }
body.page-portfolio .browser .bar em { font-style: normal; margin-left: .6rem; }
body.page-portfolio .browser img { display: block; width: 100%; }
body.page-portfolio .rp-sec { margin-top: 3.6rem; }
body.page-portfolio .rp-sec > h2 { font-size: 1.7rem !important; font-weight: 800 !important; line-height: 1.15 !important; }
body.page-portfolio .rp-sec > .sub { color: #565d6e; margin-top: .5rem !important; max-width: 64ch; }
body.page-portfolio .rp-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1rem; margin-top: 1.4rem; }
body.page-portfolio .pt a.rp-card, body.page-portfolio .rp-card {
  display: flex !important; flex-direction: column; background: #fff; border: 1px solid #e7e9ee !important; border-radius: 18px;
  overflow: hidden; box-shadow: var(--shadow-sm); color: #0c0e14 !important; text-decoration: none !important; transition: transform .2s, box-shadow .2s, border-color .2s;
}
body.page-portfolio .pt a.rp-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: var(--indigo) !important; color: #0c0e14 !important; }
body.page-portfolio .rp-card img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; object-position: top left; border-bottom: 1px solid #eef0f4; display: block; }
body.page-portfolio .rp-card .in { padding: 1rem 1.1rem 1.15rem; display: flex; flex-direction: column; gap: .35rem; flex: 1; }
body.page-portfolio .rp-card .k { font: 500 .76rem/1.3 'JetBrains Mono', monospace; color: #5f6677; }
body.page-portfolio .rp-card .t { font-weight: 800; font-size: 1.1rem; letter-spacing: -.015em; }
body.page-portfolio .rp-card .d { font-size: .88rem; color: #565d6e; line-height: 1.5; }
body.page-portfolio .rp-card .go { margin-top: auto; padding-top: .6rem; font-weight: 700; font-size: .86rem; color: var(--indigo); }
body.page-portfolio .rp-card.soon .go { color: #5f6677; }
@media (max-width: 900px) { body.page-portfolio .rp-hero { grid-template-columns: 1fr; } body.page-portfolio .rp-cards { grid-template-columns: 1fr; } }
/* .pt p zeroes paragraph margins, so a paragraph's own margin needs .pt to win */
body.page-portfolio .pt .rp-back { margin: 3rem 0 0 !important; padding-top: 1.4rem; border-top: 1px solid #e7e9ee; font-size: .92rem; }

/* ================================================================
   UMBRELLA /free-stuff/
   ================================================================ */
body.page-portfolio .u-hero { text-align: center; max-width: 760px; margin: 0 auto; }
body.page-portfolio .u-hero h1 { font-size: clamp(2rem, 4.2vw, 3rem) !important; font-weight: 800 !important; line-height: 1.06 !important; letter-spacing: -.035em !important; margin: 1rem 0 0 !important; }
body.page-portfolio .u-hero .deck { margin: 1rem auto 0 !important; font-size: 1.08rem; color: #565d6e; max-width: 60ch; }
body.page-portfolio .u-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.1rem; margin-top: 2.4rem; }
body.page-portfolio .u-card { background: #fff; border: 1px solid #e7e9ee; border-radius: 22px; overflow: hidden; box-shadow: var(--shadow-sm); display: flex; flex-direction: column; }
body.page-portfolio .u-card .media { aspect-ratio: 16 / 10; overflow: hidden; border-bottom: 1px solid #eef0f4; display: flex; }
body.page-portfolio .u-card .media img { width: 100%; height: 100%; object-fit: cover; object-position: top left; }
body.page-portfolio .u-card .in { padding: 1.2rem 1.3rem 1.3rem; display: flex; flex-direction: column; gap: .5rem; flex: 1; }
body.page-portfolio .u-card .k { display: inline-flex; align-self: flex-start; font-weight: 700; font-size: .76rem; color: #0a6f6a; background: var(--teal-soft); padding: .3rem .6rem; border-radius: 999px; }
body.page-portfolio .u-card h2 { font-size: 1.35rem !important; font-weight: 800 !important; }
body.page-portfolio .u-card .d { color: #565d6e; font-size: .93rem; }
body.page-portfolio .u-card li { position: relative; padding-left: 1.2rem; font-size: .88rem; color: #2a2f3a; margin-top: .35rem !important; }
body.page-portfolio .u-card li::before { content: ""; position: absolute; left: 0; top: .55em; width: 7px; height: 7px; border-radius: 50%; background: var(--teal); }
body.page-portfolio .u-card .acts { margin-top: auto; padding-top: .9rem; display: flex; gap: .5rem; flex-wrap: wrap; }
@media (max-width: 900px) { body.page-portfolio .u-cards { grid-template-columns: 1fr; } }
body.page-portfolio .pt .u-foot { text-align: center; color: #5f6677; font-size: .9rem; margin-top: 2rem !important; }

/* ================================================================
   /power-bi-agentic-dev/ extras
   ================================================================ */
body.page-portfolio .two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-top: 1rem; }
body.page-portfolio .two-col p { color: #2a2f3a; }
@media (max-width: 820px) { body.page-portfolio .two-col { grid-template-columns: 1fr; } }
body.page-portfolio .tag-row { display: flex; flex-wrap: wrap; gap: .4rem; }
body.page-portfolio .tag-row span { font-size: .76rem; font-weight: 700; padding: .32rem .7rem; border-radius: 999px; background: #fff; border: 1px solid #e7e9ee; color: #2a2f3a; }
body.page-portfolio .tag-row span.feat { background: var(--teal-soft); border-color: transparent; color: var(--teal-ink); }
body.page-portfolio .attrib {
  margin-top: 1.3rem; display: flex; gap: .9rem; align-items: flex-start; background: var(--teal-soft); border: 1px solid #c5ebe8;
  border-radius: 14px; padding: .95rem 1.1rem; max-width: 64ch; font-size: .93rem;
}
body.page-portfolio .attrib .fork { flex: 0 0 34px; height: 34px; border-radius: 10px; background: #fff; color: var(--teal-ink); display: grid; place-items: center; }
body.page-portfolio .code {
  margin-top: 1.2rem; background: #0c0e14; border-radius: 16px; padding: 1.1rem 1.3rem; color: #e6e8ff;
  font: 500 .88rem/1.9 'JetBrains Mono', monospace; overflow-x: auto; white-space: pre; font-variant-ligatures: none;
}
body.page-portfolio .code .p { color: #6ee7df; user-select: none; }
body.page-portfolio .code .c { color: #8b92a3; }
body.page-portfolio .plugins { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .6rem; margin-top: 1.3rem; }
body.page-portfolio .plugin { display: flex; gap: .8rem; align-items: baseline; background: #fff; border: 1px solid #e7e9ee; border-radius: 12px; padding: .75rem .95rem; }
body.page-portfolio .plugin code { flex: 0 0 150px; font: 600 .82rem/1.3 'JetBrains Mono', monospace; color: var(--indigo); background: none; padding: 0; }
body.page-portfolio .plugin span { font-size: .88rem; color: #565d6e; }
body.page-portfolio .adds { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .7rem; margin-top: 1.3rem; }
body.page-portfolio .add { background: #fff; border: 1px solid #e7e9ee; border-radius: 14px; padding: .85rem .95rem; }
body.page-portfolio .add code::before { content: ""; display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--teal); margin-right: .45rem; vertical-align: 1px; }
body.page-portfolio .add code { font: 600 .8rem/1.3 'JetBrains Mono', monospace; color: #0c0e14; background: none; padding: 0; }
body.page-portfolio .add .wip { font: 700 .64rem/1 'JetBrains Mono', monospace; letter-spacing: .06em; color: #8a5a00; background: #fff4d6; border-radius: 999px; padding: .2rem .45rem; margin-left: .45rem; vertical-align: 1px; }
body.page-portfolio .add p { font-size: .82rem; color: #565d6e; margin-top: .35rem !important; line-height: 1.45; }
body.page-portfolio .pt .also { margin-top: 1rem !important; font-size: .92rem; color: #2a2f3a; }
body.page-portfolio .also li { padding-left: 1.1rem; position: relative; margin-top: .3rem !important; }
body.page-portfolio .also li::before { content: ""; position: absolute; left: 0; top: .6em; width: 6px; height: 6px; border-radius: 50%; background: var(--teal); }
body.page-portfolio .others { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .7rem; margin-top: 1.2rem; }
body.page-portfolio .pt a.other { display: block !important; background: #fff; border: 1px solid #e7e9ee !important; border-radius: 14px; padding: .9rem 1rem; color: #0c0e14 !important; text-decoration: none !important; }
body.page-portfolio .pt a.other:hover { border-color: var(--indigo) !important; color: #0c0e14 !important; }
body.page-portfolio .other b { display: block; }
body.page-portfolio .other span { font-size: .84rem; color: #565d6e; }
body.page-portfolio .licence { margin-top: 3rem; background: #fff; border: 1px solid #e7e9ee; border-radius: 18px; padding: 1.3rem 1.4rem; font-size: .92rem; }
body.page-portfolio .licence h3 { font-size: 1.05rem !important; font-weight: 800 !important; }
body.page-portfolio .licence p { margin-top: .5rem !important; color: #2a2f3a; }
@media (max-width: 900px) { body.page-portfolio .adds { grid-template-columns: repeat(2, minmax(0, 1fr)); } body.page-portfolio .plugins, body.page-portfolio .others { grid-template-columns: 1fr; } }
@media (max-width: 480px) { body.page-portfolio .plugin { flex-direction: column; gap: .2rem; } body.page-portfolio .plugin code { flex: none; } }

@media (prefers-reduced-motion: reduce) {
  body.page-portfolio .core-orb::before, body.page-portfolio .core-orb::after { animation: none; }
}

/* Pause guard: the loops stop while the Constellation is off screen or the tab is
   hidden. The Hub's script sets .is-paused. */
body.page-portfolio .hub.is-paused .nexus *,
body.page-portfolio .hub.is-paused .nexus *::before,
body.page-portfolio .hub.is-paused .nexus *::after { animation-play-state: paused !important; }

/* ================================================================
   Hub variant "model": the spokes are Power BI relationships.
   A 1 at the core, a * where each one meets its Door and a filter
   chevron mid-line. On arrival they grow out of the mark at one speed,
   so the order they land in comes from real length. Pointing at a
   Door, or tabbing to it, cross-highlights the hub the way selecting
   a data point does in a report. Doors never move or fade during the
   entrance, and Door text never dims.
   Script-set state on .wow-model: .is-geo (glyphs aimed at the
   measured card edges), .is-play (entrance), .is-hold (entrance held
   at its first frame until the webfont has settled the card sizes).
   ================================================================ */
@property --wow-model-edge { syntax: '<color>'; inherits: false; initial-value: #e7e9ee; }

body.page-hub .wow-model {
  display: block;
  --wow-model-ease: cubic-bezier(.16, 1, .3, 1);
}
/* With no lede under the heading, the hemisphere labels need their own air above them */
@media (min-width: 861px) {
  body.page-hub .wow-model .hub-intro { margin-bottom: 2.6rem; }
}

/* Cross-highlight timing: a 120ms grace before anything restores, and the restore
   (140ms) is quicker than the entry. The hover and focus rules below retime it. */
body.page-hub .wow-model .nexus { --wow-model-td: .12s; --wow-model-tt: .14s; }

/* ---- Relationships (desktop) ---- */
/* The 1s and *s take their hemisphere's ink at rest, so they read as the ends of a
   relationship rather than as footnote marks; the filter chevron takes the full accent. */
body.page-hub .wow-model .wm-rel {
  --wow-model-tint: #a9a3f7; --wow-model-hot: #4f46e5; --wow-model-ink: #4f46e5;
  --wow-model-dur: 476ms; --wow-model-land: 449ms;
  --wow-model-stroke: var(--wow-model-tint); --wow-model-sw: 1.5px; --wow-model-lo: .55;
  --wow-model-gc: var(--wow-model-ink); --wow-model-go: .7; --wow-model-cc: var(--wow-model-hot); --wow-model-co: .8;
  --wow-model-do: 1; --wow-model-fo: 0; --wow-model-dps: running; --wow-model-fps: paused;
  transition: opacity var(--wow-model-tt) linear var(--wow-model-td);
}
body.page-hub .wow-model .wm-rel.free { --wow-model-tint: #7fd6d1; --wow-model-hot: #0fb5ae; --wow-model-ink: #0a7a75; }
/* CSS-only defaults at 1140px, for lines that still end at card centres; the script replaces them */
body.page-hub .wow-model .wm-rel:is([data-i="2"], [data-i="5"]) { --wow-model-dur: 760ms; --wow-model-land: 730ms; }

body.page-hub .wow-model .spokes line { fill: none; stroke-linecap: round; vector-effect: non-scaling-stroke; }
body.page-hub .wow-model .wm-line {
  stroke: var(--wow-model-stroke); stroke-width: var(--wow-model-sw); opacity: var(--wow-model-lo);
  transform-box: view-box; transform-origin: var(--wow-model-ox, 50%) var(--wow-model-oy, 50%);
  transition: stroke var(--wow-model-tt) linear var(--wow-model-td),
              stroke-width var(--wow-model-tt) linear var(--wow-model-td),
              opacity var(--wow-model-tt) linear var(--wow-model-td);
}
/* The comet tip: a twin of the outer part of the line, scaled with it, a 2.5px head over a
   7px wash. Both are stroked with the line's own gradient (its stroke attribute), clear at
   the tail and full accent at the head, so it glows without a blur filter. */
body.page-hub .wow-model .wm-tip { opacity: 0; transform-box: view-box; transform-origin: var(--wow-model-ox, 50%) var(--wow-model-oy, 50%); }
body.page-hub .wow-model .wm-glow { stroke-width: 7px; stroke-opacity: .2; }
body.page-hub .wow-model .wm-head { stroke-width: 2.5px; }
/* Tim's flowing dots, outward from the 1 to the *: they read as filter direction now */
body.page-hub .wow-model .wm-dots,
body.page-hub .wow-model .wm-fast {
  stroke: var(--wow-model-hot); stroke-width: 3px; stroke-dasharray: 1 22;
  animation: wow-model-flow 2.6s linear infinite;
  transition: opacity var(--wow-model-tt) linear var(--wow-model-td);
}
body.page-hub .wow-model .wm-dots { opacity: var(--wow-model-do); animation-play-state: var(--wow-model-dps); }
/* A second, twice-as-fast set crossfades in on the lit relationship, so the speed-up never
   jumps. Its beads are heavier and inked (the lit Door's own accent) against a lit line held
   at .45, so the racing beads never merge into it. */
body.page-hub .wow-model .wm-fast { stroke: var(--wow-model-ink); stroke-width: 3.5px; opacity: var(--wow-model-fo); animation-duration: 1.3s; animation-play-state: var(--wow-model-fps); }
body.page-hub .wow-model .wm-rel[data-i="2"] .wm-dots { animation-delay: -.9s; }
body.page-hub .wow-model .wm-rel[data-i="3"] .wm-dots { animation-delay: -1.7s; }
body.page-hub .wow-model .wm-rel[data-i="4"] .wm-dots { animation-delay: -.4s; }
body.page-hub .wow-model .wm-rel[data-i="5"] .wm-dots { animation-delay: -1.3s; }
body.page-hub .wow-model .wm-rel[data-i="6"] .wm-dots { animation-delay: -2.1s; }
@keyframes wow-model-flow { to { stroke-dashoffset: -46; } }

/* Glyphs: placed by the script, so without it they never show */
body.page-hub .wow-model:not(.is-geo) .wm-mark { display: none; }
/* The filter chevron wears a ground-coloured halo (stroke painted under the fill), which
   parts it from its line; the flowing dots still pass over it. Opacities are the rest
   values: the entrance keyframes leave their end open, so they land on these. */
body.page-hub .wow-model .wm-chev {
  fill: var(--wow-model-cc); opacity: var(--wow-model-co);
  stroke: #f5f6f9; stroke-width: 2px; stroke-linejoin: round; paint-order: stroke;
  transition: fill var(--wow-model-tt) linear var(--wow-model-td), opacity var(--wow-model-tt) linear var(--wow-model-td);
}
body.page-hub .wow-model .wm-one {
  fill: var(--wow-model-gc); opacity: var(--wow-model-go); transform-box: fill-box; transform-origin: center;
  transition: fill var(--wow-model-tt) linear var(--wow-model-td), opacity var(--wow-model-tt) linear var(--wow-model-td);
}
body.page-hub .wow-model .wm-many {
  fill: none; stroke: var(--wow-model-gc); stroke-width: 1.6px; stroke-linecap: round; opacity: var(--wow-model-go);
  transform-box: fill-box; transform-origin: center;
  transition: stroke var(--wow-model-tt) linear var(--wow-model-td), opacity var(--wow-model-tt) linear var(--wow-model-td);
}
body.page-hub .wow-model .wm-rings { transition: opacity var(--wow-model-tt) linear var(--wow-model-td); }
body.page-hub .wow-model .wm-ring {
  fill: none; stroke: #dfe2f3; stroke-width: 1px; stroke-dasharray: 3 6;
  transform-box: view-box; transform-origin: var(--wow-model-ox, 50%) var(--wow-model-oy, 50%);
}

/* ---- Doors ---- */
/* The border reads a registered colour, so the arrival tint can animate under the house !important */
body.page-hub .wow-model .nexus a.door {
  --wow-model-edge: #e7e9ee; --wow-model-flash: #4f46e5; --wow-model-land: 449ms;
  border-color: var(--wow-model-edge) !important;
  transition: transform .2s, border-color .2s, box-shadow var(--wow-model-tt) linear var(--wow-model-td);
}
body.page-hub .wow-model .nexus a.door.free { --wow-model-flash: #0fb5ae; }
body.page-hub .wow-model .nexus a.door:is([data-i="2"], [data-i="5"]) { --wow-model-land: 730ms; }
body.page-hub .wow-model .nexus a.door:is(:hover, :focus-visible, :active) {
  --wow-model-edge: var(--acc);
  transition: transform .2s, border-color .2s, box-shadow .2s;
}
body.page-hub .wow-model .nexus a.door .door-icon {
  transition: filter var(--wow-model-tt) linear var(--wow-model-td), opacity var(--wow-model-tt) linear var(--wow-model-td);
}

/* ---- Cross-highlight. Keyboard focus lights a relationship at once ---- */
body.page-hub .wow-model .nexus:has(a.door:focus-visible) { --wow-model-td: 0s; --wow-model-tt: .2s; }
body.page-hub .wow-model .nexus:has(a.door:focus-visible) .wm-rel { opacity: .15; --wow-model-dps: paused; }
body.page-hub .wow-model .nexus:has(a.door:focus-visible) .wm-rings { opacity: .3; }
body.page-hub .wow-model .nexus:has(a.door:focus-visible) a.door:not(:focus-visible):not(:hover) { box-shadow: none; }
body.page-hub .wow-model .nexus:has(a.door:focus-visible) a.door:not(:focus-visible):not(:hover) .door-icon { filter: grayscale(1); opacity: .55; }
body.page-hub .wow-model .nexus:has(a.door[data-i="1"]:focus-visible) .wm-rel[data-i="1"],
body.page-hub .wow-model .nexus:has(a.door[data-i="2"]:focus-visible) .wm-rel[data-i="2"],
body.page-hub .wow-model .nexus:has(a.door[data-i="3"]:focus-visible) .wm-rel[data-i="3"],
body.page-hub .wow-model .nexus:has(a.door[data-i="4"]:focus-visible) .wm-rel[data-i="4"],
body.page-hub .wow-model .nexus:has(a.door[data-i="5"]:focus-visible) .wm-rel[data-i="5"],
body.page-hub .wow-model .nexus:has(a.door[data-i="6"]:focus-visible) .wm-rel[data-i="6"] {
  opacity: 1;
  --wow-model-stroke: var(--wow-model-hot); --wow-model-sw: 2px; --wow-model-lo: .45;
  --wow-model-gc: var(--wow-model-ink); --wow-model-go: 1; --wow-model-cc: var(--wow-model-hot); --wow-model-co: 1;
  --wow-model-do: 0; --wow-model-fo: 1; --wow-model-dps: running; --wow-model-fps: running;
}

/* Pointer: the same cross-highlight after a 60ms intent, plus the hemisphere preview.
   Never on touch, where nothing recedes. */
@media (hover: hover) and (pointer: fine) {
  body.page-hub .wow-model .nexus:has(a.door:hover) { --wow-model-td: .06s; --wow-model-tt: .2s; }
  body.page-hub .wow-model .nexus:has(a.door:hover) .wm-rel { opacity: .15; --wow-model-dps: paused; }
  body.page-hub .wow-model .nexus:has(a.door:hover) .wm-rings { opacity: .3; }
  body.page-hub .wow-model .nexus:has(a.door:hover) a.door:not(:hover):not(:focus-visible) { box-shadow: none; }
  body.page-hub .wow-model .nexus:has(a.door:hover) a.door:not(:hover):not(:focus-visible) .door-icon { filter: grayscale(1); opacity: .55; }
  body.page-hub .wow-model .nexus:has(a.door[data-i="1"]:hover) .wm-rel[data-i="1"],
  body.page-hub .wow-model .nexus:has(a.door[data-i="2"]:hover) .wm-rel[data-i="2"],
  body.page-hub .wow-model .nexus:has(a.door[data-i="3"]:hover) .wm-rel[data-i="3"],
  body.page-hub .wow-model .nexus:has(a.door[data-i="4"]:hover) .wm-rel[data-i="4"],
  body.page-hub .wow-model .nexus:has(a.door[data-i="5"]:hover) .wm-rel[data-i="5"],
  body.page-hub .wow-model .nexus:has(a.door[data-i="6"]:hover) .wm-rel[data-i="6"] {
    opacity: 1;
    --wow-model-stroke: var(--wow-model-hot); --wow-model-sw: 2px; --wow-model-lo: .45;
    --wow-model-gc: var(--wow-model-ink); --wow-model-go: 1; --wow-model-cc: var(--wow-model-hot); --wow-model-co: 1;
    --wow-model-do: 0; --wow-model-fo: 1; --wow-model-dps: running; --wow-model-fps: running;
  }

  /* Hemisphere preview, a two-value slicer: that half's relationships brighten, the other half recedes */
  body.page-hub .wow-model .hemi { pointer-events: auto; cursor: default; padding: .5rem .75rem; margin: -.5rem -.75rem; }
  body.page-hub .wow-model .nexus:has(.hemi:hover) { --wow-model-td: .06s; --wow-model-tt: .2s; }
  body.page-hub .wow-model .nexus:has(.hemi:hover) .wm-rings { opacity: .3; }
  body.page-hub .wow-model .nexus:has(.hemi.tim:hover) .wm-rel.tim,
  body.page-hub .wow-model .nexus:has(.hemi.free:hover) .wm-rel.free { --wow-model-lo: .8; --wow-model-go: 1; }
  body.page-hub .wow-model .nexus:has(.hemi.tim:hover) .wm-rel.free,
  body.page-hub .wow-model .nexus:has(.hemi.free:hover) .wm-rel.tim { opacity: .15; --wow-model-dps: paused; }
  body.page-hub .wow-model .nexus:has(.hemi.tim:hover) a.door.free,
  body.page-hub .wow-model .nexus:has(.hemi.free:hover) a.door:not(.free) { box-shadow: none; }
  body.page-hub .wow-model .nexus:has(.hemi.tim:hover) a.door.free .door-icon,
  body.page-hub .wow-model .nexus:has(.hemi.free:hover) a.door:not(.free) .door-icon { filter: grayscale(1); opacity: .55; }
}

/* ---- Phone: the trunk is the one shared relationship ---- */
body.page-hub .wow-model .wm-trunk,
body.page-hub .wow-model .wm-one-phone,
body.page-hub .wow-model .wm-star { display: none; }
@media (max-width: 860px) {
  body.page-hub .wow-model .nexus a.door[data-i="1"] { --wow-model-land: 230ms; }
  body.page-hub .wow-model .nexus a.door[data-i="2"] { --wow-model-land: 340ms; }
  body.page-hub .wow-model .nexus a.door[data-i="3"] { --wow-model-land: 450ms; }
  body.page-hub .wow-model .nexus a.door[data-i="4"] { --wow-model-land: 560ms; }
  body.page-hub .wow-model .nexus a.door[data-i="5"] { --wow-model-land: 670ms; }
  body.page-hub .wow-model .nexus a.door[data-i="6"] { --wow-model-land: 780ms; }
  /* A down chevron rides the trunk's tip: the box is the trunk, the chevron sits on its foot */
  body.page-hub .wow-model .wm-trunk {
    display: block; position: absolute; left: 13px; top: 36px; bottom: 34px; width: 2px;
    opacity: 0; pointer-events: none; color: #4f46e5;
  }
  body.page-hub .wow-model .wm-trunk svg { position: absolute; left: -3px; bottom: -5px; width: 8px; height: 8px; overflow: visible; }
  body.page-hub .wow-model .wm-trunk path { fill: currentColor; }
  /* The 1 sits just right of the trunk, under the orb */
  body.page-hub .wow-model .wm-one-phone {
    display: block; position: absolute; left: 19px; top: calc(50% + 26px); width: 6px; height: 10px; overflow: visible;
  }
  body.page-hub .wow-model .wm-one-phone path { fill: #7c6cf6; transform-box: fill-box; transform-origin: center; }
  /* A * caps each stub where it meets its card, in its hemisphere's tint */
  body.page-hub .wow-model .nexus a.door .wm-star {
    display: block; position: absolute; left: -13px; top: calc(50% - 4px); width: 10px; height: 10px;
    color: #a9a3f7; pointer-events: none; transition: color .18s ease;
  }
  body.page-hub .wow-model .nexus a.door.free .wm-star { color: #7fd6d1; }
  body.page-hub .wow-model .wm-star svg { display: block; width: 100%; height: 100%; overflow: visible; }
  body.page-hub .wow-model .wm-star path { fill: none; stroke: currentColor; stroke-width: 1.6px; stroke-linecap: round; }
  body.page-hub .wow-model .nexus a.door::before { transform-origin: 0 50%; transition: opacity .18s ease; }
  /* The reading line, the touch stand-in for hover: the Door crossing mid-screen is the active one */
  body.page-hub .wow-model .nexus a.door .door-icon { transition: background-color .18s ease, color .18s ease; }
  body.page-hub .wow-model .nexus a.door.is-read::before { opacity: 1; }
  body.page-hub .wow-model .nexus a.door.is-read .wm-star { color: var(--acc); }
  body.page-hub .wow-model .nexus a.door.is-read .door-icon { background: var(--acc); color: #fff; }
}

/* ---- The entrance: CSS only, keyed off .is-play. Every keyframe fills, so it
        completes with no further script. ---- */
@media (prefers-reduced-motion: no-preference) {
  body.page-hub .wow-model.is-play .wm-ring { animation: wow-model-ring .6s var(--wow-model-ease) .1s both; }
  body.page-hub .wow-model.is-play .wm-ring + .wm-ring { animation-delay: .18s; }
  /* Constant speed, 0.6px per ms: the duration is the line's own length */
  body.page-hub .wow-model.is-play .wm-line { animation: wow-model-grow var(--wow-model-dur) linear .15s both; }
  body.page-hub .wow-model.is-play .wm-tip {
    animation: wow-model-grow var(--wow-model-dur) linear .15s both, wow-model-spent .14s ease-out var(--wow-model-land) both;
  }
  /* Each relationship completes as one gesture keyed to its own contact: the * pops, the 1
     answers 60ms later, the filter chevron slides out, then the dots start to flow. */
  body.page-hub .wow-model.is-play .wm-many { animation: wow-model-pop .18s var(--wow-model-ease) var(--wow-model-land) both; }
  body.page-hub .wow-model.is-play .wm-one { animation: wow-model-one .16s var(--wow-model-ease) calc(var(--wow-model-land) + 60ms) both; }
  body.page-hub .wow-model.is-play .wm-chev { animation: wow-model-chev .42s var(--wow-model-ease) calc(var(--wow-model-land) + 120ms) both; }
  body.page-hub .wow-model.is-play .wm-flow { animation: wow-model-fade .4s ease-out calc(var(--wow-model-land) + 200ms) both; }
}
@media (prefers-reduced-motion: no-preference) and (min-width: 861px) {
  /* At contact the Door's border takes its accent for a beat, then hands back to the rest colour */
  body.page-hub .wow-model.is-play .nexus a.door { animation: wow-model-tint .7s linear var(--wow-model-land) backwards; }
}
@media (prefers-reduced-motion: no-preference) and (max-width: 860px) {
  body.page-hub .wow-model.is-play .nexus::before { animation: wow-model-trunk .7s linear .15s both; }
  body.page-hub .wow-model.is-play .wm-trunk { animation: wow-model-drop .7s linear .15s both, wow-model-spent .16s ease-out .85s both; }
  body.page-hub .wow-model.is-play .nexus a.door::before { animation: wow-model-stub .54s ease-out var(--wow-model-land) backwards; }
  body.page-hub .wow-model.is-play .nexus a.door .wm-star { animation: wow-model-pop .16s var(--wow-model-ease) calc(var(--wow-model-land) + 110ms) both; }
  body.page-hub .wow-model.is-play .wm-one-phone path { animation: wow-model-one .16s var(--wow-model-ease) .9s both; }
}
@keyframes wow-model-ring { from { transform: scale(.82); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@keyframes wow-model-grow { from { transform: scale(0); } to { transform: scale(1); } }
@keyframes wow-model-spent { from { opacity: 1; } to { opacity: 0; } }
/* No end keyframe on the glyphs: each lands on its own rest opacity, and on the lit one mid-highlight */
@keyframes wow-model-pop { from { transform: scale(.6); opacity: 0; } }
@keyframes wow-model-one { from { transform: scale(.8); opacity: 0; } }
@keyframes wow-model-chev { from { transform: translateX(-24px); opacity: 0; } }
@keyframes wow-model-fade { from { opacity: 0; } to { opacity: 1; } }
/* No 0% or 100% keyframe: both ends are the Door's live border colour, so a hover mid-tint lands cleanly */
@keyframes wow-model-tint {
  6% { --wow-model-edge: var(--wow-model-flash); }
  40% { --wow-model-edge: var(--wow-model-flash); animation-timing-function: ease-out; }
}
@keyframes wow-model-trunk { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes wow-model-drop {
  from { transform: translateY(-100%); opacity: 1; color: #4f46e5; }
  49% { color: #4f46e5; }
  53% { color: #0fb5ae; }
  to { transform: translateY(0); opacity: 1; color: #0fb5ae; }
}
@keyframes wow-model-stub { from { transform: scaleX(0); opacity: 1; } 26% { transform: scaleX(1); opacity: 1; } }

/* Held at the first frame until the webfont settles, and paused with the rest of the hub */
body.page-hub .wow-model.is-hold .wm-e,
body.page-hub .wow-model.is-hold .nexus a.door,
body.page-hub .wow-model.is-hold .nexus a.door::before,
body.page-hub .wow-model.is-hold .nexus::before,
body.page-hub .hub.is-paused .wow-model .nexus::before { animation-play-state: paused !important; }
/* A Door focused, pressed or clicked mid-entrance ends the entrance at once (never the loops) */
body.page-hub .wow-model.is-play .nexus:is(:focus-within, :active) .wm-e,
body.page-hub .wow-model.is-play .nexus:is(:focus-within, :active) a.door,
body.page-hub .wow-model.is-play .nexus:is(:focus-within, :active) a.door::before,
body.page-hub .wow-model.is-play .nexus:is(:focus-within, :active)::before { animation-duration: 1ms !important; animation-delay: 0s !important; }

/* ---- Reduced motion: the finished model from first paint, and a still halo ---- */
@media (prefers-reduced-motion: reduce) {
  body.page-hub .wow-model .wm-dots,
  body.page-hub .wow-model .wm-fast { animation: none; }
  body.page-hub .wow-model .core-orb::before { animation: none; transform: scale(1.1); opacity: .5; }
  body.page-hub .wow-model .core-orb::after { animation: none; opacity: 0; }
  body.page-hub .wow-model .nexus { --wow-model-td: 0s !important; --wow-model-tt: .1s !important; }
  body.page-hub .wow-model .nexus a.door,
  body.page-hub .wow-model .nexus a.door .door-icon,
  body.page-hub .wow-model .nexus a.door .door-go,
  body.page-hub .wow-model .nexus a.door::before,
  body.page-hub .wow-model .wm-star { transition-duration: .1s !important; transition-delay: 0s !important; }
  body.page-hub .wow-model .nexus a.door:is(:hover, :focus-visible) { transform: none; }
  body.page-hub .wow-model .nexus a.door .door-go { transform: none; }
}
