/* ==========================================================================
   Aster Studio
   Design language: quiet, precise, physical. The artwork carries the colour,
   the interface is glass laid over it. Aster violet is the one accent, used
   once per surface. Gold and periwinkle belong to the scene lights only.
   ========================================================================== */

:root {
  --sans: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
          "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* Ink and paper */
  --ink:        #17141F;   /* headings */
  --ink-soft:   #4A4458;   /* body copy */
  --ink-faint:  #8A8399;   /* meta, eyebrows */
  --paper:      #FBF9FC;
  --paper-warm: #F6F2F8;

  /* The accent, once per surface. Aster violet. */
  --accent:      #6B47E8;
  --accent-deep: #4A2CBE;
  --accent-tint: #EDE7FE;

  /* Scene lights. These live on the artwork, never in the page chrome. */
  --light-day:   #FFC53D;
  --light-night: #A9C8FF;

  /* Liquid glass, light. */
  --glass-bg:     rgba(255, 255, 255, 0.58);
  --glass-edge:   rgba(255, 255, 255, 0.72);
  --glass-spec:   inset 0 1px 0 rgba(255, 255, 255, 0.9);
  --glass-shade:  0 10px 34px rgba(20, 12, 48, 0.16), 0 2px 8px rgba(20, 12, 48, 0.08);
  --glass-ink:    #221D31;
  --glass-ink-soft: rgba(34, 29, 49, 0.66);

  --blur: blur(22px) saturate(180%);

  --fade: 1100ms;
  --ease: cubic-bezier(0.32, 0.72, 0, 1);   /* springy, no overshoot */
  --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
}

/* Liquid glass, dark. Scoped to the hero at night, and to the recognition
   band, which is the one stretch of the page that is always night. */
