/* The hero carries the sketch story (below), which is thin charcoal linework
   and needs a light ground to exist on - ink lines on a dark photograph are
   invisible. So the ground is warm-white, the photograph is kept as a faint
   editorial wash behind the sketch rather than removed, and the copy is ink.
   Layout, type and buttons are unchanged. */
/* The hero fills the room left below the header, not a share of the whole
   window. It used to be 92svh, which - with a header of 133px at narrow
   widths and 149px once the masthead lays out wide - always came to more
   than the screen: the hero ran past the fold by that much, and because
   the copy is bottom-anchored the overflow came out of the copy. On a
   small window the client saw the result as an empty hero with the
   headline at or under the fold; on a tall one the buttons were the part
   that got cut.

   9.5rem covers the taller of the two header layouts. The floor keeps the
   hero from collapsing on a very short window, where scrolling is expected
   anyway. */
.hero {
  position: relative;
  min-block-size: max(22rem, calc(100svh - 9.5rem));
  display: grid; align-items: end;
  background: var(--warm-white);
  overflow: hidden;
}
.hero__copy {
  position: relative;
  padding-block-end: var(--space-16);
  color: var(--ink);
}
.hero__copy .display, .hero__copy .lede { color: inherit; }

/* The frosted layer, at the client's request: "make it behind the text
   blury so the text becomes visible and doesn't get annoying to look at".

   backdrop-filter blurs what is already painted below this element - the
   sketch - so the room is still there behind the words, as haze rather
   than as lines crossing the glyphs. A whisper of the page's own ground
   goes over the blur so it reads as depth instead of a smear, and the
   layer is masked to an ellipse so it fades out with no visible seam.

   Placement, not z-index: this is the copy's first child and positioned,
   the text elements after it are positioned too (rule below), so paint
   order is DOM order - sketch, frost, words. Nothing is given a z-index,
   which keeps the sticky masthead (z-index 10) above all of it.

   Sized in the copy's own box, so it follows the text at every viewport
   and type size rather than being measured against the hero. */
.hero__frost {
  position: absolute;
  inset: -9% -5% -5%;
  pointer-events: none;
  /* 3px, a third of the 9px it was, at the client's request - the room
     behind the words is now only just softened rather than turned to
     haze. */
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  /* 90% transparent, at the client's request, down from 20% opacity and
     52% before that. With the blur at 3px as well, the drawing behind the
     words is close to full strength - the glyph halo is now doing nearly
     all of the work of keeping the letters readable, which is what makes
     this safe at all. */
  background: color-mix(in srgb, var(--warm-white) 10%, transparent);
  /* Solved, not styled, and tightened at the client's second look ("make
     only around the words and don't expand it too much"): the smallest
     ellipse that still blurs under the words. It hazes 52% of the hero
     where the first fit covered 93% - 939x556 against 1187x783 at 1265px
     wide - by allowing the outermost 4% of the headline's corners to fall
     into the fade, where the glyph halo carries them instead. */
  -webkit-mask-image: radial-gradient(ellipse 38% 46% at 50% 46%,
    rgb(0 0 0) 0 86%, transparent 90%);
  mask-image: radial-gradient(ellipse 38% 46% at 50% 46%,
    rgb(0 0 0) 0 86%, transparent 90%);
}
/* The words sit above the frost by paint order, which needs them to be
   positioned - the frost is. */
.hero__copy > :not(.hero__frost) { position: relative; }
/* A halo of the page's own ground, drawn around the glyphs themselves.
   The wash above is geometry and can only ever approximate the text's
   shape; this cannot drift, because it IS the text - so at any width
   between the breakpoints, where the ellipse fits least well, the letters
   still sit on clean ground. Warm-white on warm-white: invisible except
   where it crosses the sketch's hairlines. */
.hero__copy .display, .hero__copy .lede {
  text-shadow: 0 0 14px var(--warm-white), 0 0 28px var(--warm-white),
               0 0 44px var(--warm-white);
}
/* The ghost button's face was transparent, so linework showed through the
   middle of it. Warm-white is the ground it already sits on. */
.hero__actions .button--ghost { background: var(--warm-white); }
/* Bigger than the shared .display scale, at the client's request - the
   masthead's own size, so the Work and project headings are untouched. The
   lower bound rises with it: 3rem still sets "remembered." inside a 375px
   phone's wrap. */
