/* ============================================================
   MONDUL — MOTION
   Scene animation and site-wide transitions.
   Everything here yields to prefers-reduced-motion.
   ============================================================ */

/* ---------- HERO SCENE ----------
   `contain:layout paint` puts the scene on its own compositing
   layer and tells the browser that nothing inside it can affect
   the layout or painting of anything outside. Without it, a
   repaint anywhere in a 131-path SVG can invalidate the sticky
   navigation bar sitting on top of it — which is how a small
   amount of work inside the hero turned into visible jitter over
   the whole upper page. */
#scene{position:absolute;inset:0;width:100%;height:100%;z-index:0;
 contain:layout paint;transform:translateZ(0)}

/* ------------------------------------------------------------
   NOTE: the sun, cloud, bird, wingbeat and mountain-sway rules
   that used to sit here belonged to the illustrated landscape
   that was replaced by the topographic field. None of those
   classes exist in the markup any more, so they were ~80 lines
   of CSS shipped to every visitor that could never match a
   single element. Deleted. The scene's own motion is grouped
   at the bottom of this file under TOPOGRAPHIC HERO.
   ------------------------------------------------------------ */

/* ---------- PAGE ENTRANCE ---------- */
body{opacity:0;animation:pageIn .9s cubic-bezier(.22,1,.36,1) forwards}
@keyframes pageIn{to{opacity:1}}

.hcontent > *{opacity:0;animation:riseIn 1s cubic-bezier(.22,1,.36,1) forwards}
.hcontent > *:nth-child(1){animation-delay:.28s}
.hcontent > *:nth-child(2){animation-delay:.42s}
.hcontent > *:nth-child(3){animation-delay:.56s}
.hcontent > *:nth-child(4){animation-delay:.70s}
@keyframes riseIn{from{opacity:0;transform:translateY(26px)}to{opacity:1;transform:none}}

/* ---------- SCROLL REVEALS ----------
   Several flavours so the page never feels like one repeated trick. */
.rev{opacity:0;transform:translateY(32px);
     transition:opacity .85s cubic-bezier(.22,1,.36,1),transform .85s cubic-bezier(.22,1,.36,1)}
.rev.in{opacity:1;transform:none}

.rev-l{opacity:0;transform:translateX(-38px)}
.rev-l.in{opacity:1;transform:none}
.rev-r{opacity:0;transform:translateX(38px)}
.rev-r.in{opacity:1;transform:none}
.rev-s{opacity:0;transform:scale(.94)}
.rev-s.in{opacity:1;transform:none}
.rev-l,.rev-r,.rev-s{transition:opacity .85s cubic-bezier(.22,1,.36,1),transform .85s cubic-bezier(.22,1,.36,1)}

/* headline wipe — text emerges from behind a moving edge */
.wipe{clip-path:inset(0 100% 0 0);transition:clip-path 1.05s cubic-bezier(.76,0,.24,1)}
.wipe.in{clip-path:inset(0 0 0 0)}

/* a hairline that draws itself under section headings */
.rule{position:relative}
.rule:after{content:"";position:absolute;left:0;bottom:-10px;height:2px;width:0;
 background:linear-gradient(90deg,var(--terra),var(--gold));border-radius:2px;
 transition:width 1.1s cubic-bezier(.22,1,.36,1) .15s}
.rule.in:after{width:78px}

/* ---------- CARD & TILE MOTION ---------- */
.svc,.pc,.sight,.item,.minicard,.pill,.ph,.ci,.gcard{
 transition:transform .45s cubic-bezier(.22,1,.36,1),
            box-shadow .45s cubic-bezier(.22,1,.36,1),
            border-color .45s ease, background .45s ease}

/* buttons lift and the arrow leads */
.btn{position:relative;overflow:hidden}
.btn:after{content:"";position:absolute;inset:0;background:rgba(255,255,255,.22);
 transform:translateX(-101%) skewX(-18deg);transition:transform .62s cubic-bezier(.22,1,.36,1)}
.btn:hover:after{transform:translateX(101%) skewX(-18deg)}

/* nav underline already exists; add a soft lift on the logo */
.logo{transition:transform .4s cubic-bezier(.22,1,.36,1)}
.logo:hover{transform:translateY(-2px)}

/* ---------- TAB CROSSFADE ---------- */
.panel{display:none}
.panel.on{display:block;animation:panelIn .55s cubic-bezier(.22,1,.36,1)}
@keyframes panelIn{
 from{opacity:0;transform:translateY(16px) scale(.995)}
 to  {opacity:1;transform:none}
}
.tab{transition:background .3s ease,color .3s ease,border-color .3s ease,transform .3s cubic-bezier(.22,1,.36,1)}
.tab:hover{transform:translateY(-2px)}

/* ---------- DRAWERS & OVERLAYS ---------- */
.drawer{animation:fadeIn .3s ease}
.drawer .dpanel{animation:panelRise .5s cubic-bezier(.22,1,.36,1)}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
@keyframes panelRise{from{opacity:0;transform:translateY(30px) scale(.985)}to{opacity:1;transform:none}}

#searchpanel{animation:fadeIn .26s ease}
#searchpanel .searchbox{animation:panelRise .42s cubic-bezier(.22,1,.36,1)}

/* ---------- QUICK-FACT BAR ---------- */
.qf{transition:transform .4s cubic-bezier(.22,1,.36,1)}
.qf:hover{transform:translateY(-4px)}
.qf b{transition:color .35s ease}
.qf:hover b{color:var(--amber)}

/* ---------- SCROLL CUE ---------- */
.scrollcue{animation:cue 2.8s ease-in-out infinite}
@keyframes cue{
 0%,100%{transform:translate(-50%,0);opacity:.55}
 50%    {transform:translate(-50%,8px);opacity:1}
}

/* ---------- GALLERY ---------- */
.gitem{transition:transform .5s cubic-bezier(.22,1,.36,1),box-shadow .5s ease}
.gitem img{transition:transform .8s cubic-bezier(.22,1,.36,1)}
.gitem:hover img{transform:scale(1.07)}


