/* Elysian Villa Retreat, scroll motion
   ────────────────────────────────────────────────────────────
   KEPT IN ITS OWN FILE ON PURPOSE. Two lines in index.html turn
   all of this on. Delete those two lines and the site is exactly
   what it was, with nothing left behind. That matters because the
   audience is people recovering from surgery, and if the motion
   turns out to be wrong for them it has to come out in one edit.

   Rules followed throughout:
   - transform and opacity ONLY, so nothing triggers layout
   - every element is VISIBLE by default and only hidden once JS
     has confirmed it can animate. A dead script leaves a readable
     page, never a blank one
   - prefers-reduced-motion switches everything off completely
   ──────────────────────────────────────────────────────────── */

/* Nothing hides until JS adds this. No JS, no hidden content. */
.motion-on [data-reveal]{
  opacity:0;
  will-change:transform,opacity;
}
.motion-on [data-reveal="up"]{transform:translate3d(0,42px,0)}
.motion-on [data-reveal="up-far"]{transform:translate3d(0,90px,0)}
.motion-on [data-reveal="left"]{transform:translate3d(-56px,0,0)}
.motion-on [data-reveal="right"]{transform:translate3d(56px,0,0)}
.motion-on [data-reveal="scale"]{transform:scale(.9)}
/* Rotation was removed 2026-08-13. Support review watched a card arrive
   tilted AND scaling at once and called it out for an audience that may
   be dizzy. A straight rise reads just as expensive. */
.motion-on [data-reveal="rise-tilt"]{transform:translate3d(0,70px,0) scale(.972)}
/* Was blur(9px). Support review found headings rendering as an
   unreadable low-contrast ghost mid-transition, including "Check your
   dates". Halved, so it still softens in without ever being illegible. */
.motion-on [data-reveal="blur"]{filter:blur(4px);transform:translate3d(0,26px,0)}

.motion-on [data-reveal].is-in{
  opacity:1;
  transform:none;
  filter:none;
  transition:
    opacity .9s cubic-bezier(.22,.61,.36,1),
    transform 1.05s cubic-bezier(.22,.61,.36,1),
    filter .9s cubic-bezier(.22,.61,.36,1);
  transition-delay:var(--d,0ms);
}
.motion-on [data-reveal].is-in{will-change:auto}

/* ── gold rules that draw themselves ──────────────────────── */
.motion-on .rule{transform-origin:left center;transform:scaleX(0);transition:transform 1s cubic-bezier(.22,.61,.36,1)}
.motion-on .rule.mid{transform-origin:center}
.motion-on .rule.is-in{transform:scaleX(1)}

/* ── the wordmark, revealed letter by letter ──────────────── */
.motion-on .word span{
  display:inline-block;
  opacity:0;
  transform:translate3d(0,.55em,0) rotate(4deg);
}
.motion-on .word.is-in span{
  opacity:1;transform:none;
  transition:opacity .7s ease,transform .85s cubic-bezier(.22,.61,.36,1);
  transition-delay:calc(var(--i) * 42ms);
}

/* ── the visible off switch ───────────────────────────────
   Support review made the point that prefers-reduced-motion is an
   operating system setting almost nobody has ever touched, so the
   full effect is what nearly every visitor gets. On a site for
   people who have just had surgery that is not good enough.

   So there is a button. One tap, motion stops, and the choice is
   remembered on that device. */
.motion-toggle{
  position:fixed;right:14px;bottom:14px;z-index:85;
  font:inherit;font-size:.72rem;font-weight:600;
  letter-spacing:.1em;text-transform:uppercase;
  background:var(--paper);color:var(--ink-soft);
  border:1px solid var(--line);padding:11px 15px;cursor:pointer;
  box-shadow:0 2px 14px rgba(28,26,23,.12);
  min-height:44px;
}
.motion-toggle:hover{border-color:var(--gold);color:var(--ink)}
.motion-toggle:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
@media print{.motion-toggle{display:none}}

