/*
  Pressific Studio.
  Website developed by pressific.com Emily Park, Dileep C. Kaluaratchie and Tech Generation Ltd.

  Palette and type scale were measured off the reference the client chose
  (anthropic.com) rather than guessed, then moved into Pressific's own colour.
  Every value below that matters for legibility has been contrast-checked.
*/

:root {
  /* Grounds. Warm off-white, never pure white. */
  --ground:    #FAF9F5;
  --ground-2:  #F0EEE6;
  --ground-3:  #E3DACC;
  --rule:      #E1DED2;

  /* Ink. 17.5:1 on --ground. */
  --ink:       #141413;
  --ink-mute:  #6B6A63;   /* 5.15:1 on --ground: safe for body text */
  --ink-faint: #8A877C;   /* 3.41:1: large text and non-essential labels only */

  /* Pressific orange. A FILL, not a text colour: 2.65:1 on --ground.
     Buttons take ink text on it (6.60:1), never white (2.79:1, fails). */
  --accent:     #FE6F27;
  --accent-ink: #9A3412;  /* 6.94:1 on --ground: the accent when it must be text */

  /* On the dark panels */
  --on-dark:       #FAF9F5;  /* 17.5:1 */
  --on-dark-mute:  #B0AEA5;  /* 8.29:1 */

  --sans:  'Schibsted Grotesk', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --serif: 'Source Serif 4', Georgia, 'Times New Roman', serif;

  --r-sm: 8px;
  --r-md: 16px;
  --r-lg: 24px;

  /* Measured off the reference rather than picked: its content spans 1306px at
     a 1440 viewport and 1540px at 1920, which is min(90vw, 1540px). A fixed
     1240px cap left us 380px narrower than it on a large screen, and that gap
     is what made the page feel marooned in the middle of the display. */
  --maxw: 1540px;
  --gutter: max(24px, 5vw);

  /* Vertical rhythm. Sections breathe; that is most of the reference's feel. */
  --band: 128px;
}

@media (max-width: 900px) {
  :root { --gutter: 24px; --band: 80px; }
}

*, *::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(--ground);
  color: var(--ink);
  font-family: var(--serif);
  font-size: 20px;
  line-height: 1.45;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
}

@media (max-width: 700px) { body { font-size: 18px; } }

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

/* Focus is visible and on-brand. Never removed. */
:focus-visible {
  outline: 3px solid var(--accent-ink);
  outline-offset: 3px;
  border-radius: 2px;
}

.shell {
  /* The gutter is subtracted rather than added as padding, so the declared
     max-width is the width of the CONTENT, directly comparable to the
     reference's 1540px, instead of the content plus two gutters. */
  width: min(calc(100% - 2 * var(--gutter)), var(--maxw));
  margin-inline: auto;
}

.skip-link {
  position: absolute; left: -9999px;
  background: var(--ink); color: var(--on-dark);
  padding: 12px 20px; border-radius: 0 0 var(--r-sm) 0;
  font-family: var(--sans); font-size: 15px; z-index: 100;
}
.skip-link:focus { left: 0; top: 0; }

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

h1, h2, h3, .display {
  font-family: var(--sans);
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.08;
  margin: 0;
  text-wrap: balance;
}

h1 { font-size: clamp(40px, 5.2vw, 80px); }
h2 { font-size: clamp(31px, 3.3vw, 52px); line-height: 1.12; }
h3 { font-size: clamp(21px, 2vw, 25px); line-height: 1.2; letter-spacing: -0.01em; }

p { margin: 0 0 1.1em; max-width: 64ch; }
p:last-child { margin-bottom: 0; }

/* The copy is written one sentence per line. That rhythm IS the voice, so it
   gets a type style of its own rather than being run together into paragraphs. */
.lines {
  margin: 0 0 1.4em;
  max-width: 54ch;
}
ul.lines { list-style: none; padding: 0; }
.lines > span, .lines > li {
  display: block;
  margin-bottom: 0.28em;
}
.lines > span:last-child, .lines > li:last-child { margin-bottom: 0; }

/* Statement lines: the short declarative beats that carry the argument. */
.beats {
  font-family: var(--sans);
  font-weight: 500;
  font-size: clamp(21px, 2.1vw, 26px);
  line-height: 1.32;
  letter-spacing: -0.01em;
  list-style: none;
  margin: 0 0 1.2em;
  padding: 0;
  max-width: 34ch;
}
.beats li { margin-bottom: 0.34em; }
.beats li:last-child { margin-bottom: 0; }

/* A rule-separated list. Used where the copy enumerates. */
.stack {
  list-style: none;
  margin: 0;
  padding: 0;
  font-family: var(--sans);
  font-size: 18px;
  line-height: 1.4;
  max-width: 48ch;
}
.stack li {
  padding: 14px 0;
  border-top: 1px solid var(--rule);
}
.stack li:last-child { border-bottom: 1px solid var(--rule); }

.eyebrow {
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin: 0 0 22px;
}

.lede {
  font-size: clamp(21px, 1.7vw, 26px);
  line-height: 1.4;
  max-width: 44ch;
}

.mark {
  /* The one signature borrowed from the reference: key words underlined in
     the display type. Ink text, accent rule, so contrast never depends on it. */
  text-decoration: none;
  box-shadow: inset 0 -0.14em 0 var(--accent);
}

/* --------------------------------------------------------------- layout --- */

.band { padding-block: var(--band); }
.band--tight { padding-block: calc(var(--band) * 0.62); }
.band--2 { background: var(--ground-2); }

.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
  gap: clamp(32px, 5vw, 104px);
  align-items: start;
}
.split--even { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 860px) { .split, .split--even { grid-template-columns: 1fr; gap: 32px; } }

/*
  A heading column beside a long prose column leaves a tall empty rectangle on
  the left, which is the single thing that made this page read as unfinished.
  Rather than padding it out, the heading travels with the text it introduces:
  it stays in view while its section scrolls past, which is what the column is
  for. Only where the columns are deliberately unequal, and only at widths
  where the two sit side by side at all.
*/
@media (min-width: 861px) {
  .split:not(.split--even) > *:first-child {
    position: sticky;
    top: calc(72px + 36px);
    align-self: start;
  }
  /* Inside the dark panel the section is short enough that sticking reads as
     a stutter rather than as travel. */
  .panel .split > *:first-child { position: static; }
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 24px;
}
.grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1000px) { .grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px)  { .grid--4 { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------- chrome -- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--ground) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid transparent;
  transition: border-color .2s ease, background .2s ease;
}
.site-header[data-scrolled="true"] { border-bottom-color: var(--rule); }

