/* =========================================
   home.css - Home view only
   Extracted verbatim from the pre-refactor style.css (QUO-34, OCW-5).
   3D globe container + the QUO-10 hero, including the hero half of the
   <=768px block and the prefers-reduced-motion opt-out. Source order preserved.
   ========================================= */

/* 3D Globe Container (Home View) */
.globe-container {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  z-index: 1;
}

/* =========================================
   Home Hero (QUO-10 / OCW-3)
   Desktop: hero text occupies the left half, globe the right half.
   Mobile: hero stacks above the globe (handled in the 768px block).
   COLOUR NOTE: every colour below is an EXISTING custom property. No new
   colour values are introduced, per the repo's palette rule.
   ========================================= */
.hero {
  position: absolute;
  inset: 0 50% 0 0; /* left half; globe takes the right half via .has-hero below */
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 0 clamp(20px, 2.5vw, 40px) 0 clamp(24px, 4vw, 64px);
  /* The hero's empty space must not steal pointer events from the globe, which is
     draggable. Only the actions row re-enables them. */
  pointer-events: none;
}

.hero-inner {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 20px;
  width: 100%;
  max-width: 520px;
}

.hero-headline {
  font-family: "Newsreader", Georgia, serif; /* editorial serif, as used in sidebar nav */
  font-size: clamp(34px, 4.2vw, 56px);
  font-weight: 500;
  line-height: 1.08;
  letter-spacing: -0.5px;
  color: var(--sidebar-active-text);
  text-wrap: balance;
}

.hero-subline {
  font-family: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: clamp(14px, 1.1vw, 17px);
  font-weight: 400;
  line-height: 1.6;
  color: var(--sidebar-text);
  max-width: 46ch;
}

/* Marks copy that is NOT approved. Visually distinct on purpose so unapproved
   wording can never be mistaken for final copy. Remove with the real copy. */
.is-placeholder {
  opacity: 0.5;
  font-style: italic;
}

.hero-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  pointer-events: auto; /* re-enable interaction for the CTA only */
}

.hero-cta {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-family: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.2px;
  text-decoration: none;
  color: var(--sidebar-active-text);
  background: var(--sidebar-hover-bg);
  border: 1px solid var(--border-content-frame);
  padding: 11px 20px;
  border-radius: 999px;
  /* QUO-10 fast-follow: 44px accessibility minimum. align-items:center above keeps
     the label and glyph centred as the box grows the ~2px from its emergent 42px. */
  min-height: 44px;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}

.hero-cta:hover {
  background: var(--sidebar-active-bg);
  border-color: var(--toggle-track-hover-border);
  transform: translateY(-1px);
}

.hero-cta:focus-visible {
  outline: 2px solid var(--nav-icon-hover-color);
  outline-offset: 3px;
}

.hero-cta svg {
  flex-shrink: 0;
}

/* With the hero present, the globe yields the left half to it (desktop).
   width/height must be reset to auto: the base .globe-container rule sets
   width:100%/height:100%, which would otherwise beat the `right`/`bottom` offsets
   and leave the globe overflowing off-screen instead of filling its half. */
.has-hero .globe-container {
  inset: 0 0 0 50%;
  width: auto;
  height: auto;
}

/* Restrained entrance, matching the island/globe register: a short rise and fade,
   staggered. Nothing flashy, and fully disabled for reduced-motion users. */
