/* ---------------------------------------------------------------------------
   Guppy Gulp — guppygulp.com
   ---------------------------------------------------------------------------
   One screen. No scroll.

   The page is a single viewport-height frame: a bar at the top, the pitch and
   the phone in the middle, a bar at the bottom. Everything is sized in dvh so
   it holds together on a laptop and on a phone without anything being cut off.

   Every colour is lifted from src/game/theme.ts rather than picked to look
   nice next to it. A landing page that is approximately the app's colour is
   worse than one that is obviously a different thing: the near-miss reads as a
   knock-off. These are the same hex values the tank is drawn with.
--------------------------------------------------------------------------- */

:root {
  --bg: #f7f8f4;
  --bg-soft: #eff1ec;
  --card: #ffffff;
  --line: #e7e9e3;

  --ink: #1b1f23;
  --dim: #6b7580;
  --faint: #9aa3ac;

  --cyan: #3fb6e0;
  --cyan-deep: #2591bc;
  --cyan-soft: #dbf0f9;

  --green: #5fc26a;
  --green-deep: #42a64f;

  --water: #6fcfef;
  --water-mid: #3fa8d8;
  --water-deep: #2e86b8;
  /* The deepest stop, from theme.ts. It is referenced by the hero gradient,
     and a single undefined custom property makes the WHOLE background
     declaration invalid at computed-value time - the page then falls through
     to the body's off-white and renders white text on it. That is how this
     one announced itself. */
  --water-abyss: #1e6b99;
  /* The deepest stop, from theme.ts. It is referenced by the hero gradient,
     and a single undefined custom property makes the WHOLE background
     declaration invalid at computed-value time - the page then falls through
     to the body's off-white and renders white text on it. That is how this
     one announced itself. */
  --water-abyss: #1e6b99;

  --radius: 18px;
  --radius-lg: 26px;
  --shadow-card: 0 2px 0 rgba(27, 31, 35, 0.04), 0 10px 30px rgba(27, 31, 35, 0.07);
  --shadow-lift: 0 14px 28px rgba(10, 48, 70, 0.22), 0 40px 70px rgba(10, 48, 70, 0.28);

  --max: 1180px;
}

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: Nunito, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

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

a {
  color: var(--cyan-deep);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

h1,
h2 {
  font-weight: 900;
  letter-spacing: -0.025em;
  line-height: 1.04;
  margin: 0;
}

p {
  margin: 0;
}

/* =========================================================== the one screen */

.screen {
  /* dvh, not vh: on iOS, vh is the height with the browser chrome HIDDEN, so a
     100vh layout is always taller than the window you are actually looking at
     and the bottom bar sits under Safari's toolbar. */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
  color: #fff;

  /* Depth, in three stops rather than two. Real water loses red first and
     goes green-blue before it goes dark, so the middle stop is pulled toward
     the tank's own --water-mid rather than being a straight blend of the ends.
     The radial on top is the surface directly overhead, brightest where you
     would be looking up through it. */
  background:
    /* the walls of the tank, which is most of what stops this reading as
       open sky: light falls off toward both edges, not just downward */
    linear-gradient(90deg, rgba(8, 44, 70, 0.3), transparent 18%, transparent 82%, rgba(8, 44, 70, 0.3)),
    radial-gradient(120% 70% at 50% -12%, rgba(190, 240, 255, 0.55), transparent 62%),
    linear-gradient(
      178deg,
      #7ad6f2 0%,
      var(--water) 16%,
      var(--water-mid) 44%,
      var(--water-deep) 70%,
      var(--water-abyss) 88%,
      #11486e 100%
    );
}

/* Shafts of light from the surface. Two skewed gradient stacks at different
   angles, drifting against each other, which is what stops it reading as a
   static striped overlay. */
.rays,
.caustics,
.snow,
.bubbles,
.weeds,
.bed,
.stones {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
}

.rays {
  inset: -30% -25%;
  background:
    linear-gradient(101deg, rgba(255, 255, 255, 0.22) 0 1.6%, transparent 1.6% 11%),
    linear-gradient(96deg, rgba(255, 255, 255, 0.14) 0 2.6%, transparent 2.6% 17%),
    linear-gradient(108deg, rgba(255, 255, 255, 0.1) 0 2%, transparent 2% 13%),
    linear-gradient(92deg, rgba(255, 255, 255, 0.07) 0 3.4%, transparent 3.4% 23%);
  /* Fades out with depth, the way light does. */
  -webkit-mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, 0.45) 55%, transparent 88%);
  mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, 0.45) 55%, transparent 88%);
  animation: sway 19s ease-in-out infinite alternate;
}