/* ── the same button, docked in the phone menu ─────────────────────
   Added 2026-08-30 with the move in motion.js. Below 820px the button
   is moved out of <body> and into the header's Menu panel, because
   floating it over a 375px page put it on top of "Request these dates".
   See the long note above addToggle() in motion.js.

   Here it stops being a puck and becomes a menu row: no fixed position,
   no shadow, no border, full width, and the same 45px tap height and
   lettering as the links above it. A hairline rule separates it from
   them, because it is a setting and they are places to go.

   `.top nav .mnav` on the front, matching the note in styles.css: the
   rule that hides the header links on a phone is high specificity, and
   anything in this panel needs to outrank it. */
.top nav .mnav ul .mnav-motion{border-top:1px solid var(--line)}
.top nav .mnav ul .motion-toggle{
  position:static;display:block;width:100%;
  margin:0;padding:13px 20px;      /* 45px, same as the links */
  min-height:0;
  background:transparent;border:0;box-shadow:none;
  text-align:left;
  font-size:.78rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-soft);
}
.top nav .mnav ul .motion-toggle:hover,
.top nav .mnav ul .motion-toggle:focus-visible{
  background:var(--shell);color:var(--ink);border:0;
}

/* ── the veil ─────────────────────────────────────────────
   Harold, 2026-08-15, pointing at luxurycareoc.com: he likes the way
   it moves from page to page. That site is Squarespace, and what he
   is actually seeing is its Ajax loader fading one page into the next
   instead of the browser flashing white between them.

   THIS SITE IS ONE PAGE, so there are no pages to fade between. The
   nearest honest equivalent, and the thing that gives the same
   feeling, is this: a click in the menu fades the page down to ivory,
   moves while nothing is visible, and fades back up already at the
   new section. The jump is hidden, so arriving reads as an arrival
   rather than as a long scroll past everything in between.

   It also runs once on load, so the site composes itself.

   Ivory, not white and not black. A white flash on an ivory page is
   still a flash. */
.veil{
  position:fixed;inset:0;z-index:95;
  background:var(--ivory);
  opacity:0;pointer-events:none;
  transition:opacity .34s cubic-bezier(.4,0,.2,1);
}
.veil.is-up{opacity:1;pointer-events:auto}

/* The opening. Deliberately a CSS animation with `forwards` rather
   than a class JS has to remember to take off: if the script dies
   halfway, the animation still finishes and the page is never left
   sitting under a blank ivory sheet. */
.veil.is-open{animation:veil-open 1.05s cubic-bezier(.22,.61,.36,1) forwards}
@keyframes veil-open{
  from{opacity:1}
  to{opacity:0}
}

/* ── the gold line work, drifting ─────────────────────────
   They fade in once, then ride the same --par parallax value the
   room cards use. Opacity lands back on .16, never on 1, which is
   why these rules have to outrank the generic reveal above. */
.motion-on .orn[data-reveal]{opacity:0}
.motion-on .orn[data-reveal].is-in{
  opacity:.20;  /* keep in step with .orn in styles.css */
  transform:
    translate3d(0,calc(var(--par,0) * 26px),0)
    rotate(calc(var(--rot,0deg) + var(--par,0) * 2deg))
    scaleX(var(--flip,1));
  transition:opacity 1.6s ease,transform .2s linear;
}
@media (max-width:720px){
  .motion-on .orn[data-reveal].is-in{opacity:.16}
}

/* ── scroll progress, a hairline of gold ──────────────────── */
.scroll-bar{
  position:fixed;top:0;left:0;height:2px;z-index:80;
  width:100%;transform:scaleX(0);transform-origin:0 50%;
  background:linear-gradient(90deg,var(--gold-d),var(--gold-l),var(--gold));
  pointer-events:none;
}

/* ── hero, drifts and dissolves as you leave it ───────────── */
.motion-on .hero-logo img{
  transform:translate3d(0,calc(var(--hero) * -70px),0) scale(calc(1 - var(--hero) * .07));
  opacity:calc(1 - var(--hero) * .85);
}
.motion-on .hero .lede,
.motion-on .hero-actions{
  transform:translate3d(0,calc(var(--hero) * -34px),0);
  opacity:calc(1 - var(--hero) * 1.15);
}

