/* Moonbeam Cottage — the homepage.
   Loaded by index.html after css/base.css, which carries the
   palette, the reset, the night sky, the card, the entrance cascade, the pill
   button and the footer. Everything here is either the scene itself or the
   handful of places this page departs from the rest of the site.
   Behaviour lives in js/cottage-scene.js. */

/* ---- departures from base.css ---- */

/* No radial glow behind the card: the picture is the light source on this
   page, and the body tint follows the scene. */
body{
  background:linear-gradient(170deg,var(--night) 0%,#171c2c 100%);
  padding:clamp(20px,4vw,56px) 16px;
  padding-top:max(clamp(20px,4vw,56px),env(safe-area-inset-top));
  padding-bottom:max(clamp(28px,5vw,56px),env(safe-area-inset-bottom));
  transition:background 1.2s ease;
}
body[data-tod="day"]{background:linear-gradient(170deg,#5c6b7d 0%,#39434f 100%)}
body[data-tod="night"]{background:linear-gradient(170deg,var(--night) 0%,#12161f 100%)}

/* Wider and quieter than the holding page's card — it is a mount for the
   picture, so no stitched border and no moonbeam sweeping over it. The scene
   has its own .sheen doing that job inside the frame. */
.card{
  width:min(1180px,100%);
  background:rgba(247,241,230,.05);
  border-radius:26px;
  padding:clamp(18px,2.4vw,28px);
  box-shadow:0 30px 80px rgba(0,0,0,.4);
  overflow:visible;
}
.card::before,.card::after{content:none}
@keyframes cardIn{from{opacity:0;transform:translateY(22px)}to{opacity:1;transform:none}}

/* Only six things to reveal, and they wait for the scene, so the cascade is
   shorter and starts later than the base one. */
@keyframes riseIn{
  from{opacity:0;transform:translateY(14px)}
  to  {opacity:1;transform:none}
}
.d1{animation-delay:.30s}.d2{animation-delay:.42s}.d3{animation-delay:.54s}
.d4{animation-delay:.66s}.d5{animation-delay:.78s}.d6{animation-delay:.90s}

/* The name sits right under the picture, so it takes neither the tracking nor
   the moon-glow the holding page gives its title — both fight the art. */
h1{
  font-size:clamp(1.9rem,5vw,3rem);
  margin:clamp(6px,.9vw,10px) 0 6px;
  letter-spacing:normal;
  text-shadow:none;
}
/* The kicker, not h1, holds the picture off the copy — it is the first thing
   under the scene, and the two lines belong together as one heading. */
.kicker{
  margin-top:clamp(20px,3vw,34px);
  font-size:clamp(.86rem,2.1vw,1.12rem);letter-spacing:.2em;text-transform:uppercase;
  color:var(--lavender);
}

/* The live pip, in front of "Live weather at". Sized in `em` so it tracks the
   kicker at any width. It starts `hidden` in the markup and the script unhides
   it only once the API has answered — the heading claims live weather whatever
   happened, so this dot is what makes the claim true.

   A slow breathing dot, not a blinking recording light. Opacity rests at .85 so
   the wildcard in base.css can switch the animation off and leave it lit. */
.pip{
  display:inline-block;width:.5em;height:.5em;margin-right:.7em;
  border-radius:50%;background:var(--moss);vertical-align:middle;
  opacity:.85;box-shadow:0 0 6px rgba(125,144,112,.6);
  animation:pipPulse 3.6s ease-in-out infinite;
}
.pip[hidden]{display:none}   /* a class rule would otherwise beat the UA's */
@keyframes pipPulse{
  0%,100%{opacity:.62;box-shadow:0 0 4px rgba(125,144,112,.4)}
  50%    {opacity:1;  box-shadow:0 0 9px rgba(125,144,112,.85)}
}

/* ---- scene ---- */
.scene{
  position:relative;width:100%;
  aspect-ratio:1387/752;
  border-radius:18px;overflow:hidden;
  background:#2a3040;
  box-shadow:0 18px 50px rgba(0,0,0,.45), inset 0 0 0 1px rgba(232,220,200,.14);
}
/* The art's own box, centred in .scene and always 1387/752 whatever .scene has
   been cropped to. Everything positioned as a % of the picture — the lamps and
   the moon — lives inside here, so a crop moves them with the art instead of
   sliding them off it.

   Default and any *wider* crop: driven by width, so height overflows and the
   crop takes off the top and bottom. A *taller* crop (portrait phones) has to
   flip to height-driven — see the media query at the bottom. */
.plate{
  position:absolute;left:50%;top:50%;
  transform:translate(-50%,-50%);
  width:100%;height:auto;
  aspect-ratio:1387/752;
  /* the outgoing picture is parked here for the length of a cross-fade */
  background-size:cover;background-position:center;
}
/* the backdrop plate only — must NOT match the moon's <img>, which lives
   inside .moon and would otherwise inherit opacity:0 from here */
#sceneImg{
  position:absolute;inset:0;
  width:100%;height:100%;object-fit:cover;display:block;
  opacity:0;transition:opacity 1.1s ease;
}
#sceneImg.on{opacity:1}
/* One plate replacing another — the provisional scene settling into the real
   weather. The script parks the outgoing picture on .plate's background first,
   so the new one fades up over it rather than over an empty box: `.swapping`
   drops the incoming plate to invisible with no transition, `.settling` is the
   quicker-than-first-load fade it then rides back up. Order matters — .swapping
   has to come after .on to win on source order. */
#sceneImg.swapping{transition:none;opacity:0}
#sceneImg.settling{transition:opacity .45s ease}

/* ---- window lamps ----
   Four warm blooms sitting exactly over the lit windows, blended onto the
   plate with `screen` so they add light instead of painting over the stitches.
   The three night plates were drawn from the same camera, so one set of
   coordinates fits all of them; the centres below are the measured centres of
   the lit panes in the 1387x752 art, as a % of the scene.

   Night only, and only once the plate has faded in — otherwise they'd hang in
   the dark while the image loads. Both conditions are already on the page:
   `data-tod` on <body> and `.ready` on the scene, both set by the script. */
.lamps{
  position:absolute;inset:0;pointer-events:none;z-index:1;
  opacity:0;transition:opacity 1.6s ease;
}
body[data-tod="night"] .scene.ready .lamps{opacity:1}

.lamp{
  position:absolute;
  left:var(--x);top:var(--y);width:var(--w);
  aspect-ratio:1;
  transform:translate(-50%,-50%);   /* --x/--y are centres, not corners */
  border-radius:50%;
  background:radial-gradient(circle,
    rgba(255,206,124,.55) 0%,
    rgba(255,186,96,.30) 30%,
    rgba(250,170,80,.12) 55%,
    transparent 74%);
  mix-blend-mode:screen;
  /* opacity + `scale` are animated, never `transform` — the translate above
     has to survive the animation */
  animation:flicker var(--dur) ease-in-out infinite;
  animation-delay:var(--delay);
}
/* Durations don't divide into each other, and the negative delays start each
   lamp part-way in, so the four never settle into step. */
.lamp-dormer  {--x:52.16%;--y:38.90%;--w:13%;--dur:5.3s;--delay:-1.4s}
.lamp-parlour {--x:44.60%;--y:60.77%;--w:17%;--dur:6.7s;--delay:-3.2s}
.lamp-kitchen {--x:61.57%;--y:60.51%;--w:17%;--dur:4.9s;--delay:-0.6s}
.lamp-scullery{--x:30.86%;--y:58.84%;--w:11%;--dur:7.9s;--delay:-2.5s}

/* Candlelight, not a failing bulb — the dips are shallow and never go dark. */
@keyframes flicker{
  0%,100%{opacity:.78;scale:1}
  12%    {opacity:.92;scale:1.012}
  23%    {opacity:.70;scale:.996}
  35%    {opacity:1;  scale:1.02}
  47%    {opacity:.82;scale:1.004}
  58%    {opacity:.95;scale:1.014}
  69%    {opacity:.74;scale:.998}
  81%    {opacity:.90;scale:1.008}
  92%    {opacity:.80;scale:1.002}
}

/* ---- moon overlay ----
   Positioned as a % of the scene so it tracks the art at any size.
   Numbers come from the disc's measured centre in the source images. */
.moon{
  position:absolute;
  left:13.05%; top:2.53%; width:15%;
  opacity:0;transition:opacity 1.4s ease;
  pointer-events:none;z-index:2;
  animation:moonDrift 16s ease-in-out infinite;
}
.moon.on{opacity:1}
.moon img{
  width:100%;height:auto;display:block;
  position:relative;z-index:1;opacity:1;
}
/* glow behind the disc; strength comes from the illuminated fraction */
.moon::before{
  content:"";position:absolute;left:-55%;top:-55%;width:210%;height:210%;
  border-radius:50%;
  background:radial-gradient(circle,rgba(223,231,242,.30) 0%,rgba(223,231,242,.12) 38%,transparent 68%);
  opacity:var(--glow,.5);
  animation:moonGlow 7s ease-in-out infinite;
}
@keyframes moonDrift{0%,100%{transform:translateY(0)}50%{transform:translateY(-1.2%)}}
@keyframes moonGlow{0%,100%{filter:brightness(.9)}50%{filter:brightness(1.25)}}

/* ---- the same moon, seen through cloud ----
   Used on the cloudy/windy night plate. The disc loses its edge and most of
   its contrast; the halo spreads wider and does the work instead. The whole
   thing also fades in and out slowly, as if cloud were passing over it. */
/* opacity stays on .moon so it still cross-fades in with the plate; the
   passing cloud is animated on the disc inside it, not on the wrapper */
.moon.veiled.on{opacity:.74}
.moon.veiled img{
  filter:blur(2.4px) brightness(.95) contrast(.84);
  animation:cloudPass 21s ease-in-out infinite;
}
.moon.veiled::before{
  left:-90%;top:-90%;width:280%;height:280%;
  background:radial-gradient(circle,rgba(223,231,242,.30) 0%,rgba(223,231,242,.15) 45%,transparent 72%);
}
@keyframes cloudPass{
  0%,100%{opacity:1}
  38%    {opacity:.68}
  70%    {opacity:1}
}

/* ---- the owl ----
   One cut-out, six perches. Which one is showing is a class the script adds on
   the hour — see perchFor() in js/cottage-scene.js. Everything about
   *where* stays here, the same way the lamps and the moon do.

   --x/--y are the owl's **feet**, not its centre: a perch is a surface, and the
   thing that has to land on it is the bottom of the picture. Hence
   translate(-50%,-100%) rather than the lamps' (-50%,-50%). --w is a width, so
   the height follows the art and nothing has to be kept in step by hand.

   Percentages of .plate, not .scene — same reason as the moon and the lamps:
   a cropped phone layout has to move the owl with the picture, not with the
   window. All six sit inside 12%-78% of the plate, because that is roughly what
   survives the 4:3 portrait crop (which shows about 9%-81%). Push one further
   out and the owl vanishes on a phone.

   Nine plates, one set of coordinates: every plate is 1387x752 off the same
   camera, so a surface is a surface on all of them. */
.owl{
  position:absolute;
  left:var(--x);top:var(--y);width:var(--w);
  transform:translate(-50%,-100%);
  transform-origin:50% 100%;         /* so the breath lifts the head, not the feet */
  /* the 1s delay is the plate's fade getting out of the way first — see below */
  opacity:0;transition:opacity 1.2s ease 1s;
  pointer-events:none;z-index:2;
  animation:owlBreathe 6.5s ease-in-out infinite;
}
/* No coordinates here on purpose: without a perch class the owl has no width or
   position, and it stays invisible until the script names one.

   Two conditions, the same pair that gates the lamps: `.on` is the script naming
   a perch, `.ready` is the plate actually on screen. The owl is a small cut-out
   named in the markup, so it downloads early and finishes long before a ~150KB
   plate on a slow connection — and it used to fade up on its own over the empty
   scene box, which is both the wrong order and the whole surprise given away.
   The delay on the transition above then holds it back past the plate's own 1.1s
   first-appearance fade, so the cottage settles and *then* there is an owl in it
   somewhere. */
.scene.ready .owl.on{opacity:1}

.perch-fence {--x:15.5%;--y:57.3%;--w:3.4%}   /* the rail fence, left of the garden */
.perch-table  {--x:20.0%;--y:64.3%;--w:3.8%}   /* the round stone table */
.perch-porch  {--x:30.0%;--y:51.5%;--w:3.2%}   /* the little scullery roof */
.perch-thatch {--x:56.0%;--y:16.8%;--w:3.6%}   /* the crest of the thatch */
.perch-step   {--x:51.0%;--y:73.8%;--w:4.2%}   /* the doorstep, next to the rabbit */
.perch-wall   {--x:70.0%;--y:75.5%;--w:4.4%}   /* the stone wall along the path */

/* The owl is a studio photograph and the plates are lit scenes, so it needs
   settling into them: a small contact shadow to sit it on its perch, and at
   night the cooler, dimmer light everything else in the picture is under. */
.owl img{
  width:100%;height:auto;display:block;
  filter:drop-shadow(0 1px 1px rgba(20,26,40,.5));
}
body[data-tod="night"] .owl img{
  filter:brightness(.58) saturate(.62) contrast(1.04)
         drop-shadow(0 1px 1px rgba(10,14,24,.6));
}

/* Barely there — the owl is a still object in a still garden, and anything more
   than a breath reads as a glitch. Scales about the feet (transform-origin
   above) and uses the standalone `scale`, never `transform`, which is holding
   the perch alignment. */
@keyframes owlBreathe{
  0%,100%{scale:1}
  50%    {scale:1.014}
}

/* ---- the hidden feather ----
   Ten places in the garden it might have come to rest, one of them chosen fresh
   on every load — see SPOTS and nextSpot() in js/cottage-scene.js. Same rules as
   the owl, the lamps and the moon: percentages of .plate, not .scene, and all
   ten inside 12%-78% so the 4:3 portrait crop can't hide one somewhere nobody
   could ever look.

   --x/--y are the feather's **centre**, not its feet: it lies on things rather
   than standing on them, so there is nothing to land. --rot is the angle it
   settled at. Position and tilt use the standalone `translate` and `rotate`
   properties — which leaves `transform` free for the flight when it is clicked,
   and for the resting sway in between.

   It is a <button>, undone: the feather is the one thing on this page a visitor
   is meant to do something with, and finding it should not need a mouse. */
.hidden-feather{
  position:absolute;
  left:var(--x);top:var(--y);width:var(--w);
  translate:-50% -50%;
  appearance:none;-webkit-appearance:none;
  background:none;border:0;padding:0;
  cursor:pointer;
  z-index:3;
  /* the delay is the plate's 1.1s fade and then the owl's own entrance getting
     out of the way, so the last thing to arrive in the garden is this */
  opacity:0;transition:opacity 1.4s ease 1.7s;
}
/* No coordinates here on purpose — with no spot class the feather has no width
   or position and stays invisible until the script hides it somewhere. The pair
   of conditions is the owl's: `.on` is the script having chosen a spot, `.ready`
   is the plate actually being on screen to hide it in. */
.scene.ready .hidden-feather.on{opacity:.94}
.hidden-feather picture{display:block}
.hidden-feather img{
  width:100%;height:auto;display:block;
  rotate:var(--rot,0deg);
  filter:drop-shadow(0 1px 1px rgba(20,26,40,.45));
  /* the sway is on `transform`, which composes on top of the `rotate` above, so
     the spot's own angle survives it */
  animation:featherRest 7.5s ease-in-out infinite;
}
/* Barely lit at night, but not put out the way the owl is — a feather with
   sparkles stitched into it is the one thing in the garden allowed to glow.
   Scoped to .plate: the kitchen is lamplit whatever the sky is doing, and the
   feather hidden in there has no business being dimmed by the weather. */
body[data-tod="night"] .plate .hidden-feather img{
  filter:brightness(.74) saturate(.8)
         drop-shadow(0 1px 2px rgba(10,14,24,.6));
}
/* Sweeping a mouse over the garden is a fair way to find it, and a focus ring is
   the only way a keyboard has. */
.hidden-feather:hover img{filter:brightness(1.12) saturate(1.05) drop-shadow(0 2px 4px rgba(10,14,24,.5))}
body[data-tod="night"] .plate .hidden-feather:hover img{filter:brightness(1) saturate(.95) drop-shadow(0 2px 4px rgba(10,14,24,.6))}
.hidden-feather:focus-visible{outline:2px solid rgba(223,231,242,.8);outline-offset:6px;border-radius:4px}

@keyframes featherRest{
  0%,100%{transform:rotate(-1.2deg)}
  50%    {transform:rotate(1.2deg)}
}

/* The ten spots. Every plate is 1387x752 off the same camera, so one set of
   coordinates is a surface on all nine of them — and none of them is a perch,
   so the owl can never be sitting on top of the feather. */
.spot-chimney {--x:34.5%;--y:9.0%; --w:3.6%;--rot:-18deg}  /* leaning on the chimney pots */
.spot-eave    {--x:64.5%;--y:26.0%;--w:4.4%;--rot:8deg}    /* laid along the thatch eave */
.spot-arch    {--x:51.5%;--y:46.0%;--w:3.4%;--rot:-6deg}   /* in the vine arch over the door */
.spot-trellis {--x:74.0%;--y:34.0%;--w:4.0%;--rot:-32deg}  /* caught in the ivy on the trellis */
.spot-sill    {--x:44.5%;--y:63.5%;--w:3.6%;--rot:-10deg}  /* the flowers under the parlour window */
.spot-bench   {--x:76.8%;--y:70.0%;--w:4.6%;--rot:6deg}    /* the stone bench */
.spot-foxglove{--x:14.5%;--y:71.0%;--w:4.2%;--rot:-24deg}  /* among the foxgloves, left */
.spot-steps   {--x:72.0%;--y:86.0%;--w:5.0%;--rot:14deg}   /* the stone steps */
.spot-path    {--x:59.0%;--y:89.0%;--w:5.0%;--rot:10deg}   /* the pebble path */
.spot-daisies {--x:24.0%;--y:91.0%;--w:5.0%;--rot:-8deg}   /* the flowerbed, bottom left */

/* ---- and what happens when it is found ----
   It lifts off the picture, sways its way up and out, and is gone until the page
   is loaded again. Everything is percentages of the feather's own box, so the
   flight is the same size on a phone as it is on a desktop. */
.hidden-feather.found{
  pointer-events:none;
  animation:featherRise 2.8s cubic-bezier(.33,.1,.5,1) forwards;
}
@keyframes featherRise{
  0%  {transform:translate(0,0) scale(1);opacity:1}
  7%  {transform:translate(0,-70%) scale(1.09);opacity:1}
  30% {transform:translate(55%,-420%) scale(1.03);opacity:1}
  55% {transform:translate(-45%,-760%) scale(.97);opacity:.95}
  78% {transform:translate(40%,-1070%) scale(.9);opacity:.7}
  100%{transform:translate(-10%,-1340%) scale(.84);opacity:0}
}
/* and tumbles as it goes — on top of the spot's resting angle, same as the sway */
.hidden-feather.found img{
  animation:featherTumble 2.8s ease-in-out forwards;
}
@keyframes featherTumble{
  0%  {transform:rotate(0deg)}
  25% {transform:rotate(15deg)}
  55% {transform:rotate(-13deg)}
  80% {transform:rotate(9deg)}
  100%{transform:rotate(-6deg)}
}

/* the ring of light it leaves behind — aspect-ratio keeps it a circle while the
   animation only has to touch the width */
.hidden-feather::after{
  content:"";position:absolute;left:50%;top:50%;
  width:30%;aspect-ratio:1;border-radius:50%;
  translate:-50% -50%;
  background:radial-gradient(circle,rgba(223,231,242,.6) 0%,rgba(185,174,205,.28) 45%,transparent 70%);
  opacity:0;pointer-events:none;
}
.hidden-feather.found::after{animation:featherSpark 1.2s ease-out forwards}
@keyframes featherSpark{
  0%  {width:25%;opacity:.9}
  100%{width:280%;opacity:0}
}

/* the sparks, riding up with it. Six of them, scattered by :nth-child the way
   the stars in base.css are, so nothing about where they go is in the markup. */
.motes{position:absolute;inset:0;pointer-events:none}
.motes i{
  position:absolute;width:12%;aspect-ratio:1;border-radius:50%;
  background:radial-gradient(circle,#fff 0%,rgba(223,231,242,.75) 35%,transparent 70%);
  opacity:0;
}
/* longhands, not the shorthand: the delays below are lower specificity and a
   shorthand here would reset every one of them to zero */
.hidden-feather.found .motes i{
  animation-name:mote;
  animation-duration:1.7s;
  animation-timing-function:ease-out;
  animation-fill-mode:forwards;
}
.motes i:nth-child(1){left:22%;top:26%;animation-delay:.02s}
.motes i:nth-child(2){left:62%;top:14%;animation-delay:.12s}
.motes i:nth-child(3){left:44%;top:60%;animation-delay:.06s}
.motes i:nth-child(4){left:78%;top:50%;animation-delay:.2s}
.motes i:nth-child(5){left:12%;top:66%;animation-delay:.26s}
.motes i:nth-child(6){left:88%;top:30%;animation-delay:.34s}
@keyframes mote{
  0%  {opacity:0;transform:translate(-50%,-50%) scale(.2)}
  22% {opacity:1;transform:translate(-50%,-110%) scale(1)}
  100%{opacity:0;transform:translate(-50%,-360%) scale(.45)}
}

/* The footer has been telling people to follow the feather all along, so it is
   the thing that answers when someone finally does. */
footer .feather{transition:text-shadow .8s ease,scale .8s ease}
footer.found .feather{
  text-shadow:0 0 14px rgba(185,174,205,.95);
  scale:1.3;
  animation-duration:2.2s;      /* the base sway, quickened for a moment */
}

/* ---- the front door ----
   The door is painted into every plate, so this is only the part of it a
   visitor can press: an invisible button sitting exactly over the doorway, plus
   a little of the light that is on inside. Percentages of .plate, same rule as
   everything else here — the door has to stay on the door when a phone crops
   the picture.

   --dx/--dy are the doorway's *centre*, so translate rather than a corner, and
   this one needs a height as well as a width: a doorway is a hole, not a
   picture, and there is no art whose shape could stand in for it.

   It comes second in the markup, after the feather, so the feather keeps its
   place as the page's first tab stop — the door is somewhere to go, but the
   feather is the thing worth finding. */
.door{
  position:absolute;
  left:var(--dx);top:var(--dy);width:var(--dw);height:var(--dh);
  translate:-50% -50%;
  appearance:none;-webkit-appearance:none;
  background:none;border:0;padding:0;
  cursor:pointer;
  z-index:2;                     /* under the feather, which may be on the arch */
  /* pointer-events too, not only opacity: unlike the feather this one has its
     coordinates from the markup, so without it an invisible door would be
     clickable over a cottage that has not arrived yet */
  opacity:0;pointer-events:none;
  transition:opacity 1.2s ease 1.4s;
  --rest:.42;                    /* the doorway light's resting brightness */
}
/* Same gate as the owl and the feather: nothing to press until the cottage is
   actually on screen. The delay puts the light in the doorway last, after the
   plate has settled and the garden has finished arriving. */
.scene.ready .door{opacity:1;pointer-events:auto}
.door-front{--dx:52.9%;--dy:61.7%;--dw:7.6%;--dh:20.6%}

/* the light that is on inside — the door's resting state, and half of what says
   it can be opened at all (the knock below is the other half). Soft enough to
   pass for the doorway's own glow, and it comes up when a pointer, a focus ring
   or a thumb finds it. `screen`, like the window lamps, so it adds light to the
   stitches rather than painting over them. */
.door::after{
  content:"";position:absolute;inset:-22% -34%;
  border-radius:50%;
  background:radial-gradient(ellipse at 50% 56%,
    rgba(255,206,124,.40) 0%,
    rgba(255,186,96,.18) 44%,
    transparent 72%);
  mix-blend-mode:screen;pointer-events:none;
  /* --rest is the resting brightness, and it is a custom property rather than
     an opacity of its own so that the states below can simply say 1. Written as
     an opacity, the night rule — `body[data-tod=...]` — outweighed
     `.door:hover`, and the doorway did not actually answer a pointer on the
     plates where the invitation matters most. */
  opacity:var(--rest);transition:opacity .5s ease,scale .5s ease;
  animation:doorGlow 6.2s ease-in-out infinite;
}
/* at night the doorway is the brightest invitation on the plate */
body[data-tod="night"] .door{--rest:.72}
.door:hover::after,.door:focus-visible::after{opacity:1}
/* and the press answers. A pointer has hover to tell it the door is a door; a
   finger has only this, so the light has to move the moment it is touched. */
.door:active::after{opacity:1;scale:1.05}
.door:focus-visible{outline:2px solid rgba(223,231,242,.8);outline-offset:5px;border-radius:6px}
@keyframes doorGlow{
  0%,100%{filter:brightness(.88)}
  50%    {filter:brightness(1.18)}
}

/* ---- the knock ----
   The glow above comes up on hover, which is no use to a finger: on a phone or
   an iPad the door had nothing to say for itself, and a warm doorway is easily
   just part of the painting. So the door knocks — twice, softly, every few
   seconds, and then leaves you alone again: someone inside turning the lamp up
   because they heard you at the gate.

   Two beats rather than one slow swell on purpose. A single breath at this size
   is ambient, and reads as the weather; two in quick succession reads as
   somebody doing it, which is the whole point of the hint.

   The shape is a wide, soft pool low in the doorway — light falling *out* onto
   the step, never a ring round the frame. A gradient with a hole in the middle
   drew a hard-edged egg round the door and looked like a button stuck on top of
   the crochet; this has no edge to see, and it lands on the pale flagstones,
   which is the one place a warm tint still shows on a bright day plate. */
.door::before{
  content:"";position:absolute;inset:-4% -30% -24%;
  border-radius:50%;
  background:radial-gradient(ellipse at 50% 64%,
    rgba(255,214,146,.34) 0%,
    rgba(255,192,104,.19) 36%,
    rgba(255,178,88,.07) 62%,
    transparent 82%);
  mix-blend-mode:screen;pointer-events:none;
  opacity:0;scale:.82;
  transform-origin:50% 64%;      /* it grows out of the doorstep, not the roof */
}
/* The delay holds the first knock until the door's own light has finished
   arriving (1.4s wait + 1.2s fade above), so it reads as the cottage settling
   in rather than something going off during the entrance. */
.scene.ready .door::before{animation:doorKnock 5.6s ease-out 2.7s infinite}
@keyframes doorKnock{
  0%       {opacity:0;   scale:.82}
  7%       {opacity:.88; scale:.95}   /* knock */
  17%      {opacity:.22; scale:1.02}
  26%      {opacity:1;   scale:1.10}  /* and again */
  46%      {opacity:0;   scale:1.24}
  100%     {opacity:0;   scale:1.24}  /* the wait before it tries again */
}
/* Once a pointer or a focus ring finds the door the hint has been answered —
   the hover glow takes it from there. Both selectors carry `.scene.ready` only
   to outweigh the rule that starts the animation: an animation beats a plain
   declaration whatever the specificity, so the way to stop one is to win the
   `animation` property itself. */
.scene.ready .door:hover::before,
.scene.ready .door:focus-visible::before{animation:none;opacity:0}
/* and it stops for good the first time someone goes in. A hint that keeps
   knocking after it has been heard is just a blinking light. */
.scene.been-inside .door::before{animation:none;opacity:0}

/* A touch screen has no hover at all, so the knock is the whole of the door's
   voice there: let it come round a little sooner, and let the doorway rest a
   little warmer than it does under a pointer.

   The border is the tap target, not decoration. 7.6% of the plate is a
   comfortable 105px on a desktop and a mean 34px on a phone, where the picture
   is scaled right down — under the 44px a thumb wants. A *transparent* border
   is the way to add the missing millimetres without moving anything: with
   `content-box` the padding box stays exactly --dw x --dh, so both glows, which
   are sized in percentages of it, come out the size they always were. */
@media (hover:none){
  .door{box-sizing:content-box;border:9px solid transparent;--rest:.54}
  body[data-tod="night"] .door{--rest:.8}
  .scene.ready .door::before{animation-duration:4.4s}
}

/* ---- inside the cottage ----
   One room, and it is a second picture layered over the scene rather than a
   second page: the frame does not move, so opening the door reads as stepping
   through it instead of going somewhere. The weather, the moon and the owl
   carry on outside, untouched, and stepping back out is a class coming off.

   .room-plate is the kitchen's .plate — the art's own box, centred, everything
   positioned as a % of it. The crop runs the *other way* from the garden's,
   because the art is the other shape: 1200x896 (1.339:1) inside a 1.845:1
   window, so it is width-driven by default and the scene takes the ceiling and
   the floor off. A portrait phone's 4:3 window is almost exactly the art's own
   shape, so it flips to height-driven down there and loses 0.4% off the sides
   instead. Same rule as .plate, and for the same reason: which dimension drives
   has to match which way it is cropped, or aspect-ratio ends up in a fight. */
.room{
  --room-shift:8.5%;                     /* see .room-plate, below */
  position:absolute;inset:0;z-index:4;   /* over the sheen, which is outdoors */
  overflow:hidden;
  background:#2a2018;                    /* warm, so a slow load is not a hole */
  /* visibility, not display: it keeps the room out of the tab order and away
     from a screen reader while it is shut, and still cross-fades. The delay is
     what holds it until the fade has finished. */
  opacity:0;visibility:hidden;
  transition:opacity .75s ease, visibility 0s linear .75s;
}
.scene.indoors .room{
  opacity:1;visibility:visible;
  transition:opacity .75s ease, visibility 0s;
}
/* and the garden goes the same way a beat later, so nothing out there can be
   tabbed to or clicked through a closed door */
.plate{transition:visibility 0s}
.scene.indoors .plate{visibility:hidden;transition:visibility 0s linear .75s}
/* the drifting band of light is cloud shadow moving off — there is no sky in
   the kitchen */
.scene.indoors .sheen{opacity:0;transition:opacity .4s ease}

/* --room-shift slides the art down inside its window. Centred, the width-driven
   crop takes 13.7% off the top, and the portrait hanging over the mantel starts
   at 5.7% — so the girl in it lost her head to the top edge. Shifting by 8.5%
   puts the whole frame in, and pays for it at the bottom with the rug, which is
   the least of what is down there.

   It is a percentage of the plate's own height, so it tracks the picture at any
   size, and it lives on .room so there is one number rather than one per media
   query. A portrait phone gets the whole height already and has nothing to cut:
   shifting there would open a band of bare backdrop along the top, so that block
   sets it to 0 and the landscape block — which a narrow phone matches too —
   inherits .room's value back. */
.room-plate{
  position:absolute;left:50%;top:50%;
  transform:translate(-50%,-50%) translateY(var(--room-shift));
  width:100%;height:auto;
  aspect-ratio:1200/896;
}
.room-plate > picture{display:block}
/* the room's own picture only — by id, for the same reason #sceneImg is: a
   `.room-plate img` rule would also catch the feather's <img> inside it and
   leave the feather sitting there at opacity 0 */
#roomImg{
  width:100%;height:auto;display:block;
  /* the script names the source only when someone reaches for the door, so this
     fades up whenever it lands rather than with the room */
  opacity:0;transition:opacity .7s ease;
}
#roomImg.on{opacity:1}

/* The kitchen's own feather. Everything about how it looks, hovers, flies and
   sparks is the garden feather's — the only thing that differs is where the ten
   places are, below. The entrance delay is shortened: 1.7s is the plate and the
   owl getting out of the way on a page load, and in here there is only the room
   settling in front of it. */
.room .hidden-feather{transition:opacity 1.2s ease .8s}

/* The ten places in the kitchen, as a % of .room-plate. All ten sit inside
   9%-76% vertically, which is what survives the crop once --room-shift has slid
   the art down: the widest window shows about 5%-78% of the art's height. The
   kitchen is a small room seen close up, so the feather is a good deal bigger
   here than it is out in the garden.

   Two of them also keep out of the kettle's way. It stands on the right of the
   hob and never moves, so a spot underneath it would be a feather that cannot
   be found — the same rule the garden's ten follow about the owl's perches. */
.kspot-mantel  {--x:52.0%;--y:31.5%;--w:8.0%; --rot:-6deg}  /* laid along the mantel, past the mortar */
.kspot-spoons  {--x:40.0%;--y:46.5%;--w:8.0%; --rot:-55deg} /* standing in the jar of wooden spoons */
.kspot-stove   {--x:44.0%;--y:54.2%;--w:8.0%; --rot:6deg}   /* the left of the hob, clear of the kettle */
.kspot-doily   {--x:44.5%;--y:73.5%;--w:11.0%;--rot:12deg}  /* on the table, beside the candle */
.kspot-counter {--x:22.5%;--y:59.8%;--w:10.0%;--rot:-11deg} /* the left counter, past the cauldron */
.kspot-lavender{--x:8.0%; --y:47.5%;--w:9.0%; --rot:-38deg} /* in the lavender hung up to dry */
.kspot-board   {--x:78.5%;--y:56.0%;--w:9.5%; --rot:10deg}  /* the draining board, beside the sink */
.kspot-shelf   {--x:27.0%;--y:27.5%;--w:7.5%; --rot:-8deg}  /* the high shelf, among the plates */
.kspot-jars    {--x:71.5%;--y:38.5%;--w:8.0%; --rot:11deg}  /* leaning on the spice jars */
.kspot-table   {--x:58.5%;--y:75.0%;--w:11.5%;--rot:-17deg} /* the bare end of the table */

/* ---- the kettle on the stove ----
   The one thing in this room that is *done* rather than found. It stands on the
   right of the hob, and pressing it pours a cup into the teacup already painted
   on the counter beside it. Placed the way everything on this picture is placed:
   a <button> at a % of .room-plate, with its art in a <picture> inside.

   The cut-out is flipped in the file, not here. The studio shot has the spout
   pointing left and the cup is to the right of the stove, so a scaleX on this
   element would have to be undone inside every transform below it.

   Nothing in the markup names the art, the same as the room's own picture:
   armRoom() fills it in when somebody reaches for the door, so a visitor who
   never goes inside never pays for the download. */
.kettle{
  position:absolute;
  left:52.1%;top:50.1%;width:8%;
  translate:-50% -50%;
  appearance:none;-webkit-appearance:none;
  background:none;border:0;padding:0;
  cursor:pointer;
  z-index:4;
  /* arrives after the room has settled, the way the feather in here does */
  opacity:0;transition:opacity 1s ease .5s;
}
.scene.indoors .kettle.on{opacity:1}
.kettle picture{display:block}
.kettle img{
  width:100%;height:auto;display:block;
  /* the base, just under the spout side: tipping about there lifts the kettle's
     back the way a hand would, instead of swinging it in mid-air */
  transform-origin:62% 92%;
  filter:drop-shadow(0 4px 4px rgba(24,16,8,.55));
  animation:kettleShiver 6.5s ease-in-out infinite;
}
.kettle:hover img{filter:brightness(1.07) drop-shadow(0 4px 6px rgba(24,16,8,.5))}
.kettle:focus-visible{outline:2px solid rgba(223,231,242,.85);outline-offset:5px;border-radius:6px}

/* The hint that it can be pressed. A kettle that has just come to the boil does
   not stand quite still, and on a touch screen there is no hover to fall back
   on — so this is doing the job the door's knock does downstairs. Long rests
   between shivers, so it reads as the room breathing rather than as something
   asking to be clicked. And like the knock, it stops for good once the thing it
   was pointing at has been done. */
@keyframes kettleShiver{
  0%,74%,100%{transform:rotate(0deg)}
  77%{transform:rotate(-1.7deg)}
  81%{transform:rotate(1.5deg)}
  85%{transform:rotate(-1deg)}
  89%{transform:rotate(.55deg)}
  93%{transform:rotate(-.2deg)}
}
.room.poured .kettle img{animation:none}
.room.poured .kettle{cursor:default}

/* ---- and the pour itself ----
   The kettle does not pour across the room at it: it **lifts off the hob, comes
   over until its spout is above the cup, and tips there**, then goes back. That
   is two animations on two elements, cut to the same 4.2s so their percentages
   line up and can be read side by side:

     .kettle      travels — `transform` on the button, which composes on top of
                  the `translate` property centring it, so the placement above
                  survives the journey
     .kettle img  tips — `transform` on the picture, about the base

   The far end of the travel is measured, not guessed. The element is 96x102.6 of
   the art's own pixels; tipped 34° about `transform-origin: 62% 92%`, its spout
   tip sits 119.6 across and 70.5 down from its own top-left corner; and the
   teacup's rim is at 763 479.5 on the photograph. translate(62.7%, -35.2%) is
   what puts the one about 45px above the other — and those are the coordinates
   the stream in the markup is drawn between. Move the kettle and that path has
   to move with it. */
.room.pouring .kettle{animation:kettleCarry 4.2s cubic-bezier(.4,0,.35,1) forwards}
@keyframes kettleCarry{
  0%  {transform:translate(0,0)}
  16% {transform:translate(22%,-46%)}      /* up first, so it lifts rather than slides */
  34% {transform:translate(62.7%,-35.2%)}  /* held over the cup */
  70% {transform:translate(62.7%,-35.2%)}
  84% {transform:translate(30%,-46%)}
  100%{transform:translate(0,0)}
}
/* the tilt, and a shadow that softens and drops away while it is off the hob */
.room.pouring .kettle img{animation:kettleTip 4.2s cubic-bezier(.4,0,.35,1) forwards}
@keyframes kettleTip{
  0%  {transform:rotate(0deg);  filter:drop-shadow(0 4px 4px rgba(24,16,8,.55))}
  16% {transform:rotate(10deg); filter:drop-shadow(0 11px 10px rgba(24,16,8,.4))}
  34% {transform:rotate(34deg); filter:drop-shadow(0 11px 10px rgba(24,16,8,.4))}
  70% {transform:rotate(34deg); filter:drop-shadow(0 11px 10px rgba(24,16,8,.4))}
  86% {transform:rotate(5deg);  filter:drop-shadow(0 8px 7px rgba(24,16,8,.45))}
  100%{transform:rotate(0deg);  filter:drop-shadow(0 4px 4px rgba(24,16,8,.55))}
}

/* ---- the tea ----
   One <svg> laid over the art in the art's own coordinates: viewBox
   "0 0 1200 896" is the kitchen photograph at its native size, so every number
   in the markup is a pixel measured off the picture — where the tipped spout
   ends up, where the rim of the cup is. Nothing in here is interactive; it is
   painted, not pressed. */
.brew{
  position:absolute;inset:0;
  width:100%;height:100%;
  pointer-events:none;
  z-index:5;
  overflow:visible;
}

/* The stream. `pathLength="100"` in the markup normalises the arc, so the dash
   numbers below are percentages of it and re-drawing the path doesn't mean
   re-deriving them. Offset 100 holds it back at the spout, 0 is the whole arc,
   and -100 walks it off the far end — which drains it spout-first and leaves the
   last of the tea falling into the cup, the way a real pour ends. */
.brew-stream{
  fill:none;
  stroke:var(--lavender);
  /* thin, and lit from inside rather than drawn: at full weight and full
     opacity a stroke this size reads as a length of pipe, and the glow is what
     turns it back into something pouring */
  stroke-width:3.2;
  stroke-linecap:round;
  filter:drop-shadow(0 0 2px rgba(185,174,205,.85));
  stroke-dasharray:100;
  stroke-dashoffset:100;
  opacity:0;
}
/* It waits until the kettle has arrived and tipped — 34% of the way through —
   and has drained again before the kettle starts back at 70%. */
.room.pouring .brew-stream{animation:pourRun 4.2s ease-in-out forwards}
@keyframes pourRun{
  0%,34%  {stroke-dashoffset:100;opacity:0}
  38%     {opacity:.9}
  44%     {stroke-dashoffset:0;opacity:.9}
  60%     {stroke-dashoffset:0;opacity:.9}
  69%     {stroke-dashoffset:-100;opacity:.6}
  74%,100%{stroke-dashoffset:-100;opacity:0}
}

/* what lands in the cup */
.brew-tea{
  fill:var(--lavender);
  opacity:0;
  transform-box:fill-box;transform-origin:center;
  transform:scale(.35);
}
.room.pouring .brew-tea{animation:teaFill 4.2s ease-out forwards}
/* and it stays there once the pour is over. `forwards` only holds the last
   keyframe for as long as the animation is still *applied*, and `.pouring` comes
   off at the end — so a full cup has to be a resting state of its own or the tea
   vanishes the moment the class does. It never fights the pour: an animation
   outranks a plain declaration, so the keyframes still start the cup empty and
   fill it. This is also what the reduced-motion block leans on, where the pour
   does not animate at all. */
.room.poured .brew-tea{opacity:.95;transform:none}
@keyframes teaFill{
  0%,40%{opacity:0;  transform:scale(.35)}
  52%   {opacity:.8; transform:scale(.85)}
  68%   {opacity:.95;transform:scale(1)}
  100%  {opacity:.95;transform:scale(1)}
}

/* and the sparkles that come up off it — the one bit of this that isn't
   physics. Six, staggered, starting once the stream has reached the cup at
   about 1.9s. */
.brew-motes circle{
  fill:#fdf8ef;
  opacity:0;
  transform-box:fill-box;transform-origin:center;
}
.room.pouring .brew-motes circle{animation:teaMote 2.2s ease-in-out 1.9s forwards}
.room.pouring .brew-motes circle:nth-child(2){animation-delay:2.1s}
.room.pouring .brew-motes circle:nth-child(3){animation-delay:2.3s}
.room.pouring .brew-motes circle:nth-child(4){animation-delay:2.55s}
.room.pouring .brew-motes circle:nth-child(5){animation-delay:2.8s}
.room.pouring .brew-motes circle:nth-child(6){animation-delay:3.05s}
@keyframes teaMote{
  0%  {opacity:0;  transform:translateY(0) scale(.4)}
  35% {opacity:.95;transform:translateY(-4px) scale(1)}
  100%{opacity:0;  transform:translateY(-11px) scale(.5)}
}

/* ---- the way back out ----
   A quiet pill in the corner of the room, and Escape does the same thing.
   Deliberately the only furniture the room adds: the picture is the point. */
.way-out{
  position:absolute;z-index:6;
  left:clamp(10px,2%,22px);bottom:clamp(10px,2.6%,22px);
  display:inline-flex;align-items:center;gap:9px;
  padding:9px 17px;border-radius:999px;
  font-family:inherit;font-size:clamp(.62rem,1.3vw,.74rem);
  font-weight:600;letter-spacing:.16em;text-transform:uppercase;
  color:rgba(247,241,230,.88);
  background:rgba(22,18,14,.5);
  border:1px solid rgba(232,220,200,.3);
  backdrop-filter:blur(7px);
  cursor:pointer;
  transition:background .3s ease,color .3s ease,transform .3s cubic-bezier(.2,.8,.25,1);
}
.way-out:hover{background:rgba(22,18,14,.72);color:#fdf8ef;transform:translateX(-2px)}
.way-out:focus-visible{outline:2px solid rgba(223,231,242,.85);outline-offset:3px}
.way-out .arrow{display:inline-block;transition:transform .3s cubic-bezier(.2,.8,.25,1)}
.way-out:hover .arrow{transform:translateX(-3px)}

/* ---- the sound of the weather ----
   The one control on this page that is offered rather than hidden: the sky has
   an ambience as well as a picture, and no browser will play it unasked.

   It sits in the corner opposite the way out, and *outside* .plate and .room —
   the weather carries on while somebody is in the kitchen, so the button that
   quiets it has to be reachable from in there too. Hence the z-index above
   .way-out's. Like the owl it waits for .scene.ready, and comes up last of
   anything on the picture. */
.listen{
  position:absolute;z-index:7;
  right:clamp(10px,2%,22px);top:clamp(10px,2.6%,22px);
  display:inline-flex;align-items:center;gap:8px;
  padding:8px 15px;border-radius:999px;
  font-family:inherit;font-size:clamp(.58rem,1.2vw,.68rem);
  font-weight:600;letter-spacing:.16em;text-transform:uppercase;
  color:rgba(247,241,230,.78);
  background:rgba(22,18,14,.42);
  border:1px solid rgba(232,220,200,.24);
  backdrop-filter:blur(7px);
  cursor:pointer;
  opacity:0;pointer-events:none;
  transition:opacity 1s ease 1.6s,background .3s ease,
             color .3s ease,border-color .3s ease;
}
.scene.ready .listen{opacity:1;pointer-events:auto}
.listen:hover{
  background:rgba(22,18,14,.68);color:#fdf8ef;
  border-color:rgba(232,220,200,.44);
}
.listen:focus-visible{outline:2px solid rgba(223,231,242,.85);outline-offset:3px}

/* The label is the action, not the state, so it changes on the click — and it
   holds a fixed width so the pill doesn't shrink under the pointer that just
   pressed it. */
.listen-label{display:inline-block;min-width:4.3em;text-align:center}

.listen svg{width:15px;height:15px;flex:none;overflow:visible}
.listen .horn{fill:currentColor}
.listen .wave,
.listen .hushed{
  fill:none;stroke:currentColor;stroke-width:1.7;
  stroke-linecap:round;transition:opacity .35s ease;
}
.listen .wave{opacity:0}
.listen .hushed{opacity:.72}
.listen.on .wave{opacity:.9}
.listen.on .hushed{opacity:0}

/* and while it is playing the waves breathe, so the button is visibly live
   rather than just pressed. The resting opacity above is a plain declaration,
   so the reduced-motion wildcard in base.css leaves the waves lit. */
.listen.on .wave-near{animation:listenWave 3.4s ease-in-out infinite}
.listen.on .wave-far {animation:listenWave 3.4s ease-in-out .45s infinite}
@keyframes listenWave{0%,100%{opacity:.34}50%{opacity:.95}}

/* No hover to fall back on, and 30px of pill is a mean target for a thumb. */
@media (hover:none){
  .listen{padding:12px 17px;background:rgba(22,18,14,.54)}
}

/* gentle vignette + slow breathing light */
.scene::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(120% 90% at 50% 40%,transparent 55%,rgba(10,14,24,.42));
  animation:breathe 9s ease-in-out infinite;
}
@keyframes breathe{0%,100%{opacity:.85}50%{opacity:1}}

/* a slow band of light drifting over the scene, like cloud shadow moving off */
.sheen{
  position:absolute;top:-40%;left:-45%;
  width:38%;height:180%;pointer-events:none;z-index:3;
  background:linear-gradient(100deg,transparent,rgba(223,231,242,.07) 45%,
             rgba(223,231,242,.12) 50%,rgba(223,231,242,.07) 55%,transparent);
  transform:rotate(10deg);
  animation:sheenSweep 17s ease-in-out infinite;
}
@keyframes sheenSweep{
  0%,62%{left:-45%}
  100%  {left:125%}
}

.scene .loading{
  position:absolute;inset:0;display:grid;place-items:center;
  font-size:.8rem;letter-spacing:.2em;text-transform:uppercase;
  color:rgba(247,241,230,.5);
  transition:opacity .6s ease;
  animation:pulse 2.2s ease-in-out infinite;
}
@keyframes pulse{0%,100%{opacity:.45}50%{opacity:.9}}
.scene.ready .loading{opacity:0;animation:none}

/* ---- caption ---- */
.caption{
  margin:clamp(16px,2.2vw,24px) auto 4px;max-width:60ch;
  font-size:clamp(1rem,2.2vw,1.18rem);line-height:1.7;
  color:rgba(247,241,230,.9);
  min-height:1.7em;
  transition:opacity .5s ease, transform .5s ease;
}
/* the caption lifts away as it changes, rather than just blinking */
.caption.swap{opacity:0;transform:translateY(-6px)}
.meta{
  margin-top:10px;font-size:.78rem;letter-spacing:.16em;text-transform:uppercase;
  color:rgba(247,241,230,.42);
}
.meta .sep{
  opacity:.4;margin:0 .5em;display:inline-block;
  animation:sepPulse 3.4s ease-in-out infinite;
}
.meta .sep:nth-of-type(2){animation-delay:1.1s}
@keyframes sepPulse{0%,100%{opacity:.22}50%{opacity:.7}}
.meta .moonname{
  color:var(--gold);
  animation:moonnameGlow 6s ease-in-out infinite;
}
@keyframes moonnameGlow{
  0%,100%{text-shadow:none}
  50%    {text-shadow:0 0 10px rgba(224,184,113,.55)}
}

/* ---- Instagram link ----
   The base pill, a size down: this page is mostly picture and the button is
   not the point of it. */
.cta{
  gap:9px;
  margin-top:clamp(16px,2.2vw,22px);padding:11px 22px;
  font-size:.92rem;
  box-shadow:0 10px 26px rgba(156,85,102,.32);
}
.cta:hover{box-shadow:0 15px 34px rgba(156,85,102,.48)}
.cta svg{width:18px;height:18px}
.cta:hover svg{transform:rotate(-12deg) scale(1.15)}

footer{
  margin-top:clamp(14px,2vw,20px);
  font-size:.72rem;
  color:rgba(247,241,230,.28);
}

@media (max-width:600px){
  .card{padding:14px;border-radius:20px}
  .scene{border-radius:14px}
  h1{margin:5px 0 4px}

  /* Portrait phones. At the art's own 1.845:1 the cottage is a ~180px strip on
     a 390px-wide screen, with half the viewport left empty around the card, so
     crop to 4:3 and let the picture fill the space instead.

     Height-driven, so the plate stays wider than the window and the crop takes
     off the sides. 4:3 is about as tall as this can go: the moon sits ~20% in
     from the left edge of the art, and a tighter crop loses it. The same reason
     the plate is nudged off-centre — a centred 4:3 window clips the disc. */
  .scene{aspect-ratio:4/3}
  .plate{width:auto;height:100%;left:57%}

  /* The kitchen goes the other way. 4:3 is 1.3333 and the art is 1.3393, so
     height-driven here costs 0.4% off the sides and the whole room is in the
     frame — where width-driven would leave a hairline of backdrop above and
     below it. Nothing is cut off the top here, so there is nothing to shift
     down: the whole portrait is in already, and a shift would only open a band
     of bare backdrop above the ceiling. */
  .room-plate{--room-shift:0%;width:auto;height:100%;left:50%}
  .way-out{padding:8px 13px;letter-spacing:.12em;gap:7px}
}

/* Landscape phones. Stacked, the picture has to shrink to almost nothing before
   the name and the Instagram link clear the fold — and the picture is the point
   of the page. So stand the copy beside it instead: the wide short viewport has
   the room, and stacking was only ever leaving it empty down both sides. */
@media (orientation:landscape) and (max-height:540px){
  .card{
    display:flex;align-items:center;
    gap:clamp(14px,2.4vw,28px);
    padding:clamp(10px,1.6vh,16px) clamp(14px,2vw,22px);
  }
  /* The picture takes as much as it can and the copy gets what's left — it is
     the hero here, not one column of a two-column split.

     aspect-ratio is reset explicitly: a short *landscape* phone also matches the
     max-width:600px block above, and must not keep its 4:3 portrait crop.

     All three caps are *widths*, so the sizing stays width-driven and
     `aspect-ratio` is never in a fight with a height: how much of the row the
     picture may take, how tall it may get, and how little the copy may be
     squeezed to.

     dvh, not vh, for the height cap: in landscape the browser's own chrome is a
     big slice of a small axis, and vh measures the viewport as if that chrome
     were hidden — which sizes the picture for ~390pt on a phone actually giving
     ~344pt, and pushes the bottom of the copy off screen. vh first, as the
     fallback for anything without dvh. */
  .scene{
    aspect-ratio:1387/752;
    /* 0 0: the picture does not give ground to the copy. The third cap below is
       what guarantees the copy still has somewhere to go, so nothing overflows. */
    flex:0 0 auto;
    width:min(68%,calc(86vh * 1387 / 752),calc(100% - 210px));
    width:min(68%,calc(86dvh * 1387 / 752),calc(100% - 210px));
    height:auto;
  }
  .plate{width:100%;height:auto;left:50%}
  /* a short landscape phone matches the portrait block above too, so the
     kitchen's crop has to be put back the way round the wide window needs it —
     and with the crop comes the shift, `inherit` taking .room's value rather
     than repeating the number here */
  .room-plate{--room-shift:inherit;width:100%;height:auto;left:50%}
  .copy{flex:1 1 auto;min-width:0}

  /* the copy stands beside the picture here, so the kicker's job of holding the
     two apart is gone and its margin would only cost height */
  .kicker{margin-top:0;font-size:clamp(.72rem,1.9vh,.9rem)}
  h1{font-size:clamp(1.4rem,4.4vh,2rem);margin:4px 0 2px}
  .caption{margin-top:8px;line-height:1.5;font-size:clamp(.9rem,2.4vh,1.05rem)}
  .meta{margin-top:6px}
  /* the copy column is narrow — keep the handle on one line, which is the
     difference between a button and two stacked words */
  .cta{
    margin-top:10px;white-space:nowrap;
    font-size:clamp(.72rem,1.5vw,.9rem);
    padding:9px clamp(10px,1.4vw,16px);gap:6px;
  }
  .cta svg{width:16px;height:16px}
  footer{margin-top:8px;letter-spacing:.1em}
}
@media (prefers-reduced-motion:reduce){
  #sceneImg.on{opacity:1}
  .moon.on{opacity:1}
  /* the plate gate stays — with no transition the owl simply arrives with the
     cottage instead of a beat after it, which is still not before it */
  .scene.ready .owl.on{opacity:1}
  .owl{scale:1}
  /* the flight is the reward, and it is an animation — with animations off the
     feather has to be *told* to leave, or finding it does nothing at all. Both
     feathers: the kitchen's is the same element under a different parent */
  .scene.ready .hidden-feather.found{opacity:0}
  /* the door's own light rests where the base rule puts it; only the breathing
     goes. The room and the garden swap instantly, which is all the transitions
     above were doing anyway — but visibility has to be restated, or the delay
     the wildcard removes leaves the plate covering nothing. */
  .scene.ready .door{opacity:1;pointer-events:auto}
  /* Both of the door's lights have to be named here. `base.css` switches
     animation off with `*`, and `*` does not match a pseudo-element — so the
     wildcard never reached either of these, and a two-beat pulse is exactly the
     thing somebody asking for less motion is asking to be spared.
     The knock rests invisible — it *is* the animation — so with it off, leave
     the pool of light standing instead: on a touch screen, where there is no
     hover to fall back on, it is the only thing saying the door is a door. */
  .door::after{animation:none}
  .scene.ready .door::before{animation:none;opacity:.5;scale:1.04}
  .scene.been-inside .door::before{opacity:0}
  .scene.indoors .plate{visibility:hidden}
  .scene.indoors .room{opacity:1;visibility:visible}
  #roomImg.on{opacity:1}
  /* The kettle's shiver is exactly the sort of thing somebody asking for less
     motion is asking to be spared, and it goes with the wildcard. The pour is
     the other way about: it is the reward for pressing the kettle, and with
     animations off pressing it would do nothing at all. So the stream — which
     only exists as a drawn line — is dropped, along with the sparkles. The cup
     is simply full afterwards: `.room.poured .brew-tea` above is a resting state
     rather than an animation, so it survives the wildcard on its own. Same
     bargain the feather's flight strikes above. */
  .brew-stream,.brew-motes{display:none}
  .sheen{display:none}
  /* the lamps stay, but held still and mid-flicker */
  .lamp{opacity:.85;scale:1}
  .scene .loading{opacity:.6}
  .scene.ready .loading{opacity:0}
}
