/* ==========================================================================
   jayyvisuals.com
   Light editorial studio. Type at 200-300 with wide tracking on uppercase.
   Monochrome plus one deep accent. Pill radius on interactive only.
   ========================================================================== */

:root {
  --paper:      #0E0E0F;
  --paper-2:    #141416;
  --paper-3:    #1C1C1F;
  --ink:        #ECECEA;
  --ink-soft:   #C6C6C3;
  --muted:      #918F8B;
  --line:       rgba(236, 236, 234, 0.17);
  --line-soft:  rgba(236, 236, 234, 0.09);
  --accent:     #F0653C;

  /* Full-bleed blocks that were dark in both themes. Kept as its own token
     because --paper is now dark too and these must not drift apart if the
     page ground is ever lightened. */
  --surface-dark: #0E0E0F;

  /* The sticky bar sits over the page, so its fill is a token too. */
  --bar: rgba(14, 14, 15, 0.82);

  /* Type on dark full-bleed blocks */
  --on-dark:        #FFFFFF;
  --on-dark-muted:  rgba(255, 255, 255, 0.72);
  --on-dark-line:   rgba(255, 255, 255, 0.28);

  --wrap:    1400px;
  --gutter:  clamp(1.5rem, 5vw, 4.5rem);
  --section: clamp(5.5rem, 12vw, 11rem);

  --ease:  cubic-bezier(0.16, 1, 0.3, 1);
  --track: 0.28em;   /* the wide uppercase tracking used throughout */

  color-scheme: dark;
}


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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: "Geist", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 16.5px;
  font-weight: 300;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

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

a { color: inherit; text-decoration: none; }