.site-header__inner {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  height: 72px;
}

.wordmark {
  font-family: var(--sans);
  font-weight: 700;
  font-size: 19px;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}
.wordmark span { color: var(--ink-mute); font-weight: 500; }

.site-nav { display: flex; align-items: center; gap: 20px; }
.site-nav__links { display: flex; align-items: center; gap: 28px; }
/* Scoped to the plain links: a bare `.site-nav a` also matched the button and,
   being one specificity point heavier than `.btn--primary`, painted ink text on
   an ink background. The button rendered as a solid black rectangle. */
.site-nav__links a {
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  text-decoration: none;
  padding: 6px 0;
  border-bottom: 1px solid transparent;
}
.site-nav__links a:hover { border-bottom-color: var(--ink); }

/*
  The menu button only exists for people who have JavaScript, because only then
  can it do anything. Without it the links simply wrap onto a second row and
  stay reachable: a navigation that disappears when a script fails is a broken
  site, not a tidy one.
*/
.nav-toggle {
  display: none;
  align-items: center;
  gap: 8px;
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--ink-faint);
  border-radius: var(--r-sm);
  padding: 9px 14px;
  cursor: pointer;
}

@media (max-width: 1000px) {
  html:not(.js) .site-nav__links { flex-wrap: wrap; gap: 8px 18px; justify-content: flex-end; }
  html:not(.js) .site-header__inner { height: auto; padding-block: 12px; flex-wrap: wrap; }

  .js .nav-toggle { display: inline-flex; }
  .js .site-nav__links { display: none; }
  .js .site-nav__links[data-open="true"] {
    display: flex;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    background: var(--ground);
    border-block: 1px solid var(--rule);
    padding: 12px var(--gutter) 20px;
  }
  .js .site-nav__links[data-open="true"] a { padding: 10px 0; font-size: 17px; }
}

@media (max-width: 460px) {
  .site-header__inner .btn { display: none; }
}

/* ---------------------------------------------------------------- button -- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font-family: var(--sans);
  font-size: 16px;
  font-weight: 500;
  line-height: 1;
  padding: 15px 26px;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  text-decoration: none;
  cursor: pointer;
  transition: transform .18s ease, background .18s ease, border-color .18s ease;
}
.btn:hover { transform: translateY(-1px); }
@media (prefers-reduced-motion: reduce) { .btn:hover { transform: none; } }

.btn--primary { background: var(--ink); color: var(--on-dark); }
.btn--primary:hover { background: #2a2a27; }

.btn--accent { background: var(--accent); color: var(--ink); }
.btn--accent:hover { background: #F05F14; }

.btn--ghost { background: transparent; color: var(--ink); border-color: var(--ink); }
.btn--ghost:hover { background: var(--ink); color: var(--on-dark); }

.btn--sm { font-size: 15px; padding: 11px 18px; }

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

.hero { padding-block: clamp(64px, 10vh, 116px) clamp(40px, 5vh, 64px); }
.hero__grid {
  display: grid;
  /*
    `auto`, not a fraction.

    Our headline is 34 characters where the reference's is 54, so any
    proportional column leaves the short headline sitting in a column far wider
    than the text it holds. That unused column, not the grid gap, was the
    277px channel between the headline and the paragraph. `auto` sizes the
    column to the headline itself, so the gap is the only thing separating
    them.
  */
  grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(28px, 4vw, 76px);
  align-items: start;
}
.hero__aside { padding-top: clamp(6px, 2.6vw, 42px); }
@media (max-width: 900px) {
  .hero__grid { grid-template-columns: 1fr; gap: 36px; }
  .hero__aside { padding-top: 0; }
}

/*
  No max-width on the hero headline.

  It was capped at 15ch inside an 834px column, so it stopped 189px short of
  its own column edge. Added to the 88px grid gap, that opened a 277px channel
  between the headline and the paragraph beside it, against 76px on the
  reference. The measured gap was never the problem: the unused column was.
*/
/*
  The measure is in em, not ch, and it is deliberately just wider than the
  longest line.

  `ch` is the width of the "0" glyph, which in this grotesque is 0.64em, so a
  15ch cap produced a 765px box around a 672px line: 93px of slack sitting
  inside the heading itself, which the hugging column then faithfully preserved.
  The widest line measures 8.4em, so 8.7em holds the same two-line break with a
  little tolerance and no dead space behind it.
*/
.hero h1 { max-width: 8.7em; }

/*
  The paragraph is offset down so its first baseline meets the headline's,
  rather than its box top meeting the headline's box top. Display type carries
  far more leading above the baseline than body text does, so aligning the
  boxes leaves the two first lines visibly out of step.
*/
/* No measure cap here: the column is already the measure. Capping it as well
   left the paragraph stopping short of the container edge, so its right margin
   no longer lined up with the photograph directly beneath it, and that
   misalignment is what reads as unconsidered. */
.hero__aside p { font-size: clamp(19px, 1.15vw, 22px); max-width: none; }
.hero__cta { margin-top: 36px; display: flex; flex-wrap: wrap; gap: 14px; align-items: center; }

.strapline {
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--ink-mute);
  margin: 0;
}

/* ----------------------------------------------------------------- panel -- */

.panel {
  background: var(--ink);
  color: var(--on-dark);
  border-radius: var(--r-lg);
  padding: clamp(40px, 6vw, 80px);
}
.panel h2, .panel h3 { color: var(--on-dark); }
.panel p { color: var(--on-dark-mute); }
.panel .eyebrow { color: var(--on-dark-mute); }
.panel .stack { color: var(--on-dark); }
.panel .stack li { border-color: #2E2E2A; }

/* margin:0 is load-bearing. The UA stylesheet gives <figure> `margin: 1em 40px`,
   which quietly inset every photograph 40px inside its container and made the
   page look narrower than its own grid. */
.figure { margin: 0; border-radius: var(--r-lg); overflow: hidden; background: var(--ground-3); }

/* Two photographs side by side, used where the copy argues in two columns
   (what AI does / what humans bring). Collapses with the split above it. */
.figure-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; }
@media (max-width: 860px) { .figure-pair { grid-template-columns: 1fr; } }
.figure img { width: 100%; }
.figure figcaption {
  font-family: var(--sans);
  font-size: 14px;
  color: var(--ink-mute);
  padding-top: 12px;
}

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

