/* =============================================================================
   THE SOCIAL PROJECT CO / styles
   Colours and type sizes all live in the :root block below.
   ============================================================================= */

:root {
  /* Brand palette */
  --bg:        #2E0611;
  --bg-2:      #430A19;
  --ink:       #FFF4F8;
  --ink-soft:  rgba(255, 235, 243, 0.76);
  --ink-faint: rgba(255, 235, 243, 0.48);
  --line:      rgba(255, 235, 243, 0.16);

  /* Taken straight from the TSP logo */
  --wine:      #7D0217;
  --wine-lift: #B3213C;
  --rose:      #E7899F;
  --blush:     #FEDCEA;

  /* Text gradient. Every stop stays light enough to read on the wine ground */
  --grad: linear-gradient(100deg, var(--blush), var(--rose) 68%, #D9698A);
  /* Button fill, kept pale so the wine text on top stays high contrast */
  --grad-btn: linear-gradient(100deg, #FEDCEA, #FBC8DA 55%, #F2A9C2);

  /* Type */
  --display: "Playfair Display", Georgia, "Times New Roman", serif;
  --script:  "Kaushan Script", "Brush Script MT", cursive;
  --serif:   "Playfair Display", Georgia, serif;
  --body:    "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;

  --wrap: 1180px;
  --r:    18px;
}

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

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--body);
  font-weight: 300;
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* A soft aurora behind everything, so the page still sings if WebGL fails */
body::before {
  content: "";
  position: fixed;
  inset: -20vh -10vw;
  z-index: -3;
  background:
    radial-gradient(55vw 55vw at 16% 8%,  rgba(163, 14, 40, 0.85), transparent 66%),
    radial-gradient(58vw 58vw at 86% 24%, rgba(199, 52, 82, 0.60), transparent 68%),
    radial-gradient(50vw 50vw at 60% 92%, rgba(231, 137, 159, 0.34), transparent 66%),
    radial-gradient(42vw 42vw at 38% 56%, rgba(146, 8, 30, 0.45), transparent 70%);
  filter: blur(10px);
  pointer-events: none;
}

#scene {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -2;
  pointer-events: none;
  transition: opacity 0.5s ease;
}

/* Darkens the middle of the screen so headlines stay readable over the
   carousel, and softens the edges of the frame. */
.scrim {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(ellipse 56% 48% at 50% 42%, rgba(28, 4, 11, 0.52), rgba(28, 4, 11, 0.26) 48%, transparent 78%),
    linear-gradient(to bottom, rgba(28, 4, 11, 0.48), transparent 20%, transparent 78%, rgba(28, 4, 11, 0.62));
}

/* Film grain, to take the digital edge off the gradients */
.grain {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0.055;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* Glow that follows the cursor. Desktop only. */
.cursor {
  position: fixed;
  top: 0; left: 0;
  width: 380px; height: 380px;
  margin: -190px 0 0 -190px;
  border-radius: 50%;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(circle, rgba(254, 220, 234, 0.16), transparent 62%);
  transition: opacity 0.6s ease;
}
@media (hover: hover) and (pointer: fine) { .cursor { opacity: 1; } }

.wrap { width: min(100% - 44px, var(--wrap)); margin-inline: auto; }

/* ---------- type ---------- */

h1, h2, h3 {
  font-family: var(--display);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.06;
  margin: 0;
}
em { font-family: var(--serif); font-style: italic; font-weight: 500; letter-spacing: -0.005em; }

/* The brush script from the logo lockup. Used sparingly, as a flourish
   rather than a body font. */
.script {
  font-family: var(--script);
  font-style: normal;
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.4;
}

.eyebrow {
  font-family: var(--body);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 22px;
}

.grad-text {
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ---------- buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 15px 30px;
  border: 0;
  border-radius: 100px;
  font-family: var(--display);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.25s cubic-bezier(0.2, 0.8, 0.3, 1), box-shadow 0.25s ease, background 0.25s ease;
}
.btn--solid {
  color: var(--wine);
  background: var(--grad-btn);
  background-size: 180% 100%;
  box-shadow: 0 10px 34px -12px rgba(254, 220, 234, 0.75);
}
.btn--solid:hover {
  transform: translateY(-2px);
  background-position: 100% 0;
  box-shadow: 0 16px 42px -12px rgba(231, 137, 159, 0.8);
}
.btn--ghost {
  color: var(--ink);
  background: rgba(255, 235, 243, 0.09);
  border: 1px solid var(--line);
  backdrop-filter: blur(8px);
}
.btn--ghost:hover { transform: translateY(-2px); background: rgba(255, 235, 243, 0.16); }
.btn--sm   { padding: 11px 22px; font-size: 14px; }
.btn--wide { width: 100%; padding: 17px 30px; font-size: 16px; }
.btn:disabled { opacity: 0.55; cursor: default; transform: none; }

/* ---------- nav ---------- */

.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 20px max(22px, calc((100vw - var(--wrap)) / 2));
  transition: background 0.35s ease, backdrop-filter 0.35s ease, border-color 0.35s ease, padding 0.35s ease, opacity 0.6s ease;
  border-bottom: 1px solid transparent;
}
.nav.is-stuck {
  background: rgba(46, 6, 17, 0.78);
  backdrop-filter: blur(18px) saturate(1.4);
  border-bottom-color: var(--line);
  padding-top: 13px;
  padding-bottom: 13px;
}
.nav__logo {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  font-family: var(--display);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.03em;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}
.nav__dot {
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--grad);
  box-shadow: 0 0 16px rgba(254, 220, 234, 0.9);
  animation: pulse 3.4s ease-in-out infinite;
}
@keyframes pulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(0.78); opacity: 0.7; } }

.nav__links { display: flex; gap: 30px; margin-left: auto; margin-right: 8px; }
.nav__links a {
  position: relative;
  color: var(--ink-soft);
  font-size: 15px;
  text-decoration: none;
  transition: color 0.25s ease;
}
.nav__links a::after {
  content: "";
  position: absolute;
  left: 0; bottom: -5px;
  width: 100%; height: 1px;
  background: var(--grad);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.3, 1);
}
.nav__links a:hover { color: var(--ink); }
.nav__links a:hover::after { transform: scaleX(1); }

/* ---------- top banner ---------- */
/* Lauren's own brand artwork, shown whole. It is never cropped, because the
   monogram sits at the far left and the photo at the far right, so any crop
   would lose one of them. The wine around it matches the artwork's own
   background, so the edges blend rather than framing it. */

.banner {
  position: relative;
  z-index: 2;
  display: grid;
  place-items: center;
  padding: 92px 0 26px;
}
.banner[hidden] { display: none; }
.banner img {
  display: block;
  width: 100%;
  height: auto;
  max-height: calc(100svh - 150px);
  object-fit: contain;
}

@media (min-width: 900px) {
  /* fill the first screen on a laptop, with the artwork centred in it */
  .banner { min-height: 100svh; padding: 110px 28px 34px; align-content: center; }
  .banner img { width: auto; max-width: 100%; border-radius: 10px; }
}

/* ---------- hero ---------- */

