/* HaloWake home.

   The cloud is ONE radial with a long multi-stop fade, not a set of scattered
   blobs. That is the whole trick: several small radials in different hues each
   have their own edge, and the eye assembles those edges into a lumpy silhouette.
   A single soft falloff has no edge to find, so it reads as light rather than as
   an object, and it stays one clean colour family instead of a spectrum. */

:root {
  color-scheme: light;

  --page: #fbfcfd;

  /* Two hues plus white, both off the app icon: its sky and cyan for the blue
     mass, its aqua and mint for the green one. The icon's warm ring stays out
     of the field entirely so the count does not creep to three.

     These are the icon hues shaded down, not the raw hexes. The raw ones are
     pale enough that the field plateaus around 1.4:1 under the wordmark however
     hard the wash is pushed, because mint sits at luminance 0.75 and aqua at
     0.68 while white type needs 0.30 or lower to reach even 3:1. Shading the
     same hues to here puts the field at the reference image's own level. */
  --sky: 46, 147, 204;
  --cyan: 64, 180, 219;
  --aqua: 108, 200, 203;
  --mint: 134, 219, 180;
  /* The wash the two masses tint, inside the mask. */
  --wash: 186, 224, 241;

  /* The icon's warm ring, kept low and faint so it stays an undertone. */
  --warm: 246, 222, 168;

  --ink: #16242c;
  /* The footer sits on the tinted foot of the field, not on paper. At 0.62 the
     links measured 4.23:1 against the worst tint they cross, under the 4.5:1
     floor for 12px text. */
  --ink-soft: rgba(22, 36, 44, 0.74);
  --white: #ffffff;
  --white-soft: rgba(255, 255, 255, 0.9);

  --wm: clamp(52px, 12.5vw, 200px);
  --gutter: max(24px, calc((100vw - 1320px) / 2));
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-height: 100svh;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  background: var(--page);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Helvetica Neue", Arial, sans-serif;
  font-weight: 300;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* Absolutely positioned so it stays out of the body grid. A filter's defs
   cannot be display:none in every browser, but they can be zero sized. */
.defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.mesh,
.grain,
.gloss {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.mesh {
  /* The warp sits on the wrapper so it deforms the already masked shape. Put
     the filter on the same element as the mask and CSS applies the filter
     first, leaving a crisp unwarped ellipse where the mask cut it. */
  filter: url("#cloud");
}

/* Colour and silhouette are separate. The vertical ramp decides how deep the
   field is at a given height; the radial mask decides where the field exists at
   all. Keeping them apart is what lets the deep part run the full width of the
   text band while the cloud still fades to paper before the corners. A single
   radial could only ever put its deep part in the middle, which left the ends
   of the wordmark and the right hand tagline stranded on the pale rim. */
.mesh-body {
  position: absolute;
  inset: 0;
  background-image:
    /* The second hue: aqua opening into mint, low and left. */
    radial-gradient(78% 74% at 25% 76%,
      rgba(var(--mint), 0.92) 0%,
      rgba(var(--aqua), 0.74) 30%,
      rgba(var(--aqua), 0.40) 58%,
      rgba(var(--aqua), 0.14) 80%,
      rgba(var(--aqua), 0) 100%),
    /* The dominant hue: cyan into sky, high and right. Both masses are large
       and heavily overlapped on purpose. Small radials at scattered positions
       each keep their own edge and the eye assembles them into a lumpy shape;
       two masses this size have no edge of their own inside the silhouette. */
    radial-gradient(86% 82% at 69% 29%,
      rgba(var(--cyan), 0.90) 0%,
      rgba(var(--sky), 0.80) 30%,
      rgba(var(--sky), 0.46) 60%,
      rgba(var(--sky), 0.16) 82%,
      rgba(var(--sky), 0) 100%),
    /* The wash the masses tint, so neither has to reach the silhouette's rim. */
    linear-gradient(0deg, rgb(var(--wash)) 0%, rgb(var(--wash)) 100%);
  /* Six stops so the falloff is continuous and no ring appears. This is the
     only thing that decides the outline, which is why the two colour masses
     inside it cannot introduce a second edge. */
  -webkit-mask-image: radial-gradient(64% 60% at 51% 45%,
    rgba(0, 0, 0, 0.97) 0%,
    rgba(0, 0, 0, 0.92) 24%,
    rgba(0, 0, 0, 0.74) 48%,
    rgba(0, 0, 0, 0.44) 66%,
    rgba(0, 0, 0, 0.17) 84%,
    rgba(0, 0, 0, 0) 100%);
  mask-image: radial-gradient(64% 60% at 51% 45%,
    rgba(0, 0, 0, 0.97) 0%,
    rgba(0, 0, 0, 0.92) 24%,
    rgba(0, 0, 0, 0.74) 48%,
    rgba(0, 0, 0, 0.44) 66%,
    rgba(0, 0, 0, 0.17) 84%,
    rgba(0, 0, 0, 0) 100%);
}

/* Ported from the app: one static monochrome noise tile, overlay blend. The app
   uses texture * 0.58 with texture defaulting to 0.35, which is 0.203. */
.grain {
  opacity: 0.203;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='180' height='180' filter='url(%23n)'/></svg>");
}

/* The app's gloss axis: one soft-light sheen riding above the grain, the way a
   wet finish sits on a material rather than replacing it. It starts at the
   middle, never the top, so it cannot spend contrast in the reading region.
   0.09 is the app's 0.20 * gloss at the default gloss of 0.45. Static. */
.gloss {
  mix-blend-mode: soft-light;
  background-image: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0) 50%,
    rgba(255, 255, 255, 0.09) 100%
  );
}