/* ========== TOPOGRAPHIC HERO =============================
   Three movements only: contour levels fade up in sequence,
   the whole field breathes very slowly, markers land after the
   field has settled. Nothing crosses the frame — that was what
   produced the straight lines slicing through the old hero.
   ========================================================= */

.scField{will-change:transform}
.scMarks{will-change:transform;color:#E8DCCB}
.scScale{color:#E8DCCB}

/* each contour level fades in from below, low ground first */
.scLevel{opacity:0;animation:scRise 1.5s cubic-bezier(.22,1,.36,1) forwards}
@keyframes scRise{
 0%  {opacity:0; transform:translateY(16px) scale(1.012)}
 100%{opacity:1; transform:none}
}

/* THE FIELD IS NOT RESCALED. This used to carry a 40s
   scale(1 -> 1.018) "breathing" animation. On 131 vector paths with
   sub-pixel stroke widths, a continuous scale re-rasterises every
   line every frame, and the strokes crawl and shimmer against the
   pixel grid. At hero size that reads as the whole picture
   vibrating. Movement in this scene comes from things that
   translate or fade — mist, light, the survey trace — never from
   geometry being resized under a hairline stroke. */

/* the light source drifts even slower, so highlights move
   across the ridges rather than sitting still */
.scGlow{animation:scDrift 54s ease-in-out infinite}
@keyframes scDrift{
 0%,100%{ transform:translate(0,0);      opacity:.85 }
 50%    { transform:translate(-70px,26px); opacity:1 }
}

/* markers arrive after the terrain, then stay put */
.scMark{opacity:0;animation:scMark 1s cubic-bezier(.22,1,.36,1) forwards}
@keyframes scMark{
 0%  {opacity:0; transform:translateY(8px)}
 100%{opacity:1; transform:none}
}
.scMark text{paint-order:stroke;stroke:rgba(2,10,7,.65);stroke-width:3px;stroke-linejoin:round}

/* locator pulse — expands and fades, one ring at a time */
.scPulse{transform-box:fill-box;transform-origin:50% 50%;
 animation:scPing 3.4s cubic-bezier(.22,1,.36,1) infinite}
@keyframes scPing{
 0%  {transform:scale(.55); opacity:.85}
 70% {transform:scale(2.7); opacity:0}
 100%{transform:scale(2.7); opacity:0}
}

/* turbine glyph on the wind-farm marker.
   The rotor <g> deliberately carries NO transform attribute, so
   this transform-origin is the only one in play. The previous
   version fought an SVG transform and the blades detached. */
.scRotor{transform-box:fill-box;transform-origin:50% 50%;
 animation:scSpin 4.2s linear infinite}
@keyframes scSpin{ to{ transform:rotate(360deg) } }

/* ---------- REDUCED MOTION ---------- */
@media(prefers-reduced-motion:reduce){
 *,*:before,*:after{animation:none!important;transition:none!important}
 body{opacity:1}
 .hcontent > *{opacity:1}
 .rev,.rev-l,.rev-r,.rev-s{opacity:1;transform:none}
 .wipe{clip-path:none}
 .rule:after{width:78px}
 .scLevel,.scMark{opacity:1;transform:none}
 .scLevel path{stroke-dasharray:none;stroke-dashoffset:0}
 .hcontent > *{opacity:1;transform:none}
 .scSweep{display:none}
}

/* ---------- HERO: cursor depth + contour shimmer ----------
   Two additions only. The field leans very slightly toward the
   pointer, which makes the terrain feel like it has depth rather
   than being printed on the page; and a slow highlight travels
   along the high contours so the composition is never completely
   still. Both are transform/opacity only, both are pointer-fine
   and reduced-motion aware.                                    */
/* No transition on transform anywhere in this scene. The parallax
   loop writes transform every frame; a CSS transition on the same
   property double-smooths it and the layer visibly wobbles.
   Smoothing is done in JavaScript instead — see scene.js. */
/* The high contours used to pulse between opacity 1 and .62 on a
   9s cycle. Alongside everything else moving, that read as flicker
   rather than as light. Removed; the ridges hold steady. */

/* ---------- HERO: three more movements ----------
   Restraint still applies — nothing crosses the frame, nothing
   loops fast enough to catch the eye while you read. These add
   life at the edges of attention, which is where a hero should
   move: the terrain is alive, the headline is not competing. */

/* THE PLOTTER DRAW-ON WAS REMOVED. It animated stroke-dashoffset
   on all 131 contour paths simultaneously for 2.6 seconds. Dash
   offset is geometry, not composition: the browser re-tessellates
   and repaints every one of those paths on every frame of it. That
   is what made the hero judder for the first few seconds after
   load. The levels still arrive in sequence — see .scLevel above —
   but they arrive by fading and translating, which the compositor
   does for free. */

/* 2. a warm highlight sweeps the ridges once every 14 seconds —
      slow enough to register as weather, not as a loading bar */
.scSweep{
 /* no mix-blend-mode. A blend on a full-height rect forces the
    whole SVG through an extra compositing pass on every frame it
    is visible, which showed up as stutter on the layers around it.
    Lower opacity in the gradient gets the same look for nothing. */
 pointer-events:none;
 animation:scSweepMove 14s cubic-bezier(.5,0,.5,1) infinite;
}
@keyframes scSweepMove{
 0%,72%{ opacity:0; transform:translateX(-16%) }
 78%   { opacity:.5 }
 88%   { opacity:.5 }
 100%  { opacity:0; transform:translateX(16%) }
}

/* 3. the headline block settles rather than appearing */
.hcontent > *{ opacity:0; animation:scSettle .95s cubic-bezier(.22,1,.36,1) forwards }
.hcontent > *:nth-child(1){ animation-delay:.20s }
.hcontent > *:nth-child(2){ animation-delay:.34s }
.hcontent > *:nth-child(3){ animation-delay:.48s }
.hcontent > *:nth-child(4){ animation-delay:.62s }
@keyframes scSettle{
 from{ opacity:0; transform:translateY(20px) }
 to  { opacity:1; transform:none }
}

/* ============================================================
   HERO — MIST, SURVEY LIGHT, GRATICULE, WARM BOUNCE
   ------------------------------------------------------------
   Everything below obeys the three rules the failed hero broke:
   nothing crosses the frame as a straight line, nothing loops
   fast enough to catch the eye while you are reading a sentence,
   and nothing bright ever sits behind the headline column.

   The point of adding more motion was not to make the page busy.
   It was to give the frame WEATHER — several slow cycles running
   at lengths that share no common multiple, so the composition
   never returns to a state you have already seen and never
   announces itself as a loop.
   ============================================================ */

/* --- wrappers ---
   These carry the scripted transform. The keyframe animation goes
   on the child. A CSS animation outranks an inline style, so any
   element carrying both silently loses the scripted one — which
   is what had been quietly disabling the parallax. */
.scFieldWrap,.scGlowWrap,.scMists{will-change:transform}


/* --- highland mist ---
   96s, 137s, 172s. Deliberately not multiples of each other, so
   the three bands drift in and out of alignment for hours rather
   than resynchronising every couple of minutes. Screen blend
   lifts the contours they cross instead of covering them. */
.scMist{pointer-events:none;will-change:transform,opacity;
 transform-box:fill-box;transform-origin:50% 50%}
.scMistA{animation:scMistA 96s ease-in-out infinite}
.scMistB{animation:scMistB 137s ease-in-out infinite;animation-delay:-40s}
.scMistC{animation:scMistC 172s ease-in-out infinite;animation-delay:-95s}
@keyframes scMistA{
 0%,100%{transform:translate(-58px,0)    scale(1);    opacity:.30}
 50%    {transform:translate(64px,-14px) scale(1.09); opacity:.62}
}
@keyframes scMistB{
 0%,100%{transform:translate(52px,6px)   scale(1.05); opacity:.22}
 50%    {transform:translate(-66px,-10px)scale(.95);  opacity:.50}
}
@keyframes scMistC{
 0%,100%{transform:translate(-30px,4px)  scale(.98);  opacity:.18}
 50%    {transform:translate(74px,-6px)  scale(1.12); opacity:.44}
}

/* --- the survey light ---
   A short dash runs the length of a real contour and leaves the
   end. The gap is far longer than most of the paths, so at any
   moment there is at most one dash on a line: it reads as a single
   instrument tracking a ridge, not as marching ants. Duration and
   delay are set per path in scene.js so the two never coincide. */
/* .scTrace removed for the same reason — see scene.js. A dash
   travelling along a 9,000-character path is a full repaint of that
   path, sixty times a second, forever. */

/* --- graticule ---
   Fades up once, then holds. A grid that pulsed would be noise; a
   grid that arrives is a sheet being laid down. */
.scGrat{opacity:0;animation:scGratIn 2.2s ease-out 1.4s forwards}
@keyframes scGratIn{ to{ opacity:1 } }

/* --- warm bounce ---
   Out of phase with the cool light above it, and slower, so the
   two never reach their extremes together. This is what keeps the
   frame from settling into one fixed lighting state. */
/* .scWarm removed — it was the red at the bottom of the frame. */

/* --- markers answer the pointer ---
   A marker is a door, so it has to react. The halo blooms, the
   label warms to grass gold. Keyboard focus gets exactly the same
   treatment — the two must never diverge, or the site is only
   navigable with a mouse.

   Note what is NOT animated here: the chevron. It carries an SVG
   transform attribute, and a CSS transform would replace it
   outright and fling the glyph to the origin. That is the same
   class of bug that detached the turbine blades. */
.scMark{cursor:pointer}
.scHalo{opacity:0;transform-box:fill-box;transform-origin:50% 50%;
 transition:opacity .4s ease,transform .55s cubic-bezier(.22,1,.36,1)}
.scMark:hover .scHalo,.scMark:focus-visible .scHalo,.scMark.lit .scHalo{
 opacity:.42;transform:scale(1.55)}
.scMark text{transition:fill .3s ease}
.scMark:hover text,.scMark:focus-visible text,.scMark.lit text{fill:#E9B979}

/* --- reduced motion, for everything added above ---
   The global sweep at the top of this file already kills the
   animations. These two rules handle what is left: a mist band
   frozen mid-cycle is fine and still looks like fog, but a bright
   dash frozen on a ridge looks like a rendering fault, so the
   survey light is removed outright rather than paused. */
@media(prefers-reduced-motion:reduce){
 .scGrat,.scMist{opacity:1}
}

/* ============================================================
   SITE-WIDE MOTION
   ------------------------------------------------------------
   Four of the eight pages had no motion whatsoever — the atlas,
   the spatial plan, the dossier and the legal register all sat
   completely still, which made the two animated pages look like a
   different website. assets/motion.js tags the repeating shapes on
   every page; these are the rules it tags them with.

   The restraint that applies to the hero applies here too: things
   arrive, and things answer the pointer. Nothing loops forever in
   the corner of your eye while you are trying to read a statute.
   ============================================================ */

/* table rows arrive like everything else, and light up under the
   pointer — a 28-row legal register with no hover state gives no
   feedback about which row you are actually reading */
tbody tr{transition:background .25s ease}
tbody tr:hover{background:rgba(30,122,90,.05)}
.dark tbody tr:hover{background:rgba(255,255,255,.05)}

/* anything clickable lifts slightly. One rule, every page. */
a.card,.lcard,.dcard,.gcard,.minicard,.sight,.svc,.pc,.item,.ci,.ph,
.factpill,.pill,.tab,.chip,.srcitem,.lgi,.dsi{
 transition:transform .32s cubic-bezier(.22,1,.36,1),
            box-shadow .32s cubic-bezier(.22,1,.36,1),
            border-color .28s ease, background .28s ease, color .28s ease}
a.card:hover,.lcard:hover,.gcard:hover,.minicard:hover,.sight:hover,
.svc:hover,.pc:hover,.ci:hover,.ph:hover{transform:translateY(-4px)}
.factpill:hover,.pill:hover,.chip:hover{transform:translateY(-2px)}

/* figures counting up must not reflow the layout as the digit
   count grows — that is the difference between a counter and a
   flicker */
.qf b,.statv,.bignum,[data-count]{font-variant-numeric:tabular-nums}

/* the More menu opens rather than appears */
.moremenu{transform-origin:top right}

/* section rules draw themselves in the province's own colours */
.rule:after{background:linear-gradient(90deg,var(--rust),var(--grass))}

/* focus is never less visible than hover — a keyboard user gets
   the same information a mouse user gets */
a.card:focus-visible,.lcard:focus-visible,.dcard:focus-visible,
.gcard:focus-visible,.minicard:focus-visible{transform:translateY(-4px)}

@media(prefers-reduced-motion:reduce){
 tbody tr,a.card,.lcard,.dcard,.gcard,.minicard,.sight,.svc,.pc,.item{
  transition:none!important;transform:none!important}
 .rev,.rev-l,.rev-r,.rev-s{opacity:1!important;transform:none!important}
}

/* ============================================================
   THE TRAVELLERS
   ------------------------------------------------------------
   A car with its lights on, a walker, an elephant and a monkey,
   each crossing the map along a contour that was adjusted into a
   passable west-to-east route (see route() in scene.js).

   The outer element rides the path — `offset-distance` is transform
   machinery, so the compositor handles it and the contour is never
   redrawn. The inner element carries the gait. Nothing here touches
   geometry: the reason the earlier travelling light had to be
   removed was that dash offset repainted the path every frame.

   Gated behind @supports so that where motion paths are unavailable
   the figures simply never appear, rather than piling up at the
   origin looking like a fault.
   ============================================================ */
.scTrav{opacity:0}
.scTravellers{pointer-events:none}

@supports (offset-path: path("M0 0 L1 1")){
  .scTrav{
    offset-distance:0%;
    offset-rotate:0deg;            /* upright by default — see .scCar */
    animation-name:scTravel;
    animation-timing-function:linear;
    animation-iteration-count:infinite;
    will-change:offset-distance;
  }
  @keyframes scTravel{
    0%   { offset-distance:0%;   opacity:0 }
    4%   { opacity:.92 }
    96%  { opacity:.92 }
    100% { offset-distance:100%; opacity:0 }
  }

  /* Only the car turns with the road. A walking figure, an elephant
     and a monkey stay upright on a slope — rotating them to the
     tangent would tip them over on every descent. */
  .scCar{ offset-rotate:auto }
}

/* --- gaits. Inner group only, transform only. --- */
.scWalker .scTravIn{animation:scStride .62s ease-in-out infinite}
@keyframes scStride{0%,100%{transform:translateY(0)}50%{transform:translateY(-1.7px)}}

.scLegA,.scLegB,.scArmA,.scArmB{transform-box:fill-box}
.scLegA{transform-origin:0% 0%;   animation:scStepA .62s ease-in-out infinite}
.scLegB{transform-origin:100% 0%; animation:scStepB .62s ease-in-out infinite}
.scArmA{transform-origin:0% 0%;   animation:scStepB .62s ease-in-out infinite}
.scArmB{transform-origin:100% 0%; animation:scStepA .62s ease-in-out infinite}
@keyframes scStepA{0%,100%{transform:rotate(19deg)} 50%{transform:rotate(-17deg)}}
@keyframes scStepB{0%,100%{transform:rotate(-17deg)}50%{transform:rotate(19deg)}}

.scEleph .scTravIn{animation:scLumber 2.1s ease-in-out infinite}
@keyframes scLumber{
 0%,100%{transform:translateY(0)   rotate(-.9deg)}
 50%    {transform:translateY(-1px) rotate(.9deg)}
}
.scPawA,.scPawB{transform-box:fill-box;transform-origin:50% 0%}
.scPawA{animation:scPlodA 2.1s ease-in-out infinite}
.scPawB{animation:scPlodB 2.1s ease-in-out infinite}
@keyframes scPlodA{0%,100%{transform:rotate(9deg)} 50%{transform:rotate(-9deg)}}
@keyframes scPlodB{0%,100%{transform:rotate(-9deg)}50%{transform:rotate(9deg)}}
.scTrunk{transform-box:fill-box;transform-origin:0% 0%;
 animation:scSwing 3.3s ease-in-out infinite}
@keyframes scSwing{0%,100%{transform:rotate(-7deg)}50%{transform:rotate(9deg)}}

/* the monkey leaves one contour and lands on the next */
.scMonkey .scTravIn{animation:scHop 1.25s cubic-bezier(.32,0,.42,1) infinite}
@keyframes scHop{
 0%   {transform:translateY(0)}
 30%  {transform:translateY(-30px)}
 52%  {transform:translateY(-2px)}
 72%  {transform:translateY(-16px)}
 88%  {transform:translateY(-1px)}
 100% {transform:translateY(0)}
}
.scTail{transform-box:fill-box;transform-origin:100% 100%;
 animation:scFlick 1.25s ease-in-out infinite}
@keyframes scFlick{0%,100%{transform:rotate(-11deg)}50%{transform:rotate(13deg)}}

/* the car: wheels turn, headlights breathe */
.scWheel{transform-box:fill-box;transform-origin:50% 50%;
 animation:scRoll .55s linear infinite}
@keyframes scRoll{to{transform:rotate(360deg)}}
.scBeam,.scLamp{animation:scHeadlight 4.2s ease-in-out infinite}
@keyframes scHeadlight{0%,100%{opacity:.62}50%{opacity:1}}

@media(prefers-reduced-motion:reduce){
 .scTravellers{display:none}
}

/* ============================================================
   THE MOTION SYSTEM
   ------------------------------------------------------------
   Everything below was lost once. A file write truncated this
   block, and because the reading ring is injected by JavaScript,
   its markup survived while its stylesheet did not — leaving an
   unsized <svg> in normal flow with SVG's default black fill,
   which is the huge black circle that appeared at the bottom of
   every page.

   Two lessons are now enforced rather than remembered:
     · anything injected by script carries its own width, height
       and fill as ATTRIBUTES, so it degrades to something small
       and correct rather than something enormous and black;
     · _test.js fails if any class used in JavaScript has no rule
       in CSS, which is exactly the signature that bug had.

   ONE SET OF CURVES, USED EVERYWHERE
   `linear` and `ease-in-out` are the defaults, and defaults are
   what make motion feel like a slideshow. Everything here uses
   one of four curves, each with a job.
   ============================================================ */
:root{
  --e-out:    cubic-bezier(.25,1,.5,1);      /* the workhorse: fast out, long settle */
  --e-soft:   cubic-bezier(.16,1,.3,1);      /* even longer tail, for large travel */
  --e-spring: cubic-bezier(.34,1.56,.64,1);  /* overshoots once — press feedback */
  --e-swift:  cubic-bezier(.4,0,.2,1);       /* short, for colour and opacity */
  --d-fast:.22s; --d-mid:.42s; --d-slow:.85s;
}

/* ---------- reveals, re-cut on the new curves ---------- */
.rev,.rev-l,.rev-r,.rev-s{
 transition:opacity var(--d-slow) var(--e-soft),
            transform var(--d-slow) var(--e-soft),
            filter var(--d-slow) var(--e-soft)}
.rev{transform:translate3d(0,34px,0) scale(.985);filter:blur(3px)}
.rev.in{transform:none;filter:blur(0)}
.rev-l{transform:translate3d(-40px,0,0)}
.rev-r{transform:translate3d(40px,0,0)}
.rev-s{transform:scale(.93)}
.mdrift-l.rev{transform:translate3d(-30px,30px,0) scale(.985)}
.mdrift-r.rev{transform:translate3d(30px,30px,0) scale(.985)}
.mdrift-l.rev.in,.mdrift-r.rev.in{transform:none}

/* ---------- headings arrive a word at a time ---------- */
.msplit .mword{display:inline-block;opacity:0;
 transform:translate3d(0,.55em,0) rotate(1.4deg);
 transition:opacity .62s var(--e-out),transform .62s var(--e-spring)}
.msplit.in .mword{opacity:1;transform:none}

/* ============================================================
   MICRO-INTERACTIONS
   A hover is three things happening together, not one. The card
   leans toward the pointer, lifts slightly, and grows a soft glow
   in its own accent colour. Each is cheap on its own; the
   combination is what reads as quality.

   --rx/--ry are written by motion.js from the pointer position and
   default to 0, so an element that is never hovered — or a touch
   device, where the script never runs — sits exactly flat.
   ============================================================ */
.tilt{
 --rx:0deg; --ry:0deg; --lift:0px; --glow:0;
 transform:perspective(900px) rotateX(var(--rx)) rotateY(var(--ry))
           translate3d(0,var(--lift),0);
 transition:transform var(--d-mid) var(--e-out),
            box-shadow var(--d-mid) var(--e-out),
            border-color var(--d-fast) var(--e-swift);
 transform-style:preserve-3d;
 will-change:transform}
.tilt:hover,.tilt:focus-within{
 --lift:-6px;
 box-shadow:0 26px 60px -30px rgba(15,46,36,.55),
            0 0 0 1px rgba(31,122,90,.14),
            0 0 38px -12px rgba(31,122,90,calc(.55 * var(--glow)))}
/* children float slightly above the card face, which is what
   makes the tilt read as depth rather than as skew */
.tilt > *{transform:translateZ(14px)}

/* buttons: press has to feel like contact */
.btn,.askfab,.iconbtn,.askchip,.tab{
 --mx:0px; --my:0px;
 transform:translate3d(var(--mx),var(--my),0);
 transition:transform var(--d-mid) var(--e-out),
            background var(--d-fast) var(--e-swift),
            border-color var(--d-fast) var(--e-swift),
            box-shadow var(--d-mid) var(--e-out)}
.btn:hover{transform:translate3d(var(--mx),calc(var(--my) - 3px),0) scale(1.03)}
.btn:active{transform:translate3d(var(--mx),var(--my),0) scale(.975);
 transition-duration:.09s}
.iconbtn:hover,.askchip:hover{transform:translate3d(var(--mx),calc(var(--my) - 2px),0) scale(1.04)}
.iconbtn:active,.askchip:active{transform:scale(.96);transition-duration:.09s}

/* ============================================================
   SCROLL-DRIVEN
   motion.js writes --p, this element's progress through the
   viewport from 0 to 1. The transform is a pure function of that
   number, so the movement is tied to the scroll position rather
   than to a timer, and it reverses exactly when you scroll back.
   ============================================================ */
.sdrive{--p:0;
 transform:translate3d(0,calc((1 - var(--p)) * 22px),0)
           scale(calc(.975 + var(--p) * .025));
 opacity:calc(.35 + var(--p) * .65)}

/* ============================================================
   AMBIENT
   Three soft blobs drifting behind the content on long, unequal
   cycles so the page is never completely still. Very low opacity
   and no blend mode — this is meant to be felt at the edge of
   vision, and a blend mode here would cost a compositing pass on
   every frame it is visible.
   ============================================================ */
#mamb{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden;
 contain:layout paint}
#mamb i{position:absolute;display:block;border-radius:50%;filter:blur(60px);
 will-change:transform}
#mamb .a1{width:46vw;height:46vw;left:-8vw;top:8vh;
 background:radial-gradient(circle,rgba(31,122,90,.085),transparent 68%);
 animation:mAmbA 44s var(--e-inout,ease-in-out) infinite alternate}