::selection { background: var(--ink); color: var(--paper); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.skip {
  position: absolute;
  left: -9999px;
  background: var(--ink);
  color: var(--paper);
  padding: 0.85rem 1.4rem;
  z-index: 200;
}
.skip:focus { left: 1rem; top: 1rem; }

/* --------------------------------------------------------------------------
   Type
   -------------------------------------------------------------------------- */

h1, h2, h3, h4 {
  margin: 0;
  font-weight: 300;
  letter-spacing: -0.012em;
  line-height: 1.08;
}

.display {
  font-size: clamp(2.3rem, 4.8vw, 4.1rem);
  font-weight: 200;
  line-height: 1.06;
  letter-spacing: -0.018em;
}

.h2 {
  font-size: clamp(1.9rem, 3.5vw, 3.05rem);
  font-weight: 200;
  line-height: 1.1;
}

.h3 {
  font-size: clamp(1.2rem, 1.7vw, 1.5rem);
  font-weight: 300;
  letter-spacing: -0.005em;
}

/* The wide-tracked uppercase label. Rationed, not sprinkled everywhere. */
.label {
  display: block;
  font-size: 0.68rem;
  font-weight: 400;
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 1.5rem;
}

.label-rule::before {
  content: "";
  display: inline-block;
  width: 26px;
  height: 1px;
  background: var(--accent);
  vertical-align: middle;
  margin-right: 0.9rem;
  transform: translateY(-1px);
}

.lede {
  font-size: clamp(1.02rem, 1.3vw, 1.18rem);
  color: var(--muted);
  line-height: 1.6;
  max-width: 48ch;
  margin: 0;
}

.body {
  color: var(--muted);
  max-width: 60ch;
  margin: 0;
}

.body + .body { margin-top: 1.2rem; }

/* --------------------------------------------------------------------------
   Header
   -------------------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  height: 76px;
  display: flex;
  align-items: center;
  background: var(--bar);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-bottom: 1px solid var(--line-soft);
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
}

/* Over a full-bleed dark hero the bar goes transparent and inverts, then
   returns to the solid light bar once the hero has scrolled past. */
.site-header.over-hero {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom-color: transparent;
  color: var(--on-dark);
}

.site-header.over-hero .nav a { color: var(--on-dark-muted); }
.site-header.over-hero .nav a:hover { color: var(--on-dark); }
.site-header.over-hero .nav a.btn-solid { background: var(--on-dark); color: var(--surface-dark); }
.site-header.over-hero .nav a.btn-solid:hover { background: var(--accent); color: var(--surface-dark); }
.site-header.over-hero .nav-toggle { color: var(--on-dark); }

@media (max-width: 900px) {
  /* The dropdown panel is always the light surface, even over the hero. */
  .site-header.over-hero .nav { color: var(--ink); }
  .site-header.over-hero .nav a { color: var(--muted); }
  .site-header.over-hero .nav a:hover { color: var(--ink); }
}

.brand {
  font-size: 0.92rem;
  font-weight: 400;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  white-space: nowrap;
}

.nav {
  display: flex;
  align-items: center;
  gap: clamp(1.5rem, 2.8vw, 2.75rem);
  order: 2;
  margin-left: auto;   /* brand stays left, everything else groups right */
}

.nav a {
  font-size: 0.72rem;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
  transition: color 0.3s var(--ease);
}

.nav a:hover,
.nav a[aria-current="page"] { color: var(--ink); }

/* The CTA is a button first, so it must not inherit the dim link color. */
.nav a.btn-solid,
.nav a.btn-solid:hover,
.nav a.btn-solid[aria-current="page"] { color: var(--paper); }

.nav-toggle {
  display: none;
  order: 4;
  background: none;
  border: 0;
  color: var(--ink);
  line-height: 1;
  padding: 0.3rem;
  cursor: pointer;
}

.nav-toggle .ico { width: 1.55rem; height: 1.55rem; }

@media (max-width: 900px) {
  .nav {
    position: fixed;
    inset: 76px 0 auto 0;
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    background: var(--paper);
    border-bottom: 1px solid var(--line);
    padding: 0.5rem var(--gutter) 1.75rem;
  }
  .nav[hidden] { display: none; }
  .nav a {
    width: 100%;
    padding: 1rem 0;
    font-size: 0.8rem;
    border-bottom: 1px solid var(--line-soft);
  }
  /* .nav a sets padding and size for text links, so the CTA reclaims both. */
  .nav a.btn {
    width: auto;
    margin-top: 1.5rem;
    padding: 0.9rem 1.8rem;
    font-size: 0.72rem;
    border-bottom: 0;
  }
  .nav-toggle { display: block; }
}

/* --------------------------------------------------------------------------
   Buttons. Pill radius lives here and nowhere else.
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.9rem 1.9rem;
  border-radius: 9999px;
  font-size: 0.72rem;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid transparent;
  cursor: pointer;
  font-family: inherit;
  transition: transform 0.2s var(--ease), background-color 0.3s var(--ease),
              color 0.3s var(--ease), border-color 0.3s var(--ease);
}

.btn:active { transform: translateY(1px); }

.btn-solid { background: var(--ink); color: var(--paper); }
.btn-solid:hover { background: var(--accent); }

.btn-outline { background: transparent; color: var(--ink); border-color: var(--line); }
.btn-outline:hover { border-color: var(--ink); }

/* On full-bleed dark blocks */
/* These sit on a surface that is dark in BOTH themes, so they must not use
   --paper or --ink: those flip, and the button turns dark-on-dark. */
.on-dark .btn-solid { background: var(--on-dark); color: var(--surface-dark); }
.on-dark .btn-solid:hover { background: var(--accent); color: var(--surface-dark); }
.on-dark .btn-outline { color: var(--on-dark); border-color: var(--on-dark-line); }
.on-dark .btn-outline:hover { border-color: var(--on-dark); }

.btn .ico { width: 1em; height: 1em; }

/* Rule-and-word link, the "Explore" pattern */
.link {
  display: inline-flex;
  align-items: center;
  gap: 0.85rem;
  font-size: 0.72rem;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
  transition: color 0.3s var(--ease);
}

.link::before {
  content: "";
  width: 34px;
  height: 1px;
  background: currentColor;
  transition: width 0.4s var(--ease);
}

.link:hover { color: var(--accent); }
.link:hover::before { width: 52px; }

.on-dark .link { color: var(--on-dark); }
.on-dark .link:hover { color: var(--on-dark); }

/* --------------------------------------------------------------------------
   Sections
   -------------------------------------------------------------------------- */

.pad { padding-block: var(--section); }
.pad-sm { padding-block: clamp(3.5rem, 7vw, 6.5rem); }

/* For a section that follows a page head, so the two paddings do not stack. */
.pt-0 { padding-top: 0; }

.tint { background: var(--paper-2); }
.ruled { border-top: 1px solid var(--line-soft); }

.on-dark {
  background: var(--surface-dark);
  color: var(--on-dark);
}
.on-dark .body,
.on-dark .lede { color: var(--on-dark-muted); }
.on-dark .label { color: var(--on-dark-muted); }

.ico { width: 1em; height: 1em; flex: none; fill: currentColor; display: block; }

/* --------------------------------------------------------------------------
   Hero : full-bleed image, centred wordmark
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  min-height: 100dvh;
  margin-top: -76px;          /* sit under the sticky header */
  display: grid;
  place-items: center;
  text-align: center;
  overflow: hidden;
  background: var(--surface-dark);
  color: var(--on-dark);
}

.hero-media {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.hero-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The hero still sits back so the wordmark stays the thing you read. One knob:
   lower for fainter, higher for a stronger picture. */
.hero-media-soft img { opacity: var(--hero-still-opacity, 0.30); }

.hero-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(14,14,15,0.52) 0%, rgba(14,14,15,0.34) 45%, rgba(14,14,15,0.68) 100%);
}

.hero-inner {
  position: relative;
  z-index: 1;
  padding: 7rem var(--gutter) 4rem;
  max-width: 62rem;
}

.wordmark {
  font-size: clamp(1.85rem, 6.2vw, 4.4rem);
  font-weight: 200;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  line-height: 1.05;
  margin: 0;
  text-indent: 0.2em;          /* balance the trailing letterspace */
}