/* ── hero, arrives with a slow breath ──────────────────────
   Added 2026-08-19, Harold asked for some kind of movement in the
   hero — it had none of its own, only the page-wide veil fading up
   and the scroll-away drift above. Two things, both scoped to
   .motion-on so they vanish completely if these two files are ever
   removed, and both already covered by the reduced-motion block
   below since JS never adds .motion-on when a visitor has asked for
   less movement.

   1. A ONE-TIME ARRIVAL. The logo, the line of copy, and the two
      buttons rise in on load rather than simply being there, timed
      to land as the ivory veil finishes lifting (veil-open runs
      1.05s, the JS safety timeout clears it by 1.6s at the latest).

      `backwards`, never `forwards`. The lede and the buttons are
      also driven by the scroll-away rule two blocks up, on the same
      transform/opacity properties. `forwards` would pin them at the
      animation's end state and permanently block that rule from
      ever taking hold again. `backwards` only reaches back into the
      delay — it holds the first frame while the element waits its
      turn, then hands the property back to the normal cascade the
      moment it ends. Scroll position is still ~0 when that happens,
      so the handoff lands on the same values and nothing jumps.

   2. A SLOW AMBIENT GLOW behind the logo, breathing in and out on a
      7s cycle, never past a 5% scale change. This is the first thing
      a guest who just had surgery sees. It is meant to read as calm,
      close to a resting breath, not as decoration competing with her
      logo art. */
.motion-on .hero-logo,
.motion-on .hero .lede,
.motion-on .hero-actions{
  animation: hero-arrive 1s cubic-bezier(.22,.61,.36,1) backwards;
}
.motion-on .hero-logo{animation-delay:.3s}
.motion-on .hero .lede{animation-delay:.55s}
.motion-on .hero-actions{animation-delay:.72s}
@keyframes hero-arrive{
  from{opacity:0;transform:translate3d(0,20px,0)}
  to{opacity:1;transform:none}
}

.motion-on .hero-logo{position:relative}
.motion-on .hero-logo::before{
  content:"";position:absolute;inset:-16%;z-index:-1;border-radius:50%;
  background:radial-gradient(closest-side, var(--gold-l) 0%, rgba(244,231,189,0) 70%);
  opacity:0;
  animation: hero-breathe 7s ease-in-out 1.4s infinite;
}
@keyframes hero-breathe{
  0%,100%{opacity:.3;transform:scale(.97)}
  50%{opacity:.5;transform:scale(1.05)}
}

/* ── the house, behind everything ──────────────────────────
   Added 2026-08-19. Seven photographs from Monday's shoot, stacked and
   cross faded against scroll position by motion.js, so the page reads
   as a slow walk from the front door through to the back deck.

   THE SCRIM IS THE WHOLE DESIGN. An ivory sheet at .88 sits over the
   photographs, so roughly a tenth of the picture survives to the eye.
   Everything else about this is ordinary; that number is what keeps it
   tasteful instead of a hotel website. If the rooms ever become
   properly legible behind the text, this has gone wrong — raise the
   scrim toward .92, never drop it toward .80.

   The DOM for all of this is built by motion.js and only exists while
   motion is on, so there is nothing here to switch off for reduced
   motion further down the file. */
.house-bg{
  position:fixed;inset:0;z-index:0;
  pointer-events:none;overflow:hidden;
  /* Under the fixed layer, so a photograph that has not arrived yet
     never shows as a white hole. */
  background:var(--ivory);
}
.house-frame{
  position:absolute;inset:0;opacity:0;
  transform:scale(1.06);
  /* No transition. Opacity and transform are written every frame from
     the scroll position, and a transition on top of that would fight
     the scroll and lag behind it. */
}
.house-frame img{width:100%;height:100%;object-fit:cover;display:block}

/* The scrim. Last child, so it paints over every frame. Slightly
   heavier top and bottom, which settles the header and the footer
   onto something solid. */
.house-bg::after{
  content:"";position:absolute;inset:0;
  background:
    linear-gradient(
      180deg,
      rgba(253,252,250,.93) 0%,
      rgba(253,252,250,.87) 22%,
      rgba(253,252,250,.87) 78%,
      rgba(253,252,250,.93) 100%
    );
}

/* Everything on the page is lifted over the backdrop. The header is
   already at z-index 60 and the veil at 95, both well clear. */
.house-on main,
.house-on footer{position:relative;z-index:1}

/* The three sections that paint their own solid colour would hide the
   house completely. Given the same colours with alpha, they instead
   read as tinted glass over it, and the photograph carries through the
   whole page instead of appearing in gaps. Kept high enough that body
   text is on a near solid ground: the difference is visible as depth,
   not as a picture. */
