
  :root{
    --coral:#ee8272; --coral-deep:#d9584f; --rose:#f6cfc6;
    --blush:#f7f3e6; --cream:#fffdf6; --sage:#6f7b5a; --sage-soft:#c3d0ae; --kraft:#cfa863;
    --ink:#4f544a; --muted:#8b8d78; --line:#e7e1cc;
    --airmail:#3f5a72; --airmail-deep:#2c4055;
    --script:'Mrs Saint Delafield','Segoe Script','Brush Script MT',cursive;
    --serif:'Fraunces',Georgia,'Times New Roman',serif;
    --mono:'Space Mono','Courier New',monospace;
    /* high-contrast garamond for the names: elegant, and nothing like the body face */
    --display:'Cormorant Garamond','Didot','Baskerville','Palatino Linotype',Georgia,serif;
    /* the bride's name under "Miss to Mrs" */
    --name-font:'Bodoni Moda','Didot','Bodoni 72',Georgia,serif;
  }
  *{box-sizing:border-box;}
  html{scroll-behavior:smooth; overflow-x:clip;}
  body{
    margin:0; font-family:var(--serif); color:var(--ink);
    /* The washes all live in the .paper layer below, drawn in SVG where they can
       have torn watercolour edges. Here the page is only its paper colour. */
    background:var(--blush);
    overflow-x:hidden;
  }
  .label{ font-family:var(--mono); text-transform:uppercase; letter-spacing:.16em; font-size:.68rem; color:var(--sage); }

  @keyframes fadeUp{ from{ opacity:0; transform:translateY(18px); } to{ opacity:1; transform:translateY(0); } }

  .airmail-edge{
    height:9px; flex:none;
    background:repeating-linear-gradient(135deg,
      var(--coral-deep) 0 12px, var(--cream) 12px 17px,
      var(--airmail) 17px 29px, var(--cream) 29px 34px);
  }

  /* ---- hero ---- */
  .hero{
    position:relative; min-height:100svh; display:flex; flex-direction:column;
    align-items:center; justify-content:center; text-align:center; padding:40px 20px 24px;
  }
  /* the decorative art (butterflies, case, mill, stamps, keepsakes) are all .deco —
     stacked so the actual hero copy always paints in front of them instead of
     mixing into the artwork on narrow screens where the two crowd together */
  .hero > :not(.deco){ position:relative; z-index:1; }
  .hero .label{ margin:0 0 16px; animation:fadeUp .8s ease both; }
  /* "Travelling from" — a line just above Miss to Mrs; blank = gone. Set in the
     sturdy body italic, not the hairline script: at this size the script was
     too thin to read, and the headline under it is already script enough. */
  .hero .miss-lead{
    margin:0 0 8px; font-family:var(--serif); font-style:italic; font-weight:500;
    color:var(--coral-deep); letter-spacing:.02em;
    font-size:clamp(1.15rem,3.2vw,1.45rem); line-height:1.2;
    animation:fadeUp .8s ease .05s both;
  }
  .hero .miss-lead:empty{ display:none; }
  /* the big cursive headline: Miss, a dashed heart loop with "to" tucked in its
     dip and a plane flying the loop, then Mrs.
     A grid of 1fr | heart | 1fr rather than a flex row: the two fr columns come
     out equally wide (as wide as the longer word), so the heart sits on the exact
     centre line — the line the name and "Travelling from" are centred on. As a
     flex row the wider "Miss" pushed the heart right of centre, and the name
     under it looked off-centre on a phone. */
  .hero h1.miss-to-mrs{
    display:grid; grid-template-columns:1fr auto 1fr; align-items:center;
    column-gap:12px; margin:0;
    font-family:var(--script); font-weight:400; color:var(--coral-deep);
    font-size:clamp(2.8rem,9.5vw,5.2rem); line-height:1;
    animation:fadeUp .8s ease .1s both;
  }
  /* overflow:visible + above the words: the plane starts under "Miss" and ends
     under "Mrs", outside this SVG's own box */
  .hero h1.miss-to-mrs #txtMissWord{ text-align:right; }
  .hero h1.miss-to-mrs #txtMrsWord{ text-align:left; }
  .hero .heart-loop{ width:clamp(46px,9vw,64px); height:auto; flex:none; overflow:visible; position:relative; z-index:1; }
  .hero .heart-loop .heart-to{
    font-family:var(--mono); font-size:16px; font-weight:700; text-transform:uppercase;
    letter-spacing:.08em; fill:var(--sage);
  }
  /* The flight, in the heart SVG's own units (the heart spans 0–70): in from
     under "Miss" at x=-75, up into the heart's point heading up-right, round the
     right lobe, through the dip, round the left lobe, back out of the point
     heading down-right and on under "Mrs" to x=145. Each join keeps the same
     direction on both sides, so the plane never kinks. It fades in at the start
     and out at the end, waits a moment off-screen, and goes again. The words are
     sized in rem/vw and the heart in vw, so on any screen the two ends land
     under the right-hand part of "Miss" and the left-hand part of "Mrs". */
  .hero .heart-loop .plane-dart{
    offset-path:path('M-75 74 C-20 80 15 83 35 64 C55 45 70 32 70 20 C70 10 65 2 55 2 C45 2 35 10 35 20 C35 10 25 2 15 2 C5 2 0 10 0 20 C0 32 15 45 35 64 C55 83 90 80 145 74');
    offset-rotate:auto;
    animation:flyHeart 7.5s linear infinite;
  }
  /* 7.5s a pass: 6s of flight (0–80%), then 1.5s with nothing at all before the
     next one sets off */
  @keyframes flyHeart{
    0%   { offset-distance:0%;   opacity:0; }
    5%   { opacity:1; }
    75%  { opacity:1; }
    80%  { offset-distance:100%; opacity:0; }
    100% { offset-distance:100%; opacity:0; }
  }
  /* The dashed trail: a solid copy of the same path in the mask is unrolled
     (pathLength=100, so the dash offset is a percentage of the length, exactly
     like offset-distance) on the same clock and timing, so the line ends right
     at the plane; then it fades just after the plane, and stays gone through
     the pause. The unrolling and the fade are ONE animation on ONE element on
     purpose: as two (the fade on the visible path) they could restart a frame
     apart, and the whole trail flashed up for an instant between passes.
     Without animation (reduced motion) it simply shows the whole route. */
  .hero .heart-loop .flight-trail-reveal{
    stroke-dasharray:100 100; stroke-dashoffset:0;
    animation:drawTrail 7.5s linear infinite;
  }
  @keyframes drawTrail{
    0%   { stroke-dashoffset:100; opacity:1; }
    80%  { stroke-dashoffset:0;   opacity:1; }
    87%  { stroke-dashoffset:0;   opacity:0; }
    100% { stroke-dashoffset:0;   opacity:0; }
  }
  /* the bride's name, demoted to a subtitle now the headline is "Miss to Mrs" */
  /* Bodoni Moda italic: a high-contrast invitation face, so the name reads as a
     name under the script headline rather than as one more spaced-out label */
  .hero .bride-name{
    font-family:var(--name-font); font-style:italic; font-weight:400; color:var(--ink);
    letter-spacing:.03em;
    font-size:clamp(1.75rem,4.6vw,2.3rem); line-height:1.1; margin:12px 0 0;
    /* Optical centring. The box is centred, but the name is not: letter-spacing
       leaves a gap after the last "a", and the italic leans every letter's top to
       the right, so beside the upright, centred headline it reads as pushed right.
       The left padding balances the trailing gap; the nudge (via left, since the
       fadeUp animation owns transform) brings the lean back over the middle. */
    padding-left:.03em; left:-.06em;
    animation:fadeUp .8s ease .18s both;
  }
  .hero .tagline{
    margin:22px auto 0; max-width:46ch; line-height:1.8; color:var(--muted);
    animation:fadeUp .8s ease .3s both;
  }
  .hero .tagline .lead{
    display:block; font-family:var(--display); font-style:normal; font-weight:400;
    font-size:1.35rem; letter-spacing:.06em; color:var(--coral-deep); margin-bottom:.5em;
  }
  /* one per paragraph of the tagline — a \n in the content file starts a new one */
  .hero .tagline .para{ display:block; }
  .hero .tagline .para + .para{ margin-top:1em; }
  .scroll-cue{
    margin-top:26px; color:var(--muted); text-decoration:none; font-family:var(--mono);
    text-transform:uppercase; letter-spacing:.18em; font-size:.68rem;
    animation:fadeUp .8s ease .45s both;
  }
  .scroll-cue .arrow{ display:block; font-size:1.3rem; margin-top:6px; animation:bob 2.2s ease-in-out infinite; }
  @keyframes bob{ 0%,100%{transform:translateY(0)} 50%{transform:translateY(7px)} }

  /* ---- globe ---- */
  /* Two numbers in settings.js scale the globe: each media query below sets
     --globe-size, and --globe-scale multiplies whatever it says. Narrow screens
     swap in the mobile number below, at the same 860px line the rest of the
     hero's cast uses, so "mobile" means one width across the whole page. */
  .stage{
    --globe-size:300px;
    position:relative; flex:none; z-index:1;
    width:calc(var(--globe-size) * var(--globe-scale,1));
    height:calc(var(--globe-size) * var(--globe-scale,1));
    margin:16px 0 6px;
    border-radius:50%; animation:fadeUp .8s ease .2s both;
    box-shadow:0 0 0 4px var(--cream), 0 0 0 7px var(--kraft), 0 22px 44px rgba(91,74,70,.22);
  }
  #globe{ display:block; width:100%; height:100%; border-radius:50%; }
  /* The hero is a flex column, so this margin does not collapse into the globe's
     own bottom margin — the two add up, and the caption sits clear of the ring. */
  .route-caption{
    display:flex; align-items:center; justify-content:center; gap:10px; font-family:var(--mono);
    margin-top:18px; color:var(--muted); text-transform:uppercase; letter-spacing:.12em; font-size:.66rem;
  }
  .route-caption::before, .route-caption::after{
    content:''; width:34px; height:1px;
    background:repeating-linear-gradient(90deg,var(--kraft) 0 3px, transparent 3px 7px);
  }

  /* ---- the paper the page is written on ------------------------------------
     Watercolour stationery: blush washes pooled into the corners, a warm sunrise
     glow along the bottom, and a scatter of splashes, leaving the middle of the
     sheet clear for the writing.

     The torn edge of each wash is the trick. A plain ellipse looks like a
     gradient; the same ellipse pushed about by fractal noise (feDisplacementMap)
     and then blurred looks like pigment that has run into the paper. Everything
     is drawn in the palette's own colours, so `settings.js` still owns them.

     Fixed and z-index -1, so cards, stamps and butterflies sit on top of it, and one
     SVG scaled to the viewport means nothing repeats and nothing tiles. */
  .paper{
    position:fixed; inset:0; width:100%; height:100%;
    z-index:-1; pointer-events:none;
  }
  /* The SVG above is a fixed 1200x800 sheet scaled to cover ("slice"), so on a
     tall narrow phone most of that width is cropped away — the corner washes
     land off-screen and all that's left is flat blush. A CSS gradient doesn't
     have that problem (it sizes itself to the real viewport), so mobile gets
     its own wash instead — a diagonal pink-to-honey half/half, with a few
     off-centre blobs breaking it up so it doesn't read as two neat circles.
     Only the wash/splash groups step aside; the world map stays, so it isn't
     lost along with them. */
  /* The wash is the ground the whole page is read off, so on a phone it has to
     stay behind the words rather than compete with them. Two things were making
     it compete: the gradient is painted over the *document*, not the viewport, so
     its far end landed as a solid honey block under the account card; and the
     world map underneath it is sized to a 1200x800 sheet, so on a 375px screen a
     single continent filled the column and every muted line of text sat on top of
     one. The colour is pulled right down to a hint at the corners.
     The map is not faded to nothing (at .04 nobody could see it): it is shrunk
     instead, so a portrait phone shows several continents — the Americas' edge,
     Europe, Africa, Asia — rather than one blown-up coastline, and the words land
     on open "sea" as often as on land. Small enough features read as a map at a
     strength the text can still sit on. Portrait only: a landscape phone already
     sees the whole sheet, and shrinking it there would leave bare margins. */
  @media (max-width:860px){
    .paper .washes, .paper .splashes{ display:none; }
    #worldMap{ opacity:.13; }
  }
  @media (max-width:860px) and (orientation:portrait){
    #worldMap{ transform-box:view-box; transform-origin:50% 50%; transform:scale(.62); }
  }
  @media (max-width:860px){
    body{
      background:
        radial-gradient(58% 30% at 12% 4%, rgba(245,200,200,.62) 0%, transparent 70%),
        radial-gradient(46% 24% at 78% 14%, rgba(242,152,107,.18) 0%, transparent 70%),
        radial-gradient(52% 26% at 18% 62%, rgba(245,200,200,.26) 0%, transparent 68%),
        radial-gradient(64% 30% at 88% 96%, rgba(224,184,74,.28) 0%, transparent 68%),
        linear-gradient(170deg, #f9e2dd 0%, var(--blush) 26%, var(--blush) 76%, #f7ead0 100%);
    }
  }

  /* ---- decorations ---- */
  .deco{ position:absolute; pointer-events:none; z-index:0; }
  .d-butterflies{ top:10px; left:0; width:min(340px,40vh); }   /* clear of the airmail stripe they flutter up toward */
  /* The opening text is taller than the screen, so anything pinned to the
     hero's bottom (or placed by % of its height) landed below the fold on every
     screen. The decorations are placed against the FIRST SCREEN instead:
     --screen is its height measured from the hero's top (the hero starts under
     the 9px airmail stripe). The suitcase and windmill hang from its bottom
     edge: top = screen − their own height, worked out from their width (--w)
     and their drawings' proportions (140/190 and 172/140). Their widths also
     shrink with the screen's height, so a short laptop still fits the
     keepsakes above them. */
  .hero{
    --screen:calc(100vh - 9px); --screen:calc(100svh - 9px);
    --case-w:min(288px,30vh); --mill-w:min(224px,24vh);
  }
  /* the paragraph never runs into the corners the suitcase and windmill stand in:
     it is kept narrower than the gap between them (only bites on a tight screen) */
  .hero .tagline{ max-width:min(46ch, calc(100vw - 2 * var(--case-w) - 48px)); }
  .globe-row{ display:contents; }   /* a row on phones only — see the 520px block */
  .d-case{
    --w:var(--case-w); width:var(--w); left:8px;
    top:calc(var(--screen) - var(--w) * .737 - 6px);
  }
  .d-mill{
    --w:var(--mill-w); width:var(--w); right:12px;
    top:calc(var(--screen) - var(--w) * 1.229 - 10px);
    /* the sail tips reach a few units above the viewBox (y≈-3) as they turn;
       without this they are clipped flat each time one passes the top */
    overflow:visible;
  }
  /* the pair strolls the width of the page, turns on the spot, strolls back */
  /* no overflow clip here: the raised shoe needs to cross this box's top edge.
     The stroll keeps the pair on screen by itself, so nothing needs cutting off. */
  /* The lane sits in the "What it costs" grid, between the heading and the bank
     card, so the walk is the lane's own width (a container, read with cqw), not the
     screen's. js/app.js paceClogs() measures the same width. */
  .clog-lane{ width:100%; container-type:inline-size; }
  .d-clogs{
    --stroll-x:calc(100cqw - 100%);
    width:250px; display:block; opacity:.95; overflow:visible;
    transform-origin:50% 82%;              /* pivot on the soles, not mid-air */
    /* linear on purpose: the gait ticks at a fixed rate, so any easing on the
       travel leg leaves the feet still stepping while the pair has stalled
       short of the edge. The turn keyframes set their own easing below. */
    animation:stroll var(--stroll-secs,26s) linear infinite;
  }
  /* Turning by mirroring reads as a cheat — the pair blinks inside out. Swinging
     them through rotateY means they foreshorten to edge-on and open the far side,
     which is what a real about-face looks like. */
  /* translateX first so the walk stays flat, then a local perspective so the
     about-face looks identical at either end instead of shearing toward a
     vanishing point fixed to the page. */
  /* The edge-on moment is where a rotateY turn falls apart: dead flat, the pair
     vanishes. So the swing starts the instant they reach the edge, snaps through 60°–120° in a
     fraction of a second, then settles — plus a hop and a lean into the turn,
     the way you actually shift weight to spin on your heels. */
  @keyframes stroll{
    0%    { transform:translateX(0) translateY(0) perspective(900px) rotateY(0deg) rotateZ(0deg); }
    40%   { transform:translateX(var(--stroll-x)) translateY(0) perspective(900px) rotateY(0deg) rotateZ(0deg); }
    44%   { transform:translateX(var(--stroll-x)) translateY(-4px) perspective(900px) rotateY(62deg) rotateZ(-4deg); }
    45.6% { transform:translateX(var(--stroll-x)) translateY(-5px) perspective(900px) rotateY(118deg) rotateZ(-2deg);
            animation-timing-function:ease-out; }
    48%   { transform:translateX(var(--stroll-x)) translateY(-3px) perspective(900px) rotateY(150deg) rotateZ(2deg); }
    52%   { transform:translateX(var(--stroll-x)) translateY(0) perspective(900px) rotateY(180deg) rotateZ(0deg); }
    90%   { transform:translateX(0) translateY(0) perspective(900px) rotateY(180deg) rotateZ(0deg); }
    93.3% { transform:translateX(0) translateY(-4px) perspective(900px) rotateY(242deg) rotateZ(4deg); }
    94.7% { transform:translateX(0) translateY(-5px) perspective(900px) rotateY(298deg) rotateZ(2deg);
            animation-timing-function:ease-out; }
    96.7% { transform:translateX(0) translateY(-3px) perspective(900px) rotateY(330deg) rotateZ(-2deg); }
    100%  { transform:translateX(0) translateY(0) perspective(900px) rotateY(360deg) rotateZ(0deg); }
  }
  /* every duration and travel distance below comes from settings.js */
  .sails{ transform-origin:70.5px 48.5px; animation:turn var(--sail-secs,14s) linear infinite; }
  @keyframes turn{ to{ transform:rotate(360deg); } }

  /* A real gait rather than a lift on the spot: the planted shoe slides back at a
     steady rate (it is standing still while the pair moves over it), then toes off,
     swings through the air and plants again out front. The two are half a cycle apart. */
  .klomp{ transform-box:fill-box; transform-origin:50% 100%; }
  .klomp-a{ animation:gait var(--clog-secs,1.5s) infinite; }
  .klomp-b{ animation:gait var(--clog-secs,1.5s) infinite var(--clog-offset,-.75s); }
  @keyframes gait{
    0%   { transform:translate(22px,0) rotate(-4deg);   animation-timing-function:linear; }
    50%  { transform:translate(-22px,0) rotate(4deg);   animation-timing-function:ease-out; }
    58%  { transform:translate(-15px,-12px) rotate(-10deg); }
    78%  { transform:translate(4px,-16px) rotate(-7deg); animation-timing-function:ease-in; }
    100% { transform:translate(22px,0) rotate(-4deg); }
  }

  /* the shadow tracks each shoe and shrinks away as it leaves the ground */
  .clog-shadow{ transform-box:fill-box; transform-origin:50% 50%; }
  .shadow-a{ animation:footprint var(--clog-secs,1.5s) infinite; }
  .shadow-b{ animation:footprint var(--clog-secs,1.5s) infinite var(--clog-offset,-.75s); }
  @keyframes footprint{
    0%   { transform:translateX(22px) scale(1); opacity:.26; animation-timing-function:linear; }
    50%  { transform:translateX(-22px) scale(1); opacity:.26; }
    78%  { transform:translateX(4px) scale(.7,.6); opacity:.1; }
    100% { transform:translateX(22px) scale(1); opacity:.26; }
  }

  /* The butterfly flock (top-left of the hero). Each one drifts a loose loop
     around the spot its outer <g> puts it on — the loop is relative, so the same
     keyframes serve all five. The per-butterfly factors below vary the speed, the
     start point and the wingbeat so the flock never moves in step; settings.js
     (butterflySeconds / butterflyPixels) scales them all. The SVG lets them stray
     past its own box rather than clipping a wing at the edge. */
  .d-butterflies{ overflow:visible; }
  .butterfly{ animation:flutter var(--flutter-secs,10s) ease-in-out infinite; }
  .bf-1{ animation-duration:calc(var(--flutter-secs,10s) * 1);    animation-delay:0s; }
  .bf-2{ animation-duration:calc(var(--flutter-secs,10s) * .82);  animation-delay:-3.1s; animation-direction:reverse; }
  .bf-3{ animation-duration:calc(var(--flutter-secs,10s) * 1.18); animation-delay:-6.4s; }
  .bf-4{ animation-duration:calc(var(--flutter-secs,10s) * .9);   animation-delay:-1.7s; animation-direction:reverse; }
  .bf-5{ animation-duration:calc(var(--flutter-secs,10s) * 1.07); animation-delay:-4.8s; }
  .bf-2 .wing{ animation-duration:.19s; }
  .bf-3 .wing{ animation-duration:.26s; }
  .bf-4 .wing{ animation-duration:.21s; }
  .bf-5 .wing{ animation-duration:.17s; }
  @keyframes flutter{
    0%,100%{ transform:translate(0,0) rotate(0deg); }
    25%{ transform:translate(calc(var(--flutter-px,30px) * .6), calc(var(--flutter-px,30px) * -.6)) rotate(10deg); }
    50%{ transform:translate(var(--flutter-px,30px), calc(var(--flutter-px,30px) * .2)) rotate(-6deg); }
    75%{ transform:translate(calc(var(--flutter-px,30px) * .33), calc(var(--flutter-px,30px) * .8)) rotate(8deg); }
  }

  /* keepsakes scattered through the four quadrants of the hero */
  .keepsake{ animation:drift var(--float-secs,7s) ease-in-out infinite; }
  @keyframes drift{
    0%,100%{ transform:translateY(0) rotate(var(--k-tilt,0deg)); }
    50%{ transform:translateY(calc(var(--float-px,10px) * -1)) rotate(calc(var(--k-tilt,0deg) + 1.5deg)); }
  }
  /* tops are fractions of the first screen (--screen), not of the hero, so the
     lower two stay above the fold and clear of the suitcase and windmill */
  .k-tag{      --k-tilt:-7deg;  top:calc(var(--screen) * .36); left:17%;  width:132px; animation-delay:-.4s; }
  .k-ticket{   --k-tilt:6deg;   top:calc(var(--screen) * .36); right:16%; width:158px; animation-delay:-2.1s; }
  .k-letter{   --k-tilt:-5deg;  top:calc(var(--screen) * .55); left:15%;  width:140px; animation-delay:-3.4s; }
  .k-glasses{  --k-tilt:8deg;   top:calc(var(--screen) * .57); right:13%; width:120px; animation-delay:-1.2s; }
  .wing{ transform-origin:0px 0px; animation:flap .22s ease-in-out infinite alternate; }
  .wing-r{ animation-delay:.03s; }
  @keyframes flap{ from{ transform:scaleX(1); } to{ transform:scaleX(.3); } }

  .stamp{ width:124px; height:124px; opacity:.7; }
  .stamp text{
    font-family:var(--mono); font-weight:700; letter-spacing:.14em;
    fill:currentColor; font-size:11px;
  }
  .stamp .stamp-line{ font-size:13px; letter-spacing:.2em; }
  .stamp .stamp-small{ font-size:8px; letter-spacing:.22em; opacity:.75; }
  /* The arc word is fitted to the arc with textLength, so the spacing is set
     there — a long line like LET THE ADVENTURE used to run off both ends. The arc
     is 129 long and runs down to the stamp's middle on both sides; textLength is
     100 (not the full arc) so the words stop short of those ends and arch over
     the top, instead of drooping down the sides where they looked cut off. */
  .stamp .stamp-arc{ font-size:9px; letter-spacing:0; }
  .stamp-journey{ color:var(--coral); top:60px; right:26px; transform:rotate(12deg); width:min(172px,22vh); height:min(172px,22vh); }
  .stamp-adventure{ color:var(--sage); top:6px; left:14px; transform:rotate(-11deg); }

  /* tablets: pull the keepsakes toward the edges as the text column widens out */
  @media (max-width:1100px){
    .k-tag{ left:5%; width:118px; }
    .k-ticket{ right:5%; width:140px; }
    .k-letter{ left:4%; width:124px; }
    .k-glasses{ right:4%; width:108px; }
  }

  /* Phones keep the whole cast — sized in vw and tucked into the margins either
     side of the text column, rather than hidden. */
  @media (max-width:860px){
    /* declared on .stage rather than :root so it beats the desktop number, which
       arrives as an inline style on <html> and is only inherited down to here */
    .stage{ --globe-scale:var(--globe-scale-mobile,1); }

    /* fluid so they track the screen, capped so a 768px tablet doesn't get a
       340px flock crowding the heading */
    .d-butterflies{ top:12px; left:0; width:min(44vw,200px); }
    .hero{ --case-w:min(17vw,130px,26vh); --mill-w:min(14vw,110px,22vh); }
    .d-case{ left:0; opacity:.92; }
    .d-mill{ right:0; opacity:.92; }
    .d-clogs{ width:min(56vw,230px); }

    .stamp{ width:min(21vw,104px); height:min(21vw,104px); opacity:.62; }
    /* below the fixed language pill (top 16px, ~35px tall), not tucked under it */
    .stamp-journey{ top:60px; right:2%; }
    .stamp-adventure{ top:2px; left:2px; }

    .keepsake{ opacity:.95; }
    .k-tag{     top:calc(var(--screen) * .24); left:0;    width:min(23vw,116px); }
    .k-ticket{  top:calc(var(--screen) * .27); right:0;   width:min(25vw,126px); }
    .k-letter{  top:calc(var(--screen) * .46); left:-1%;  width:min(21vw,110px); }
    .k-glasses{ top:calc(var(--screen) * .48); right:-1%; width:min(21vw,104px); }
  }
  /* ---- phones: the hero, recomposed --------------------------------------
     On a wide screen the cast stands in the margins either side of a narrow text
     column. A phone has no margins — the text column IS the screen — so the same
     absolute positions put the butterflies through the headline and the postmark
     through the bride's name.

     So on a phone the hero stops being "text with art around it": a top band the
     butterflies and the postmark own (reserved by the top padding), then the words,
     with the suitcase and the mill standing either side of the globe — the one
     place on a phone's opening screen with room beside the words. Every piece is
     sized so it cannot outgrow its place. */
  @media (max-width:520px){
    /* reserve the top band. vw-based so it tracks the art, with a floor for the
       narrowest phones where the art has stopped shrinking. */
    .hero{ padding:max(104px,30vw) 18px 40px; }

    .keepsake{ display:none; }              /* no margin left for them beside the globe */
    /* the postmark arc text is tuned to just fit its path (see .stamp-arc below), so
       it needs a legible floor here rather than shrinking with the viewport forever —
       under ~100px the letters blur together and read as cut off */
    /* Both stamps are rotated, so their real footprint is wider than the box they
       declare and the arc text was running off the screen edge. These insets are
       measured against the rotated bounds, not the box. */
    .stamp{ width:min(25vw,100px); height:min(25vw,100px); opacity:.75; }
    /* top:44px clears the fixed language pill (top 8px, ~27px tall here), which
       otherwise sat right across the stamp's arc text on first load */
    .stamp-journey{ top:44px; right:18px; width:min(23vw,90px); height:min(23vw,90px); }
    .stamp-adventure{ top:6px; left:10px; }

    /* trimmed to fit inside the reserved bands rather than to fill the corner */
    .d-butterflies{ top:10px; left:0; width:min(36vw,140px); }
    /* the suitcase and windmill stand either side of the globe, in its row, so they
       are on the opening screen with it rather than at the foot of the long text */
    /* Two equal side columns, so the globe sits on the page's centre line however
       wide the suitcase and windmill are (as a flex row the wider suitcase pushed
       it ~14px right). minmax(0,…) keeps them equal even when the suitcase is a
       touch wider than its column — it just reaches a little into the gutter. */
    .globe-row{ display:grid; grid-template-columns:minmax(0,1fr) auto minmax(0,1fr); align-items:end; column-gap:2px; width:100%; }
    .globe-row .d-case, .globe-row .d-mill{ position:static; top:auto; }
    .globe-row .d-case{ justify-self:end; }
    .globe-row .d-mill{ justify-self:start; }
    .globe-row .d-case{ width:min(24vw,96px); margin-bottom:6px; }
    .globe-row .d-mill{ width:min(17vw,70px); }

    /* A cap, not a size: globeScaleMobile in settings.js multiplies --globe-size,
       so at 1.5 the 46vw below became a 259px ball that ate a third of the screen
       and pushed the scroll cue off the fold. Both dimensions are capped together
       so the stage stays a circle whatever number the couple types there. */
    .stage{ max-width:min(44vw,170px); max-height:min(44vw,170px); }

    /* set as one column of type: a short lead, then a paragraph that is read, not
       skimmed past on its way to the numbers */
    .hero .bride-name{ margin-top:12px; }
    .hero .tagline{ max-width:34ch; margin-top:16px; font-size:.94rem; line-height:1.62; }
    .hero .tagline .lead{ font-size:1.16rem; margin-bottom:.4em; }
    .scroll-cue{ margin-top:16px; }
    .scroll-cue .arrow{ margin-top:2px; }
  }

  /* Short phones: 375x667 leaves no clear band for the corner pieces, so open up
     the hero and let it scroll a little rather than overlap the text.
     The base min-height:100svh deliberately stays. Ending the hero short of the
     screen — 679px of hero on a 740px phone — pulled the registry's corner stamp up
     over the fold, where the bottom edge sliced it in half on first load. Content
     taller than the screen still scrolls: 100svh is a floor here, not a cap. */
  @media (max-width:860px) and (max-height:740px){
    /* The padding is scoped above 520px on purpose. Narrower than that, the phone
       block earlier in this file already reserves a top and a bottom band for the
       art at every height, and a flat 40px top here would put the butterflies back
       through the headline. Wider, this is still the rule that opens the hero up. */
    @media (min-width:521px){ .hero{ padding:40px 16px 76px; } }
    .hero .tagline{ margin-top:10px; line-height:1.55; font-size:.92rem; }
    .d-butterflies{ width:min(32vw,40vh); top:10px; left:0; }
    .hero{ --case-w:min(17vw,30vh); --mill-w:min(13vw,26vh); }

    .stamp{ width:min(24vw,100px); height:min(24vw,100px); }
    .k-tag,.k-ticket,.k-letter,.k-glasses{ width:min(19vw,22vh); }
    .k-tag{ top:calc(var(--screen) * .24); } .k-ticket{ top:calc(var(--screen) * .26); }
    .k-letter{ top:calc(var(--screen) * .5); } .k-glasses{ top:calc(var(--screen) * .52); }
  }
  /* A phone on its side has well under 500px of height: no room on the opening
     screen for the four keepsakes as well, so they step aside, as on an upright phone. */
  @media (max-height:500px){ .keepsake{ display:none; } }
  /* Small tablets (521–700px): the globe is still big here and fills the width
     the keepsakes would stand in, so they step aside as on a phone, and the top
     is reserved for the butterflies so they stay off "Miss". */
  @media (min-width:521px) and (max-width:700px){
    .keepsake{ display:none; }
    .hero{ padding-top:max(150px,26vw); }
  }

  /* the longer tagline pushes the scroll cue past the fold on short screens */
  @media (max-height:880px){
    .stage{ --globe-size:min(46vw,204px); margin:8px 0 4px; }
    .route-caption{ margin-top:11px; }   /* the same clearance, scaled to a short screen */
    .hero h1{ font-size:clamp(2.2rem,5.4vw,3.1rem); }
    .hero .tagline{ margin-top:14px; line-height:1.65; }
    .hero .tagline .lead{ font-size:1.18rem; margin-bottom:.35em; }
    .scroll-cue{ margin-top:12px; }
  }

  /* Small, short phones — an iPhone SE or a 360x640 Android, where the hero has
     about 490px of usable height between the two art bands and wants 600. Left
     alone it overflows and the scroll cue, the one thing telling a guest there is
     more below, is the part that falls off the bottom. So everything gives a
     little: the bands close up around smaller art, the globe takes one more step
     down, and the paragraph tightens. Nothing is removed. */
  @media (max-width:520px) and (max-height:760px){
    .hero{ padding:max(76px,24vw) 16px 36px; }
    .d-butterflies{ top:8px; }
    .globe-row .d-case{ width:min(22vw,84px); }
    .globe-row .d-mill{ width:min(15vw,58px); }
    .hero .label{ margin-bottom:10px; }
    /* the headline's own rule is a class deeper than the one in the block above,
       so the short-screen shrink has to name it too or it never applies */
    .hero h1.miss-to-mrs{ font-size:clamp(2.3rem,8.6vw,2.9rem); }
    .hero .bride-name{ margin-top:8px; font-size:1.55rem; }
    .stage{ max-width:min(38vw,138px); max-height:min(38vw,138px); }
    .hero .tagline{ font-size:.88rem; line-height:1.5; }
    .hero .tagline .lead{ font-size:1.08rem; }
    .scroll-cue{ margin-top:10px; }
  }

  /* touch screens: thumb-sized targets, and no hover state to get stuck on */
  @media (hover:none){
    .card-body a.link{ padding:12px 0; }
    .scroll-cue{ padding:8px 12px; }
    /* the button is the text-size toggle — a single letter, and so about 9px wide
       to aim at unless it is given the same treatment as the links beside it */
    footer a, footer button{ display:inline-block; padding:10px 6px; }
    .card.in-view:hover{
      transform:translateY(0) rotate(var(--tilt));
      box-shadow:0 4px 18px rgba(160,90,80,.10);
    }
  }

  @media (prefers-reduced-motion:reduce){
    .sails,.scroll-cue .arrow,.butterfly,.wing,.klomp,.clog-shadow,.keepsake,.d-clogs,.plane-dart,.flight-trail-reveal{ animation:none; }
    .hero .heart-loop .plane-dart{ opacity:0; }   /* parked under "Miss" it would just look stray */
    .hero .label,.hero .miss-lead,.hero h1,.hero .tagline,.hero .stage,.scroll-cue,.card{ animation:none !important; opacity:1 !important; transform:none !important; }
  }

  /* ---- gallery ---- */
  .gallery-head{ position:relative; text-align:center; padding:72px 20px 0; }
  /* the corner stamp grows past the 72px desktop clearance once its legible mobile
     floor (see .stamp under max-width:860px/520px above) kicks in, so the kicker
     line under it needs more air there or the two overlap */
  @media (max-width:860px){ .gallery-head{ padding-top:112px; } }
  @media (max-width:520px){ .gallery-head{ padding-top:106px; padding-left:18px; padding-right:18px; } }
  /* the same high-contrast garamond as the names in the hero, so the two big
     headings on the page are visibly the same voice */
  .gallery-head h2{
    font-family:var(--display); font-weight:300; color:var(--coral-deep);
    font-size:clamp(2.2rem,6.2vw,3.3rem); line-height:1.06; margin:10px 0 8px;
    letter-spacing:.09em; text-indent:.09em;   /* the tracking would otherwise skew it left */
  }
  .gallery-head p{ margin:0; color:var(--muted); font-style:italic; font-size:.95rem; }
  .divider{
    width:min(340px,80%); margin:24px auto 0; height:1px;
    background:repeating-linear-gradient(90deg, var(--kraft) 0 5px, transparent 5px 12px);
  }

  /* The cards normally sit under their own heading, so 40px is the gap after the
     heading's divider. Put them first and the hero is what is above them instead —
     the adjacent selector catches exactly that case, since the hero is always the
     first block on the page whatever order the others are in. */
  .hero + #registrySection > .grid-gifts{ padding-top:72px; }
  .grid-gifts{
    max-width:1200px; margin:0 auto; padding:40px 20px 70px;
    display:grid; grid-template-columns:repeat(auto-fill,minmax(260px,1fr)); gap:28px;
  }
  /* One big card per row reads as a list, not a registry, on a phone. Below 860px
     the cards shrink to a fixed column count instead of auto-fill's single wide
     column, so the grid still reads as a grid — three tiles down to tablet width,
     two once a third would squeeze each card under ~150px. */
  @media (max-width:860px){
    .grid-gifts{ grid-template-columns:repeat(var(--mobile-columns,3),1fr); gap:16px; padding-left:16px; padding-right:16px; }
    /* Grid items stretch to their row by default, so a card with two lines in it
       was being pulled to the height of a card with five and the difference came
       out as a blank panel of cream with a dashed border round it. Let each card
       end where its words end; the ragged foot is what makes a wall of them read
       as pinned-up notes rather than as a broken table. */
    .grid-gifts{ align-items:start; }
  }
  /* below this width a third column gets too narrow to read regardless of
     --mobile-columns, so two is the floor rather than something settings.js picks */
  @media (max-width:480px){
    .grid-gifts{ grid-template-columns:repeat(2,1fr); gap:12px; padding:32px 14px 48px; }
  }
  /* a section title spans the whole grid row, so the cards under it stay a grid */
  .section-head{
    grid-column:1/-1; margin:38px 0 2px; text-align:center; font-weight:400;
    display:flex; align-items:center; justify-content:center; gap:18px;
  }
  .section-head:first-child{ margin-top:4px; }
  .section-head::before, .section-head::after{
    content:''; flex:1; max-width:140px; height:1px;
    background:repeating-linear-gradient(90deg,var(--kraft) 0 5px, transparent 5px 12px);
  }
  /* the name lives in one flex item, so a wrap happens *inside* it and the dashed
     rules stay put at either end instead of being flung onto the next line.
     (It used to carry the section's subtotal under the name too; subtotals were
     removed at the couple's request.) */
  .section-head-inner{
    flex:0 1 auto; display:flex; flex-direction:column; align-items:center; gap:9px;
    font-family:var(--display); font-weight:500; text-transform:uppercase;
    letter-spacing:.18em; text-indent:.18em; font-size:1.42rem; color:var(--ink);
    line-height:1.15;
  }
  /* narrow screens cannot hold the dashed rules beside a wrapping name */
  @media (max-width:620px){
    .section-head::before, .section-head::after{ display:none; }
    .section-head-inner{ font-size:1.24rem; }
  }

  .card{
    --tilt:0deg;
    background:var(--cream); border-radius:4px; padding:10px; position:relative;
    display:flex; flex-direction:column;
    box-shadow:0 4px 18px rgba(160,90,80,.10);
    opacity:0; transform:translateY(20px) rotate(var(--tilt));
    transition:opacity .6s ease, transform .6s ease, box-shadow .25s ease;
  }
  .card:nth-child(3n+1){ --tilt:-.6deg; }
  .card:nth-child(3n+2){ --tilt:.55deg; }
  .card:nth-child(3n){ --tilt:-.25deg; }
  .card.in-view{ opacity:1; transform:translateY(0) rotate(var(--tilt)); }
  .card::after{ content:''; position:absolute; inset:6px; border:1px dashed var(--line); border-radius:3px; pointer-events:none; }
  .card.in-view:hover{ transform:translateY(-6px) rotate(0deg); box-shadow:0 14px 30px rgba(160,90,80,.16); }
  /* The window stays 4:3 whatever the picture is, so the grid keeps its rhythm.
     Whether the picture is cropped to cover it or shrunk to sit inside it is
     imageFit in settings.js. --shot-bg is what shows behind it: under 'fit' the
     picture no longer reaches the sides, so the band either side is the card's
     own paper and the picture reads as sitting on the card. Under 'fill' the
     picture covers the window, and the colour is only the floor that shows while
     it loads, and if it never does. */
  .card .shot{
    position:relative; width:100%; aspect-ratio:4/3; overflow:hidden;
    background:var(--shot-bg,var(--line)); border-radius:2px;
  }
  .card .shot img{
    position:absolute; inset:0; width:100%; height:100%; display:block;
    object-fit:var(--image-fit,cover);
  }
  .card .noimg{
    width:100%; aspect-ratio:4/3; background:var(--line); border-radius:2px;
    display:flex; align-items:center; justify-content:center; color:var(--sage); font-size:.9rem;
  }
  .card-body{ padding:14px 10px 8px; display:flex; flex-direction:column; gap:8px; flex:1; }
  .card-body h3{ margin:0; font-size:1.05rem; font-weight:600; }
  /* every figure is shown in both currencies at once: the euro price is the fact,
     the rand underneath is what makes it mean something in Pretoria */
  .price{ display:flex; flex-direction:column; gap:1px; }
  .price-eur{
    font-family:var(--mono); font-weight:700; font-size:1.3rem; color:var(--coral-deep);
    font-variant-numeric:tabular-nums; letter-spacing:-.01em;
  }
  .price-zar{
    font-family:var(--mono); font-weight:400; font-size:.82rem; color:var(--muted);
    font-variant-numeric:tabular-nums;
  }
  .card-body a.link{
    color:var(--sage); font-size:.78rem; text-decoration:none; align-self:flex-start; font-family:var(--mono);
    text-transform:uppercase; letter-spacing:.1em; border-bottom:1px dotted var(--sage);
  }
  .note{ font-size:.84rem; color:var(--muted); font-style:italic; margin-top:auto; padding-top:4px; }
  .empty{ text-align:center; color:var(--muted); padding:60px 20px; font-style:italic; }
  /* "…and so much more": the list's last word, so it reads as an impression
     rather than the complete list. Pulled up into the grid's bottom padding;
     the dotted ornament echoes the dashed rules elsewhere on the page. */
  .registry-more{
    max-width:34em; margin:-28px auto 0; padding:0 22px 64px; text-align:center;
    font-family:var(--display); font-style:italic; font-size:1.25rem; line-height:1.5;
    color:var(--muted);
  }
  .registry-more::before{
    content:'· · ·'; display:block; margin-bottom:10px;
    font-style:normal; letter-spacing:.3em; color:var(--kraft);
  }
  @media (max-width:480px){ .registry-more{ margin-top:-16px; padding-bottom:48px; font-size:1.1rem; } }

  /* smaller tiles need smaller everything inside them, or the card outgrows the
     grid cell it was just given */
  @media (max-width:860px){
    .card{ padding:7px; }
    .card-body{ padding:9px 6px 6px; gap:5px; }
    .card-body h3{ font-size:.86rem; line-height:1.25; }
    .price-eur{ font-size:.98rem; }
    .price-zar{ font-size:.68rem; }
    .card-body a.link{ font-size:.66rem; }
    .note{ font-size:.72rem; }
  }
  @media (max-width:620px){
    /* On a phone this placeholder was the single largest thing on the page: a
       4:3 slab of grey saying "no picture" on top of every costed item that has
       no photograph yet, half the height of each card, repeated down the screen.
       A cost with no picture is not a broken card — it is a line of type. The
       name and the two figures carry it, and the cards that DO have a photograph
       now stand out instead of being lost in a field of grey. */
    .card .noimg{ display:none; }
    /* with no window above it, the body is the whole card and needs its own air */
    .card:not(:has(.shot)) .card-body{ padding:12px 8px 10px; }

    /* "See an example" is long in both languages and was breaking across two
       lines inside a 160px card, dragging its underline the full width with it.
       Tighter tracking fits it on one line; inline keeps the rule under the
       words rather than under the card. */
    .card-body a.link{
      position:relative; align-self:flex-start; width:fit-content;
      font-size:.62rem; letter-spacing:.05em; line-height:1.5;
      padding:0 0 3px;
    }
    /* the visible rule stays tucked under the words; the tap area is stretched
       back out to a thumb's worth without drawing anything, same trick the
       account card's copy buttons use */
    .card-body a.link::after{ content:''; position:absolute; inset:-14px -8px; }
  }
  @media (max-width:480px){
    .card-body h3{ font-size:.82rem; }
    .price-eur{ font-size:.92rem; }
  }

  /* ---- "What it costs", with the landing-fund card beside it ----
     The same arrangement as wedding.html, in this page's own paper-and-coral
     voice. Three columns on a wide screen: an empty one, the heading (so it stays
     centred over the list) and the card. Below 1000px the card drops under the
     heading. The card is a few words and one button; the account numbers live in
     a pop-up (#bankDialog) with both accounts, South African and Dutch. */
  .costs-layout{
    max-width:1200px; margin:0 auto; padding:0 20px;
    display:grid; grid-template-columns:1fr minmax(0,600px) 1fr; column-gap:32px; align-items:end;
  }
  .costs-layout .gallery-head{ grid-column:2; grid-row:1; padding-left:0; padding-right:0; }
  /* the clogs: under the heading and the card on a wide screen, walking the
     whole width; between the two once the card drops under the heading */
  .costs-layout .clog-lane{ grid-column:1 / -1; grid-row:2; margin-top:22px; }
  @media (max-width:1000px){
    .costs-layout{ grid-template-columns:1fr; row-gap:30px; }
    .costs-layout .gallery-head{ grid-column:1; grid-row:auto; }
    .costs-layout .clog-lane{ grid-column:1; grid-row:auto; margin-top:0; }
  }

  /* No drop shadow, unlike a cost card: it should sit back on the page rather
     than lift off it. The dashed inner rule carries the ticket look. */
  .bank{
    grid-column:3; grid-row:1; justify-self:start; width:100%; max-width:300px; margin-bottom:6px;
    background:var(--cream); border:1px solid var(--line); border-radius:4px;
    padding:26px 22px 24px; text-align:center; position:relative;
  }
  .bank::after{ content:''; position:absolute; inset:6px; border:1px dashed var(--line); border-radius:3px; pointer-events:none; }
  .bank > *{ position:relative; z-index:1; }   /* above the dashed rule */
  @media (max-width:1000px){ .bank{ grid-column:1; grid-row:auto; justify-self:center; max-width:420px; margin-bottom:0; } }
  .bank[hidden]{ display:none; }
  .bank h3{
    font-family:var(--display); font-weight:300; color:var(--coral-deep);
    font-size:2rem; line-height:1.1; margin:8px 0 8px;
    letter-spacing:.05em; text-indent:.05em;
  }
  .bank-note{ margin:0 auto 18px; max-width:42ch; color:var(--muted); font-style:italic; font-size:.88rem; line-height:1.6; }
  .bank-note[hidden]{ display:none; }
  /* the one thing on the page meant to be pressed, so it is the one thing that
     looks like it: a coral pill in the page's mono-uppercase voice */
  .bank-toggle{
    appearance:none; cursor:pointer;
    font-family:var(--mono); text-transform:uppercase; letter-spacing:.14em; font-size:.66rem; font-weight:700;
    color:var(--cream); background:var(--coral-deep); border:1px solid var(--coral-deep);
    border-radius:999px; padding:14px 26px;
    box-shadow:0 6px 16px color-mix(in srgb, var(--coral-deep) 30%, transparent);
    transition:background .2s, border-color .2s, transform .15s, box-shadow .2s;
  }
  .bank-toggle:hover{
    background:var(--coral); border-color:var(--coral); transform:translateY(-1px);
    box-shadow:0 9px 20px color-mix(in srgb, var(--coral-deep) 38%, transparent);
  }
  .bank-toggle:active{ transform:translateY(0); }
  .bank-toggle:focus-visible{ outline:2px solid var(--coral-deep); outline-offset:3px; }

  /* ---- the pop-up with both accounts ----
     A native <dialog>, opened with showModal(). The dialog itself is invisible and
     unpadded — the paper box is .bank-dialog-inner — so a click on the dialog
     element can only be on the dimmed backdrop (js/app.js closes it). */
  .bank-dialog{
    border:none; padding:0; background:transparent; color:inherit;
    width:min(940px, calc(100vw - 32px)); max-width:none; max-height:calc(100svh - 32px);
    overflow:visible;
  }
  .bank-dialog[open]{ animation:popIn .28s ease both; }
  @keyframes popIn{ from{ opacity:0; transform:translateY(14px) scale(.97); } to{ opacity:1; transform:none; } }
  .bank-dialog::backdrop{
    background:color-mix(in srgb, var(--ink) 55%, transparent);
    -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px);
  }
  .bank-dialog-inner{
    position:relative; background:var(--blush); border-radius:4px;
    padding:38px 30px 30px; max-height:calc(100svh - 32px); overflow:auto;
    box-shadow:0 24px 70px rgba(79,84,74,.35); text-align:center;
  }
  /* the airmail stripe along the top, as on the page itself */
  .bank-dialog-inner::before{
    content:''; position:absolute; left:0; right:0; top:0; height:7px; border-radius:4px 4px 0 0;
    background:repeating-linear-gradient(135deg,
      var(--coral-deep) 0 12px, var(--cream) 12px 17px,
      var(--airmail) 17px 29px, var(--cream) 29px 34px);
  }
  .bank-dialog h3{
    font-family:var(--display); font-weight:300; color:var(--coral-deep);
    font-size:clamp(2rem,5vw,2.6rem); line-height:1.1; margin:4px 0 24px;
    letter-spacing:.05em; text-indent:.05em;
  }
  .bank-close{
    position:absolute; top:14px; right:12px; appearance:none; cursor:pointer;
    background:none; border:none; font-size:1.9rem; line-height:1; color:var(--muted);
    padding:6px 10px;
  }
  .bank-close:hover{ color:var(--coral-deep); }
  .bank-close:focus-visible{ outline:2px solid var(--coral); outline-offset:2px; }

  .bank-accounts{ display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); gap:24px; }
  .bank-card{
    background:var(--cream); border-radius:4px; padding:30px 26px 24px; position:relative;
    border:1px solid var(--line); text-align:center;
  }
  .bank-card::after{ content:''; position:absolute; inset:6px; border:1px dashed var(--line); border-radius:3px; pointer-events:none; }
  .bank-card > *{ position:relative; z-index:1; }
  .bank-card[hidden]{ display:none; }
  .bank-card h4{
    font-family:var(--display); font-weight:400; color:var(--ink);
    text-transform:uppercase; letter-spacing:.16em; text-indent:.16em;
    font-size:1.25rem; line-height:1.15; margin:0 0 6px;
  }
  .bank-card .bank-note{
    font-family:var(--mono); font-style:normal; text-transform:uppercase; letter-spacing:.14em;
    font-size:.6rem; color:var(--sage); margin-bottom:14px;
  }
  .bank-rows{
    margin:14px 0 0; display:grid; grid-template-columns:auto 1fr; gap:0; text-align:left;
    border-top:1px dashed var(--line);
  }
  .bank-rows dt, .bank-rows dd{ padding:11px 2px; border-bottom:1px dashed var(--line); }
  .bank-rows dt{
    font-family:var(--mono); text-transform:uppercase; letter-spacing:.12em;
    font-size:.62rem; color:var(--sage); align-self:center; padding-right:18px;
  }
  .bank-rows dd{
    margin:0; font-family:var(--mono); font-size:.92rem; color:var(--ink);
    display:flex; align-items:center; gap:12px; justify-content:space-between;
  }
  /* the break lives on the value, not the row: a flex child needs min-width:0
     before it will wrap at all, and the button must never be the thing that wraps */
  .bank-rows dd span{ min-width:0; word-break:break-word; }

  /* Phones: stack the label over the value instead of beside it. Two columns on a
     320-375px screen left the value about 65px wide, so an IBAN came out broken
     over four lines and the account holder's name split mid-word — and this is the
     one block on the page a guest actually retypes into their banking app. Stacked,
     every value gets the full width of the card and stays on one line. */
  @media (max-width:560px){
    .bank-dialog-inner{ padding:36px 14px 18px; }
    .bank-card{ padding:26px 16px 22px; }
    .bank-accounts{ grid-template-columns:1fr; gap:18px; }
    .bank-rows{ grid-template-columns:1fr; }
    .bank-rows dt{ padding:14px 2px 0; border-bottom:none; }
    .bank-rows dd{ padding:4px 2px 13px; gap:14px; }
    .bank-rows dd span{ word-break:normal; overflow-wrap:anywhere; }
  }
  /* an IBAN retyped by hand is how money reaches a stranger, so every line is
     copyable. Sized as a quiet ticket stub, in the card's own mono-uppercase voice. */
  .bank-copy{
    flex:none; appearance:none; background:none; cursor:pointer;
    border:1px dotted var(--sage); border-radius:2px;
    font-family:var(--mono); text-transform:uppercase; letter-spacing:.12em;
    font-size:.6rem; line-height:1; color:var(--sage); padding:7px 9px;
    transition:color .2s, border-color .2s; position:relative;
  }
  /* The stub is deliberately small — it should not shout next to the numbers. This
     stretches the tappable area to roughly a thumb's 44px without drawing anything,
     so a phone hits it every time. */
  .bank-copy::after{ content:''; position:absolute; inset:-9px -6px; }
  .bank-copy:hover{ color:var(--coral-deep); border-color:var(--coral-deep); }
  .bank-copy:focus-visible{ outline:2px solid var(--coral); outline-offset:2px; }
  .bank-copy.is-done{ color:var(--coral-deep); border-color:var(--coral-deep); border-style:solid; }

  footer{ text-align:center; padding:26px 20px 52px; color:var(--muted); font-size:.8rem; border-top:1px dashed var(--line); }
  footer a{ color:var(--muted); }
  footer p{ margin:14px 0 0; }
  /* looks and sits exactly like the "Manage" link beside it, but is a <button>
     since it changes the page rather than navigating to another one */
  .lang-switch{
    appearance:none; background:none; border:none; padding:0; margin:0;
    font:inherit; color:var(--muted); text-decoration:underline; cursor:pointer;
  }
  .lang-switch:hover{ color:var(--coral-deep); }
  .lang-switch:focus-visible{ outline:2px solid var(--coral); outline-offset:2px; }

  /* Fixed pill, top-right, above everything — meant to be noticed, unlike the old
     footer link it replaced. Shows the language a tap switches TO. */
  .lang-switch-top{
    appearance:none; cursor:pointer; position:fixed; top:16px; right:16px; z-index:50;
    font-family:var(--mono); text-transform:uppercase; letter-spacing:.1em; font-size:.72rem;
    color:var(--coral-deep); background:var(--cream); border:1.5px solid var(--coral-deep);
    border-radius:999px; padding:8px 16px; box-shadow:0 2px 10px rgba(0,0,0,.12);
    transition:transform .15s, box-shadow .15s;
  }
  .lang-switch-top:hover{ transform:translateY(-1px); box-shadow:0 4px 14px rgba(0,0,0,.16); }
  .lang-switch-top:focus-visible{ outline:2px solid var(--coral); outline-offset:2px; }
  /* Fixed means it covers whatever scrolls under it, and on a phone that is a card
     title every time. It has to stay visible — a guest reading the wrong language
     is the thing it exists to prevent — so instead it gets as small as it can be
     while still being a comfortable thumb target: the shadow goes, the border
     thins, and an invisible inset stretches the tap area back out to ~44px. */
  @media (max-width:520px){
    .lang-switch-top{
      top:8px; right:8px; padding:6px 11px; font-size:.6rem; letter-spacing:.08em;
      border-width:1px; box-shadow:0 1px 5px rgba(120,80,70,.14);
    }
    .lang-switch-top::after{ content:''; position:absolute; inset:-8px -6px; }
  }
