/* ── FONT ── */
@font-face {
  font-family: 'Separat';
  src: url('./fonts/separat-regular.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;

  /* Without this the default is `auto`, which browsers treat as block: text is
     invisible for up to three seconds and then every metric on the page
     changes at once, moving the document under your finger. `swap` shows the
     fallback immediately, so the only later change is the glyphs themselves. */
  font-display: swap;
}

/* ── VARIABLES ── */
:root {
  /* The one place the mobile breakpoint is defined. common.js reads it so the
     media queries below and the JS behaviour can never disagree. */
  --bp-mobile:   768px;

  /* Height of the fixed bar. Everything that has to clear it — page padding,
     anchor scroll offset — is derived from this rather than guessing. */
  --nav-h:       3.2rem;

  /* ── Section reveal ──
     --reveal-time     how long one element takes to arrive
     --reveal-ease     its curve. This one decelerates hard and coasts to a
                       stop, which is what reads as smooth rather than as a
                       slide that simply ends
     --reveal-stagger  the gap between siblings arriving ── */
  --reveal-time:    1.1s;
  --reveal-ease:    cubic-bezier(0.16, 1, 0.3, 1);
  --reveal-stagger: 110ms;

  --navy:        #00156C;
  --teal:        #80CED7;
  --green-dark:  #0F550A;
  --green-light: #97C621;
  --brown:       #311E10;
  --orange:      #DD6031;
}

/* ── RESET ── */
*, *::before, *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* ── BASE ── ───────────────────────────────────────────────────────────────────
   Root font-size scales fluidly between 13px (narrow viewport) and 30px (wide).
   ALL spacing/sizing in this file is in rem so the whole design scales as one
   unit with the root. At full screen 1rem = 30px (the "design baseline").
   Do not introduce raw px values for layout — it breaks uniform scaling.
   ── */
html {
  font-size: clamp(17px, 2.1vw, 30px);
  scroll-padding-top: calc(var(--nav-h) + 0.667rem);
  scroll-behavior: smooth;

  /* The page does not move sideways, ever.
     `clip` rather than `hidden` because hidden would make this a scroll
     container, which quietly changes how anchors and sticky positioning
     behave; clip just refuses to show anything past the edge. The `hidden`
     line before it is the fallback for browsers without clip.
     overscroll-behavior-x kills the horizontal rubber-band as well, so a
     diagonal swipe cannot drag the whole page with it. */
  overflow-x: hidden;
  overflow-x: clip;
  overscroll-behavior-x: none;
}

body {
  background-color: var(--navy);
  color: var(--teal);
  font-family: 'Separat', sans-serif;
  padding: 0 1.267rem;
}

body.contact-page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

a {
  color: var(--teal);
  text-decoration: none;
}

a:hover {
  opacity: 1;
  text-decoration: underline;
  text-underline-offset: 0.12em;
}

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

/* ── NAV ── */
nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  padding: 0.8rem 1.667rem;
  background-color: var(--navy);
  transition: background-color 0.3s ease, color 0.3s ease, padding 0.3s ease;
}

/* Inverted state when page is scrolled */
nav.scrolled {
  background-color: rgba(128, 206, 215, 0.6);
  padding: 0.6rem 1.667rem;
}

nav.scrolled a {
  color: var(--navy);
}

.logo {
  color: var(--teal);
}

nav.scrolled .logo {
  color: var(--navy);
}

.logo-mark {
  position: relative;
  display: block;
  width: 9.333rem;
  height: 1.6rem;
}

.logo-mark img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: opacity 0.3s ease;
}

.logo-default {
  opacity: 1;
}

.logo-scrolled {
  opacity: 0;
}

nav.scrolled .logo-default {
  opacity: 0;
}

nav.scrolled .logo-scrolled {
  opacity: 1;
}

nav ul {
  list-style: none;
  display: flex;
  gap: 1.067rem;
  line-height: 1;
}

nav ul li a {
  font-size: 1.3rem;
  letter-spacing: 0.04em;
}

/* ── SECTION REVEAL: the pre-paint state ──
   These are hidden before a single line of script has run, using the class the
   inline snippet in each page's <head> sets. Without it the browser paints the
   page in place and common.js — the last thing to run — then hides it, so the
   load flickers.

   :not(.reveal) is what hands over: the moment common.js marks an element, this
   rule stops applying and .reveal below owns its state. Anything common.js does
   not mark is released when it drops the class off <html>, so a selector listed
   here but skipped there can never strand content invisible.

   Mirrors the REVEAL list in common.js — keep the two in step. ── */
html.js-reveal body:not(.contact-page) :is(
  .hero-title, .hero-video-wrap, .hero-tagline, .hero-cta,
  .work,
  .project-title-bar, .project-hero, .project-copy, .project-right,
  .more-projects,
  .about-heading, .about-bio-text, .about-bio-photo, .about-credentials,
  .about-projects
):not(.reveal) {
  opacity: 0;
  transform: translateY(2rem);
}

/* ── SECTION REVEAL ──
   Added by common.js, so a page with scripting off simply shows everything
   rather than hiding content behind an animation that never runs. Honours
   prefers-reduced-motion — the observer is not even created in that case. ── */
.reveal {
  opacity: 0;
  transform: translateY(2rem);

  /* Opacity settles a little before the movement does, so an element has
     arrived visually just as it stops travelling — matching the two exactly
     makes the end of the motion the moment you notice it. */
  transition: opacity calc(var(--reveal-time) * 0.72) ease-out,
              transform var(--reveal-time) var(--reveal-ease);
  transition-delay: calc(var(--reveal-i, 0) * var(--reveal-stagger));
}

.reveal.is-in {
  opacity: 1;
  transform: none;
}

/* ── MOBILE NAVIGATION ──
   Injected by common.js and hidden at every width except phone. The fixed bar
   slides away once you scroll, and these two floating controls take its place.
   ── */
.mobile-bar,
.mobile-menu {
  display: none;
}