.hero-sub {
  margin: 1.9rem 0 0;
  font-size: clamp(0.68rem, 1vw, 0.78rem);
  font-weight: 400;
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--on-dark-muted);
}

.hero-actions {
  margin-top: 3rem;
  display: flex;
  gap: 0.85rem;
  justify-content: center;
  flex-wrap: wrap;
}

/* --------------------------------------------------------------------------
   Split panels : the three-way full-height navigation
   -------------------------------------------------------------------------- */

.panels {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  min-height: 84vh;
}

.panel {
  position: relative;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  min-height: 380px;
  padding: clamp(1.75rem, 3vw, 3rem);
  color: var(--on-dark);
  isolation: isolate;
}

.panel::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(180deg, rgba(14,14,15,0.12) 0%, rgba(14,14,15,0.68) 100%);
  transition: background 0.5s var(--ease);
}

.panel img {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1) contrast(1.04);
  transform: scale(1.02);
  transition: filter 0.6s var(--ease), transform 0.9s var(--ease);
}

.panel:hover img,
.panel:focus-visible img {
  filter: grayscale(0) contrast(1.04);
  transform: scale(1.07);
}

.panel:hover::after,
.panel:focus-visible::after {
  background: linear-gradient(180deg, rgba(14,14,15,0.06) 0%, rgba(14,14,15,0.62) 100%);
}

/* Inset hairline that draws itself on hover */
.panel::before {
  content: "";
  position: absolute;
  inset: clamp(0.9rem, 1.5vw, 1.4rem);
  border: 1px solid rgba(255, 255, 255, 0);
  transition: border-color 0.5s var(--ease);
  pointer-events: none;
  z-index: 1;
}

.panel:hover::before,
.panel:focus-visible::before { border-color: rgba(255, 255, 255, 0.42); }

.panel-body { position: relative; z-index: 2; }

.panel h2 {
  font-size: clamp(1.35rem, 2.1vw, 1.95rem);
  font-weight: 300;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  margin-bottom: 1.1rem;
}

/* Panels are a dark surface, so the link must not inherit ink from .link. */
.panel .link {
  color: var(--on-dark);
  opacity: 0.78;
  transition: opacity 0.4s var(--ease);
}
.panel:hover .link,
.panel:focus-visible .link { color: var(--on-dark); opacity: 1; }

@media (max-width: 900px) {
  .panels { grid-template-columns: 1fr; min-height: 0; }
  .panel { min-height: 58vh; }
}

/* --------------------------------------------------------------------------
   Statement band
   -------------------------------------------------------------------------- */

.statement {
  text-align: center;
  margin-inline: auto;
}

.statement p {
  margin: 0;
  /* The one place the site drops its sans. A statement this short can carry a
     display serif, and the contrast against the uppercase Geist everywhere
     else is what makes it read as a pull quote rather than body copy. */
  font-family: "Instrument Serif", "Iowan Old Style", Georgia, "Times New Roman", serif;
  font-size: clamp(1.9rem, 4vw, 3.4rem);
  /* The measure lives here, not on the wrapper: in ch it has to be computed
     against the serif's own size or the line length collapses. */
  max-width: 26ch;
  margin-inline: auto;
  font-weight: 400;
  line-height: 1.24;
  letter-spacing: -0.015em;
  color: var(--ink);
}

/* Marked phrases: italic, with the accent carried by a rule under the words
   rather than by recolouring them. Colouring the text itself would break the
   line into stripes; an underline keeps the sentence one object. */
.statement .pull {
  font-style: italic;
  position: relative;
  white-space: nowrap;
  background-image: linear-gradient(var(--accent), var(--accent));
  background-repeat: no-repeat;
  background-size: 100% 0.06em;
  background-position: 0 0.94em;
  padding-bottom: 0.06em;
}

@media (max-width: 640px) {
  /* Long marked phrases would force a horizontal scroll if kept unbroken. */
  .statement .pull { white-space: normal; }
}

/* --------------------------------------------------------------------------
   Numbered steps
   -------------------------------------------------------------------------- */

.steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
  gap: clamp(2.25rem, 4vw, 4rem);
}

.step { border-top: 1px solid var(--line); padding-top: 1.5rem; }

.step-num {
  display: block;
  font-size: clamp(1.6rem, 2.4vw, 2.1rem);
  font-weight: 200;
  color: var(--accent);
  line-height: 1;
  margin-bottom: 1.4rem;
}

.step h3 { margin-bottom: 0.7rem; }

.step p { margin: 0; color: var(--muted); font-size: 0.95rem; }

/* --------------------------------------------------------------------------
   Service rows : hairline between, never around
   -------------------------------------------------------------------------- */

.rows { border-top: 1px solid var(--line); }

.row {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr) auto;
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: start;
  padding: clamp(2rem, 3.5vw, 3.25rem) 0;
  border-bottom: 1px solid var(--line);
}

.row h3 { font-size: clamp(1.35rem, 2.2vw, 1.85rem); font-weight: 200; }

.row p { margin: 0; color: var(--muted); }