.card {
  background: var(--ground);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  padding: 28px;
}
.card h3 { margin-bottom: 10px; }
.card p { font-size: 17px; line-height: 1.45; color: var(--ink-mute); margin: 0; }

.panel .card {
  background: #1E1E1B;
  border-color: #2E2E2A;
}
.panel .card p { color: var(--on-dark-mute); }

/* ------------------------------------------------- capabilities panel -- */
/* One unified panel with hairline dividers (gap over a rule-coloured bed),
   so six items read as a considered set rather than six floating boxes. */
.caps {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  margin-top: 56px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.cap {
  position: relative;
  background: var(--ground);
  padding: 40px 34px 44px;
  transition: background 0.45s var(--ease-soft);
}
.cap__n {
  position: absolute;
  top: 26px;
  right: 30px;
  font-family: var(--serif);
  font-size: 34px;
  line-height: 1;
  font-weight: 400;
  color: var(--ground-3);
  transition: color 0.45s var(--ease-soft);
}
.cap h3 {
  font-family: var(--sans);
  font-size: 21px;
  letter-spacing: -0.01em;
  margin: 0 44px 14px 0;
  padding-top: 16px;
}
/* Accent tick above each title; grows on hover. */
.cap h3::before {
  content: "";
  position: absolute;
  top: 40px;
  left: 34px;
  width: 26px;
  height: 3px;
  background: var(--accent);
  border-radius: 2px;
  transition: width 0.45s var(--ease-soft);
}
.cap p {
  font-size: 16.5px;
  line-height: 1.5;
  color: var(--ink-mute);
  margin: 0;
}
.js .cap:hover { background: var(--ground-2); }
.js .cap:hover .cap__n { color: var(--accent); }
.js .cap:hover h3::before { width: 48px; }

@media (max-width: 900px) {
  .caps { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 40px; }
}
@media (max-width: 560px) {
  .caps { grid-template-columns: 1fr; }
  .cap { padding: 32px 26px 34px; }
}

/* Card icons - inlined line icons from the Pressific resource library.
   fill inherits currentColor so they match whichever band they sit in;
   drawn small and quiet so the numbers stay the organising device. */
.card__icon {
  display: block;
  width: 36px;
  height: 36px;
  fill: #1f6e3a;   /* the logo's green: ties the icon set to the mark */
  margin: 14px 0 12px;
}
.card__pulse + .card__icon { margin-top: 6px; }

.card__num {
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 500;
  color: var(--accent);
  letter-spacing: 0.1em;
  display: block;
  margin-bottom: 14px;
}

/* -------------------------------------------------------------- contrast -- */

.versus {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px;
  margin-top: 40px;
}
@media (max-width: 700px) { .versus { grid-template-columns: 1fr; } }
.versus > div {
  border-top: 3px solid var(--ink);
  padding-top: 20px;
  font-family: var(--sans);
  font-size: clamp(19px, 1.8vw, 23px);
  font-weight: 500;
  line-height: 1.25;
}
.versus > div:last-child { border-top-color: var(--accent); }

/* --------------------------------------------------------------- pricing -- */

.currency-switch {
  display: inline-flex;
  gap: 4px;
  padding: 4px;
  background: var(--ground-2);
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
}
.currency-switch button {
  font-family: var(--sans);
  font-size: 14px;
  font-weight: 500;
  padding: 8px 16px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--ink-mute);
  cursor: pointer;
}
.currency-switch button[aria-pressed="true"] {
  background: var(--ink);
  color: var(--on-dark);
}

/* All three prices ship in the HTML. This only chooses which one shows, so a
   visitor with no JavaScript still gets a complete, correctly priced page:
   <html> carries data-currency="GBP" from the server. */
[data-price] > span { display: none; }
[data-currency="GBP"] [data-price] > span[data-cur="GBP"],
[data-currency="USD"] [data-price] > span[data-cur="USD"],
[data-currency="EUR"] [data-price] > span[data-cur="EUR"] { display: inline; }

.tiers {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
  align-items: start;
  margin-top: 44px;
}
@media (max-width: 1000px) { .tiers { grid-template-columns: 1fr; max-width: 560px; } }

.tier {
  background: var(--ground);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  padding: 32px 28px;
  display: flex;
  flex-direction: column;
  height: 100%;
}
.tier--feature { border-color: var(--ink); border-width: 2px; }

/*
  Only the featured tier carries a flag, which pushed its name and price a flag's
  height below the other two and broke the line the three prices should share.
  The other cards reserve the same space. Side by side only: once the tiers stack
  there is no shared line to hold, and the gap would just be dead space.
*/
@media (min-width: 1001px) {
  .tier:not(.tier--feature)::before {
    content: "";
    display: block;
    height: 28px;
    margin-bottom: 16px;
  }
}

.tier__flag {
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  background: var(--accent);
  color: var(--ink);
  border-radius: 4px;
  padding: 5px 10px;
  align-self: flex-start;
  margin-bottom: 16px;
}
.tier__name { font-family: var(--sans); font-weight: 700; font-size: 24px; margin: 0 0 18px; }
.tier__price {
  font-family: var(--sans);
  font-weight: 700;
  font-size: 42px;
  line-height: 1;
  letter-spacing: -0.02em;
  margin: 0;
}
.tier__month {
  font-family: var(--sans);
  font-size: 17px;
  font-weight: 500;
  color: var(--ink-mute);
  margin: 10px 0 0;
}
.tier__for {
  font-size: 17px;
  line-height: 1.45;
  color: var(--ink-mute);
  margin: 20px 0 0;
  padding-bottom: 24px;
  border-bottom: 1px solid var(--rule);
}
.tier__group { margin-top: 24px; }
.tier__group h4 {
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin: 0 0 14px;
}
.tier__list {
  list-style: none;
  margin: 0;
  padding: 0;
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.4;
}
.tier__list li {
  position: relative;
  padding-left: 24px;
  margin-bottom: 10px;
}
.tier__list li::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 0.52em;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  background: var(--accent);
}
.tier__best {
  font-size: 16px;
  color: var(--ink-mute);
  margin: 24px 0 20px;
  padding-top: 20px;
  border-top: 1px solid var(--rule);
}
.tier .btn { margin-top: auto; width: 100%; }