@keyframes heroRise {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.hero-headline,
.hero-subline,
.hero-actions {
  animation: heroRise 0.7s cubic-bezier(0.22, 0.61, 0.36, 1) both;
}

.hero-subline {
  animation-delay: 0.1s;
}

.hero-actions {
  animation-delay: 0.2s;
}

@media (prefers-reduced-motion: reduce) {
  .hero-headline,
  .hero-subline,
  .hero-actions {
    animation: none;
  }

  .hero-cta:hover {
    transform: none;
  }
}

/* =========================================
   Ecosystem orbit (QUO-11 / OCW-4)

   The four apps orbiting the globe as one connected system. Every colour
   below is an EXISTING custom property - no new colour values, monochrome
   only, per the palette rule.

   GEOMETRY NOTE: the layer is positioned to match .globe-container exactly,
   including the .has-hero half-frame and the mobile split, so the orbit
   shares the globe's centre by construction rather than by a magic offset.
   Node x/y, depth scale, opacity, z-order and the arc paths are all written
   per frame by assets/js/orbit.js, driven by the globe's real yaw.

   MEASURED against the live globe before choosing radii: the rendered sphere
   is R=291.5px inside a 709x831 canvas on desktop, and R=145.5px inside
   372x415 on mobile. The orbit's horizontal semi-axis is 0.41 x width
   (290.7px desktop), so nodes graze the sphere's limb at the sides and cross
   its face front and back - reading as a true orbit rather than a flat ring.
   ========================================= */
.orbit-layer {
  /* ONE source of truth for how much a tapped card grows. CSS applies it, JS
     reads it back with getComputedStyle, so the layout maths and the visual
     scale can never drift. Mobile gets less - 1.6 in a 372px frame would
     swallow the globe entirely. */
  --orbit-expand: 1.6;
  position: absolute;
  inset: 0;
  z-index: 3; /* above the globe canvas (z-index 1), below the hero text (2)? no - see below */
  pointer-events: none; /* only the nodes and the peek card opt back in */
  overflow: hidden;
}

/* The hero owns the left half on desktop, so the orbit follows the globe into
   the right half. Mirrors .has-hero .globe-container exactly. */
.has-hero .orbit-layer {
  inset: 0 0 0 50%;
}

.orbit-arcs {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* Static connector: card -> globe. Replaces the five inter-node relationship
   arcs from the orbit version, which described a cycle that only made sense
   while the cards travelled through one. This states "this app belongs to this
   globe" and nothing more. Deliberately quiet - it must never compete with the
   screenshots, which are the thing being shown off. */
.orbit-link {
  stroke: var(--border-content-frame);
  stroke-width: 1;
  opacity: 0.5;
  fill: none;
}

/* The pin at the globe end. A telemetry marker, not a moving body. It breathes
   on its own slow cycle, completely independent of the globe's rotation - which
   is the whole point of this revision. */
.orbit-pin {
  fill: var(--text-primary);
  opacity: 0.55;
  animation: orbit-pin-breathe 3.6s ease-in-out infinite;
}

@keyframes orbit-pin-breathe {
  0%, 100% { opacity: 0.32; }
  50% { opacity: 0.7; }
}

.orbit-node {
  position: absolute;
  top: 0;
  left: 0;
  /* Revised again 2026-09-19. Matthew: "make them bigger, make them brighter...
     show off the application." 48px was too small for a phone screenshot to
     read as anything - that was the real cause of "they are not brighter",
     along with the dimming filter and opacity ramp the orbit version applied.
     96px wide at the native 576x1280 aspect => 96 x 213, double the previous
     size. Static placement is what affords it: a travelling card could not be
     this big AND clear the globe, because a closed path is limited by its
     narrowest direction - at 96x213 a clearing orbit needs a radius of 345px
     inside a frame that only allows 307px. A parked card uses the corner
     diagonal instead, which has 546px to play with. */
  width: 96px;
  /* Hit-area padded to the 44x44 minimum even though the visual is smaller
     (Quomy's addition) - without it, tap-to-peek is fiddly on a real phone. */
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  background: none;
  border: 0;
  cursor: pointer;
  pointer-events: auto;
  /* JS writes `left`/`top` only. CSS owns `transform` outright - the idle
     float, the hover lift and the tap scale all live here. If JS also wrote
     transform on every resize it would stamp over the running animation. */
  will-change: transform;
  /* Each card breathes on its OWN cycle, with its own duration and delay, so
     the four are never in lockstep and none of it is tied to the globe's spin.
     This is the "own small idle motion, fully independent of the globe". */
  animation: orbit-card-float 7.5s ease-in-out infinite;
  transform-origin: 50% 50%;
  /* Spring easing, matching the site's transition tier. Only opacity and the
     scale-on-tap are transitioned - never the orbital transform, which must
     stay frame-exact or it would lag the globe. */
  transition: opacity 0.24s cubic-bezier(0.16, 1, 0.3, 1);
}

/* Four different periods and offsets, chosen as non-multiples so they do not
   visibly re-sync. */
.orbit-node[data-role='customer'] { animation-duration: 7.4s; animation-delay: 0s; }
.orbit-node[data-role='restaurant'] { animation-duration: 8.6s; animation-delay: -1.9s; }
.orbit-node[data-role='rider'] { animation-duration: 6.9s; animation-delay: -3.4s; }
.orbit-node[data-role='business'] { animation-duration: 9.3s; animation-delay: -5.1s; }

@keyframes orbit-card-float {
  0%, 100% { transform: translateY(-4px); }
  50% { transform: translateY(4px); }
}

@media (prefers-reduced-motion: reduce) {
  .orbit-node { animation: none; }
  .orbit-pin { animation: none; }
}

/* Position (left/top) transitions so the inward shift on tap is smooth. It is
   NOT transform, because transform is owned by the float animation - see the
   note on .orbit-node. Transitioning left/top is cheaper than it looks here:
   it happens once per tap on four elements, never per frame. */
.orbit-node {
  transition: opacity 0.24s cubic-bezier(0.16, 1, 0.3, 1),
              left 0.42s cubic-bezier(0.16, 1, 0.3, 1),
              top 0.42s cubic-bezier(0.16, 1, 0.3, 1);
}

.orbit-node-shot {
  display: block;
  width: 96px;
  height: 213px;
  border-radius: 10px;
  overflow: hidden;
  /* A hairline edge so a dark screenshot still reads as a device against the
     dark globe. Backgrounds on the screenshots themselves are untouched. */
  border: 1px solid var(--border-content-frame);
  background: var(--sidebar-hover-bg);
  transition: border-color 0.24s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
}

.orbit-node-shot img {
  display: block;
  width: 100%;
  height: 100%;
  /* cover + centre: fills the 9:20 frame with no squashing, so the screenshot
     is never distorted. Matthew's "the writing must not distort it" applies to
     text placement; this is the image half of the same promise. */
  object-fit: cover;
  object-position: center top;
  user-select: none;
}

/* Label rides OUTSIDE the image bounds, below it - never overlaid, so no text
   ever sits on a screenshot. */
.orbit-node-label {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
  font-family: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.3px;
  color: var(--sidebar-text);
  transition: color 0.24s cubic-bezier(0.16, 1, 0.3, 1);
}

.orbit-node-icon {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
}

/* Desktop affordance hint: a micro scale-up before any tap, no card yet. */
@media (hover: hover) {
  .orbit-node:hover .orbit-node-shot {
    transform: scale(1.06);
    border-color: var(--toggle-track-hover-border);
  }

  .orbit-node:hover .orbit-node-label {
    color: var(--sidebar-active-text);
  }
}

.orbit-node:focus-visible {
  outline: 2px solid var(--nav-icon-hover-color);
  outline-offset: 4px;
  border-radius: 9px;
}

/* QUO-11 Mechanism 1 - "spread outside, open wide". The scale lives on the
   SHOT, not the node, precisely so it never collides with the float animation
   on the node's own transform. JS feeds the same 1.6 into layout() so the card
   also moves inward far enough that the bigger footprint cannot clip. */
/* Growth direction, per quadrant. Each card scales from its OUTERMOST corner,
   so it expands inward toward the globe and can never reach the frame edge.
   This replaced a repositioning approach that clipped 3 of 4 cards, because an
   element's box does not change when CSS scales it - the maths and the render
   disagreed. Fixing the direction removes the whole class of bug. */
.orbit-node[data-role='customer'] .orbit-node-shot { transform-origin: 0 0; }
.orbit-node[data-role='restaurant'] .orbit-node-shot { transform-origin: 100% 0; }
.orbit-node[data-role='rider'] .orbit-node-shot { transform-origin: 100% 100%; }
.orbit-node[data-role='business'] .orbit-node-shot { transform-origin: 0 100%; }

.orbit-node.is-active .orbit-node-shot {
  transform: scale(var(--orbit-expand, 1.6));
  border-color: var(--text-primary);
  z-index: 2;
}

/* The other three recede rather than vanish, so the ecosystem still reads as
   four connected apps while one is being examined. */
.orbit-layer.has-active .orbit-node:not(.is-active) .orbit-node-shot {
  transform: scale(0.94);
}

.orbit-node.is-active .orbit-node-label {
  color: var(--sidebar-active-text);
}

/* Non-active nodes recede while one is being read. */
.orbit-layer.has-active .orbit-node:not(.is-active) {
  opacity: 0.3;
}

/* Reduced motion: the orbit holds a static arrangement. orbit.js checks the
   same query and stops advancing, so this is belt and braces. */
@media (prefers-reduced-motion: reduce) {
  .orbit-node,
  .orbit-node-shot,

.orbit-node:hover .orbit-node-shot,
  .orbit-node.is-active .orbit-node-shot {
    transform: none;
  }
}
/* =========================================
   5. Mobile Responsiveness (<= 768px)
   ========================================= */
@media (max-width: 768px) {

  .orbit-layer {
    --orbit-expand: 1.4;
  }

  /* Hero stacks ABOVE the globe on mobile (QUO-10). Splitting the frame keeps the
     text clear of the globe instead of overlaying it, so nothing competes. */
  .hero {
    inset: 0 0 54% 0;
    align-items: flex-end;
    justify-content: center;
    padding: 0 20px 8px;
  }

  .hero-inner {
    align-items: center;
    text-align: center;
    gap: 14px;
    max-width: 100%;
  }

  .hero-headline {
    font-size: clamp(27px, 8vw, 36px);
  }

  .hero-subline {
    font-size: 14px;
    max-width: 38ch;
  }

  .has-hero .globe-container {
    inset: 47% 0 0 0;
    width: auto;
    height: auto;
  }

  /* Orbit follows the globe into the lower split, same insets. */
  .has-hero .orbit-layer {
    inset: 47% 0 0 0;
  }

  /* Revised with the desktop sizing: 34px wide, native aspect => 34 x 76.
     Below the 44px hit floor, so min-width/min-height on .orbit-node carry the
     tap target here - that is exactly why they are there. */
  .orbit-node {
    width: 64px;
    gap: 4px;
  }

  .orbit-node-shot {
    width: 64px;
    height: 142px;
    border-radius: 4px;
  }

  .orbit-node-label {
    font-size: 9.5px;
    gap: 4px;
  }

  .orbit-node-icon {
    width: 10px;
    height: 10px;
  }
}

/* ============================================================
   QUO-11 Mechanism 1 - hero copy crossfade.

   Scope is exactly what Quomy granted: the swap MECHANISM only. The shipped
   QUO-22 default copy is untouched in views/home.html and is read out of the
   DOM at boot, so this file never duplicates it.

   Only the two text elements fade. The CTA is deliberately NOT inside the
   transition, and JS reserves the tallest variant's height on both text
   elements so a longer subline can never reflow and shove the button down.
   Matthew was explicit: "You're going to leave that CTA over there."
   ============================================================ */
.hero-headline,
.hero-subline {
  transition: opacity 0.17s ease;
}

.hero.is-swapping .hero-headline,
.hero.is-swapping .hero-subline {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .hero-headline,
  .hero-subline {
    transition: none;
  }
  .orbit-node {
    transition: none;
  }
}


/* ============================================================
   QUO-37 Mechanism 2 - scroll-driven four-app sequence
   ============================================================

   The track is an empty spacer that exists purely to create scroll distance
   while .top-layer is pinned. It is a SIBLING of .top-layer inside
   .page-container - it cannot live inside views/home.html, because that markup
   sits below two overflow:hidden layers where sticky does not function (see the
   note on .page-container.has-scroll-sequence in components.css). Placement
   approved by Quomy on QUO-37.

   HEIGHT: QUO-37 fixed the total added scroll at 240vh for the whole sequence.
   dvh is used where supported rather than porting vh 1:1, because mobile browser
   chrome collapsing mid-scroll changes what vh resolves to and would shift the
   step boundaries under the user's finger. dvh tracks the real dynamic viewport,
   so the step maths stays stable while the address bar moves. */
.orbit-scroll-track {
  width: 100%;
  height: 240vh;
  pointer-events: none;   /* a spacer must never intercept anything */
}

@supports (height: 100dvh) {
  .orbit-scroll-track {
    height: 240dvh;
  }
}

/* 1-of-4 progress indicator. Mandatory per QUO-37 risk 2, so a visitor can see
   the sequence is finite rather than an endless pin. Monochrome only - site DNA
   forbids introducing any colour. */
.orbit-progress {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 22px;
  display: flex;
  align-items: center;
  gap: 10px;
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: none;
  z-index: 11;
}

.orbit-progress.is-visible {
  opacity: 1;
}

.orbit-progress-dots {
  display: flex;
  gap: 6px;
}

.orbit-progress-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--text-secondary);
  opacity: 0.3;
  transition: opacity 0.25s ease, transform 0.25s ease;
}