#mamb .a2{width:38vw;height:38vw;right:-6vw;top:38vh;
 background:radial-gradient(circle,rgba(185,143,53,.07),transparent 68%);
 animation:mAmbB 61s ease-in-out infinite alternate}
#mamb .a3{width:52vw;height:52vw;left:22vw;bottom:-14vh;
 background:radial-gradient(circle,rgba(169,102,60,.055),transparent 70%);
 animation:mAmbC 79s ease-in-out infinite alternate}
@keyframes mAmbA{to{transform:translate3d(9vw,7vh,0) scale(1.14)}}
@keyframes mAmbB{to{transform:translate3d(-11vw,-6vh,0) scale(1.2)}}
@keyframes mAmbC{to{transform:translate3d(7vw,-9vh,0) scale(1.1)}}
/* THE AMBIENT LAYER SITS BEHIND EVERYTHING, AND IT DOES SO ON ITS
   OWN. There used to be a rule here raising nav, header, main,
   section and footer to `position:relative; z-index:1` so they
   would paint over the blobs. It broke two things at once, and
   both were reported as separate bugs:

     · `nav` is `position:sticky; z-index:300` in style.css. This
       rule has identical specificity and motion.css loads later,
       so it won — the navigation stopped being sticky and scrolled
       away with the page. That is the "getting lost when scrolling".

     · It also turned the nav into a z-index:1 stacking context,
       which trapped `.langmenu` (z-index:310) inside it. The
       language menu opened correctly and rendered UNDERNEATH the
       next section every time. That is the "language button
       doesn't work" — the button was fine; its menu was buried.

   A negative z-index does the same job without touching a single
   other element. `body` has a background, which propagates to the
   canvas and paints behind negative-z-index children, so the blobs
   land between the page background and the content exactly as
   intended. NEVER restyle position or z-index on a shared landmark
   from this file. */