.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: 140px 0 100px;
  z-index: 2;
}
.hero__inner { width: min(100% - 44px, 900px); text-align: center; }
/* The carousel passes behind this copy, so give the type its own soft halo
   rather than darkening the whole scene. */
.hero__inner > * { text-shadow: 0 2px 20px rgba(20, 2, 8, 0.85), 0 0 8px rgba(20, 2, 8, 0.5); }
.hero__cta { text-shadow: none; }
.hero__title {
  font-size: clamp(2.9rem, 8.4vw, 6.6rem);
  margin-bottom: 28px;
}
/* The payoff line. Bigger and brighter than the setup line above it, with a
   pink glow so it reads as the thing to remember. */
.hero__title em {
  display: inline-block;
  font-size: 1.1em;
  /* All pink, but a wide enough spread from pale to deep that the ombre is
     clearly visible across the two words. */
  background: linear-gradient(100deg, #FFEDF5 0%, #FCC3D9 30%, #F58FBA 66%, #E85F9B 100%);
  background-size: 145% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  padding-right: 0.06em;
  /* drop-shadow works on the clipped text; text-shadow would print a blurred
     duplicate behind the gradient instead */
  text-shadow: none;
  filter:
    drop-shadow(0 0 30px rgba(250, 160, 200, 0.55))
    drop-shadow(0 3px 16px rgba(28, 4, 11, 0.8));
  animation: shimmer 9s ease-in-out infinite alternate;
}
@keyframes shimmer { to { background-position: 100% 0; } }
.hero__lede {
  max-width: 560px;
  margin: 0 auto 40px;
  color: var(--ink-soft);
  font-size: clamp(1rem, 1.6vw, 1.16rem);
}
/* The script line from the logo lockup */
.hero__sig {
  margin: -18px 0 34px;
  font-size: clamp(1.35rem, 3.2vw, 1.9rem);
  color: var(--blush);
  opacity: 0.85;
}

.hero__cta { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }


/* ---------- ticker ---------- */

.ticker {
  position: relative;
  z-index: 2;
  overflow: hidden;
  padding: 20px 0;
  border-block: 1px solid var(--line);
  background: rgba(46, 6, 17, 0.4);
  backdrop-filter: blur(10px);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.ticker__track {
  display: flex;
  gap: 26px;
  width: max-content;
  /* The scroll is driven from site.js, which measures one copy of the list and
     animates by exactly that many pixels. A CSS keyframe can only shift by a
     percentage of the whole track, which cannot land on a copy boundary. */
}
.ticker__item {
  display: inline-flex;
  align-items: center;
  gap: 26px;
  font-family: var(--display);
  font-size: clamp(1rem, 2.4vw, 1.55rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  white-space: nowrap;
  color: var(--ink-soft);
}
.ticker__item i {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--grad);
  flex: none;
}
.ticker__item:nth-child(even) { color: var(--ink); }

/* Reduce Motion: the strip can't scroll, so it becomes a centred list of the
   labels instead of a stopped marquee. The edge mask comes off with it, since
   with nothing moving through it, it just clips the first and last words. */
.ticker--still {
  -webkit-mask-image: none;
          mask-image: none;
}
.ticker--still .ticker__track {
  width: auto;
  flex-wrap: wrap;
  justify-content: center;
  column-gap: 32px;
  row-gap: 10px;
  padding: 0 20px;
}
/* Every dot, not just the last: they sit after their label, so a wrapped line
   ends on a separator dangling off the end of the row. The wider column gap
   and the alternating colour keep the labels apart without them. */
.ticker--still .ticker__item i { display: none; }

/* ---------- generic section ---------- */

/* Was clamp(90px, 13vw, 150px), which put 300px of empty page between every
   pair of sections on a laptop. This keeps them distinct without the scroll. */
.section { position: relative; z-index: 2; padding: clamp(56px, 7.2vw, 94px) 0; }
.section--alt { background: linear-gradient(180deg, transparent, rgba(125, 2, 23, 0.30) 12%, rgba(125, 2, 23, 0.30) 88%, transparent); }

/* About sits on a deeper band than the other alternating sections. The 3D
   cards drifting behind would otherwise read straight through the picture,
   and the pool of wine on the right is the exact red of the picture's own
   background, which is what lets its edges disappear into the page. */
#about {
  overflow-x: clip;
  background:
    radial-gradient(46% 56% at 74% 50%, rgba(122, 1, 20, 0.42), rgba(122, 1, 20, 0) 72%),
    linear-gradient(180deg,
      rgba(30, 4, 11, 0) 0%,
      rgba(30, 4, 11, 0.95) 8%,
      rgba(30, 4, 11, 0.95) 92%,
      rgba(30, 4, 11, 0) 100%);
}

.sec-head { max-width: 680px; margin-bottom: clamp(30px, 3.2vw, 44px); }
.sec-title { font-size: clamp(2.1rem, 4.7vw, 3.5rem); margin-bottom: 22px; }
.sec-title em {
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.sec-lede { color: var(--ink-soft); max-width: 520px; margin: 0; }
.sec-lede + .sec-lede { margin-top: 16px; }
.sec-lede:empty { display: none; }

/* ---------- work grid ---------- */

.work-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(200px, 100%), 1fr));
  gap: 18px;
}
.w-card {
  position: relative;
  display: block;
  aspect-ratio: 9 / 16;
  border-radius: var(--r);
  overflow: hidden;
  text-decoration: none;
  color: var(--ink);
  background: var(--bg-2);
  border: 1px solid var(--line);
  transform: translateY(0);
  transition: transform 0.4s cubic-bezier(0.2, 0.8, 0.3, 1), box-shadow 0.4s ease, border-color 0.4s ease;
}
.w-card:hover {
  transform: translateY(-8px);
  border-color: rgba(254, 224, 236, 0.24);
  box-shadow: 0 26px 60px -26px rgba(0, 0, 0, 0.9), 0 0 0 1px rgba(254, 220, 234, 0.16);
}
.w-card video,
.w-card img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
/* the animated placeholder that shows until a real video is added */
.w-card__ph {
  position: absolute;
  inset: 0;
  background: linear-gradient(150deg, var(--c1), var(--c2));
  background-size: 200% 200%;
  filter: brightness(0.86) saturate(1.05);
  animation: drift 14s ease-in-out infinite alternate;
}
.w-card__ph::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(60% 44% at 50% 34%, rgba(255, 255, 255, 0.30), transparent 70%);
  mix-blend-mode: soft-light;
}
@keyframes drift { to { background-position: 100% 100%; } }

.w-card__play {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 52px; height: 52px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: rgba(20, 2, 8, 0.34);
  border: 1px solid rgba(255, 255, 255, 0.34);
  backdrop-filter: blur(6px);
  transition: transform 0.35s cubic-bezier(0.2, 0.8, 0.3, 1);
}
.w-card:hover .w-card__play { transform: translate(-50%, -50%) scale(1.12); }
.w-card__play::after {
  content: "";
  margin-left: 4px;
  border-left: 13px solid rgba(255, 255, 255, 0.92);
  border-block: 8px solid transparent;
}

