/* Deltas for the public page only. welcome.css is the shared system and is
   copied in at build time, so nothing here is allowed to restyle it -- these
   are the four things a landing page needs that a post-install page does not. */

/* The hero slot used to hold a status tag. A button is taller than a tag, so
   the row needs to centre on it and lose the button's stacked margin. */
.hero__meta--install {
  flex-wrap: wrap;
  gap: 16px;
}

.cta--hero {
  margin-top: 0;
  padding: 14px 24px;
  font-size: 14px;
}

@media (max-width: 900px) {
  /* The separator only reads as one when the two halves sit on a line. */
  .hero__meta--install .hero__sep {
    display: none;
  }
}

/* ---------------------------------------------------------------- the wall

   His own favourites, and only ever texture. They sit further back than the
   extension's version because on the web they are the one personal thing on
   an otherwise anonymous page -- present, not competing with the type. */
.wall__tile {
  filter: grayscale(1) brightness(0.3) contrast(1.05);
}

/* ----------------------------------------------------------- hero (web)

   No new elements: the difference between this and the post-install hero is
   entirely type and motion. Lines are set rather than faded in, the scale
   contrast between the display and the labels is widened, and the whole block
   hands off to the beats on scroll instead of stopping dead. */

.hero {
  max-width: 1180px;
  padding-top: 96px;
}

/* Tracking is size-specific. This runs larger than the extension's hero, so
   it is pulled tighter -- one fixed letter-spacing would be wrong here. */
.display {
  max-width: 9ch;
  font-size: clamp(52px, 8.4vw, 132px);
  line-height: 0.9;
  letter-spacing: -0.055em;
  font-weight: 700;
}

.lede {
  max-width: 74ch;
  margin-top: 34px;
  font-size: 17px;
  line-height: 1.5;
}

.lede .ln + .ln {
  color: var(--dim);
}

/* Each line clears its own mask. overflow hides the descenders too, so the
   box is padded and pulled back by the same amount. */
.ln {
  display: block;
  overflow: hidden;
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
}

/* A keyframe animation rather than a class toggled from script: a tab that is
   not focused suspends requestAnimationFrame, and a reveal that depends on it
   never runs. CSS animations are not on that hook, and it is how the rest of
   the page already animates. */
@keyframes set-line {
  from {
    transform: translateY(105%);
  }
}

.ln__i {
  display: block;
  will-change: transform;
  /* Critically damped curve: nothing here carried momentum, so overshoot
     would be decoration rather than a physical consequence. */
  animation: set-line 900ms var(--ease) both;
}

/* The stagger is what makes it read as being set rather than arriving. */
.display .ln:nth-child(1) .ln__i { animation-delay: 60ms; }
.display .ln:nth-child(2) .ln__i { animation-delay: 130ms; }
.lede .ln:nth-child(1) .ln__i { animation-delay: 260ms; }
.lede .ln:nth-child(2) .ln__i { animation-delay: 320ms; }

/* The wordmark and the button are not lines of type, so they arrive rather
   than get set. Same curve, so the block still reads as one gesture. */
.wordmark {
  animation-delay: 0ms;
}

.hero__meta--install {
  animation: rise 700ms 420ms var(--ease) both;
}

/* The hero leaves at a fraction of the scroll rate, so the beats arrive over
   it instead of the page cutting from one block to the next. */
.hero__copy-shift {
  will-change: transform, opacity;
}

/* ---------------------------------------------------------------- the wall

   His own favourites, and only ever texture. A personal touch, present but
   never competing with the type. */
.wall__tile {
  filter: grayscale(1) brightness(0.3) contrast(1.05);
}

@media (prefers-reduced-motion: reduce) {
  /* A gentler equivalent, not the absence of feedback: the lines cross-fade
     in place with no vertical travel. */
  .ln {
    overflow: visible;
  }

  .ln__i,
  .wordmark,
  .hero__meta--install {
    animation: fade-in 300ms ease both;
  }

  @keyframes fade-in {
    from {
      opacity: 0;
    }
  }
}