.hero__copy .display { font-size: clamp(3rem, 8.6vw, 7rem); }
.hero__copy .lede { font-size: clamp(1.0625rem, 1.5vw, 1.25rem); }
/* Centred to match the now-centred .display/.lede above it (round 2) - a
   flex justify-content, not the .wrap > .button rule (base.css), because
   this is a two-button group one level deeper than that rule reaches, and
   auto margins on two flex items would space them apart unpredictably
   rather than centre the pair. */
.hero__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-4); margin-block-start: var(--space-8); }
.hero__actions .button--ghost { color: var(--ink); }
/* Larger than the site's standard button, again only in the hero. The
   inline padding is a clamp so two buttons still sit on one row on a
   375px phone instead of stacking. */
.hero__actions .button {
  font-size: 0.8125rem;
  padding-block: var(--space-4);
  padding-inline: clamp(var(--space-4), 3.4vw, var(--space-8));
}

/* Varied ratios and offsets — an editorial grid, not a repeating box grid. */
.services {
  display: grid; gap: var(--space-12);
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  margin-block: var(--space-16);
}
/* Two lines' worth of room for every heading, so the paragraphs below
   them start on the same line across the row - "Corporate & Brand
   Experiences" wraps where the others do not, and used to push its own
   paragraph a line lower than its neighbours'. A floor, not a fixed
   height: a heading that needs three lines still gets them. */
.services h3 {
  margin-block-end: var(--space-3);
  min-block-size: calc(2em * 1.06);
}
/* The homepage's four service cards, centred like the rest of the prose. */
.services article :is(p, h3, .label) { text-align: center; }
.services article { border-block-start: 1px solid var(--hairline); padding-block-start: var(--space-6); }
/* Four cards in a three-column row leave the fourth alone at the start of
   the second row; the client asked for it centred. The wrap caps at 78rem,
   which with an 18rem minimum and a 3rem gap tops out at three columns, so
   66rem is exactly where the third column - and the orphan - appears.
   Below it the four cards sit two-by-two and nothing is orphaned. */
@media (min-width: 66rem) {
  .services article:nth-child(4) { grid-column: 2; }
}

/* The coordinated pair. Two panels rather than two full-bleed bands, per
   the client: "i don't want two large boxes/containers ... make them
   matching together or coordinated". 3fr/2fr because the process rows keep
   number, name and description on one line and need the room; the Bride
   Show panel is a heading, a slogan and a link.

   The panels supply their own padding here instead of relying on .section,
   which they no longer carry - .paired is not a section, it holds two. */
.paired {
  display: grid; grid-template-columns: 3fr 2fr;
  gap: var(--space-1);
  margin-block: var(--space-8);
}
.paired__panel {
  padding: clamp(var(--space-8), 4vw, var(--space-12));
  display: flex; flex-direction: column; justify-content: center;
  text-align: center;
}
/* Centred throughout, per the client - including the label and the lede,
   which are nested too deep for the generic rule in base.css to reach. */
.paired__panel > p { text-align: center; margin-inline: auto; }
.paired__panel :is(.label, .lede, .statement) { text-align: center; }
.paired__panel > .button { align-self: center; margin-block-start: var(--space-6); }

@media (max-width: 60rem) {
  .paired { grid-template-columns: 1fr; }
}

.process { list-style: none; margin: var(--space-8) 0 0; padding: 0; display: grid; gap: 0; text-align: start; }
.process li {
  display: grid; grid-template-columns: auto minmax(9rem, auto) 1fr;
  align-items: baseline; gap: var(--space-6);
  padding-block: var(--space-8); border-block-start: 1px solid var(--hairline);
}
.process li:last-child { border-block-end: 1px solid var(--hairline); }
.process li h3, .process li p { margin: 0; }
.process__num {
  font-family: var(--font-heading); color: var(--rose-light);
  font-size: clamp(2.5rem, 4.5vw, 3.5rem); line-height: 1;
}

@media (max-width: 48rem) {
  .process li { grid-template-columns: 1fr; gap: var(--space-2); }
}

/* The process steps rise into place in sequence. The start state is applied
   only when the script is present (.js on <html>, set by reveal.js), so a
   page without JavaScript shows them in place rather than holding them at
   zero opacity forever. */
