/* ==========================================================================
   JEEVMEDIA — reset, typography, layout primitives, utilities
   Nothing section-specific lives here. Section padding is owned by `.section`
   and by nothing else, so no child selector can cancel it out.
   ========================================================================== */

/* ---- Reset -------------------------------------------------------------- */

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

* {
  margin: 0;
  padding: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-padding-top: calc(var(--nav-h) + var(--sp-5));
}

/* NOTE: `scroll-behavior: smooth` is deliberately NOT set on `html` here.
   Lenis writes the scroll position itself, every frame, using a bare
   `scrollTo(x, y)` — which honours CSS `scroll-behavior`. With `smooth` set,
   each of those per-frame writes was being re-animated by the browser over
   ~600ms, so Lenis and the CSS fought continuously: measured as ~400ms of dead
   time before the page moved at all, plus dropped wheel input.

   It is opted into from the other direction instead — initScroll in main.js
   adds this class only when Lenis is NOT driving the scroll, so the two can
   never be active at once regardless of load order or CDN timing. With no JS
   at all the class is never added and anchor links simply jump, which is fine. */
html.css-smooth-scroll {
  scroll-behavior: smooth;
}

body {
  background: var(--bone);
  color: var(--paper);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

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

img {
  height: auto;
}

/* Several components set `display`, which would otherwise beat the `hidden`
   attribute used by the nav drawer, form panels and roster filter. */
[hidden] {
  display: none !important;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
}

button {
  cursor: pointer;
}

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

ul,
ol {
  list-style: none;
}

table {
  border-collapse: collapse;
}

/* Bodoni's numerals default to old-style in some cuts; the terminal half of
   the brand needs them lining and tabular everywhere numbers appear. */
.mono {
  font-variant-numeric: tabular-nums lining-nums;
}

/* ---- Focus -------------------------------------------------------------- */

:focus {
  outline: none;
}

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

.skip-link {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 999;
  padding: var(--sp-3) var(--sp-5);
  background: var(--accent);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  transform: translateY(-120%);
  transition: transform var(--dur-fast) var(--ease-soft);
}

.skip-link:focus-visible {
  transform: translateY(0);
  outline-offset: -4px;
}

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

/* ---- Layout primitives -------------------------------------------------- */

.section {
  position: relative;
  padding-block: var(--section-y);
}

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

.stack > * + * {
  margin-top: var(--sp-4);
}

.measure {
  max-width: var(--max-text);
}

.rule {
  height: 1px;
  background: var(--rule);
  border: 0;
}

.rule--firm {
  background: var(--rule-firm);
}

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

.display {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: var(--lh-tight);
  letter-spacing: var(--track-display);
}

.masthead {
  font-family: var(--font-display);
  font-size: var(--fs-masthead);
  font-weight: 400;
  line-height: var(--lh-tight);
  letter-spacing: var(--track-display);
}

.masthead em,
.display em {
  font-style: italic;
  font-weight: 400;
}

.title {
  font-family: var(--font-display);
  font-size: var(--fs-title);
  font-weight: 400;
  line-height: var(--lh-snug);
  letter-spacing: var(--track-display);
}

/* The eyebrow is the page's most repeated device: a mono label preceded by a
   short rule. It marks every section without any extra chrome. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 400;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--paper);
}

/* The rule takes the accent; the LABEL DOES NOT. That split is the whole
   colour system in one rule — `--fs-label` is 11.5px, which is exactly the
   size the accent cannot legibly be (see tokens.css). A 1px mark carries no
   contrast requirement, so it can. */
.eyebrow::before {
  content: "";
  width: 28px;
  height: 1px;
  background: var(--accent);
  flex: none;
}

.eyebrow--plain::before {
  display: none;
}

.label {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--slate);
}

.micro {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--slate-dim);
}

.lead {
  font-size: var(--fs-lead);
  color: var(--slate);
  line-height: var(--lh-body);
}

.lead strong {
  color: var(--paper);
  font-weight: 500;
}