.w-card__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(20, 2, 9, 0.82) 2%, rgba(20, 2, 9, 0.14) 44%, transparent 68%);
}
.w-card__meta { position: absolute; left: 17px; right: 17px; bottom: 15px; }
.w-card__cat {
  display: inline-block;
  margin-bottom: 7px;
  padding: 4px 11px;
  border-radius: 100px;
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
  background: rgba(254, 224, 236, 0.13);
  border: 1px solid rgba(254, 224, 236, 0.14);
  backdrop-filter: blur(6px);
}
.w-card__title {
  font-family: var(--display);
  font-size: 1.02rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.25;
}
.work-note { margin-top: 30px; color: var(--ink-faint); font-size: 14px; }
.work-note__link {
  color: var(--blush);
  text-decoration: none;
  border-bottom: 1px solid rgba(254, 220, 234, 0.35);
  margin-left: 6px;
  white-space: nowrap;
}
.work-note__link:hover { border-bottom-color: var(--blush); }

/* When a tile holds a real video file it plays on hover, so the badge steps
   out of the way. Tiles that link to Instagram keep theirs. */
.w-card--file:hover .w-card__play,
.w-card.is-playing .w-card__play { opacity: 0; }
.w-card__play { transition: transform 0.35s cubic-bezier(0.2, 0.8, 0.3, 1), opacity 0.3s ease; }

/* ---------- content pillars ---------- */

.pillars {
  list-style: none;
  margin: -18px 0 34px;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.pillars li {
  padding: 8px 16px;
  border-radius: 100px;
  border: 1px solid var(--line);
  background: rgba(255, 235, 243, 0.07);
  color: var(--ink-soft);
  font-size: 13.5px;
  letter-spacing: 0.01em;
}

/* ---------- why work with me ---------- */

.why-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 16px;
  /* Every tile the same size, at any column count. Grid already stretches
     tiles to match their own row, so they only matched across a row and not
     down the grid, and on one column nothing matched at all. 1fr rows take
     their height from the tallest tile and hand it to all of them. */
  grid-auto-rows: 1fr;
}
.why {
  position: relative;
  padding: 26px 26px 24px;
  border-radius: var(--r);
  background: rgba(255, 235, 243, 0.07);
  border: 1px solid var(--line);
  /* frosts the carousel behind it, so the label stays crisp */
  backdrop-filter: blur(7px);
}
/* Declared at this weight on purpose. These tiles also carry .reveal, whose own
   transition rule sits further down the file and would otherwise replace this
   one, leaving the background and border to snap rather than fade. */
.why-grid .why {
  transition: transform 0.4s cubic-bezier(0.2, 0.8, 0.3, 1),
              background 0.4s ease,
              border-color 0.4s ease,
              box-shadow 0.4s ease;
}

/* Only where there is a real pointer: on a touch screen :hover sticks after a
   tap and the tile would stay lit until something else was tapped. */
@media (hover: hover) and (pointer: fine) {
  .why-grid .why:hover {
    transform: translateY(-5px) scale(1.035);
    background: rgba(255, 235, 243, 0.13);
    border-color: rgba(254, 224, 236, 0.42);
    z-index: 1;                 /* lifts clear of its neighbours' edges */
    box-shadow:
      inset 0 1px 0 rgba(255, 240, 247, 0.22),      /* light along the top edge */
      0 20px 44px -22px rgba(0, 0, 0, 0.85),        /* it sits off the page */
      0 0 32px -4px rgba(231, 137, 159, 0.5),       /* and the glow, blush */
      0 0 72px -10px rgba(231, 137, 159, 0.32);
  }
}
.why__label {
  font-family: var(--body);
  /* 700 is a real weight in the font request, not a synthesised bold, which
     smears at this size and with tracking this wide */
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--blush);
  margin-bottom: 12px;
}
.why__text { margin: 0; color: var(--ink-soft); font-size: 15.5px; line-height: 1.55; }

/* ---------- packages ---------- */
/* Minimal blush tiles: number, name, price. The detail is folded away behind a
   toggle so the section reads as four clean choices at a glance, and opens on
   click, tap or Enter. Cards are independent, so two can be open at once to
   compare, and align to the top of the row so opening one does not stretch the
   others into empty space. */

.pkg-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 20px;
  /* Tiles size themselves. Without this, grid stretches every one to the
     tallest in the row, so opening a tile visibly grew the three that had not
     been touched, which reads as all four opening at once. */
  align-items: start;
}

.pkg {
  position: relative;
  display: flex;
  flex-direction: column;
  /* Height rules live on .pkg-grid.is-row below, because they only make sense
     when the tiles are actually beside each other. */
  padding: 28px 26px 22px;
  border-radius: 6px;
  background: var(--blush);
  color: #2E0409;
  cursor: pointer;
  box-shadow: 0 26px 60px -30px rgba(0, 0, 0, 0.95);
  transition: transform 0.4s cubic-bezier(0.2, 0.8, 0.3, 1),
              box-shadow 0.4s ease,
              min-height 0.5s cubic-bezier(0.22, 0.9, 0.24, 1);
}
.pkg:hover { transform: translateY(-7px); box-shadow: 0 34px 70px -30px rgba(0, 0, 0, 1); }
.pkg.is-open { box-shadow: 0 38px 76px -30px rgba(0, 0, 0, 1); }

/* Only while the tiles sit in a row together. site.js sets is-row from the
   actual column count, not from the viewport width: the grid can still be two
   columns on a fairly narrow screen, and keying this to a breakpoint meant
   side-by-side tiles stopped matching. Shut they share a floor; open they all
   go to --pkg-open, the height of the longest package, so any two open tiles
   are the same size. Stacked, none of it applies and each opens to its own
   natural height. */
.pkg-grid.is-row .pkg { min-height: 238px; }
.pkg-grid.is-row .pkg.is-open { min-height: var(--pkg-open, auto); }
/* Held for a beat while site.js measures, so nothing animates or flashes. The
   descendants matter as much as the card: the fold's own grid-template-rows
   transition would otherwise still be at its closed value on the very frame the
   height is read, and every package would measure as its shut height. */
.pkg.is-measuring,
.pkg.is-measuring * { transition: none !important; }
.pkg.is-measuring .pkg__more { grid-template-rows: 1fr; }
.pkg.is-measuring .pkg__clip { opacity: 0; }
.pkg:has(.pkg__toggle:focus-visible) { outline: 2px solid var(--wine); outline-offset: 3px; }

/* The numbers sit up off the card rather than being drawn on it: tone on tone
   like the tile itself, fattened by a stroke in its own colour so the shapes
   read soft rather than sharp, and lifted by a stack of shadows that go from a
   tight contact edge out to a wide soft one. */