.hero,
.site-footer {
  position: relative;
  z-index: 1;
  padding-inline: var(--gutter);
}

.hero {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 0;
  padding-block: clamp(24px, 5vh, 56px);
}

.hero-inner {
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
}

.wordmark-wrap {
  position: relative;
}

/* Measured from the rendered glyphs, not estimated. The l ends at 30.5% of the
   wordmark's width and the k begins at 75.6%, so the caption spans exactly the
   gap between them. Its top sits just above the ascender line, which is at
   0.114em: riding it exactly read a touch low against the l. */
.eyebrow {
  position: absolute;
  left: 30.5%;
  top: calc(var(--wm) * 0.091);
  width: 45%;
  margin: 0;
  color: var(--white-soft);
  font-size: clamp(11px, 1.05vw, 14px);
  font-weight: 350;
  line-height: 1.35;
}

.wordmark {
  margin: 0;
  color: var(--white);
  font-size: var(--wm);
  font-weight: 200;
  letter-spacing: -0.045em;
  line-height: 0.88;
  white-space: nowrap;
}

.hero-row {
  display: flex;
  /* Baseline, not centre. Centring the two boxes lines up their midpoints, but
     the pill holds one line of text and the tagline holds two, so the label
     landed in the gutter between them and shared a baseline with neither. On
     the first baseline the label and "rest better." sit on one reading line. */
  align-items: baseline;
  justify-content: space-between;
  gap: clamp(20px, 4vw, 56px);
  margin-top: clamp(20px, 4vh, 44px);
}

.download {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 46px;
  padding: 11px 22px;
  border: 1px solid rgba(255, 255, 255, 0.5);
  border-radius: 999px;
  /* Tinted down rather than up. A white veil lightens whatever the warp happens
     to put underneath, which is the one place the label cannot afford it; a
     tinted glass fill means the pill carries its own contrast wherever it sits.
     Keyed to the field's own blue so it reads as glass, not as a dark blob. */
  background: rgba(24, 96, 142, 0.34);
  color: var(--white);
  font: inherit;
  font-size: 14px;
  font-weight: 400;
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

.download:disabled {
  cursor: not-allowed;
}

.download .dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgb(var(--warm));
}

.tagline {
  margin: 0;
  color: var(--white-soft);
  font-size: clamp(12px, 1.1vw, 15px);
  font-weight: 350;
  line-height: 1.42;
  text-align: right;
}

.site-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 24px;
  padding-block: 14px;
  /* Without this the home indicator sits over the footer on an iPhone. */
  padding-bottom: calc(14px + env(safe-area-inset-bottom, 0px));
  color: var(--ink-soft);
  font-size: 12px;
}