.js .process li {
  opacity: 0;
  transform: translateY(0.75rem);
  transition: opacity 700ms var(--ease), transform 700ms var(--ease);
  transition-delay: var(--step-delay, 0ms);
}
.js .process li.is-stepped { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .js .process li { opacity: 1; transform: none; transition: none; }
}

/* ---- The hero sketch -----------------------------------------------------
   An empty venue that draws itself into a finished wedding, behind the hero
   copy. Inline SVG, animated here with no JavaScript: every path carries
   pathLength="1", so `stroke-dasharray: 1` and a dashoffset run from 1 to 0
   draws any line from nothing to complete, whatever its true length.

   One 14-second loop, staged in story order - the client asked for more
   speed three times; it ran at 42 to begin with.

   The middle runs quicker than the ends, at the client's last note: the
   hall and its three table waves take 6% each, then the five middle
   chapters - the far tables, chairs, stage, dance floor and florals -
   draw in 4% each, and lighting and the finishing details close at 5.5%.
   The whole room lands at 44.5% (6.2s) and the finished hall then holds
   for 43% - six seconds - before it un-draws. Each group's keyframes hold two
   things: when its lines draw, and the shared fade at the end that returns
   the venue to empty before the loop restarts - the redraw reads as the story
   beginning again rather than a video snapping back to its first frame.

   Charcoal and neutral only - both locked palette tokens - at low opacity, so
   the sketch stays a background. The layer never captures the pointer. */

.hero__sketch {
  position: absolute; inset: 0;
  inline-size: 100%; block-size: 100%;
  pointer-events: none;
  /* Darker again at the client's request, so the tables, chairs, kosha and
     dance floor read clearly. The frost behind the copy, not a mask over
     the drawing, is what keeps the type calm - so the drawing can be this
     strong everywhere it is not behind the words. */
  opacity: 0.8;
  /* Set by the optional parallax in animate.js; 0 without it. */
  translate: var(--sketch-x, 0) var(--sketch-y, 0);
  /* Round five, at the client's direction: eye level. The perspective is
     DRAWN into the scene's geometry (one-point, from the aisle entrance),
     so no transform tilts the layer - a tilt on top of drawn perspective
     would double-distort the room. */
  /* No mask. The copy protects itself: the frost layer inside it blurs
     whatever is drawn behind the words (and on phones, a flat wash does
     the same job without a live filter) - see .hero__frost above.

     That replaces three rounds of fixed-percentage masks. Every one of
     them was measured at one viewport and wrong at the next, because the
     copy's share of the hero changes with the type scale and the window:
     when the headline grew to 110px its first line moved from 31% of the
     hero up to 21%, straight through a ramp that ended at 31.5%, and the
     arch lines crossed it at full strength. The wash is drawn relative to
     the copy's own box, so it cannot drift out of register - at any
     viewport, in either language, at any type size. */
}
.hero__sketch path {
  fill: none;
  stroke: var(--charcoal);
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  vector-effect: non-scaling-stroke;
}
/* The room itself is quieter than the things placed into it. */
.hero__sketch .g-venue path { stroke: var(--neutral); stroke-width: 1.1; }
/* The seating mass is the quieter grey so the stage, florals and details
   read as the design layer over it. */
.hero__sketch .g-dancefloor path,
.hero__sketch .g-tables path,
.hero__sketch .g-tables2 path,
.hero__sketch .g-tables3 path,
.hero__sketch .g-chairs path { stroke: var(--neutral); }

/* Ten chapters on one 42-second loop; the percentages are the schedule.
   The client's re-ordering is the schedule's whole shape: the tables come
   FIRST, in three waves (inner, middle, outer), then every
   chair, and only then the stage and dance floor - so the viewer watches the
   space fill before the focal points anchor it. Design, lighting and
   finishing build to the reveal.

   No opacity anywhere: after the finished wedding holds (77% to 92%, about
   six seconds), the scene UN-DRAWS in reverse chapter order - details first,
   the ground's own lines last - so the loop is one continuous story with no
   cut and no dissolve.

   The camera rides on the same clock, below: closest at the empty ground,
   pulling slowly wider as the venue fills, widest through the reveal. */