.hero[data-mode="night"],
.recognition {
  --glass-bg:     rgba(18, 22, 44, 0.52);
  --glass-edge:   rgba(255, 255, 255, 0.18);
  --glass-spec:   inset 0 1px 0 rgba(255, 255, 255, 0.16);
  --glass-shade:  0 12px 40px rgba(0, 0, 0, 0.42), 0 2px 8px rgba(0, 0, 0, 0.24);
  --glass-ink:    #F2F4FF;
  --glass-ink-soft: rgba(242, 244, 255, 0.7);
  --accent:       #9E86FF;
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.62;
  color: var(--ink-soft);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img { display: block; max-width: 100%; }

a { color: inherit; }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.wrap {
  width: 100%;
  max-width: 1080px;
  margin: 0 auto;
  padding-inline: clamp(20px, 5vw, 48px);
}

/* ---------- the glass primitive ---------- */

.glass {
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
  border: 1px solid var(--glass-edge);
  box-shadow: var(--glass-shade), var(--glass-spec);
  color: var(--glass-ink);
}

/* ==========================================================================
   Header
   ========================================================================== */

/* A transparent bar across the full width of the hero, so it must not take
   the pointer: a light near the top of the scene sits under it, and half of
   the telescope does on a short window. Only the two controls in it are
   pressable. */
.site-header {
  pointer-events: none;
  position: absolute;
  inset: 0 0 auto 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: clamp(14px, 2.2vw, 22px) clamp(16px, 4vw, 32px);
  padding-top: max(clamp(14px, 2.2vw, 22px), env(safe-area-inset-top));
}

.brand {
  pointer-events: auto;
  display: inline-flex;
  align-items: center;
  gap: 11px;
  text-decoration: none;
  color: #fff;
  font-weight: 640;
  font-size: 1.02rem;
  letter-spacing: -0.011em;
  text-shadow: 0 1px 14px rgba(0, 0, 0, 0.4);
}

.brand img {
  width: 34px; height: 34px;
  border-radius: 10px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}

.btn-music {
  pointer-events: auto;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  height: 40px;
  padding: 0 16px;
  border-radius: 999px;
  font: inherit;
  font-size: 0.86rem;
  font-weight: 570;
  letter-spacing: -0.005em;
  cursor: pointer;
  transition: background var(--fade) var(--ease-soft),
              border-color var(--fade) var(--ease-soft),
              color var(--fade) var(--ease-soft),
              transform 220ms var(--ease);
}

.btn-music:active { transform: scale(0.96); }

/* three bars that stand still when off and animate when on */
.eq {
  display: flex;
  align-items: flex-end;
  gap: 2.5px;
  height: 13px;
}

/* at rest the bars sit at different heights so the control still reads as
   sound rather than as a three dot menu */
.eq i {
  display: block;
  width: 2.5px;
  border-radius: 2px;
  background: currentColor;
  opacity: 0.55;
  transition: height 240ms var(--ease), opacity 240ms var(--ease);
}

.eq i:nth-child(1) { height: 6px; }
.eq i:nth-child(2) { height: 12px; }
.eq i:nth-child(3) { height: 8px; }

.btn-music[aria-pressed="true"] { color: var(--accent); }
.btn-music[aria-pressed="true"] .eq i {
  opacity: 1;
  animation: eq 1s ease-in-out infinite;
}
.btn-music[aria-pressed="true"] .eq i:nth-child(2) { animation-delay: 0.18s; }
.btn-music[aria-pressed="true"] .eq i:nth-child(3) { animation-delay: 0.36s; }

@keyframes eq {
  0%, 100% { height: 4px; }
  50%      { height: 13px; }
}

/* ==========================================================================
   Hero
   ========================================================================== */

.hero {
  /* The artwork, scaled to cover the viewport at any shape. Cropping it is
     always right and shrinking it never is: an edge, or a band of sky around
     it, is what stops the page reading as one piece. */
  --cover-w: max(100vw, calc(100svh * 1672 / 941));
  --cover-h: calc(var(--cover-w) * 941 / 1672);

  /* how far the bar and the cue above it sit off the bottom edge */
  --bar-inset: max(clamp(18px, 3vh, 30px), env(safe-area-inset-bottom));

  position: relative;
  height: 100svh;
  min-height: 520px;
  overflow: hidden;
  isolation: isolate;
}

/* --- sky --- */

.sky {
  position: absolute;
  inset: 0;
  z-index: 0;
  transition: opacity var(--fade) var(--ease-soft);
}

.sky-day {
  opacity: 1;
  background:
    radial-gradient(120% 90% at 82% 4%, rgba(255, 241, 199, 0.95) 0%, rgba(255, 241, 199, 0) 46%),
    linear-gradient(180deg, #6FB3E4 0%, #A6D4EE 42%, #D6E9DE 74%, #E9F0D8 100%);
}

.sky-night {
  opacity: 0;
  background:
    radial-gradient(110% 80% at 18% 6%, rgba(96, 118, 196, 0.5) 0%, rgba(96, 118, 196, 0) 52%),
    linear-gradient(180deg, #070B1E 0%, #131A38 44%, #202A55 76%, #2C3765 100%);
}

.hero[data-mode="night"] .sky-day   { opacity: 0; }
.hero[data-mode="night"] .sky-night { opacity: 1; }

/* --- the scene ---
   The rail holds the artwork's exact aspect ratio, so a point at artwork
   pixel (px, py) sits at (px/1672, py/941) of the rail at every viewport
   size. Two stages fill it: the art below, the interaction layer above,
   with the particle canvas between them. Nothing here takes a z-index of
   its own, so those three still stack in the hero's own order.

   Off the scrolling screens this is one still frame, centred on the house
   by a negative margin and clipped to the viewport. */

/* clip rather than hidden: hidden is still a scroll container, and focusing
   a light near the edge would scroll the scene sideways for good */
.scene-scroll {
  position: absolute;
  inset: 0;
  overflow: clip;
}

.rail {
  position: relative;
  width: var(--cover-w);
  height: var(--cover-h);
  top: calc((100% - var(--cover-h)) / 2);
  margin-left: calc((100% - var(--cover-w)) / 2);
}

.stage { position: absolute; inset: 0; }

.stage-art { z-index: 1; }
.stage-ui  { z-index: 3; pointer-events: none; }

.scene {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: fill;
}

.scene-night { opacity: 0; transition: opacity var(--fade) var(--ease-soft); }
.hero[data-mode="night"] .scene-night { opacity: 1; }

.particles {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* ==========================================================================
   Hotspots
   ========================================================================== */

.hotspots { position: absolute; inset: 0; }

.hotspot {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: 46px;
  height: 46px;
  margin: -23px 0 0 -23px;
  display: grid;
  place-items: center;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  pointer-events: auto;
  -webkit-tap-highlight-color: transparent;
  transition: opacity var(--fade) var(--ease-soft);
}

/* the light itself: a white core, a warm body, a bright rim so it holds up
   over pale flowers and dark snow alike */
.hotspot::before {
  content: "";
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: radial-gradient(circle at 34% 30%,
    #fff 0%, #fff 24%, var(--light) 70%, var(--light-deep) 100%);
  box-shadow:
    0 0 0 2px rgba(255, 255, 255, 0.9),
    0 0 0 3.5px var(--rim),
    0 0 20px 5px var(--halo),
    0 2px 6px rgba(0, 0, 0, 0.4);
  transition: transform 300ms var(--ease), box-shadow 300ms var(--ease);
}

/* the breathing ring */
.hotspot::after {
  content: "";
  position: absolute;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid var(--light);
  animation: pulse 2.8s var(--ease-soft) infinite;
}

.hero[data-mode="day"] .hotspot {
  --light: #FFC53D;
  --light-deep: #F09A05;
  --rim: rgba(84, 46, 0, 0.5);
  --halo: rgba(255, 186, 48, 0.8);
}

.hero[data-mode="night"] .hotspot {
  --light: #CFE1FF;
  --light-deep: #7FA9FF;
  --rim: rgba(10, 18, 56, 0.5);
  --halo: rgba(150, 190, 255, 0.75);
}

@keyframes pulse {
  0%        { transform: scale(1);   opacity: 0.9; }
  70%, 100% { transform: scale(3.4); opacity: 0; }
}

.hotspot:hover::before,
.hotspot[aria-expanded="true"]::before { transform: scale(1.28); }

.hotspot:focus-visible {
  outline: 2.5px solid #fff;
  outline-offset: 3px;
  border-radius: 50%;
}

/* an open card lifts its whole hotspot above its siblings */
.hotspot[aria-expanded="true"] { z-index: 5; }

/* An unreleased light still glows, just at a lower wattage and breathing more
   slowly, so the scene reads as a house with rooms yet to be lit. Dimming is
   done with box-shadow and animation-duration rather than opacity, because the
   pulse keyframes own ::after's opacity and would win. */
.hotspot[data-live="false"]::before {
  opacity: 0.82;
  box-shadow:
    0 0 0 2px rgba(255, 255, 255, 0.62),
    0 0 0 3.5px var(--rim),
    0 0 13px 3px var(--halo),
    0 2px 6px rgba(0, 0, 0, 0.34);
}

.hotspot[data-live="false"]::after { animation-duration: 4.4s; }

/* the inactive scene's lights fade out with the crossfade */
.hero[data-mode="day"]   .hotspot[data-scene="night"],
.hero[data-mode="night"] .hotspot[data-scene="day"] {
  opacity: 0;
  pointer-events: none;
}

@media (pointer: coarse) {
  .hotspot { width: 52px; height: 52px; margin: -26px 0 0 -26px; }
  .hotspot::before, .hotspot::after { width: 15px; height: 15px; }
}

/* ---------- product card ---------- */

/* The card centres on its light, then --dx slides it back inside the
   viewport when the light sits near an edge. The origin follows --dx so it
   always grows out of the light it belongs to. */
.card {
  --dx: 0px;
  position: absolute;
  bottom: calc(100% + 14px);
  left: 50%;
  width: max-content;
  max-width: min(292px, calc(100vw - 28px));
  padding: 16px 18px 15px;
  border-radius: 18px;
  text-align: left;
  cursor: default;
  opacity: 0;
  visibility: hidden;
  transform: translate(calc(-50% + var(--dx)), 8px) scale(0.96);
  transform-origin: calc(50% - var(--dx)) 100%;
  transition: opacity 260ms var(--ease), transform 260ms var(--ease),
              visibility 0s 260ms;
}

.hotspot[aria-expanded="true"] .card {
  opacity: 1;
  visibility: visible;
  transform: translate(calc(-50% + var(--dx)), 0) scale(1);
  transition: opacity 260ms var(--ease), transform 260ms var(--ease);
}

.card.flip-below {
  bottom: auto;
  top: calc(100% + 14px);
  transform-origin: calc(50% - var(--dx)) 0;
  transform: translate(calc(-50% + var(--dx)), -8px) scale(0.96);
}

.hotspot[aria-expanded="true"] .card.flip-below {
  transform: translate(calc(-50% + var(--dx)), 0) scale(1);
}

.card-name {
  margin: 0 0 4px;
  font-size: 1rem;
  font-weight: 660;
  letter-spacing: -0.014em;
  line-height: 1.25;
  color: var(--glass-ink);
}

.card-desc {
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.45;
  color: var(--glass-ink-soft);
}

/* Optically the Apple mark sits high in its box, so nudge it back down. */
.apple-mark { flex: none; margin-top: -1px; }

/* An app still in development: the word alone, no name, no line, no button.
   Sized so the card reads as a deliberate placeholder, not a broken one. */
.card-soon {
  padding: 12px 16px;
  border-radius: 15px;
}

.card-soon .card-name {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 600;
  letter-spacing: 0.002em;
  color: var(--glass-ink-soft);
  white-space: nowrap;
}

/* ==========================================================================
   Hero bar: the day / night switch, and on the scrolling screens the row of
   lights the scene can stop at
   ========================================================================== */

.hero-bar {
  position: absolute;
  z-index: 50;
  left: 50%;
  transform: translateX(-50%);
  bottom: var(--bar-inset);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 7px;
  border-radius: 999px;
  transition: background var(--fade) var(--ease-soft),
              border-color var(--fade) var(--ease-soft),
              color var(--fade) var(--ease-soft);
}

/* Only the screens that scroll have anywhere to go, so off them the row and
   its rule are not there at all and the bar is the switch alone. */
.scene-dots,
.hero-bar-rule { display: none; }

.hero-bar-rule {
  width: 1px;
  height: 22px;
  background: currentColor;
  opacity: 0.16;
  flex: none;
}

/* ==========================================================================
   The rail
   Where the viewport is too narrow to hold the whole house, the scene runs
   left to right instead of being shrunk until it fits. Every light is a
   place it stops, the one in the middle of the screen comes up to full
   size, and the row in the hero bar says which one that is.

   main.js turns this on by measuring, not by guessing: the pointer is coarse
   or the window is small AND the scene is genuinely wider than the frame. A
   large touchscreen showing the whole house gets none of it.
   ========================================================================== */

.hero[data-rail="true"] .scene-scroll {
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}

.hero[data-rail="true"] .scene-scroll::-webkit-scrollbar { display: none; }

.hero[data-rail="true"] .rail { margin-left: 0; }

/* only the scene on show can be stopped at */
.hero[data-rail="true"][data-mode="day"]   .hotspot[data-scene="day"],
.hero[data-rail="true"][data-mode="night"] .hotspot[data-scene="night"] {
  scroll-snap-align: center;
}

/* The lights step back so the one in the middle can come forward. The ring
   is stopped rather than faded, because the pulse keyframes own its opacity
   and would win. */
.hero[data-rail="true"] .hotspot::before { transform: scale(0.78); }
.hero[data-rail="true"] .hotspot:not(.is-focus)::after { animation: none; opacity: 0; }

.hero[data-rail="true"] .hotspot.is-focus::before {
  transform: scale(1.55);
  box-shadow:
    0 0 0 2px rgba(255, 255, 255, 0.95),
    0 0 0 3.5px var(--rim),
    0 0 26px 9px var(--halo),
    0 2px 8px rgba(0, 0, 0, 0.45);
}

/* hover is not a thing here, and opening a card should not shrink the light
   it belongs to */
.hero[data-rail="true"] .hotspot:hover::before { transform: scale(0.78); }
.hero[data-rail="true"] .hotspot.is-focus:hover::before,
.hero[data-rail="true"] .hotspot.is-focus[aria-expanded="true"]::before { transform: scale(1.55); }

/* ---------- the row of lights ---------- */

.hero[data-rail="true"] .scene-dots {
  display: flex;
  align-items: center;
  gap: 3px;
  padding-left: 5px;
}

.hero[data-rail="true"] .hero-bar-rule { display: block; }

/* the tap target is 26px, the mark inside it is 7 */
.scene-dot {
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  background: none;
  display: grid;
  place-items: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.scene-dot::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.3;
  transition: opacity 260ms var(--ease), transform 260ms var(--ease),
              background 260ms var(--ease);
}

.scene-dot[aria-current="true"]::before {
  opacity: 1;
  transform: scale(1.28);
  background: var(--light-day);
  box-shadow: 0 0 8px 2px rgba(255, 197, 61, 0.55);
}

.hero[data-mode="night"] .scene-dot[aria-current="true"]::before {
  background: var(--light-night);
  box-shadow: 0 0 8px 2px rgba(169, 200, 255, 0.55);
}

.scene-dot:focus-visible {
  outline: 2.5px solid #fff;
  outline-offset: 1px;
  border-radius: 50%;
}

/* the row follows the scene, so it lists this scene's lights only */
.hero[data-mode="day"]   .scene-dot[data-scene="night"],
.hero[data-mode="night"] .scene-dot[data-scene="day"] { display: none; }

/* ---------- the way down ---------- */

/* Sits above the bar, breathing downward. Hidden until main.js offers it,
   and gone for good once the page has been scrolled at all. */
.scroll-cue {
  position: absolute;
  z-index: 45;
  left: 50%;
  bottom: calc(var(--bar-inset) + 64px);
  width: 40px;
  height: 40px;
  margin-left: -20px;
  padding: 0;
  border: none;
  background: none;
  display: grid;
  place-items: center;
  color: #fff;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transform: translateY(-7px);
  filter: drop-shadow(0 1px 7px rgba(10, 14, 40, 0.6));
  transition: opacity 850ms var(--ease-soft), transform 850ms var(--ease);
  -webkit-tap-highlight-color: transparent;
}

.scroll-cue.is-shown {
  opacity: 0.92;
  pointer-events: auto;
  transform: none;
}

.scroll-cue svg {
  width: 25px;
  height: 25px;
  animation: cue-nudge 2.6s var(--ease-soft) infinite;
}

@keyframes cue-nudge {
  0%, 62%, 100% { transform: translateY(0);   opacity: 0.8; }
  31%           { transform: translateY(5px); opacity: 1; }
}

.scroll-cue:focus-visible {
  outline: 2.5px solid #fff;
  outline-offset: 2px;
  border-radius: 50%;
}

/* ---------- the switch ---------- */

.mode-toggle {
  position: relative;
  flex: none;
  width: 74px;
  height: 38px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 999px;
  background: rgba(120, 130, 170, 0.22);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  box-shadow: inset 0 1px 3px rgba(10, 8, 30, 0.16);
  transition: background var(--fade) var(--ease-soft),
              border-color var(--fade) var(--ease-soft);
}

.hero[data-mode="night"] .mode-toggle {
  background: rgba(8, 10, 28, 0.42);
  border-color: rgba(255, 255, 255, 0.16);
}

/* knob travel: 74 - 4 - 30 - 4 = 36 */
.mode-knob {
  position: absolute;
  left: 4px;
  top: 3px;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: linear-gradient(180deg, #FFFFFF 0%, #F2F0F8 100%);
  box-shadow: 0 2px 6px rgba(10, 8, 30, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.9);
  transition: transform 520ms var(--ease);
  z-index: 1;
}

.hero[data-mode="night"] .mode-knob { transform: translateX(36px); }

.mode-icon {
  position: absolute;
  top: 50%;
  width: 16px;
  height: 16px;
  margin-top: -8px;
  z-index: 2;
  transition: color 400ms var(--ease-soft), opacity 400ms var(--ease-soft);
  pointer-events: none;
}

.icon-sun  { left: 11px; }
.icon-moon { right: 11px; width: 14px; height: 14px; margin-top: -7px; }

/* the icon the knob sits under goes dark; the other stays light on the track */
.hero[data-mode="day"] .icon-sun    { color: #E09B12; opacity: 1; }
.hero[data-mode="day"] .icon-moon   { color: #fff;    opacity: 0.72; }
.hero[data-mode="night"] .icon-sun  { color: #fff;    opacity: 0.6; }
.hero[data-mode="night"] .icon-moon { color: #3D4A86; opacity: 1; }

.mode-toggle:active .mode-knob { width: 34px; }
.mode-toggle:focus-visible { outline: 2.5px solid #fff; outline-offset: 3px; }

/* ==========================================================================
   Apps
   The section the hero deliberately is not. Everything explanatory lives here,
   in the page source, where it can be read without running any JavaScript.

   Each card opens with a sky drawn from the hero's own day and night
   gradients, and the app's light hangs in it at the same percentage
   coordinates the app occupies in the house artwork. The card is a window
   onto that app's corner of the scene, at that app's hour.
   ========================================================================== */

.apps {
  background: var(--paper);
  padding: clamp(72px, 10vw, 128px) 0 clamp(64px, 9vw, 112px);
}

.app-grid {
  list-style: none;
  margin: clamp(36px, 5vw, 56px) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(16px, 2vw, 24px);
}

.app-card {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: 22px;
  background: #fff;
  border: 1px solid rgba(30, 18, 70, 0.07);
  box-shadow: 0 1px 2px rgba(30, 18, 70, 0.04), 0 12px 34px rgba(30, 18, 70, 0.06);
}

/* The card does not answer the pointer. Lifting a card on hover is a tired
   gesture, and it is also a lie: the card is a container, the two buttons in
   it are what can be pressed, and they have their own states. The abstract
   above the copy is already the thing that holds the eye. */

/* jumping here from the hero should land with the card visible, not shaved
   off under the top edge of the viewport */
.app-card { scroll-margin-top: 24px; }

/* ---------- the abstract ----------
   White line work on the app's own palette, animated by CSS alone. Each
   abstract is the app's mechanism reduced to one gesture: comb being built,
   nutrient rings turning, orbits keeping their periods. */

.app-viz {
  position: relative;
  height: 128px;
  flex: none;
}

.app-viz svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* honey: dark comb up to sunlit amber */
.app-viz[data-app="hivelab"] {
  background: linear-gradient(135deg, #5C3A0B 0%, #96621A 52%, #D99A2B 100%);
}

/* vibrant purple-blue, the Gramigo brand */
.app-viz[data-app="gramigo"] {
  background: linear-gradient(135deg, #4330E0 0%, #6A48F2 55%, #8E62FF 100%);
}

/* Even's own night, with the moon blue it marks a settled day in */
.app-viz[data-app="even"] {
  background:
    radial-gradient(90% 72% at 78% 16%, rgba(169, 194, 232, 0.34) 0%, rgba(169, 194, 232, 0) 62%),
    linear-gradient(135deg, #0A0D14 0%, #161C28 54%, #2A3550 100%);
}

/* the hero's night sky, which is also Orrery's */
.app-viz[data-app="orrery"] {
  background:
    radial-gradient(110% 80% at 18% 6%, rgba(96, 118, 196, 0.5) 0%, rgba(96, 118, 196, 0) 52%),
    linear-gradient(180deg, #070B1E 0%, #131A38 52%, #202A55 100%);
}

/* a still starfield behind the orbits, three sizes of star, no script */
.app-viz[data-app="orrery"]::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(1.6px 1.6px at 18% 30%, rgba(255, 255, 255, 0.95), transparent 60%),
    radial-gradient(1.3px 1.3px at 62% 18%, rgba(255, 255, 255, 0.85), transparent 60%),
    radial-gradient(1.2px 1.2px at 44% 62%, rgba(255, 255, 255, 0.6), transparent 60%),
    radial-gradient(1px 1px at 86% 48%, rgba(255, 255, 255, 0.7), transparent 60%),
    radial-gradient(1px 1px at 30% 78%, rgba(255, 255, 255, 0.45), transparent 60%),
    radial-gradient(1.2px 1.2px at 8% 55%, rgba(255, 255, 255, 0.55), transparent 60%);
  background-size: 160px 120px;
}

/* HiveLab: cells of comb glowing awake in sequence, the way a colony works
   a frame, then resting and starting again */
.comb .cell {
  opacity: 0.22;
  animation: cell-wake 5.6s var(--ease-soft) infinite;
}

.comb .c1 { animation-delay: 0s;    }
.comb .c2 { animation-delay: 0.35s; }
.comb .c5 { animation-delay: 0.7s;  }
.comb .c3 { animation-delay: 1.05s; }
.comb .c4 { animation-delay: 1.4s;  }
.comb .c7 { animation-delay: 1.75s; }
.comb .c6 { animation-delay: 2.1s;  }
.comb .c8 { animation-delay: 2.45s; }
.comb .c9 { animation-delay: 2.8s;  }

@keyframes cell-wake {
  0%, 100% { opacity: 0.22; }
  18%      { opacity: 1; }
  40%      { opacity: 0.45; }
}

/* Gramigo: nutrient rings, each turning at its own pace */
.ring {
  transform-origin: 0 0;   /* the group is already translated to centre */
  animation: ring-turn linear infinite;
}

.ring.r1 { animation-duration: 26s; }
.ring.r2 { animation-duration: 42s; animation-direction: reverse; }
.ring.r3 { animation-duration: 64s; }

@keyframes ring-turn {
  to { transform: rotate(360deg); }
}

/* Orrery: inner planets run faster than outer ones, as they really do */
.orbit {
  transform-origin: 150px 64px;
  animation: ring-turn linear infinite;
}

.orbit.o1 { animation-duration: 9s;  }
.orbit.o2 { animation-duration: 21s; }
.orbit.o3 { animation-duration: 38s; }

/* Even: a wave passing, three readings of the same day at their own rates.
   Each path holds four wavelengths of 150, so a drift of one wavelength
   returns it exactly where it started and the loop cannot be seen. */
.wave {
  animation: wave-drift linear infinite;
}

.wave.w1 { animation-duration: 9s;  }
.wave.w2 { animation-duration: 14s; animation-direction: reverse; }
.wave.w3 { animation-duration: 21s; }

@keyframes wave-drift {
  to { transform: translateX(-150px); }
}

/* Each abstract holds at its first frame until main.js says its card is on
   screen, so it is met starting rather than mid loop, and four infinite
   animations stay off the compositor the rest of the time. These come after
   the declarations above on purpose: the `animation` shorthand resets
   play-state to running, so a gate written before them does nothing. */
.comb .cell,
.ring,
.orbit,
.wave {
  animation-play-state: paused;
}

.app-card.viz-on .comb .cell,
.app-card.viz-on .ring,
.app-card.viz-on .orbit,
.app-card.viz-on .wave {
  animation-play-state: running;
}

/* ---------- the room ---------- */

.app-card-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  flex: 1;
  padding: 22px 24px 24px;
}

.app-card h3 {
  margin: 0 0 3px;
  font-size: 1.28rem;
  font-weight: 660;
  letter-spacing: -0.022em;
  color: var(--ink);
}

.app-card-tagline {
  margin: 0 0 14px;
  font-size: 0.9rem;
  font-weight: 560;
  letter-spacing: -0.008em;
  color: var(--accent-deep);
}

.app-card-desc {
  margin: 0 0 22px;
  font-size: 0.925rem;
  line-height: 1.58;
  color: var(--ink-soft);
}

/* pushes the buttons to the bottom so the cards line their actions up
   however unevenly the descriptions wrap */
.app-card-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: auto;
}

.app-card-cta {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 38px;
  padding: 0 17px;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: 0.875rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  text-decoration: none;
  box-shadow: 0 3px 14px rgba(107, 71, 232, 0.3);
  transition: transform 200ms var(--ease), box-shadow 200ms var(--ease),
              background 200ms var(--ease);
}

.app-card-cta:hover {
  background: var(--accent-deep);
  transform: translateY(-1px);
  box-shadow: 0 7px 20px rgba(107, 71, 232, 0.36);
}

.app-card-cta:active { transform: translateY(0) scale(0.98); }
.app-card-cta:focus-visible { outline: 2px solid var(--accent-deep); outline-offset: 3px; }

/* The app's own site, second to the App Store button and quieter than it:
   one filled button per card is the rule, so this one is drawn in outline. */
.app-card-site {
  display: inline-flex;
  align-items: center;
  height: 38px;
  padding: 0 17px;
  border-radius: 999px;
  border: 1px solid rgba(107, 71, 232, 0.3);
  color: var(--accent-deep);
  font-size: 0.875rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  text-decoration: none;
  transition: transform 200ms var(--ease), background 200ms var(--ease),
              border-color 200ms var(--ease);
}

.app-card-site:hover {
  background: var(--accent-tint);
  border-color: rgba(107, 71, 232, 0.5);
  transform: translateY(-1px);
}

.app-card-site:active { transform: translateY(0) scale(0.98); }
.app-card-site:focus-visible { outline: 2px solid var(--accent-deep); outline-offset: 3px; }

.app-grid-note {
  margin: clamp(28px, 3.5vw, 40px) 0 0;
  font-size: 0.925rem;
  line-height: 1.6;
  color: var(--ink-faint);
  max-width: 46ch;
}

/* ==========================================================================
   Recognition
   Orrery is a night app, so its recognition happens at night: the hero's
   night sky, a still starfield, and the dark glass tokens the hero uses
   after sunset (declared alongside them at the top of this file).
   ========================================================================== */

.recognition {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(110% 80% at 18% 6%, rgba(96, 118, 196, 0.5) 0%, rgba(96, 118, 196, 0) 52%),
    linear-gradient(180deg, #070B1E 0%, #131A38 52%, #202A55 100%);
  padding: clamp(64px, 9vw, 112px) 0;
}

.recognition::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(1.8px 1.8px at 12% 22%, rgba(255, 255, 255, 0.95), transparent 60%),
    radial-gradient(1.4px 1.4px at 38% 64%, rgba(255, 255, 255, 0.7), transparent 60%),
    radial-gradient(1.5px 1.5px at 57% 14%, rgba(255, 255, 255, 0.85), transparent 60%),
    radial-gradient(1.2px 1.2px at 76% 42%, rgba(255, 255, 255, 0.75), transparent 60%),
    radial-gradient(1px 1px at 90% 74%, rgba(255, 255, 255, 0.5), transparent 60%),
    radial-gradient(1.2px 1.2px at 24% 86%, rgba(255, 255, 255, 0.6), transparent 60%),
    radial-gradient(1px 1px at 68% 88%, rgba(255, 255, 255, 0.45), transparent 60%),
    radial-gradient(1.4px 1.4px at 88% 8%, rgba(255, 255, 255, 0.8), transparent 60%);
  background-size: 230px 180px;
  pointer-events: none;
}

.recognition .wrap {
  position: relative;
  z-index: 1;
  text-align: center;
}

.recognition .eyebrow { color: var(--accent); }
.recognition .display { color: #F2F4FF; margin-left: auto; margin-right: auto; }

.honours {
  list-style: none;
  margin: clamp(26px, 3.5vw, 38px) 0 0;
  padding: 0;
  display: grid;
  gap: 14px;
  justify-items: center;
}

/* one line per honoured app: the name, then its lists */
.honour {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px;
}

.honour-app {
  margin-right: 6px;
  font-size: 1.06rem;
  font-weight: 660;
  letter-spacing: -0.018em;
  color: var(--glass-ink);
}

.honour-badge {
  display: inline-flex;
  align-items: center;
  height: 31px;
  padding: 0 14px;
  border-radius: 999px;
  border: 1px solid rgba(158, 134, 255, 0.4);
  background: rgba(158, 134, 255, 0.14);
  color: #CFC3FF;
  font-size: 0.815rem;
  font-weight: 600;
  letter-spacing: -0.004em;
}

/* ==========================================================================
   Studio
   ========================================================================== */

.studio {
  position: relative;
  overflow: hidden;
  background: linear-gradient(180deg, var(--paper-warm) 0%, var(--paper) 100%);
  padding: clamp(72px, 11vw, 140px) 0 clamp(76px, 12vw, 148px);
}

/* soft colour behind the glass so it has something to refract */
/* Two lights behind the studio copy. They drift on long, mismatched cycles,
   which is enough for the section to feel lit rather than printed, and far
   too slow to be caught at it. */
.orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  pointer-events: none;
  animation: orb-drift linear infinite;
  will-change: transform;
}

.orb-a { animation-duration: 54s; }
.orb-b { animation-duration: 78s; animation-direction: reverse; }

@keyframes orb-drift {
  0%   { transform: translate3d(0, 0, 0) scale(1); }
  33%  { transform: translate3d(3.5%, 4.5%, 0) scale(1.06); }
  66%  { transform: translate3d(-3%, 2.5%, 0) scale(0.96); }
  100% { transform: translate3d(0, 0, 0) scale(1); }
}

.orb-a {
  width: 46vw; max-width: 620px; aspect-ratio: 1;
  background: rgba(107, 71, 232, 0.2);
  top: -12%; right: -8%;
}

.orb-b {
  width: 40vw; max-width: 520px; aspect-ratio: 1;
  background: rgba(255, 176, 71, 0.2);
  bottom: -16%; left: -10%;
}

.studio .wrap { position: relative; z-index: 1; }

.eyebrow {
  margin: 0 0 14px;
  font-size: 0.775rem;
  font-weight: 650;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
}

.display {
  margin: 0 0 24px;
  font-size: clamp(1.95rem, 4.6vw, 3.35rem);
  line-height: 1.1;
  font-weight: 680;
  letter-spacing: -0.031em;
  color: var(--ink);
  text-wrap: balance;
}

.display-sm { font-size: clamp(1.6rem, 3.4vw, 2.35rem); margin-bottom: 20px; }

.lede {
  margin: 0 0 26px;
  max-width: 60ch;
  font-size: clamp(1.02rem, 1.5vw, 1.19rem);
  line-height: 1.6;
  color: var(--ink-soft);
}

.mission {
  margin: 0;
  font-size: clamp(1.15rem, 2.2vw, 1.55rem);
  font-weight: 620;
  letter-spacing: -0.02em;
  line-height: 1.35;
  color: var(--accent-deep);
  max-width: 22ch;
}

.values {
  list-style: none;
  margin: clamp(44px, 6vw, 68px) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 14px;
}

.values li {
  padding: 20px 18px 22px;
  border-radius: 18px;
}

.values h3 {
  margin: 0 0 6px;
  font-size: 0.94rem;
  font-weight: 650;
  letter-spacing: -0.012em;
  color: var(--ink);
}

.values p {
  margin: 0;
  font-size: 0.865rem;
  line-height: 1.48;
  color: var(--ink-faint);
}

/* ==========================================================================
   Founder
   ========================================================================== */

.founder {
  background: var(--paper);
  padding: clamp(72px, 11vw, 136px) 0;
}

.founder-grid {
  display: grid;
  grid-template-columns: minmax(200px, 300px) 1fr;
  gap: clamp(36px, 6vw, 76px);
  align-items: center;
}

.founder-figure { margin: 0; text-align: center; }

.founder-photo {
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 50%;
  box-shadow:
    0 24px 60px rgba(30, 18, 70, 0.16),
    0 0 0 1px rgba(107, 71, 232, 0.1);
}

.founder-figure figcaption {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.founder-figure strong {
  font-size: 1rem;
  font-weight: 640;
  letter-spacing: -0.012em;
  color: var(--ink);
}

.founder-figure span {
  font-size: 0.83rem;
  color: var(--ink-faint);
}

.founder-copy p:not(.eyebrow) {
  margin: 0 0 17px;
  max-width: 56ch;
}

.founder-copy p:last-child { margin-bottom: 0; }

/* ==========================================================================
   Footer
   ========================================================================== */

.site-footer {
  background: #14111E;
  color: rgba(255, 255, 255, 0.62);
  padding: 40px 0 max(40px, env(safe-area-inset-bottom));
  font-size: 0.86rem;
}

.footer-grid {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
}

.brand-dark { text-shadow: none; }
.brand-dark .brand-name { color: #fff; font-size: 0.95rem; }

.footer-meta {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
}

.footer-meta a { color: #B9A6FF; text-decoration: none; }
.footer-meta a:hover { text-decoration: underline; }

/* ==========================================================================
   Responsive
   ========================================================================== */

/* iPad and small laptops */
@media (max-width: 1023px) {
  .values { grid-template-columns: repeat(2, 1fr); }
}

/* Three app cards need about 260px each before the descriptions start
   breaking into ragged four line columns. */
@media (max-width: 899px) {
  .app-grid { grid-template-columns: 1fr; gap: 14px; }
  .app-viz { height: 112px; }
  .app-card-body { padding: 20px 22px 22px; }
}

/* iPad portrait and below: the portrait centres, the reading copy stays
   left aligned so multi-line paragraphs remain easy to read */
@media (max-width: 860px) {
  .founder-grid {
    grid-template-columns: 1fr;
    justify-items: center;
    gap: 34px;
  }
  .founder-figure { max-width: 220px; }
  .founder-copy { width: 100%; text-align: left; }
}

/* Phones */
@media (max-width: 639px) {
  body { font-size: 16px; }

  .brand-name { display: none; }
  .btn-music-label { display: none; }
  .btn-music { padding: 0 13px; }

  .hero-bar {
    gap: 10px;
    max-width: calc(100vw - 24px);
  }

  .values {
    grid-template-columns: 1fr 1fr;
    gap: 10px;
  }

  .footer-grid { flex-direction: column; text-align: center; gap: 14px; }

}

@media (max-width: 400px) {
  .values { grid-template-columns: 1fr; }
}

/* Short landscape phones: give the art the room instead of the bar */
@media (max-height: 460px) and (orientation: landscape) {
  .hero { min-height: 100svh; }
  .hero-bar { padding: 6px; gap: 8px; }
  .scroll-cue { display: none; }   /* no room above the bar to offer it */
}

/* ==========================================================================
   Reduced motion
   ========================================================================== */

/* Things arrive as they enter. The .reveal class is only added by main.js,
   and only when motion is allowed, so without JavaScript nothing is hidden.

   Four sections, four arrivals: one gesture used everywhere is the thing
   that makes a page read as a template, and these are each doing a
   different job. Animations rather than transitions, with backwards fill, so
   the element goes back to its own styles afterwards and the hover
   transitions underneath are left alone. */
.reveal { opacity: 0; }

.reveal.is-in {
  opacity: 1;
  animation: var(--reveal-anim) var(--reveal-time) var(--ease)
             var(--reveal-delay, 0ms) backwards;
}

/* an app card is an object, so it lifts into place */
.reveal-rise { --reveal-anim: arrive-rise; --reveal-time: 700ms; }

@keyframes arrive-rise {
  from { opacity: 0; transform: translateY(26px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

/* the four values are a set, so they resolve almost together, barely moving */
.reveal-settle { --reveal-anim: arrive-settle; --reveal-time: 520ms; }

@keyframes arrive-settle {
  from { opacity: 0; transform: translateY(9px); }
  to   { opacity: 1; transform: none; }
}

/* a recognition is conferred, so it arrives in place rather than travelling */
.reveal-confer { --reveal-anim: arrive-confer; --reveal-time: 620ms; }

@keyframes arrive-confer {
  from { opacity: 0; transform: scale(0.88); }
  to   { opacity: 1; transform: none; }
}

/* the portrait, slowest of the four, coming up out of the page */
.reveal-lift { --reveal-anim: arrive-lift; --reveal-time: 1000ms; }

@keyframes arrive-lift {
  from { opacity: 0; transform: scale(0.94); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

@media (prefers-reduced-motion: reduce) {
  :root { --fade: 200ms; }
  .hotspot::after,
  .btn-music[aria-pressed="true"] .eq i,
  .scroll-cue svg { animation: none; }
  .hotspot::after { opacity: 0; }
  .comb .cell, .ring, .orbit, .wave, .orb { animation: none; }
  .reveal.is-in { animation: none; }
  .comb .cell { opacity: 0.7; }
  .particles { display: none; }
  .mode-knob { transition-duration: 1ms; }
  * { scroll-behavior: auto; }
}
