/* ══════════════════════════════════════════════════════════════
   site.css — design tokens, layout and every component
   Shared by index.html and wedding.html. Edit here once, not twice.
   ══════════════════════════════════════════════════════════════ */
/* ════════════════════════════════════════════════════════════
   ONE DESIGN SYSTEM
   Palette : deep forest, from COLORS.png
   Type    : EB Garamond + DM Sans, from TYPOGRAPHY.png
   Radius  : 4px everywhere
   ════════════════════════════════════════════════════════════ */
:root{
  /* Forest ramp — the ONLY background colours in the whole site */
  --f-900:#0A1716;
  --f-800:#0E1F1D;
  --f-700:#112523;   /* base */
  --f-600:#16302C;
  --f-500:#1B3B36;
  --f-400:#214B45;
  --f-300:#2F665E;
  --f-200:#4E8A80;
  --f-100:#8FC4B8;   /* accent text */

  /* Ivory ramp — the ONLY foreground colours */
  --ivory:#F4F3F5;
  --ivory-80:rgba(244,243,245,.80);
  --ivory-62:rgba(244,243,245,.62);
  --ivory-42:rgba(244,243,245,.42);
  --ivory-22:rgba(244,243,245,.22);
  --line:rgba(244,243,245,.12);
  --line-2:rgba(244,243,245,.20);

  /* The list tick, in its two colourways — honey #DFA075 for light plates,
     near-white #FBF9F9 for dark ones. Declared once here so a list only has to
     say which plate it sits on. Inlined rather than fetched: each is 500 bytes,
     less than the overhead of the request that would go and get it, and a list
     cannot silently lose its markers if the asset is ever moved.
     Both are 20x20 with the same path, so they are interchangeable. */
  --tick-ink:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='none'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M14.4781 6.91058C14.8036 7.23602 14.8036 7.76366 14.4781 8.08909L9.47813 13.0891C9.15269 13.4145 8.62506 13.4145 8.29962 13.0891L5.79962 10.5891C5.47418 10.2637 5.47418 9.73602 5.79962 9.41058C6.12506 9.08514 6.65269 9.08514 6.97813 9.41058L8.88888 11.3213L13.2996 6.91058C13.6251 6.58514 14.1527 6.58514 14.4781 6.91058Z' fill='%23DFA075'/%3E%3C/svg%3E");
  --tick-light:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='none'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M14.4781 6.91083C14.8036 7.23626 14.8036 7.7639 14.4781 8.08934L9.47813 13.0893C9.15269 13.4148 8.62506 13.4148 8.29962 13.0893L5.79962 10.5893C5.47418 10.2639 5.47418 9.73626 5.79962 9.41083C6.12506 9.08539 6.65269 9.08539 6.97813 9.41083L8.88888 11.3216L13.2996 6.91083C13.6251 6.58539 14.1527 6.58539 14.4781 6.91083Z' fill='%23FBF9F9'/%3E%3C/svg%3E");

  /* Single accent */
  --honey:#DFA075;
  --err:#D70307;        /* inline form validation only */
  --honey-40:rgba(223,160,117,.40);

  /* ink for the light Contact section */
  --ink-1:#12211F;
  --ink-2:#3A4644;
  --grad-radial:radial-gradient(circle at 50% 50%,#2F665E 6%,#162B29 100%);

  /* light quote panel in Відгуки */
  --rev-panel:#F1F6FC;
  --rev-ink:#12211F;
  --rev-ink-2:#33403E;
  --rev-ink-3:#5C6A68;
  --red:#D9634F;

  /* Prose — the hero lede's settings, shared by every body block */
  /* 16px everywhere body text appears — the site's one prose size. This was
     clamp(16px,1.7vw,21px) at weight 300, which meant the About paragraphs and
     the section subtitles were the only prose that grew with the window: 21px
     on any laptop, against the flat 16px of Контакти, the wedding intro and
     the package rows. Same 400 weight and same 24px line as .wed-intro__p, so
     every block of prose on the site now reads identically.
     Only .sec-head .sub and .about__body p read these three. */
  --prose-size:16px;
  --prose-leading:1.5;      /* 24px */
  --prose-weight:400;
  --prose-ink:#F7FAF7;

  /* Type */
  --serif:'EB Garamond','Times New Roman',Georgia,serif;
  --sans:'DM Sans','Helvetica Neue',-apple-system,BlinkMacSystemFont,Arial,sans-serif;

  /* Hero */
  --hero-overlay-opacity:0.25;

  /* Geometry */
  --r:4px;                       /* one radius, everywhere */
  --stroke:2.5px;                /* one border weight on every button */
  --maxw:1440px;
  --gutter:clamp(20px,5vw,80px);
  --nav-h:80px;
  --ease:cubic-bezier(.22,.61,.36,1);
  --ease-soft:cubic-bezier(.4,0,.2,1);
}

/* ── reset ─────────────────────────────────────────────── */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
body{
  background:var(--f-700);
  color:var(--ivory);
  font-family:var(--sans);font-weight:300;font-size:16px;line-height:1.7;
  /* clip, not hidden: overflow:hidden makes body a scroll container
     and position:sticky stops working inside it */
  overflow-x:clip;-webkit-font-smoothing:antialiased;
}
img{display:block;max-width:100%;height:auto}
a{color:inherit;text-decoration:none}
button,input,textarea,select{font:inherit;color:inherit}
button{background:none;border:none;cursor:pointer}
h1,h2,h3,h4,h5,h6{font-family:var(--serif);font-weight:500;line-height:1.14;letter-spacing:.005em}
h2{font-weight:400;font-size:clamp(32px,5vw,62px)}   /* 62px Regular, steps down only when it must */
::selection{background:var(--f-300);color:var(--ivory)}
::-webkit-scrollbar{width:9px}
::-webkit-scrollbar-track{background:var(--f-900)}
::-webkit-scrollbar-thumb{background:var(--f-400);border-radius:20px}
::-webkit-scrollbar-thumb:hover{background:var(--f-300)}

.wrap{max-width:var(--maxw);margin:0 auto;padding:0 var(--gutter);width:100%}
section[id]{scroll-margin-top:var(--nav-h)}

/* ── section rhythm: alternating forest depths only ────── */
.sec{padding:clamp(80px,10vw,148px) 0;position:relative;overflow:hidden}
.sec--800{background:var(--f-800)}
.sec--700{background:var(--f-700)}
.sec--600{background:var(--f-600)}
/* Довіра клієнтів sits on the bokeh plate; the section's own radial
   glow is switched off so it does not muddy the photograph. */
#trust{background:url('https://res.cloudinary.com/yfoyq1xk/image/upload/f_auto,q_auto:good/v1785855576/Green_Background_v6wox0.webp') center center/cover no-repeat, var(--f-800)}
#trust::before{display:none}
/* Послуги та ціни on its own plate; the section's radial glow is off
   so it does not sit on top of the artwork. */
#services{background:url('https://res.cloudinary.com/yfoyq1xk/image/upload/f_auto,q_auto:good/v1785868667/Green_backgound_v2_afv1v6.webp')
          center center/cover no-repeat, var(--f-700)}
#services::before{display:none}
/* Portfolio, Reviews and About on plates too; each section's own radial
   glow is switched off so it does not sit on top of the artwork. */
#portfolio{background:url('https://res.cloudinary.com/yfoyq1xk/image/upload/f_auto,q_auto:good/v1785867861/Green_Background_etmfiu.webp')
           center center/cover no-repeat, var(--f-800)}
#portfolio::before{display:none}
#reviews{background:url('https://res.cloudinary.com/yfoyq1xk/image/upload/f_auto,q_auto:good/v1785868667/Green_backgound_v2_afv1v6.webp')
         center center/cover no-repeat, var(--f-600)}
#reviews::before{display:none}
#about{background:url('https://res.cloudinary.com/yfoyq1xk/image/upload/f_auto,q_auto:good/v1785867861/Green_Background_etmfiu.webp')
       center center/cover no-repeat, var(--f-700)}
/* The section was content-height (~525px image + padding ≈ 820px), so on
   a shorter viewport the next panel showed at the bottom before any
   scrolling. A viewport floor keeps the plate filling the screen. */
#about{min-height:100svh;display:flex;align-items:center}
#about > .wrap{width:100%}
#about::before{display:none}

/* soft light bloom shared by every section — the luxury glue */
.sec::before{
  content:'';position:absolute;inset:0;pointer-events:none;
  background:
    radial-gradient(ellipse 60% 50% at 15% 8%,rgba(47,102,94,.20),transparent 60%),
    radial-gradient(ellipse 55% 55% at 88% 92%,rgba(33,75,69,.24),transparent 62%);
}
.sec > .wrap{position:relative;z-index:1}

/* ── language ──────────────────────────────────────────── */
/* Every translated node carries data-lang. display:revert restores the
   element's own default (inline for span, block for p/div), which is why
   this needs no per-element overrides the way the 2-language version did. */
[data-lang]{display:none}
html[lang="uk"] [data-lang="uk"],
html[lang="en"] [data-lang="en"],
html[lang="pl"] [data-lang="pl"],
html[lang="ru"] [data-lang="ru"]{display:revert}

/* ════════════════════════════════════════════════════════
   TYPE COMPONENTS  (used identically in every block)
   ════════════════════════════════════════════════════════ */
/* The colour already clears 6:1 on every section — what made this
   disappear was 10.5px at weight 300 with .38em tracking, over a
   backdrop that shifts under it. Heavier, larger, tighter, plus a
   shadow so it holds against the bokeh plate too. */
.eyebrow{
  display:flex;align-items:center;justify-content:center;gap:16px;
  font-family:var(--sans);font-weight:500;
  font-size:12.5px;letter-spacing:.28em;text-transform:uppercase;
  /* Ivory, not honey. Про мене is the only eyebrow on the site, so this rule
     is in practice scoped to that one block, where no text may carry the
     accent colour. 62% ivory measures 6.4:1 on the darkest part of the plate
     — quieter than the heading, still past AA at this size. The flanking rule
     follows the text; leaving it orange under ivory type read as a mistake. */
  color:var(--ivory-62);margin-bottom:22px;
  text-shadow:0 1px 3px rgba(0,0,0,.92), 0 0 14px rgba(0,0,0,.65);
}
.eyebrow::before,.eyebrow::after{
  content:'';width:clamp(22px,4vw,54px);height:1.5px;flex:none;
  filter:drop-shadow(0 1px 2px rgba(0,0,0,.8));
}
.eyebrow::before{background:linear-gradient(90deg,transparent,var(--ivory-42))}
.eyebrow::after {background:linear-gradient(90deg,var(--ivory-42),transparent)}
.eyebrow--left{justify-content:flex-start}
.eyebrow--left::before{display:none}

.sec-head{text-align:center;margin-bottom:clamp(46px,6vw,78px)}
/* the home page opens with the hero h1, so its section heads are h2; the
   portfolio page starts here, so its head carries the h1 — both are styled
   the same, the difference is document structure, not appearance */
.sec-head h1,.sec-head h2{color:var(--ivory)}
.sec-head h1{font-weight:400;font-size:clamp(32px,5vw,62px);line-height:1.14;letter-spacing:.005em}
.sec-head .sub{
  max-width:56ch;margin:20px auto 0;
  font-family:var(--sans);font-weight:var(--prose-weight);
  font-size:var(--prose-size);line-height:var(--prose-leading);
  color:var(--prose-ink);
}
.hairline{
  width:clamp(60px,8vw,110px);height:1px;margin:24px auto 0;
  background:linear-gradient(90deg,transparent,var(--honey-40),transparent);
}

/* ════════════════════════════════════════════════════════
   BUTTONS  —  radius 4px, one hover language
   ════════════════════════════════════════════════════════ */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:10px;
  padding:14px 40px;border-radius:var(--r);
  font-family:var(--serif);font-size:17px;font-weight:500;letter-spacing:.04em;
  color:var(--ivory);border:var(--stroke) solid var(--line-2);
  background:transparent;position:relative;isolation:isolate;
  /* clips the ellipse to the inside of the stroke, so the outline stays
     visible for the whole sweep instead of being painted over halfway */
  overflow:hidden;
  /* the label waits for the ellipse to reach it before changing colour */
  transition:color .38s var(--ease) .3s,border-color .5s var(--ease);
  white-space:nowrap;cursor:pointer;
}
/* An ellipse rises from below the button and swallows it — the same movement
   as the Забронювати button on the wedding page, in each button's own colours.
   Everything is a percentage of the button, so one definition serves the
   nav CTA, the hero button, the card buttons and the full-width mobile ones
   without any of them needing their own numbers.
   118 x 250 at right:-11 / top:-75 puts all four corners of the border box
   comfortably inside the ellipse at every aspect ratio. */
.btn::before{
  content:'';position:absolute;z-index:-1;
  width:118%;height:250%;right:-11%;top:-75%;left:auto;bottom:auto;
  border-radius:50%;
  background:var(--ivory);
  transform:translateY(120%);          /* parked below the button */
  transition:transform 1.05s var(--ease);
}
.btn:hover::before{transform:translateY(0)}
.btn:hover{color:var(--f-700);border-color:var(--ivory)}
/* A tap on a touch screen fires :hover as well, so the ellipse would begin its
   full second-long rise and the finger would leave mid-sweep — which is the
   half-covered button you saw. On press the fill snaps home instead, so a tap
   reads as a press rather than as an animation caught halfway. */
.btn:active::before{transform:translateY(0);transition:transform .18s var(--ease)}
.btn:active{transition:color .18s var(--ease)}

/* The two filled buttons are not outlines, so they keep the flat upward wipe
   they already had — an ellipse reads as decoration on a solid plate. */
.btn--fill,.form .btn{overflow:visible}
.btn--fill::before,.form .btn::before{
  width:auto;height:auto;inset:calc(var(--stroke) * -1);
  border-radius:var(--r);
  transform:scaleY(0);transform-origin:bottom;
  transition:transform .55s var(--ease);
}
.btn--fill:hover::before,.form .btn:hover::before{transform:scaleY(1)}

/* Solid forest button — the closing band's Зв'язатися. Same geometry, same
   typography and the same rising ellipse as .btn--ink beside it on this page;
   only the two colours differ, because this one is filled rather than outlined.
   It deliberately stays OUT of the filled group above, so it inherits the
   ellipse from .btn::before instead of the flat wipe. */