.hero__sketch .g-venue path { animation: sketch-venue 14s linear infinite; }
.hero__sketch .g-tables path { animation: sketch-tables 14s linear infinite; }
.hero__sketch .g-tables2 path { animation: sketch-tables2 14s linear infinite; }
.hero__sketch .g-tables3 path { animation: sketch-tables3 14s linear infinite; }
.hero__sketch .g-chairs path { animation: sketch-chairs 14s linear infinite; }
.hero__sketch .g-stage path { animation: sketch-stage 14s linear infinite; }
.hero__sketch .g-dancefloor path { animation: sketch-dancefloor 14s linear infinite; }
.hero__sketch .g-florals path { animation: sketch-florals 14s linear infinite; }
.hero__sketch .g-lighting path { animation: sketch-lighting 14s linear infinite; }
.hero__sketch .g-details path { animation: sketch-details 14s linear infinite; }

@keyframes sketch-venue {
  0%          { stroke-dashoffset: 1; }
  6%          { stroke-dashoffset: 0; }
  93%         { stroke-dashoffset: 0; }
  95.5%, 100% { stroke-dashoffset: 1; }
}
@keyframes sketch-tables {
  0%, 5.5%    { stroke-dashoffset: 1; }
  11.5%       { stroke-dashoffset: 0; }
  92%         { stroke-dashoffset: 0; }
  94%, 100%   { stroke-dashoffset: 1; }
}
@keyframes sketch-tables2 {
  0%, 11%     { stroke-dashoffset: 1; }
  17%         { stroke-dashoffset: 0; }
  91.5%       { stroke-dashoffset: 0; }
  93.5%, 100% { stroke-dashoffset: 1; }
}
@keyframes sketch-tables3 {
  0%, 16.5%   { stroke-dashoffset: 1; }
  20.5%       { stroke-dashoffset: 0; }
  91%         { stroke-dashoffset: 0; }
  93%, 100%   { stroke-dashoffset: 1; }
}
@keyframes sketch-chairs {
  0%, 20%     { stroke-dashoffset: 1; }
  24%         { stroke-dashoffset: 0; }
  90.5%       { stroke-dashoffset: 0; }
  92.5%, 100% { stroke-dashoffset: 1; }
}
@keyframes sketch-stage {
  0%, 23.5%   { stroke-dashoffset: 1; }
  27.5%       { stroke-dashoffset: 0; }
  90%         { stroke-dashoffset: 0; }
  92%, 100%   { stroke-dashoffset: 1; }
}
@keyframes sketch-dancefloor {
  0%, 27%     { stroke-dashoffset: 1; }
  31%         { stroke-dashoffset: 0; }
  89.5%       { stroke-dashoffset: 0; }
  91.5%, 100% { stroke-dashoffset: 1; }
}
@keyframes sketch-florals {
  0%, 30.5%   { stroke-dashoffset: 1; }
  34.5%       { stroke-dashoffset: 0; }
  89%         { stroke-dashoffset: 0; }
  91%, 100%   { stroke-dashoffset: 1; }
}
@keyframes sketch-lighting {
  0%, 34%     { stroke-dashoffset: 1; }
  39.5%       { stroke-dashoffset: 0; }
  88.5%       { stroke-dashoffset: 0; }
  90.5%, 100% { stroke-dashoffset: 1; }
}
@keyframes sketch-details {
  0%, 39%     { stroke-dashoffset: 1; }
  44.5%       { stroke-dashoffset: 0; }
  88%         { stroke-dashoffset: 0; }
  90%, 100%   { stroke-dashoffset: 1; }
}

/* The camera: a slow pull-back on the same 42-second clock. Closest on the
   empty ground, easing wider as the tables and chairs fill the space, widest
   from the reveal through the hold - the brief's "as the setup becomes
   larger, the camera can gradually pull farther back". The `scale` property,
   not transform, so it composes with the parallax's `translate` instead of
   clobbering it. The whole range is 1.18 to 1.0: every frame is a wide shot,
   which also keeps the drawing from ever cropping tighter than its first
   frame. */
.hero__sketch {
  transform-origin: 50% 36%;
  animation: sketch-camera 14s linear infinite;
}
/* Gentler than the first cut (1.18 -> 1): the client asked for the whole
   scene to stay more visible, and the padded viewBox already carries air -
   the camera now only breathes. Still never below 1, so the room never
   crops tighter than its widest frame. */
@keyframes sketch-camera {
  0%   { scale: 1.1; }
  20%  { scale: 1.06; }
  47%  { scale: 1.03; }
  77%  { scale: 1; }
  92%  { scale: 1; }
  100% { scale: 1.1; }
}