.house-on .steps{background:rgba(255,255,255,.84)}
.house-on section.tint{background:rgba(247,243,236,.84)}
.house-on section.paper{background:rgba(255,255,255,.86)}

@media (max-width:900px){
  /* Heavier on a phone. The text is bigger relative to the screen, the
     photograph is cropped much harder by the narrow viewport, and the
     result is busier for the same scrim value. */
  .house-bg::after{background:rgba(253,252,250,.91)}
}

/* ── parallax on the section that holds the four words ────── */
.motion-on .steps{overflow:hidden}
.motion-on .step .n{
  transform:translate3d(0,calc(var(--par,0) * -14px),0);
}

/* ── rooms, a staggered rise with a whisper of tilt ───────── */
.motion-on .room .shot span{
  transform:scale(calc(1 + var(--par,0) * .06));
  transition:transform .2s linear;
}

/* ── the pinned vision quote ──────────────────────────────── */
.motion-on .pin-quote{
  position:sticky;top:96px;
}
.motion-on .pin-quote blockquote{
  transform:scale(calc(1 - var(--pin,0) * .04));
  opacity:calc(1 - var(--pin,0) * .55);
}
/* ⚠️ Only while Vision and Mission sit SIDE BY SIDE. The sticky pin exists so
   the Vision column holds still and fades while the Mission column scrolls up
   beside it — which needs two columns to be true. `.vision` collapses to one
   column at 860px, and below that the pinned block just parks itself at the
   top of the screen while the Mission text slides underneath it. Harold, on
   his phone: "our mission and our vision scroll into each other."
   So: below the same 860px the pin is switched off entirely, including the
   fade — a static block left at opacity .45 would look like a rendering
   fault rather than an effect. Keep this breakpoint and the one on `.vision`
   in styles.css the same number. */
@media (max-width:860px){
  .motion-on .pin-quote{position:static;top:auto}
  .motion-on .pin-quote blockquote{transform:none;opacity:1}
}

/* ── booking steps, each numbered badge pops in ───────────── */
.motion-on .bstep>h3 .n{transform:scale(.4);opacity:0}
.motion-on .bstep.is-in>h3 .n{
  transform:none;opacity:1;
  transition:transform .6s cubic-bezier(.34,1.56,.64,1) .12s,opacity .4s ease .12s;
}

/* ── the footer lotus rule ────────────────────────────────── */
.motion-on footer .legal{opacity:0;transform:translate3d(0,20px,0)}
.motion-on footer .legal.is-in{
  opacity:1;transform:none;
  transition:opacity 1s ease,transform 1s cubic-bezier(.22,.61,.36,1);
}

/* ══════════════════════════════════════════════════════════
   OFF. Completely. For anyone who has asked their device for
   less movement, and for print.

   This is not decoration. The people using this site have just
   had surgery. Some of them are nauseated, on painkillers, or
   dizzy. Motion is not a small thing for them.
   ══════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion:reduce){
  .motion-on [data-reveal],
  .motion-on [data-reveal].is-in,
  .motion-on .word span,
  .motion-on .bstep>h3 .n,
  .motion-on footer .legal{
    opacity:1 !important;
    transform:none !important;
    filter:none !important;
    transition:none !important;
  }
  .motion-on .rule{transform:scaleX(1) !important;transition:none !important}
  .motion-on .hero-logo,
  .motion-on .hero-logo img,
  .motion-on .hero .lede,
  .motion-on .hero-actions,
  .motion-on .pin-quote blockquote,
  .motion-on .room .shot span,
  .motion-on .step .n{transform:none !important;opacity:1 !important;animation:none !important}
  .motion-on .hero-logo::before{display:none !important}
  .motion-on .pin-quote{position:static}
  .scroll-bar{display:none}
  .motion-toggle{display:none}
  /* The row it now lives in on a phone goes with it, or the menu keeps a
     blank line with a rule over it. Added 2026-08-30. */
  .mnav-motion{display:none}
  /* No veil at all. A full screen fade is exactly the kind of movement
     somebody asks their device to stop doing. */
  .veil{display:none !important;animation:none !important}
  .motion-on .orn[data-reveal],
  .motion-on .orn[data-reveal].is-in{
    opacity:.20;transition:none !important;
    transform:rotate(var(--rot,0deg)) scaleX(var(--flip,1)) !important;
  }
}