.orbit-progress-dot.is-on {
  opacity: 0.95;
  transform: scale(1.35);
}

.orbit-progress-label {
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-secondary);
  opacity: 0.65;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 768px) {
  .orbit-progress {
    bottom: 14px;
  }
  .orbit-progress-label {
    font-size: 9.5px;
  }
}

/* =========================================================================
   QUO-37 - Mechanism 2: scroll track + progress indicator
   ========================================================================= */

/* Pure scroll distance for the four-step sequence. 60dvh per step x 4 = 240dvh.
   dvh, not vh, DELIBERATELY: on mobile the address bar collapses and expands
   during a scroll, which changes the actual viewport height mid-gesture. vh is
   frozen to the largest viewport, so the track would be longer than the visible
   scroll and the last step would never be reached on some mobile browsers. dvh
   tracks the live height. vh first as a fallback for engines without dvh. */
.scroll-sequence-track {
  width: 100%;
  /* Zero by default. The height arrives only with .has-scroll-sequence, so the
     About view never inherits the sequence's scroll distance even though the
     element is present in the shared markup. */
  height: 0;
  pointer-events: none;
}

.page-container.has-scroll-sequence .scroll-sequence-track {
  height: 240vh;
  height: 240dvh;
}

/* 1-of-4 progress, mandatory per the spec so a visitor can see there is a
   finite end rather than an unbounded pinned section. Monochrome, borrowing
   existing theme tokens only - no new colours, per site DNA. */