/* Set by animate.js while the hero is scrolled out of view: ten chapter
   animations plus the camera keep compositing forever otherwise, on a
   layer nobody can see. Purely additive - without JS the class never
   lands and the loop simply runs. */
.hero__sketch.is-resting,
.hero__sketch.is-resting path { animation-play-state: paused; }

/* The still version is the finished wedding, not the empty room: everything
   drawn, nothing moving. This is what reduced-motion visitors see, and it is
   a complete picture in its own right. */
@media (prefers-reduced-motion: reduce) {
  .hero__sketch {
    animation: none;
    scale: 1;
  }
  .hero__sketch path {
    animation: none;
    stroke-dashoffset: 0;
  }
}

/* Small screens get the essential story - venue, stage, one table, the
   pedestal florals, one chandelier - rather than every desktop detail
   squeezed into a phone width. The framing changes too: with `slice` on a
   full-height layer, the tall phone viewport sets the zoom and crops the
   room to a quarter of its width. So the layer becomes a SHORT, WIDE band
   anchored to the hero's lower two thirds: the reduced height lowers the
   slice scale (measured: ~530 scene units across a 375px hero, up from
   ~340), the doubled width keeps the sides fed, the hero's overflow clips
   the rest. The copy's own wash handles the type here as it does on the
   desktop, so this box needs no mask of its own. */
@media (max-width: 48rem) {
  /* A light wash only, to soften whatever the drawing's lower edge leaves
     near the first line - not the opaque ellipse this used to be. That one
     covered the copy's whole box in solid warm-white, and since the sketch
     band sat at the hero's bottom, behind the copy, it erased the drawing
     outright: a phone showed a blank hero with a headline in it, which is
     what the client reported twice. The band now sits above the copy (see
     .hero__sketch below), so there is little left to hide. */
  .hero__copy {
    /* A vertical wash, not an ellipse: the band is above the copy now, so
       the split this needs is horizontal - clear above the words, veiled
       from their top edge down. An ellipse left the headline's outer
       corners uncovered (measured 0.7 residual there, against the 0.19
       the type is held to), because no ellipse fits a block of text whose
       widest line is at the top. This has no horizontal geometry to get
       wrong, and it follows the copy's own box at every size.

       88% warm-white leaves 0.7 x 0.12 = 0.08 of a line showing under the
       glyphs; the first 8% of the box fades in so the drawing's lower edge
       is not cut by a hard line. The box is given top padding so that fade
       happens ABOVE the headline rather than across its first line - the
       copy is bottom-anchored, so padding at the top grows the box upward
       into the space the gradient needs. */
    padding-block-start: var(--space-8);
    background: linear-gradient(to bottom,
      transparent 0 2%,
      color-mix(in srgb, var(--warm-white) 88%, transparent) 8% 100%);
  }
  /* "remembered." is the binding word, and at 8.6vw it left only 15px of
     clearance on a 375px phone - nothing at 320px. Scaled by viewport
     instead so it shrinks with the frame: 48px at 375, 41px at 320, and
     up to 68px on a tablet, which is larger than it was before. */
  .hero__copy .display { font-size: clamp(2.5rem, 12.8vw, 4.25rem); }
  .hero__actions { gap: var(--space-3); }
  /* No live blur on a phone: the backdrop is animating, so the filter
     would be recomputed every frame on the weakest hardware. The wash
     above and the glyph halo do the work here instead. */
  .hero__frost { display: none; }
  .hero__sketch .sketch-extra { display: none; }
  /* The band moves to the TOP of the hero, which is where a phone's empty
     space was: the copy is bottom-anchored, so everything above it was
     bare while the drawing sat underneath the words being washed out.
     Anchored to the top and 52% tall, the room now fills that space and
     the copy sits below it on clean ground.

     Still twice the viewport wide, and shorter than before, which is what
     keeps the framing wide: `slice` scales by whichever axis needs more,
     so a shorter box scales less and shows more of the room. Measured at
     375px: about 670 scene units across, against 539 with the old band and
     roughly 340 before that. The drawing is bottom-aligned inside its box,
     so what a phone sees is the floor, the tables and the stage - the
     elements that read as a wedding - with the ceiling cropped away. */
  .hero__sketch {
    opacity: 0.7;
    inset: 0 -50% auto;
    inline-size: 200%;
    block-size: 52%;
  }
}