/* Anything sitting over photography inverts. */
.on-media,
.on-media .eyebrow,
.on-media .display,
.on-media .masthead {
  color: var(--paper);
}

.on-media .lead,
.on-media .label,
.on-media .micro {
  color: rgba(255, 255, 255, 0.78);
}

/* ---- Buttons — everything is a pill -------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  padding: 0.95rem 1.65rem;
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  transition: background var(--dur-fast) var(--ease-soft),
    color var(--dur-fast) var(--ease-soft),
    border-color var(--dur-fast) var(--ease-soft),
    transform var(--dur-fast) var(--ease-soft);
}

.btn__arrow {
  transition: transform var(--dur-mid) var(--ease-out);
}

.btn:hover .btn__arrow {
  transform: translateX(4px);
}

/* Solid was a black pill on the bone page; the page's ground is what carries
   black now, so the pill takes the accent instead — the one fill on the page
   that is neither the ground nor paper. */
.btn--solid {
  background: var(--accent);
  color: var(--paper);
  border-color: var(--accent);
  font-weight: 500;
}

/* The roster's only button, and the one place `--solid` is used — the nav's
   CTA carries `.btn` alone, so this cannot reach above the hero. */
.btn--solid:hover {
  background: var(--accent-lift);
  border-color: var(--accent-lift);
}

.btn--ghost {
  border-color: var(--rule-firm);
  color: var(--paper);
}

/* Fills solid paper rather than solid black — a dark pill would sit inside a
   dark page and read as nothing happening. Text goes dark with it, the same
   inversion `--solid` above went through. */
.btn--ghost:hover {
  border-color: var(--paper);
  background: var(--paper);
  color: var(--black);
}

/* Over photography: a solid white pill and a hairline white outline. */
.btn--light {
  background: var(--paper);
  color: var(--black);
  border-color: var(--paper);
  font-weight: 500;
}

/* The one place the accent becomes a GROUND rather than a mark, and the only
   place it carries text. White on the accent is 5.98:1, which passes for
   normal text where the accent on ink does not — so the colour can be the
   whole button here and nothing anywhere else.

   It replaced an 86%-white fade, which on a black card was a hover state you
   had to look for. This is the page's last call to action; it should be the
   most definite thing on it. */
.btn--light:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--paper);
}

.btn--outline-light {
  border-color: rgba(255, 255, 255, 0.55);
  color: var(--paper);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  background: rgba(255, 255, 255, 0.08);
}

.btn--outline-light:hover {
  border-color: var(--paper);
  background: rgba(255, 255, 255, 0.18);
}

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

/* A text link that wipes an underline in from the left on hover. */
.link-wipe {
  position: relative;
  display: inline-block;
}

.link-wipe::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -3px;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-mid) var(--ease-out);
}

/* The wipe takes the accent everywhere BELOW the hero, and nowhere above it.
   `main` and `.footer` is the whole scope: the nav sits in a `<header>`
   outside both, and it is fixed — colouring its underlines would put the
   accent over the hero, which is the one part of the page that was asked to
   stay as it is. The link text itself is untouched; only the 1px mark moves,
   for the same reason the eyebrow's does. */
main .link-wipe::after,
.footer .link-wipe::after {
  background: var(--accent);
  /* 2px rather than 1. At 1px the accent reads as a slightly-off grey against
     bone; the mark has to be thick enough for the eye to resolve the hue. */
  height: 2px;
}

.link-wipe:hover::after,
.link-wipe:focus-visible::after,
.link-wipe[aria-current="true"]::after {
  transform: scaleX(1);
}

/* ==========================================================================
   PANELS
   Rounded surfaces floating on the bone ground. The hero opts out of the
   radius and runs edge to edge; everything else keeps it.
   ========================================================================== */

.panel {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-shell);
  background: var(--bone-deep);
  isolation: isolate;
}

.panel--card {
  border-radius: var(--r-card);
}