.seq-progress {
  position: absolute;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 13px;
  border-radius: 999px;
  background: var(--sidebar-hover-bg);
  border: 1px solid var(--border-content-frame);
  backdrop-filter: blur(9px);
  -webkit-backdrop-filter: blur(9px);
  opacity: 0;
  transition: opacity 0.3s ease;
  z-index: 6;
}

.page-container.has-scroll-sequence .seq-progress {
  opacity: 1;
}

.seq-progress.is-done {
  opacity: 0.35;
}

.seq-dots {
  display: flex;
  align-items: center;
  gap: 5px;
}

.seq-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.28;
  transition: opacity 0.28s ease, transform 0.28s ease;
}

.seq-dot.is-on {
  opacity: 1;
  transform: scale(1.25);
}

.seq-count {
  font-size: 11px;
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
  opacity: 0.75;
}

@media (max-width: 768px) {
  .page-container.has-scroll-sequence .scroll-sequence-track {
    /* Shorter per step on mobile: a phone scroll gesture covers less distance
       per flick, so the desktop 240dvh felt like a wall. Verified empirically
       rather than ported 1:1, per spec. dvh matters most here - the address bar
       collapsing mid-scroll changes the real viewport height, and frozen vh
       would leave the last step unreachable. */
    height: 200vh;
    height: 200dvh;
  }

  .seq-progress {
    bottom: 14px;
    padding: 6px 11px;
  }
}