.mobile-bar[hidden],
.mobile-menu[hidden] {
  display: none;
}

/* ── HERO ── */
.hero {
  padding: 3rem 0.267rem 1.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.267rem;
}

.hero-title {
  align-self: flex-start;
  font-size: 1.45rem;
  font-weight: 100;
  color: var(--teal);
  text-transform: uppercase;
  letter-spacing: 0.01em;
}

/* ── Hero reel ──
   The reel carries its own burned-in titles in the bottom-left corner, so the
   frame must never be cropped horizontally or they get sliced off. Giving the
   box the video's own 16:9 ratio means object-fit: cover has nothing to trim.
   The width cap keeps a tall hero from overflowing the viewport on wide, short
   windows — it shrinks the box rather than cropping the picture. ── */
.hero-video-wrap {
  position: relative;
  width: min(100%, calc(88vh * 16 / 9));
  margin-inline: auto;
  aspect-ratio: 16 / 9;
  border-radius: 0.4rem;
  overflow: hidden;
  cursor: pointer;
}

.hero-tagline {
  max-width: 36rem;
  margin: 1.4rem auto 0;
  text-align: center;
  color: var(--teal);
  font-size: 1.1rem;
  line-height: 1.15;
  letter-spacing: 0.01rem;
}

.hero-cta {
  display: inline-block;
  margin-top: 1.5rem;
  color: var(--teal);
  font-size: 1.45rem;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}

.hero-video-wrap video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero-video-wrap .play-btn {
  position: absolute;
  bottom: 0.467rem;
  left: 0.467rem;
  z-index: 2;
  pointer-events: none;
}

/* ── WORK ── */
.work {
  padding: 0 1.6rem 3.333rem;
}

.work-heading {
  font-size: 1.45rem;
  font-weight: 100;
  color: var(--teal);
  text-transform: uppercase;
  letter-spacing: 0.01em;
  margin-bottom: 0.4rem;
}

/* 3-column grid; align-items: start so cards don't stretch to match their neighbour */
.work-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  column-gap: 0.467rem;
  row-gap: 4rem;
  align-items: start;
}

.work-card {
  display: flex;
  flex-direction: column;
  gap: 0.333rem;
}

/* ── Thumbnail container ──
   aspect-ratio is set per-card via --card-ratio, declared on .work-card so
   the carousel can compute a width from it too.
   Ratios are taken directly from the reference dimensions (e.g. 316 / 405)
   so proportions stay correct at any screen width. ── */
.work-thumb {
  position: relative;
  border-radius: 0.4rem;
  overflow: hidden;
  cursor: pointer;
  aspect-ratio: var(--card-ratio);
}

/* Both the image and video fill the container */
.work-thumb img,
.work-thumb video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s ease;
}

/* ── Poster ──
   Sits above the video showing the frame the clip starts on, so the card is a
   real image at rest and never flashes black while the clip loads. Fades out
   when the cameo starts and back in as it ends. ── */
.work-poster {
  z-index: 1;
  opacity: 1;
  transition: transform 0.4s ease, opacity 0.3s ease;
}

.work-card.playing .work-poster {
  opacity: 0;
}

/* .ending is set while the clip's audio is fading out, so the picture comes
   back in step with the sound rather than after it. */
.work-card.playing.ending .work-poster {
  opacity: 1;
  transition: transform 0.4s ease, opacity 0.4s ease;
}

/* Zoom on hover — only when paused (no .playing class on card) */
.work-thumb:hover video,
.work-thumb:hover .work-poster {
  transform: scale(1.05);
}

/* When playing, disable the zoom even if still hovering */
.work-card.playing .work-thumb:hover video,
.work-card.playing .work-thumb:hover .work-poster {
  transform: scale(1);
}

.work-thumb:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 3px;
}

/* Play / Pause label */
.play-btn {
  position: absolute;
  bottom: 0.467rem;
  left: 0.467rem;
  z-index: 2;
  color: var(--teal);
  font-family: 'Separat', sans-serif;
  font-size: 1.5rem;
  pointer-events: none; /* click is handled by the thumb container */
}

/* Project title below the card */
/* The root clamp bottoms out at 13px, which would leave these labels at under
   12px on a phone. They are the only navigation out of a card, so they get a px
   floor — it only wins below roughly 850px and changes nothing on desktop. */
.work-title {
  font-size: max(1.05rem, 19px);
  color: var(--teal);
  padding-left: 0.067rem;
}

.work-title a {
  text-decoration: none;
}

.work-title a:hover {
  text-decoration: underline;
  opacity: 1; /* override the global a:hover opacity fade */
}

/* ── ABOUT PAGE ── */
.about-main {
  padding: 3.067rem 0.5rem 2.667rem;
}

.about-heading {
  font-size: 1.4rem;
  font-weight: normal;
  color: var(--teal);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin-bottom: 0.5rem;
}

.about-bio {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.333rem;
  align-items: start;
  padding-bottom: 2.667rem;
}

.about-bio-left,
.about-bio-right {
  display: flex;
  flex-direction: column;
  gap: 1.333rem;
}

.about-bio-text {
  display: flex;
  flex-direction: column;
  gap: 1.4rem;
}

.about-bio-text p {
  color: var(--teal);
  font-size: 1.1rem;
  line-height: 1.2;
}

.about-bio-photo {
  position: relative;
  border-radius: 0.667rem;
  overflow: hidden;

  /* Its height came from the video's own metadata, which arrives over the
     network — so the block was zero-height until then and everything below it
     jumped when it landed. The source is 1920x1080. */
  aspect-ratio: 16 / 9;
}

.about-bio-photo img,
.about-bio-photo video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.about-credentials {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2rem;
  padding: 2rem;
  flex-wrap: wrap;
}

.about-credentials img {
  max-height: 4rem;
  min-height: 4rem;        /* holds the row's height before the logos decode */
  object-fit: contain;
}

