/* ============================================================
   hub.css — the Poly launcher
   One screen, one job: pick an app. Everything here is either the
   wordmark, the two cards, or the space around them.
   ============================================================ */

/* Every vertical measurement on this screen is fluid in dvh, not a value
   picked per breakpoint. A breakpoint is a STEP: with one compact size for
   everything under 640px tall, a 639px window and a 400px window render
   identically, so at best only one of them fits. These clamps scale the
   padding, the gaps, the wordmark and the artwork continuously with the real
   window height instead, so the hub is as large as the window allows and no
   larger. dvh rather than vh because a mobile browser's vh is the toolbar-
   hidden height, which would size the page for room it does not have yet. */
.hub {
  width: 100%;
  max-width: 860px;
  margin-inline: auto;
  padding: calc(var(--sat) + clamp(10px, 2.8dvh, 30px)) var(--s4)
           calc(var(--sab) + clamp(10px, 2.4dvh, 22px));
  display: flex;
  flex-direction: column;
  gap: clamp(11px, 3.2dvh, 30px);
  align-items: center;
}

/* ---------- Head ---------- */
.hub-head {
  text-align: center; display: flex; flex-direction: column;
  gap: clamp(2px, .8dvh, 8px); align-items: center;
}
/* The wordmark is the one place Poly gets to be big — and the head is the last
   thing allowed to shrink, because the mark and the tagline are what tell a
   first-time visitor what the two abstract card marks below them are. */
.hub-brand .logo { font-size: clamp(17px, 3.3dvh, 30px); }
.hub-brand .brand-name { font-size: clamp(19px, 3.8dvh, 34px); letter-spacing: 1px; }
.hub-tagline { color: var(--muted); font-size: clamp(10.5px, 1.7dvh, 14px); letter-spacing: .2px; }

/* ---------- Cards ----------
   Two equal columns on desktop, stacked on phones. Nothing else is ever
   added to this row: a third app gets its own column and the grid
   re-divides on its own. */
.hub-grid {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: clamp(8px, 1.9dvh, 16px);
}

.app-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: clamp(7px, 1.8dvh, 16px);
  padding: clamp(9px, 1.9dvh, 22px) var(--s4) clamp(9px, 1.6dvh, 16px);
  background: linear-gradient(180deg, var(--card), var(--bg-soft));
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  color: inherit;
  text-decoration: none;
  overflow: hidden;
  transition: border-color .2s, transform .12s ease, box-shadow .2s;
}
/* A pool of accent light bleeding up from behind the card on hover — the
   same brand glow the app backgrounds use, so the hover reads as "this is
   the thing that lights up". */
.app-card::after {
  content: '';
  position: absolute; inset: -40% -10% auto;
  height: 120%;
  background: radial-gradient(50% 50% at 50% 50%, var(--accent-soft), transparent 70%);
  opacity: 0;
  pointer-events: none;
  transition: opacity .25s;
}
.app-card:hover, .app-card:focus-visible { border-color: var(--accent); transform: translateY(-2px); }
.app-card:hover::after, .app-card:focus-visible::after { opacity: 1; }
.app-card:active { transform: translateY(0); box-shadow: var(--shadow-sm); }

.card-text { display: flex; flex-direction: column; gap: clamp(2px, .9dvh, 8px); }
.card-text h2 { font-size: clamp(15px, 2.4dvh, 20px); font-weight: 700; letter-spacing: .3px; }
.card-text p { color: var(--muted); font-size: clamp(11px, 1.6dvh, 13px); line-height: 1.5; max-width: 34ch; }

.card-go {
  position: absolute; top: var(--s4); right: var(--s4);
  color: var(--muted-2); font-size: 18px; line-height: 1;
  transition: color .2s, transform .2s;
}
.app-card:hover .card-go, .app-card:focus-visible .card-go { color: var(--accent); transform: translateX(3px); }

/* ---------- Card artwork ----------
   Both marks are the real thing at small scale: same colours, same
   geometry, same rhythm the app runs. They keep animating because a
   still frame of either would read as a screenshot, not a rhythm. */