.btn--forest{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  width:244px;height:48px;padding:0;max-width:100%;
  border:1.5px solid transparent;border-radius:4px;
  font-family:var(--serif);font-size:20px;font-weight:400;letter-spacing:.04em;
  color:rgba(247,250,247,.99);
  background:linear-gradient(180deg,#162B29 0%,#2F665E 100%);
}
/* the ellipse arrives as the gradient flipped end over end, so the light
   appears to sweep up through the button */
.btn--forest::before{background:linear-gradient(180deg,#2F665E 0%,#162B29 100%)}
.btn--forest:hover,.btn--forest:focus-visible{color:rgba(247,250,247,.99);border-color:transparent}
.btn--forest:focus-visible{outline-color:#0D0D0D}

@media(prefers-reduced-motion:reduce){
  .btn::before{transition:opacity .25s linear;transform:translateY(0);opacity:0}
  .btn:hover::before{opacity:1}
  .btn--fill::before,.form .btn::before{transform:scaleY(1);opacity:0}
  .btn--fill:hover::before,.form .btn:hover::before{transform:scaleY(1);opacity:1}
}

/* Fixed 244x48. Scoped to .nav so it outranks .btn--sm, which is declared
   later in this file and would otherwise win on padding at equal specificity.
   padding:0 lets the flex centring place the label exactly in the middle. */
.nav .btn--nav{width:244px;height:48px;padding:0;flex:none;font-size:17.5px}
.btn--fill{border-color:transparent;background:linear-gradient(180deg,var(--f-300),var(--f-500))}
.btn--fill::before{background:linear-gradient(180deg,var(--f-200),var(--f-400))}
.btn--fill:hover{color:var(--ivory);border-color:transparent}
.btn--honey{border-color:var(--honey-40);color:var(--honey)}
.btn--honey::before{background:var(--honey)}
.btn--honey:hover{color:var(--f-800);border-color:var(--honey)}
.btn--sm{padding:11px 30px;font-size:15.5px}
.btn--wide{width:100%;padding:12px 20px}
.btn--lg{padding:17px 54px;font-size:19px}
.btn:disabled{opacity:.4;cursor:not-allowed}
.btn svg{width:17px;height:17px;stroke:currentColor;fill:none;stroke-width:1.4}

.link-arrow{
  display:inline-flex;align-items:center;gap:8px;
  font-family:var(--serif);font-weight:400;font-size:20px;
  letter-spacing:.02em;
  color:var(--honey);position:relative;padding-bottom:3px;cursor:pointer;
  transition:color .4s var(--ease);
}
.link-arrow::after{
  content:'';position:absolute;left:0;bottom:0;width:0;height:1px;
  background:var(--honey);transition:width .45s var(--ease);
}
.link-arrow:hover::after{width:100%}
.link-arrow .arw{transition:transform .4s var(--ease);display:inline-flex;align-items:center}
/* an inline glyph, not a character — U+2197 picks up emoji presentation on
   Android and renders as a blue tile */
.arw svg{width:1em;height:1em;display:block;stroke:currentColor;fill:none;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
.link-arrow:hover .arw{transform:translate(3px,-3px)}

/* ════════════════════════════════════════════════════════
   NAV
   ════════════════════════════════════════════════════════ */
.nav{
  position:fixed;top:0;left:0;right:0;z-index:1000;height:var(--nav-h);
  display:flex;align-items:center;
  transition:background .55s var(--ease),backdrop-filter .55s var(--ease),box-shadow .55s var(--ease);
}
.nav.solid{
  background:rgba(14,31,29,.90);
  backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);
  box-shadow:0 1px 0 var(--line);
}
.nav .wrap{display:flex;align-items:center;justify-content:space-between;gap:24px}

.langsw{position:relative;font-family:var(--serif);font-size:17px;font-weight:400;letter-spacing:.06em}
.langsw__cur{
  display:flex;align-items:center;gap:9px;padding:9px 4px;color:var(--ivory);
  transition:color .35s var(--ease),text-shadow .35s var(--ease);
}
.langsw__cur:hover,.langsw.open .langsw__cur{
  color:var(--honey);text-shadow:0 0 14px rgba(223,160,117,.55);
}
.langsw__cur svg{width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:1.8;transition:transform .35s var(--ease)}
.langsw.open .langsw__cur svg{transform:rotate(180deg)}
.langsw__list{
  position:absolute;top:calc(100% + 6px);left:50%;transform:translate(-50%,-6px);
  min-width:max(100%,86px);
  /* Lighter glass and a tighter box. brightness() inside backdrop-filter is
     what lets the tint stay this low: it takes the edge off whatever frame is
     behind before the tint goes over it, so the panel can read as barely
     there and the type still holds up against a bright photograph. */
  background:rgba(10,23,22,.32);
  backdrop-filter:blur(18px) saturate(150%) brightness(.6);
  -webkit-backdrop-filter:blur(18px) saturate(150%) brightness(.6);
  border:1px solid rgba(244,243,245,.18);border-radius:10px;
  padding:4px;overflow:hidden;
  box-shadow:0 12px 28px rgba(0,0,0,.26),
             inset 0 1px 0 rgba(255,255,255,.16);
  opacity:0;visibility:hidden;
  transition:opacity .3s var(--ease),transform .3s var(--ease),visibility .3s var(--ease);
}
/* without backdrop-filter the glass would be a transparent hole over the hero */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .langsw__list{background:rgba(10,23,22,.92)}
}
/* Safari and Chrome both have backdrop-filter, but if it is ever unavailable
   the glass would become a transparent hole over the photographs. */

.langsw.open .langsw__list{opacity:1;visibility:visible;transform:translate(-50%,0)}
.langsw__list button{
  display:block;width:100%;padding:8px 12px;border-radius:6px;
  color:var(--ivory);letter-spacing:.06em;text-align:center;
  font-family:var(--serif);font-size:17px;font-weight:400;
  transition:color .25s var(--ease),background .25s var(--ease);
}
.langsw__list button:hover{
  color:var(--honey);background:rgba(244,243,245,.14);
}

.nav__links{display:flex;align-items:center;gap:clamp(14px,1.9vw,32px);list-style:none}
.nav__links li{display:flex;justify-content:center}
.nav__links .nav__cta{display:none}   /* drawer only; must out-rank .nav__links li */
.nav__links a:not(.btn){
  font-family:var(--serif);letter-spacing:.02em;
  font-size:20px;font-weight:400;              /* default */
  color:var(--ivory);position:relative;padding:4px 0;white-space:nowrap;
  transition:font-size .55s var(--ease),color .45s var(--ease);
}
.nav__links a:not(.btn):hover{font-size:26px;font-weight:400}          /* hover  */
.nav__links a:not(.btn):active{font-size:26px;font-weight:500}         /* press  */
/* Invisible measuring state. JS renders each label at its largest
   (26px/500) and locks the <li> to that width, so growing on hover
   never shifts the neighbouring links. */
.nav__links a:not(.btn).measure{font-size:26px;font-weight:500;transition:none}
.nav__links a:not(.btn)::after{
  content:'';position:absolute;left:50%;bottom:0;width:0;height:1px;
  background:var(--honey);transition:all .4s var(--ease);transform:translateX(-50%);
}
.nav__links a:not(.btn):hover::after,.nav__links a:not(.btn):active::after{width:100%}

.burger{display:none;width:30px;height:20px;position:relative;z-index:1002}
/* The bars are 30x20 by design; the thing you tap should not be. An invisible
   ::after stretches the hit area to 54x44 without moving the bars — padding
   would have, because the three spans are absolutely positioned and resolve
   their top offsets against the padding box. This is the primary navigation
   control on a phone, so 20px of height was the worst target on the site. */
.burger::after{content:'';position:absolute;inset:-12px}
.burger span{position:absolute;left:0;width:100%;height:1.5px;background:var(--ivory);transition:.35s var(--ease)}
.burger span:nth-child(1){top:0}.burger span:nth-child(2){top:9px}.burger span:nth-child(3){top:18px}
.burger.on span:nth-child(1){top:9px;transform:rotate(45deg)}
.burger.on span:nth-child(2){opacity:0}
.burger.on span:nth-child(3){top:9px;transform:rotate(-45deg)}

/* Raised from 1100 to 1250: each <li> is locked to its 26px/500 hover width,
   so the Ukrainian row measures ~700px on its own and would otherwise run
   into the CTA. Went to 1340 while the menu briefly held seven links; back to
   1250 now that Відгуки has been removed and six remain. */
@media(max-width:1250px){
  .burger{display:block}
  .nav__links{
    position:fixed;inset:0;flex-direction:column;justify-content:center;gap:26px;
    /* the artwork is 1920x1896, close to square, so `cover` fills a portrait
       phone and a landscape tablet alike without stretching. The veil over it
       is what guarantees the links stay legible whichever part shows. */
    background:
      linear-gradient(rgba(10,23,22,.72),rgba(10,23,22,.86)),
      url('https://res.cloudinary.com/yfoyq1xk/image/upload/f_auto,q_auto/v1785867861/Green_Background_etmfiu.webp') center center/cover no-repeat,
      var(--f-900);
    opacity:0;visibility:hidden;transition:.45s var(--ease);
  }
  .nav__links.on{opacity:1;visibility:visible}
  /* width no longer needs reserving in a column, but height does: the link
     grows to 26px, so each row is sized for that up front and nothing below
     it moves when it does */
  .nav__links li{min-width:0!important;min-height:50px;align-items:center}
  .nav__links{overflow-y:auto}       /* short landscape screens */
  .nav__links a{line-height:1.25}        /* no width lock in the drawer */

  .nav .btn--nav{display:none}
  /* the drawer covers the whole header, so the one control that still has to
     be reachable while it is open is lifted above it */
  .langsw{z-index:1002}
  .nav__links .nav__cta{display:flex;margin-top:10px}
  .nav__cta .btn{width:244px;max-width:100%;height:48px;padding:0;font-size:17.5px}
}

/* ════════════════════════════════════════════════════════
   HERO
   LAYER 1  .hero__reel   3 photo columns — left ↑, centre ↓, right ↑
   LAYER 2  .hero__veil   stationary dark overlay
   LAYER 3  .hero__inner  stationary real HTML text + CTA
   ════════════════════════════════════════════════════════ */
.hero{position:relative;height:100svh;min-height:640px;overflow:hidden;background:var(--f-900)}

/* ── LAYER 1 — the only moving part ────────────────────── */
.hero__reel{
  --gap:10px;
  position:absolute;inset:0;z-index:1;
  display:flex;gap:var(--gap);
}
.hero__col{flex:1;overflow:hidden;min-width:0;position:relative}
.hero__track{
  display:flex;flex-direction:column;
  animation:reelY var(--dur,180s) linear infinite;
  /* held until the photos have arrived and JS knows the real track
     height — otherwise the clock runs against a collapsed track and
     the columns lurch when the images finally land */
  animation-play-state:paused;
  will-change:transform;
}
.hero__reel.ready .hero__track{animation-play-state:running}
/* travel exactly one copy: spacing lives on the cells (margin),
   never on the track (gap) — a gap would add one extra space
   between the two copies and the loop would drift */
/* The distance is an explicit pixel value, set by tuneReel() from the measured
   offset of one copy. It used to be -50%, which the browser resolves against
   the track's own border box — a different number from the one the duration was
   calculated against, so each column ran at its own slightly wrong speed and
   the centre one visibly outpaced the others. Distance and duration now come
   from the same measurement, so px/s is identical by construction.
   The 50% fallback keeps it sane if the JS has not run yet. */
@keyframes reelY{
  from{transform:translate3d(0,0,0)}
  to  {transform:translate3d(0,calc(var(--travel,50%) * -1),0)}
}
.hero__col--down .hero__track{animation-direction:reverse}
.hero__cell{
  flex:none;overflow:hidden;border-radius:var(--r);
  margin-bottom:var(--gap);
}
.hero__cell img{
  width:100%;height:auto;display:block;
  /* no filter — photographs are delivered as shot; the only
     darkening is the stationary overlay in layer 2 */
}
@media(prefers-reduced-motion:reduce){.hero__track{animation:none}}

/* ── LAYER 2 — stationary overlay, keeps the text readable ── */
.hero__veil{
  position:absolute;inset:0;z-index:2;pointer-events:none;
  background:rgba(0,0,0,var(--hero-overlay-opacity));
}

/* ── LAYER 3 — stationary content ──────────────────────── */
.hero__inner{
  position:relative;z-index:3;height:100%;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  text-align:center;padding:var(--nav-h) var(--gutter) clamp(80px,12vh,110px);
}
.hero h1{
  /* 80px Medium is the desktop spec — the clamp only steps down on
     screens too narrow to hold it, never above it */
  font-family:var(--serif);
  font-size:clamp(38px,7vw,80px);font-weight:500;line-height:1.08;
  text-wrap:balance;max-width:16ch;
  text-shadow:0 6px 48px rgba(0,0,0,.75);
  margin-bottom:40px;   /* to the CTA */
}
/* Fixed 260x48. Scoped to .hero so it outranks .btn--lg's own padding;
   padding:0 lets the flex centring place the label and icon dead centre.
   max-width guards against overflow on narrow phones. */
.hero .btn--lg{width:260px;height:48px;max-width:100%;padding:0;flex:none}

.scroll-cue{
  position:absolute;bottom:32px;left:50%;transform:translateX(-50%);z-index:4;
  display:flex;flex-direction:column;align-items:center;gap:10px;
  font-family:var(--sans);font-weight:400;
  font-size:11px;letter-spacing:.36em;text-transform:uppercase;
  color:var(--ivory);
  /* sits over whatever photo happens to be passing, so the shadow has to
     carry the contrast on its own — the overlay alone can't */
  text-shadow:0 1px 3px rgba(0,0,0,.95), 0 0 16px rgba(0,0,0,.8);
}
.scroll-cue i{display:block;width:1px;height:48px;background:linear-gradient(180deg,var(--honey),transparent);animation:cue 2.4s ease-in-out infinite;filter:drop-shadow(0 0 4px rgba(0,0,0,.85))}
@keyframes cue{0%,100%{opacity:.25;transform:scaleY(.5);transform-origin:top}50%{opacity:1;transform:scaleY(1);transform-origin:top}}

@media(max-width:900px){.hero__reel{--gap:8px}}
@media(max-width:640px){
  .hero__reel{--gap:6px}
  .hero__col:nth-child(3){display:none}   /* 2 columns keeps photos legible */
  .scroll-cue{display:none}
}

/* ── Stacked scroll (наїзд) ─────────────────────────────────
   Every panel is sticky, and each one drives up over the one before
   with a 48px rounded top edge.
   The stick point is NOT top:0 — that would pin a tall section by its
   top and bury everything below the fold. JS sets --stick per panel to
   min(0, viewportHeight - panelHeight), so a panel scrolls through
   completely and only then holds while the next rides over it. */
.stack{position:relative}
.stack__panel{
  /* Sections scroll normally. This was position:sticky with a JS-computed
     top, which pinned each one while the next slid over it — the effect that
     made ordinary scrolling feel unnatural. The clipping stays; it is visual,
     not scroll behaviour. */
  position:relative;
  /* the rounded top edge belonged to the stacking effect that was removed —
     with the sections scrolling normally it just showed the section behind
     through the corners */
  border-radius:0;
  overflow:hidden;
}
@media(prefers-reduced-motion:reduce){
}

/* ── reveal ────────────────────────────────────────────── */
.rv{opacity:0;transform:translateY(56px);transition:opacity 1.05s var(--ease),transform 1.05s var(--ease)}
.rv.in{opacity:1;transform:none}
.rv-d1{transition-delay:.10s}.rv-d2{transition-delay:.20s}.rv-d3{transition-delay:.30s}
.rv-d4{transition-delay:.40s}
.rv-d5{transition-delay:.50s}
.rv-d6{transition-delay:.60s}
@media(prefers-reduced-motion:reduce){.rv{opacity:1;transform:none}}

/* ════════════════════════════════════════════════════════
   ABOUT  — same system: forest panel, hairline frame
   ════════════════════════════════════════════════════════ */
.about__grid{display:grid;grid-template-columns:minmax(0,636px) 1fr;gap:clamp(38px,6vw,96px);align-items:center}
.about__ph{position:relative;border-radius:var(--r);overflow:hidden}
/* Static and unfiltered: the old default dulled it with
   grayscale(22%) brightness(.9), and only the hover state showed it
   clean. That clean state is now the default — no filter, no transform,
   no transition, and no overlay frame on top of it. */
.about__ph img{
  /* 636x525 — the source is 1024x845, the same ratio, so cover crops nothing */
  width:100%;aspect-ratio:636/525;object-fit:cover;border-radius:var(--r);
  display:block;
}
.about__body h2{text-align:left}
/* Scoped, not global: the same .hairline sits in Контакти and on the wedding
   page and keeps its honey there. Only Про мене drops the accent. */
.about__body .hairline{
  margin:26px 0 32px;
  background:linear-gradient(90deg,var(--ivory-22),transparent);
}
.about__body p{
  font-family:var(--sans);font-weight:var(--prose-weight);
  font-size:var(--prose-size);line-height:var(--prose-leading);
  color:var(--prose-ink);margin-bottom:18px;
}
/* The closing line was honey italic behind a honey rule, then medium weight.
   Both were tried and dropped: it is now simply the third paragraph, styled
   by .about__body p above and nothing else. No .hl rule and no .hl class in
   the markup — do not reintroduce one without a reason. */
.about__sign{
  display:flex;flex-wrap:wrap;gap:14px;align-items:center;margin-top:38px;
}
/* Fixed 260x48, matching the hero CTA. Scoped to .about__sign so it
   outranks .btn's own padding; flex:none stops the row squeezing it. */
.about__sign .btn{width:260px;height:48px;max-width:100%;padding:0;flex:none}
@media(max-width:940px){
  .about__grid{grid-template-columns:1fr}
  .about__body h2,.about__body .eyebrow{text-align:center;justify-content:center}
  .about__body .eyebrow--left::before{display:block}
  .about__body .hairline{margin:26px auto 32px}
  .about__sign{justify-content:center}
}

/* ════════════════════════════════════════════════════════
   TRUST / STATS
   ════════════════════════════════════════════════════════ */
/* 2 x 416 + 40 gap = 872px, centred. minmax lets them shrink below
   that rather than overflowing narrow screens. */
.stats__grid{
  display:grid;
  /* Each track used to be clamped to its own 416px maximum, so the two were
     sized independently and could settle on different widths once the grid
     had to give ground. Two equal fr tracks inside a fixed outer width are
     identical by definition — 2 x 416 plus the 40px gap. */
  grid-template-columns:repeat(2,minmax(0,1fr));
  max-width:872px;margin-inline:auto;
  gap:40px;justify-content:center;
  margin-bottom:clamp(42px,5vw,64px);
}
.stat{
  /* BorderGlow config, ported from the React component.
     JS writes --edge-proximity (0-100) and --cursor-angle on pointermove;
     every layer below is driven purely by those two values. */
  --edge-proximity:0;
  --cursor-angle:45deg;
  --edge-sensitivity:30;
  --color-sensitivity:calc(var(--edge-sensitivity) + 20);
  --glow-padding:40px;
  --cone-spread:25;
  --fill-opacity:.5;
  /* glowColor 40 80 80 */
  --glow-color:hsl(40deg 80% 80% / 100%);
  --glow-60:hsl(40deg 80% 80% / 60%);
  --glow-50:hsl(40deg 80% 80% / 50%);
  --glow-40:hsl(40deg 80% 80% / 40%);
  --glow-30:hsl(40deg 80% 80% / 30%);
  --glow-20:hsl(40deg 80% 80% / 20%);
  --glow-10:hsl(40deg 80% 80% / 10%);
  /* mesh colours: #112523, #214B45, #a0a1a1 */
  --g1:radial-gradient(at 80% 55%,#112523 0px,transparent 50%);
  --g2:radial-gradient(at 69% 34%,#214B45 0px,transparent 50%);
  --g3:radial-gradient(at 8% 6%,#a0a1a1 0px,transparent 50%);
  --g4:radial-gradient(at 41% 38%,#112523 0px,transparent 50%);
  --g5:radial-gradient(at 86% 85%,#214B45 0px,transparent 50%);
  --g6:radial-gradient(at 82% 18%,#a0a1a1 0px,transparent 50%);
  --g7:radial-gradient(at 51% 4%,#214B45 0px,transparent 50%);

  width:100%;height:232px;
  border-radius:6px;background:transparent;
  position:relative;isolation:isolate;transform:translate3d(0,0,.01px);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;
  text-align:center;
}
/* content sits above every glow layer */
.stat b,.stat > span{position:relative;z-index:3}

/* mesh fill, revealed in a cone pointing at the cursor */
.stat::after{
  content:'';position:absolute;inset:0;border-radius:inherit;
  pointer-events:none;z-index:0;
  background:var(--g1),var(--g2),var(--g3),var(--g4),var(--g5),var(--g6),var(--g7),
             linear-gradient(#112523 0 100%);
  -webkit-mask-image:conic-gradient(from var(--cursor-angle) at center,transparent 5%,#000 15%,#000 85%,transparent 95%);
          mask-image:conic-gradient(from var(--cursor-angle) at center,transparent 5%,#000 15%,#000 85%,transparent 95%);
  opacity:calc(var(--fill-opacity) * (var(--edge-proximity) - var(--color-sensitivity)) / (100 - var(--color-sensitivity)));
  mix-blend-mode:soft-light;
  transition:opacity .25s ease-out;
}
/* outer glow ring — the visible part of the effect */
.stat__glow{
  position:absolute;inset:calc(var(--glow-padding) * -1);
  border-radius:inherit;pointer-events:none;z-index:2;
  -webkit-mask-image:conic-gradient(from var(--cursor-angle) at center,#000 2.5%,transparent 10%,transparent 90%,#000 97.5%);
          mask-image:conic-gradient(from var(--cursor-angle) at center,#000 2.5%,transparent 10%,transparent 90%,#000 97.5%);
  opacity:calc((var(--edge-proximity) - var(--edge-sensitivity)) / (100 - var(--edge-sensitivity)));
  mix-blend-mode:plus-lighter;
  transition:opacity .25s ease-out;
}
.stat__glow::before{
  content:'';position:absolute;inset:var(--glow-padding);border-radius:6px;
  box-shadow:
    inset 0 0 0 1px var(--glow-color), inset 0 0 1px 0 var(--glow-60),
    inset 0 0 3px 0 var(--glow-50),    inset 0 0 6px 0 var(--glow-40),
    inset 0 0 15px 0 var(--glow-30),   inset 0 0 25px 2px var(--glow-20),
    inset 0 0 50px 2px var(--glow-10),
    0 0 1px 0 var(--glow-60), 0 0 3px 0 var(--glow-50), 0 0 6px 0 var(--glow-40),
    0 0 15px 0 var(--glow-30), 0 0 25px 2px var(--glow-20), 0 0 50px 2px var(--glow-10);
}
/* the section must not clip the 40px outer glow */
#trust .wrap,.stats__grid{overflow:visible}
/* Gradient stroke. border-image cannot follow a border-radius, so the ring
   is a masked pseudo-element instead: a 4px padding box with the middle
   punched out, which keeps the 6px corners. */
.stat::before{
  content:'';position:absolute;inset:0;border-radius:6px;padding:4px;
  background:linear-gradient(135deg,#FFFFFF 0%,#174C45 52%,#FFFFFF 100%);
  background-size:220% 220%;
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
          mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;
          mask-composite:exclude;
  animation:statStroke 5s ease-in-out infinite alternate;
  pointer-events:none;z-index:1;
}
/* 'alternate' rather than a one-way loop: the gradient tile cannot be
   made to repeat seamlessly at a fluid width, so easing it back and
   forth keeps the motion continuous with no jump at the wrap. */
@keyframes statStroke{
  from{background-position:0% 50%}
  to  {background-position:100% 50%}
}
.stat:nth-child(2)::before{animation-delay:1.2s}
.stat:nth-child(3)::before{animation-delay:2.4s}
.stat:nth-child(4)::before{animation-delay:3.6s}
@media(prefers-reduced-motion:reduce){.stat::before{animation:none}}
.stat b{font-family:var(--serif);font-size:80px;font-weight:500;line-height:1;color:var(--ivory)}
.stat span{
  font-family:var(--sans);font-weight:400;font-size:20px;
  letter-spacing:0;line-height:26px;color:var(--ivory);
}
@media(max-width:1000px){.stats__grid{gap:24px;max-width:856px}}
@media(max-width:860px){
  .stats__grid{grid-template-columns:minmax(0,1fr);max-width:416px}
}
@media(max-width:520px){
  .stat{height:196px}
  .stat b{font-size:60px}
  .stat span{font-size:17px}
}

/* ════════════════════════════════════════════════════════
   SERVICES
   ════════════════════════════════════════════════════════ */
/* 3 x 416px + 2 x 16px gap = 1280px, which is exactly the .wrap content
   width at the 1440px design size. Below 1440 the cards go fluid but keep
   the same 416:550 photo proportion. */
.svc-grid{
  display:grid;grid-template-columns:repeat(3,minmax(0,416px));
  gap:16px;justify-content:center;
}
.svc{
  height:800px;
  position:relative;border-radius:var(--r);overflow:hidden;
  border:1px solid var(--line);
  background:var(--f-700);
  display:flex;flex-direction:column;
  transition:transform .6s var(--ease),border-color .6s var(--ease),box-shadow .6s var(--ease);
}
.svc:hover{transform:translateY(-8px);border-color:var(--honey-40);box-shadow:0 26px 60px rgba(0,0,0,.5)}
.svc__ph{overflow:hidden;height:580px;flex:none;cursor:pointer}
.svc__ph:focus-visible{outline:var(--stroke) solid var(--honey);outline-offset:-3px}
.svc__ph img{
  width:100%;height:100%;object-fit:cover;
  filter:grayscale(24%) brightness(.82) contrast(1.06);
  transition:transform 1.3s var(--ease),filter .8s var(--ease);
}
.svc:hover .svc__ph img{transform:scale(1.08);filter:grayscale(0) brightness(1)}
/* Background lives on the body, not the whole card. On the card it scaled
   to 810x800 and only the bottom strip showed through under the photo —
   the dark edge of the file. Here the visible 416x250 panel gets its own
   centred cover crop, which is the bokeh at the middle of the image. */
.svc__body{
  flex:1;padding:26px 28px;text-align:center;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px;
  background:url('https://res.cloudinary.com/yfoyq1xk/image/upload/f_auto,q_auto:good/v1785855576/Green_Background_v6wox0.webp')
             center center/cover no-repeat, var(--f-700);
}
.svc__body h3{font-family:var(--serif);font-size:32px;font-weight:400;line-height:normal}
.svc__body .btn{width:244px;height:48px;max-width:100%;padding:0;flex:none}
.svc__body .link-arrow{margin:0 auto}

@media(max-width:1439px){
  .svc-grid{grid-template-columns:repeat(3,minmax(0,1fr))}
  .svc{height:auto}
  .svc__ph{height:auto;aspect-ratio:416/580}
  .svc__body h3{font-size:clamp(21px,2.3vw,32px)}
}
@media(max-width:960px){.svc-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:620px){.svc-grid{grid-template-columns:minmax(0,1fr)}}

/* ── service modal ─────────────────────────────────────────
   Every card is the same box: 1128x768 desktop, 288x415 mobile.
   Content length varies a lot between services (0-3 intro paragraphs,
   5-7 list items, price and note optional), so the panel scrolls inside
   the fixed box rather than the box growing to fit. */
.modal{
  position:fixed;inset:0;z-index:2000;display:flex;align-items:center;justify-content:center;
  padding:clamp(14px,4vw,44px);
  background:rgba(6,15,14,.86);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  opacity:0;visibility:hidden;transition:.45s var(--ease);
}
.modal.on{opacity:1;visibility:visible}
.modal__shell{
  display:flex;flex-direction:column;align-items:center;gap:16px;
  width:1128px;max-width:100%;max-height:100%;
}
.modal__box{
  width:100%;height:768px;max-height:100%;
  flex:0 1 auto;min-height:0;
  border-radius:var(--r);overflow:hidden;
  background:url('https://res.cloudinary.com/yfoyq1xk/image/upload/f_auto,q_auto:good/v1785855576/Green_Background_v6wox0.webp')
             center center/cover no-repeat, var(--f-800);
  border:1px solid var(--line-2);box-shadow:0 40px 110px rgba(0,0,0,.7);
  transform:translateY(26px) scale(.98);transition:transform .5s var(--ease);
  position:relative;
}
.modal.on .modal__box{transform:none}
.modal__x{
  position:absolute;top:16px;right:18px;z-index:3;
  width:38px;height:38px;display:grid;place-items:center;
  border-radius:50%;font-size:22px;line-height:1;color:#F7FAF7;
  background:linear-gradient(180deg,#2F665E 6%,#162B29 100%);border:none;
  box-shadow:0 10px 30px rgba(0,0,0,.5);
  transition:transform .35s var(--ease),filter .35s var(--ease),
             box-shadow .35s var(--ease);
}
.modal__x:hover{transform:rotate(90deg);filter:brightness(1.14);box-shadow:0 16px 36px rgba(0,0,0,.58)}
.modal__x:active{transform:rotate(90deg) scale(.93);filter:brightness(.94);box-shadow:0 6px 18px rgba(0,0,0,.5)}
.modal__x:focus-visible{outline:2px solid #F7FAF7;outline-offset:3px}

.modal__grid{display:grid;grid-template-columns:1fr 1fr;height:100%}
.modal__hero{overflow:hidden;height:100%}
.modal__hero img{width:100%;height:100%;object-fit:cover;display:block}

/* --fit is the shrink-to-fit factor. It stays 1 everywhere except the desktop
   overlay, where fitSvcBody() dials it down just far enough that the longest
   service (Love Story RU: 3 paragraphs + 6 packages + price + note) still shows
   its Замовити button without the user having to scroll. Every vertical value
   below is expressed in terms of it, so the whole block scales as one. */
.modal__body{
  --fit:1;
  height:100%;overflow:hidden;padding:calc(34px * var(--fit)) 40px;
  display:flex;flex-direction:column;align-items:center;
  justify-content:center;justify-content:safe center;   /* 'safe' keeps the top reachable when it overflows */
  text-align:center;
}
/* one type scale, one colour */
.modal__body h3{font-family:var(--serif);font-size:calc(29px * var(--fit));font-weight:500;line-height:1.15;
                color:var(--prose-ink);margin-bottom:calc(13px * var(--fit))}
.modal__lead{font-family:var(--sans);font-weight:300;font-size:calc(14px * var(--fit));line-height:1.55;
             color:var(--prose-ink);max-width:46ch}
.modal__lead+.modal__lead{margin-top:calc(7px * var(--fit))}
.modal__inc-title{font-family:var(--serif);font-size:calc(20px * var(--fit));font-weight:600;line-height:1.2;
                  color:var(--prose-ink);margin:calc(18px * var(--fit)) 0 calc(10px * var(--fit))}
.modal__inc{list-style:none;max-width:46ch}
.modal__inc li{font-family:var(--sans);font-weight:300;font-size:calc(14px * var(--fit));line-height:1.55;
               color:var(--prose-ink);margin-bottom:calc(5px * var(--fit))}
/* Dark plate, so the near-white tick. Centred lines like the wedding rows, so
   the same inline treatment; geometry comes from the shared rule above and is
   in em, which means it rides the --fit shrink and the 12.5px phone size
   without a rule of its own. */
.modal__inc li::before{
  display:inline-block;margin-right:.25em;vertical-align:-.25em;
  background-image:var(--tick-light);
}
.modal__bigprice{font-family:var(--serif);font-size:calc(33px * var(--fit));font-weight:700;line-height:1;
                 color:var(--prose-ink);margin:calc(16px * var(--fit)) 0 calc(4px * var(--fit));letter-spacing:.01em}
.modal__note{margin:calc(13px * var(--fit)) 0 0;max-width:44ch}
.modal__note b{display:block;font-family:var(--sans);font-weight:500;font-size:calc(13px * var(--fit));
               line-height:1.5;color:var(--prose-ink)}
.modal__note span{display:block;font-family:var(--sans);font-weight:300;font-size:calc(13px * var(--fit));
                  line-height:1.5;color:var(--prose-ink)}
.modal__cta{display:flex;flex-direction:column;align-items:center;
            gap:calc(14px * var(--fit));margin-top:calc(22px * var(--fit))}
.modal__cta .btn{width:244px;height:max(44px,calc(48px * var(--fit)));max-width:100%;padding:0;flex:none}
/* last resort: a window too short for even the smallest scale gets a scrollbar
   rather than a clipped button */
.modal__body.is-scrolling{overflow-y:auto}

/* On a short laptop screen the 44px frame and the 16px gap are the difference
   between the copy fitting at full size and having to be scaled down, so give
   the card back that room rather than spending it on margin. */
@media(max-height:900px){
  .modal{padding:16px clamp(14px,4vw,44px)}
  .modal__shell{gap:10px}
}

/* Outside the card — inside it, the rail sat on top of the secondary link. */
.modal__pager{flex:none}
/* nine dots is a wide rail — tighten it inside the modal */
.pager--compact{gap:14px}
.pager--compact .pager__arrow{width:32px;height:32px}
.pager--compact .pager__arrow svg{width:18px;height:18px}
.pager--compact .pager__dots{gap:8px}
.pager--compact .pager__dots i{width:10px;height:10px}
.pager--compact .pager__dots i.on{width:18px;height:18px}
.pager--compact .pager__bar{width:48px;height:1.4px}

@media(max-width:880px){
  .modal__box{width:100%;height:min(768px,100%)}
  .modal__grid{grid-template-columns:1fr;grid-template-rows:260px 1fr}
  .modal__hero{order:-1;height:260px}
}
/* 288x415 */
@media(max-width:620px){
  .modal{padding:12px}
  .modal__shell{width:288px;gap:12px}
  .modal__box{height:415px}
  .modal__grid{grid-template-rows:118px 1fr}
  .modal__hero{height:118px}
  .modal__body{padding:18px 16px;justify-content:flex-start}
  .modal__x{width:32px;height:32px;font-size:19px;top:10px;right:10px}
  .modal__body h3{font-size:19px;margin-bottom:10px}
  .modal__lead{font-size:12.5px;line-height:1.55}
  .modal__inc-title{font-size:15px;margin:16px 0 8px}
  .modal__inc li{font-size:12.5px;line-height:1.5;margin-bottom:4px}
  .modal__bigprice{font-size:24px;margin:16px 0 2px}
  .modal__note b,.modal__note span{font-size:11.5px}
  .modal__cta{margin-top:18px;gap:12px;width:100%}
  .modal__cta .btn{width:100%;height:44px}
  /* 288px cannot hold nine dots plus the bar */
  .pager--compact{gap:8px}
  .pager--compact .pager__arrow{width:26px;height:26px}
  .pager--compact .pager__dots{gap:6px}
  .pager--compact .pager__dots i{width:8px;height:8px}
  .pager--compact .pager__dots i.on{width:14px;height:14px}
  .pager--compact .pager__bar{display:none}
}

/* ════════════════════════════════════════════════════════
   PORTFOLIO
   ════════════════════════════════════════════════════════ */
/* The shared section-head gap is 46–78px, which is right when a heading is
   followed by a grid of photographs but leaves the category filters looking
   like a separate block rather than part of the header. Scoped to the
   portfolio so no other section moves. */
#portfolio .sec-head{margin-bottom:clamp(18px,2.2vw,28px)}

.filters{
  display:flex;flex-wrap:wrap;justify-content:center;align-items:center;
  gap:clamp(12px,1.8vw,32px);margin-bottom:clamp(34px,4vw,52px);
}
/* Text buttons, same language as the header nav: size marks the state,
   an underline sweeps in on hover. */
.filters button{
  font-family:var(--serif);font-size:20px;font-weight:400;letter-spacing:.02em;
  color:var(--ivory-80);          /* resting: quieter than the chosen one */background:none;border:none;padding:4px 0;
  position:relative;white-space:nowrap;line-height:1.1;
  transition:font-size .55s var(--ease),color .45s var(--ease);
}
.filters button::after{
  content:'';position:absolute;left:50%;bottom:0;width:0;height:1px;
  background:var(--honey);transform:translateX(-50%);
  transition:width .4s var(--ease);
}
.filters button:hover::after{width:100%}
/* The active filter used to jump to 48px, which made whichever word was
   chosen the largest thing in the section and re-wrapped the row on every
   change. Colour carries the state now, and the size shift is small enough
   that the row stays put. */
.filters button.on{font-size:28px;color:var(--honey)}
.filters button.on::after{width:100%;background:var(--honey)}
@media(max-width:900px){.filters button.on{font-size:25px}}
@media(max-width:560px){.filters button{font-size:16px}.filters button.on{font-size:20px}}

.gal{display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:12px;gap:14px}
.gal__item{position:relative;overflow:hidden;border-radius:var(--r);cursor:pointer}
.gal__item img{width:100%;height:100%;object-fit:cover;filter:grayscale(26%) brightness(.8) contrast(1.05);transition:transform 1.2s var(--ease),filter .7s var(--ease)}
.gal__item:hover img{transform:scale(1.06);filter:grayscale(0) brightness(1)}
.gal__item::before{
  content:'';position:absolute;inset:0;z-index:1;pointer-events:none;
  background:rgba(10,23,22,.34);opacity:0;transition:opacity .5s var(--ease);
}
.gal__item:hover::before{opacity:1}
.gal__item::after{
  content:attr(data-label);position:absolute;left:0;right:0;bottom:0;z-index:2;
  padding:30px 18px 18px;
  font-family:var(--sans);font-weight:400;font-size:13px;
  letter-spacing:.04em;text-transform:none;color:var(--ivory);
  text-shadow:0 1px 3px rgba(0,0,0,.95),0 0 16px rgba(0,0,0,.8);
  background:linear-gradient(0deg,rgba(10,23,22,.97) 0%,rgba(10,23,22,.72) 45%,transparent 100%);
  opacity:0;transform:translateY(10px);transition:.5s var(--ease);
}
.gal__item:hover::after{opacity:1;transform:none}
.gal__item.hide{display:none}

/* Overview: three hand-placed columns. Each cover is pinned to a column and
   given an explicit order, so the stack is exactly the one that was agreed
   rather than whatever auto-placement decides. Because rows are 12px, two
   covers in the same column simply sit one under the other and the three
   columns end at different depths. !important is needed on grid-row: the
   four-column span is an inline style and would otherwise win. */
.gal--overview{
  grid-template-columns:repeat(4,1fr);
  /* dense is load-bearing: tiles carry a span, not a fixed column, and the
     default sparse cursor would leave each one below the last instead of
     filling the row beside it. */
  grid-auto-flow:row dense;
}
.gal--overview .gal__item{
  grid-column:span var(--ocols);
  grid-row:span var(--ospan) !important;   /* the 4-column span is inline */
  order:var(--oord);
}
/* narrow screens get proportional heights and no hand placement — three
   pinned columns would be unreadable at phone width */

/* in the overview every tile is a category, so its name is not a hover
   detail any more — it has to be readable before the click */
/* Seven covers cannot fill a rectangle, so the eighth cell earns its keep
   instead of sitting empty. Two columns wide, one tile tall. */
.gal__cta{display:none}
.gal--overview .gal__cta{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:18px;text-align:center;padding:28px;
  grid-column:span 1;grid-row:span 17;order:8;
  border:1px solid var(--line);border-radius:var(--r);
  overflow:hidden;position:relative;
  /* the artwork is 416x580 (1:1.394) and the cell is 310x428 (1:1.38), so it
     lands almost exactly — no meaningful crop. The gradient is what keeps the
     type legible: it has to hold over whatever part of the photo shows. */
  background:
    linear-gradient(180deg,rgba(10,23,22,.55) 0%,rgba(10,23,22,.82) 100%),
    url('https://res.cloudinary.com/yfoyq1xk/image/upload/f_auto,q_auto/v1785873061/%D0%94%D0%BE%D0%B4%D0%B0%D1%82%D0%BA%D0%BE%D0%B2%D1%96_%D0%BF%D0%BE%D1%81%D0%BB%D1%83%D0%B3%D0%B8_z86dw1.webp') center center/cover no-repeat,
    var(--f-800);
}
.gal--overview .gal__cta p,
.gal--overview .gal__cta .link-arrow{text-shadow:0 1px 3px rgba(0,0,0,.6)}
.gal--overview .gal__cta p{
  font-family:var(--serif);font-weight:400;
  font-size:clamp(22px,2.2vw,32px);line-height:1.25;color:var(--ivory);margin:0;
}

@media(max-width:960px){
  .gal--overview{grid-template-columns:repeat(2,1fr);grid-auto-rows:auto}
  .gal--overview .gal__item{grid-column:span 1;grid-row:auto !important;order:0}
  .gal--overview .gal__item img{height:auto}
  .gal--overview .gal__cta{grid-column:span 1;grid-row:auto;min-height:260px}
}
@media(max-width:540px){.gal--overview{grid-template-columns:1fr}}

/* In the overview each tile IS a category, so its name is the point of the
   tile — not a caption. It moves from the bottom-left corner to the centre and
   takes the same treatment as the Готові створити свою історію tile beside it:
   EB Garamond at the same size over the same gradient. All eight cells then
   read as one set, and the name is legible before any hover. */
.gal--overview .gal__item::after{
  inset:0;                          /* fill the tile instead of hugging the base */
  display:grid;place-items:center;
  padding:24px;
  font-family:var(--serif);font-weight:400;
  /* --lbl comes from the tile's inline style: 1 for the single-width tiles,
     2 on Весілля and 1.2 on Індивідуальна, which are twice as wide and were
     making the same size look small */
  font-size:calc(clamp(22px,2.2vw,32px) * var(--lbl,1));line-height:1.25;
  letter-spacing:.01em;text-align:center;
  color:var(--ivory);
  /* A 5% wash leaves the photograph essentially untouched, which is the point
     — but it also does nothing for legibility, so the type has to carry its own
     contrast. Same approach as the hero headline: a dark halo in three layers
     rather than a scrim that would flatten the picture. */
  background:rgba(62,60,60,.05);
  text-shadow:0 2px 22px rgba(10,23,22,.92),
              0 1px 6px  rgba(10,23,22,.85),
              0 0 2px    rgba(10,23,22,.65);
  opacity:1;transform:none;
}
/* the scrim above already carries the legibility, so the hover darkening only
   needs to be a hint — at full strength the two together went muddy */
.gal--overview .gal__item::before{opacity:0}
.gal--overview .gal__item:hover::before{opacity:.5}
.gal-more{display:flex;justify-content:center;margin-top:clamp(28px,3.5vw,46px)}
.svc-more{display:none}        /* desktop shows every service at once */
.gal-more.hide{display:none}
.gal-more .btn{width:244px;height:48px;max-width:100%;padding:0}
@media(max-width:960px){.gal{grid-template-columns:repeat(2,1fr)}}
@media(max-width:540px){.gal{grid-template-columns:1fr}}

.lb{
  position:fixed;inset:0;z-index:2100;background:rgba(6,15,14,.96);backdrop-filter:blur(12px);
  display:flex;align-items:center;justify-content:center;padding:0;
  opacity:0;visibility:hidden;transition:.45s var(--ease);
}
.lb.on{opacity:1;visibility:visible}
/* fill the viewport instead of sitting at the file's native size —
   max-width/max-height alone only ever shrink, never enlarge */
.lb img{
  width:100%;height:100%;max-width:none;max-height:none;
  object-fit:contain;display:block;
}
.lb__x{
  position:absolute;top:clamp(14px,2vw,26px);right:clamp(14px,2vw,26px);z-index:4;
  width:40px;height:40px;display:grid;place-items:center;
  /* same skin as the back-to-top control: circle, forest gradient, ivory glyph */
  border-radius:50%;font-size:26px;line-height:1;color:#F7FAF7;
  background:linear-gradient(180deg,#2F665E 6%,#162B29 100%);border:none;
  box-shadow:0 10px 30px rgba(0,0,0,.5);
  transition:transform .35s var(--ease),filter .35s var(--ease),
             box-shadow .35s var(--ease);
}
/* the quarter-turn is the original close animation and is deliberately kept */
.lb__x:hover{transform:rotate(90deg);filter:brightness(1.14);box-shadow:0 16px 36px rgba(0,0,0,.58)}
.lb__x:active{transform:rotate(90deg) scale(.93);filter:brightness(.94);box-shadow:0 6px 18px rgba(0,0,0,.5)}
.lb__x:focus-visible{outline:2px solid #F7FAF7;outline-offset:3px}

.lb .pager{
  position:absolute;left:0;right:0;bottom:clamp(16px,3vh,34px);z-index:4;
  padding:0 clamp(12px,3vw,40px);
}

/* ════════════════════════════════════════════════════════
   REVIEWS  —  with client photos
   ════════════════════════════════════════════════════════ */
.rev-view{
  /* Geometry lives here, on the fixed-height wrapper, so the cards can
     inherit it AND the wrapper can size itself from --h-centre. */
  --rev-gap:22px;
  --w-side:639px;  --h-side:378px;
  --w-centre:731px;--h-centre:480px;
  overflow:hidden;padding:14px 0;
  /* Locked to the centred card. Without this the wrapper tracks the
     tallest child, and mid-transition both cards sit at an intermediate
     height — so the section, and the green behind it, would breathe. */
  height:calc(var(--h-centre) + 28px);
}
.rev-track.no-anim,.rev-track.no-anim .rev{transition:none!important}
.rev-track{
  display:flex;align-items:center;gap:var(--rev-gap);height:100%;
  transition:transform .8s var(--ease);will-change:transform;
}
.rev{
  flex:0 0 var(--w-side);width:var(--w-side);height:var(--h-side);
  display:grid;grid-template-columns:1fr 1fr;
  border-radius:6px;overflow:hidden;
  background:var(--rev-panel);
  box-shadow:0 14px 40px rgba(0,0,0,.32);
  opacity:.62;
  transition:flex-basis .8s var(--ease),width .8s var(--ease),height .8s var(--ease),
             opacity .8s var(--ease),box-shadow .8s var(--ease);
}
.rev.is-centre{
  flex:0 0 var(--w-centre);width:var(--w-centre);height:var(--h-centre);
  opacity:1;box-shadow:0 26px 64px rgba(0,0,0,.5);
}
.rev__ph{overflow:hidden;height:100%}
.rev__ph img{width:100%;height:100%;object-fit:cover;display:block;transition:transform 1.4s var(--ease)}
.rev:hover .rev__ph img{transform:scale(1.04)}
/* the side cards are clickable; the centre one is already where it belongs */
.rev{cursor:pointer}
.rev.is-centre{cursor:default}
@media(hover:hover){
  .rev:not(.is-centre):hover{opacity:.85}
}
.rev:not(.is-centre):focus-visible{outline:2px solid var(--honey);outline-offset:4px}
.rev__body{
  background:var(--rev-panel);color:var(--rev-ink);
  padding:clamp(18px,2vw,34px) clamp(16px,1.6vw,28px);
  display:flex;flex-direction:column;align-items:center;
  /* `safe` matters here. With plain centring, a quote taller than the panel
     overflows BOTH ends, so its first lines are pushed up out of the padding
     box and the panel reads as though its background stopped short. `safe`
     falls back to top alignment the moment the content stops fitting, which
     keeps every line inside the panel. */
  justify-content:center;justify-content:safe center;
  gap:14px;text-align:center;overflow:hidden;
  min-width:0;min-height:0;          /* never outgrow the grid cell */
}
/* nothing inside may be wider than the panel it sits in */
.rev__body > *{max-width:100%}
.rev__tag{font-family:var(--serif);font-size:clamp(13px,1.1vw,16px);font-weight:600;color:var(--rev-ink)}
.rev__quote{font-family:var(--sans);font-style:italic;font-size:clamp(12.5px,1.05vw,15px);line-height:1.6;color:var(--rev-ink-2)}
.rev__by{font-family:var(--sans);font-style:italic;font-size:clamp(12px,1vw,14px);color:var(--rev-ink-3)}

/* Sizes stay in px on purpose: placeRev() reads these with parseFloat, and an
   unregistered custom property computes to its own token — a vw value would
   come back as a bare number and the track would be positioned nonsensically.
   The steps below are chosen so the widest card still leaves a sliver of its
   neighbours showing at each common device width, while the panel is deep
   enough for the longest review (about 300 characters). */
@media(max-width:1240px){.rev-view{--w-side:560px;--h-side:392px;--w-centre:680px;--h-centre:446px}}
@media(max-width:900px){.rev-view{--w-side:420px;--h-side:388px;--w-centre:520px;--h-centre:440px;--rev-gap:16px}}
@media(max-width:620px){
  .rev-view{--w-side:340px;--h-side:690px;--w-centre:430px;--h-centre:730px;--rev-gap:12px}
  /* Stacked. The photo row was a share of the card height, which made it a
     landscape slot — and every one of these photographs is portrait, so cover
     kept only the middle band of each. It now takes its own shape and the
     text takes what is left. 8:9 matches five of the six exactly, so those
     are shown whole. */
  .rev{grid-template-columns:1fr;grid-template-rows:auto 1fr}
  /* Stated explicitly rather than left to the card's own clipping: rounded
     where the photo meets the top of the card, square where it meets the
     text panel below it. */
  .rev__ph{
    height:auto;aspect-ratio:8/9;
    border-radius:6px 6px 0 0;
  }
  .rev__ph img{border-radius:inherit}
}
@media(max-width:520px){
  .rev-view{--w-side:300px;--h-side:650px;--w-centre:380px;--h-centre:690px}
}
@media(max-width:430px){
  .rev-view{--w-side:240px;--h-side:600px;--w-centre:300px;--h-centre:640px}
}
@media(max-width:360px){
  /* 280px of content at 320px wide: the card fills nearly all of it, and the
     panel grows taller to keep the longest review inside */
  .rev-view{--w-side:190px;--h-side:580px;--w-centre:240px;--h-centre:620px}
}

/* ── shared pager: ‹  ○ ○ ● ——— ○ ○  › ─────────────────────
   The bar always sits next to the active dot, on the side that still
   has slides left, so it reads as "more this way". */
.pager{display:flex;align-items:center;justify-content:center;gap:clamp(14px,1.6vw,26px)}
.pager__arrow{
  width:38px;height:38px;display:grid;place-items:center;
  color:var(--ivory);opacity:.85;
  transition:opacity .3s var(--ease),transform .3s var(--ease);
}
.pager__arrow:hover{opacity:1;transform:scale(1.12)}
.pager__arrow:disabled{opacity:.28;cursor:not-allowed;transform:none}
.pager__arrow svg{width:22px;height:22px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.pager__dots{display:flex;align-items:center;gap:clamp(10px,1.1vw,18px)}
.pager__dots i{
  width:14px;height:14px;border-radius:50%;flex:none;cursor:pointer;
  border:1.4px solid rgba(244,243,245,.5);background:transparent;
  transition:width .45s var(--ease),height .45s var(--ease),background .45s var(--ease),border-color .45s var(--ease);
}
.pager__dots i:hover{border-color:var(--ivory)}
.pager__dots i.on{width:26px;height:26px;background:var(--ivory);border-color:var(--ivory);cursor:default}
.pager__bar{
  height:1.6px;background:var(--ivory);flex:none;
  width:clamp(56px,8vw,124px);
  transition:width .45s var(--ease);
}
/* The arrows sit on the carousel itself, pinned just inside the left and
   right edges of the centre card. --w-centre is a px value, so the offset can
   be worked out in CSS rather than measured in JS. */
.rev-view{position:relative}
.rev-arrow{
  position:absolute;top:50%;transform:translateY(-50%);z-index:5;
  width:44px;height:44px;border-radius:50%;
  display:grid;place-items:center;cursor:pointer;
  background:rgba(10,23,22,.55);border:1px solid rgba(244,243,245,.22);
  color:var(--ivory);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
  transition:background .3s var(--ease),transform .3s var(--ease),opacity .3s var(--ease);
}
.rev-arrow svg{width:20px;height:20px;stroke:currentColor;fill:none;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round}
.rev-arrow--prev{left:calc(50% - var(--w-centre) / 2 + 10px)}
.rev-arrow--next{right:calc(50% - var(--w-centre) / 2 + 10px)}
@media(hover:hover){
  .rev-arrow:hover{background:rgba(10,23,22,.78);transform:translateY(-50%) scale(1.08)}
}
.rev-arrow:active{transform:translateY(-50%) scale(.94)}

/* ════════════════════════════════════════════════════════
   CONTACT  —  light section
   Every foreground flips: the ivory type used everywhere else
   measures ~1:1 on this near-white plate.
   ════════════════════════════════════════════════════════ */
#contact{
  background:url('https://res.cloudinary.com/yfoyq1xk/image/upload/f_auto,q_auto:good/v1785921787/Background_for_contacts_cyjubm.webp')
             center center/cover no-repeat, #F2F3F1;
}
#contact::before{display:none}          /* the green bloom would tint the plate */
/* Same fix as Про мене: content height left the footer showing before any scroll. */
#contact{min-height:100svh;display:flex;align-items:center}
#contact > .wrap{width:100%}
/* On a phone the section is far taller than it is wide, so `cover` scales the
   1440x681 plate by its height and crops out a strip of the source only about
   320px wide. Centred, that strip runs to source x~877 — and the camera strap
   begins at x~715, which is the dark sliver hard against the right edge. It
   reads as a smudge, not a photograph. Sliding the window to 22% puts its
   right edge between 560px and 655px across every phone size from 320x568 to
   430x932, so only bare concrete shows. Horizontal only; vertical stays
   centred. */
@media(max-width:760px){
  #contact{background-position:22% center}
}

.contact__grid{
  display:grid;grid-template-columns:1.1fr .9fr;
  gap:clamp(34px,5vw,80px);align-items:center;
}

/* ── left: the details ──────────────────────────────────── */
.contact__info{text-align:center}
.contact__info h2{text-align:center;font-family:var(--serif);font-size:clamp(34px,6vw,48px);font-weight:400;
  background:linear-gradient(90deg,#112523 35%,#214B45 75%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent;}
.contact__info .hairline{
  margin:22px auto 26px;
  background:linear-gradient(90deg,transparent,rgba(18,33,31,.45),transparent);
}
.contact__info .lede{
  font-family:var(--sans);font-weight:400;
  font-size:16px;line-height:1.6;color:#0D0D0D;
  margin:0 auto 30px;max-width:56ch;
}
.contact__info .lede--intro{margin-bottom:14px}
.contact__name{
  font-family:var(--serif);font-size:clamp(24px,4vw,32px);font-weight:400;line-height:normal;
  margin-bottom:24px;
  background:linear-gradient(90deg,#112523 35%,#214B45 75%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent;
}
.cinfo{
  display:flex;flex-wrap:nowrap;justify-content:center;align-items:flex-start;
  gap:14px 28px;
}
/* below this the three items plus icons cannot sit on one line at 16px */
/* The single-line contact row is 642px wide with the current address —
   it only clears column 1 from about 1400px up, so it wraps below that
   rather than spilling out of its column. Raised from 1200px when the
   address grew by nine characters. */
@media(max-width:1400px){.cinfo{flex-wrap:wrap;gap:14px 34px}}
/* A bare `.cinfo span` also matched the nested language spans and, at
   (0,1,1), outranked [data-lang]{display:none} — so all four languages
   rendered at once. Scoping to a class keeps the hide rule in charge. */
.cinfo__item{
  display:inline-flex;align-items:flex-start;gap:8px;
  /* nowrap on the ROW alone was not enough — the text inside each item
     still wrapped, and flex items shrink by default */
  flex:none;white-space:nowrap;
  font-family:var(--sans);font-weight:400;font-size:16px;line-height:1.5;
  color:#0D0D0D;transition:color .3s var(--ease);
}
.cinfo__stack a{white-space:nowrap}
a.cinfo__item:hover,.cinfo__item a:hover{color:var(--f-400)}
.cinfo__item a{color:inherit}
.cinfo__item img{width:18px;height:18px;flex:none;margin-top:2px}
.cinfo__stack{display:flex;flex-direction:column;gap:4px}

.socials{display:flex;gap:40px;justify-content:center;margin-top:30px}
.socials a{
  width:32px;height:32px;display:grid;place-items:center;
  border:none;background:none;border-radius:0;
  transition:transform .35s var(--ease),opacity .35s var(--ease);
}
.socials a:hover{transform:translateY(-3px);opacity:.75;background:none}
.socials img{width:100%;height:100%;object-fit:contain}

/* ── right: the form ────────────────────────────────────── */
.form{
  display:flex;flex-direction:column;gap:26px;
  border:none;background:none;padding:0;
  width:100%;
}
/* the form and the confirmation share one grid cell, so neither one
   re-flows the two-column layout when they swap */
.form-cell{max-width:520px;width:100%;justify-self:end}

.sent{
  display:none;flex-direction:column;align-items:center;text-align:center;
}
.sent.on{display:flex}
.sent__img{width:200px;max-width:100%;height:auto;aspect-ratio:1;object-fit:contain;margin-bottom:32px}
.sent__text{
  font-family:var(--serif);font-weight:400;font-size:20px;line-height:1.5;
  color:#0D0D0D;margin:0 0 32px;
}
.sent .btn{
  width:244px;max-width:100%;height:48px;padding:0;margin-bottom:32px;
  background:var(--grad-radial);border-color:transparent;color:#F7FAF7;
}
/* same component as "Дивитись роботи", only the ink changes */
.link-arrow--ink{color:#0D0D0D}
.link-arrow--ink::after{background:#0D0D0D}
.sent__back img{width:32px;height:32px;flex:none;transition:transform .4s var(--ease)}
.sent__back:hover img{transform:translateX(-3px)}

@media(max-width:520px){
  .sent__img{width:150px;height:150px}
  .sent__text{font-size:18px}
}
.field{position:relative;display:flex;flex-direction:column;gap:8px}
.field label{
  font-family:var(--sans);font-weight:400;font-size:16px;letter-spacing:0;
  text-transform:none;color:#0D0D0D;margin:0;transition:color .3s var(--ease);
}
.field input,.field textarea{
  width:100%;background:none;border:none;border-bottom:1px solid rgba(18,33,31,.45);
  border-radius:0;padding:6px 2px;height:33px;
  font-family:var(--sans);font-weight:400;font-size:14px;color:#0D0D0D;
  transition:border-color .35s var(--ease),height .3s var(--ease),padding .3s var(--ease);
}
/* Empty and unfocused, the control folds down onto its own underline, so the
   label sits exactly 8px above the line — that 8px is .field's own gap.
   It opens back up the moment there is a value or the caret lands in it.
   :placeholder-shown is what detects "empty", hence placeholder=" " in the
   markup. The two dropdowns are deliberately excluded: their placeholder
   text is visible, so they must keep their height. */
.field input:placeholder-shown:not(:focus),
.field textarea:placeholder-shown:not(:focus){
  height:1px;min-height:0;padding-top:0;padding-bottom:0;
}
/* ...but not on a touch screen. The fold is a pointer-device nicety: with a
   mouse you aim at the label, or anywhere on the line, and it opens. On a
   phone there is no hover, and an empty field collapses to a 1px strip — a
   target you cannot reliably hit, on the one form the site has. Tapping the
   label still works, but the control itself has to be reachable too.
   hover:none rather than a width query: it is the input method that matters,
   not the size of the screen. */
@media(hover:none){
  .field input:placeholder-shown:not(:focus){height:33px;padding:6px 2px}
  .field textarea:placeholder-shown:not(:focus){height:auto;min-height:34px;padding:6px 2px}
}
.field textarea{resize:vertical;min-height:34px}
.field input::placeholder,.field textarea::placeholder{color:rgba(18,33,31,.4)}
.field input:focus,.field textarea:focus{outline:none;border-bottom-color:var(--f-400)}

/* custom dropdown — a native <select> cannot carry the radial fill */
.sel{position:relative}
.sel__btn{
  width:100%;display:flex;align-items:center;justify-content:space-between;gap:10px;
  background:none;border:none;border-bottom:1px solid rgba(18,33,31,.45);
  padding:6px 2px;cursor:pointer;text-align:left;min-width:0;
  font-family:var(--sans);font-weight:400;font-size:14px;color:#0D0D0D;
  transition:border-color .35s var(--ease);
}
.sel__btn:focus-visible{outline:none;border-bottom-color:var(--f-400)}
.sel__btn svg{width:14px;height:14px;stroke:var(--ink-1);fill:none;stroke-width:1.8;
              flex:none;transition:transform .3s var(--ease)}
.sel.open .sel__btn svg{transform:rotate(180deg)}
.sel__panel{
  position:absolute;left:0;right:0;top:calc(100% + 10px);z-index:20;
  background:#FBFBFA;border-radius:10px;
  box-shadow:0 18px 44px rgba(18,33,31,.22);
  padding:6px;max-height:288px;overflow-y:auto;   /* the service list runs to ten */
  opacity:0;visibility:hidden;transform:translateY(-8px);
  transition:opacity .3s var(--ease),transform .3s var(--ease),visibility .3s var(--ease);
}
.sel.open .sel__panel{opacity:1;visibility:visible;transform:none}
.sel__opt{
  display:block;width:100%;padding:13px 12px;border:none;border-radius:6px;
  background:none;cursor:pointer;text-align:center;
  font-family:var(--sans);font-weight:400;font-size:14px;color:#0D0D0D;
  transition:color .25s var(--ease);
}
.sel__opt:hover,.sel__opt.on{background:var(--grad-radial);color:#F7FAF7}

.consent{
  display:flex;gap:11px;align-items:flex-start;
  font-family:var(--sans);font-weight:400;font-size:14px;line-height:1.5;color:#0D0D0D;
  transition:color .3s var(--ease);
}
.consent input{
  appearance:none;-webkit-appearance:none;margin-top:1px;flex:none;
  width:15px;height:15px;border-radius:50%;border:1.5px solid rgba(18,33,31,.5);
  cursor:pointer;transition:.3s var(--ease);
  position:relative;
}
/* Same trick as the burger. The dot stays 15px — it is a design element — but
   the tap area becomes 39x39. It matters here more than anywhere: consent is
   required to submit, and the rest of the label cannot stand in for it,
   because the label contains the privacy-policy link and a tap there
   navigates away instead of ticking the box. */
.consent input::after{content:'';position:absolute;inset:-12px;border-radius:50%}
.consent input:checked{background:var(--grad-radial);border-color:transparent}
.consent a{color:var(--ink-1);text-decoration:underline}

.form .btn{
  align-self:center;width:244px;max-width:100%;height:48px;padding:0;
  background:var(--grad-radial);border-color:transparent;color:#F7FAF7;
}
.form .btn::before{background:var(--grad-radial);filter:brightness(1.18)}
.form .btn:hover{color:#F7FAF7;border-color:transparent}

.form-msg{
  font-family:var(--sans);font-size:13px;padding:11px 15px;border-radius:var(--r);display:none;
}

/* ── inline validation ──────────────────────────────────
   Only ever applied by JS on submit, and removed the moment
   the field is fixed. Nothing is red on first paint. */
.field__err{
  /* laid out at all times and only made visible on error — the text is
     already in flow, so the height is identical either way and the block
     never jumps when validation fires */
  visibility:hidden;margin:0;
  font-family:var(--sans);font-style:italic;font-weight:400;font-size:13px;
  line-height:1.4;color:var(--err);
}
.is-err > .field__err{visibility:visible}
.field.is-err label{color:var(--err)}
.field.is-err input,
.field.is-err textarea,
.field.is-err .sel__btn{border-bottom-color:var(--err)}
.field.is-err .sel__btn,
.field.is-err .sel__value{color:var(--err)}
.field.is-err .sel__btn svg{stroke:var(--err)}
.consent-wrap{display:flex;flex-direction:column;gap:8px}
.consent-wrap.is-err .consent,
.consent-wrap.is-err .consent a{color:var(--err);text-decoration-color:var(--err)}
.consent-wrap.is-err .consent input{border-color:var(--err)}
.sel__value.is-ph{color:rgba(18,33,31,.5)}   /* nothing chosen yet */
.form-msg.ok{display:block;background:rgba(47,102,94,.14);color:var(--f-500);border-left:2px solid var(--f-400)}
.form-msg.err{display:block;background:rgba(217,99,79,.12);color:#9B2F1C;border-left:2px solid var(--red)}

@media(max-width:940px){
  .contact__grid{grid-template-columns:1fr}
  /* the cell, not the form, carries the width and alignment now — the old
     rule stopped biting when .form-cell was introduced, leaving the form
     pinned to the right edge of a single-column grid */
  .form-cell{justify-self:center;margin-inline:auto}
}

/* ════════════════════════════════════════════════════════
   FOOTER
   ════════════════════════════════════════════════════════ */
.footer{
  /* the supplied artwork is 1440x416 (~3.46:1); the footer sits at a very
     similar ratio on desktop, so `cover` barely crops it */
  background:url('https://res.cloudinary.com/yfoyq1xk/image/upload/f_auto,q_auto/v1785926099/Footer_background_m4azfj.webp') center center/cover no-repeat,var(--f-900);
  /* Every gap below is measured, so the footer is content-height: the
     numbers add up to ~390px at 1440. Forcing 416px would have to put the
     26px of slack into one of the gaps and break it. */
  padding:clamp(56px,7vw,100px) 0 24px;    /* 100px above the title, 24px below the credit */
  text-align:center;position:relative;
}
.footer .wrap{position:relative;z-index:1}

/* The wordmark, not a section heading. As an <h3> it sat in the document
   outline directly under the <h1> on pages with no <h2> — portfolio jumped
   h1 -> h3 — and it heads nothing: the nav and socials below it are their own
   landmarks. A <p> keeps the type identical and takes it out of the outline. */
.footer__name{
  font-family:var(--serif);font-weight:400;
  font-size:clamp(28px,5.4vw,52px);        /* 52px from 963px up */
  line-height:1.15;letter-spacing:0;color:#F7FAF7;
  margin:0 0 32px;
}

.footer nav{
  display:flex;flex-wrap:wrap;justify-content:center;
  /* 12px is the phone value, where the row wraps and the links are smaller.
     16px on desktop, asked for once Політика конфіденційності joined the row
     and made it long enough that the items read as one word. */
  gap:12px;margin-bottom:32px;            /* 32px down to the icons */
}
@media(min-width:761px){.footer nav{gap:24px}}
.footer nav a{
  font-family:var(--serif);font-weight:400;font-size:20px;line-height:1.3;
  color:#F7F5F5;text-decoration:none;
  transition:color .3s var(--ease);
}
/* hover adds an underline and nothing else; guarded so a tap on a phone
   does not leave the link stuck in its hover state */
@media(hover:hover){
  .footer nav a:hover{
    color:#F7F5F5;
    text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:6px;
  }
}
.footer nav a:active{color:#F7FAF7;text-decoration:none}

.footer .socials{display:flex;justify-content:center;gap:40px;margin:0 0 62px}
.footer .socials a{
  width:32px;height:32px;display:grid;place-items:center;
  border:none;background:none;
  transition:transform .3s var(--ease),opacity .3s var(--ease);
}
.footer .socials a:hover{transform:translateY(-3px);opacity:.78;background:none}
.footer .socials a:active{transform:translateY(0) scale(.9);opacity:.6;transition-duration:.12s}
.footer .socials a:focus-visible{outline:1.5px solid #F7FAF7;outline-offset:6px;border-radius:50%}
.footer .socials img{width:100%;height:100%;object-fit:contain;display:block}

.footer .credit{
  margin:0;padding:0;border:none;
  font-family:var(--serif);font-weight:400;font-size:14px;letter-spacing:0;color:#FBF9F9;
}
.footer .credit a{color:inherit;text-decoration:none;transition:opacity .3s var(--ease)}
@media(hover:hover){.footer .credit a:hover{text-decoration:underline;text-underline-offset:4px}}
.footer .credit a:active{opacity:.7}

@media(max-width:760px){
  .footer nav a{font-size:17px}
  .footer .socials{gap:28px}
}
@media(max-width:420px){
  .footer nav a{font-size:16px}
  .footer .socials{gap:24px}
}
/* ClickSpark: one canvas over the whole viewport. In React the component
   wraps its children in a relative box; here it wraps the page, so fixed
   + inset:0 is the equivalent. */
#clickSpark{
  position:fixed;inset:0;width:100%;height:100%;display:block;
  pointer-events:none;user-select:none;z-index:9999;
}
.totop{
  position:fixed;right:22px;bottom:22px;z-index:900;
  width:46px;height:46px;display:grid;place-items:center;
  border-radius:50%;                       /* circle, per the reference */
  background:linear-gradient(180deg,#2F665E 6%,#162B29 100%);
  color:#F7FAF7;border:none;cursor:pointer;
  opacity:0;visibility:hidden;transform:translateY(12px);
  box-shadow:0 10px 30px rgba(0,0,0,.5);
  transition:opacity .45s var(--ease),visibility .45s var(--ease),
             transform .3s var(--ease),box-shadow .3s var(--ease),filter .3s var(--ease);
}
.totop svg{width:24px;height:24px;stroke:#F7FAF7;fill:none;stroke-width:2.4;
           stroke-linecap:round;stroke-linejoin:round}
/* .on carries two classes, so hover/press are scoped to it to stay on top */
.totop.on{opacity:1;visibility:visible;transform:translateY(0)}
.totop.on:hover{transform:translateY(-4px);filter:brightness(1.14);
                box-shadow:0 16px 36px rgba(0,0,0,.58)}
.totop.on:active{transform:translateY(-1px) scale(.93);filter:brightness(.94);
                 box-shadow:0 6px 18px rgba(0,0,0,.5)}
.totop:focus-visible{outline:2px solid #F7FAF7;outline-offset:3px}

/* ════════════════════════════════════════════════════════
   RESPONSIVE QA PASS
   Appended last on purpose — at equal specificity the later rule wins, so
   everything here reliably lands on top of the base rules and the earlier
   media queries without needing !important.
   ════════════════════════════════════════════════════════ */

/* A photographer's grid should not crop to fit.
   The masonry row spans are absolute pixels (12px rows, 14px gaps) while the
   columns are fluid, so a tile only matches its photograph's proportions near
   1440px. At 1024 a four-column track is ~214px wide but a span-17 tile is
   still 428px tall — 1:2.0 against a 1:1.4 photograph, so roughly a third of
   every frame is sliced off. Below 1200px the grid therefore drops a column
   and lets each image state its own height; the varied heights still read as
   a mosaic, and nothing is cropped.
   Above 1200px the approved desktop masonry is untouched. */
@media(max-width:1199px){
  .gal{grid-template-columns:repeat(3,minmax(0,1fr));grid-auto-rows:auto}
  .gal .gal__item{grid-row:auto !important}
  .gal .gal__item img{height:auto}

  .gal--overview{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:auto}
  .gal--overview .gal__item{grid-column:span 1;grid-row:auto !important;order:0}
  .gal--overview .gal__item img{height:auto}
  .gal--overview .gal__cta{grid-column:span 1;grid-row:auto;min-height:260px}
}
@media(max-width:960px){
  .gal{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media(max-width:540px){
  .gal,.gal--overview{grid-template-columns:minmax(0,1fr)}
}

/* Touch devices have no hover and need roughly 44px of target. Scoped by
   pointer type rather than width, so the approved desktop design is left
   alone at every size while a 1024px tablet still gets the bigger targets. */
@media(hover:none){
  .filters button{min-height:44px;padding:11px 2px}
  .footer nav a{display:inline-flex;align-items:center;min-height:44px;padding:6px 0}
  /* the box grows to 44px, the padding keeps the icon itself at 32px */
  .socials a,.footer .socials a{width:44px;height:44px;padding:6px}
  .pager__arrow{width:44px;height:44px}
  .pager--compact .pager__arrow{width:40px;height:40px}
  .lb__x,.modal__x{width:44px;height:44px}
  .modal__x{top:12px;right:12px}
  .nav__links a{padding:8px 4px}
  .langsw__cur{min-height:44px}
  .langsw__list button{min-height:44px}
  .link-arrow{padding-block:6px}
  .cinfo__item{padding-block:4px}
  /* hover states never fire here and leave elements looking inert, so the
     press itself carries the feedback */
  .gal__item:active img{transform:scale(1.04)}
  .socials a:active,.footer .socials a:active{transform:scale(.92);opacity:.7}
  .filters button:active{color:var(--honey)}
}

/* A phone held sideways is about 360px tall — a 640px hero would be all
   photo reel and no headline. */
@media(max-height:520px){
  .hero{min-height:0}
  .scroll-cue{display:none}
}

/* Nothing on the page should be able to push a container wider than the
   viewport because of one long unbroken token — an address, a URL, a
   hyphen-free compound. Wrapping rules already win over this where a line
   is deliberately kept on one line. */
/* On a phone the Контакти column already carries the heading, the name, the
   contact details and the whole form — the long paragraph pushed the form
   itself well below the fold. The opening line stays. */
@media(max-width:1024px){
  .contact__info .lede:not(.lede--intro){display:none}
}

h1,h2,h3,h4,p,li,label,figcaption,.sel__opt,.sent__text,.gal__cta p{
  overflow-wrap:break-word;
}
/* grid and flex children default to min-width:auto, so one wide child can
   push its track past the container. These are the tracks that hold prose
   and nowrap contact details. */
.contact__grid > *,.modal__grid > *,.rev > *{min-width:0}

/* Below 880px the service overlay stacks and its photograph becomes a wide
   shallow band. The covers are portraits with the subject in the upper half,
   so bias the crop upwards rather than taking the centre. */
@media(max-width:880px){
  .modal__hero img{object-position:center 28%}
}

/* ════════════════════════════════════════════════════════
   SERVICE CARD FLIP
   Desktop keeps the approved card exactly as it was: the two wrappers are
   display:contents, so .svc still sees .svc__ph and .svc__body as its own
   children, and the back is not rendered at all.
   ════════════════════════════════════════════════════════ */
.svc__flip,.svc__face--front{display:contents}
.lbl-narrow{display:none}          /* the front CTA reads Замовити on desktop */
.svc__face--back{display:none}

@media(max-width:1024px){
  /* the front carries a single control, and it says Детальніше */
  .lbl-wide{display:none}
  .lbl-narrow{display:revert}
  .svc__face--front .svc__more{display:none}

  .svc{
    /* the card is now the stage; the faces do the turning */
    perspective:1600px;background:none;border:none;overflow:visible;
    display:block;position:relative;height:auto;
    transition:height .68s cubic-bezier(.32,.06,.24,1);
  }
  /* a turned card is taller than its neighbours; without this the whole row
     would stretch to match it */
  .svc-grid{align-items:start}

  /* Nine full-height cards in one column is a very long scroll before the
     rest of the page is reachable, so the section opens with four. */
  .svc--hidden{display:none}
  /* released for the frame in which the shared card height is measured, so a
     card waiting behind the button still counts towards it */
  body.svc-measuring .svc--hidden{display:block}
  .svc-more{display:flex;justify-content:center;margin-top:clamp(26px,4vw,40px)}
  .svc-more .btn{width:244px;max-width:100%;height:48px;padding:0}
  .svc__flip{
    display:block;position:relative;width:100%;height:100%;
    transform-style:preserve-3d;
    transition:transform .68s cubic-bezier(.32,.06,.24,1);
  }
  .svc.is-flipped .svc__flip{transform:rotateY(180deg)}

  /* Each face is pinned to the top and left free to take its natural
     height — that is what lets a long package grow the card instead of
     being clipped. backface-visibility stops the reversed side showing
     through mid-turn. */
  .svc__face{
    display:flex;flex-direction:column;
    /* inset:0 makes both faces fill the card, so the two sides of a card and
       every card in the row share one height. The measuring class below
       releases them for a frame so their natural height can be read. */
    position:absolute;inset:0;
    backface-visibility:hidden;-webkit-backface-visibility:hidden;
    border:1px solid var(--line);border-radius:var(--r);overflow:hidden;
    background:var(--f-700);
  }
  .svc__face--back{
    transform:rotateY(180deg);
    background:url('https://res.cloudinary.com/yfoyq1xk/image/upload/f_auto,q_auto/v1785827189/Green_Background_v6wox0.webp') center center/cover no-repeat,var(--f-800);
    padding:16px 0 14px;
  }
  .svc__face--back .modal__body{
    /* It was height:auto, so the block was only as tall as its own text and
       justify-content had no room to distribute — the content simply sat at
       the top of the card. Now the cards share one height, the block fills
       the face and centres inside it. `safe` means a back that happens to
       fill the whole card is aligned from the top instead of having its
       first line pushed out of view. */
    flex:1;min-height:0;overflow:visible;      /* fills, never nests a scroll */
    padding:24px 20px;
    justify-content:center;justify-content:safe center;
  }
  /* The photograph and the text block were both flex:1, so they split the
     spare height evenly and the picture never got more than half the card.
     The text block is now only as tall as its content and the photograph
     takes everything else — which is what makes the image read as large. */
  .svc__face--front .svc__ph{flex:1;min-height:0;aspect-ratio:auto}
  .svc__face--front .svc__body{
    flex:none;
    justify-content:flex-start;
    padding:16px 20px 24px;   /* 16 under the photo, 24 under the button */
    gap:16px;                 /* title to button */
  }
  /* one frame with the faces at their natural height, so they can be measured */
  body.svc-measuring .svc__face{position:absolute;inset:0 0 auto 0;height:auto}
  body.svc-measuring .svc,body.svc-measuring .svc__flip{transition:none}
  .svc__face--back .modal__x{top:10px;right:10px}

  /* the CTA row has room to breathe on a narrow card */
  .svc__face--back .modal__cta{width:100%;margin-top:18px}
  .svc__face--back .modal__cta .btn{width:100%;max-width:280px}
}

/* A flip is a big movement. Anyone asking for less motion gets the same two
   faces, swapped without rotation. */
@media(prefers-reduced-motion:reduce){
  .svc,.svc__flip{transition:none}
  .svc.is-flipped .svc__flip{transform:none}
  @media(max-width:1024px){
    .svc__face{backface-visibility:visible;-webkit-backface-visibility:visible;
               transition:opacity .2s linear}
    .svc__face--back{transform:none;opacity:0;visibility:hidden}
    .svc.is-flipped .svc__face--front{opacity:0;visibility:hidden}
    .svc.is-flipped .svc__face--back{opacity:1;visibility:visible}
  }
}

/* The overlay is still reachable above 1024px on a short window. Its box was
   a fixed 415px on small screens, which is what cut the package list off in
   mid-line; it now sizes to the content and only scrolls if it has to, and
   the pager below it can never be pushed out of view. */
@media(max-width:620px){
  .modal__box{height:auto;max-height:calc(100svh - 104px)}
  .modal__grid{grid-template-rows:118px minmax(0,1fr)}
  .modal__body{overflow-y:auto}
}

/* ════════════════════════════════════════════════════════
   KEYBOARD FOCUS
   Six controls had a focus ring; everything else was invisible to anyone
   navigating by keyboard, which is a desktop problem above all. One ring,
   drawn in the accent already used for hover, applied to every focusable
   element. :focus-visible means a mouse click never shows it.
   ════════════════════════════════════════════════════════ */
a:focus-visible,
button:focus-visible,
[tabindex]:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible{
  outline:2px solid var(--honey);
  outline-offset:3px;
  border-radius:2px;
}
/* the field keeps its underline cue and gains the ring; the existing
   .field input:focus{outline:none} is more specific, so it is restated */
.field input:focus-visible,
.field textarea:focus-visible,
.sel__btn:focus-visible{outline:2px solid var(--honey);outline-offset:3px}
.sel__opt:focus-visible{outline-offset:-2px}
/* Контакти is the one light plate on the site — honey on near-white is too
   faint to read as a focus indicator, so the ring goes dark there */
#contact a:focus-visible,
#contact button:focus-visible,
#contact input:focus-visible,
#contact textarea:focus-visible{outline-color:#0D0D0D}
/* the tile is the focusable element, and its own corners are rounded */
.gal__item:focus-visible{outline-offset:2px;border-radius:var(--r)}

/* a long chosen value truncates rather than pushing the chevron out */
.sel__value{
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;
}

/* While the drawer is open the header must not filter anything: a
   backdrop-filter — even mid-transition, where the computed value is still a
   blur — makes .nav a containing block and traps the fixed drawer inside the
   80px header. transition:none is deliberate; letting it fade would keep the
   containing block alive for the first half second. */
.nav.menu-open{
  background:transparent;box-shadow:none;
  backdrop-filter:none;-webkit-backdrop-filter:none;
  transition:none;
}

/* ════════════════════════════════════════════════════════
   STAT GLOW ON TOUCH DEVICES
   The glow is driven entirely by pointermove, so on a phone or tablet
   --edge-proximity never left 0 and the opacity formula resolved to a
   negative number: the effect simply never appeared. Nothing was broken —
   there was just no pointer to track.
   Rather than bolt on a different effect, the same glow is given a pointer
   of its own: --edge-proximity is held at a visible level and --cursor-angle
   is swept a full turn, so the light travels the border by itself. Registering
   the custom property is what lets the browser interpolate an angle rather
   than snap between keyframes.
   ════════════════════════════════════════════════════════ */
@property --cursor-angle{syntax:'<angle>';inherits:false;initial-value:45deg}

@media(hover:none){
  .stat{--edge-proximity:78}
  .stat__glow{animation:statSweep 7s linear infinite}
  /* the four cards are offset so the row does not pulse in unison */
  .stat:nth-child(2) .stat__glow{animation-delay:-1.75s}
  .stat:nth-child(3) .stat__glow{animation-delay:-3.5s}
  .stat:nth-child(4) .stat__glow{animation-delay:-5.25s}
  /* the gradient stroke reads faintly on a small screen — a shorter cycle
     and a tighter gradient give it more presence without changing a colour */
  .stat::before{animation-duration:3.4s;background-size:170% 170%}
}
@keyframes statSweep{to{--cursor-angle:405deg}}

@media(prefers-reduced-motion:reduce){
  .stat__glow{animation:none}
}

/* ══════════════════════════════════════════════════════════════
   WEDDING PAGE  (wedding.html)
   Lives in the shared sheet so the two pages cannot drift on
   colour, type or spacing. Nothing here matches on index.html.
   ══════════════════════════════════════════════════════════════ */

/* ── closing band ─────────────────────────────────────────────
   1440 x (120/72 padding), everything centred in one column with 24px
   between the pieces. Replaces the old .wed-light plate, which is no longer
   used anywhere on the page. */
.wed-close{
  background:url('https://res.cloudinary.com/yfoyq1xk/image/upload/f_auto,q_auto:good/v1786805440/Block_conclusion_Signature_ajeesu.webp')
             center center/cover no-repeat,#F2F3F1;
  color:#0D0D0D;
}
.wed-close__inner{
  max-width:var(--maxw);margin-inline:auto;
  display:flex;flex-direction:column;justify-content:center;align-items:center;
  gap:24px;
  padding:clamp(56px,8.3vw,120px) clamp(20px,5vw,72px);
}
.wed-close__lede,.wed-close__name{
  width:min(1021px,100%);margin:0;text-align:center;
  font-family:var(--serif);font-weight:400;
  font-size:clamp(21px,2.3vw,32px);line-height:normal;
}
.wed-close__lede{color:#0D0D0D}
/* gap:24px already spaces the icons from the button */
.wed-close .socials{margin-top:0}
.wed-close a:focus-visible,.wed-close button:focus-visible{outline-color:#0D0D0D}


/* ── intro: copy left, arch photograph right ──────────────────
   Its own section rather than a .wed-light one: it carries a different
   photograph as its backdrop, and standing alone means the generic
   .wed-light p colour rule cannot outrank the type scale below. */
.wed-intro{
  background:url('https://res.cloudinary.com/yfoyq1xk/image/upload/f_auto,q_auto:good/v1786635209/Wedding_background_nn3zb9.webp')
             center center/cover no-repeat,#F2F3F1;
  color:#0D0D0D;
}
/* The backdrop is a 1440x867 photograph and the copy is placed against it,
   not flowed inside padding: 72 from the left, 135 from the top, 636 wide.
   Those are written as percentages of the frame so the whole arrangement
   scales with the picture instead of drifting across it. */
.wed-intro__frame{
  position:relative;
  max-width:var(--maxw);margin-inline:auto;
  aspect-ratio:191/115;                 /* 1440 / 867 */
}
.wed-intro__copy{
  position:absolute;
  left:5%;                              /*  72 / 1440 */
  top:15.571%;                          /* 135 /  867 */
  /* 691 / 1440, was 636 (44.167%). The heading has to hold one line in four
     languages at 48px, and Polish — "Tam, gdzie zaczyna się Wasza historia" —
     measures 679px. It missed a 636px column, and so did Ukrainian, by a
     single pixel: 637. 691 clears the widest language by 12px. The block is
     centred text, so only Polish ever reaches the full width; the other three
     sit well inside it. Right-hand clearance goes from 732px to 677px, which
     still leaves the couple in the photograph clear of the type. */
  width:48%;
  text-align:center;
}
.wed-intro__copy h2{
  font-family:var(--serif);font-weight:400;font-size:clamp(30px,4.4vw,48px);
  line-height:1.2;color:#0D0D0D;margin:0;
}
/* 526 x 1, rounded ends, 24px under the heading — 526 of the 636 column, so
   it stays centred and in proportion as the frame scales */
.wed-intro__rule{
  width:82.7%;max-width:526px;height:1px;margin:24px auto 0;
  background:#0D0D0D;border:0;border-radius:999px;
}
.wed-intro__body{margin-top:24px}          /* 24px from the rule to the copy */
.wed-intro__p{
  font-family:var(--sans);font-size:16px;font-weight:400;line-height:24px;
  color:#0D0D0D;text-align:center;margin:0;
}
.wed-intro__p + .wed-intro__p{margin-top:40px}
/* a group can hold two sentences, each starting on its own line but with no
   gap between them — only the 40px between groups */
.wed-intro__p .ln{display:block}
/* display:flex makes the button block-level, so text-align no longer centres
   it — the auto margins do */
.wed-intro__copy .btn{margin:32px auto 0;width:244px;height:48px;padding:0}

/* Below roughly 1100px the copy no longer fits the placed box — the type does
   not shrink with the frame, so it would run past the bottom of the picture.
   From here the section goes back to ordinary flow with padding, and the
   backdrop is anchored left so the text keeps the quiet part of the frame. */
@media(max-width:1100px){
  .wed-intro{background-position:left center;padding:clamp(48px,7vw,96px) 0}
  .wed-intro__frame{aspect-ratio:auto;padding-inline:var(--gutter)}
  /* 691 to match the placed column above, so the heading keeps its single
     line here too — below 1091px the type shrinks with the viewport anyway,
     so the extra width costs nothing and the block stays centred. */
  .wed-intro__copy{position:static;width:100%;max-width:691px;margin-inline:auto}
}

/* ── the outline button on a light plate ──────────────────────
   244 x 48, 1.5px #0D0D0D, 4px corners. On hover an ellipse rises from below
   and swallows the button; the label turns ivory as it arrives. */
.btn--ink{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  width:244px;max-width:100%;height:48px;padding:0;
  border:1.5px solid #0D0D0D;border-radius:4px;
  color:#0D0D0D;background:transparent;
  font-family:var(--serif);font-size:20px;font-weight:400;letter-spacing:.04em;
  /* clips the ellipse to the inside of the stroke, so the 1.5px outline stays
     visible the whole way through the sweep */
  overflow:hidden;position:relative;isolation:isolate;
  /* the label waits for the ellipse to reach it before turning ivory */
  transition:color .38s var(--ease) .3s,border-color .5s var(--ease);
}
/* Only the colour is overridden. The ellipse itself comes from .btn::before,
   which is sized in percentages — 118% x 250% at -11% / -75%.
   It used to be pinned here at the design's 286 x 110 / right:-27px, which is
   correct at 244px wide and wrong at every other width: this button stretches
   to 300px in the package rows on a phone, and a 286px shape pinned to the
   right edge then left a 41px strip on the left uncovered. */
.btn--ink::before{background:linear-gradient(180deg,#162B29 0%,#2F665E 100%)}
.btn--ink:hover,.btn--ink:focus-visible{color:rgba(247,250,247,.99);border-color:#0D0D0D}
.btn--ink:hover::before,.btn--ink:focus-visible::before{transform:translateY(0)}
/* pressed: the ellipse is already home, and darkens */
.btn--ink:active{color:rgba(247,250,247,.99)}
.btn--ink:active::before{
  transform:translateY(0);
  background:linear-gradient(133deg,#162B29 .62%,#0E1E1C 99.38%);
  transition:transform .2s var(--ease),background .2s var(--ease);
}
.btn--ink:focus-visible{outline-color:#0D0D0D}
@media(prefers-reduced-motion:reduce){
  .btn--ink::before{transition:opacity .25s linear;transform:translateY(0);opacity:0}
  .btn--ink:hover::before,.btn--ink:focus-visible::before,.btn--ink:active::before{opacity:1}
}

/* ── packages ─────────────────────────────────────────────────
   Header band, then one row per package: a 720x693 photograph beside a
   720-wide plate of copy. Row width caps at 1440, so at the design size the
   two halves are exactly 720 each. */

/* The forest gradient painted through the letterforms. Falls back to the
   darker stop on the handful of engines without background-clip:text. */
.grad-ink{
  background:linear-gradient(133deg,#112523 35.19%,#214B45 75.17%);
  background-clip:text;-webkit-background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
}
@supports not ((background-clip:text) or (-webkit-background-clip:text)){
  .grad-ink{background:none;color:#112523;-webkit-text-fill-color:currentColor}
}

.wed-pkghead{background:#F1F6FC;padding:120px 0 80px;text-align:center}
.wed-pkghead h2{
  font-family:var(--serif);font-weight:400;font-size:clamp(34px,4.4vw,62px);
  line-height:normal;
}
/* same rule as Чесні емоції: 526 x 1, rounded ends, 24px above and below */
.wed-pkghead__rule{
  width:min(526px,100%);height:1px;margin:24px auto;
  background:#0D0D0D;border:0;border-radius:999px;
}
.wed-pkghead p{
  font-family:var(--sans);font-weight:400;font-size:16px;line-height:24px;
  color:#0D0D0D;max-width:72ch;margin:0 auto;
}

/* Full bleed: the row runs edge to edge, each half taking exactly 50% of the
   window. At 1440 that is the 720 + 720 of the design; wider, both halves
   grow together. */
/* Half and half, and it has to be half.
   The photographs alternate sides, so consecutive frames sit on opposite
   edges of the page and meet only at the row boundary — corner to corner. For
   those corners to actually meet, the two widths have to add up to the full
   row. At 45% they added up to 90%, which left a 191px band of the copy plate
   showing through diagonally between every pair of photographs: row 1's frame
   ended at x=860 and row 2's began at x=1051. The rows were already flush
   vertically; the gap was entirely horizontal and entirely arithmetic.
   50 + 50 is the only split where equally sized frames touch. Anything else
   either reopens the gap or makes alternate photographs different widths.
   No nth-child flip needed now — the two tracks are identical, so the
   grid-column rules below are enough to swap the sides. */
.wed-row{display:grid;grid-template-columns:1fr 1fr;align-items:stretch}
/* No aspect-ratio here, deliberately. A grid item that has one is not allowed
   to stretch in the axis the ratio governs — so as soon as max-height clamped
   the height, the browser narrowed the WIDTH to keep the ratio (760 x 720/693
   = 790px) and left the rest of the cell showing the page behind it.
   A min-height gives the same 693px at the design size, lets the row grow when
   the plate beside it needs more, and keeps the frame stretched edge to edge
   at every width. The photograph inside is object-fit:cover, so it crops
   rather than distorts. */
/* Height tracks the viewport, exactly as the 45% width does, so the frame keeps
   one proportion at every size instead of one fixed pixel height against a
   width that moves: 45vw by 50vw is 0.90 — upright — whether the window is
   1280 or 2560. The ceiling stops a 34-inch monitor turning each row into a
   1600px wall. Photograph is 864 x 960 at 1920, identical on all five rows.
   The 1020px floor is measured, not chosen. Whenever the frame is shorter
   than the copy plate the plate sets the row height, and the five plates are
   not equal — the packages have different numbers of lines and wrap
   differently in a narrow column. So the floor has to clear the tallest plate
   any package produces in any of the four languages, at any width where the
   two columns apply. Measured across 901-2560: 996px, at a 1000px viewport
   with the terms note under every price. The plate now holds the package list
   plus the two terms lists, and the worst case is 1055px — at a 901px
   viewport, in Polish, where the longest lines wrap hardest in the narrowest
   column the two-column layout ever has. 1080 clears it.
   That figure went up when the split moved from 45/55 to 50/50 to close the
   diagonal gap between photographs: a narrower copy plate wraps harder.
   This number has moved every time the copy did: 693 -> 760 -> 1020 -> 760 ->
   810 -> 1070. 693 was 43px short and let the rows drift apart by up to 52px.
   It must be the tallest plate the content can produce, in any language, at
   any width where the two columns apply. RE-MEASURE whenever a package or the
   terms gain a line — this has bitten twice. */
.wed-row__ph{
  overflow:hidden;background:#E4EAF2;width:100%;display:grid;position:relative;
  min-height:clamp(1080px,50vw,1200px);
}
/* Taken out of flow entirely, so the picture can never set the height of the
   frame that holds it. It could before: `height:100%` against a parent with
   only a min-height resolves to auto, the image fell back to its own
   proportions, and each row ended up as tall as whichever photograph happened
   to land in it — 747px for a landscape frame, 1194px for an upright one, side
   by side down the page. That is the "different sizes" problem, and no amount
   of adjusting the min-height fixes it while the image is still in flow.
   Absolutely positioned, the row is max(frame min-height, copy plate) and
   nothing else, so every row measures the same whatever is photographed. */
.wed-row__ph img{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;display:block;
}
/* The rows alternate: 1st, 3rd and 5th put the photograph on the left, 2nd and
   4th on the right. Placed by column rather than by writing the markup twice
   or reordering it — the DOM keeps the price plate first either way, so a
   screen reader always reaches the package details before the photograph.
   (The previous `order:2` was a no-op: the figure is already second in the
   markup, so every row was rendering text-left, photo-right.)
   The row is pinned too, not just the column. Grid auto-placement is sparse
   by default: the plate comes first in the markup, so placing it in column 2
   moved the cursor past column 1, and the photograph — asking for column 1,
   which the cursor had already passed — was pushed down to a SECOND grid row.
   That is the phantom band: an empty cell beside the plate, and another empty
   cell beside the photograph below it.
   NOTE: this comment used to carry a stray comment-close after
   "photo-right.)", which ended it six lines early. Everything after that was
   parsed as CSS, and the error recovery swallowed the very next rule — the
   .wed-row__ph line below. The rows still looked right, but only by luck:
   with the plate pinned to column 2, auto-placement happened to drop the
   photograph into the one free cell. Never write the two-character close
   sequence inside a comment, not even in quotes — CSS has no escaping here
   and the comment ends at the first one. */
.wed-row__ph  {grid-column:1;grid-row:1}
.wed-row__body{grid-column:2;grid-row:1}
.wed-row:nth-child(even) .wed-row__ph  {grid-column:2}
.wed-row:nth-child(even) .wed-row__body{grid-column:1}
.wed-row__body{
  background:#F4F3F5;color:#0D0D0D;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  text-align:center;
  /* 70.5 / 97 of a 720-wide plate. Percentage padding always resolves against
     WIDTH, vertical included, so both figures are over 720 — not over 693.
     Clamped at each end: proportional through the middle, but it neither
     collapses on a phone nor balloons on an ultrawide screen. */
  padding:clamp(36px,9.792%,86px) clamp(20px,13.472%,150px);
}
.wed-row__body > *{align-self:stretch}
.wed-row__body h3{
  font-family:var(--serif);font-weight:600;font-size:clamp(24px,2.2vw,32px);
  line-height:normal;margin:0;
}
.wed-row__rule{
  width:min(337px,100%);height:1px;margin:12px auto 0;
  background:#0D0D0D;border:0;
}
.wed-row__inc-title{
  font-family:var(--serif);font-weight:600;font-size:clamp(19px,1.7vw,24px);
  line-height:normal;margin:32px 0 0;
}
.wed-row__inc{list-style:none;margin:24px 0 0;padding:0;min-height:125px}
/* The 125px floor above evens out the package list, whose length varies by
   tier. The two terms lists are the same in every package, so the floor would
   only pad them — a two-line list would reserve room for five. */
.wed-row__inc--terms{min-height:0}
/* Medium weight only — the tick comes from the shared rule above, same as
   every other line. DM Sans 500 is already in the font request at the top of
   every page, so this costs no extra download. */
.wed-row__inc li.em{font-weight:500}
.wed-row__inc li{
  font-family:var(--sans);font-weight:400;font-size:16px;line-height:1.5;
  color:#0D0D0D;
}
.wed-row__inc li + li{margin-top:4px}
/* ── the tick that replaces a bullet ──────────────────────────
   Every size here is in em, so the mark is always 1.25x the text it belongs
   to and needs no per-breakpoint tuning: 20px beside 16px copy on the wedding
   rows and the privacy page, 17.5px beside the overlay's 14px, 15.6px beside
   the 12.5px it drops to on a phone, and it follows the overlay's --fit
   shrink for free because that changes font-size. A fixed 20px would have
   loomed over the small sizes and needed a rule at every breakpoint.
   The mark is decorative — the sentence beside it carries the meaning — so it
   is a background on ::before rather than an <img>, and screen readers skip
   it. Note the two lists differ in alignment, handled separately below. */
.wed-row__inc li::before,
.modal__inc li::before,
.legal__block li::before{
  content:'';
  width:1.25em;height:1.25em;
  background-repeat:no-repeat;background-position:center;background-size:contain;
}
/* Centred lines: the tick sits inside the centred box and travels with the
   text, so it must be inline. On a line that wraps it marks the first line
   only, which is what the mockup shows. -0.25em lifts it onto the middle of
   the line and scales with the type like everything else here. */
.wed-row__inc li::before{
  display:inline-block;margin-right:.25em;vertical-align:-.25em;
  background-image:var(--tick-ink);
}
.wed-row__price{
  font-family:var(--serif);font-weight:400;font-size:clamp(30px,2.8vw,40px);
  line-height:normal;margin:40px 0 0;
}
.wed-row__cta{display:flex;flex-direction:column;align-items:center;gap:24px;margin-top:32px}
.wed-row__cta .btn{width:244px;height:48px;max-width:100%;padding:0}
/* the light plate needs the dark focus ring, same rule the Контакти block uses */
.wed-row__body a:focus-visible,.wed-row__body button:focus-visible,
.wed-intro a:focus-visible,.wed-intro button:focus-visible{outline-color:#0D0D0D}
.wed-row__body .link-arrow{color:#0D0D0D}
.wed-row__body .link-arrow:hover{color:var(--honey)}

@media(max-width:1100px){
  .wed-pkghead{padding:clamp(56px,9vw,120px) 0 clamp(40px,6vw,80px)}
}
@media(max-width:900px){
  .wed-row{grid-template-columns:1fr}
  /* stacked: the alternation has nowhere to go, so every row leads with its
     photograph and the details follow underneath */
  .wed-row__ph,.wed-row:nth-child(even) .wed-row__ph,
  .wed-row__body,.wed-row:nth-child(even) .wed-row__body{grid-column:auto;grid-row:auto}
  .wed-row__ph{order:-1}
  /* one column, so the ratio can set the height without fighting a stretch —
     but the width is pinned all the same */
  .wed-row__ph{min-height:0;width:100%;aspect-ratio:4/3}
  .wed-row__body{padding:clamp(36px,7vw,70px) clamp(20px,6vw,60px)}
  .wed-row__cta .btn{width:100%;max-width:300px}
}


/* ══════════════════════════════════════════════════════════════
   PORTFOLIO PAGE  (portfolio.html)
   The grid, filters and lightbox are the home page's, unchanged.
   Only the page frame differs.
   ══════════════════════════════════════════════════════════════ */
/* first section on the page, so it has to clear the fixed header itself */
.pf-first{padding-top:calc(var(--nav-h) + clamp(28px,5vw,64px))}

/* ── skip link ─────────────────────────────────────────────────
   Off screen until it takes focus, then it lands under the header. */
.skip{
  position:absolute;left:50%;top:0;transform:translate(-50%,-120%);
  z-index:1100;padding:12px 22px;border-radius:0 0 var(--r) var(--r);
  background:var(--f-800);color:var(--ivory);
  font-family:var(--sans);font-size:14px;letter-spacing:.02em;
  transition:transform .25s var(--ease);
}
.skip:focus{transform:translate(-50%,0)}

/* ══════════════════════════════════════════════════════════════
   LEGAL PAGE  (privacy.html)
   A reading page, so the measure is narrow and the type is plain.
   ══════════════════════════════════════════════════════════════ */
.legal-hero{
  background:var(--f-900);text-align:center;
  padding:calc(var(--nav-h) + clamp(44px,7vw,96px)) 0 clamp(44px,6vw,88px);
}
.legal-hero h1{
  font-family:var(--serif);font-weight:400;font-size:clamp(30px,4.6vw,58px);
  line-height:1.15;color:var(--ivory);
}
.legal-hero__meta{margin-top:26px}
.legal-hero__meta p{
  font-family:var(--sans);font-weight:300;font-size:14px;line-height:1.75;
  color:var(--ivory-62);
}
.legal-hero__meta a{color:var(--ivory-80);text-decoration:underline}
.legal-hero__meta a:hover{color:var(--honey)}

.legal{background:#F1F6FC;color:#0D0D0D;padding:clamp(48px,7vw,96px) 0}
.legal__doc{max-width:72ch;margin-inline:auto}   /* a comfortable reading measure */
.legal__block + .legal__block{margin-top:clamp(28px,3.4vw,44px)}
.legal__block h2{
  font-family:var(--serif);font-weight:600;font-size:clamp(21px,2.2vw,27px);
  line-height:1.2;color:#0D0D0D;margin-bottom:14px;
}
.legal__block p{
  /* 16px, like every other block of prose. The looser 1.75 line stays — this
     is a long legal document read straight through, not a short paragraph. */
  font-family:var(--sans);font-weight:400;font-size:16px;line-height:1.75;
  color:#0D0D0D;
}
.legal__block p + p{margin-top:10px}
/* Light plate, so the honey tick — and unlike the two centred lists, this one
   is left-aligned running prose, so the mark hangs in the indent and the
   wrapped lines stay flush with the first. An inline tick here would push the
   second line back under the mark and ruin the left edge of a long clause. */
.legal__block ul{margin:10px 0 0;padding-left:1.75em;list-style:none}
.legal__block li{position:relative}
.legal__block li::before{
  position:absolute;left:-1.6em;top:.16em;
  background-image:var(--tick-ink);
}
.legal__block li{
  /* 16px, like every other block of prose. The looser 1.75 line stays — this
     is a long legal document read straight through, not a short paragraph. */
  font-family:var(--sans);font-weight:400;font-size:16px;line-height:1.75;
  color:#0D0D0D;
}
.legal__block a{color:#0D0D0D;text-decoration:underline;text-underline-offset:2px}
.legal__block a:hover{color:var(--f-300)}
.legal a:focus-visible{outline-color:#0D0D0D}