@keyframes sway {
  to {
    transform: translateX(3.5%) skewX(-1.4deg);
  }
}

/* The rippling net of light you get on a shallow floor. Three offset radial
   grids, scaled slowly in and out so the cells breathe instead of pulsing in
   step. */
.caustics {
  background-image:
    radial-gradient(circle at 20% 30%, rgba(255, 255, 255, 0.16) 0 6%, transparent 32%),
    radial-gradient(circle at 70% 18%, rgba(255, 255, 255, 0.12) 0 5%, transparent 28%),
    radial-gradient(circle at 44% 62%, rgba(255, 255, 255, 0.1) 0 7%, transparent 34%);
  background-size: 38% 30%, 46% 34%, 32% 26%;
  opacity: 0.75;
  -webkit-mask-image: linear-gradient(180deg, #000 0%, transparent 70%);
  mask-image: linear-gradient(180deg, #000 0%, transparent 70%);
  animation: ripple 14s ease-in-out infinite alternate;
}

@keyframes ripple {
  to {
    background-position: 6% 4%, -5% 3%, 4% -4%;
    opacity: 0.45;
  }
}

/* Marine snow: the specks of drift that make water read as water rather than
   as a blue panel. Two sizes, falling at different rates. */
.snow {
  background-image:
    radial-gradient(circle, rgba(255, 255, 255, 0.5) 0.8px, transparent 1.1px),
    radial-gradient(circle, rgba(255, 255, 255, 0.3) 0.6px, transparent 0.9px);
  background-size: 190px 190px, 120px 120px;
  opacity: 0.5;
  animation: drift 42s linear infinite;
}

@keyframes drift {
  to {
    background-position: 40px 190px, -30px 120px;
  }
}

/* Bubbles. Each carries its own size, duration, delay and sideways drift as
   custom properties from the markup, so thirteen of them share one keyframe
   and none of them travel together. */
.bubbles i {
  position: absolute;
  bottom: -40px;
  width: var(--s);
  height: var(--s);
  border-radius: 50%;
  background: radial-gradient(
    circle at 32% 28%,
    rgba(255, 255, 255, 0.85),
    rgba(255, 255, 255, 0.22) 46%,
    rgba(255, 255, 255, 0.06) 70%
  );
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5);
  animation: rise var(--d) linear infinite;
  animation-delay: var(--t);
}

@keyframes rise {
  0% {
    transform: translate3d(0, 0, 0) scale(0.7);
    opacity: 0;
  }
  12% {
    opacity: 0.9;
  }
  /* The wobble: a bubble does not go straight up. */
  50% {
    transform: translate3d(var(--x), -55dvh, 0) scale(1);
  }
  88% {
    opacity: 0.7;
  }
  100% {
    transform: translate3d(calc(var(--x) * -0.4), -115dvh, 0) scale(1.1);
    opacity: 0;
  }
}

/* --------------------------------------------------------------- the deep
   Two more layers under the content: kelp rooted at the bed, and the bed
   itself. Together they turn a blue gradient with bubbles in it into
   somewhere. */

/* Kelp. Each blade is a long rounded sliver that pivots about its base,
   which is how a plant moves. Rotating one about its centre instead reads as
   a windscreen wiper, which is what the first pass looked like. */
.weeds i {
  position: absolute;
  bottom: clamp(26px, 5dvh, 54px);
  width: var(--w);
  height: var(--h);
  border-radius: 50% 50% 18% 18% / 46% 46% 6% 6%;
  transform-origin: 50% 100%;
  transform: rotate(var(--lean));
  background: linear-gradient(
    to top,
    rgba(8, 54, 58, 0.62),
    rgba(18, 96, 88, 0.5) 42%,
    rgba(44, 142, 118, 0.3)
  );
  animation: lean var(--t) ease-in-out infinite alternate;
  animation-delay: var(--d);
}

@keyframes lean {
  to {
    transform: rotate(calc(var(--lean) * -1 - 4deg));
  }
}

/* The bed. A flat floor with three ellipses poking above it, darkening as it
   drops so the footer's white type still has something to sit on. */
.bed {
  top: auto;
  height: clamp(60px, 11dvh, 112px);
  /* Transparent at the crest, solid at the floor. A flat fill at one opacity
     was the first version and it read as a beige stripe ruled across the
     page: sand does not have a top edge, it has a depth at which you start
     to see it. */
  background: linear-gradient(
    to bottom,
    rgba(198, 184, 136, 0) 0%,
    rgba(198, 184, 136, 0.34) 28%,
    rgba(176, 162, 116, 0.56) 58%,
    rgba(104, 92, 62, 0.62) 100%
  );
}

/* Dunes: five ellipses of different widths and heights breaking the line,
   each fading out upward like the floor does. Three identical ones sat in a
   row and read as scallops on a bunting. */
.bed::before {
  content: '';
  position: absolute;
  left: -6%;
  right: -6%;
  bottom: 100%;
  height: clamp(40px, 7dvh, 86px);
  background:
    radial-gradient(44% 100% at 9% 100%, rgba(198, 184, 136, 0.34) 0%, rgba(198, 184, 136, 0.08) 62%, transparent 100%),
    radial-gradient(30% 82% at 31% 100%, rgba(198, 184, 136, 0.28) 0%, rgba(198, 184, 136, 0.07) 60%, transparent 100%),
    radial-gradient(52% 94% at 55% 100%, rgba(198, 184, 136, 0.3) 0%, rgba(198, 184, 136, 0.08) 62%, transparent 100%),
    radial-gradient(26% 70% at 74% 100%, rgba(198, 184, 136, 0.26) 0%, rgba(198, 184, 136, 0.07) 58%, transparent 100%),
    radial-gradient(48% 100% at 93% 100%, rgba(198, 184, 136, 0.34) 0%, rgba(198, 184, 136, 0.08) 62%, transparent 100%);
}

/* Air stones. Loose bubbles drift up from anywhere; a stone sends a column
   from one fixed point, and that is the detail that says tank rather than
   open water. */
.stone {
  position: absolute;
  bottom: clamp(30px, 6dvh, 60px);
  width: 2px;
}

.stone i {
  position: absolute;
  bottom: 0;
  left: 0;
  width: var(--s);
  height: var(--s);
  border-radius: 50%;
  background: radial-gradient(
    circle at 32% 28%,
    rgba(255, 255, 255, 0.8),
    rgba(255, 255, 255, 0.2) 48%,
    rgba(255, 255, 255, 0.05) 72%
  );
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.45);
  animation: column var(--d) linear infinite;
  animation-delay: var(--t);
}