.tag {
  font-size: 0.63rem;
  font-weight: 400;
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
  padding-top: 0.55rem;
}

@media (max-width: 820px) {
  .row { grid-template-columns: 1fr; gap: 1rem; }
  .tag { padding-top: 0; }
}

/* --------------------------------------------------------------------------
   Media frames and work grid
   -------------------------------------------------------------------------- */

.frame {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--paper-3);
}

/* Portfolio frames stay in full colour. The footage is the product, so
   desaturating it by default would hide the thing being sold. Only the
   navigation panels below use the grayscale-to-colour treatment. */
.frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.8s var(--ease);
}

a.frame:hover img,
.card:hover .frame img { transform: scale(1.04); }

.ratio-16x9 { aspect-ratio: 16 / 9; }
.ratio-4x5  { aspect-ratio: 4 / 5; }
.ratio-3x2  { aspect-ratio: 3 / 2; }
.ratio-9x16 { aspect-ratio: 9 / 16; }

.frame-play {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-items: center;
}

.play-dot {
  width: clamp(62px, 6vw, 84px);
  height: clamp(62px, 6vw, 84px);
  border-radius: 9999px;
  display: grid;
  place-items: center;
  background: rgba(255, 255, 255, 0.94);
  color: var(--ink);
  transition: transform 0.4s var(--ease), background-color 0.3s var(--ease);
}

.play-dot .ico { width: 1.25rem; height: 1.25rem; margin-left: 3px; }

a.frame:hover .play-dot { transform: scale(1.08); background: var(--paper); }

.frame-caption {
  position: absolute;
  left: 0;
  right: 0;                 /* full width, or the scrim ends in a hard edge
                               partway across the image */
  bottom: 0;
  z-index: 2;
  padding: clamp(1.25rem, 2vw, 2rem);
  color: var(--on-dark);
}

.frame-caption strong {
  display: block;
  font-size: clamp(1.05rem, 1.6vw, 1.35rem);
  font-weight: 300;
  letter-spacing: 0.02em;
}

.frame-caption span {
  display: block;
  margin-top: 0.5rem;
  font-size: 0.63rem;
  font-weight: 400;
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--on-dark-muted);
}

/* Scrim so captions stay legible over any photo, including a bright one.
   Measured at 3.1:1 with a shorter ramp, which fails AA at this text size. */
.frame-caption::before {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 340%;
  z-index: -1;
  background: linear-gradient(180deg,
    rgba(14,14,15,0) 0%,
    rgba(14,14,15,0.34) 42%,
    rgba(14,14,15,0.90) 100%);
  pointer-events: none;
}

.work-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  /* Rows size themselves now. The covers are cut to 4:5, so the card declares
     its own ratio rather than being stretched to a fixed row height and
     cropped back out of shape. */
  grid-auto-rows: auto;
  gap: clamp(0.9rem, 1.6vw, 1.5rem);
}

/* Cards in a row share one height so captions line up. The frame takes the
   slack and crops, which is why source ratios can vary. */
.card {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  height: 100%;
}

/* Client covers are 4:5. Anything shot wide says so with data-cover. */
.work-grid .frame { height: auto; aspect-ratio: 4 / 5; }
.work-grid .card[data-cover="16x9"] .frame { aspect-ratio: 16 / 9; }

.card-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.25rem;
  margin-top: 1rem;
}

/* A quarter-width card has nowhere near the room for a title and its label on
   one line, so they stack. The full-width pieces keep the side-by-side. */
.card.span-3 .card-meta,
.card.span-4 .card-meta {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.3rem;
}

.card-meta h3 { font-size: clamp(1rem, 1.4vw, 1.2rem); font-weight: 300; }

.card-meta span {
  font-size: 0.63rem;
  font-weight: 400;
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

/* Four client covers across the full content width. Without this the
   cards fall back to one column each and huddle on the left. */
.span-3 { grid-column: span 3; }
.span-4 { grid-column: span 4; }
.span-5 { grid-column: span 5; }
.span-6 { grid-column: span 6; }
.span-7 { grid-column: span 7; }
.span-8 { grid-column: span 8; }
.span-12 { grid-column: span 12; }

@media (max-width: 820px) {
  .span-4, .span-5, .span-6, .span-7, .span-8 { grid-column: span 12; }
  /* The client covers go two-up rather than full width: a 4:5 image across a
     whole phone screen is enormous, and four of them would be a scroll
     marathon. Without this rule span-3 never collapses and the grid overflows
     the viewport sideways. */
  .span-3 { grid-column: span 6; }
  .card { height: auto; }
  .work-grid .frame { aspect-ratio: 4 / 5; }
  .work-grid .card[data-cover="16x9"] .frame { aspect-ratio: 16 / 9; }
}

@media (max-width: 460px) {
  /* Below this a two-up cover is too small to read, so single file, slightly
     wider than 4:5 to stop the page running long. */
  .span-3 { grid-column: span 12; }
  .work-grid .frame { aspect-ratio: 4 / 4.4; }
  .card.span-3 .card-meta { flex-direction: row; align-items: baseline; gap: 1.25rem; }
}

/* --------------------------------------------------------------------------
   Section header and split band
   -------------------------------------------------------------------------- */

.sec-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2.5rem;
  margin-bottom: clamp(2.5rem, 4.5vw, 4rem);
}