/* --------------------------------------------------------------- the shots

   Real captures rather than the extension's CSS mocks, at his call. They are
   framed rather than dropped flat on the page: a capture with no edge reads as
   a stray image, and a frame says "this is a picture of software" without
   pretending to be a browser window. Arc does the same thing and it is the one
   device that makes a screenshot feel deliberate.

   These go stale when YouTube or Letterboxd move a control. That is the trade
   the mocks existed to avoid -- re-shoot rather than patch. */

.shot__img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 10px;
  /* Deliberately unframed, at his call. The captures are of a dark UI on a
     dark page, so with no border the controls read as sitting on the page
     itself rather than inside a picture of somewhere else. */
  background: transparent;
}

.shot__img--stack {
  margin-top: 12px;
}

/* The action-row capture is a 11:1 strip. Left at full width it is a hairline
   in a large column, so it gets a floor and sits centred in its own field. */
.beat__art {
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* ============================================================ interaction

   Everything below the hero used to be inert: no hover, no focus beyond the
   browser default, nothing that acknowledged a pointer. These are the five
   things that were missing, and none of them are decoration -- each one is
   feedback the page owed the reader. */

/* ------------------------------------------------------------------ dock

   The only way to install was a button in the hero, so past the fold there
   was none. This arrives when the hero leaves and stays. Translucent, with a
   bright top edge, because it floats over content rather than replacing a
   strip of it. */
.dock {
  position: fixed;
  z-index: 20;
  left: 50%;
  bottom: 22px;
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 9px 9px 9px 18px;
  border-radius: 999px;
  border: 1px solid var(--line);
  border-top-color: rgba(255, 255, 255, 0.16);
  background: rgba(16, 20, 24, 0.72);
  backdrop-filter: blur(24px) saturate(160%);
  box-shadow: 0 18px 44px -18px rgba(0, 0, 0, 0.9);

  /* Hidden until earned: not display:none, because it has to animate, and not
     opacity alone, because an invisible bar would still eat clicks. */
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translate(-50%, 14px) scale(0.97);
  transition: opacity 420ms var(--ease), transform 420ms var(--ease),
    visibility 0s linear 420ms;
}

.dock.is-up {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translate(-50%, 0) scale(1);
  transition: opacity 420ms var(--ease), transform 420ms var(--ease),
    visibility 0s;
}

.dock__mark {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.dock__name {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

.cta--dock {
  margin-top: 0;
  padding: 9px 16px;
  font-size: 12px;
}

/* ---------------------------------------------------------------- reveals

   Sections arrive as they are reached rather than all existing at once. The
   travel is small on purpose: a section that flies in is a slide transition,
   and the page is not a deck. */
.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 750ms var(--ease), transform 750ms var(--ease);
}

.reveal.is-in {
  opacity: 1;
  transform: none;
}

/* The art follows its copy rather than arriving with it, so the eye is led
   from the sentence to the thing it describes. */
.reveal .beat__art {
  transition: opacity 750ms 120ms var(--ease), transform 750ms 120ms var(--ease);
  opacity: 0;
  transform: translateY(10px);
}

.reveal.is-in .beat__art {
  opacity: 1;
  transform: none;
}

/* ------------------------------------------------------------------ shots

   A capture is the only thing on the page worth looking at closely, so it
   answers a pointer: the frame brightens and the image lifts a little, the
   way a card does when you pick it up. */
.shot__img {
  transition: transform 320ms var(--ease);
}

/* With the frame gone there is no border to brighten, so the response is the
   lift alone. */
.beat__art:hover .shot__img {
  transform: translateY(-3px);
}

/* --------------------------------------------------------------- pressing

   Feedback on the press, not the release. Waiting for the click to acknowledge
   a button is the moment directness dies. */
.cta:active {
  transform: scale(0.975);
  transition: transform 90ms ease-out;
}

/* ------------------------------------------------------------------ focus

   Every interactive thing had the browser default and nothing else. */
.foot__link:focus-visible,
.cta:focus-visible,
.dock a:focus-visible {
  outline: 2px solid var(--green);
  outline-offset: 3px;
  border-radius: 3px;
}

@media (max-width: 700px) {
  .dock {
    left: 16px;
    right: 16px;
    transform: translate(0, 14px) scale(0.97);
  }

  .dock.is-up {
    transform: translate(0, 0) scale(1);
  }

  .dock__mark {
    flex: 1;
  }
}

@media (prefers-reduced-transparency: reduce) {
  .dock {
    background: var(--raised);
    backdrop-filter: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* Still feedback, just not vestibular: things appear rather than travel. */
  .reveal,
  .reveal .beat__art {
    transform: none;
    transition: opacity 260ms ease;
  }

  .dock {
    transform: translate(-50%, 0);
    transition: opacity 260ms ease, visibility 0s linear 260ms;
  }

  .dock.is-up {
    transform: translate(-50%, 0);
  }

  .shot__img,
  .cta:active {
    transition: none;
    transform: none;
  }
}

/* ============================================================== the stage

   The three beats used to be three rows, which is the structure of every
   product page. Now the copy scrolls past one pinned stage and the captures
   pass through it in depth. Scroll position drives it, not time: every
   frame is where the reader's hand put it, so it can be stopped, reversed
   and scrubbed. The stage also tilts a few degrees toward the pointer, on a
   critically damped spring, so it answers the hand as well as the wheel. */

:root {
  /* YouTube's dark ground exactly, so its captures sit flush on the page
     instead of showing as a slightly bluer rectangle. Web only. */
  --ground: #0f0f0f;
  --display: "Bricolage Grotesque", -apple-system, BlinkMacSystemFont,
    "SF Pro Display", "Segoe UI", Inter, Roboto, sans-serif;
}

.display,
.beat__title,
.step__body h3 {
  font-family: var(--display);
  font-optical-sizing: auto;
}

.display {
  font-weight: 800;
  letter-spacing: -0.045em;
  line-height: 0.88;
}

.beat__title {
  font-weight: 700;
  font-size: clamp(26px, 2.7vw, 36px);
  letter-spacing: -0.03em;
}

.beats__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
  gap: 72px;
  align-items: start;
}

/* Each beat owns most of a viewport, so the stage has room to change over
   between them and the reader is never mid-sentence when it does. */
.beats__copy .beat {
  min-height: 78vh;
  padding: 0;
  border: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* The shared rule centres the grid row; in a flex column that same
     align-items centres the copy sideways. */
  align-items: stretch;
}

.beats__copy .beat__art {
  display: none;
}

.beat__mark {
  display: block;
  width: 15px;
  height: 14.5px;
  margin-bottom: 22px;
  background: linear-gradient(to right, var(--green) 0 50%, rgba(0, 224, 84, 0.22) 50% 100%);
}

/* Copy that is not for the scene on stage falls back. --a is |p|. */
.beat__body {
  opacity: calc(1 - var(--a, 0) * 0.72);
  transform: translateY(calc(var(--p, 0) * 10px));
  transition: opacity 120ms linear;
}

/* Pinned at 18vh with 64vh of height, so its box ends at 82vh: the last
   beat's block still reaches below that when its copy is centred, which is
   what keeps the stage pinned through the third scene rather than sliding
   off with the column. */
.beats__stage {
  position: sticky;
  top: 18vh;
  height: 64vh;
  perspective: 1400px;
}

.stage__tilt {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  will-change: transform;
}

/* p < 0: gone, up and back. p > 0: waiting, down and back. p = 0: on. The
   path in is the path out mirrored, so scrolling back plays it in reverse
   exactly, which is the only way a scrubbed sequence stays honest. */
.scene {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  opacity: calc(1 - var(--a, 1));
  transform:
    translate3d(0, calc(var(--p, 1) * 34%), calc(var(--a, 1) * -260px))
    rotateX(calc(var(--p, 1) * -14deg));
  pointer-events: none;
  will-change: transform, opacity;
}

/* No shadow: the YouTube captures carry YouTube's own #0f0f0f, and a shadow
   around that rectangle is what makes it read as a pasted rectangle. */
.scene .shot__img {
  transition: none;
  transform: none;
}

/* Stage-only: the action row is a thin strip and reads small in a tall
   stage, so it runs a little larger than its column. */
.scene[data-scene="0"] .shot__img {
  width: 108%;
  margin-left: -4%;
}

/* ---------------------------------------------------------------- meter

   The dock reads the page's progress in the product's own units: five
   stars, half steps. Same gradient split as the wordmark, so the half star
   is one shape everywhere it appears. */
.meter {
  display: inline-flex;
  gap: 3px;
}

.meter__star {
  width: 12px;
  height: 11.5px;
  clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
  background: linear-gradient(to right, var(--green) 0 50%, rgba(255, 255, 255, 0.14) 50% 100%);
  background-size: 200% 100%;
  background-position: 100% 0;
  transition: background-position 260ms var(--ease);
}

.meter__star.is-half { background-position: 50% 0; }
.meter__star.is-full { background-position: 0% 0; }

/* The wordmark's own half star fills on hover: the mark is a rating, and a
   pointer on it completes it. */
.wordmark .mark,
.dock__mark .mark {
  background-size: 200% 100%;
  background-position: 100% 0;
  transition: background-position 320ms var(--ease);
}

.wordmark:hover .mark,
.dock__mark:hover .mark {
  background-position: 0% 0;
}

/* ----------------------------------------------------------- the plate

   One block of the page commits to the green as ground rather than accent.
   The install steps sit on it: the page's second act, and the one place
   the palette is turned all the way up. */
.setup {
  margin-top: 40px;
  padding: 64px 72px 72px;
  border-radius: 22px;
  background: var(--green);
  color: #04180b;
}

.setup .eyebrow {
  border-bottom-color: rgba(4, 24, 11, 0.22);
  color: rgba(4, 24, 11, 0.62);
}

.setup .step {
  border-bottom-color: rgba(4, 24, 11, 0.16);
}

.setup .step__n {
  color: rgba(4, 24, 11, 0.5);
}

.setup .step__body h3 {
  color: #04180b;
  font-weight: 700;
  font-size: 22px;
  letter-spacing: -0.024em;
}

.setup .step__body p {
  color: rgba(4, 24, 11, 0.78);
}

.setup .key {
  border-color: rgba(4, 24, 11, 0.28);
  background: rgba(4, 24, 11, 0.08);
  color: #04180b;
}

.foot {
  margin-top: 72px;
}

/* The wall is fixed behind everything and the page is a 1180px column, so
   the gutters showed tiles all the way to the footer. The ground is painted
   out sideways from the page instead; clipped above so it never covers the
   hero as the page rides up over it. */
.page {
  box-shadow: 0 0 0 100vw var(--ground);
  clip-path: inset(0 -100vw -100vw -100vw);
  /* Room under the footer for the dock, which otherwise covers its last line. */
  padding-bottom: 170px;
}

/* ------------------------------------------------------------ magnetic

   A button that leans toward the pointer before it is pressed. */
.cta {
  transform: translate3d(var(--mx, 0px), var(--my, 0px), 0);
  transition: transform 200ms var(--ease), box-shadow 200ms var(--ease),
    background 200ms var(--ease);
}

@media (max-width: 900px) {
  .beats__grid {
    display: block;
  }

  .beats__copy .beat {
    min-height: 0;
    padding: 44px 0;
    border-bottom: 1px solid var(--line-soft);
  }

  .beats__copy .beat__body {
    opacity: 1;
    transform: none;
  }

  .beats__copy .beat__art {
    display: block;
    margin-top: 28px;
  }

  .beats__stage {
    display: none;
  }

  .setup {
    padding: 44px 26px 48px;
    border-radius: 16px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .scene {
    transform: none;
    transition: opacity 260ms ease;
  }

  .stage__tilt {
    transform: none;
  }

  .beat__body {
    transform: none;
  }

  .meter__star,
  .wordmark .mark,
  .dock__mark .mark {
    transition: none;
  }
}

/* The shared scrim fades to the old bluer ground; with the ground now
   neutral it needs the same neutral, or a faint band shows where it ends. */
.scrim {
  background:
    linear-gradient(
      to bottom,
      rgba(15, 15, 15, 0.55) 0%,
      rgba(15, 15, 15, 0.72) 38%,
      rgba(15, 15, 15, 0.96) 78%,
      var(--ground) 100%
    ),
    radial-gradient(120% 80% at 12% 20%, rgba(15, 15, 15, 0.2) 0%, rgba(15, 15, 15, 0.9) 70%);
}