.about-projects-heading {
  font-size: 1.4rem;
  font-weight: normal;
  color: var(--teal);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin-bottom: 1.067rem;
}

/* ── FOOTER ── */
footer {
  padding: 0.333rem 1.2rem 1.6rem;
  min-height: 4.667rem;
  margin: 0 -1.267rem; /* matches body padding so footer spans full viewport width */
  background-color: var(--navy);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: end;
  position: relative;
  filter: drop-shadow(0 0.133rem 0.267rem rgba(255, 255, 255, 0));
  transition: filter 0.2s ease;
}

footer:hover {
  filter: drop-shadow(0 0.133rem 0.267rem rgba(255, 244, 218, 0.4));
}

.footer-left {
  display: flex;
  flex-direction: column;
  gap: 0.133rem;
  line-height: 1.1;
  margin-left: 2.4rem;
}

.footer-center {
  display: flex;
  gap: 1.067rem;
  align-items: center;
}

.footer-row {
  display: flex;
  gap: 1.067rem;
}

.footer-socials {
  margin-left: 2.5em; /* indent so Instagram sits roughly under the 'b' in Youtube — tune to taste */
}

.footer-contact,
.footer-terms {
  font-size: 0.85rem;
}

.footer-right {
  display: flex;
  gap: 1.067rem;
  align-items: center;
  justify-self: end;
  margin-right: 1.067rem;
}

.footer-year {
  font-size: 0.85rem;
}

footer a {
  font-size: 0.85rem;
}

/* CONTACT PAGE */
.contact-page footer {
  margin-top: auto;
}

.contact-main {
  flex: 1;
  min-height: calc(100vh - 4.667rem);
  padding: 4.067rem 0.6rem 1.867rem;
}

.contact-panel {
  /* no max-width — uniform scaling handles sizing */
}

.contact-title {
  margin-bottom: 1.133rem;
  font-size: 1.5rem;
  font-weight: normal;
  letter-spacing: 0.01em;
  color: var(--teal);
}

.contact-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 2.133rem;
  align-items: start;
}

.contact-copy {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  color: var(--teal);
  font-size: 1.1rem;
  /* Unitless, and never below 1. This was 0.6rem against a 1.1rem font — a
     line box barely half the height of the text — so the moment a sentence
     wrapped, its second line landed on top of the first. The 0.6rem was doing
     the job of the `gap` above, which is what actually spaces the sentences. */
  line-height: 1.2;
  text-align: right;
  align-self: end;
}

.contact-form {
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
  max-width: 17rem;
}

.contact-form input,
.contact-form textarea,
.contact-form button {
  width: 100%;
  border: 0;
  border-radius: 0.6rem;
  font-family: 'Separat', sans-serif;
  font-size: 0.95rem;
}

.contact-form input,
.contact-form textarea {
  padding: 0.6rem 0.933rem;
  color: rgba(0, 21, 108, 0.55);
  background: rgba(243, 239, 236, 0.92);
  outline: none;
}

.contact-form input::placeholder,
.contact-form textarea::placeholder {
  color: rgba(0, 21, 108, 0.55);
}

.contact-form input {
  min-height: 1.933rem;
}

.contact-form textarea {
  min-height: 2.933rem;
  resize: vertical;
}

.contact-form button {
  min-height: 2.2rem;
  color: var(--navy);
  background-color: var(--teal);
  cursor: pointer;
  transition: filter 0.2s ease;
}

.contact-form button:hover {
  filter: brightness(1.03);
}

.contact-form button:disabled {
  cursor: default;
  opacity: 0.7;
}

/* Off-screen rather than display: none — some bots skip fields that are not
   rendered, and the point is for them to fill it in. */
.contact-trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.contact-status {
  min-height: 1.2em;
  color: var(--teal);
  font-size: 0.85rem;
  line-height: 1.2;
}

/* ── PROJECT PAGE ────────────────────────────────────────────────────────────*/

/* Title bar sits between the fixed nav and the video */
.project-title-bar {
  display: flex;
  align-items: baseline;
  gap: 0.667rem;
  padding: 3.333rem 0 1.667rem;
}

.project-title {
  font-size: 1.8rem;
  font-weight: normal;
  color: var(--teal);
  text-transform: uppercase;
  word-spacing: 0.05em;
}

.project-year {
  font-size: 0.85rem;
  color: var(--teal);
  opacity: 0.5;
}

.project-hero {
  padding: 0 0 4rem;
}

/* ── Project hero ──
   Same trap the demo reel fell into: a fixed height plus object-fit: cover
   crops the picture, and which edges get cropped depends on the window shape —
   so a half-width window sliced the sides off. These videos are not all the
   same shape either (DJ Glove is 3:2 where the rest are 16:9), so the ratio is
   read off the video itself by project.js and written to --video-ratio. The
   16/9 below is only what's used for the moment before metadata arrives. ── */
.project-video-wrap {
  --video-ratio: 16 / 9;
  position: relative;
  width: min(100%, calc(88vh * (var(--video-ratio))));
  margin-inline: auto;
  aspect-ratio: var(--video-ratio);
  border-radius: 0.4rem;
  overflow: hidden;
  cursor: pointer;
}

.project-video-wrap video,
.project-poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Above the video, below the Play label. Stays until the video has painted a
   frame of its own — the same frame — so the fade is invisible in practice;
   it only softens things if the two ever differ. */
.project-poster {
  z-index: 1;
  pointer-events: none;
  transition: opacity 0.25s ease;
}

.project-video-wrap.is-playing .project-poster {
  opacity: 0;
}

.project-content {
  padding: 0 0 4rem;
}

.project-body {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.533rem;
  align-items: stretch;
}

/* Left column */
.project-text {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}


/* line-height 1.0 set the lines touching, which did as much damage to
   legibility as the size did. The px floor keeps this readable once the root
   clamp bottoms out; the image column stretches to match, so text and picture
   stay aligned. */