@media (max-width: 720px) {
  .sec-head { flex-direction: column; align-items: flex-start; gap: 1.5rem; }
}

.band {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2.5rem, 6vw, 5.5rem);
  align-items: center;
}

@media (max-width: 900px) { .band { grid-template-columns: 1fr; } }

/* --------------------------------------------------------------------------
   Contact
   -------------------------------------------------------------------------- */

.contact-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(2.5rem, 6vw, 5.5rem);
}

@media (max-width: 900px) { .contact-grid { grid-template-columns: 1fr; } }

.direct { margin-top: 2.75rem; border-top: 1px solid var(--line); }

.direct a {
  display: flex;
  align-items: center;
  gap: 1.15rem;
  padding: 1.3rem 0;
  border-bottom: 1px solid var(--line);
  transition: padding-left 0.4s var(--ease);
}

.direct a:hover { padding-left: 0.6rem; }

.direct .ico { width: 1.2rem; height: 1.2rem; color: var(--accent); }

.direct strong { display: block; font-size: 1rem; font-weight: 300; }

.direct small {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.63rem;
  font-weight: 400;
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--muted);
}

/* Form */

.field { display: grid; gap: 0.55rem; margin-bottom: 1.6rem; }

.field label {
  font-size: 0.63rem;
  font-weight: 400;
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--muted);
}

.field input,
.field select,
.field textarea {
  width: 100%;
  font-family: inherit;
  font-size: 1rem;
  font-weight: 300;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--line);
  padding: 0.7rem 0;
  border-radius: 0;
  transition: border-color 0.3s var(--ease);
}

.field select { appearance: none; cursor: pointer; }

.field textarea { min-height: 130px; resize: vertical; }

.field input::placeholder,
.field textarea::placeholder { color: #9A9895; }

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-bottom-color: var(--accent);
}

.field-error { font-size: 0.83rem; color: #A32E10; margin: 0; }

/* Empty error slots must not add height, or field spacing goes uneven. */
.field-error:empty { display: none; }

.form-note { margin-top: 1.4rem; font-size: 0.87rem; color: var(--muted); }

.form-status {
  margin-top: 1.4rem;
  padding: 1rem 1.2rem;
  background: var(--paper-2);
  border-left: 2px solid var(--accent);
  font-size: 0.92rem;
  color: var(--ink-soft);
}

.form-status[hidden] { display: none; }

form .btn { margin-top: 0.75rem; }

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */

.site-footer {
  border-top: 1px solid var(--line-soft);
  padding-block: clamp(3.5rem, 6vw, 5rem) 2.5rem;
}

.footer-top {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2.5rem;
  flex-wrap: wrap;
  padding-bottom: 3rem;
}

.footer-links { display: flex; gap: 2rem; flex-wrap: wrap; }

.footer-links a {
  font-size: 0.68rem;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  transition: color 0.3s var(--ease);
}

.footer-links a:hover { color: var(--ink); }

.footer-bottom {
  border-top: 1px solid var(--line-soft);
  padding-top: 1.85rem;
  display: flex;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
  font-size: 0.68rem;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   Scroll reveal
   -------------------------------------------------------------------------- */

.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.85s var(--ease), transform 0.85s var(--ease);
  will-change: opacity, transform;
}

.reveal.in { opacity: 1; transform: none; will-change: auto; }

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  .frame img, .panel img, .play-dot, .btn, .link::before, .direct a { transition: none; }
  a.frame:hover img, .panel:hover img { transform: none; }
}

/* --------------------------------------------------------------------------
   Monogram
   -------------------------------------------------------------------------- */

.mark {
  display: block;
  fill: currentColor;
  flex: none;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
}

/* The mark is calligraphic, so it needs a little more room than a geometric
   logo would to stay legible in the bar. */
.brand .mark { width: 33px; height: 33px; }

.hero-mark {
  width: clamp(72px, 9vw, 118px);
  height: clamp(72px, 9vw, 118px);
  margin: 0 auto 2rem;
  color: var(--on-dark);
}

.site-footer .brand .mark { width: 34px; height: 34px; }

/* --------------------------------------------------------------------------
   Trusted by
   -------------------------------------------------------------------------- */

.trusted { text-align: center; }

.trusted .label { margin-bottom: 2.5rem; }

.trusted-logos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(2rem, 5vw, 4.5rem);
}

.trusted-logo {
  display: block;
  height: var(--logo-h, 30px);
  width: calc(var(--logo-h, 30px) * var(--logo-r, 3));
  max-width: 46vw;
  /* Same guard as the textures: no mask support would mean solid rectangles. */
  background-color: transparent;
  color: var(--ink-soft);
  -webkit-mask-image: var(--logo);
          mask-image: var(--logo);
  -webkit-mask-size: contain;
          mask-size: contain;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  transition: color 0.4s var(--ease), opacity 0.4s var(--ease);
  opacity: 0.72;
}