#mamb{z-index:-1}

/* ============================================================
   READING PROGRESS
   It is drawn as a halo around Mondul rather than as a fourth
   floating widget in its own corner. See mondul.css.
   ============================================================ */
#mring{position:fixed;right:26px;bottom:118px;width:44px;height:44px;z-index:200;
 opacity:0;transform:translate3d(0,10px,0) scale(.9);pointer-events:none;
 transition:opacity var(--d-mid) var(--e-swift),transform var(--d-mid) var(--e-out)}
#mring.on{opacity:1;transform:none}
#mring svg{width:44px;height:44px;display:block;transform:rotate(-90deg)}
#mring .mrbg{fill:rgba(255,253,248,.86);stroke:var(--line);stroke-width:2}
#mring .mrfg{fill:none;stroke:var(--emerald);stroke-width:2.6;stroke-linecap:round;
 transition:stroke-dashoffset .1s linear}
@media(max-width:700px){ #mring{display:none} }

@media(prefers-reduced-motion:reduce){
 .msplit .mword{opacity:1!important;transform:none!important}
 .tilt,.btn,.askfab,.iconbtn,.askchip,.tab{transform:none!important}
 .rev,.rev-l,.rev-r,.rev-s{opacity:1!important;transform:none!important;filter:none!important}
 .sdrive{transform:none!important;opacity:1!important}
 #mamb,#mring{display:none}
}

/* ---- .reveal : the page-level reveal class ----
   community.js, live.js and contribute.js have always added this
   class and toggled `.in` on it with their own observers — and it
   has never had a single rule, so on three pages that code has been
   running and doing nothing at all. Found by _css_check.js. Given
   the same treatment as `.rev` so those pages now animate through
   the machinery they already had. */
.reveal{opacity:0;transform:translate3d(0,30px,0) scale(.99);
 transition:opacity var(--d-slow) var(--e-soft),transform var(--d-slow) var(--e-soft)}
.reveal.in{opacity:1;transform:none}

/* chart category labels — the hook the collision test reads */
.barlab{font-variant-numeric:tabular-nums}

@media(prefers-reduced-motion:reduce){
 .reveal{opacity:1!important;transform:none!important}
}

/* ---- more of the site in motion, all on the shared curves ---- */
tbody tr{opacity:0;transform:translate3d(0,10px,0);
 animation:mRow .55s var(--e-out) forwards}
tbody tr:nth-child(1){animation-delay:.02s}
tbody tr:nth-child(2){animation-delay:.06s}
tbody tr:nth-child(3){animation-delay:.10s}
tbody tr:nth-child(4){animation-delay:.14s}
tbody tr:nth-child(5){animation-delay:.18s}
tbody tr:nth-child(n+6){animation-delay:.22s}
@keyframes mRow{to{opacity:1;transform:none}}

/* figures and images arrive behind a moving edge rather than fading */
.sightimg,.gitem,.chartwrap{clip-path:inset(0 0 0 0)}
.sightimg.rev,.gitem.rev,.chartwrap.rev{clip-path:inset(0 0 100% 0)}
.sightimg.rev.in,.gitem.rev.in,.chartwrap.rev.in{clip-path:inset(0 0 0 0);
 transition:clip-path 1s var(--e-soft),opacity .8s var(--e-out),transform .8s var(--e-soft)}

/* the nav underline springs rather than slides */
.navlinks a:after{transition:width .42s var(--e-spring)}
.moremenu a{transition:background .2s var(--e-swift),padding-left .3s var(--e-out)}
.moremenu a:hover{padding-left:18px}

/* tabs and pills answer the press */
.tab:active,.factpill:active{transform:scale(.95);transition-duration:.09s}

/* the scroll cue eases instead of bobbing linearly */
@keyframes cue{
 0%,100%{transform:translate(-50%,0);opacity:.5}
 50%    {transform:translate(-50%,9px);opacity:1}
}
.scrollcue{animation:cue 3.2s var(--e-inout,cubic-bezier(.65,0,.35,1)) infinite}

@media(prefers-reduced-motion:reduce){
 tbody tr{opacity:1!important;transform:none!important;animation:none!important}
 .sightimg,.gitem,.chartwrap,.sightimg.rev,.gitem.rev,.chartwrap.rev{clip-path:none!important}
}

/* ============================================================
   WATER
   ------------------------------------------------------------
   The request was that moving from one part of the site to another
   should feel like flow rather than like slides advancing. Four
   things produce that, and none of them is a new effect — they are
   all about REMOVING edges.

   1. A longer tail on the curve. `--e-water` decelerates for most
      of its duration, the way a mass of liquid settles: almost all
      the distance is covered early, then it eases for a long time.
      Nothing in a fluid stops abruptly.

   2. No hard seams between sections. A band of colour meeting
      another band at a straight line is the visual opposite of
      flow, so every change of ground now bleeds through a soft
      gradient instead of cutting.

   3. Arrival by depth, not by index. Things enter slightly out of
      focus and settle into sharpness, which is how something rising
      through water resolves.

   4. A wave, not a queue. Reveals are staggered by the element's
      position across the row, so a grid fills like a swell moving
      across it rather than items arriving one after another.
   ============================================================ */
:root{
  --e-water: cubic-bezier(.19,1,.22,1);   /* very long tail */
  --e-swell: cubic-bezier(.37,0,.16,1);   /* gathers, then releases */
}

/* 1 + 3 — everything that arrives, arrives like this */
.rev,.rev-l,.rev-r,.rev-s,.reveal{
 transition:opacity .8s var(--e-water), transform .8s var(--e-water)}
/* NO BLUR ON REVEALS. A filter forces a fresh raster of the element
   on every frame of the transition; on a page with dozens of them
   arriving at once that is where the stutter came from. Translate
   and opacity are composited — they cost nothing and read the same
   at these distances. */
.rev{transform:translate3d(0,34px,0) scale(.985)}
.rev.in{transform:none}
.reveal{transform:translate3d(0,30px,0) scale(.99)}
.reveal.in{transform:none}

/* 2 — sections bleed into one another instead of butting up.
   The `position:relative` these gradients need lives in style.css,
   where landmarks are owned. Setting it from here is what the
   landmark guard in _css_check.js exists to prevent — and it caught
   this the first time it ran, which is the whole point of it. */
.alt::before,.dark::before,.qfbar::before{
 content:"";position:absolute;left:0;right:0;top:-64px;height:64px;pointer-events:none;
 background:linear-gradient(to bottom,rgba(247,240,224,0),rgba(247,240,224,1))}
.dark::before{background:linear-gradient(to bottom,rgba(15,46,36,0),rgba(15,46,36,1))}
.qfbar::before{background:linear-gradient(to bottom,rgba(15,46,36,0),rgba(15,46,36,1))}
.alt::after,.dark::after,.qfbar::after{
 content:"";position:absolute;left:0;right:0;bottom:-64px;height:64px;pointer-events:none;
 background:linear-gradient(to top,rgba(247,240,224,0),rgba(247,240,224,1))}
.dark::after{background:linear-gradient(to top,rgba(15,46,36,0),rgba(15,46,36,1))}
.qfbar::after{background:linear-gradient(to top,rgba(15,46,36,0),rgba(15,46,36,1))}

/* the hero releases into the page rather than ending */
header::before{content:"";position:absolute;left:0;right:0;bottom:0;height:120px;z-index:3;
 pointer-events:none;
 background:linear-gradient(to bottom,rgba(5,19,14,0),rgba(255,253,248,.22))}

/* 4 — a swell across the row, written by motion.js as --wave */
.rev,.reveal{--wave:0}
.rev,.reveal{transition-delay:calc(var(--wave) * 1ms)}

/* panels and drawers move like water finding a level */
.panel.on{animation:panelIn .8s var(--e-water)}
@keyframes panelIn{
 from{opacity:0;transform:translateY(18px) scale(.994)}
 to  {opacity:1;transform:none}
}
.drawer .dpanel,#searchpanel .searchbox,.askbox{
 animation:panelRise .75s var(--e-water)}