.vat { font-family: var(--sans); font-size: 15px; color: var(--ink-mute); margin-top: 28px; }

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

.site-footer {
  background: var(--ink);
  color: var(--on-dark);
  padding-block: 72px 40px;
}
.site-footer a { color: var(--on-dark); }
.site-footer__top {
  display: flex;
  flex-wrap: wrap;
  gap: 32px;
  justify-content: space-between;
  align-items: flex-start;
  padding-bottom: 40px;
  border-bottom: 1px solid #2E2E2A;
}
.site-footer .wordmark { color: var(--on-dark); font-size: 22px; }
.site-footer .wordmark span { color: var(--on-dark-mute); }
.site-footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 28px;
  font-family: var(--sans);
  font-size: 15px;
}
.site-footer__legal {
  padding-top: 28px;
  font-family: var(--sans);
  font-size: 14px;
  color: var(--on-dark-mute);
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
  justify-content: space-between;
}
.site-footer__legal a { color: var(--on-dark-mute); }

/* -------------------------------------------------------------------- a11y */

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

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

/*
  Motion is decoration and must never be load-bearing. Every from-state below
  is scoped under .js, a class this document only gets if JavaScript ran, so a
  visitor without it sees finished, fully visible content. Reduced motion turns
  the whole thing off.
*/
@media (prefers-reduced-motion: no-preference) {
  .js .reveal {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity .7s cubic-bezier(.22,.61,.36,1), transform .7s cubic-bezier(.22,.61,.36,1);
  }
  .js .reveal.is-in {
    opacity: 1;
    transform: none;
  }
}

/* ------------------------------------------------------------------ form -- */

.field { margin-bottom: 20px; }
.field label {
  display: block;
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 500;
  margin-bottom: 8px;
}
.field input,
.field textarea {
  width: 100%;
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.4;
  color: var(--ink);
  background: var(--ground);
  border: 1px solid var(--ink-faint);
  border-radius: var(--r-sm);
  padding: 13px 14px;
}
.field textarea { resize: vertical; min-height: 120px; }
.field input:focus, .field textarea:focus { border-color: var(--ink); }
.field input:user-invalid, .field textarea:user-invalid { border-color: var(--accent-ink); }

/* The honeypot. Hidden from people, still reachable by the bots that fill it. */
.field--gotcha {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-status {
  font-family: var(--sans);
  font-size: 15px;
  margin: 0 0 16px;
  color: var(--accent-ink);
}
.form-status:empty { margin: 0; }

.start-form .btn { width: 100%; }

/* ============================================================== motion ====
   Composed from the luxury-dark motif's motion vocabulary
   (/srv/vibedsites/motifs/luxury-dark/css/base.css): word-split headlines,
   [data-stagger] child indexing, reveal-on-enter, parallax via --py. The
   primitives are lifted; the timing and the register are re-cut for a warm,
   light, editorial page rather than a dark luxury one.

   THE RULE THAT OUTRANKS ALL OF THIS: motion never decides whether content is
   readable. Every from-state below sits under `.js`, a class only added once
   the motion script is running, and a watchdog force-finishes everything after
   2.5s. No JavaScript, a thrown error, a dead IntersectionObserver: the page is
   simply finished and static. This shop has shipped invisible sections before.
   ========================================================================= */

:root {
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-soft: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* ----- Reading-progress rule ----- */
.progress {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 2px;
  background: transparent;
  z-index: 60;
  pointer-events: none;
}
.progress__bar {
  height: 100%;
  width: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
}
html:not(.js) .progress { display: none; }

/* ----- Word-split headlines ----- */
/* A split heading manages its own entrance, so it must not also be faded out
   as a whole: the two would compound into a double delay. */
.js [data-split].reveal { opacity: 1; transform: none; }
.word {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  line-height: inherit;
  /* room for descenders, which overflow:hidden would otherwise shear off */
  padding-bottom: 0.1em;
  margin-bottom: -0.1em;
}
.js .word-inner {
  display: inline-block;
  transform: translate3d(0, 110%, 0);
  opacity: 0;
  transition:
    transform 1s var(--ease-out),
    opacity 0.8s var(--ease-out);
  transition-delay: calc(var(--w, 0) * 0.045s);
  will-change: transform;
}
.js [data-split].is-in .word-inner { transform: none; opacity: 1; }

/* The accent rule under a marked word draws itself in after the word lands.
   Drawn as a background rather than a border or box-shadow so the word's own
   overflow:hidden cannot clip it. */
.mark {
  background-image: linear-gradient(var(--accent), var(--accent));
  background-repeat: no-repeat;
  background-position: 0 92%;
  background-size: 100% 0.13em;
  box-shadow: none;
}
.js .mark {
  background-size: 0% 0.13em;
  transition: background-size 0.9s var(--ease-out) 0.5s;
}
.js [data-split].is-in .mark,
.js .mark.is-in { background-size: 100% 0.13em; }

/* ----- Staggered children ----- */
.js [data-stagger] > * {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 0.8s var(--ease-soft), transform 0.8s var(--ease-soft);
  transition-delay: calc(var(--i, 0) * 0.07s);
}
.js [data-stagger].is-in > * { opacity: 1; transform: none; }

/* ----- Parallax, JS writes --py ----- */
.js [data-parallax] { will-change: transform; }
.js [data-parallax] img { transform: translate3d(0, var(--py, 0px), 0) scale(1.06); }

/* ----- The opening: the hero photograph widens out of a centre column ----- */
.js .figure--open { clip-path: inset(0 46% 0 46% round var(--r-lg)); }
.js .figure--open.is-in {
  clip-path: inset(0 0 0 0 round var(--r-lg));
  transition: clip-path 1.5s var(--ease-out) 0.25s;
}

/* Scroll-driven opening. Where the browser supports scroll timelines the
   curtain tracks the scroll position itself - opening as the figure enters,
   closing again if you scroll back - instead of playing once on a timer. The
   one-shot reveal above stays as the fallback everywhere else. Keyframes hold
   both end states (fill both), and the base clip-path stays the OPEN state so
   a timeline that never engages can never hide the photograph. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .js .figure--open,
    .js .figure--open.is-in {
      clip-path: inset(0 0 0 0 round var(--r-lg));
      transition: none;
      animation: studio-open linear both;
      animation-timeline: view();
      animation-range: entry 5% cover 45%;
    }
    @keyframes studio-open {
      from { clip-path: inset(0 46% 0 46% round var(--r-lg)); }
      to   { clip-path: inset(0 0 0 0 round var(--r-lg)); }
    }
  }
}

/* Ambient drift. Continuous, slow enough to read as depth rather than motion,
   and it only ever scales up so no edge is ever exposed. */
@keyframes studio-drift {
  from { transform: scale(1.03) translate3d(var(--mx, 0px), calc(var(--py, 0px) + var(--my, 0px)), 0); }
  to   { transform: scale(1.09) translate3d(var(--mx, 0px), calc(var(--py, 0px) + var(--my, 0px)), 0); }
}
/* alternate + infinite: a drift that finishes and stops leaves a page that is
   dead at rest, which is exactly what it was accused of. */
.js .figure--open.is-in img {
  animation: studio-drift 24s var(--ease-soft) infinite alternate;
}
/* Custom properties are read fresh every frame, so the pointer offset written
   by JS composes with the keyframed scale rather than fighting it. */
.js .figure img { transition: none; }

/* ----- The photographs answer the pointer ----- */
.js .figure { position: relative; }
.js [data-tilt] img { will-change: transform; }

/* ----- Ambient: the monitoring cards show a live pulse -----
   The section claims the platform is continuously watching. A static list of
   four boxes asserts that; a quiet pulse demonstrates it. Decorative only:
   the dot carries no information a screen reader needs, so it is aria-hidden
   in the markup. */
.card__pulse {
  display: block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent);
  margin-bottom: 16px;
  position: relative;
}
.js .card__pulse::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--accent);
  animation: studio-pulse 2.8s var(--ease-soft) infinite;
  animation-delay: calc(var(--i, 0) * 0.7s);
}
@keyframes studio-pulse {
  0%   { transform: scale(1);   opacity: 0.55; }
  70%  { transform: scale(3.2); opacity: 0; }
  100% { transform: scale(3.2); opacity: 0; }
}