@keyframes column {
  0% {
    transform: translate3d(0, 0, 0) scale(0.6);
    opacity: 0;
  }
  10% {
    opacity: 0.85;
  }
  50% {
    transform: translate3d(var(--x), -46dvh, 0) scale(1);
  }
  90% {
    opacity: 0.5;
  }
  100% {
    transform: translate3d(calc(var(--x) * -0.5), -92dvh, 0) scale(1.15);
    opacity: 0;
  }
}

/* The one gutter. Set here and nowhere else, so every band down the page
   lines up and nothing can lose its side padding to a shorthand elsewhere. */
.wrap {
  width: 100%;
  max-width: var(--max);
  margin: 0 auto;
  padding: 0 clamp(20px, 4vw, 40px);
}

/* ------------------------------------------------------------------ top bar */

.bar-top {
  position: relative;
  z-index: 2;
  flex: 0 0 auto;
  padding-top: clamp(16px, 2.4dvh, 26px);
  padding-bottom: clamp(8px, 1.4dvh, 16px);
}

.bar-top .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}

.mark {
  height: clamp(36px, 5.2dvh, 52px);
  width: auto;
  filter: drop-shadow(0 3px 10px rgba(14, 54, 78, 0.35));
}

.bar-links {
  display: flex;
  gap: clamp(16px, 2.4vw, 30px);
}