@keyframes panelRise{
 from{opacity:0;transform:translateY(28px) scale(.99)}
 to  {opacity:1;transform:none}
}

/* tab switches glide rather than snap */
.tab{transition:background .45s var(--e-water),color .45s var(--e-water),
     border-color .45s var(--e-water),transform .5s var(--e-swell)}

/* the section rule fills like a channel */
.rule.in:after{transition:width 1.4s var(--e-water) .1s}

@media(prefers-reduced-motion:reduce){
 .alt::before,.alt::after,.dark::before,.dark::after,
 .qfbar::before,.qfbar::after,header::before{display:none}
 .rev,.reveal{filter:none!important;transition-delay:0s!important}
}

/* ---- a little more magic in the flow ---- */
/* Section headings surface with their rule filling underneath, so a
   new section announces itself the way a swell reaches a shore
   rather than the way a slide changes. */
section h2.rule.in{animation:mSurface 1.3s var(--e-water) both}
@keyframes mSurface{
 0%  {transform:translateY(18px) scale(.988);opacity:0}
 100%{transform:none;opacity:1}
}
/* links ripple under the cursor instead of switching state */
.navlinks a,.moremenu a,.sres,footer a{
 transition:color .45s var(--e-water),background .45s var(--e-water),
            padding-left .45s var(--e-water)}