/* prefers-reduced-motion: the whole mechanism is torn down in JS (no track, no
   pin, no indicator). This is belt and braces in case the class ever lingers. */
@media (prefers-reduced-motion: reduce) {
  .scroll-sequence-track { display: none; }
  .seq-progress { display: none; }
  /* specificity must beat .page-container.has-scroll-sequence .top-layer, in
     case the class lingers before JS tears it down on first paint */
  .page-container.has-scroll-sequence .top-layer { position: relative; }
  .page-container.has-scroll-sequence .scroll-sequence-track { height: 0; }
}

/* =========================================================
   QUO-12 / OCW-9: Home mid-page sections
   Content inside the existing .dark-section / .effect-layer shells
   (those classes live in components.css and are not edited). Monochrome,
   base.css tokens only, spring easing, no literal hex.
   ========================================================= */
.home-block {
  width: min(100%, 1080px);
  margin: 0 auto;
  padding: clamp(40px, 7vh, 72px) 0;
  box-sizing: border-box;
}

.home-block + .home-block { border-top: 1px solid var(--border-content-frame); }

.home-eyebrow {
  display: inline-block;
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sidebar-text);
  margin-bottom: 14px;
}

.home-title,
.home-closing-title {
  margin: 0 0 14px;
  font-size: clamp(1.6rem, 3.2vw, 2.3rem);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--sidebar-active-text);
}