.bar-links a {
  color: #fff;
  font-weight: 800;
  font-size: 0.95rem;
  opacity: 0.9;
}

.bar-links a:hover {
  opacity: 1;
}

/* -------------------------------------------------------------------- hero */

.hero {
  position: relative;
  z-index: 2;
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  min-height: 0; /* lets the phone shrink instead of overflowing the flex row */
}

.hero .wrap {
  display: grid;
  grid-template-columns: 1.02fr 0.98fr;
  gap: clamp(24px, 4vw, 60px);
  align-items: center;
  min-height: 0;
}

.hero h1 {
  font-size: clamp(2.3rem, 5.4vw, 4.1rem);
  text-shadow: 0 3px 18px rgba(14, 54, 78, 0.26);
}

.sub {
  font-size: clamp(1rem, 1.5vw, 1.22rem);
  font-weight: 700;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.93);
  margin-top: clamp(12px, 2dvh, 20px);
  max-width: 34ch;
  /* Stops the last word being orphaned on a line of its own. The non-breaking
     space in the markup is the belt to this pair of braces. */
  text-wrap: balance;
}

/* The badge is Apple's own artwork, served from our own assets rather than
   hotlinked. Sized by height, because that is how Apple's guidelines specify
   it and the aspect is fixed. */
.badge {
  display: inline-block;
  margin-top: clamp(18px, 3dvh, 30px);
  transition: transform 0.15s ease;
}

.badge:hover {
  transform: translateY(-2px);
  text-decoration: none;
}

.badge img {
  height: clamp(48px, 6.4dvh, 58px);
  width: auto;
}

/* ------------------------------------------------------------ the device
   One image. The frame, the status bar, the home indicator and the capture
   are composited by tools/build_mockup.py, so this is an <img> and nothing
   else: no aspect to agree on, no border-radius to clip against, no
   letterbox to mis-split. Four earlier attempts assembled those in CSS and
   every one of them clipped the artwork somewhere.

   Both maxima with width and height auto: the browser then fits the picture
   inside whichever bound binds first and keeps its ratio either way, so it
   can be neither squashed nor overflow a narrow screen.

   No rotation. A rotated image is resampled whole by the compositor, and at
   this reduction that lands on top of the downscale and turns the phone's
   text to mush. Straight on, every pixel maps to a pixel. */
.device {
  position: relative;
  width: fit-content;
  margin: 0 auto;
}

.device img {
  max-height: min(74dvh, 760px);
  max-width: 100%;
  width: auto;
  height: auto;
  margin: 0 auto;
  display: block;
  filter: drop-shadow(0 18px 30px rgba(8, 40, 60, 0.3))
    drop-shadow(0 42px 70px rgba(8, 40, 60, 0.32));
}

/* ------------------------------------------------------------ the swimmers
   The capture is taken with the app's own fish hidden, and these go over the
   top so they move. Three nested elements, because one element can only run
   one transform: the <i> swims, the <span> bobs, the <img> turns.

   The <i> is a full width rail rather than the fish itself. That is what lets
   the swim be a `transform`, since a percentage translate resolves against
   the element's OWN width - so a rail the width of the tank makes `--run: 40%`
   mean forty percent of the tank, which is what it looks like it means. The
   first version animated `left` instead, and `left` is a layout property: the
   browser re-lays-out the page every frame for it, which is where the
   stuttering came from.

   Timing and geometry are the app's, not invented. Fish.tsx lays the path out
   as `minX + tri * (maxX - minX)`, so a swimmer crosses the WHOLE tank, wall
   to wall, and `tri` is a triangle wave, so it does it at constant speed.
   Hence `linear` and the full width run: an earlier pass turned fish at
   arbitrary points mid-water on an ease, which looked like them changing
   their minds halfway. lapDuration() gives the times, and a lap is out AND
   back, so one leg is half of it.

   The turn shares the swim's delay. It must: with `alternate`, the swim's
   cycle is 2x its duration, the turn's duration IS 2x, so equal delays put
   them in phase and anything else does not. Doubling the turn's delay, which
   is the obvious thing to do and what the first version did, left some fish
   swimming backwards. */