.project-copy {
  font-size: max(0.9rem, 19px);
  line-height: 1.2;
}

/* Right column */
.project-right {
  position: relative;
}

.project-images {
  position: absolute;
  inset: -0.667rem 0;
}

/* ── Project image slideshow ──
   Built by project.js inside .project-images, which keeps its box exactly as
   before — only what fills it changed. Every picture is shown whole on black,
   so a portrait poster or a square still fits without cropping or reflowing
   the page. ── */
.slideshow {
  --slide-fade: 0.8s;
  overflow: hidden;
  border-radius: 0.4rem;   /* matches the videos */
  background: #000;
}

.slide-track {
  position: absolute;
  inset: 0;
}

.slideshow .slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity var(--slide-fade) ease;
  user-select: none;
}

.slideshow .slide.is-active {
  opacity: 1;
}

/* Same glyphs and teal as the site's other carousels, with a soft shadow so
   they still read over a light picture. Hidden until a mouse is over the show. */
.slide-arrow {
  position: absolute;
  top: 50%;
  z-index: 2;
  padding: 0.4rem 0.5rem;
  border: 0;
  background: none;
  color: var(--teal);
  font-family: 'Separat', sans-serif;
  font-size: 1.6rem;
  line-height: 1;
  text-shadow: 0 0 0.4rem rgba(0, 0, 0, 0.6);
  cursor: pointer;
  opacity: 0;
  transform: translateY(-50%);
  transition: opacity 0.25s ease, transform 0.18s ease;
}

.slide-prev { left: 0.2rem; }
.slide-next { right: 0.2rem; }

.slideshow:hover .slide-arrow,
.slide-arrow:focus-visible {
  opacity: 1;
}

.slide-arrow:hover  { transform: translateY(-50%) scale(1.12); }
.slide-arrow:active { transform: translateY(-50%) scale(1.04); }

.slide-arrow:focus-visible,
.slide-dot:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
}

/* ── Touch mode ──
   Phone width or no hover. The slides stop stacking and become a row the
   browser scrolls itself, snapping one picture at a time, so a swipe drags the
   picture under the finger. No arrows — the swipe does their job.

   Mirrors SWIPE_QUERY in project.js; 768px is --bp-mobile. ── */
@media (max-width: 768px), (hover: none) {
  .slide-track {
    display: flex;
    /* A clean break between pictures mid-swipe, and room enough that a
       fractional pixel width can never leave a sliver of the next one showing. */
    gap: 1rem;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;   /* no page drag or back-swipe at the ends */
    scrollbar-width: none;
  }

  .slide-track::-webkit-scrollbar { display: none; }

  .slideshow .slide {
    position: static;
    flex: 0 0 100%;
    opacity: 1;
    transition: none;
    scroll-snap-align: start;
    scroll-snap-stop: always;         /* one picture per flick, never skip two */
  }

  .slide-arrow { display: none; }
}

.slide-dots {
  position: absolute;
  left: 50%;
  bottom: 0.5rem;
  z-index: 2;
  display: flex;
  transform: translateX(-50%);
}

/* The button is the tap target; the dot is drawn inside it, so a 6px mark
   still has a finger-sized hit area. */
.slide-dot {
  padding: 0.3rem;
  border: 0;
  background: none;
  cursor: pointer;
}

.slide-dot::before {
  content: '';
  display: block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #fff;
  opacity: 0.4;
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.slide-dot:hover::before { opacity: 0.75; }

.slide-dot[aria-current]::before {
  opacity: 1;
  transform: scale(1.2);
}

@media (prefers-reduced-motion: reduce) {
  .slideshow .slide { transition: none; }
}

/* More Projects */
/* --section-inset is coupled to --card-width on .carousel below: a narrower
   inset fits more cards, which leaves the carousel less to scroll. Drop this
   to ~1.6rem (matching .work) and all four cards fit at full screen, so the
   arrows and fades never appear. Raise one, lower the other. */
.more-projects {
  --section-inset: 2.4rem;
  padding: 0 var(--section-inset) 3.333rem;
}

.more-projects-label {
  font-size: 1.3rem;
  text-transform: uppercase;
  margin-bottom: 1.067rem;
}

/* ── MORE PROJECTS CAROUSEL ──
   A 3-up grid leaves an orphan row once there are four cards, so this scrolls
   horizontally instead.

   Cards are sized exactly as they are on the homepage: a FIXED WIDTH, with
   height falling out of each card's --card-ratio. That is what gives the row
   its ragged bottom edge — the uneven heights are the point, not a side
   effect. (An earlier version fixed the height instead, which squared
   everything off.) The track is top-aligned so the cards hang from a common
   line, same as the grid.
   ── */
.carousel {
  position: relative;

  /* ── Tuning knobs ──────────────────────────────────────────────────────────
     --card-width   width of every card. Height follows from --card-ratio, so
                    this alone scales the whole row.
     --card-gap     space between cards. This and --section-inset on
                    .more-projects both eat horizontal room, so they trade off
                    against each other: widen the gap and you can narrow the
                    inset without any more cards coming into view.
     --fade-width   how far the fade reaches in from each edge. Bigger = more
                    of the neighbouring card is veiled.
     --fade-solid   how much of that width is FULLY navy before it starts
                    falling off. This is the severity: 0% is a soft wash that
                    only tints the card, 50% genuinely buries the edge.
     --fade-curve   where the midpoint of the falloff sits. Raise it for a
                    late, abrupt edge; lower it for a long, gentle one.
     --scroll-duration
                    how long an arrow click takes to move one card. The
                    browser owns the timing of scroll-behavior: smooth, so
                    this is animated in JS instead.
     --fade-ramp    how far you scroll before a fade reaches full strength.
                    This is the "feel" knob: small values snap the fade in
                    almost immediately, large ones bleed it in slowly across
                    the scroll. There is deliberately NO transition on the
                    fade — its opacity is written per scroll frame by
                    initCarousel(), so it tracks your finger exactly. A
                    transition here would reintroduce the lag this replaced.
     ───────────────────────────────────────────────────────────────────────── */
  --card-width: 14rem;
  --card-gap: 1.2rem;
  --fade-width: 5rem;
  --fade-solid: 18%;
  --fade-curve: 55%;
  --fade-ramp: 6.5rem;
  --scroll-duration: 450ms;

  /* Overwritten by initCarousel() with the tallest thumbnail's height in px.
     The 100% fallback resolves to the old top: 50% if JS has not run. */
  --thumb-h: 100%;

  /* Space between an arrow and the edge of the artwork. The arrows sit outside
     the track, in the --section-inset gutter. Positive pushes them further out
     into the margin; negative pulls them back over the track's faded edge. */
  --arrow-gap: -0.1rem;
  --arrow-size: 1.8rem;
}

.carousel-track {
  display: flex;
  align-items: flex-start;      /* ragged bottoms, like the homepage grid */
  gap: var(--card-gap);
  overflow-x: auto;

  /* Swipe past the last card and the leftover scroll stops here instead of
     chaining outward and taking the page with it — which is what let an arced
     swipe push the whole layout sideways. */
  overscroll-behavior-x: contain;
  /* No scroll-behavior: smooth — the browser owns its duration and will not
     let you set it, so initCarousel() animates the arrow steps itself.

     No scroll-snap either, at this width. Several cards share the view here
     and bringIntoView() parks one a fade's width clear of the edge, which is
     not a snap position — snapping would drag it straight back. The media
     query switches snapping on for phones, where one card fills the view and
     the two positions coincide. */
  scrollbar-width: none;           /* Firefox */
  -ms-overflow-style: none;
}

.carousel-track::-webkit-scrollbar {
  display: none;
}

.carousel-track .work-card {
  flex: 0 0 auto;
  width: var(--card-width);
}

/* ── Work grid inside a carousel ──
   The homepage and About use the carousel's markup but lay out as a grid on
   desktop. Two classes outrank the single-class flex rules above, so this wins
   without !important. On a phone the media query flips it back to a row.

   initCarousel() needs no branch for this: a grid's scrollWidth equals its
   clientWidth, so it reports no overflow, hides both arrows and leaves the
   fades at zero. The same code runs on both layouts and simply has nothing to
   do on one of them. ── */
.carousel-track.work-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  column-gap: 0.467rem;
  row-gap: 4rem;
  align-items: start;
  overflow: visible;
}