@media print{
  [data-reveal]{opacity:1 !important;transform:none !important}
  .scroll-bar{display:none}
}


/* ---------- the house, behind the hero ----------
   Harold, 2026-08-19, after the four-frame slideshow was pulled: "I like the
   way it zooms in but think it should be a subtle picture or video of the
   outside of the house that zooms in as you scroll down, zooms out as you
   scroll up."

   So there is no timer here and no cross fade. One photograph, and the zoom
   is bound to `--hero`, the scroll progress motion.js already writes every
   frame (0 while the hero is in place, 1 by the time it has left). Tying it
   to position rather than to time is the whole point: scroll back up and it
   unwinds itself, exactly in step with the wheel.

   No transition on the transform, deliberately. A transition would lag
   behind the scroll and feel like the picture is chasing the page.

   Everything is under .hero-on, which motion.js only adds when it builds the
   layer. Motion off and none of this exists. */
.hero-on .hero{position:relative;overflow:hidden}
.hero-on .hero > *:not(.hero-photo){position:relative;z-index:1}

.hero-photo{position:absolute;inset:0;overflow:hidden;z-index:0;background:var(--ivory)}
/* Harold, after seeing the first version: "I want the scroll down on the hero
   to almost be like a drone zooming in, I can barely tell anything is
   happening now."

   Two things were wrong, and the range was only one of them.

   1. The push was 1.02 to 1.18. Sixteen percent spread over a thousand pixels
      of scrolling is under the threshold where the eye reads it as movement at
      all. It is now 1.00 to 1.90 — Harold set both ends of that himself, by
      naming the photograph he wanted it to START at (the wide establishing
      shot from down the lawn) and saying it should finish about where the
      hero used to open. 1.90 is the number that closes one to the other.
   2. It was tied to `--hero`, which is linear over the full height of the
      hero — so most of the zoom happened after the hero had scrolled away.
      `--dive` is the same progress front loaded (see motion.js), so the move
      happens while there is still a photograph on screen to watch.

   The 6% drift downward is what turns a zoom into a camera. A pure scale
   reads as an image getting bigger; scale plus a little vertical travel reads
   as something moving toward the house. Small on purpose — this is a drone
   easing in, not a fly-by. */
.hero-photo img{
  width:100%;height:100%;object-fit:cover;display:block;
  transform-origin:50% 40%;
  transform:
    translate3d(0, calc(var(--dive,0) * 5%), 0)
    scale(calc(1 + var(--dive,0) * .90));
  will-change:transform;
}

/* The wash. Heavier than the one on the pulled slideshow, because "subtle" is
   the brief and this is a picture of a whole building rather than a corner of
   a room. Measured over the darkest 2% patch inside the area the hero content
   sits in — the shade under the porch roof, effectively black — body ink
   (#1c1a17) reads 9.7:1 at the .76 shipped here, 11.1:1 at .82 and 8.6:1 at
   .72. WCAG asks 4.5:1.
   .82 was tried first and the house vanished into the page — subtle turned
   into invisible. .76 is the number where it still reads as a building. This
   is the one knob: raise it to fade the house back, lower it to bring it
   forward, and do not go below .70.
   The heavy bottom edge dissolves the photograph into the ivory page instead
   of ending it on a hard line above the next section. */
.hero-photo::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(
    180deg,
    rgba(253,252,250,.78) 0%,
    rgba(253,252,250,.76) 42%,
    rgba(253,252,250,.80) 74%,
    rgba(253,252,250,.94) 100%
  );
}

/* A phone crops the same photograph much harder and shows the type larger
   against it, so the wash goes up again. */
@media (max-width:900px){
  .hero-photo::after{
    background:linear-gradient(
      180deg,
      rgba(253,252,250,.88) 0%,
      rgba(253,252,250,.86) 42%,
      rgba(253,252,250,.90) 74%,
      rgba(253,252,250,.97) 100%
    );
  }
}