.home-lede {
  max-width: 60ch;
  margin: 0 0 28px;
  font-size: 1rem;
  line-height: 1.6;
  color: var(--sidebar-text);
}

.home-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 24px;
}

.home-stat,
.home-role-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 22px;
  border: 1px solid var(--border-content-frame);
  border-radius: 18px;
  background: var(--modal-close-bg);
}

.home-stat-value {
  font-size: clamp(2.2rem, 5vw, 3.2rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--sidebar-active-text);
}

.home-stat-label { font-size: 0.95rem; line-height: 1.45; color: var(--sidebar-text); }
.home-stat-source { margin-top: auto; padding-top: 6px; font-size: 0.72rem; letter-spacing: 0.04em; color: var(--sidebar-text); opacity: 0.75; }

.home-role-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

.home-role-card {
  transition: border-color 0.25s cubic-bezier(0.16, 1, 0.3, 1), transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}

.home-role-card:hover { border-color: var(--modal-dialog-border); transform: translateY(-2px); }

.home-role-icon { width: 22px; height: 22px; color: var(--sidebar-active-text); margin-bottom: 4px; }

.home-role-name {
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sidebar-text);
}

.home-role-headline {
  margin: 0 0 10px;
  font-size: 1.08rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.012em;
  color: var(--sidebar-active-text);
}

.home-cta-link,
.home-text-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  align-self: flex-start;
  margin-top: auto;
  min-height: 44px;
  font: inherit;
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--sidebar-active-text);
  cursor: pointer;
  transition: background-color 0.22s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.22s cubic-bezier(0.16, 1, 0.3, 1), gap 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}

.home-cta-link {
  padding: 0 18px;
  border: 1px solid var(--modal-dialog-border);
  border-radius: 999px;
  background: var(--nav-icon-hover-bg);
}

.home-cta-link:hover { border-color: var(--sidebar-active-text); gap: 11px; }

.home-text-link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--sidebar-text);
}

.home-text-link:hover { color: var(--sidebar-active-text); gap: 11px; }

.home-cta-link:focus-visible,
.home-text-link:focus-visible { outline: 2px solid var(--sidebar-active-text); outline-offset: 3px; border-radius: 999px; }

.home-link-arrow { flex: 0 0 auto; }

.home-closing { text-align: center; }
.home-closing .home-lede { margin-left: auto; margin-right: auto; }
.home-closing .home-cta-link { align-self: center; }

@media (max-width: 1024px) {
  .home-role-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 600px) {
  .home-stats,
  .home-role-grid { grid-template-columns: 1fr; }
  .home-block { padding: 36px 0; }
}

@media (prefers-reduced-motion: reduce) {
  .home-role-card,
  .home-cta-link,
  .home-text-link { transition: none; }
  .home-role-card:hover { transform: none; }
}