/* ---- Media --------------------------------------------------------------
   Photography runs FULL COLOUR everywhere. There is no duotone and no
   greyscale-at-rest: the images are the only colour on the page and draining
   them would defeat the direction. */

.media {
  position: relative;
  overflow: hidden;
  background: var(--bone-deep);
  border-radius: var(--r-card);
  isolation: isolate;
}

.media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.01);
  transition: transform var(--dur-slow) var(--ease-out);
}

.media-host:hover .media img {
  transform: scale(1.05);
}

/* Scroll-linked parallax (initScrubs in main.js) lives on this wrapper, never
   on the <img> itself — the image already owns its own transform for the
   hover-zoom above, and GSAP takes over the whole `transform` property on
   whatever element it animates. Two elements, two independent transforms,
   no fight. Oversized with slack on both edges so the translate range never
   uncovers the container. */
.media__parallax {
  position: absolute;
  top: -10%;
  left: 0;
  width: 100%;
  height: 120%;
}

/* Scrim for text sitting over an image. `--scrim-*` is tuned in tokens.css so
   the strength can be adjusted in one place once real photography lands. */
.media--scrim::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(
    to top,
    var(--scrim-strong) 0%,
    var(--scrim) 26%,
    transparent 58%
  );
  pointer-events: none;
}

/* ---- Frosted glass ------------------------------------------------------
   The overlay language that unifies the roster wall now that the duotone is
   gone. Falls back to a solid dark ground where backdrop-filter is missing,
   so legibility never depends on support. */

.frost {
  background: var(--frost-solid);
  border: 1px solid var(--frost-edge);
  color: var(--paper);
}

@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .frost {
    background: var(--frost);
    backdrop-filter: blur(14px) saturate(1.4);
    -webkit-backdrop-filter: blur(14px) saturate(1.4);
  }
}

.frost--pill {
  border-radius: var(--r-pill);
  padding: 0.4rem 0.85rem;
}

/* ---- Motion gate --------------------------------------------------------
   Reveal styles apply only under `.js-anim`, which main.js adds after GSAP
   has actually resolved. If the CDN is blocked or JS fails, every element
   keeps its natural visible state and the page just loses its polish. */

.js-anim .reveal {
  opacity: 0;
  transform: translateY(24px);
}

/* The mask itself is unconditional — the hero headline must break into its
   lines whether or not the animation runs. The padding/negative-margin pair
   gives italic descenders room inside the clip without changing the leading. */
.mask-line {
  display: block;
  overflow: hidden;
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}

.mask-line > span {
  display: block;
}

.js-anim .mask-line > span {
  transform: translateY(105%);
}

/* Case stills and door images are uncovered rather than faded in — a
   clip-path wipe plus a scale settle. Both live on the media wrapper itself
   (the element carrying `.media`), never on the <img>, so they can never
   collide with `.media-host:hover .media img`'s separate, permanent
   hover-zoom on the image. */
.js-anim .reveal-media {
  clip-path: inset(0 0 0 100%);
  transform: scale(1.06);
}

/* ---- Reduced motion -----------------------------------------------------
   Not a nicety: everything above must resolve to its final state, or a user
   with the preference set sees an empty page. */

@media (prefers-reduced-motion: reduce) {
  /* The class is listed too so it can never out-specify this. main.js already
     declines to add it under reduced motion; this makes that belt-and-braces. */
  html,
  html.css-smooth-scroll {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  /* Scoped to `.reveal` and `.mask-line`, and it has to stay scoped. A blanket
     `transform: none` would flatten `.pincard`'s tilt, which is not motion —
     it is the resting shape of a card pinned to a board, and it is still there
     when the visitor has asked for no animation. The rule of thumb: reset the
     transforms that exist only as an animation's start state, never the ones
     that are part of the design at rest. */
  .js-anim .reveal,
  .js-anim .mask-line > span {
    opacity: 1 !important;
    transform: none !important;
  }

  .js-anim .reveal-media {
    clip-path: none !important;
    transform: none !important;
  }
}

/* ---- Utilities ---------------------------------------------------------- */

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