/* ---------- the back of the house, behind the footer ----------
   The hero move, repeated at the other end of the page, on photo 7: the rear
   elevation, the deck and the stairs. Front of the house as you arrive, back
   of it as you leave.

   Its own progress variable. `--hero` is measured from the top of the page and
   finished long before anyone gets down here, so this rides `--sink`, which
   motion.js runs 0 to 1 as the footer rises into view — same front loaded
   curve, so the move happens while the band is arriving rather than after it
   has settled.

   Travel is 1.00 to 1.42, and both ends of that were set by Harold rather than
   chosen: "the image should start at the full size so you see the house and
   the yard then zoom in so you only see the full house."

   Start: the whole photograph, house and yard and trees, letterboxed in the
   band (see the note on `contain` below).
   End: 1.42, which is the measured number that makes the BUILDING span the
   full width of the band — the house spans 0.71 of the frame, and 1/0.71 is
   1.41. Push past it and the shed and the far gable start being cut off,
   which is no longer "the full house".

   The drift goes UP here (-4%) where the hero drifts down. Both are moving
   with the page rather than against it, which is what keeps it feeling like
   one camera rather than two effects. */
.footer-on footer{position:relative;overflow:hidden}
.footer-on footer > *:not(.footer-photo){position:relative;z-index:1}

.footer-photo{position:absolute;inset:0;overflow:hidden;z-index:0;background:var(--ink)}
/* ⚠️ `contain`, not `cover`, and that is the whole trick.
   Harold: "on the bottom lets have the scroll go the opposite, start as wide
   as possible and then zoom in."
   The footer band is about 946x624, so 1.52:1, while the photograph is a
   2.44:1 panorama. Under `cover` the browser fits it by height and throws away
   a third of its width before anyone sees it — which is the opposite of wide.
   Under `contain` the whole picture is there at rest, fitted to the width,
   with the band's own ink showing above and below it. That is not a gap: the
   layer's background is var(--ink), the same colour as the footer, and the
   wash sits over both, so it reads as a letterboxed strip rather than a hole.
   The push then grows it to 1.42, where the house itself spans the band edge
   to edge. It never reaches the scale that would fill the band top to bottom
   (about 1.6), and that is correct: the house is a 2.8:1 shape in a 1.5:1
   band, so filling it vertically would mean cutting the ends off the building.
   A strip of sky and lawn stays above and below it, which is what "the full
   house" actually looks like in a band this shape. */
.footer-photo img{
  width:100%;height:100%;object-fit:contain;display:block;
  /* The origin is the middle of the BUILDING, not the middle of the picture.
     Measured off the frame rather than eyeballed: the house spans x 0.16-0.87
     and y 0.27-0.90, so its centre is 52% / 59%. Zooming about the centre of
     the photograph instead would drift the roof out of the top of the band. */
  transform-origin:52% 59%;
  transform:
    translate3d(0, calc(var(--sink,0) * -1.5%), 0)
    scale(calc(1 + var(--sink,0) * .42));
  will-change:transform;
}

/* The wash is ink here, not ivory, and it is measured against the BRIGHTEST
   part of the photograph rather than the darkest, because the type in this
   band is pale and the risk runs the other way.
   Over the sky, the brightest 2% of the frame: body copy (#cfc7bb) reads
   8.3:1 and the gold headings (#c19a45) read 5.3:1 at .88. The gold is the
   limiting element — it is small, uppercase and letterspaced, so it needs
   4.5:1 and gets nowhere near that at .70. Do not take this below .82.
   The photograph itself was already darkened in the export rather than being
   buried under more scrim here, which is why any of it survives at all. */
.footer-photo::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(
    180deg,
    rgba(28,26,23,.84) 0%,
    rgba(28,26,23,.86) 45%,
    rgba(28,26,23,.90) 100%
  );
}

@media (max-width:900px){
  .footer-photo::after{
    background:linear-gradient(
      180deg,
      rgba(28,26,23,.88) 0%,
      rgba(28,26,23,.90) 45%,
      rgba(28,26,23,.93) 100%
    );
  }
}

/* The backdrop fades up once the photograph has decoded. Before that the layer
   is the page's own ivory, so what a visitor sees is the logo on ivory —
   exactly the page they would get with motion off — and then the house
   arriving behind it, rather than a flash. */
.hero-photo{opacity:0;transition:opacity .9s ease-out}
.hero-photo.is-ready{opacity:1}