/* The tank, as a window. Its rectangle comes from tank.css, which the capture
   writes, and the overflow is not decoration: without it the fish position
   against the whole phone and swim out over the card below. */
.swim {
  position: absolute;
  left: var(--tank-x);
  top: var(--tank-y);
  width: var(--tank-w);
  height: var(--tank-water);
  overflow: hidden;
  pointer-events: none;
}

.swim .f {
  position: absolute;
  top: var(--top);
  left: 0;
  width: 100%;
  height: 0;
  animation: swim var(--t) linear infinite alternate;
  animation-delay: var(--d);
  will-change: transform;
}

.swim .f span {
  position: absolute;
  top: 0;
  left: var(--from);
  width: var(--w);
  display: block;
  animation: bob var(--t) ease-in-out infinite;
  animation-delay: var(--d);
}

.swim .f img {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 3px 5px rgba(4, 28, 44, 0.3));
  animation: turn calc(var(--t) * 2) linear infinite;
  animation-delay: var(--d);
}

/* Setting off to the left, so it starts out facing the way it is drawn. */
.swim .f.back img {
  animation-name: turnback;
}

@keyframes swim {
  to {
    transform: translateX(var(--run));
  }
}

/* One rise and fall per leg, and both the height and the bank come back to
   zero at 0% and 100% - which, since this runs at exactly the swim's leg
   duration and shares its delay, is exactly where the fish turns.
   That is the app's rule reproduced structurally rather than by hand: a hard
   bank happening at the same instant as a flip reads as one long swooping
   turn rather than as a fish changing its mind, so the tilt has to be out
   from under the turn. Here it cannot be anywhere else. */
@keyframes bob {
  0% {
    transform: translateY(0) rotate(0deg);
  }
  25% {
    transform: translateY(-16%) rotate(calc(var(--bank, 4deg) * -1));
  }
  50% {
    transform: translateY(0) rotate(0deg);
  }
  75% {
    transform: translateY(16%) rotate(var(--bank, 4deg));
  }
  100% {
    transform: translateY(0) rotate(0deg);
  }
}

/* The turn, which is the whole point of this block.
 *
 * It was `steps(1)`: the fish was one width facing left and the next frame
 * the same width facing right, which is a sprite being swapped, not a fish
 * turning. The app does not do that. It interpolates the direction, and
 * interpolating a step passes through zero - the fish goes edge-on, holds a
 * third of its width for an instant, and comes out the other way.
 *
 * So these keyframes narrow through ~0 instead of cutting. The flip at the
 * halfway mark is plain; the one at the cycle boundary is split across it,
 * with 0% and 100% holding the same edge-on value so the loop is seamless
 * and the fish is edge-on at exactly the moment the swim reverses.
 *
 * Never exactly 0: a zero-scale matrix is degenerate and some compositors
 * drop the frame entirely, which puts the flicker back.
 *
 * The window is a percentage of the cycle, so a slow fish turns slowly and a
 * quick one flicks. That falls out for free and it is correct.
 */
@keyframes turn {
  0% {
    transform: scaleX(0.05);
  }
  1.2% {
    transform: scaleX(-0.4);
  }
  2.4%,
  47.6% {
    transform: scaleX(-1);
  }
  48.8% {
    transform: scaleX(-0.4);
  }
  50% {
    transform: scaleX(0.05);
  }
  51.2% {
    transform: scaleX(0.4);
  }
  52.4%,
  97.6% {
    transform: scaleX(1);
  }
  98.8% {
    transform: scaleX(0.4);
  }
  100% {
    transform: scaleX(0.05);
  }
}

/* The same shape for a fish that sets off to the left, with the signs after
   each flip swapped. */
@keyframes turnback {
  0% {
    transform: scaleX(0.05);
  }
  1.2% {
    transform: scaleX(0.4);
  }
  2.4%,
  47.6% {
    transform: scaleX(1);
  }
  48.8% {
    transform: scaleX(0.4);
  }
  50% {
    transform: scaleX(0.05);
  }
  51.2% {
    transform: scaleX(-0.4);
  }
  52.4%,
  97.6% {
    transform: scaleX(-1);
  }
  98.8% {
    transform: scaleX(-0.4);
  }
  100% {
    transform: scaleX(0.05);
  }
}