@supports ((-webkit-mask-image: url("/assets/media/logo-eybl.png")) or (mask-image: url("/assets/media/logo-eybl.png"))) {
  .trusted-logo { background-color: currentColor; }
}

.trusted-link:hover .trusted-logo { color: var(--ink); opacity: 1; }
.trusted-logo:hover { color: var(--ink); opacity: 1; }

/* Ink on paper carries further than grey on black, so the dark theme needs the
   marks brought up to stay legible. */

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

.trusted-names {
  margin: 2.5rem 0 0;
  font-size: 0.78rem;
  font-weight: 400;
  letter-spacing: 0.1em;
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   Work filter tabs
   -------------------------------------------------------------------------- */

.tabs {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: clamp(2rem, 3.5vw, 3rem);
}

.tab {
  appearance: none;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 9999px;
  padding: 0.7rem 1.5rem;
  font-family: inherit;
  font-size: 0.68rem;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease),
              background-color 0.3s var(--ease);
}

.tab:hover { color: var(--ink); border-color: var(--ink); }

.tab[aria-selected="true"] {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.card[hidden] { display: none; }

/* --------------------------------------------------------------------------
   Video lightbox
   -------------------------------------------------------------------------- */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: grid;
  place-items: center;
  padding: clamp(1rem, 4vw, 3rem);
  background: rgba(8, 8, 9, 0.94);
  opacity: 0;
  transition: opacity 0.35s var(--ease);
}

.lightbox[hidden] { display: none; }
.lightbox.open { opacity: 1; }

.lightbox-inner {
  width: min(100%, 1180px);
  aspect-ratio: 16 / 9;
  background: #000;
}

/* Five of the pieces are shot 9:16. Played in a 16:9 box they would sit as a
   thin stripe with black either side, so the box turns portrait instead and
   takes its size from the viewport height. */
.lightbox-inner.is-portrait {
  aspect-ratio: 9 / 16;
  width: auto;
  height: min(84vh, 920px);
  max-width: 100%;
}

.lightbox-inner iframe,
.lightbox-inner video {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

.lightbox-close {
  position: absolute;
  top: clamp(0.75rem, 2vw, 1.75rem);
  right: clamp(0.75rem, 2vw, 1.75rem);
  width: 46px;
  height: 46px;
  border-radius: 9999px;
  display: grid;
  place-items: center;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.28);
  color: #fff;
  cursor: pointer;
  transition: background-color 0.3s var(--ease);
}

.lightbox-close:hover { background: rgba(255, 255, 255, 0.22); }
.lightbox-close .ico { width: 1.1rem; height: 1.1rem; }

/* An empty video slot must not pretend to be playable: no play button, no
   pointer cursor. Paste a URL into data-video and both come back. */
[data-video=""] .frame-play,
.card:not([data-video]) .frame-play { display: none; }

a[data-video=""] { cursor: default; }

@media (prefers-reduced-motion: reduce) {
  .lightbox { transition: none; }
  .trusted-logos img, .tab { transition: none; }
}

/* --------------------------------------------------------------------------
   Texture

   Four pieces of art, all stored the same way: a black PNG whose alpha IS the
   texture. They are used as MASKS with background-color: currentColor, not as
   background images. That one decision does a lot of work:

     - the texture takes the colour of whatever it sits in, so it is ink on the
       light theme, near-white on the dark theme, and white on the always-dark
       bands, from a single file with no filters and no second copy
     - each file is feathered at its own edges, so it dissolves into the page
       rather than ending on a straight line. No CSS fade mask is needed on top
       and nothing gets clipped by the section box.

   Placement rule: one texture per section, alternating side to side, bleeding
   off the viewport edge. A plain rectangle of texture behind copy is the
   failure mode being avoided.
   -------------------------------------------------------------------------- */

.tex {
  position: relative;
  isolation: isolate;
}

.tex > * {
  position: relative;
  z-index: 1;
}

.tex::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* Deliberately transparent by default. The fill is switched on only inside
     the @supports below, because if a browser ignored the mask this would
     paint a solid ink rectangle across the section. */
  background-color: transparent;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-size: var(--tex-size, 48% auto);
          mask-size: var(--tex-size, 48% auto);
  -webkit-mask-position: var(--tex-pos, right -7% center);
          mask-position: var(--tex-pos, right -7% center);
  opacity: var(--tex-op, 0.30);
}

@supports ((-webkit-mask-image: url("/assets/media/tex-halftone.png")) or (mask-image: url("/assets/media/tex-halftone.png"))) {
  .tex::before { background-color: currentColor; }
}

/* Which art. The url is written out per variant rather than passed through a
   custom property: var() inside mask-image has a patchy history in Safari, and
   this costs three lines to sidestep entirely. */