/* the language menu unfolds rather than appearing */
.langmenu{transform-origin:100% 0;
 transition:opacity .3s var(--e-swift),transform .5s var(--e-spring)}
.langmenu:not(.open){opacity:0;transform:translateY(-8px) scale(.96);
 pointer-events:none;display:block;visibility:hidden}
.langmenu.open{opacity:1;transform:none;visibility:visible}
/* charts wash in from their baseline */
svg[data-chart="bars"] rect{transform-box:fill-box;transform-origin:0% 50%;
 animation:mFill 1.1s var(--e-water) both}
@keyframes mFill{from{transform:scaleX(0);opacity:.4}to{transform:none;opacity:1}}
@media(prefers-reduced-motion:reduce){
 section h2.rule.in,svg[data-chart="bars"] rect{animation:none!important}
 .langmenu:not(.open){display:none}
}

/* ============================================================
   THE LAST LAYER — THINGS YOU FEEL RATHER THAN NOTICE
   ============================================================ */

/* ---- LEAVING A PAGE ----
   A hard white flash between pages is the single most dated thing
   left on most static sites. The page now dims and lifts a few
   pixels as the next one is fetched, so a navigation reads as one
   continuous movement instead of a cut. Kept under 260ms: any
   longer and it stops feeling responsive and starts feeling slow. */
