/* ═══════════════════════════════════════════════════════════
   Ai & Imran — 結婚式
   Modern Japanese: washi ivory, sumi ink, shu-iro vermilion.
   Design principles: ma (negative space), asymmetry, one accent.
   ═══════════════════════════════════════════════════════════ */

:root{
  /* palette */
  --kinari:   #F6F2EA;   /* 生成り — unbleached washi */
  --kinari-2: #EFE9DD;   /* tinted band */
  --sumi:     #1C1B18;   /* 墨 — ink */
  --sumi-70:  #4A4741;
  --sumi-45:  #817C72;
  --sumi-15:  #D6CFC2;
  --shu:      #B2371F;   /* 朱 — vermilion seal */
  --ai:       #2C3A52;   /* 藍 — indigo */
  --kin:      #A98B4F;   /* 金 — antique gold */
  --beni:     #B0341E;   /* 紅 — the invitation ground */
  --beni-lo:  #972B17;
  --kin-2:    #C0993E;   /* the gold of the seigaiha fans */

  /* type */
  --serif: 'Cormorant Garamond', 'Shippori Mincho', 'Hiragino Mincho ProN', 'Yu Mincho', serif;
  --mincho: 'Shippori Mincho', 'Hiragino Mincho ProN', 'Yu Mincho', serif;
  --sans: 'Zen Kaku Gothic New', 'Hiragino Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  /* rhythm */
  --gut: clamp(1.25rem, 5vw, 5rem);
  --sec-y: clamp(5rem, 12vw, 11rem);
  --maxw: 1180px;
  --ease: cubic-bezier(.22,.61,.36,1);

  /* The quatrefoil the invitation's crest is clipped to — the same path as
     #quatrefoil in index.html. Held here so every 寿 seal is cut to one shape;
     the favicon carries its own copy, because a data URI cannot read CSS. */
  --mark: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><path d='M50 8c11 0 20 9 20 20 11 0 20 9 20 22s-9 22-20 22c0 11-9 20-20 20s-20-9-20-20c-11 0-20-9-20-22s9-22 20-22c0-11 9-20 20-20z'/></svg>");
}

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

html{ scroll-behavior:smooth; -webkit-text-size-adjust:100%; }
@media (prefers-reduced-motion:reduce){ html{ scroll-behavior:auto; } }

body{
  margin:0;
  font-family:var(--sans);
  font-weight:300;
  font-size:clamp(.95rem,.4vw + .85rem,1.05rem);
  line-height:1.95;
  color:var(--sumi);
  /* Flat, so anything that fades into --kinari lands on exactly --kinari.
     The old fixed radial tints were anchored to the viewport rather than the
     page, so every section boundary met a slightly different colour. */
  background:var(--kinari);
  overflow-x:hidden;
}

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

::selection{ background:var(--shu); color:var(--kinari); }

/* ── Loading veil ───────────────────────────────────────── */
.veil{
  position:fixed; inset:0; z-index:999;
  display:grid; place-items:center;
  background:var(--kinari);
  transition:opacity .8s var(--ease), visibility .8s;
}
.veil.is-gone{ opacity:0; visibility:hidden; }
.veil__mark{
  font-family:var(--mincho); font-size:clamp(2.5rem,8vw,4rem);
  color:var(--shu); letter-spacing:.1em;
  animation:breathe 2.4s ease-in-out infinite;
}
@keyframes breathe{ 0%,100%{opacity:.25; transform:scale(.97)} 50%{opacity:1; transform:scale(1)} }

/* ── Nav ────────────────────────────── */
.nav{
  position:fixed; top:0; left:0; right:0; z-index:100;
  display:flex; align-items:center; gap:1.5rem;
  padding:1.1rem var(--gut);
  transition:background .5s var(--ease), padding .5s var(--ease), box-shadow .5s var(--ease);
}
.nav.is-stuck{
  background:rgba(246,242,234,.9);
  backdrop-filter:blur(14px) saturate(1.2);
  -webkit-backdrop-filter:blur(14px) saturate(1.2);
  padding-block:.7rem;
  box-shadow:0 1px 0 var(--sumi-15);
}
.nav__brand{
  display:flex; align-items:center; gap:.7rem;
  text-decoration:none; margin-right:auto;
  opacity:0; transform:translateY(-6px);
  transition:opacity .5s var(--ease), transform .5s var(--ease);
  pointer-events:none;
}
.nav.is-stuck .nav__brand{ opacity:1; transform:none; pointer-events:auto; }
.nav__seal{
  display:grid; place-items:center; width:2.05rem; height:2.05rem;
  background:var(--shu); color:var(--kinari);
  font-family:var(--mincho); font-size:.92rem;
  /* cut to the crest's shape rather than a rounded square */
  -webkit-mask-image:var(--mark); mask-image:var(--mark);
  -webkit-mask-size:100% 100%;    mask-size:100% 100%;
  -webkit-mask-repeat:no-repeat;  mask-repeat:no-repeat;
}
.nav__names{ font-family:var(--serif); font-size:1.15rem; letter-spacing:.06em; }

.nav__links{ display:flex; align-items:center; gap:clamp(.9rem,2vw,2rem); margin-left:auto; }
.nav.is-stuck .nav__links{ margin-left:0; }
.nav__links a{
  font-size:.74rem; letter-spacing:.18em; text-transform:uppercase;
  text-decoration:none; color:var(--sumi-70); position:relative; padding-block:.35rem;
  white-space:nowrap; transition:color .3s;
}
.nav__links a::after{
  content:''; position:absolute; left:0; bottom:0; height:1px; width:0;
  background:var(--shu); transition:width .35s var(--ease);
}
.nav__links a:hover{ color:var(--sumi); }
.nav__links a:hover::after{ width:100%; }
.nav__cta{
  border:1px solid var(--sumi-15); padding:.5rem 1.1rem !important; border-radius:2px;
  color:var(--shu) !important;
}
.nav__cta::after{ display:none; }
.nav__cta:hover{ background:var(--shu); color:var(--kinari) !important; border-color:var(--shu); }

.lang{
  display:flex; align-items:center; gap:.4rem; background:none; border:0; cursor:pointer;
  font-family:var(--sans); font-size:.7rem; letter-spacing:.14em; color:var(--sumi-45);
  padding:.4rem .2rem;
}
.lang__on{ color:var(--sumi); border-bottom:1px solid var(--shu); padding-bottom:1px; }
.lang__off{ opacity:.55; }
html[lang="ja"] .lang__on{ color:var(--sumi-45); border-bottom-color:transparent; opacity:.55; }
html[lang="ja"] .lang__off{ color:var(--sumi); opacity:1; border-bottom:1px solid var(--shu); padding-bottom:1px; }

.burger{ display:none; background:none; border:0; cursor:pointer; padding:.4rem; }
.burger i{ display:block; width:22px; height:1px; background:var(--sumi); margin:5px 0; transition:.35s var(--ease); }
.burger[aria-expanded="true"] i:nth-child(1){ transform:translateY(6px) rotate(45deg); }
.burger[aria-expanded="true"] i:nth-child(2){ opacity:0; }
.burger[aria-expanded="true"] i:nth-child(3){ transform:translateY(-6px) rotate(-45deg); }

/* Floating over the invitation, the bar reads light. Desktop only —
   the mobile bar has its own ivory ground. */