/* The artwork is the flexible one: it is the part with no words in it, so it
   gives up height first and keeps giving it up all the way down. The mark
   holds its 16:9 proportions and simply centres in whatever frame is left. */
.card-art {
  background: var(--bg);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  padding: clamp(5px, 1.1dvh, 12px);
}
.card-art svg {
  display: block; margin: 0 auto;
  width: 100%; height: auto;
  max-height: min(220px, 26dvh);
}

/* --- metronome: 4 : 3 : 5 layers --- */
.mm-dots circle { fill: var(--accent); opacity: .28; }
.lane-b .mm-dots circle { fill: var(--ghost); }
.lane-c .mm-dots circle { fill: var(--good); }

.mm-head { fill: none; stroke: var(--accent); stroke-width: 2; }
.lane-b .mm-head { stroke: var(--ghost); }
.lane-c .mm-head { stroke: var(--good); }

/* One shared cycle, stepped into 4, 3 and 5 — so the three lanes line up
   only on the downbeat, exactly as they do in the app. */
.mm-head { animation-duration: 2.4s; animation-iteration-count: infinite; }
.n4 { animation-name: mm4; animation-timing-function: steps(4, end); }
.n3 { animation-name: mm3; animation-timing-function: steps(3, end); }
.n5 { animation-name: mm5; animation-timing-function: steps(5, end); }
@keyframes mm4 { from { transform: translateX(0) } to { transform: translateX(160px) } }
@keyframes mm3 { from { transform: translateX(0) } to { transform: translateX(180px) } }
@keyframes mm5 { from { transform: translateX(0) } to { transform: translateX(150px) } }

/* --- trainer: 3 against 4, flowing to the playhead --- */
/* the same two voice colours the trainer itself uses — the saturated pair,
   so the card previews how much louder that app looks than the metronome */
.pt-a circle { fill: #35d6ff; }
.pt-b circle { fill: #b167ff; }
.pt-band { fill: rgba(255, 255, 255, .04); }
.pt-head { stroke: var(--text); stroke-width: 1.5; opacity: .75; }
/* One cycle is 96 units wide; the note rows repeat every 96, so sliding by
   exactly that and looping is seamless. */
.pt-flow { animation: ptFlow 2.4s linear infinite; }
@keyframes ptFlow { from { transform: translateX(0) } to { transform: translateX(-96px) } }

/* ---------- Foot ---------- */
.hub-foot { color: var(--muted-2); font-size: 11.5px; letter-spacing: .2px; }
.hub-foot kbd {
  font-family: var(--mono); font-size: 10.5px;
  background: var(--bg-soft); border: 1px solid var(--line);
  border-radius: 5px; padding: 2px 6px; margin: 0 1px;
  color: var(--muted);
}

/* ---------- Responsive ----------
   Only what genuinely depends on the LAYOUT lives here now. Everything that
   depends on how much height there is scales fluidly above, so there is no
   step where the page stops fitting. */
@media (max-width: 560px) {
  /* Stacked, so there are two artworks in the column instead of one row of
     them — each gets about half the share of the height it had side by side. */
  .card-art svg { max-height: clamp(34px, 13.5dvh, 150px); }
  /* physical keyboards are the exception on touch; the hint is noise there */
  .hub-foot { display: none; }
}

/* Below this the clamps have all bottomed out and the artwork is the only
   thing left between the words and an overflow. The marks are decorative —
   the names and the descriptions are not — so here the artwork goes rather
   than the page scrolling. Measured, not guessed: with the artwork shown and
   every clamp at its minimum the hub bottoms out at ~370px tall, so 380 is
   the last height that still fits comfortably with the marks in place. */
@media (max-height: 380px) and (max-width: 560px) {
  .card-art { display: none; }
}

/* the mark is the affordance on touch — there is no hover to reveal */
@media (hover: none) {
  .card-go { color: var(--accent); }
}