/* ----- The two contrasting statements draw their rules in ----- */
.js .versus > div { position: relative; border-top-color: transparent; }
.js .versus > div::before {
  content: "";
  position: absolute;
  top: -3px; left: 0;
  height: 3px;
  width: 100%;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 1s var(--ease-out);
}
.js .versus > div:last-child::before {
  background: var(--accent);
  transition-delay: 0.18s;
}
.js .versus.is-in > div::before { transform: scaleX(1); }

/* ----- Hover: the cards lift, the photographs breathe ----- */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .card, .tier { transition: transform 0.4s var(--ease-soft), border-color 0.4s ease; }
  .card:hover { transform: translateY(-3px); border-color: var(--ink-faint); }
  .tier:hover { transform: translateY(-4px); }
}

/* ----- Everything above is off when the visitor asks for less motion.
         Final states only, no transitions, no infinite animations. ----- */
@media (prefers-reduced-motion: reduce) {
  .js .word-inner,
  .js [data-stagger] > *,
  .js [data-split].reveal,
  .js .reveal {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .js .mark { background-size: 100% 0.13em !important; transition: none !important; }
  .js .figure--open,
  .js .figure--open.is-in { clip-path: none !important; transition: none !important; animation: none !important; }
  .js .figure--open.is-in img { animation: none !important; transform: none !important; }
  .js [data-parallax] img { transform: none !important; }
  .js .card__pulse::after { animation: none !important; }
  .js .versus > div { border-top-color: var(--ink); }
  .js .versus > div:last-child { border-top-color: var(--accent); }
  .js .versus > div::before { display: none !important; }
  .progress__bar { transition: none !important; }
}


/* ----- Live network in the dark panel -----
   The section's claim is that the platform is continuously watching. This is
   that claim drawn: nodes drift, links form and break between the ones that are
   near each other, and the pointer pulls the nearest ones toward it. Purely
   decorative, so it is aria-hidden, sits behind the text, takes no pointer
   events of its own, and is not painted at all for a visitor who asked for
   reduced motion or has no JavaScript. */
.panel { position: relative; overflow: hidden; }
.panel > * { position: relative; z-index: 1; }
.panel__net {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: none;
  pointer-events: none;
}
.js .panel__net { display: block; }
@media (prefers-reduced-motion: reduce) {
  .js .panel__net { display: none; }
}

/* ----- Buttons lean toward the pointer ----- */
.js .btn { will-change: transform; }
.js .btn[data-magnet] {
  transform: translate3d(var(--bx, 0px), var(--by, 0px), 0);
  transition: transform 0.25s var(--ease-soft), background 0.18s ease;
}
.js .btn[data-magnet]:hover { transition-duration: 0.08s; }
@media (prefers-reduced-motion: reduce) {
  .js .btn[data-magnet] { transform: none !important; }
}

/* ----- Cards light up under the pointer ----- */
.card, .tier { position: relative; overflow: hidden; }
.card > *, .tier > * { position: relative; z-index: 1; }
.js .card::after, .js .tier::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0;
  transition: opacity 0.35s ease;
  background: radial-gradient(
    260px circle at var(--cx, 50%) var(--cy, 50%),
    color-mix(in srgb, var(--accent) 12%, transparent),
    transparent 70%);
}
@media (hover: hover) {
  .js .card:hover::after, .js .tier:hover::after { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .js .card::after, .js .tier::after { display: none; }
}

/* ----- Nav underline sweeps rather than snapping ----- */
.site-nav__links a {
  background-image: linear-gradient(var(--ink), var(--ink));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  border-bottom: 0;
  transition: background-size 0.32s var(--ease-out);
}
.site-nav__links a:hover { background-size: 100% 1px; }
@media (prefers-reduced-motion: reduce) {
  .site-nav__links a { transition: none; }
}

/* ================================================================ work ====
   Live client sites. Two feature cards on the top row, three below, so five
   items land on a balanced grid rather than leaving a lone orphan on row two.
   ========================================================================= */

.work {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 28px;
  margin-top: 48px;
}
.work > *:nth-child(-n+2) { grid-column: span 3; }
.work > *:nth-child(n+3)  { grid-column: span 2; }

@media (max-width: 1000px) {
  .work { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; }
  .work > * { grid-column: span 1; }
}
@media (max-width: 620px) {
  .work { grid-template-columns: 1fr; }
}

.work-card {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--ground);
  height: 100%;
}
.work-card__shot {
  margin: 0;
  overflow: hidden;
  background: var(--ground-3);
  border-bottom: 1px solid var(--rule);
  aspect-ratio: 16 / 10;
}
.work-card__shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  display: block;
}
.work-card__body { padding: 22px 24px 26px; display: flex; flex-direction: column; flex: 1; }
.work-card h3 { font-size: clamp(19px, 1.5vw, 23px); margin-bottom: 8px; }
.work-card__desc {
  font-size: 16px;
  line-height: 1.45;
  color: var(--ink-mute);
  margin: 0 0 18px;
  max-width: none;
}
.work-card__meta {
  margin: auto 0 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  font-family: var(--sans);
  font-size: 14px;
  color: var(--ink-mute);
}
.work-card__tag {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 4px 9px;
  border-radius: 4px;
  background: var(--ground-2);
  border: 1px solid var(--rule);
  color: var(--ink);
}
.work-card__tag--built { background: var(--accent); border-color: var(--accent); }
.work-card__domain { display: inline-flex; align-items: center; gap: 6px; }
.work-card__domain::after {
  content: "\2197";
  font-size: 13px;
  transition: transform 0.25s var(--ease-soft);
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .work-card { transition: transform 0.4s var(--ease-soft), border-color 0.3s ease; }
  .work-card:hover { transform: translateY(-4px); border-color: var(--ink-faint); }
  .work-card__shot img { transition: transform 0.7s var(--ease-soft); }
  .work-card:hover .work-card__shot img { transform: scale(1.045); }
  .work-card:hover .work-card__domain::after { transform: translate(2px, -2px); }
}