@media (min-width:761px){
  .nav:not(.is-stuck) .nav__links a{ color:rgba(251,247,240,.82); }
  .nav:not(.is-stuck) .nav__links a:hover{ color:#fff; }
  .nav:not(.is-stuck) .nav__links a::after{ background:var(--kin-2); }
  .nav:not(.is-stuck) .nav__cta{ color:#FBF7F0 !important; border-color:var(--kin-2); }
  .nav:not(.is-stuck) .nav__cta:hover{ background:var(--kin-2); color:var(--beni-lo) !important; border-color:var(--kin-2); }
  .nav:not(.is-stuck) .lang{ color:rgba(251,247,240,.55); }
  .nav:not(.is-stuck) .lang__on{ color:#fff; border-bottom-color:var(--kin-2); }
  html[lang="ja"] .nav:not(.is-stuck) .lang__on{ color:rgba(251,247,240,.55); border-bottom-color:transparent; }
  html[lang="ja"] .nav:not(.is-stuck) .lang__off{ color:#fff; opacity:1; border-bottom-color:var(--kin-2); }
}

/* ══ THEME A — the invitation ═══════════════════ */
.invite{
  position:relative; min-height:100svh;
  display:grid; place-items:center;
  padding:7rem var(--gut) 6rem; text-align:center;
  /* Clipped on all four sides by default; the block below reopens the bottom
     on wide screens. Kept as the fallback so a browser without overflow:clip
     lands on today's behaviour rather than on hidden+visible, which the spec
     resolves to auto — a scrollbar inside the hero. */
  overflow:hidden;
  background:var(--beni);
  background-image:radial-gradient(ellipse 80% 60% at 50% 38%, rgba(255,255,255,.06), transparent 70%);
  color:#FBF7F0;
}
/* ── The floor is not an edge ─────────────────────────────
   The sides of the panel are the edge of the page, so a disc cut there reads
   as the pattern running off the paper — which is what it should do. The
   bottom is not an edge at all: .bridge below opens on the same --beni and
   holds it before the wash starts, so there is no line for a disc to be cut
   against. Cropping there just stopped the circle mid-arc in the middle of
   flat colour, and it read as a mistake rather than as a bleed.

   So the bottom is reopened and the discs are allowed to finish. overflow-x
   still clips the sides; only the y axis goes visible. The pair must be set
   as clip/visible rather than hidden/visible, because the spec turns
   `visible` into `auto` when the other axis is `hidden`, and `clip` is the
   one value that lets the other axis stay visible.

   z-index:1 is what makes it show: .bridge comes after the hero in the
   document, so without it the gradient paints straight over the part of the
   disc that now hangs below. It stays under the nav (100), the leaves (90)
   and .bridge::after's grain (2), which is right — the grain should run
   across the disc like it runs across everything else in the wash.

   The top bleed is reopened by the same rule, with no effect: it hangs above
   y=0, where the document does not scroll.

   Unscoped, so the phone gets it too — its own arrangement is untouched, but
   its two bottom discs (maru--5 and --8) were being cut against the same
   non-edge and now finish as well. Their bleed is a % of panel height there,
   so it grows if the type outgrows the panel; at the deepest it is about 12%
   against a bridge that is never shorter than 14rem, so the circle still
   lands well inside the wash. */
@supports (overflow:clip){
  .invite{ overflow-x:clip; overflow-y:visible; z-index:1; }
}
/* The card's banded roundels — the printed invitation's pattern struck as
   whole circles rather than cut into the corners, in the card's own two inks:
   金 gold and a warm 銀 gray. Sizes vary deliberately; the band spacing inside
   them does not, so they read as one cloth. */
.maru{
  position:absolute; z-index:0; pointer-events:none;
  height:auto; max-width:none;
  /* Two raster roundels do all seven slots — assets/img/maru-kin.webp (gold)
     and maru-gin.webp (silver-grey), cropped from the supplied artwork and
     masked to a true circle. The artwork's cream/white ground is keyed out, so
     only the bands are drawn and the panel red shows between the curves; the
     roundels sit in the panel rather than on top of it. */
}
/* ── Wide: five discs, all of them bled off an edge ──────────
   The panel used to carry eight whole discs dropped around it. Eight is
   enough to reach every edge, so they read as a ring drawn round the type
   rather than as a pattern behind it, and at those diameters each one still
   read as a circle somebody had placed — the arrangement showed, which is the
   one thing a ground must not do.

   So the phone's rule is applied here too: fewer, larger, and every one of
   them running off a side. Five discs, no two the same diameter, none
   answered by a mirror of itself across the panel. Each is anchored to the
   edge it bleeds off and pushed past it, so what is in frame is an arc of
   cloth continuing off the page — the way the printed card carries the
   pattern into its corners — and not a circle with a slice missing. .invite
   has overflow:hidden, which is what does the cropping.

   maru--2, --6 and --7 are the three that went. They sat down the left edge
   with --4, and four discs stacked down one side is where the ring came from.

   Anchoring is by the near edge on both axes, because a width in % resolves
   against panel width while top/bottom resolve against panel height: held to
   an edge, a disc cannot drift into the middle as the aspect changes. Both
   files are drawn at 700px, and the clamp maxima are what stop a slot asking
   for more than was drawn — past that the bands would soften.

   Where they can go is set by the type, measured on the page at 1440x817 and
   carried as fractions of the panel so it holds at other sizes:
     · the centre column runs x 27%–73%, from the 招待状 label down to the
       button — roughly y 14%–93%;
     · the nav sits top-right, x 55%–89% across y 0–7%, and its ground is
       transparent over the red up here, so a weave behind those links is a
       legibility problem, not just a busy corner;
     · the vertical 結婚式のご案内 runs down the right edge at x 96%–97%,
       y 42%–58%.
   That leaves the two side margins, the band under the button, and the
   top-left corner. The five sit in those, weighted low and to the corners.

   The composition is a diagonal: the largest disc bleeds off the bottom-right
   corner, the second largest off the top-left, and the remaining three are
   spaced around them at three smaller sizes so the eye has somewhere to go
   after the two anchors. All sit at z-index 0 against the type's z-index 2,
   so anything passing near a word passes behind it — but they are kept off
   the type anyway, because white text over a gold weave is hard to read
   whatever the stacking order. */

/* Two things are held constant as the panel changes shape, because a
   percentage offset alone holds neither.

   How deep each one bleeds is a translate, not an offset: a translate
   percentage resolves against the element's own box, so "26% of this disc is
   off the right edge" is true at every viewport, while bottom:-24% resolves
   against panel height and would push the same disc clean out of frame on a
   tall panel and barely crop it on a short one.

   How large each one is is capped in svh as well as %: a width in % resolves
   against panel width, so on a wide, short panel the discs grow while the
   room between them does not. Left on % alone, maru--1 and maru--3 crossed by
   10px at 1920x817 and both ran over the vertical ご案内. The svh figure in
   each min() is that disc's diameter as a fraction of panel height in the
   1440 layout, so the wide case holds every disc at the size it has there and
   only the gaps open up.

   Scoped to min-width, not left to cascade: the phone block below sets no
   transform, and a transform inherited from here would drag its five discs
   off their own edges. */
@media (min-width:761px){
  /* The anchor: off the bottom-right corner. Held clear of the vertical
     ご案内 above it (which ends at 58% of the panel) by starting below 64%. */
  .maru--1{ right:0; bottom:0; transform:translate(26%, 40%);
            width:clamp(260px, min(34%, 60svh), 700px); }
  /* Its counterweight, off the top-left corner — the only corner with no type
     in it. Ends left of the centre column and well short of the nav. */
  .maru--8{ left:0;  top:0;    transform:translate(-35%, -30%);
            width:clamp(200px, min(26%, 45.8svh), 560px); }
  /* Right edge, above the vertical ご案内 and right of the nav's last link. */
  .maru--3{ right:0; top:3%;   transform:translateX(55%);
            width:clamp(150px, min(20%, 35.2svh), 440px); }
  /* Left edge, low — fills the gap between the top-left corner and the floor. */
  .maru--4{ left:0;  bottom:4%; transform:translateX(-59%);
            width:clamp(160px, min(22%, 38.8svh), 480px); }
  /* The small one, off the bottom edge left of the button, to keep the weight
     low and stop the floor reading as a straight line between the corners. */
  .maru--5{ left:17%; bottom:0; transform:translateY(49%);
            width:clamp(110px, min(15%, 26.4svh), 330px); }

  .maru--2, .maru--6, .maru--7{ display:none; }
}

/* ── On a phone: struck larger, and run off the edge ───────
   The whole disc floating clear is a desktop luxury. Narrow, the roundels are
   drawn half again as large and pushed off the panel so only an arc of each is
   in frame — the way the printed card carries them into its corners. A bigger
   disc shows a flatter arc, which is the point: it reads as cloth running off
   the page rather than as a circle somebody cropped.

   Positioning is by edge anchor plus translate, not by percentage offsets. A
   translate percentage resolves against the element's own box, so "38% of this
   disc is off the panel" holds at every width; a percentage offset would
   resolve against panel width on one axis and panel height on the other, and
   the fraction hidden would drift with the phone's aspect.

   Where they can go is set by the type, which is the constraint that matters:
   at 390px the gutter is 20px and .invite__inner runs practically edge to
   edge, so there is no clear margin down the sides at all. Measured against
   the panel's own rhythm, the type occupies roughly 20%–83% of the height —
   which leaves the band under the nav and the band under the button, and
   nothing in between. Discs therefore sit at the corners and along the bottom,
   and the middle is left alone. The scatter comes from size and bleed depth,
   which no two share, rather than from spreading them down the sides where
   they would have to sit behind the names.

   Nothing bleeds off the top edge: the nav has an opaque ivory ground below
   760px, so a disc cropped at the top is sliced flat by the bar and reads as a
   cut circle rather than a bled one. The upper pair are pushed far enough out
   that their arcs clear it.

   The breakpoint is 760px to match the nav's. It was 700px, which left the
   band from 701 to 760 showing the full desktop arrangement underneath an
   already-opaque mobile bar. */
@media (max-width:760px){
  /* Struck 35% smaller than the figures below, about each disc's own centre.

     The scale is what keeps the arrangement while changing the size. Every
     offset below is an edge anchor — a % of panel width across, a % of panel
     height down — and none of them knows the diameter, so trimming the widths
     alone drags each disc toward the edge it is pinned to rather than
     shrinking it in place. maru--1 is the plain case: its left edge is held
     at -48.2% of the panel while it is 65.6vw wide, so at 65% of that width
     it would sit entirely off the left of the screen. A transform scales
     about the centre by default, so the centres stay put, each disc keeps the
     same share of itself over the edge it bleeds off, and the composition is
     the one above at a smaller size.

     Done as a transform rather than as rewritten numbers because the two axes
     carry different units: the sideways offsets are a share of panel width
     and the vertical ones a share of panel height, so there is no pair of
     figures that holds a disc's centre still at more than one aspect ratio.
     scale() has no such problem — it is exact at every phone size. */
  .maru{ width:var(--d); transform:scale(.65); }

  /* Sideways bleed is a translate, because a translate percentage resolves
     against the disc's own box: "62% of this one is off the left edge" holds
     at any width. Vertical bleed is a calc instead, which fixes how tall each
     arc stands in pixels rather than as a share of a disc whose diameter
     moves. That matters because the band the arc has to fit inside does not
     move: once the type is tall enough to outgrow the panel, the section grows
     with it, and the clear strip below the button settles at exactly the 6rem
     bottom padding — 96px, whatever the phone. Each arc below is sized against
     that 96px with room to spare, so the clearance holds on a 320px SE and a
     430px Pro Max alike instead of being a figure that happened to work on one
     of them. --d is repeated inside each calc because a custom property cannot
     be read back into the value that defines it. */
  /* Five, deliberately uneven: no two the same size, none answered by a
     mirror of itself on the other side, and the weight sitting low and left.
     An even, balanced scatter on a screen this narrow starts to look like a
     border. Sizes in vw so they grow with the phone. */
  .maru--1{ left:-48.2%;    right:auto; bottom:2.6%;    top:auto;    width:65.6vw; }
  .maru--3{ right:-12.9%;   left:auto;  top:12.8%;      bottom:auto; width:45.1vw; }
  /* Pulled in from -24%. The scale above shrinks each disc about its own
     centre, and this one's centre sits off the left edge — so shrinking it
     moved the visible edge further out rather than leaving it where it was,
     and what reached the corner was a 7px sliver of a 100px disc. At -12% it
     shows about two thirds of itself, which is the arc the corner wants. */
  .maru--4{ left:-12%;      right:auto; top:7%;         bottom:auto; width:30.8vw; }
  .maru--5{ right:-19.1%;   left:auto;  bottom:-5%;     top:auto;    width:53.3vw; }
  .maru--8{ left:12.2%;     right:auto; bottom:-11.8%;  top:auto;    width:36.9vw; }

  .maru--2, .maru--6, .maru--7{ display:none; }
}

.invite__inner{ position:relative; z-index:2; max-width:44rem; }
.invite__label{
  margin:0; font-family:var(--mincho); font-size:clamp(1.1rem,3.4vw,1.5rem);
  letter-spacing:.42em; text-indent:.42em; color:#FBF7F0;
}
.invite__rule{ display:grid; gap:3px; width:min(58%,16rem); margin:1.1rem auto clamp(2.4rem,6vw,3.6rem); }
.invite__rule span{ height:1px; background:var(--kin-2); }
.invite__rule span:last-child{ opacity:.55; }

.invite__names{
  margin:0; font-family:var(--serif); font-weight:300;
  font-size:clamp(2.3rem,7.6vw,4.4rem); line-height:1.02; color:#fff;
  display:flex; align-items:center; justify-content:center; flex-wrap:wrap;
  gap:clamp(.6rem,2.5vw,1.5rem);
}
.invite__names i{ font-style:italic; color:var(--kin-2); font-size:.5em; }
/* The Japanese names lead and the English sits under them — the invitation is
   a Japanese one. Letter-spacing eases off as the size grows, or the tracking
   that suits a caption pushes the full string into a wrap. */
.invite__jp{
  margin:1.1rem 0 0; font-family:var(--mincho); font-weight:500;
  font-size:clamp(1.1rem,3.4vw,1.95rem); letter-spacing:.12em;
  color:#FBF7F0;
}
.invite__mark{ width:clamp(38px,7vw,52px); height:auto; margin:clamp(1.6rem,4vw,2.2rem) auto 0; display:block; }
.invite__date{
  margin:clamp(1.6rem,4vw,2.2rem) 0 0; font-family:var(--mincho);
  font-size:clamp(.95rem,2.8vw,1.18rem); letter-spacing:.16em; color:#fff;
}
.invite__where{
  margin:.9rem auto 0; max-width:30rem;
  font-size:.86rem; line-height:1.9; color:rgba(251,247,240,.72);
}
.invite__vertical{
  position:absolute; right:clamp(.9rem,3vw,2.4rem); top:50%; translate:0 -50%;
  writing-mode:vertical-rl; font-family:var(--mincho);
  font-size:.74rem; letter-spacing:.5em; color:rgba(251,247,240,.5); z-index:2;
}
.invite__scroll{
  position:absolute; bottom:2rem; left:50%; translate:-50% 0; z-index:2;
  width:1px; height:3.2rem; background:rgba(251,247,240,.28); overflow:hidden;
}
.invite__scroll span{ position:absolute; inset:0; background:var(--kin-2); animation:drip 2.6s var(--ease) infinite; }
@keyframes drip{ 0%{transform:translateY(-100%)} 55%,100%{transform:translateY(100%)} }

.btn--gold{
  margin-top:clamp(2.2rem,5vw,3rem);
  background:transparent; color:#FBF7F0; border:1px solid var(--kin-2);
}
.btn--gold:hover{ background:var(--kin-2); color:var(--beni-lo); transform:translateY(-2px); }

/* ══ THE BRIDGE — A dissolves into B ══════════════ */
.bridge{
  position:relative; overflow:hidden;
  margin-top:-1px;   /* both sides of the join are --beni; kills any subpixel hairline */
  /* Only as tall as the dissolve needs. It holds no content at all now — the
     countdown used to sit inside this gradient, which stranded the numbers in
     mid-wash; it has its own section below, on the flat ivory. */
  min-height:clamp(14rem,30svh,20rem);
  /* One continuous wash from the invitation red to the washi ivory the rest
     of the page lives in. Many closely-spaced stops, eased rather than
     linear, so there is no band the eye can catch; it settles on flat
     --kinari well before the section ends, so the boundary is invisible. */
  background:linear-gradient(180deg,
    var(--beni)    0%,
    var(--beni)   15%,
    #B0351F   17.1%,
    #B13822   19.1%,
    #B33D26   21.1%,
    #B5422B   23.2%,
    #B7482F   25.2%,
    #B94D34   27.3%,
    #BB5238   29.4%,
    #BD573C   31.4%,
    #BF5B3F   33.5%,
    #C15E42   35.5%,
    #C26044   37.5%,
    #C36245   39.6%,
    #C36245   41.7%,
    #C5684B   43.7%,
    #CA7356   45.8%,
    #CF7F62   47.8%,
    #D4896B   49.9%,
    #D78F71   51.9%,
    #D89173   53.9%,
    #DA977A   56.0%,
    #DDA086   58.1%,
    #E1AC93   60.1%,
    #E5B59E   62.1%,
    #E7BCA5   64.2%,
    #E8BEA7   66.2%,
    #E9C2AC   68.3%,
    #EBC8B5   70.3%,
    #EDD0BF   72.4%,
    #EFD8C8   74.4%,
    #F1DDCE   76.5%,
    #F1DED0   78.6%,
    #F1DFD1   80.6%,
    #F2E1D4   82.6%,
    #F2E4D8   84.7%,
    #F3E7DC   86.8%,
    #F4EAE0   88.8%,
    #F5EDE4   90.9%,
    #F5F0E7   92.9%,
    #F6F1E9   95.0%,
    #F6F2EA   97.0%,
    var(--kinari) 100%);
}
/* A whisper of noise across the wash. Smooth gradients over this many
   pixels band on 8-bit displays; dithering them removes the rings. */
.bridge::after{
  content:''; position:absolute; inset:0; z-index:2; pointer-events:none;
  opacity:.055; mix-blend-mode:overlay;
  /* Only where the colour is actually moving. Run it across the flat red at
     the top and the overlay blend shifts that red a shade off the identical
     red in the panel above — which reads as a hard line between two shades. */
  -webkit-mask-image:linear-gradient(to bottom, transparent 0%, transparent 18%, #000 34%);
          mask-image:linear-gradient(to bottom, transparent 0%, transparent 18%, #000 34%);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}
/* The countdown's own section. The four .bridge .count__* overrides that used
   to live here existed only to make the numbers legible on dark red; on the
   ivory the base ink colours below are already right, so they are gone. */
.count{ text-align:center; width:100%; }

/* ── 紅葉 Falling maple leaves — they begin at the bridge ── */
.momiji{
  position:fixed; inset:0; z-index:90;
  width:100%; height:100%;
  pointer-events:none;
  opacity:0; transition:opacity 1.6s var(--ease);
}
.momiji.is-on{ opacity:.85; }
@media (prefers-reduced-motion:reduce){ .momiji{ display:none; } }

/* ── Countdown ──────────────────────────────────────────── */
.count__label{
  margin:0 0 1.8rem; font-size:.7rem; letter-spacing:.34em; text-transform:uppercase;
  color:var(--sumi-70);   /* --sumi-45 is 3.72:1 on --kinari — under AA at this size */
}
.count__grid{
  list-style:none; margin:0 auto; padding:0; max-width:36rem;
  display:grid; grid-template-columns:repeat(4,1fr); gap:clamp(.5rem,3vw,1.5rem);
}
.count__grid li{ position:relative; }
.count__grid li + li::before{
  content:''; position:absolute; left:calc(-1 * clamp(.25rem,1.5vw,.75rem)); top:15%; bottom:25%;
  width:1px; background:var(--sumi-15);
}
.count__grid b{
  display:block; font-family:var(--serif); font-weight:300;
  font-size:clamp(2rem,7vw,3.4rem); line-height:1; letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;
}
.count__grid span{
  display:block; margin-top:.6rem;
  font-size:.62rem; letter-spacing:.22em; text-transform:uppercase; color:var(--sumi-70);
}

/* ── Section scaffolding ────────────────────────────────── */
.sec{ padding:var(--sec-y) var(--gut); }
/* The countdown needs less room around it than a reading section does, and it
   must sit close to what follows — the whole point of moving it out of the
   wash was to close that gap. Declared after .sec so it actually wins. */
.sec--count{ padding-block:clamp(3rem,6vw,4.5rem); }
.sec--tint{ background:rgba(239,233,221,.55); }
.sec--narrow > *{ max-width:44rem; margin-inline:auto; }

.sec__head{ max-width:var(--maxw); margin:0 auto clamp(3rem,7vw,5rem); text-align:center; position:relative; }
.sec__no{
  display:block; font-family:var(--serif); font-size:.82rem;
  letter-spacing:.3em; color:var(--shu); margin-bottom:.9rem;
}
.sec__ja{
  display:block; font-family:var(--mincho); font-size:.75rem;
  letter-spacing:.7em; text-indent:.7em; color:var(--sumi-45); margin-bottom:.8rem;
}
.sec__title{
  margin:0; font-family:var(--serif); font-weight:300;
  font-size:clamp(2.1rem,6.5vw,3.6rem); line-height:1.1; letter-spacing:-.005em;
}
.sec__sub{
  margin:1.2rem auto 0; max-width:34rem;
  font-size:.92rem; color:var(--sumi-70);
}

/* divider */
.divider{
  display:flex; align-items:center; gap:1.4rem;
  max-width:26rem; margin:0 auto; padding:0 var(--gut);
  color:var(--sumi-15);
}
.divider span{ flex:1; height:1px; background:currentColor; }
.divider svg{ width:40px; height:24px; color:var(--kin-2); opacity:.85; flex:none; }

/* ── Photo placeholders ─────────────────────────────────── */
.ph{
  position:relative; overflow:hidden; border-radius:2px;
  display:grid; place-items:center; margin:0;
  background-color:var(--kinari-2);
  box-shadow:0 1px 0 rgba(28,27,24,.06), 0 18px 40px -28px rgba(28,27,24,.45);
}
.ph::after{ /* asanoha-ish weave */
  content:''; position:absolute; inset:0; opacity:.5;
  background-image:
    linear-gradient(45deg, rgba(28,27,24,.05) 25%, transparent 25%, transparent 75%, rgba(28,27,24,.05) 75%),
    linear-gradient(-45deg, rgba(28,27,24,.05) 25%, transparent 25%, transparent 75%, rgba(28,27,24,.05) 75%);
  background-size:26px 26px;
}
.ph > span{
  position:relative; z-index:1; font-family:var(--mincho);
  font-size:.72rem; letter-spacing:.42em; text-indent:.42em; color:var(--sumi-45);
}
.ph--tall{ aspect-ratio:3/4; }
.ph--wide{ aspect-ratio:16/10; }
.ph--1{ background-image:linear-gradient(150deg,#E7DFD1,#D9CFBD); }
.ph--2{ background-image:linear-gradient(150deg,#DFE2E0,#C9D0CB); }
.ph--3{ background-image:linear-gradient(150deg,#EADFD8,#DCC9BF); }
.ph--4{ background-image:linear-gradient(150deg,#DCDDE4,#C4C8D4); }
.ph--5{ background-image:linear-gradient(150deg,#E9E3D2,#D5CCB4); }
.ph--6{ background-image:linear-gradient(150deg,#E4DCD9,#CFC2BD); }

/* ── Timeline ───────────────────────────────────────────── */
/* Three columns — time, dot, title — on one centreline. The dot used to be a
   pseudo-element pinned by a top offset worked out from the row's padding,
   which is why it never quite lined up with the title; it is a real grid cell
   now. The rule behind it is positioned from the same three custom properties
   as the columns, so the line and the dots cannot drift apart. */
.timeline{
  max-width:44rem; margin:0 auto; position:relative;
  --tl-time:clamp(4.4rem,13vw,6rem);
  --tl-dot:11px;
  --tl-gap:clamp(.9rem,2.6vw,1.5rem);
}
.timeline::before{
  content:''; position:absolute; top:.7rem; bottom:.7rem;
  left:calc(var(--tl-time) + var(--tl-gap) + var(--tl-dot) / 2 - .5px);
  width:1px; background:var(--sumi-15);
}
.tl{
  display:grid; align-items:center;
  grid-template-columns:var(--tl-time) var(--tl-dot) 1fr;
  gap:var(--tl-gap);
  padding-block:clamp(.85rem,2.2vw,1.25rem);
}
.tl__time{
  margin:0; text-align:right;
  font-family:var(--serif); font-weight:400;
  font-size:clamp(1.05rem,2.9vw,1.3rem); letter-spacing:.02em;
  font-variant-numeric:tabular-nums; white-space:nowrap;
  color:var(--sumi-70);
}
/* Opaque, so it masks the rule running behind it. */
.tl__dot{
  width:var(--tl-dot); height:var(--tl-dot); border-radius:50%;
  background:#fff; border:1px solid var(--sumi-15);
  position:relative; z-index:1;
}
.tl__title{
  margin:0; font-family:var(--serif); font-weight:400;
  font-size:clamp(1.15rem,3.2vw,1.5rem); letter-spacing:.01em;
}

/* ── Venue ──────────────────────────────────────────────── */
.venue__name{
  margin:0; font-family:var(--serif); font-weight:400;
  font-size:clamp(1.7rem,4.5vw,2.3rem); line-height:1.2;
}
.venue__jp{
  margin:.5rem 0 1.6rem; font-family:var(--mincho);
  font-size:.85rem; letter-spacing:.3em; color:var(--sumi-45);
}
.venue address{
  font-style:normal; color:var(--sumi-70); line-height:1.8;
  padding-bottom:1.6rem; border-bottom:1px solid var(--sumi-15);
}
.facts{ margin:1.6rem 0 2rem; display:grid; gap:1.15rem; }
/* Stacked, which is the single biggest thing that uncramps these cards: the
   label column was 45% of the card's usable width and it was being spent on
   one short word. Above the value it costs a line and gives the text the
   whole card. This is what the narrow breakpoint already did; it is now what
   they all do. */
.facts > div{ display:grid; grid-template-columns:1fr; gap:.2rem; }
.facts dt{
  font-size:.64rem; letter-spacing:.2em; text-transform:uppercase; color:var(--sumi-45);
}
.facts dd{ margin:0; font-size:.92rem; color:var(--sumi); }
.venue__btns{ display:flex; gap:.8rem; flex-wrap:wrap; }

/* ── Venue cards ────────────────────────── */
/* Wider than the rest of the page on purpose. Everything else here is prose
   at --maxw, which is a comfortable measure for a paragraph; this is three
   cards side by side, and at 1180px that left each one 352px with 42px of
   padding — 268px of usable width, of which the labels took 120. Values had
   131px and wrapped to four and five lines. A little more room and a smaller
   gap buys back most of it. */
.venues{
  max-width:min(100%, 78rem); margin:0 auto;
  display:grid; grid-template-columns:repeat(auto-fit,minmax(min(100%,21rem),1fr));
  gap:clamp(1.2rem,2.5vw,2rem);
}
.place{
  position:relative; padding:clamp(1.8rem,4vw,2.6rem);
  border:1px solid var(--sumi-15); border-radius:2px;
  background:rgba(255,255,255,.35);
}
.place__tag{
  display:inline-block; margin-bottom:1.4rem; padding:.4rem .9rem;
  font-size:.6rem; letter-spacing:.2em; text-transform:uppercase;
  color:var(--kinari); background:var(--shu); border-radius:2px;
}
.place__tag--alt{ background:var(--ai); }
/* The third card. Gold rather than vermilion or indigo, so the three tags
   read as three different invitations at a glance. */
.place__tag--kin{ background:var(--kin); }
/* No address line on this one, and the rule above the facts comes from the
   address block. Without it the facts would sit tight under the name, so the
   space that rule occupied is given back here. */
.place--soon .facts{ margin-top:2.6rem; }
.place__soon{
  margin:1.4rem 0 0; font-size:.82rem; line-height:1.8;
  color:var(--sumi-45); font-style:italic;
}
.place__name{
  margin:0; font-family:var(--serif); font-weight:400;
  font-size:clamp(1.6rem,4.2vw,2.1rem); line-height:1.2;
}
.place__jp{
  margin:.45rem 0 1.3rem; font-family:var(--mincho);
  font-size:.82rem; letter-spacing:.3em; color:var(--sumi-45);
}
.place address{
  font-style:normal; color:var(--sumi-70); line-height:1.8;
  padding-bottom:1.4rem; border-bottom:1px solid var(--sumi-15);
}
.place .facts{ margin:1.4rem 0 1.8rem; }
.place .btn{ padding:.85rem 1.7rem; }

/* form hints */
.f__hint{
  margin:.6rem 0 0; font-size:.76rem; line-height:1.65;
  color:var(--sumi-45); text-transform:none; letter-spacing:.01em;
}
/* "We already have a reply for this name." The plain hint is grey because it
   is housekeeping; this one is news, so it takes the invitation red and a
   rule down the side to separate it from the label above. It is a warning,
   not an error — nothing is blocked and the reply still sends — so it stops
   short of the .is-bad treatment on the field itself. */
.f__hint.is-dupe{
  color:var(--beni); padding-left:.7rem;
  border-left:2px solid var(--beni);
}

/* ── Cards ──────────────────────────────────────────────── */
/* Five cards: three across, then two — both rows full. Auto-fit gave four to
   a row and stranded the fifth alone beside three empty cells, which read as a
   hole in the section. A six-unit grid makes 3-then-2 exact: the first three
   take two units each, the last two take three. The count is fixed content,
   so it is stated rather than inferred. */
.cards{
  max-width:var(--maxw); margin:0 auto;
  display:grid; grid-template-columns:repeat(6,1fr);
  gap:1px; background:var(--sumi-15); border:1px solid var(--sumi-15);
}
.card{ grid-column:span 2; }
.card:nth-child(n+4){ grid-column:span 3; }
.card{
  position:relative; background:var(--kinari);
  padding:clamp(1.8rem,4vw,2.6rem);
  transition:background .4s var(--ease);
}
.card:hover{ background:#FBF8F2; }
.card__ja{
  position:absolute; top:clamp(1.8rem,4vw,2.6rem); right:clamp(1.3rem,3vw,1.8rem);
  writing-mode:vertical-rl; font-family:var(--mincho);
  font-size:1.5rem; color:var(--shu); opacity:.16; letter-spacing:.2em;
}
.card h3{
  margin:0 0 .9rem; font-family:var(--serif); font-weight:400;
  font-size:1.35rem; padding-right:2rem;
}
.card p{ margin:0; font-size:.9rem; color:var(--sumi-70); line-height:1.85; }

/* ── Gallery ────────────────────── */
/* A collage that keeps changing: nine photographs in three columns, each one at
   its own natural shape, and every few seconds one of them dissolving into a
   picture you have not seen yet.

   The packing is worked out in assets/js/wall.js — the note at the top of that
   file explains why it cannot be done here — so every tile is placed
   absolutely with a transform and an explicit size. What this file supplies is
   the transitions, which is what turns a recalculated layout into tiles gliding
   to their new places. */
.wall{
  position:relative;
  max-width:var(--maxw); margin:0 auto;
  --wall-cols:3;
  /* A ceiling on the work, not the look. What actually decides how many
     photographs the section holds is the height band below — see fits() in
     assets/js/wall.js for why a fixed count could not keep the section
     contained once real photographs were in it. */
  --wall-slots:12;
  /* The band the collage aims to fill: a little under two thirds of the
     screen, never shorter than this nor taller than that. Three plain numbers
     rather than a clamp() because the packer reads them back, and a custom
     property comes back as it was written. */
  --wall-target-vh:58;
  --wall-target-min:380;
  --wall-target-max:640;
  /* Declared as a real property as well as a custom one because the packer
     reads it back through getComputedStyle().columnGap: a custom property comes
     back as the clamp() was written, and columnGap comes back in pixels. */
  --wall-gap:clamp(.6rem,1.6vw,1.1rem);
  column-gap:var(--wall-gap);
  transition:height .7s var(--ease);
}
.wall[hidden]{ display:none; }
@media (min-width:1200px){
  /* Four columns once there is room. A narrower tile is a shorter tile, and
     shorter tiles pack into the height band with less left over — the
     section comes out closer to the size it is aiming for, and holds eight
     or nine photographs rather than six. */
  .wall{ --wall-cols:4; --wall-slots:14; }
}

.wall__tile{
  position:absolute; top:0; left:0; margin:0;
  overflow:hidden; border-radius:2px; background:var(--kinari-2);
  opacity:0;
  transition:opacity .7s var(--ease), transform .7s var(--ease),
             width .7s var(--ease), height .7s var(--ease);
}
.wall__tile.is-in{ opacity:1; }
/* For one frame, while a newly made tile is appended and measured: without
   this it would visibly fly in from the corner of the section to the place the
   packer has just given it. */
.wall__tile.is-placing{ transition:none; }
/* Taken down from the dashboard while people are looking at it. Faded first,
   so it reads as deliberate rather than as a picture that failed. */
.wall__tile.is-going{ opacity:0; transform:scale(.97); transition:opacity .3s, transform .3s; }

.wall__frame{ display:block; position:relative; width:100%; height:100%; }

.wall__img{
  position:absolute; inset:0;
  width:100%; height:100%; object-fit:cover; display:block;
  transition:opacity .7s var(--ease), transform .7s var(--ease);
}
/* Arriving: a little small and a little low, so it settles into the tile
   rather than simply being there. */
.wall__img.is-fresh{ opacity:0; transform:scale(.94) translateY(6px); }
/* Leaving: barely growing, which reads as the photograph receding behind the
   one taking its place. Both are in the tile at once for the length of the
   fade — object-fit keeps the outgoing one honest while the tile changes
   shape underneath it. */
.wall__img.is-out{ opacity:0; transform:scale(1.04); }

/* ── Seeing all of them ─────────────────── */
/* The collage shows nine of however many there are, shuffled, so it needs a
   way out for anybody looking for one photograph in particular — their own,
   most likely. Expanded, the cycling stops and the real order returns. */
.wall__all{
  display:block; margin:clamp(1.4rem,3.5vw,2.2rem) auto 0;
  appearance:none; -webkit-appearance:none; background:none; border:0;
  padding:.55rem .2rem; cursor:pointer;
  font-family:var(--sans); font-size:.74rem;
  letter-spacing:.2em; text-indent:.2em; text-transform:uppercase;
  color:var(--sumi-45); border-bottom:1px solid var(--sumi-15);
  transition:color .3s var(--ease), border-color .3s var(--ease);
}
.wall__all:hover{ color:var(--shu); border-bottom-color:var(--shu); }
.wall__all:focus-visible{ outline:2px solid var(--ai); outline-offset:3px; }
.wall__all[hidden]{ display:none; }
/* Two labels, one of them shown. applyLang() in main.js assigns textContent to
   every element carrying data-en, which would wipe out any markup inside it —
   so each label is its own element with its own pair of translations, and the
   button switches between them in CSS. */
.wall__all .is-less{ display:none; }
.wall__all.is-open .is-all{ display:none; }
.wall__all.is-open .is-less{ display:inline; }

/* Shown in place of the picker when photo sharing is switched off, so the
   section says why it is quiet rather than just being empty. */
.share__closed{
  max-width:34rem; margin:0 auto; text-align:center;
  font-size:.86rem; color:var(--sumi-45); font-style:italic;
}
.share__closed[hidden], .share[hidden]{ display:none; }

.gallery__empty{
  max-width:34rem; margin:clamp(1.5rem,4vw,2.5rem) auto 0; text-align:center;
  font-size:.86rem; color:var(--sumi-45); font-style:italic;
}
.gallery__empty[hidden]{ display:none; }
.gallery__note{
  max-width:34rem; margin:clamp(2rem,5vw,3rem) auto 0; text-align:center;
  font-size:.82rem; color:var(--sumi-45); font-style:italic;
}

/* ── The friends' invitations ────────────────────────────
   A name the guest list does not know belongs to a friend, and friends are
   asked to two things the family are not. Set apart from the questions above
   it — indigo rather than the vermilion the rest of the form uses — because
   it is an invitation being made, not a detail being collected. */
.person--friend{ border-color:var(--ai); }
.person--friend > legend{ color:var(--ai); }
/* A little more air between the two invitations than the grid gives the
   one-line fields it was built for. */
.person--friend .person__grid{ gap:1.4rem; }

/* The sentence that explains why a friend is being asked about an evening
   rather than the dinner. Set as prose, not as a form label. */
.person--friend .friend__note{
  margin:0 0 1.2rem; max-width:44rem;
  font-family:var(--sans); font-size:.84rem; line-height:1.8;
  letter-spacing:.01em; color:var(--sumi-70);
}

.ev{ border:0; margin:0; padding:0; min-width:0; }
/* Both of these undo the form's own label styling deliberately. Every other
   label on this form is a two-word tag — GUEST 1, FOOD — and letterspaced
   small caps suit that. These are a sentence naming an evening and a sentence
   answering it, and setting a sentence in caps at .2em tracking makes it
   something to decode rather than read. */
.person--friend .ev > legend{
  display:block; padding:0; margin:0 0 .45rem;
  font-family:var(--sans); font-size:.82rem; line-height:1.5;
  letter-spacing:.02em; text-transform:none; color:var(--sumi);
}
.person--friend .ev__opt{
  display:flex; align-items:center; gap:.55rem;
  margin:0; padding:.3rem .1rem; cursor:pointer;
  font-family:var(--sans); font-size:.86rem; line-height:1.5;
  letter-spacing:.01em; text-transform:none; color:var(--sumi-70);
}
.person--friend .ev__opt input{ accent-color:var(--ai); width:1rem; height:1rem; flex:none; }
.person--friend .ev__opt:hover{ color:var(--sumi); }

/* Unanswered, after the form has been asked to send. Both invitations want a
   number to book a table on, so neither may be left blank — and the mark
   comes off the moment one is chosen. */
.person--friend .ev.is-bad > legend{ color:var(--beni); }
.ev.is-bad{
  outline:1px solid var(--beni-lo); outline-offset:.5rem;
  border-radius:2px;
}

/* ── The viewer ───────────────────── */
/* Above the nav (100) and the falling leaves (90), below the loading veil
   (999) — the veil is the one thing that should ever cover a photograph. */
.lightbox{
  position:fixed; inset:0; z-index:900;
  display:grid; place-items:center;
  opacity:0; transition:opacity .26s var(--ease);
}
.lightbox[hidden]{ display:none; }
.lightbox.is-open{ opacity:1; }

.lightbox__scrim{
  position:absolute; inset:0;
  background:rgba(28,27,24,.94);
  touch-action:none;
}

/* Inset rather than given a width and a height, so that "the whole
   photograph fits" is a property of the box instead of a sum that has to come
   out right. The top inset is what keeps a picture clear of the close button
   rather than under it; the rest is margin so that a photograph is never
   bleeding off an edge.

   No maximum width. A photograph is shown at its own size or smaller, never
   enlarged — that is what width:auto with a max of 100% means — so a cap here
   would do nothing but waste a large screen on a large photograph. */
.lightbox__stage{
  position:absolute; margin:0;
  inset:clamp(3.4rem,8vh,4.8rem) clamp(.5rem,2vw,1.5rem) clamp(1rem,4vh,2.4rem);
  /* One cell, declared as exactly the size of the stage. It has to be
     declared: an auto track is sized BY its contents, so a photograph asking
     to be at most 100% of a row that is 100% of the photograph is asking for
     nothing at all, and a tall picture simply ran off the bottom of the
     screen. A definite track is what gives max-height something real to mean. */
  display:grid; grid-template:100% / 100%; place-items:center;
  --drag:0px;
  /* Sideways is ours, to page between photographs; up and down stays the
     browser's, so a scroll that begins on the picture still scrolls. */
  touch-action:pan-y;
}

/* width and height stay auto and are bounded on both axes, which is what
   guarantees the whole photograph is visible whatever shape it is: a panorama
   runs out of width first, a tall portrait runs out of height first, and
   either way the other side is whatever the ratio makes it. Nothing is ever
   cropped — object-fit is named here only to say so, since with auto sizing
   there is nothing left for it to do. */
.lightbox__img{
  grid-area:1 / 1;
  max-width:100%; max-height:100%; width:auto; height:auto;
  /* And the other half of it. A grid item's automatic minimum size is its
     content's size, which for an image is its natural size — so a 3000px-tall
     photograph had a floor of 3000px and the maximum above could not pull it
     below that. These two zeros are what let it be told to be smaller. */
  min-width:0; min-height:0;
  object-fit:contain; display:block;
  transform:translateX(var(--drag));
  -webkit-user-select:none; user-select:none; -webkit-user-drag:none;
}
/* The thumbnail the guest just tapped. It is already in the cache, so it can
   be on screen in the same frame as the tap and hold the frame while the
   full-size file arrives. Blurred, so that the moment before the real one
   lands does not read as a soft photograph. */
.lightbox__img--pre{ filter:blur(8px); transform:translateX(var(--drag)) scale(1.03); }
.lightbox__img:not(.lightbox__img--pre){ opacity:0; transition:opacity .3s var(--ease); }
.lightbox__img.is-ready{ opacity:1; }
/* While a finger is on it the picture tracks the finger, and a transition
   would put it a third of a second behind. */
.lightbox__stage.is-dragging .lightbox__img{ transition:none; }

.lightbox__x, .lightbox__nav{
  position:absolute; z-index:2;
  appearance:none; -webkit-appearance:none; border:0; background:none;
  width:3rem; height:3rem; padding:0; cursor:pointer;
  color:rgba(246,242,234,.72);
  transition:color .3s var(--ease);
}
.lightbox__x:hover, .lightbox__nav:hover{ color:var(--kinari); }
.lightbox__x:focus-visible, .lightbox__nav:focus-visible{
  color:var(--kinari); outline:2px solid var(--kinari); outline-offset:2px;
}
.lightbox__x[hidden], .lightbox__nav[hidden]{ display:none; }

.lightbox__x{ top:clamp(.4rem,1.6vw,1.1rem); right:clamp(.4rem,1.6vw,1.1rem); }
/* Drawn rather than written, so the cross and the chevrons are the same
   weight and neither depends on a glyph being present in the font. */
.lightbox__x::before, .lightbox__x::after{
  content:''; position:absolute; left:50%; top:50%;
  width:1.1rem; height:1px; background:currentColor;
}
.lightbox__x::before{ transform:translate(-50%,-50%) rotate(45deg); }
.lightbox__x::after{  transform:translate(-50%,-50%) rotate(-45deg); }

.lightbox__nav{ top:50%; margin-top:-1.5rem; }
.lightbox__nav--prev{ left:clamp(.1rem,1.2vw,1.1rem); }
.lightbox__nav--next{ right:clamp(.1rem,1.2vw,1.1rem); }
.lightbox__nav::before{
  content:''; position:absolute; left:50%; top:50%;
  width:.68rem; height:.68rem;
  border-top:1px solid currentColor; border-right:1px solid currentColor;
}
.lightbox__nav--next::before{ transform:translate(-62%,-50%) rotate(45deg); }
.lightbox__nav--prev::before{ transform:translate(-38%,-50%) rotate(-135deg); }

@media (prefers-reduced-motion:reduce){
  /* The collage does not cycle at all under this — wall.js checks the same
     query and simply never starts — so what is left to quiet is the
     rearranging, and the scale on anything that does still change. */
  .wall, .wall__tile, .wall__img, .lightbox, .lightbox__img{ transition:none; }
  .wall__img.is-fresh, .wall__img.is-out, .wall__tile.is-going{ transform:none; }
  .lightbox__img--pre{ transform:translateX(var(--drag)); }
}

/* ── Sharing your own ───────────────────────────────────── */
.share{ max-width:44rem; margin:0 auto clamp(2rem,5vw,3rem); text-align:center; }
.share__pick input{ position:absolute; opacity:0; width:0; height:0; }
.share__pick{ cursor:pointer; display:inline-block; }
.share__pick input:focus-visible + span{ outline:2px solid var(--ai); outline-offset:3px; }
.share__hint{
  margin:1.1rem auto 0; max-width:32rem;
  font-size:.82rem; line-height:1.75; color:var(--sumi-45);
}
.share__queue{ list-style:none; margin:1.4rem 0 0; padding:0; display:grid; gap:.5rem; text-align:left; }
.share__queue:empty{ display:none; }
.share__queue li{
  display:flex; align-items:center; gap:.8rem;
  font-size:.8rem; color:var(--sumi-70);
  padding:.55rem .8rem; border:1px solid var(--sumi-15); border-radius:2px;
  background:rgba(255,255,255,.4);
}
.share__queue li span:first-child{ flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.share__queue .is-done{ color:var(--kin); }
.share__queue .is-failed{ color:var(--shu); }
.share__msg{ margin:1.1rem 0 0; font-size:.84rem; color:var(--sumi-70); min-height:1.2em; }
.share__msg.is-warn{ color:var(--shu); }

.share__from{ max-width:34rem; margin:clamp(3rem,7vw,4.5rem) auto 0; text-align:center; }
.share__from h3{
  margin:0 0 .7rem; font-family:var(--serif); font-weight:400;
  font-size:clamp(1.15rem,3vw,1.45rem);
}
.share__from p{ margin:0; font-size:.86rem; color:var(--sumi-45); font-style:italic; }

/* ── Prose ──────────────────────────────────────────────── */
.prose{ text-align:center; }
.prose p{ margin:0 0 1.5rem; color:var(--sumi-70); }
.prose__note{
  font-size:.85rem !important; font-style:italic; color:var(--sumi-45) !important;
  padding-top:1.5rem; border-top:1px solid var(--sumi-15);
}

/* ── FAQ ────────────────────────────────────────────────── */
.faq{ border-top:1px solid var(--sumi-15); }
.faq details{ border-bottom:1px solid var(--sumi-15); }
.faq summary{
  list-style:none; cursor:pointer; position:relative;
  padding:1.4rem 3rem 1.4rem 0;
  font-family:var(--serif); font-size:clamp(1.08rem,3vw,1.3rem);
  transition:color .3s;
}
.faq summary::-webkit-details-marker{ display:none; }
.faq summary:hover{ color:var(--shu); }
.faq summary::after{
  content:''; position:absolute; right:.4rem; top:50%; translate:0 -50%;
  width:11px; height:11px;
  background:
    linear-gradient(var(--shu),var(--shu)) center/11px 1px no-repeat,
    linear-gradient(var(--shu),var(--shu)) center/1px 11px no-repeat;
  transition:transform .35s var(--ease);
}
.faq details[open] summary::after{ transform:rotate(135deg); }
.faq details p{
  margin:0; padding:0 3rem 1.7rem 0;
  color:var(--sumi-70); font-size:.93rem;
  animation:fadeDown .45s var(--ease);
}
@keyframes fadeDown{ from{ opacity:0; transform:translateY(-6px);} to{ opacity:1; transform:none;} }

/* ── Buttons ────────────────────────────────────────────── */
.btn{
  display:inline-block; cursor:pointer;
  font-family:var(--sans); font-size:.73rem; font-weight:400;
  letter-spacing:.22em; text-transform:uppercase; text-decoration:none;
  padding:1.05rem 2.3rem; border-radius:2px;
  transition:.4s var(--ease);
}
.btn--solid{
  background:var(--shu); color:var(--kinari); border:1px solid var(--shu);
}
.btn--solid:hover{ background:#962C17; border-color:#962C17; transform:translateY(-2px); box-shadow:0 12px 24px -14px rgba(178,55,31,.8); }
.btn--line{
  background:transparent; color:var(--sumi); border:1px solid var(--sumi-15);
}
.btn--line:hover{ border-color:var(--sumi); }
.btn:disabled{ opacity:.45; cursor:not-allowed; transform:none !important; }

/* ── RSVP form ──────────────────────────────────────────── */
.sec--rsvp{ background:rgba(239,233,221,.55); }
.form{
  max-width:46rem; margin:0 auto;
  display:grid; grid-template-columns:1fr 1fr; gap:1.5rem clamp(1rem,3vw,2rem);
}
/* display:grid above outranks the browser's own [hidden] rule, so the form
   ignored `form.hidden = true` and stayed on screen underneath the thank-you
   panel after a reply was sent — which read as "nothing happened" and invited
   a second submission. The same guard .thanks, .gallery, .guest and .yesonly
   already carry. */
.form[hidden]{ display:none; }
.f--half{ grid-column:span 1; }
.f--full{ grid-column:1 / -1; }
.yesonly{
  grid-column:1 / -1;
  display:grid; grid-template-columns:1fr 1fr; gap:1.5rem clamp(1rem,3vw,2rem);
  animation:fadeDown .5s var(--ease);
}
.yesonly[hidden]{ display:none; }

.f label, .f legend{
  display:block; margin-bottom:.6rem; padding:0;
  font-size:.66rem; letter-spacing:.2em; text-transform:uppercase; color:var(--sumi-45);
}
fieldset.f{ border:0; margin:0; padding:0; min-width:0; }

.form input[type=text], .form input[type=email], .form select, .form textarea{
  width:100%; font:inherit; font-size:.95rem; color:var(--sumi);
  background:rgba(255,255,255,.5); border:0; border-bottom:1px solid var(--sumi-15);
  padding:.7rem .2rem; border-radius:2px 2px 0 0;
  transition:border-color .3s, background .3s;
}
.form textarea{ resize:vertical; line-height:1.7; }
.form select{ appearance:none; cursor:pointer;
  background-image:linear-gradient(45deg,transparent 50%,var(--sumi-45) 50%),linear-gradient(135deg,var(--sumi-45) 50%,transparent 50%);
  background-size:5px 5px,5px 5px; background-position:right 12px center,right 7px center; background-repeat:no-repeat;
}
.form :is(input,select,textarea):focus{
  outline:none; border-bottom-color:var(--shu); background:rgba(255,255,255,.85);
}
.form :is(input,select,textarea).is-bad{ border-bottom-color:var(--shu); background:rgba(178,55,31,.06); }


/* ── The family party roster ─────────────────────────────── */
.party__note{
  padding:1.2rem 1.4rem; border-left:2px solid var(--kin-2);
  background:rgba(255,255,255,.4); border-radius:2px;
}
.party__note h4{
  margin:0 0 .5rem; font-family:var(--serif); font-weight:400; font-size:1.15rem;
}
.party__note p{ margin:0; font-size:.86rem; line-height:1.8; color:var(--sumi-70); }


/* One group per household, so a guest can see who is on the invitation
   alongside the fields they are filling in for them. */
.person__grid .f--wide{ grid-column:1 / -1; }
.person.is-bad{ border-color:var(--shu); background:rgba(178,55,31,.05); }
.person{
  margin:0; padding:1.1rem 1.3rem 1.3rem; min-width:0;
  border:1px solid var(--sumi-15); border-radius:2px;
  background:rgba(255,255,255,.34);
}
.person legend{
  padding:0 .5rem; margin:0 0 0 -.5rem;
  font-size:.64rem; letter-spacing:.2em; text-transform:uppercase; color:var(--shu);
}
.person--child legend{ color:var(--ai); }
/* While a name is being checked. Deliberately faint and not a spinner — a
   name check that flashes something busy reads as an approval gate, and it is
   not one: an unrecognised name still sends perfectly well. */
.person.is-checking{ opacity:.55; }
.guest.is-checking [data-questions]{ opacity:.55; transition:opacity .2s var(--ease); }
.person__grid{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.9rem clamp(.9rem,2.5vw,1.6rem); }
.person__grid .f{ min-width:0; }
.person__grid label{ margin-bottom:.35rem; font-size:.6rem; }

@media (max-width:620px){
  .party__counts{ grid-template-columns:1fr 1fr; }
  .party__total{ grid-column:1 / -1; margin-bottom:0; }
  .person__grid{ grid-template-columns:1fr; }
}

.hp{ position:absolute !important; left:-9999px; width:1px; height:1px; opacity:0; }

.form__foot{ display:flex; align-items:center; gap:1.4rem; flex-wrap:wrap; margin-top:.8rem; }
.form__msg{ margin:0; font-size:.82rem; color:var(--shu); }
.form__msg.is-ok{ color:var(--kin); }

.thanks{
  max-width:34rem; margin:0 auto; text-align:center;
  animation:fadeDown .6s var(--ease);
}
.thanks[hidden]{ display:none; }
.thanks__seal{
  display:inline-grid; place-items:center; width:3.8rem; height:3.8rem;
  background:var(--shu); color:var(--kinari);
  font-family:var(--mincho); font-size:1.45rem; margin-bottom:1.8rem;
  /* cut to the crest's shape rather than a rounded square */
  -webkit-mask-image:var(--mark); mask-image:var(--mark);
  -webkit-mask-size:100% 100%;    mask-size:100% 100%;
  -webkit-mask-repeat:no-repeat;  mask-repeat:no-repeat;
}
.thanks h3{
  margin:0 0 1rem; font-family:var(--serif); font-weight:300;
  font-size:clamp(1.8rem,5vw,2.4rem);
}
.thanks p{ margin:0 0 2.2rem; color:var(--sumi-70); }

/* ── Footer ─────────────────────────────────────────────── */
/* The footer picks up the red from the top of the site, closing the page where
   it opened. Two lines only, both Japanese in either language: the invitation
   has already said the names in romaji at the top of the page and there is no
   reason to say them again on the way out.

   Every colour here is an ivory alpha tuned against --beni rather than against
   near-black; measured, .5 on the date came out at 2.10:1 and had to be
   lifted. Keep new colours on the same footing. */
.foot{ position:relative; background:var(--beni); color:var(--kinari); text-align:center; }
.foot__inner{ padding:clamp(3rem,8vw,5rem) var(--gut) clamp(2.5rem,6vw,3.5rem); }
.foot__jp{
  margin:0; font-family:var(--mincho); font-weight:400;
  font-size:clamp(1rem,3.4vw,1.35rem); letter-spacing:.18em;
  /* The ideographic spaces inside each name already do the work of tracking,
     so the indent only has to put back the trailing letter-space. */
  text-indent:.18em; color:rgba(246,242,234,.94);
}
.foot__date{
  margin:1.1rem 0 0; font-family:var(--mincho);
  font-size:.82rem; letter-spacing:.34em; text-indent:.34em;
  color:rgba(246,242,234,.86);
}

/* ── Reveal on scroll ───────────────────────────────────── */
.reveal{ opacity:0; transform:translateY(26px); }
.reveal.is-in{
  opacity:1; transform:none;
  transition:opacity .9s var(--ease), transform .9s var(--ease);
}
@media (prefers-reduced-motion:reduce){
  .reveal{ opacity:1 !important; transform:none !important; }
  .veil__mark{ animation:none; }
}

/* ── Responsive ─────────────────────────────────────────── */
@media (max-width:960px){
  .cards{ grid-template-columns:repeat(2,1fr); }
  .card, .card:nth-child(n+4){ grid-column:span 1; }
  .cards > .card:last-child:nth-child(odd){ grid-column:1 / -1; }
  .venue{ grid-template-columns:1fr; }
  .venue__art{ max-width:26rem; margin-inline:auto; }
  .wall{ --wall-cols:2; --wall-slots:8; --wall-target-max:620; }
}

@media (max-width:760px){
  /* The full string is 19 ideographic widths; at the desktop tracking it needs
     374px and a 390px phone offers 350, so it wrapped mid-name. Tighter here
     to keep both names on one line. Declared down here because the base rule
     is further up the file and would otherwise win on source order. */
  .invite__jp{ font-size:clamp(.95rem,4vw,1.4rem); letter-spacing:.05em; }
  /* No room for it beside the names on a phone; it is decoration, and the
     names are not. */
  .invite__vertical{ display:none; }
  .burger{ display:block; order:3; }
  .lang{ order:2; margin-left:auto; }
  .nav__brand{ opacity:1; transform:none; pointer-events:auto; margin-right:0; }
  .nav{ background:rgba(246,242,234,.9); backdrop-filter:blur(14px); }
  .nav__links{
    position:fixed; inset:0 0 auto; top:0; padding:6rem 2rem 3rem;
    flex-direction:column; align-items:flex-start; gap:.4rem;
    background:var(--kinari); border-bottom:1px solid var(--sumi-15);
    transform:translateY(-102%); transition:transform .5s var(--ease);
    max-height:100svh; overflow:auto;
  }
  .nav__links.is-open{ transform:none; }
  .nav__links a{ font-size:1.05rem; letter-spacing:.14em; text-transform:none;
    font-family:var(--serif); padding-block:.75rem; width:100%; }
  .nav__cta{ margin-top:1rem; }

    .count__grid{ max-width:24rem; }
  .form, .yesonly{ grid-template-columns:1fr; }
  .f--half{ grid-column:1 / -1; }
  /* The row keeps its three columns when narrow — the time is short enough to
     hold a column, and restacking would put the dots off the rule. */
  .timeline{ --tl-time:4rem; --tl-gap:.75rem; }
  .tl__time{ font-size:.95rem; }
  .tl__title{ font-size:1.05rem; }
}

@media (max-width:560px){
  /* Two columns still, and a shallower band: on a phone the collage should
     be something you come upon, not something you scroll through. */
  /* A deeper band than the desktop proportion would give. Two columns of a
     narrow screen make short tiles, and at 52vh a portrait-heavy deal filled
     a column with one photograph and stopped — two tiles is not a collage. */
  .wall{ --wall-slots:8; --wall-target-vh:62; --wall-target-max:580; }
}

@media (max-width:420px){
  .count__grid{ grid-template-columns:repeat(2,1fr); gap:1.6rem .5rem; }
  .count__grid li:nth-child(3)::before{ display:none; }
}

/* Below this the cards go single file. Declared last on purpose: the 960px
   block above also matches these widths, and would otherwise win on source
   order and hold them at two across. */
@media (max-width:620px){
  .cards{ grid-template-columns:1fr; }
  .card, .card:nth-child(n+4), .cards > .card:last-child:nth-child(odd){ grid-column:1 / -1; }
}

/* ── RSVP: adding and removing guests ────────────────────── */
/* Two boxes are shown to begin with; the rest are added on request. */
.guest[hidden]{ display:none; }
.guest__head{ display:flex; align-items:baseline; justify-content:space-between; gap:1rem; }
.guest__head label{ margin-bottom:.6rem; }
.guest__remove{
  border:0; background:none; padding:0; cursor:pointer;
  font:inherit; font-size:.66rem; letter-spacing:.16em; text-transform:uppercase;
  color:var(--sumi-45); transition:color .25s var(--ease);
}
.guest__remove:hover{ color:var(--shu); }
.guest__remove:focus-visible{ outline:2px solid var(--ai); outline-offset:3px; }

.guest__add{ margin-top:-.4rem; }
.guest__add[hidden]{ display:none; }
/* Lighter than the send button: adding a guest is a small step, not the
   action the form is for. */
.btn--add{
  padding:.6rem 1.2rem; font-size:.74rem; letter-spacing:.1em;
  border-style:dashed; color:var(--sumi-70);
}
.btn--add:hover{ border-style:solid; }
.guest__add .f__hint{ margin-top:.55rem; }