.carousel-track.work-grid .work-card {
  width: auto;
}

/* ── Edge fades ──
   A card only half in view is veiled towards the edge it is leaving, which is
   what signals there is more to scroll to. Each side appears only when
   something is actually hidden that way, so a carousel with nothing to scroll
   has neither. Painted in the page's own navy rather than masked, so it
   behaves identically in every browser. ── */
.carousel::before,
.carousel::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  width: var(--fade-width);
  z-index: 2;
  pointer-events: none;
}

.carousel::before {
  left: 0;
  opacity: var(--fade-l, 0);
  background: linear-gradient(to right,
    var(--navy) 0,
    var(--navy) var(--fade-solid),
    rgba(0, 21, 108, 0.45) var(--fade-curve),
    transparent 100%);
}

.carousel::after {
  right: 0;
  opacity: var(--fade-r, 0);
  background: linear-gradient(to left,
    var(--navy) 0,
    var(--navy) var(--fade-solid),
    rgba(0, 21, 108, 0.45) var(--fade-curve),
    transparent 100%);
}

/* ── Arrows ──
   Bare glyphs out in the gutter on desktop, where there is nothing behind them
   to fight. On a phone they sit over the artwork, so the media query puts them
   back in the back-to-top's circle. Same arrow either way. ── */
.carousel-btn {
  position: absolute;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.4rem 0.333rem;        /* hit area only — nothing is painted */
  border: 0;
  background: none;
  color: var(--teal);
  font-family: 'Separat', sans-serif;
  font-size: var(--arrow-size);
  line-height: 1;
  cursor: pointer;
  transition: transform 0.18s ease, opacity 0.18s ease;
}

.carousel-btn:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 4px;
}

/* Beats .carousel-btn's own display rule, which would otherwise win over the
   browser default for [hidden]. */
.carousel-btn[hidden] {
  display: none;
}

/* Shifted fully clear of the track by its own width, so it lands in the gutter
   whatever the glyph measures. */
.carousel-prev {
  left: calc(-1 * var(--arrow-gap));
  top: calc(var(--thumb-h) / 2);
  transform: translate(-100%, -50%);
}

.carousel-next {
  right: calc(-1 * var(--arrow-gap));
  top: calc(var(--thumb-h) / 2);
  transform: translate(100%, -50%);
}

.carousel-prev:hover  { transform: translate(-100%, -50%) scale(1.12); }
.carousel-next:hover  { transform: translate(100%, -50%) scale(1.12); }
.carousel-prev:active { transform: translate(-100%, -50%) scale(1.04); }
.carousel-next:active { transform: translate(100%, -50%) scale(1.04); }
.carousel-btn:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 4px;
}

/* Beats .carousel-btn's own display rule, which would otherwise win over the
   browser default for [hidden]. */
.carousel-btn[hidden] {
  display: none;
}


/* ── TERMS PAGE ──
   Type is matched to the about page (1.1rem body), which is the site's
   long-form reading size. Two departures from the rest of the file, both on
   purpose:

   1. A capped measure — everything else runs full-bleed, but a legal document
      at full width gives 200-character lines.
   2. A px floor via max(). Elsewhere the root clamp is allowed to shrink type
      right down on narrow viewports; that is fine for a heading or a caption
      and not fine for thirteen sections of contract. The floor only ever wins
      below roughly 800px, so it changes nothing on desktop.
   ── */
.terms-main {
  padding: 3.067rem 0.5rem 2.667rem;
}