body{transition:opacity .26s ease, transform .26s cubic-bezier(.4,0,1,1)}
body.mLeaving{opacity:0;transform:translateY(-10px)}

/* ---- LIGHT THAT FOLLOWS THE POINTER ----
   On the dark bands a soft warm light tracks the cursor. It costs
   one custom-property write per frame and no layout at all, and it
   is the difference between a flat dark rectangle and a surface. */
.deep-band{--mx:50%;--my:50%}
.deep-band::marker{content:""}
.mSpot{position:absolute;inset:0;pointer-events:none;z-index:0;
 background:radial-gradient(420px circle at var(--mx) var(--my),
   rgba(111,227,173,.13), rgba(111,227,173,.05) 42%, transparent 68%);
 opacity:0;transition:opacity .5s var(--e-water,ease)}
.deep-band:hover .mSpot{opacity:1}

/* ---- A LIGHT PASSES OVER A HEADING AS IT ARRIVES ----
   One sweep, once, on reveal. Not a loop — a looping shimmer on a
   heading is a sale banner. */
.rule.in:before{content:"";position:absolute;inset:-2px -8px;pointer-events:none;
 background:linear-gradient(105deg,transparent 38%,rgba(255,255,255,.5) 50%,transparent 62%);
 transform:translateX(-120%);
 animation:mSheen 1.5s cubic-bezier(.22,1,.36,1) .25s}