/* ------------------------------------------------------------- intro ----- */
/* Hidden unless the script opts in (.intro--play), so a no-JS visitor never
   sees it and a failure can only mean NO overlay, never a stuck one. */
.intro { display: none; }
.intro--play {
  display: grid;
  place-items: center;
  position: fixed;
  inset: 0;
  z-index: 200;
  background: var(--ground);
  cursor: pointer;
  padding: var(--gutter);
}
html.intro-lock { overflow: hidden; }
.intro__line {
  font-family: var(--sans);
  font-weight: 700;
  font-size: clamp(28px, 4.6vw, 64px);
  line-height: 1.14;
  letter-spacing: -0.015em;
  color: var(--ink);
  max-width: 18ch;
  margin: 0;
  text-wrap: balance;
}
.intro__beat {
  display: block;
  opacity: 0;
  transform: translateY(0.35em);
  animation: intro-beat 0.5s var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1)) forwards;
  animation-delay: calc(var(--b) * 0.26s);
}
.intro__mark {
  font-style: normal;
  box-shadow: inset 0 -0.14em 0 var(--accent);
}
@keyframes intro-beat {
  to { opacity: 1; transform: none; }
}
.intro--out {
  transition: transform 0.6s cubic-bezier(0.7, 0, 0.3, 1), opacity 0.6s ease;
  transform: translateY(-100%);
  opacity: 0.98;
  pointer-events: none;
}

/* --------------------------------------------------- image interactions --- */
/* Four photographs, four different behaviours, each one demonstrating the
   claim of the section it sits in. All of it is plain CSS driven by a few
   custom properties; nothing here loads a library or blocks the page, and
   with JS absent every figure is simply a photograph. */

/* 1. X-ray lens (the workspace photo): a circular lens follows the pointer
      and shows the "layers underneath" - the same image re-graded as a
      technical blueprint with a measurement grid over it. */