.pkg__num {
  display: inline-block;
  font-family: var(--body);
  font-weight: 800;
  font-size: 3.15rem;
  line-height: 1;
  letter-spacing: -0.01em;
  color: #FFF2F8;
  /* Fattens the shapes a little and rounds the joins. Kept modest on purpose:
     any heavier and the stroke grows inwards far enough to close up the hole
     in the zero. */
  -webkit-text-stroke: 2.5px #FFF2F8;
  paint-order: stroke fill;
  text-shadow:
    0 0 9px rgba(255, 255, 255, 0.55),      /* the soft fuzz right at the edge */
    0 2px 0 rgba(255, 255, 255, 0.9),       /* light catching the top */
    0 5px 7px rgba(146, 26, 62, 0.28),      /* where it meets the card */
    0 13px 20px rgba(125, 2, 23, 0.2),      /* the lift */
    0 26px 38px rgba(125, 2, 23, 0.12);     /* and the spread under that */
  /* The easing overshoots a little past its mark before settling, which is what
     makes the number read as popping up rather than sliding. */
  transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1),
              text-shadow 0.5s ease;
}

/* Pointing at a tile lifts its number off the card. The shadows travel with it:
   the contact edge slides down and softens while the wide one spreads, the way
   a shadow behaves when the thing casting it rises. */
.pkg:hover .pkg__num {
  transform: translateY(-9px) scale(1.07);
  text-shadow:
    0 0 13px rgba(255, 255, 255, 0.6),
    0 2px 0 rgba(255, 255, 255, 0.95),
    0 11px 13px rgba(146, 26, 62, 0.26),
    0 24px 32px rgba(125, 2, 23, 0.22),
    0 44px 58px rgba(125, 2, 23, 0.14);
}

/* Opened, it goes further again and tips slightly off square. */
.pkg.is-open .pkg__num {
  transform: translateY(-15px) scale(1.2) rotate(-2deg);
  text-shadow:
    0 0 17px rgba(255, 255, 255, 0.7),
    0 3px 0 rgba(255, 255, 255, 1),
    0 17px 18px rgba(146, 26, 62, 0.24),
    0 36px 46px rgba(125, 2, 23, 0.24),
    0 62px 78px rgba(125, 2, 23, 0.16);
}
/* both at once should not stack into something silly */
.pkg.is-open:hover .pkg__num { transform: translateY(-17px) scale(1.22) rotate(-2deg); }

.pkg__title {
  font-size: 1.3rem;
  color: var(--wine);
  margin: 14px 0 8px;
}
/* Lives inside the fold now, so it only shows once a tile is opened */
.pkg__price {
  margin: 0 0 20px;
  font-family: var(--body);
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(125, 2, 23, 0.72);
}

/* The fold. A grid row going 0fr to 1fr is the one way an automatic height can
   actually animate; the clip inside it must carry no padding of its own or the
   row can never reach zero, so the spacing lives on its children. */
.pkg__more {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.5s cubic-bezier(0.22, 0.9, 0.24, 1);
}
.pkg.is-open .pkg__more { grid-template-rows: 1fr; }
.pkg__clip {
  overflow: hidden;
  min-height: 0;
  opacity: 0;
  transition: opacity 0.3s ease;
}
.pkg.is-open .pkg__clip { opacity: 1; transition: opacity 0.4s ease 0.12s; }

.pkg__blurb {
  margin: 20px 0 18px;
  font-size: 15px;
  line-height: 1.6;
  color: rgba(46, 4, 9, 0.78);
}
.pkg__list {
  list-style: none;
  margin: 0 0 20px;
  padding: 0;
  display: grid;
  gap: 9px;
}
.pkg__list li {
  position: relative;
  padding-left: 20px;
  font-size: 13.8px;
  line-height: 1.45;
  color: rgba(46, 4, 9, 0.82);
}
.pkg__list li::before {
  content: "";
  position: absolute;
  left: 0; top: 7px;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--wine);
}
.pkg__cta {
  display: inline-block;
  font-family: var(--body);
  font-size: 14px;
  color: var(--wine);
  text-decoration: none;
  border-bottom: 1px solid rgba(125, 2, 23, 0.35);
  transition: border-color 0.25s ease;
}
.pkg__cta:hover { border-bottom-color: var(--wine); }

/* the affordance: says what it does, and the plus turns into a close */
.pkg__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  width: 100%;
  margin-top: auto;      /* sits on the bottom edge, so the footers align */
  padding: 18px 0 0;
  border: 0;
  border-top: 1px solid rgba(125, 2, 23, 0.18);
  background: none;
  color: var(--wine);
  font-family: var(--body);
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-align: left;
  cursor: pointer;
}
.pkg__toggle:focus { outline: none; }   /* the card shows the ring instead */

.pkg__plus {
  position: relative;
  flex: none;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: rgba(125, 2, 23, 0.1);
  transition: transform 0.45s cubic-bezier(0.22, 0.9, 0.24, 1), background 0.3s ease;
}
.pkg__plus::before,
.pkg__plus::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: 11px; height: 1.5px;
  border-radius: 2px;
  background: var(--wine);
  translate: -50% -50%;
}
.pkg__plus::after { rotate: 90deg; }
.pkg:hover .pkg__plus { background: rgba(125, 2, 23, 0.16); }
/* 135 rather than 45, so it unwinds the long way back and reads as a flourish */
.pkg.is-open .pkg__plus { transform: rotate(135deg); background: rgba(125, 2, 23, 0.2); }

/* ---------- what to expect ---------- */

.expect { margin-top: clamp(56px, 8vw, 88px); }
.expect .eyebrow { margin-bottom: 26px; }
.expect-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: 30px;
}
.expect__item { border-top: 1px solid var(--line); padding-top: 22px; }
.expect__quote {
  margin: 0 0 14px;
  font-family: var(--serif);
  font-style: italic;
  font-size: 1.12rem;
  line-height: 1.5;
  color: var(--ink);
}
.expect__label {
  margin: 0;
  font-family: var(--body);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--blush);
}

/* ---------- how it works ---------- */

.steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(195px, 100%), 1fr));
  gap: 18px;
}
.step {
  position: relative;
  padding: 28px 24px 26px;
  border-radius: var(--r);
  border: 1px solid var(--line);
  background: rgba(255, 235, 243, 0.06);
  backdrop-filter: blur(7px);
}
/* the connecting line between steps, only once they sit side by side */
.step::after { content: none; }
@media (min-width: 880px) {
  .step:not(:last-child)::after {
    content: "";
    position: absolute;
    top: 44px; right: -18px;
    width: 18px; height: 1px;
    background: linear-gradient(90deg, rgba(254, 220, 234, 0.5), transparent);
  }
}
.step__num {
  display: block;
  margin-bottom: 14px;
  font-family: var(--serif);
  font-style: italic;
  font-size: 1.9rem;
  line-height: 1;
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.step__title { font-size: 1.18rem; margin-bottom: 9px; }
.step__text { margin: 0; color: var(--ink-soft); font-size: 14.8px; line-height: 1.55; }

/* ---------- testimonials ---------- */

.words-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr));
  gap: 18px;
}
.word {
  position: relative;
  margin: 0;
  padding: 32px 28px 26px;
  border-radius: var(--r);
  background: rgba(255, 235, 243, 0.07);
  border: 1px solid var(--line);
  backdrop-filter: blur(7px);
}
/* Declared at this weight on purpose. These cards also carry .reveal, whose own
   transition rule sits further down the file and would otherwise replace this
   one, leaving the background and border to snap rather than fade. */