@keyframes mSheen{to{transform:translateX(120%)}}

/* ---- CARDS LIFT TOWARD THE POINTER ----
   Shadow and lift together. A card that lifts without its shadow
   growing looks like it is sliding, not rising. */
.pcard,.dcard,.gcard,.minicard{
 transition:transform .5s var(--e-spring,cubic-bezier(.34,1.56,.64,1)),
            box-shadow .5s var(--e-water,ease)}
.pcard:hover,.dcard:hover,.gcard:hover,.minicard:hover{
 transform:translateY(-5px)}

/* ---- THE NAV CONDENSES ONCE YOU HAVE LEFT THE TOP ---- */
nav{transition:box-shadow .4s var(--e-water,ease), backdrop-filter .4s ease}
body.mScrolled nav{box-shadow:0 6px 26px rgba(11,33,24,.16)}

@media (prefers-reduced-motion:reduce){
  body,body.mLeaving{transition:none;opacity:1;transform:none}
  .mSpot{display:none}
  .rule.in:before{animation:none;display:none}
}

/* ============================================================
   CATEGORY CHANGE — A DISSOLVE, NOT A CUT
   ------------------------------------------------------------
   The old panel used to be removed in the same frame the new one
   appeared. Two states, no motion between them: that is the flash.

   Now they OVERLAP. For 190 milliseconds both panels are in the
   document — the outgoing one settling down and out, the incoming
   one already rising — and because they are both absolutely
   positioned during the handover neither pushes the other around.
   The eye reads one movement rather than two states.
   ============================================================ */
.panel.on{animation:panelRise .62s cubic-bezier(.22,1,.36,1)}
@keyframes panelRise{
 from{opacity:0;transform:translateY(20px) scale(.994)}
 to  {opacity:1;transform:none}
}

/* the outgoing panel — still in flow, on its way out */
.panel.leaving{
 animation:panelSink .22s cubic-bezier(.4,0,1,1) forwards;
 pointer-events:none}
@keyframes panelSink{
 from{opacity:1;transform:none}
 to  {opacity:0;transform:translateY(-12px) scale(.996)}
}

/* The tab itself moves with the content. A tab that only changes
   colour while the panel below animates feels disconnected from
   what it just did. */
.tab{transition:background .32s var(--e-water,ease),color .32s ease,
     border-color .32s ease,transform .38s var(--e-spring,ease)}
.tab.on{transform:translateY(-2px)}

/* the strip itself gets a travelling underline */
.tabs{position:relative}

/* ============================================================
   10 · THE SMOOTHNESS PASS
   ------------------------------------------------------------
   What makes a site like jasminadenner.com feel smooth is not one
   effect. It is three disciplines applied without exception:

   1. ONE EASING FAMILY. Every transition on that kind of site
      shares a curve. Mixing `ease`, `ease-out` and three different
      cubic-beziers is what makes a page feel assembled by
      different people — each element arrives with a slightly
      different personality and the eye reads the inconsistency
      even when it cannot name it.

   2. NOTHING SNAPS. No transition under 240ms on anything larger
      than a small control. Fast is not smooth; fast is abrupt.

   3. EVERYTHING HAS A RESTING STATE IT RETURNS TO. Hover effects
      that leave instantly but arrive slowly feel broken, because
      real objects take as long to settle as they took to move.
      Exit durations here are 80% of entry — slightly quicker, so
      it feels responsive, never half.
   ============================================================ */
:root{
  /* the single family. Every duration below is a multiple of 60ms,
     which is why sequences on this page always feel related. */
  --sm:    cubic-bezier(.22, 1, .36, 1);      /* the house curve  */
  --sm-in: cubic-bezier(.55, 0, 1, .45);      /* leaving          */
  --t1: .24s;  --t2: .42s;  --t3: .66s;  --t4: .96s;
}

/* Anything interactive settles on the house curve unless it has
   a considered reason not to. */
a, button, .btn, .tab, .qf, .pill, .factpill, .dcard, .svc,
.minicard, .sight, .gcard, .pcard, .ci, .ph, input, select, textarea,
.mdpBtn, .dgo, .item{
  transition-timing-function: var(--sm);
  transition-duration: var(--t2);
}
a:active, button:active, .btn:active, .tab:active{
  transition-duration: var(--t1);         /* the press is the one
                                             place quick is right */
}

/* Links get a drawn underline rather than a colour flip. A colour
   change is a state; a line that grows is a movement, and movement
   is what reads as smooth. */
.body a:not(.btn):not(.dcard):not(.dgo),
p a:not(.btn){
  background-image:linear-gradient(currentColor,currentColor);
  background-size:0% 1px;background-repeat:no-repeat;
  background-position:0 100%;
  transition:background-size var(--t3) var(--sm), color var(--t2) var(--sm);
  text-decoration:none}
.body a:not(.btn):not(.dcard):not(.dgo):hover,
p a:not(.btn):hover{background-size:100% 1px}

/* Images settle rather than pop when they finish loading. */
img{transition:opacity var(--t3) var(--sm)}

/* The whole document scrolls on one behaviour. Section anchors
   inherit the eased scroller in motion.js; this covers anything
   the browser handles itself. */
@media (prefers-reduced-motion:no-preference){
  html{scroll-padding-top:96px}
}

/* Focus rings arrive on the same curve as everything else —
   keyboard users get the same quality of motion, not a hard
   outline snapping into place. */
:focus-visible{
  outline:2px solid var(--mint,#6FE3AD);outline-offset:3px;
  transition:outline-offset var(--t1) var(--sm)}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    transition-duration:.01ms!important;
    animation-duration:.01ms!important;
    animation-iteration-count:1!important}
}