.fx-lens { position: relative; }
.fx-lens__alt {
  position: absolute; inset: 0;
  --x: 50%; --y: 50%;
  opacity: 0;
  transition: opacity 0.35s ease;
  pointer-events: none;
  -webkit-mask-image: radial-gradient(circle 150px at var(--x) var(--y), #000 0 58%, transparent 62%);
          mask-image: radial-gradient(circle 150px at var(--x) var(--y), #000 0 58%, transparent 62%);
  background:
    repeating-linear-gradient(0deg,  rgba(20, 35, 43, 0.22) 0 1px, transparent 1px 32px),
    repeating-linear-gradient(90deg, rgba(20, 35, 43, 0.22) 0 1px, transparent 1px 32px),
    var(--ground-2);
}
.fx-lens__alt img {
  width: 100%; height: 100%; object-fit: cover;
  filter: grayscale(1) contrast(1.5) brightness(1.2);
  mix-blend-mode: multiply;
  opacity: 0.72;
}
/* Drafting grid ON TOP of the re-grade, so the lens reads as a blueprint over
   dark screen areas too, plus the lens ring itself. */
.fx-lens__alt::after {
  content: "";
  position: absolute; inset: 0;
  background:
    repeating-linear-gradient(0deg,  color-mix(in srgb, var(--accent-ink) 42%, transparent) 0 1px, transparent 1px 32px),
    repeating-linear-gradient(90deg, color-mix(in srgb, var(--accent-ink) 42%, transparent) 0 1px, transparent 1px 32px);
}
.fx-lens__alt::before {
  content: "";
  position: absolute;
  width: 272px; height: 272px;
  left: var(--x); top: var(--y);
  transform: translate(-50%, -50%);
  border-radius: 50%;
  border: 1.5px solid var(--accent);
  z-index: 1;
}
.fx-lens.is-live .fx-lens__alt { opacity: 1; }
.fx-lens__tag {
  position: absolute; left: 20px; bottom: 16px;
  font-family: var(--sans); font-size: 12px; font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--on-dark);
  background: color-mix(in srgb, var(--ink) 72%, transparent);
  padding: 7px 12px; border-radius: 999px;
  opacity: 0; transform: translateY(6px);
  transition: opacity 0.3s ease, transform 0.3s ease;
}
.fx-lens:hover .fx-lens__tag { opacity: 1; transform: none; }
@media (hover: none) { .fx-lens__alt, .fx-lens__tag { display: none; } }

/* 2. Breathing diptych (the AI / human pair): attention follows the pointer.
      Widths rebalance towards whichever half you consider. */
.fx-duo { display: flex; gap: 24px; }
.fx-duo__panel {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  height: clamp(260px, 30vw, 430px);
  transition: flex-grow 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}
.fx-duo__panel img { width: 100%; height: 100%; object-fit: cover; }
.fx-duo:hover .fx-duo__panel { flex-grow: 0.72; }
.fx-duo .fx-duo__panel:hover { flex-grow: 1.9; }
.fx-duo__tag {
  position: absolute; left: 16px; bottom: 14px;
  font-family: var(--sans); font-size: 13px; font-weight: 600;
  color: var(--ink);
  background: color-mix(in srgb, var(--ground) 88%, transparent);
  backdrop-filter: blur(6px);
  padding: 7px 14px; border-radius: 999px;
  opacity: 0; transform: translateY(8px);
  transition: opacity 0.35s ease 0.08s, transform 0.35s ease 0.08s;
  white-space: nowrap;
}
.fx-duo__panel:hover .fx-duo__tag { opacity: 1; transform: none; }
@media (max-width: 860px) {
  .fx-duo { flex-direction: column; }
  .fx-duo__panel, .fx-duo:hover .fx-duo__panel, .fx-duo .fx-duo__panel:hover { flex: 0 0 auto; height: 240px; }
  .fx-duo__tag { opacity: 1; transform: none; }
  /* the open-from-seam entry animates clip on width; vertical stack wants none */
  .fx-duo.is-in .fx-duo__panel { animation: none; }
}
@media (hover: none) { .fx-duo__tag { opacity: 1; transform: none; } }

/* 3. Comparison slider (the review photo): a real <input type="range"> spans
      the figure, so dragging, arrow keys and assistive tech all work without
      any custom event gymnastics. The left share shows the "draft" grade. */
.fx-compare { position: relative; --pos: 50%; }
.fx-compare__b {
  position: absolute; inset: 0;
  clip-path: inset(0 calc(100% - var(--pos)) 0 0);
  pointer-events: none;
}
.fx-compare__b img {
  width: 100%; height: 100%; object-fit: cover;
  /* the before is a real AI wireframe draft image, no synthetic grade needed */
}
.fx-compare__handle {
  position: absolute; top: 0; bottom: 0;
  left: var(--pos);
  width: 2px; transform: translateX(-1px);
  background: var(--ground);
  box-shadow: 0 0 0 1px rgba(20, 35, 43, 0.18);
  pointer-events: none;
}
.fx-compare__handle::after {
  content: "\2194";
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--ground);
  color: var(--ink);
  font-size: 18px;
  box-shadow: 0 2px 10px rgba(20, 35, 43, 0.22);
}
.fx-compare__label {
  position: absolute; top: 14px;
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase;
  padding: 6px 12px; border-radius: 999px;
  pointer-events: none;
}
.fx-compare__label--a { left: 14px;  background: color-mix(in srgb, var(--ink) 78%, transparent); color: var(--on-dark); }
.fx-compare__label--b { right: 14px; background: color-mix(in srgb, var(--ground) 90%, transparent); color: var(--ink); }
.fx-compare__range {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  margin: 0; opacity: 0;
  cursor: ew-resize;
  touch-action: pan-y;
}
.fx-compare__range:focus-visible ~ * { outline: none; }
.fx-compare:has(.fx-compare__range:focus-visible) {
  outline: 3px solid var(--accent-ink);
  outline-offset: 3px;
}

/* 4. Colour explorer (the palette photo): sweeping across it re-grades the
      photograph live and deals a matching swatch trio, --h driven. */
.fx-tint { position: relative; --h: 26; cursor: crosshair; }
.fx-tint__wash {
  position: absolute; inset: 0;
  background: hsl(var(--h) 72% 46%);
  mix-blend-mode: color;
  opacity: 0;
  transition: opacity 0.4s ease;
  pointer-events: none;
}
.fx-tint.is-live .fx-tint__wash { opacity: 0.5; }
.fx-tint__chips {
  position: absolute; right: 16px; bottom: 14px;
  display: flex; gap: 8px;
  opacity: 0; transform: translateY(8px);
  transition: opacity 0.35s ease, transform 0.35s ease;
  pointer-events: none;
}
.fx-tint.is-live .fx-tint__chips { opacity: 1; transform: none; }
.fx-tint__chips i {
  width: 26px; height: 26px; border-radius: 8px;
  box-shadow: inset 0 0 0 1px rgba(20, 35, 43, 0.15), 0 2px 8px rgba(20, 35, 43, 0.25);
}
.fx-tint__chips i:nth-child(1) { background: hsl(var(--h) 72% 46%); }
.fx-tint__chips i:nth-child(2) { background: hsl(calc(var(--h) + 28) 64% 62%); }
.fx-tint__chips i:nth-child(3) { background: hsl(calc(var(--h) + 200) 38% 30%); }
.fx-tint__tag {
  position: absolute; left: 20px; bottom: 16px;
  font-family: var(--sans); font-size: 12px; font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--on-dark);
  background: color-mix(in srgb, var(--ink) 72%, transparent);
  padding: 7px 12px; border-radius: 999px;
  opacity: 0; transform: translateY(6px);
  transition: opacity 0.3s ease, transform 0.3s ease;
}
.fx-tint:hover .fx-tint__tag, .fx-tint:focus-visible .fx-tint__tag { opacity: 1; transform: none; }
.fx-tint:focus-visible { outline: 3px solid var(--accent-ink); outline-offset: 3px; }

/* Reduced motion: the slider and colour explorer are user-driven and stay,
   but every self-moving flourish goes. */
@media (prefers-reduced-motion: reduce) {
  .fx-duo__panel { transition: none !important; }
  .fx-duo:hover .fx-duo__panel, .fx-duo .fx-duo__panel:hover { flex-grow: 1 !important; }
  .fx-lens__alt, .fx-lens__tag { display: none !important; }
  .fx-tint__wash, .fx-tint__chips, .fx-tint__tag { transition: none !important; }
}