.site-footer a {
  color: var(--ink-soft);
}

.footer-links {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2.4vw, 30px);
}

/* 44px is the touch minimum. The original links were 18px. */
.footer-links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  text-decoration: none;
}

.footer-links a:hover,
.footer-links a:focus-visible {
  color: var(--ink);
}

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

:where(a, button):focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: 6px;
}

/* Entrance. Opacity and transform only, so each element is handed to the
   compositor and nothing re-runs the mesh's displacement filter per frame.
   The whole block sits inside a no-preference query: with Reduce Motion on,
   none of it is declared, so the page renders at its final state rather than
   relying on an animation to reveal it. animation-fill-mode backwards holds
   the from-state during the delay, so nothing flashes before its turn. */
@media (prefers-reduced-motion: no-preference) {
  .mesh,
  .grain,
  .gloss {
    animation: wash 1200ms cubic-bezier(0.33, 0.68, 0.3, 1) backwards;
  }

  .wordmark,
  .eyebrow,
  .download,
  .tagline,
  .site-footer {
    animation: rise 900ms cubic-bezier(0.22, 0.68, 0.24, 1) backwards;
  }

  /* The wordmark leads, the caption reads down into it, then the row, then the
     footer. Roughly a beat apart rather than evenly spaced. */
  .wordmark { animation-delay: 140ms; }
  .eyebrow { animation-delay: 320ms; }
  .download { animation-delay: 440ms; }
  .tagline { animation-delay: 520ms; }
  .site-footer { animation-delay: 640ms; }
}

@keyframes wash {
  from {
    opacity: 0;
  }
}

@keyframes rise {
  from {
    opacity: 0;
    transform: translate3d(0, 14px, 0);
  }
}

@media (max-width: 760px) {
  .mesh {
    /* Same warp, but the displacement is scaled to the smaller box. Reusing the
       desktop filter here would throw a 300px bend across a 375px screen. */
    filter: url("#cloud-sm");
  }

  .mesh-body {
    /* Taller and narrower to follow a portrait screen, same two masses. */
    background-image:
      radial-gradient(96% 56% at 24% 74%,
        rgba(var(--mint), 0.92) 0%,
        rgba(var(--aqua), 0.74) 30%,
        rgba(var(--aqua), 0.40) 58%,
        rgba(var(--aqua), 0.14) 80%,
        rgba(var(--aqua), 0) 100%),
      radial-gradient(104% 62% at 70% 30%,
        rgba(var(--cyan), 0.90) 0%,
        rgba(var(--sky), 0.80) 30%,
        rgba(var(--sky), 0.46) 60%,
        rgba(var(--sky), 0.16) 82%,
        rgba(var(--sky), 0) 100%),
      linear-gradient(0deg, rgb(var(--wash)) 0%, rgb(var(--wash)) 100%);
    -webkit-mask-image: radial-gradient(66% 50% at 51% 45%,
      rgba(0, 0, 0, 0.97) 0%,
      rgba(0, 0, 0, 0.92) 24%,
      rgba(0, 0, 0, 0.74) 48%,
      rgba(0, 0, 0, 0.44) 66%,
      rgba(0, 0, 0, 0.17) 84%,
      rgba(0, 0, 0, 0) 100%);
    mask-image: radial-gradient(66% 50% at 51% 45%,
      rgba(0, 0, 0, 0.97) 0%,
      rgba(0, 0, 0, 0.92) 24%,
      rgba(0, 0, 0, 0.74) 48%,
      rgba(0, 0, 0, 0.44) 66%,
      rgba(0, 0, 0, 0.17) 84%,
      rgba(0, 0, 0, 0) 100%);
  }

  .eyebrow {
    position: static;
    width: auto;
    margin: 0 0 8px;
    text-align: center;
  }

  .hero-row {
    flex-direction: column;
    align-items: center;
    gap: 18px;
  }

  .tagline {
    order: 2;
    text-align: center;
  }

  .download {
    order: 1;
  }

  .site-footer {
    justify-content: center;
    gap: 2px 18px;
  }
}