[data-tex^="dots"]::before {
  -webkit-mask-image: url("/assets/media/tex-halftone.png");
          mask-image: url("/assets/media/tex-halftone.png");
}
[data-tex^="concrete"]::before {
  -webkit-mask-image: url("/assets/media/tex-concrete.png");
          mask-image: url("/assets/media/tex-concrete.png");
}
[data-tex^="scratch"]::before {
  -webkit-mask-image: url("/assets/media/tex-scratch.png");
          mask-image: url("/assets/media/tex-scratch.png");
}

[data-tex^="dots"]     { --tex-size: 48% auto; --tex-op: 0.18; }
[data-tex^="concrete"] { --tex-size: 56% auto; --tex-op: 0.26; }
[data-tex^="scratch"]  { --tex-size: 60% auto; --tex-op: 0.30; }

/* Where it sits. Horizontal bleed off the viewport edge reads as deliberate;
   vertical bleed would be clipped by the section box, so it stays inside. */
[data-tex$="-l"]  { --tex-pos: left -6% center; }
[data-tex$="-r"]  { --tex-pos: right -7% center; }
[data-tex$="-tr"] { --tex-pos: right -12% top 2%; }
[data-tex$="-bl"] { --tex-pos: left -18% bottom 2%; }

/* The dark theme needs less, for the same reason. */

@media (max-width: 720px) {
  /* One column, no side margin to hide in: push the art further off-screen so
     it never sits under a line of type. */
  .tex { --tex-op: 0.16; }
  [data-tex$="-l"]  { --tex-pos: left -34% center; }
  [data-tex$="-r"]  { --tex-pos: right -34% center; }
  [data-tex$="-bl"] { --tex-pos: left -40% bottom 2%; }
  [data-tex$="-tr"] { --tex-pos: right -34% top 2%; }
}

/* --------------------------------------------------------------------------
   Film grain

   One seamless tile over the whole viewport. This is what stops the large flat
   areas reading as dead background, and it is why the sectional textures above
   can stay as sparse as they are.

   It sits above the page but below the header (100), the skip link (200) and
   the lightbox (300), so it never grains the chrome or a playing video.
   -------------------------------------------------------------------------- */

body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 90;
  pointer-events: none;
  background-color: transparent;
  -webkit-mask-image: url("/assets/media/tex-grain.png");
          mask-image: url("/assets/media/tex-grain.png");
  -webkit-mask-size: 256px 256px;
          mask-size: 256px 256px;
  opacity: 0.08;
}


@supports ((-webkit-mask-image: url("/assets/media/tex-grain.png")) or (mask-image: url("/assets/media/tex-grain.png"))) {
  body::after { background-color: currentColor; }
}

/* Screen-reader-only text */

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   Hero scroll cue
   -------------------------------------------------------------------------- */

.scroll-cue {
  position: absolute;
  left: 50%;
  bottom: clamp(1.75rem, 4vh, 3rem);
  transform: translateX(-50%);
  z-index: 2;
  display: grid;
  place-items: center;
  width: 44px;
  height: 64px;
  color: var(--on-dark);
  opacity: 0.55;
  transition: opacity 0.5s var(--ease);
}

.scroll-cue:hover { opacity: 1; }

/* A drawn line rather than an icon file: it animates on its own and costs
   nothing to load. */
.scroll-cue-line {
  position: relative;
  display: block;
  width: 1px;
  height: 46px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.25);
}

.scroll-cue-line::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 1px;
  height: 40%;
  background: var(--on-dark);
  animation: cue 2.2s var(--ease) infinite;
}

@keyframes cue {
  0%   { transform: translateY(-110%); }
  60%  { transform: translateY(250%); }
  100% { transform: translateY(250%); }
}

/* Once the reader has started scrolling the cue has done its job. */
.scroll-cue.is-gone { opacity: 0; pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
  .scroll-cue-line::after { animation: none; height: 100%; opacity: 0.7; }
}

@media (max-height: 620px) {
  /* On a short window the hero is already tight; the cue would collide. */
  .scroll-cue { display: none; }
}

/* --------------------------------------------------------------------------
   Films

   A project can carry one film or six. The first one runs full width because
   it is the piece you want watched; the rest pair up. Adding another is one
   more <a class="film"> block - the grid absorbs it with no other change.
   -------------------------------------------------------------------------- */

.films {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: clamp(1.25rem, 2.5vw, 2.25rem);
}

.films > :first-child { grid-column: 1 / -1; }

.film { display: block; }

.film .frame { background: var(--paper-3); }

/* A vertical piece in a full-width frame would be absurdly tall, so tall films
   are held to a sensible column and centred. */
.film[data-orient="tall"] .frame {
  max-width: 480px;
  margin-inline: auto;
}

.films > :first-child[data-orient="tall"] .frame { max-width: 560px; }

.film-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.25rem;
  margin-top: 1rem;
}

.film-meta h3 {
  margin: 0;
  font-size: clamp(1rem, 1.4vw, 1.2rem);
  font-weight: 300;
}