/* ------------------------------------------ image fx: scroll self-demo ---- */
/* The interactions above are hover-driven, which means a visitor who is only
   scrolling never sees them. On entry (.is-in, set by the reveal observer) each
   figure now performs its own idea once, so the capability is visible without a
   pointer; hover remains the replay / explore layer. All one-shot, all disabled
   under reduced motion. */

/* Lens: a bright scan bar sweeps across on entry, dragging the blueprint grid
   behind it, so the "layers underneath" reveal themselves as you scroll. */
.fx-lens__scan {
  position: absolute; inset: 0;
  pointer-events: none; opacity: 0;
  background:
    linear-gradient(90deg, transparent 0 calc(var(--sweep, 0%) - 9%),
      color-mix(in srgb, var(--accent) 85%, transparent) calc(var(--sweep, 0%) - 0.4%),
      #fff calc(var(--sweep, 0%)),
      color-mix(in srgb, var(--accent) 85%, transparent) calc(var(--sweep, 0%) + 0.4%),
      transparent calc(var(--sweep, 0%) + 9%)),
    repeating-linear-gradient(0deg,  color-mix(in srgb, var(--accent-ink) 55%, transparent) 0 1px, transparent 1px 34px),
    repeating-linear-gradient(90deg, color-mix(in srgb, var(--accent-ink) 55%, transparent) 0 1px, transparent 1px 34px);
  -webkit-mask-image: linear-gradient(90deg, #000 0 var(--sweep, 0%), transparent var(--sweep, 0%));
          mask-image: linear-gradient(90deg, #000 0 var(--sweep, 0%), transparent var(--sweep, 0%));
}
@property --sweep { syntax: "<percentage>"; inherits: true; initial-value: 0%; }
.fx-lens.is-in .fx-lens__scan { animation: fx-lens-sweep 2.1s cubic-bezier(0.65,0,0.35,1) 0.25s 1 both; }
@keyframes fx-lens-sweep {
  0%   { opacity: 0;   --sweep: 0%; }
  8%   { opacity: 0.95; }
  70%  { opacity: 0.95; --sweep: 108%; }
  100% { opacity: 0;   --sweep: 108%; }
}

/* Tint: on entry the photograph is auto-graded through a few colour directions,
   dealing the swatch chips, then settles back to full colour. */
@property --h { syntax: "<number>"; inherits: true; initial-value: 26; }
.fx-tint.is-in { animation: fx-tint-hue 4.4s ease 0.3s 1 both; }
.fx-tint.is-in .fx-tint__wash { animation: fx-tint-wash 4.4s ease 0.3s 1 both; }
.fx-tint.is-in .fx-tint__chips { animation: fx-tint-chips 4.4s ease 0.3s 1 both; }
@keyframes fx-tint-hue  { 0%{--h:26} 20%{--h:210} 45%{--h:320} 70%{--h:110} 100%{--h:26} }
@keyframes fx-tint-wash { 0%,100%{opacity:0} 12%,80%{opacity:0.5} }
@keyframes fx-tint-chips{ 0%,100%{opacity:0;transform:translateY(8px)} 15%,85%{opacity:1;transform:none} }

/* Diptych: opens from a central seam on entry and keeps its captions showing,
   so it reads as an intentional two-part image even before you hover. */
.fx-duo.is-in .fx-duo__panel { animation: fx-duo-open 1.1s cubic-bezier(0.22,1,0.36,1) 0.15s 1 both; }
.fx-duo.is-in .fx-duo__tag  { opacity: 1; transform: none; transition-delay: 0.7s; }
@keyframes fx-duo-open {
  0%   { clip-path: inset(0 48% 0 48%); }
  100% { clip-path: inset(0 0 0 0); }
}

/* Compare: a one-time auto-sweep of the divider on entry advertises the drag. */
.fx-compare.is-in.fx-compare--hint { animation: fx-compare-hint 2.4s ease 0.2s 1 both; }
@keyframes fx-compare-hint {
  0%{--pos:50%} 22%{--pos:76%} 60%{--pos:26%} 100%{--pos:50%}
}

@media (prefers-reduced-motion: reduce) {
  .fx-lens__scan, .fx-tint.is-in, .fx-tint.is-in .fx-tint__wash, .fx-tint.is-in .fx-tint__chips,
  .fx-duo.is-in .fx-duo__panel, .fx-compare.is-in.fx-compare--hint { animation: none !important; }
  .fx-duo.is-in .fx-duo__tag { opacity: 1; transform: none; }
}

/* ------------------------------------------ colour-direction explorer ----- */
/* Four real colourways of one photograph, crossfaded. The active one is chosen
   by scroll position through the viewport, and overridden by pointer x while
   hovering. Works on touch (scroll-driven) and mouse (both). */
.fx-swatch { position: relative; }
.fx-swatch__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0;
}
.fx-swatch__img:first-of-type { position: relative; } /* holds the figure's height */
.fx-swatch__img.is-active { opacity: 1; }
/* The crossfade. Specificity must beat the global `.js .figure img { transition: none }`
   (0,2,1) that would otherwise kill it, since the swatch lives inside a .figure. */
.js .fx-swatch .fx-swatch__img { transition: opacity 0.55s var(--ease-soft); }
.fx-swatch__dots {
  position: absolute; left: 18px; bottom: 16px;
  display: flex; gap: 9px;
  padding: 8px 10px; border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 62%, transparent);
  backdrop-filter: blur(6px);
}
.fx-swatch__dots i {
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--c);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.35);
  opacity: 0.5; transform: scale(0.82);
  transition: opacity 0.3s ease, transform 0.3s ease;
}
.fx-swatch__dots i.is-on { opacity: 1; transform: scale(1); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.6), 0 0 0 2px rgba(255,255,255,0.55); }
.fx-swatch__tag {
  position: absolute; right: 18px; bottom: 18px;
  font-family: var(--sans); font-size: 12px; font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--on-dark);
  background: color-mix(in srgb, var(--ink) 62%, transparent);
  backdrop-filter: blur(6px);
  padding: 8px 13px; border-radius: 999px;
}
@media (prefers-reduced-motion: reduce) {
  .js .fx-swatch .fx-swatch__img { transition: none; }
}