.terms-heading {
  font-size: max(1.4rem, 26px);
  font-weight: normal;
  color: var(--teal);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin-bottom: 0.5rem;
}

.terms-meta {
  color: var(--teal);
  font-size: max(0.9rem, 17px);
  line-height: 1.3;
  margin-bottom: 1.867rem;
  opacity: 0.75;
}

.terms-body {
  max-width: 34rem;
  color: var(--teal);
}

.terms-intro {
  font-size: max(1.2rem, 19px);
  line-height: 1.25;
  margin-bottom: 2rem;
}

.terms-body section {
  margin-bottom: 1.867rem;
}

.terms-body h2 {
  font-size: max(1.25rem, 20px);
  font-weight: normal;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin-bottom: 0.6rem;
}

.terms-body h3 {
  font-size: max(1.1rem, 18px);
  font-weight: normal;
  letter-spacing: 0.02em;
  margin: 1rem 0 0.4rem;
  opacity: 0.85;
}

.terms-body p,
.terms-body li {
  font-size: max(1.1rem, 18px);
  line-height: 1.2;
}

.terms-body p + p {
  margin-top: 0.6rem;
}

.terms-body ul {
  margin: 0.6rem 0 0.6rem 1.2rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.terms-colophon {
  margin-top: 2.4rem;
  opacity: 0.75;
}


/* ── RESPONSIVE ─────────────────────────────────────────────────────────────
   With uniform scaling, the only breakpoint left is the mobile stack point:
   below 768px viewport width, multi-column grids collapse to one column and
   horizontal padding tightens. Everything else scales linearly via the root
   font-size clamp.
   ── */

/* ── CONTACT: narrow, but not yet a phone ──
   The two-column split stops working a long way before the mobile breakpoint.
   The copy starts wrapping, the form keeps its 17rem cap, and both sit
   top-aligned in a section that is still a whole viewport tall — so the page
   fills its bottom half with empty navy.

   Stacked and centred instead: the copy reads first, the form sits under it,
   and the pair are centred in the space rather than pinned to the top. ── */
@media (max-width: 1100px) {
  .contact-main {
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  .contact-layout {
    grid-template-columns: 1fr;
    justify-items: center;
    gap: 1.733rem;
  }

  /* The form comes first in the markup so it is first for a keyboard and a
     screen reader; visually the copy introduces it. */
  .contact-copy {
    order: -1;
    align-self: auto;
    text-align: center;
    gap: 0.4rem;
  }

  .contact-title {
    text-align: center;
  }

  .contact-form {
    width: 100%;
    max-width: 20rem;
  }
}

@media (max-width: 768px) {
  /* ── Nav ──
     A phone gets the logo and nothing else; the hamburger is the navigation.
     The inverted scrolled state is switched off too — it was flashing teal for
     a moment just before the bar slid away. ── */
  /* The bar is given the same total height as the floating button's block
     (its offset plus its diameter plus the offset again), and its contents are
     centred — so the logo and the button share a centre line instead of the
     logo sitting a dozen pixels high. */
  /* Matches the floating button's block: its offset, its diameter, its offset
     again. Both then centre on the same line by construction. */
  :root {
    --nav-h: calc(3.2rem + 1.066rem);
  }

  /* Driven straight off scroll position by common.js rather than toggled with
     a class. A class plus a transition made the bar leave on its own schedule
     — you scrolled, then a moment later it animated away. Now it moves with
     your finger, so slow scrolling slides it slowly. */
  nav {
    padding: 0.533rem 0.667rem;
    align-items: center;
    min-height: var(--nav-h);
    transform: translateY(calc(var(--nav-away, 0) * -100%));
    transition: background-color 0.3s ease;
  }

  nav ul {
    display: none;
  }

  .logo-mark {
    width: 11.667rem;      /* was 9.333rem — same 5.83:1 shape, just larger */
    height: 2rem;
  }

  nav.scrolled {
    background-color: var(--navy);
    padding: 0.533rem 0.667rem;
  }

  nav.scrolled .logo-default  { opacity: 1; }
  nav.scrolled .logo-scrolled { opacity: 0; }

  /* ── Hero ──
     The reel runs edge to edge — at this size every pixel of width is height,
     and the gutters were costing about 15% of the picture. The section fills
     the screen and the video floats in the middle of it, which leaves the
     tagline sitting right on the fold rather than competing with the reel. ── */
  .hero {
    padding: calc(var(--nav-h) + 0.8rem) 0.667rem 1.333rem;

    /* ── Tuning the reel ──────────────────────────────────────────────────
       The source is 16:9. Cutting it taller means throwing width away, and
       the window has to keep the burned-in titles, which sit from 10% to 44%
       across the frame — so the shorter the reel, the more centred the window
       can be. Set the first two together from this table:

         ratio  height  keeps  focus   window   centre  tagline on load
         2/3    585px    38%    13%    8-46%     27%    hidden
         3/4    520px    42%    14%    8-50%     29%    hidden
         5/6    468px    47%    15%    8-55%     31%    hidden
         1/1    390px    56%    18%    8-64%     36%    visible
         5/4    312px    70%    27%    8-78%     43%    visible
         4/3    293px    75%    32%    8-83%     45%    visible
         3/2    260px    84%    51%    8-92%     50%    visible  (centred)

       Heights are for a 390px-wide screen; the reel scales with viewport
       WIDTH, so it is taller on a wider phone.

       "centre" is where the middle of the visible window falls — 50% is a
       properly centred picture, lower reads as left-shifted. The focus column
       is the furthest right each ratio can go while keeping the titles whole;
       push past it and they start getting clipped.

       "tagline on load" is a small-screen figure. Because the reel is sized by
       width and the reveal line is a share of height, the two do not track
       each other — on a tall phone the tagline sits above the line and slides
       up with the hero rather than waiting for a scroll.
       ── */
    --reel-ratio: 1/1;
    --reel-focus: 50%;
    --reel-offset: 1rem;
  }

  /* Cropped to 3:2 rather than shown at its native 16:9 — on a portrait screen
     width is the only source of height, and trading a little picture for 19%
     more of it is worth it. The crop leans left (object-position 25%) because
     the reel's burned-in titles live in the bottom-left corner: a centred crop
     would leave them only 2% of clearance, this leaves 6%.

     margin-bottom only, never margin-block: the free space all goes below, so
     the title stays with the video instead of being shoved to the top. */
  .hero-video-wrap {
    width: 100vw;
    max-width: none;
    aspect-ratio: var(--reel-ratio);
    margin-inline: calc(-1 * (1.267rem + 0.667rem));   /* out past both gutters */
    margin-top: var(--reel-offset);
    border-radius: 0;
  }

  .hero-video-wrap video {
    object-position: var(--reel-focus) center;
  }

  .work {
    padding: 0 0.667rem 1.333rem;
  }

  .work-grid {
    grid-template-columns: 1fr;
    row-gap: 1.333rem;
  }

  .about-main {
    padding: calc(var(--nav-h) + 0.8rem) 0 1.333rem;
  }

  /* ── Mobile nav ──
     The bar slides up out of the way instead of being hidden outright, so it
     comes back in place when you return to the top. ── */
  nav {
    transition: transform 0.3s ease, background-color 0.3s ease, padding 0.3s ease;
  }

  /* Top right, opposite the logo. The hamburger is always here; back-to-top
     drops in underneath it once you have scrolled. */
  .mobile-bar {
    position: fixed;
    top: 0.533rem;
    right: 0.667rem;
    z-index: 120;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.533rem;
  }

  .mobile-bar button {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 3.2rem;
    height: 3.2rem;
    padding: 0;
    border: 0.067rem solid var(--teal);
    border-radius: 50%;
    background-color: var(--navy);
    color: var(--teal);
    font-family: 'Separat', sans-serif;
    font-size: 1.6rem;
    line-height: 1;
    cursor: pointer;
  }

  .mobile-bar button[hidden] {
    display: none;
  }

  /* ── The morphing control ──
     One button doing two jobs. At the top of the page it is three lines; once
     scrolled it is an up arrow. The two icons are stacked in the same circle
     and slide past each other — the outgoing one leaves upward as the incoming
     one arrives from below, so it reads as one object moving rather than two
     things swapping.

     The button clips them, so neither is ever visible outside the circle. ── */
  .mobile-toggle {
    position: relative;
    overflow: hidden;
  }

  .mobile-icon {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .mobile-bars {
    flex-direction: column;
    gap: 0.267rem;
  }

  .mobile-bars span {
    display: block;
    width: 1.467rem;
    height: 0.133rem;
    background-color: var(--teal);
    transition: transform 0.25s ease, opacity 0.2s ease;
  }

  /* Both icons ride --nav-away, so the swap happens across the scroll rather
     than snapping at a threshold: at halfway, half of each. */
  .mobile-bars {
    transform: translateY(calc(var(--nav-away, 0) * -115%));
    opacity: calc(1 - var(--nav-away, 0));
  }

  .mobile-up {
    font-size: 1.667rem;
    transform: translateY(calc((1 - var(--nav-away, 0)) * 115%));
    opacity: var(--nav-away, 0);
  }

  /* Three lines become a cross while the menu is open. Only reachable at the
     top of the page, which is the only place the bars are showing. */
  .mobile-toggle.is-open .mobile-bars span:nth-child(1) { transform: translateY(0.4rem) rotate(45deg); }
  .mobile-toggle.is-open .mobile-bars span:nth-child(2) { opacity: 0; }
  .mobile-toggle.is-open .mobile-bars span:nth-child(3) { transform: translateY(-0.4rem) rotate(-45deg); }

  .mobile-menu {
    position: fixed;
    inset: 0;
    z-index: 110;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.6rem;
    background-color: var(--navy);
  }

  .mobile-menu a {
    color: var(--teal);
    font-size: 1.8rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
  }

  /* A 7rem inset would eat a phone screen, so the section goes back to the
     page gutters. */
  /* Same clearance every other page gets — 3.333rem was 50px against a 64px
     bar, so the project title sat behind it. */
  .project-title-bar {
    padding: calc(var(--nav-h) + 0.8rem) 0 1.2rem;
  }

  .more-projects {
    padding: 0 0.667rem 1.333rem;
  }

  /* ── Carousels on a phone ──
     One card in view with the next peeking, which is what makes it read as a
     carousel rather than a cropped grid. Sized in vw so it tracks the screen
     rather than the root font size. ── */
  /* One card, full width — no peek. The overhang reads well on desktop but on
     a phone it just looks like a cropped card. The fade still appears while
     you scroll, which is when it means something, but much gentler. */
  .carousel {
    --card-width: 100%;
    --card-gap: 1.6rem;
    --fade-width: 2rem;
    --fade-solid: 4%;
    --fade-curve: 40%;
    --fade-ramp: 4rem;
    --arrow-size: 1.5rem;
  }

  /* ── Native snapping ──
     The browser owns the snap on a phone. It is the only thing that can act
     on a flick straight away: once a finger lifts, momentum is driven by the
     compositor, and any attempt to animate scrollLeft against it is simply
     overridden — which is why every JS approach here ended up waiting for the
     glide to finish before it could do anything.

     This was removed earlier because it fought bringIntoView(), which parked
     cards a fade's width clear of the edge. That conflict is gone: with one
     card to a screen the target is the card's own left edge, which is exactly
     where scroll-snap-align: start puts it. The two now agree, so the browser
     has nothing to correct. ── */
  .carousel-track {
    scroll-snap-type: x mandatory;
  }

  .carousel-track .work-card {
    scroll-snap-align: start;
  }

  /* The work grid becomes a row like any other track. */
  .carousel-track.work-grid {
    display: flex;
    align-items: flex-start;
    overflow-x: auto;
    column-gap: var(--card-gap);
    row-gap: 0;
  }

  .carousel-track.work-grid .work-card {
    width: var(--card-width);
  }

  /* Arrows move inside, over the artwork — the desktop version sits in a
     gutter that does not exist at this width. They stay visible: on a phone
     they are the cue that there is anything to swipe to. */
  /* Over the artwork here, so they take the back-to-top's circle to stay
     legible against a bright frame. */
  .carousel-btn {
    width: calc(var(--arrow-size) * 1.9);
    height: calc(var(--arrow-size) * 1.9);
    padding: 0;
    border: 0.067rem solid var(--teal);
    border-radius: 50%;
    background-color: var(--navy);
  }

  /* Inside the track — the desktop gutter does not exist at this width. */
  .carousel-prev {
    left: 0.4rem;
    transform: translateY(-50%);
  }

  .carousel-next {
    right: 0.4rem;
    transform: translateY(-50%);
  }

  .carousel-prev:hover,
  .carousel-next:hover,
  .carousel-prev:active,
  .carousel-next:active {
    transform: translateY(-50%) scale(1.08);
  }

  .terms-main {
    padding: calc(var(--nav-h) + 0.8rem) 0 1.333rem;
  }

  .about-bio {
    grid-template-columns: 1fr;
    gap: 0.8rem;
    padding-bottom: 1.6rem;
  }

  .about-credentials {
    padding: 0.267rem 0 2rem;
  }

  /* ── Footer ──
     Desktop runs three columns (1fr auto 1fr). On a phone that needs ~578px
     against a 390px screen, and because the middle column is `auto` it refuses
     to shrink — so it won the squeeze, the phone number wrapped at its spaces
     into "+352 / 691 / 218 / 898", and the year was pushed off the side of the
     page entirely. That overflow is what let the whole site scroll sideways.

     Same three groups, stacked, each keeping its own asymmetry. ── */
  footer {
    grid-template-columns: 1fr;
    gap: 1.333rem;
    align-items: start;
    padding: 1.6rem 1.267rem 2rem;
    min-height: 0;
  }

  /* Each group spans the full width rather than hugging the left edge, so the
     footer fills its space instead of leaving a column of air down the right.
     Type is up a step too — at footer size it was the smallest text on the
     site. */
  footer a,
  .footer-contact,
  .footer-terms,
  .footer-year {
    font-size: 1.05rem;
  }

  /* ── The three groups ──
     Links hold the left edge, contact holds the right, terms and the year span
     the full width underneath. Each group keeps its own offset rather than
     lining up flush — the socials step in from Youtube above them, and the
     phone steps in from the email, mirrored. So the two blocks lean towards
     each other instead of sitting in tidy columns. ── */
  .footer-left {
    margin-left: 0;
    align-items: flex-start;
    gap: 0.333rem;
  }

  .footer-socials {
    margin-left: 2.5em;          /* steps in under Youtube */
    justify-content: flex-start;
  }

  .footer-center {
    flex-direction: column;
    align-items: flex-end;       /* the whole block sits right */
    text-align: right;
    gap: 0.333rem;
  }

  .footer-center .footer-contact:last-child {
    margin-right: 2.5em;         /* the same step, mirrored inward */
  }

  .footer-right {
    justify-self: stretch;
    justify-content: space-between;
    margin-right: 0;
    gap: 1.333rem;
  }

  /* ── Project page ──
     .project-body was still 1fr 1fr on a phone, so the copy and the photo sat
     side by side and the photo came out a sliver. Stacked instead.

     The image also has to come out of absolute flow: it is positioned inside
     .project-right, which only has a height at all because the grid stretches
     it to match the text column beside it. Stacked, that column would collapse
     to nothing and take the photo with it. ── */
  /* Stacked, with the credits moved below the photo so the picture breaks up
     the run of text rather than sitting after all of it.

     .project-text becomes display: contents, which dissolves the wrapper and
     makes its four paragraphs direct children of .project-body — so they can
     be ordered around the image without moving anything in the HTML, and the
     desktop two-column layout is left exactly as it was. */
  .project-body {
    display: flex;
    flex-direction: column;
    gap: 1.2rem;
  }

  .project-text {
    display: contents;
  }

  .project-copy      { order: 1; }
  .project-right     { order: 2; position: static; }
  #project-credits   { order: 3; }

  /* Stacked, there is no column height to fill, so the pictures set the shape:
     the track takes the tallest one's ratio (--slides-ratio, written by
     project.js once they load; 3:2 until then). Shorter pictures sit centred
     on the page's own background rather than on black, and the dots move out
     underneath, so nothing floats over empty space. */
  .project-images {
    position: static;
    inset: auto;
  }

  .slideshow {
    overflow: visible;
    border-radius: 0;
    background: none;
  }

  /* Each picture is its own size rather than filling the track and letter-
     boxing inside it, so the rounded corners land on the picture itself
     whatever its shape. Full width always fits: the track is as tall as the
     tallest picture, so none can overflow it. Radius matches the videos. */
  .slideshow .slide {
    height: auto;
    align-self: center;
    border-radius: 0.4rem;
  }

  .slide-track {
    position: relative;
    aspect-ratio: var(--slides-ratio, 3 / 2);
  }

  .slide-dots {
    position: static;
    justify-content: center;
    margin-top: 0.3rem;
    transform: none;
  }

  .slide-dot::before {
    box-shadow: none;
  }

  /* ── Contact ──
     Three blocks with real air between them: the question, the three
     sentences, the form. Top-aligned rather than centred — centring pushed
     the heading a third of the way down the screen, and it should be the
     first thing you read. ── */
  .contact-main {
    padding: calc(var(--nav-h) + 0.8rem) 0 1.333rem;
    justify-content: flex-start;
  }

  .contact-title {
    margin-bottom: 3.4rem;
  }

  .contact-layout {
    grid-template-columns: 1fr;
    gap: 3.4rem;
  }

  .contact-form {
    gap: 0.6rem;
  }
}