.words-grid .word {
  transition: transform 0.4s cubic-bezier(0.2, 0.8, 0.3, 1),
              background 0.4s ease,
              border-color 0.4s ease,
              box-shadow 0.4s ease;
}

/* Matches the why-work-with-me tiles. Only where there is a real pointer: on a
   touch screen :hover sticks after a tap and the card would stay lit until
   something else was tapped. */
@media (hover: hover) and (pointer: fine) {
  .words-grid .word:hover {
    transform: translateY(-5px) scale(1.035);
    background: rgba(255, 235, 243, 0.13);
    border-color: rgba(254, 224, 236, 0.42);
    z-index: 1;                 /* lifts clear of its neighbours' edges */
    box-shadow:
      inset 0 1px 0 rgba(255, 240, 247, 0.22),      /* light along the top edge */
      0 20px 44px -22px rgba(0, 0, 0, 0.85),        /* it sits off the page */
      0 0 32px -4px rgba(231, 137, 159, 0.5),       /* and the glow, blush */
      0 0 72px -10px rgba(231, 137, 159, 0.32);
  }
}
.word__quote {
  margin: 0 0 18px;
  font-family: var(--serif);
  font-style: italic;
  font-size: 1.18rem;
  line-height: 1.5;
  color: var(--ink);
}
/* The name carries the credibility, so it is the bright one. It used to be
   --ink-faint at 0.48 alpha while the business took the blush, which read the
   wrong way round and left the person's name disappearing into the card. */
.word__who {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--blush);          /* the pink the package tiles are built from */
}
/* Soft neutral, so the pink name stays the thing the eye lands on. The two
   were the other way round, which left the name dissolving into the card. */
.word__who span { color: var(--ink-soft); font-weight: 500; }

/* placeholder testimonials, only ever rendered on the author's own machine */
#words.is-preview .word {
  border-style: dashed;
  border-color: rgba(255, 235, 243, 0.3);
}
#words.is-preview .word__quote { opacity: 0.72; }

/* ---------- faq ---------- */

.faq { max-width: 780px; }
.faq__item {
  border-bottom: 1px solid var(--line);
  padding: 4px 0;
}
.faq__q {
  list-style: none;
  cursor: pointer;
  padding: 20px 44px 20px 0;
  position: relative;
  font-family: var(--display);
  font-size: clamp(1.05rem, 2vw, 1.25rem);
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--ink);
  transition: color 0.25s ease;
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q:hover { color: var(--blush); }
/* the plus that turns into a minus */
.faq__q::after,
.faq__q::before {
  content: "";
  position: absolute;
  right: 6px; top: 50%;
  width: 15px; height: 2px;
  border-radius: 2px;
  background: var(--blush);
  transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.3, 1), opacity 0.3s ease;
}
.faq__q::before { transform: translateY(-50%) rotate(90deg); }
.faq__q::after  { transform: translateY(-50%); }
.faq__item[open] .faq__q::before { transform: translateY(-50%) rotate(0deg); opacity: 0; }
.faq__a {
  margin: 0 0 22px;
  padding-right: 44px;
  color: var(--ink-soft);
  font-size: 15.5px;
  line-height: 1.65;
}

/* ---------- about ---------- */

.about {
  display: grid;
  grid-template-columns: 1fr 1.15fr;
  --about-gap: clamp(30px, 4.6vw, 66px);
  gap: 0 var(--about-gap);
  align-items: center;

  /* Scroll progress for this section, written by site.js on every frame that
     matters: -1 entering from the bottom, 0 dead centre, +1 leaving the top.
     --near is 1 in the middle and 0 at either end. Both default to a resting
     state, so the section still looks right if the script never runs. */
  --p: 0;
  --near: 1;
}
/* Three areas: the title across the top, the copy under it on the left, and the
   picture spanning both rows on the right, lifted above the title so she
   overlaps it rather than colliding with it. */
.about__head {
  grid-column: 1 / -1;
  grid-row: 1;
  z-index: 1;
  margin-bottom: clamp(8px, 1.6vw, 22px);
}
.about__text   { grid-column: 1; grid-row: 2; z-index: 2; }
.about__visual { grid-column: 2; grid-row: 1 / span 2; z-index: 3; align-self: center; }

.about__head .eyebrow { margin-bottom: clamp(10px, 1.4vw, 18px); }

/* The oversized title, the way a magazine page runs one across the top. It
   carries the same full-strength ombre as "No studio needed" and the other
   highlighted headings, so it reads as part of that family rather than as a
   watermark behind the section.

   Playfair sets this word about 5.16x its font size wide, and the measure is
   min(100vw - 44px, 1180px), so this fills about 94% of it: enough to read as a
   full-width title, with the end stopping short of the edge so it tucks behind
   her rather than running into the margin. Measured at 920, 1120 and 1500 wide.
   Re-measure if the wording changes. If the webfont fails the fallback sets
   wider, which the section's overflow-x: clip absorbs. */