/* --------------------------------------------------------------- bottom bar */

.bar-foot {
  position: relative;
  z-index: 2;
  flex: 0 0 auto;
  padding: clamp(12px, 2dvh, 20px) 0 clamp(16px, 2.6dvh, 26px);
  border-top: 1px solid rgba(255, 255, 255, 0.18);
  margin-top: clamp(10px, 2dvh, 20px);
}

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

.foot-mark {
  display: flex;
  align-items: center;
  gap: 12px;
}

.foot-mark img {
  width: clamp(34px, 4.4dvh, 42px);
  height: clamp(34px, 4.4dvh, 42px);
  border-radius: 24%;
  box-shadow: 0 4px 12px rgba(10, 48, 70, 0.25);
}

.foot-name {
  font-weight: 900;
  font-size: 0.98rem;
  line-height: 1.2;
}

.foot-name span {
  display: block;
  font-weight: 700;
  font-size: 0.82rem;
  color: rgba(255, 255, 255, 0.72);
}

.copyright {
  font-size: 0.82rem;
  font-weight: 700;
  color: rgba(255, 255, 255, 0.72);
}

/* ============================================ privacy.html and support.html
   These two DO scroll — they are documents, and Apple requires both as URLs
   on the listing. They share one narrow reading column on the app's own
   off-white rather than the underwater treatment. */

.doc-page {
  background: var(--bg);
  color: var(--ink);
}

.doc {
  max-width: 740px;
  margin: 0 auto;
  padding: 44px 24px 90px;
}

.doc h1 {
  font-size: clamp(2rem, 5vw, 2.7rem);
  margin-bottom: 10px;
}

.doc .updated {
  color: var(--faint);
  font-weight: 700;
  font-size: 0.85rem;
  margin-bottom: 34px;
}

.doc h2 {
  font-size: 1.28rem;
  margin: 36px 0 12px;
}

.doc p,
.doc li {
  color: var(--dim);
  font-weight: 600;
  margin-bottom: 14px;
}

.doc ul {
  padding-left: 22px;
}

.doc strong {
  color: var(--ink);
}

.doc .back {
  display: inline-block;
  margin-bottom: 26px;
  font-weight: 800;
}

.callout {
  background: var(--cyan-soft);
  border-radius: var(--radius);
  padding: 22px 24px;
  margin: 24px 0;
}

.callout p {
  color: #15485e;
  margin: 0;
  font-weight: 700;
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-weight: 900;
  font-size: 1rem;
  padding: 14px 26px;
  border-radius: 999px;
  background: var(--cyan);
  color: #fff;
  box-shadow: 0 4px 0 var(--cyan-deep), 0 12px 24px rgba(37, 145, 188, 0.3);
  transition: transform 0.15s ease;
}

.btn:hover {
  text-decoration: none;
  transform: translateY(-2px);
}

/* ------------------------------------------------------------------ narrow
   Under 900px the two columns stack. The screen stays one screen: the phone
   gives up height so the pitch and the badge are never pushed off. */

@media (max-width: 900px) {
  .hero .wrap {
    grid-template-columns: 1fr;
    gap: clamp(14px, 2.4dvh, 26px);
    text-align: center;
    justify-items: center;
  }

  .sub {
    margin-left: auto;
    margin-right: auto;
  }


  .device img {
    max-height: 42dvh;
  }

  .bar-foot .wrap {
    justify-content: center;
    text-align: center;
  }
}

/* Very short windows — a laptop in landscape, or a phone on its side. Give the
   phone up entirely rather than clipping the words. */
@media (max-height: 560px) {
  .device {
    display: none;
  }

  .hero .wrap {
    grid-template-columns: 1fr;
    justify-items: center;
    text-align: center;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bubbles i,
  .rays,
  .caustics,
  .snow,
  .weeds i,
  .stone i,
  .swim .f,
  .swim .f span,
  .swim .f img {
    animation: none;
  }
}