.film-meta span {
  font-size: 0.7rem;
  font-weight: 400;
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

@media (max-width: 560px) {
  .film-meta { flex-direction: column; align-items: flex-start; gap: 0.3rem; }
  .film-meta span { white-space: normal; }
}

/* --------------------------------------------------------------------------
   Project page head

   Title, blurb and cover share one band. Stacked, they pushed the films nearly
   two screens down - and the films are the reason anyone opened the page.
   -------------------------------------------------------------------------- */

.band-head {
  align-items: center;
  /* Text carries the weight, the cover sits beside it. Left column is wider
     because a title and two paragraphs need the room; the picture does not. */
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 0.7fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  margin-top: clamp(1.5rem, 3vw, 2.5rem);
}

/* A 4:5 cover at full column width was 585x731 on a laptop, which pushed the
   films almost two screens down. */
.band-head .frame {
  max-width: 340px;
  width: 100%;
  margin-left: auto;
}

.band-head .display { font-size: clamp(2rem, 4.2vw, 3.4rem); }

@media (max-width: 900px) {
  .band-head { grid-template-columns: 1fr; }
  .band-head .frame { max-width: 300px; margin-left: 0; }
}

/* The cue again, but in the flow of the page rather than pinned to the hero. */
/* The hero version is a bare line over a photograph, where it reads fine. In
   the body of a page a 1px line at low opacity is invisible, so this one is
   labelled - the point is to make it obvious there is work further down. */
/* Pinned to the bottom of the first screen rather than sitting in the flow,
   so it is on screen the moment the page opens - a cue you have to scroll to
   reach is not a cue. It is out of flow, so the sections keep their spacing.
   The sentinel below decides when it fades. */
.scroll-cue-inline {
  position: fixed;
  left: 50%;
  bottom: clamp(1.25rem, 3.5vh, 2.25rem);
  transform: translateX(-50%);
  z-index: 6;
  width: auto;
  height: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
  margin: 0;
  padding: 0.7rem 1.1rem 0.55rem;
  color: var(--muted);
  opacity: 1;
  /* A soft pad of page colour keeps the label legible if content scrolls
     under it during the fade. */
  background: radial-gradient(
    ellipse at center,
    color-mix(in srgb, var(--paper) 88%, transparent) 0%,
    color-mix(in srgb, var(--paper) 0%, transparent) 72%
  );
  transition: color 0.4s var(--ease), opacity 0.45s var(--ease);
}

/* Browsers without color-mix get a plain transparent pad rather than a
   mis-parsed one. */
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
  .scroll-cue-inline { background: none; }
}

.scroll-cue-inline:hover { color: var(--ink); }

/* The inline cue does not carry .scroll-cue, so it needs its own fade rule -
   without this the class lands but nothing visibly happens. Removing the
   class on the way back up fades it in again. */
.scroll-cue-inline.is-gone {
  opacity: 0;
  pointer-events: none;
}

/* An invisible strip down the top of the page. While any of it is on screen
   the cue shows; scroll past it and the cue fades, scroll back up and it
   returns. Absolute, so it adds no height. */
[data-scroll-sentinel] {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 25vh;
  pointer-events: none;
  visibility: hidden;
}

main { position: relative; }

@media (max-height: 780px) {
  /* On a short window the cue lands on top of the copy and the still strip -
     measured overlap at 727px tall - so it is dropped rather than allowed to
     sit over content. Tall phones and laptops keep it. */
  .scroll-cue-inline { display: none; }
}

.scroll-cue-label {
  font-size: 0.7rem;
  font-weight: 400;
  letter-spacing: var(--track);
  text-transform: uppercase;
}

.scroll-cue-inline .scroll-cue-line {
  height: 34px;
  background: var(--line);
}

.scroll-cue-inline .scroll-cue-line::after { background: currentColor; }

/* A small strip of stills under the project copy. It fills the space beside
   the cover and gives a reason to keep going, without repeating the gallery at
   full size - it links straight down to it. */
.head-thumbs {
  display: flex;
  gap: 0.6rem;
  margin-top: clamp(1.75rem, 3vw, 2.5rem);
  max-width: 340px;
}

.head-thumb {
  display: block;
  flex: 1 1 0;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--paper-3);
}

.head-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.72;
  /* Mono at rest so the strip reads as one quiet block under the copy and never
     competes with the cover beside it. Colour returns on the one being hovered,
     which makes the strip feel live rather than decorative. */
  filter: grayscale(1) contrast(1.04);
  transition: opacity 0.4s var(--ease), filter 0.5s var(--ease),
    transform 0.6s var(--ease);
}

.head-thumbs:hover .head-thumb img { opacity: 1; }
.head-thumb:hover img { filter: grayscale(0) contrast(1); transform: scale(1.05); }

@media (prefers-reduced-motion: reduce) {
  .head-thumb img { transition: opacity 0.4s var(--ease), filter 0.4s var(--ease); }
  .head-thumb:hover img { transform: none; }
}

@media (max-width: 900px) { .head-thumbs { max-width: 260px; } }