.about__big {
  /* line-height 0.82 pulls the box in tighter than the letters are tall: at this
     size the baseline lands a hair below the bottom of the box. Because the
     gradient is painted inside the box and then clipped to the text, anything
     below that edge gets no paint at all, which quietly ate the bottoms of the
     O and the U (the round letters overshoot past the baseline; the flat ones
     sit on it and were fine). The padding gives the gradient enough box to
     cover them, and the matching negative margin hands the space straight back,
     so the title sits exactly where it did. Both are in em, so they hold at
     every size. */
  padding: 0.04em 0 0.09em;
  margin: -0.04em 0 -0.09em;
  font-family: var(--display);
  font-weight: 400;
  font-size: min(18.3vw - 9px, 215px);
  line-height: 0.82;
  letter-spacing: 0.012em;
  text-transform: uppercase;
  white-space: nowrap;
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* the greeting, in the same script as the lockup, heading the copy */
.about__hi {
  margin: 0 0 26px;
  font-family: var(--script);
  font-weight: 400;
  font-size: clamp(1.7rem, 3.4vw, 2.6rem);
  line-height: 1.25;
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.about__text p:not(.eyebrow) { color: var(--ink-soft); }
.about__socials { display: flex; gap: 12px; margin-top: 32px; flex-wrap: wrap; }
.social {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 11px 20px;
  border-radius: 100px;
  border: 1px solid var(--line);
  background: rgba(255, 235, 243, 0.08);
  color: var(--ink);
  font-size: 14.5px;
  text-decoration: none;
  transition: transform 0.25s ease, background 0.25s ease, border-color 0.25s ease;
}
.social:hover { transform: translateY(-2px); background: rgba(255, 235, 243, 0.15); border-color: rgba(255, 235, 243, 0.32); }
.social svg { width: 17px; height: 17px; fill: currentColor; }

/* ---------- the About picture ----------

   She is lifted off her studio backdrop and sits on a wine disc drawn here in
   CSS, which she breaks out of at the head, the lens and the camera base. The
   picture leads this section rather than sitting beside it, so it is given the
   wider of the two columns.

   Everything moves off two custom properties set on .about by site.js: --p is
   -1 as the section enters from the bottom, 0 dead centre and +1 as it leaves
   the top; --near is 1 in the middle and 0 at either end. The disc, the
   subject and the label all travel at different rates, which is what gives the
   section depth on the way past.
*/

.about__visual {
  position: relative;
  display: grid;
  place-items: center;
}

.portrait {
  position: relative;
  margin: 0;
  aspect-ratio: 940 / 968;        /* the cut-out's own proportions */

  /* Wider than its column on purpose, and anchored to the right edge so the
     extra width spills left into the gutter rather than off the side of the
     page. The overhang is the gutter less 34px, so the front of the lens keeps
     that much clear of the copy at every width instead of crowding it. */
  width: min(640px, calc(100% + var(--about-gap) - 34px));
  justify-self: end;
  transform: translate3d(0, calc(var(--p) * -26px), 0);
  will-change: transform;
}

/* The wine behind her. It has no edge: it is dense where she is and dissolves
   outwards into the page, so it still reads as a circle without ever drawing a
   rim. That is why the element is wider than the wine looks, and why there is
   no border-radius and no box-shadow here. Both of those follow the box rather
   than the paint, so either one would put back the hard circle this is meant to
   avoid.

   Centred with percentage margins rather than a translate, so the
   reduced-motion rule can clear its transform without knocking it off centre:
   a percentage margin resolves against the container's width either way, and
   the element is square, so -58% is half of it vertically as well. */
.portrait::before {
  content: "";
  position: absolute;
  z-index: -1;
  /* Half again the size of the picture, so it surrounds her rather than sitting
     behind her, and sat slightly left of centre because the picture is anchored
     to the right of the page and the spread has more room on the gutter side.
     It runs past the edge of the page, which the section clips. */
  left: 46%;
  top: 45%;
  margin-left: -75%;
  margin-top: -75%;
  width: 150%;
  aspect-ratio: 1;
  background:
    /* the light, falling from the upper left the way it does in the photo */
    radial-gradient(circle closest-side at 43% 33%,
      rgba(172, 26, 54, 0.4), rgba(172, 26, 54, 0) 52%),
    /* the body: dense where she is, then a long fall the whole way out */
    radial-gradient(circle closest-side at 50% 50%,
      #7E0117 0%,
      rgba(122, 1, 21, 0.92) 30%,
      rgba(116, 2, 22, 0.78) 44%,
      rgba(104, 2, 21, 0.58) 57%,
      rgba(90, 2, 19, 0.36) 70%,
      rgba(76, 2, 16, 0.17) 83%,
      rgba(62, 2, 14, 0.05) 93%,
      rgba(58, 2, 13, 0) 100%);
  opacity: calc(0.86 + var(--near) * 0.14);
  transform: scale(calc(0.965 + var(--near) * 0.035));
  will-change: transform;
}

/* She travels slightly against the disc, so the two read as separate layers.
   The drop shadow follows her outline, which is what keeps her sitting on the
   disc rather than floating in front of it. */
.portrait__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  filter: drop-shadow(0 22px 30px rgba(18, 2, 7, 0.5));
  transform: translate3d(0, calc(var(--p) * -1.6%), 0);
  will-change: transform;
}

/* the small floating label, on its own slower track, sitting on the disc */
.portrait__chip {
  position: absolute;
  left: 2%;
  top: 26%;
  z-index: 3;
  padding: 8px 15px;
  border-radius: 100px;
  border: 1px solid rgba(254, 224, 236, 0.24);
  background: rgba(46, 6, 17, 0.55);
  backdrop-filter: blur(9px);
  -webkit-backdrop-filter: blur(9px);
  color: var(--ink);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  box-shadow: 0 16px 34px -18px rgba(0, 0, 0, 0.9);
  transform: translate3d(0, calc(var(--p) * 34px), 0);
  will-change: transform;
}

/* the copy drifts the other way to the picture, which is what sells the depth */
.about__text {
  transform: translate3d(0, calc(var(--p) * 14px), 0);
  will-change: transform;
}

/* ---------- contact ---------- */

.contact {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(40px, 6vw, 80px);
  align-items: start;
}
/* Two columns shared by every row, so the values line up in a straight edge no
   matter how long the labels are. The rows are subgrids of this one rather than
   grids of their own, which is what lets the label column size itself from the
   longest word in the whole list. A fixed label width was doing this before and
   Instagram and Facebook were both wider than it. */
.contact__details {
  list-style: none;
  margin: 36px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 14px 14px;
}
.contact__details li {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  align-items: baseline;
  font-size: 15px;
  color: var(--ink-soft);
}
/* A long email address must wrap rather than run off the edge of the page */
.contact__details li > span { min-width: 0; overflow-wrap: anywhere; }
.contact__details b {
  font-family: var(--body);
  font-weight: 500;
  font-size: 11.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
  white-space: nowrap;
}
.contact__details a { color: var(--ink); text-decoration: none; border-bottom: 1px solid rgba(254, 224, 236, 0.25); }
.contact__details a:hover { border-bottom-color: var(--blush); }

.contact__formwrap {
  position: relative;
  padding: clamp(26px, 3.4vw, 40px);
  border-radius: 24px;
  background: rgba(36, 5, 14, 0.5);
  border: 1px solid var(--line);
  backdrop-filter: blur(16px);
  box-shadow: 0 40px 90px -50px rgba(0, 0, 0, 1);
}
.form { display: grid; gap: 18px; }
.form__row { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.field { display: grid; gap: 8px; }
.field > span {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.field b { color: var(--blush); font-weight: 500; }
.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 14px 16px;
  border-radius: 13px;
  border: 1px solid var(--line);
  background: rgba(255, 235, 243, 0.075);
  color: var(--ink);
  font-family: var(--body);
  font-size: 15.5px;
  font-weight: 300;
  transition: border-color 0.25s ease, background 0.25s ease, box-shadow 0.25s ease;
}
.field textarea { resize: vertical; min-height: 118px; line-height: 1.6; }
.field select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23F6F4FF' stroke-opacity='.5' stroke-width='1.6' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
  padding-right: 42px;
  cursor: pointer;
}
.field option { background: #2A0410; color: var(--ink); }
.field input::placeholder,
.field textarea::placeholder { color: rgba(254, 224, 236, 0.24); }
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: rgba(254, 220, 234, 0.6);
  background: rgba(255, 235, 243, 0.12);
  box-shadow: 0 0 0 4px rgba(254, 220, 234, 0.16);
}
.field input:user-invalid,
.field textarea:user-invalid { border-color: rgba(255, 122, 122, 0.6); }

/* Spam trap. Hidden without pushing the page wider, which an off-screen
   left offset would do. */
.honey {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.form__note { margin: 0; font-size: 13px; color: var(--ink-faint); text-align: center; }
.form__err {
  display: none;
  margin: 0;
  padding: 12px 16px;
  border-radius: 12px;
  font-size: 14px;
  color: #FFC9C9;
  background: rgba(255, 122, 122, 0.12);
  border: 1px solid rgba(255, 122, 122, 0.3);
}
.form__err.show { display: block; }

.form__success { display: none; text-align: center; padding: 30px 10px; }
.form__success.show { display: block; animation: pop 0.5s cubic-bezier(0.2, 0.9, 0.3, 1.2); }
@keyframes pop { from { opacity: 0; transform: scale(0.94); } to { opacity: 1; transform: scale(1); } }
.form__tick {
  width: 60px; height: 60px;
  margin: 0 auto 22px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 26px;
  color: var(--wine);
  background: var(--grad-btn);
  box-shadow: 0 12px 34px -12px rgba(254, 220, 234, 0.8);
}
.form__success h3 { font-size: 1.8rem; margin-bottom: 10px; }
.form__success p { color: var(--ink-soft); margin: 0; }

/* ---------- footer ---------- */

.foot { position: relative; z-index: 2; border-top: 1px solid var(--line); padding: 40px 0; }
.foot__inner { display: flex; align-items: center; justify-content: space-between; gap: 22px; flex-wrap: wrap; }
.foot__brand { font-family: var(--display); font-weight: 600; letter-spacing: -0.03em; font-size: 16px; }
.foot__socials { display: flex; gap: 10px; }
.foot__socials a {
  width: 38px; height: 38px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  color: var(--ink-soft);
  transition: color 0.25s ease, border-color 0.25s ease, transform 0.25s ease;
}
.foot__socials a:hover { color: var(--ink); border-color: rgba(254, 224, 236, 0.3); transform: translateY(-2px); }
.foot__socials svg { width: 17px; height: 17px; fill: currentColor; }
.foot__legal { font-size: 13px; color: var(--ink-faint); }
.foot__heart { cursor: default; transition: color 0.4s ease; }
.foot__heart.on { color: var(--blush); }

/* ---------- birthday card ---------- */

.gift {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: 24px;
  background: rgba(28, 4, 11, 0.84);
  backdrop-filter: blur(22px);
  transition: opacity 0.7s ease, visibility 0.7s;
}
.gift[hidden] { display: none; }
.gift.is-closing { opacity: 0; visibility: hidden; }
.gift__confetti { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.gift__card {
  position: relative;
  width: min(100%, 560px);
  text-align: center;
  padding: clamp(34px, 5vw, 54px);
  border-radius: 26px;
  background: rgba(58, 10, 24, 0.99);
  border: 1px solid rgba(254, 224, 236, 0.14);
  box-shadow: 0 50px 110px -40px #000;
  animation: pop 0.7s cubic-bezier(0.2, 0.9, 0.3, 1.15);
}
.gift__eyebrow {
  font-size: 11.5px; font-weight: 500; letter-spacing: 0.24em;
  text-transform: uppercase; color: var(--ink-faint); margin: 0 0 18px;
}
.gift__title { font-size: clamp(2rem, 5.4vw, 3rem); margin-bottom: 24px; }
.gift__title em {
  background: var(--grad);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.gift__body p { color: var(--ink-soft); margin: 0 0 16px; }
.gift__from { margin: 26px 0 0; font-family: var(--serif); font-style: italic; font-size: 1.15rem; color: var(--ink-soft); }
.gift .btn { margin-top: 12px; }

/* ---------- opening shot ----------

   A camera lens, built here rather than filmed: it starts tilted and catching
   the light, turns to face you, then the view pushes through the glass until
   the dark centre fills the screen and the site comes up out of it. The lens is
   in the site's own wine and blush so the hand-off is seamless, since the page
   underneath opens on the wine banner.

   Everything is drawn with gradients, so there is no video to download and it
   stays sharp at any size. Timings live in site.js next to INTRO_MS. */

.intro {
  position: fixed;
  inset: 0;
  z-index: 300;              /* over the nav at 50 and the birthday card at 200 */
  display: grid;
  place-items: center;
  overflow: hidden;
  background:
    radial-gradient(125% 95% at 50% 44%, #340815 0%, #1B040C 44%, #0A0205 100%);
}
.intro.is-out {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.75s ease;
}
/* display: grid above beats the browser's own [hidden] rule, so without this
   line setting intro.hidden leaves the overlay sitting over the whole page. */
.intro[hidden] { display: none; }

.intro__lens {
  position: relative;
  width: min(58vw, 54svh);
  aspect-ratio: 1;
  border-radius: 50%;
  will-change: transform;
  animation: introLens 3.4s both;

  /* the barrel: a dark body with a lit top edge and a deep shadow beneath */
  background: radial-gradient(circle closest-side at 50% 50%,
    #140308 0 60%, #240610 60% 72%, #100206 72% 84%,
    #2E0813 84% 94%, #0C0205 94% 100%);
  box-shadow:
    inset 0 4px 5px rgba(255, 228, 240, 0.22),
    inset 0 -14px 30px rgba(0, 0, 0, 0.9),
    inset 0 0 0 1px rgba(255, 228, 240, 0.07),
    0 50px 90px -28px rgba(0, 0, 0, 0.95);
}

/* the machined ridges around the focus ring */
.intro__barrel {
  position: absolute;
  inset: 7%;
  border-radius: 50%;
  animation: introDetail 3.4s both;
  background: repeating-radial-gradient(circle at 50% 50%,
    rgba(255, 228, 240, 0.07) 0 1px,
    rgba(255, 228, 240, 0) 1px 5px);
}

/* the scale marks, blush here where the reference has them in gold */
.intro__ticks {
  position: absolute;
  inset: 21%;
  border-radius: 50%;
  background: repeating-conic-gradient(from -20deg,
    rgba(6, 1, 3, 0.88) 0deg 1.3deg,
    rgba(6, 1, 3, 0) 1.3deg 12deg);
  animation: introDetail 3.4s both;
  /* closest-side matters: a plain `circle` sizes to the farthest corner, which
     put this whole ring outside the rounded edge where it was clipped away. */
  -webkit-mask-image: radial-gradient(circle closest-side, transparent 0 76%, #000 82% 100%);
  mask-image: radial-gradient(circle closest-side, transparent 0 76%, #000 82% 100%);
}

/* the glass, with two soft reflections sitting off centre */
.intro__glass {
  position: absolute;
  inset: 31%;
  border-radius: 50%;
  background:
    radial-gradient(circle at 37% 31%, rgba(255, 242, 248, 0.92) 0 3.5%, rgba(255, 242, 248, 0) 21%),
    radial-gradient(circle at 61% 63%, rgba(254, 220, 234, 0.5) 0 2.5%, rgba(254, 220, 234, 0) 15%),
    radial-gradient(circle closest-side at 50% 44%, #4A0C1B 0%, #22050D 54%, #0A0206 100%);
  box-shadow:
    inset 0 0 34px rgba(0, 0, 0, 0.9),
    inset 0 4px 7px rgba(255, 228, 240, 0.14);
}
/* Passing through the glass puts the screen into darkness, which is what the
   site then fades up out of. */
.intro__glass::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: #0A0206;
  animation: introThrough 3.4s both;
}

/* the light raking across it while the lens is still turning */
.intro__flare {
  position: absolute;
  inset: -30%;
  pointer-events: none;
  background: linear-gradient(116deg,
    transparent 34%, rgba(231, 137, 159, 0.3) 47%, rgba(255, 242, 248, 0.14) 51%, transparent 64%);
  animation: introFlare 3.4s both;
}

.intro__skip {
  position: absolute;
  right: clamp(16px, 3vw, 34px);
  bottom: clamp(16px, 3vw, 34px);
  padding: 10px 20px;
  border-radius: 100px;
  border: 1px solid rgba(254, 224, 236, 0.28);
  background: rgba(255, 235, 243, 0.06);
  color: var(--ink-soft);
  font-family: var(--body);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  cursor: pointer;
  opacity: 0;
  animation: introSkipIn 0.5s ease 0.9s forwards;
}
.intro__skip:hover { color: var(--ink); border-color: rgba(254, 224, 236, 0.5); }

/* Turns to face you on an ease out, holds, then accelerates into the glass.
   The per-keyframe timing functions are what give it those two distinct
   movements rather than one even slide. */
@keyframes introLens {
  0% {
    transform: perspective(1000px) rotateX(58deg) rotateZ(-24deg) scale(0.84);
    animation-timing-function: cubic-bezier(0.2, 0.8, 0.25, 1);
  }
  34% {
    transform: perspective(1000px) rotateX(0deg) rotateZ(0deg) scale(1);
    animation-timing-function: ease-in-out;
  }
  50% {
    transform: perspective(1000px) rotateX(0deg) rotateZ(0deg) scale(1.06);
    animation-timing-function: cubic-bezier(0.5, 0, 0.85, 0.42);
  }
  100% { transform: perspective(1000px) rotateX(0deg) rotateZ(0deg) scale(20); }
}
@keyframes introFlare {
  0%   { opacity: 0; transform: translate3d(-38%, -22%, 0); }
  18%  { opacity: 1; }
  46%  { opacity: 0.5; transform: translate3d(26%, 16%, 0); }
  62%, 100% { opacity: 0; transform: translate3d(40%, 24%, 0); }
}
/* the machining and the markings fade as the view closes on the glass, so the
   last thing on screen is the dark centre rather than a wall of tick marks */
@keyframes introDetail {
  0%, 50% { opacity: 1; }
  76%, 100% { opacity: 0; }
}
@keyframes introThrough {
  0%, 58% { opacity: 0; }
  90%, 100% { opacity: 1; }
}
@keyframes introSkipIn { to { opacity: 1; } }

/* The hand-off. The intro pushes into the dark centre of a lens, and the banner
   then pulls back out of the lens on the camera in Lauren's hands, so the two
   read as one continuous move. site.js sets the origin to the exact middle of
   that camera's glass, measured on the image rather than guessed, so the pull
   back is anchored on it however the banner is sized. Only the banner scales,
   not the whole page: at this magnification nothing else is on screen anyway,
   and scaling the page with its videos and canvas would be far heavier. */
.banner.is-zooming {
  animation: introZoomOut 1.6s cubic-bezier(0.16, 0.84, 0.28, 1) both;
  will-change: transform;
}
/* The translate is what makes this a match cut rather than just a zoom. The
   intro's lens sits dead centre of the screen, but the camera in the artwork
   does not, so the banner starts shifted to put its lens exactly where the
   intro left off and drifts back to its resting place as it pulls out.
   site.js measures both offsets and sets them here. */
@keyframes introZoomOut {
  0%   { transform: translate(var(--zx, 0px), var(--zy, 0px)) scale(var(--zs, 14)); }
  100% { transform: translate(0px, 0px) scale(1); }
}
/* the chrome stays out of the way until the move has mostly landed. The
   opacity transition is folded into the base .nav rule rather than added here,
   because a second `transition` declaration replaces the shorthand outright and
   would drop the background and padding transitions the stuck nav relies on. */
.nav.is-holding { opacity: 0; }

/* ---------- scroll reveal ---------- */

.reveal {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 0.85s cubic-bezier(0.2, 0.8, 0.3, 1), transform 0.85s cubic-bezier(0.2, 0.8, 0.3, 1);
}
.reveal.in { opacity: 1; transform: none; }
.reveal.d1 { transition-delay: 0.08s; }
.reveal.d2 { transition-delay: 0.16s; }
.reveal.d3 { transition-delay: 0.24s; }
.reveal.d4 { transition-delay: 0.32s; }
.reveal.d5 { transition-delay: 0.4s; }

/* ---------- responsive ---------- */

@media (max-width: 900px) {
  /* The hero holds a full screen on a laptop, where the banner above it also
     fills one and the pairing reads as two deliberate openings. On a tablet or
     a phone the banner is only a few hundred pixels tall, so that full screen
     left the headline floating in the middle of an empty field. Sized to its
     own content here instead, which brings it up under the banner. */
  .hero {
    min-height: 0;
    padding: clamp(54px, 9vw, 88px) 0 clamp(62px, 10vw, 96px);
  }

  .about, .contact { grid-template-columns: 1fr; }
  /* stacked there is nothing to overlap, so the three parts simply follow on */
  .about__head   { grid-column: 1; grid-row: 1; }
  .about__visual { grid-column: 1; grid-row: 2; }
  .about__text   { grid-column: 1; grid-row: 3; }
  .about__big    { font-size: min(15.6vw, 116px); }
  /* the picture drops below the copy rather than disappearing */
  .about__visual { margin-top: 10px; }
  .portrait { width: min(460px, 100%); justify-self: center; }
  /* stacked, the two columns travel together, so the copy stops drifting */
  .about__text { transform: none; }
  .portrait { transform: translate3d(0, calc(var(--p) * -14px), 0); }
  .portrait__chip {
    transform: translate3d(0, calc(var(--p) * 18px), 0);
    left: 2%;
    top: 5%;
    font-size: 11px;
    padding: 7px 13px;
  }
  /* the picture is now under the copy, so the wine pool follows it down */
  #about {
    background:
      radial-gradient(70% 26% at 50% 82%, rgba(122, 1, 20, 0.42), rgba(122, 1, 20, 0) 74%),
      linear-gradient(180deg,
        rgba(30, 4, 11, 0) 0%,
        rgba(30, 4, 11, 0.95) 5%,
        rgba(30, 4, 11, 0.95) 95%,
        rgba(30, 4, 11, 0) 100%);
  }
}
@media (max-width: 640px) {
  body { font-size: 16px; }
  .nav__links { display: none; }
  .form__row { grid-template-columns: 1fr; }
  .work-grid { grid-template-columns: 1fr 1fr; gap: 12px; }
  .w-card__title { font-size: 0.9rem; }
}

/* ---------- reduced motion ---------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .reveal { opacity: 1; transform: none; }
  /* site.js skips the opening shot entirely here, this is just belt and braces */
  .intro { display: none; }
  .about__text, .portrait, .portrait__img, .portrait__chip { transform: none; }
  .portrait::before { opacity: 1; transform: none; }
}
