/* ==========================================================================
   NOOFI — the QR landing
   Design carried over from the existing WordPress page — white type, Krona One
   wordmark, Inter body, Azeret Mono for labels — on a black ground since
   2026-08-23. The hero is full-screen with the OOFI brand film behind it; the
   brand cards sit underneath so a card-scan lands on the whole family in two
   scrolls.
   ========================================================================== */

:root {
  /* True black, as of 2026-08-23. The page ran on the WordPress #111 until the
     Red Carpet poster arrived carrying its own near-black ground and made the
     grey panels look like a second, older palette. Everything sits on black
     now and the cards are a hair above it rather than below. */
  --ink:     #000000;   /* the page ground */
  --ink-2:   #0b0b0b;   /* card surface, a hair off the ground */
  --white:   #ffffff;
  --muted:   #a4a4a4;   /* WP "contrast-3" — 8.4:1 on black */
  /* WordPress's "contrast-2" is #636363, which was under the 4.5:1 AA floor for
     small text even on #111, and every use here is small tracked caps (brand
     labels, card categories, the address). #888 is 5.9:1 on black and still
     reads as a quiet secondary tone. */
  --dim:     #888888;
  --line:    rgba(255, 255, 255, 0.14);
  /* 0.30 rather than the 0.26 this was on #111: alpha over a darker ground
     renders darker, and this one draws the buttons. Holding the rendered edge
     where it was keeps them looking as tappable as before. The card hairline
     above is deliberately left to fall back — quiet card edges are the point
     of the black. */
  --line-2:  rgba(255, 255, 255, 0.30);
  --teal:    #128181;   /* from the mark, used sparingly */

  --display: 'Azeret Mono', ui-monospace, SFMono-Regular, monospace;
  --body:    'Inter', system-ui, -apple-system, sans-serif;
  --serif:   'Cardo', Georgia, serif;

  --col: 32rem;
  --pad: clamp(1.25rem, 5vw, 2.5rem);
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

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

html {
  -webkit-text-size-adjust: 100%;   /* iOS Safari must not inflate our type */
  scroll-behavior: smooth;          /* the Explore link glides, not jumps */
  scroll-padding-top: 1rem;         /* anchors land with a little air above them */
  background: var(--ink);           /* no white flash before CSS paints */
  color-scheme: dark;               /* dark form controls */

  /* Hide the scrollbar without disabling scrolling. Wheel, trackpad, touch,
     keyboard and the Explore link all still work; only the bar is gone. */
  scrollbar-width: none;            /* Firefox */
  -ms-overflow-style: none;         /* legacy Edge */
}

html::-webkit-scrollbar,
body::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;                    /* Safari and Chrome */
}

body {
  margin: 0;
  background: var(--ink);
  color: var(--white);
  font-family: var(--body);
  font-size: clamp(0.9375rem, 0.9rem + 0.2vw, 1rem);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  /* Stops the rubber-band bounce revealing browser chrome behind the page. */
  overscroll-behavior-y: none;
  /* Kills the grey flash iOS paints over every tapped link. */
  -webkit-tap-highlight-color: transparent;
}

::selection { background: var(--teal); color: var(--white); }

h1, h2 { margin: 0; font-weight: 400; }
p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
img { max-width: 100%; height: auto; }
a { color: inherit; }

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

/* Off-screen but still announced — used where a logo carries the name. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 30;
  padding: 0.75rem 1.25rem;
  background: var(--white); color: var(--ink);
  font-weight: 600; text-decoration: none;
}
.skip:focus { left: 0; }

.alert {
  position: relative; z-index: 30;
  margin: 0; padding: 0.75rem var(--pad);
  background: var(--white); color: var(--ink);
  font-size: 0.8125rem; font-weight: 500; text-align: center;
}

/* --------------------------------------------------------------------------
   Hero — full screen, brand film behind
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100svh;
  padding: clamp(3.5rem, 9vh, 6rem) var(--pad);
  text-align: center;
  overflow: hidden;
  isolation: isolate;
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--ink);
}

/* The still paints immediately; the player fades in over it once it's ready,
   so the hero is never empty while a video boots on a phone connection. */
.hero__poster {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}

.hero__frame {
  position: absolute;
  top: 50%; left: 50%;
  /* Cover the viewport at 16:9. Background mode is genuinely chromeless, so
     this only needs a hair of overscan to hide sub-pixel seams — not the 35%
     it took to hide player furniture. That is a lot less to composite. */
  width: max(100vw, 177.78svh);
  height: max(56.25vw, 100svh);
  transform: translate(-50%, -50%) scale(1.02);
  border: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.2s var(--ease);
}

.hero__frame.is-ready { opacity: 1; }

/* Keeps the type legible over any frame of the film. */
.hero__scrim {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(120% 80% at 50% 45%, rgba(0,0,0,0.45) 0%, rgba(0,0,0,0.78) 60%, rgba(0,0,0,0.92) 100%),
    linear-gradient(to bottom, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.25) 40%, var(--ink) 100%);
}

.hero__inner { position: relative; max-width: 44rem; }

.hero__title {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2.4rem, 1.2rem + 5vw, 4.75rem);
  line-height: 1.1;
  letter-spacing: 0.02em;
}

/* The wordmark carries the mark and the lettering together, so it stands in
   for both the old icon and the Krona One heading. 190.48 x 47.23 — roughly
   4:1, so height follows from width. */
.hero__wordmark {
  display: block;
  /* The floor is what phones get — 18vw is only ~77px at 430px wide, so the
     min is doing the work there. Raising it enlarges mobile without touching
     desktop, which stays on the 18vw track above ~890px. */
  width: clamp(10rem, 18vw, 13rem);
  height: auto;
  margin: 0 auto;
}

/* On the 404 the wordmark sits above the heading rather than being it, so it
   drops back to a sign-post size. (On the front page it is inside the <h1>.) */
.hero__inner > .hero__wordmark {
  width: clamp(9rem, 22vw, 13rem);
  margin-bottom: 2.25rem;
}

.hero__intro {
  max-width: 46rem;
  margin: 1.75rem auto 0;
  color: rgba(255, 255, 255, 0.82);
  font-size: clamp(0.9375rem, 0.9rem + 0.3vw, 1.0625rem);
  line-height: 1.7;
}

/* Client wall. Logos arrive in mixed colours — mostly gold, Silver Sands is
   black and would vanish on this ground — so everything is flattened to white
   and set back with opacity. Capping BOTH height and width matters: these run
   from 1.03 to 3.47 in aspect, so a height-only rule would make the square
   marks tower over the wide ones. */
.hero__clients {
  position: relative;
  width: 100%;
  /* Wide enough to keep the full roster on one line at desktop; the wall
     wraps on its own below that. */
  max-width: 40rem;
  margin-top: clamp(2rem, 5vh, 3rem);
}

.hero__clients-label {
  margin-bottom: 1.1rem;
  color: var(--dim);
  font-family: var(--display);
  font-size: 0.625rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.clients {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(0.9rem, 2.5vw, 1.4rem) clamp(1rem, 2.6vw, 1.75rem);
}

.clients img {
  /* --s equalises optical area across very different aspect ratios. */
  height: calc(clamp(16px, 2.9vw, 22px) * var(--s, 1));
  width: auto;
  max-width: calc(clamp(62px, 13vw, 96px) * var(--w, 1));
  object-fit: contain;
  filter: brightness(0) invert(1);
  opacity: 0.62;
  transition: opacity 0.25s var(--ease);
}

@media (hover: hover) {
  .clients img:hover { opacity: 1; }
}

.hero__cue {
  position: relative;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  margin-top: clamp(2rem, 5vh, 3.25rem);
  color: var(--muted);
  font-family: var(--display);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  transition: color 0.25s var(--ease);
}

/* Replaces the 10 KB animated GIF the WordPress page used. */
.hero__chev {
  width: 26px; height: 28px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.85;
}

.hero__chev polyline { animation: chev 2.1s var(--ease) infinite; }
.hero__chev polyline:last-child { animation-delay: 0.16s; }

@keyframes chev {
  0%, 55%, 100% { opacity: 0.35; transform: translateY(0); }
  25%           { opacity: 1;    transform: translateY(3px); }
}

.hero__cue:focus-visible { color: var(--white); }
@media (hover: hover) {
  .hero__cue:hover { color: var(--white); }
}

/* --------------------------------------------------------------------------
   Brands
   -------------------------------------------------------------------------- */

.chain {
  position: relative;
  display: flex;
  flex-flow: row wrap;
  justify-content: center;      /* a short last row centres itself */
  align-items: stretch;         /* equal-height cards across a row */
  gap: 1.25rem;
  max-width: 78rem;             /* three cards plus their gaps */
  margin: 0 auto;
  /* Kept tight on top: this section is the Explore target, and a large top
     padding stacked with scroll-padding left ~128px of dead space on arrival. */
  padding: clamp(1.5rem, 3vw, 2.5rem) var(--pad) clamp(3rem, 7vw, 5rem);

  /* Explore can only bring this section to the top of the screen if the page
     is long enough to scroll that far. With two cards and a short footer it
     wasn't: at 1920x1080 the browser ran out of page 268px early and stopped
     with the tail of the hero still showing. Reserving a viewport (less the
     footer) for brands + footer guarantees the scroll can complete. */
  min-height: calc(100svh - 150px);
  align-content: flex-start;   /* rows stay at the top; cards keep their height */
}

/* The label is not a card — it takes its own full-width line. The max-width
   below has to be cleared or flex-basis:100% is capped at it and the label
   sits *in* the first row, shunting a card onto the next one. */
/* The label is not a card — it takes its own full-width line. No max-width
   here: capping it makes flex-basis:100% resolve to that cap, and the label
   then sits *in* the first row and shunts a card onto the next one. */
.chain__lede {
  flex: 0 0 100%;
  margin-bottom: 1.5rem;
  color: var(--dim);
  font-family: var(--display);
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-align: center;
}

.card {
  position: relative;
  /* Basis sets how many fit per row: three above ~72rem, two on a tablet,
     one on a phone. flex-grow lets a short row fill its share evenly. */
  flex: 1 1 20rem;
  max-width: 24rem;
  display: flex;
  flex-direction: column;
  padding: clamp(1.75rem, 4vw, 2.25rem) clamp(1.35rem, 4vw, 2rem);
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: 16px;
  text-align: center;
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease),
              border-color 0.3s var(--ease);
}

/* Pre-reveal state applies only when JS is running to undo it. */
.js .card { opacity: 0; transform: translateY(14px); }
.js .card.is-visible { opacity: 1; transform: none; }

@media (hover: hover) {
  .card:hover { border-color: var(--line-2); }
}

.card__atoll {
  width: 54px; height: 54px;
  margin: 0 auto 1.1rem;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 50%;
}

/* contain, not cover: this slot holds brand marks, and cover would crop
   them to fill the circle. */
.card__atoll img { width: 100%; height: 100%; object-fit: contain; }

/* A real logo carries its own shape — no ring, no fixed circle. */
.card__atoll--logo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: auto;
  height: 3.25rem;
  padding: 0;
  border: 0;
  border-radius: 0;
}

/* --s trims a wide wordmark so it doesn't outweigh a compact mark on the card
   beside it (from logo_scale in content.json). It shrinks the mark inside the
   slot, not the slot, so the eyebrows below still line up across a row. */
.card__atoll--logo img {
  width: auto;
  max-width: 100%;
  height: calc(100% * var(--s, 1));
}

.atoll { width: 100%; height: 100%; fill: none; }

.atoll__ring {
  stroke: var(--teal);
  stroke-width: 2.5;
  transition: stroke-dashoffset 1s var(--ease);
}
.atoll__ring:nth-of-type(2) { transition-delay: 0.12s; }

.atoll__core {
  fill: var(--white);
  transform-origin: center;
  transition: transform 0.45s var(--ease) 0.4s;
}

.js .atoll__ring { stroke-dasharray: 160; stroke-dashoffset: 160; }
.js .atoll__core { transform: scale(0); }
.js .card.is-visible .atoll__ring { stroke-dashoffset: 0; }
.js .card.is-visible .atoll__core { transform: scale(1); }

.card__cat {
  margin-bottom: 0.85rem;
  color: var(--dim);
  font-family: var(--display);
  font-size: 0.625rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
}

.card__name {
  font-family: var(--display);
  font-weight: 400;
  /* Monospace sets wider than the old display face, so this comes down
     a step to keep longer brand names on one line. */
  font-size: clamp(1.0625rem, 0.95rem + 0.7vw, 1.4rem);
  line-height: 1.3;
  letter-spacing: 0.01em;
}

/* The sprite is only a definition store — it draws nothing itself, but it has
   to stay in the layout for <use> to resolve, so it is shrunk rather than
   display:none'd. */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* Secondary to the buttons above: quiet until touched, and sized as circles so
   each one is a 40px target rather than a 17px glyph. */
.card__social {
  display: flex;
  justify-content: center;
  gap: 0.25rem;
  margin-top: 1.1rem;
}

.card__social a {
  display: grid;
  place-items: center;
  width: 2.5rem; height: 2.5rem;
  color: var(--dim);
  border-radius: 50%;
  transition: color 0.2s var(--ease), background 0.2s var(--ease);
}

.card__social svg { width: 1.0625rem; height: 1.0625rem; fill: currentColor; }

.card__social a:focus-visible {
  color: var(--white);
  background: rgba(255, 255, 255, 0.09);
}

@media (hover: hover) {
  .card__social a:hover {
    color: var(--white);
    background: rgba(255, 255, 255, 0.09);
  }
}

/* --------------------------------------------------------------------------
   Teaser poster

   A poster, not a panel. The artwork is 907x1376 with its own typography set
   into it, so the card lends it nothing but the row's width, radius and
   hairline — anything else would be a second design arguing with the first.
   -------------------------------------------------------------------------- */

.card--teaser {
  padding: 0;
  background: none;
  border: 0;
  /* Should the poster ever be shorter than the row it sits in, it hangs from
     the same baseline as its neighbours rather than floating above a hole. */
  justify-content: flex-end;
}

.card__poster {
  display: block;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 16px;
  /* The artwork's own ground is true black, so this is what shows in the moment
     before it decodes — the slot never flashes a lighter panel. */
  background: var(--ink);
}

.card__poster img { display: block; width: 100%; height: auto; object-fit: cover; }

.card__poster-link { display: block; border-radius: 16px; }

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

/* Two cards to a row is the one width where the middle slot works against the
   content: the poster pairs off with OOFI and leaves SESSION stranded on a row
   of its own, cut off from the thing it is announcing. Sent to the end for
   that band only, where it takes a full row and nothing has to stretch around
   it. Above and below this band it keeps its place between the two. */
@media (min-width: 46rem) and (max-width: 68.99rem) {
  .card--teaser { order: 1; }
}

/* Three to a row, and the poster has to answer to a height it doesn't choose:
   the two brand cards set it, and the eyebrows lining up across them is worth
   more than any crop of this picture. So the picture comes out of flow — an
   article with nothing in it contributes no height, leaving the row measured
   by the cards alone — and then fills whatever slot they leave.

   Which way that lands depends on the copy. As the cards stand, four buttons
   apiece, the fit is within 14px of exact. Shorter copy would make the poster
   the taller of the two and something would have to give; taking it out of
   flow is what decides in advance that the give comes out of the picture and
   not out of the row. It is trimmed from the top, where there is nothing but
   the arm, so the headline and the logo are never at risk. */
@media (min-width: 69rem) {
  .card--teaser .card__poster { position: absolute; inset: 0; }
  .card--teaser .card__poster img { height: 100%; object-position: 50% 100%; }
}

/* Closes the card as a bookend to .card__cat: identical face, size, tracking
   and colour, so the top line says what the brand does and the bottom line
   says how it sounds. Deliberately shares every value with the eyebrow —
   change one and change both. */
.card__tagline {
  margin-top: 1.25rem;
  color: var(--dim);
  font-family: var(--display);
  font-size: 0.625rem;
  line-height: 1.6;
  letter-spacing: 0.15em;
  text-transform: uppercase;
}

/* No auto margin here on purpose. Cards in a row stretch to a common height,
   and letting the blurb absorb some of that slack pushes it away from the
   eyebrow by a different amount on each card — two visibly unaligned text
   blocks. All the slack goes above the buttons instead, so the blurbs start on
   the same line across the row. The real fix for a gap is matching the number
   of buttons, not moving the gap around. */
.card__blurb {
  max-width: 24rem;
  margin: 0.85rem auto 0;
  color: var(--muted);
  font-size: 0.875rem;
  line-height: 1.65;
}

.card__links {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  /* auto pushes the buttons down, so they align across a row whose cards
     carry different amounts of copy. */
  margin-top: auto;
  padding-top: 1.5rem;
}

.card__links a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.85rem 1.15rem;
  border: 1px solid var(--line-2);
  border-radius: 10px;
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  text-align: left;
  text-decoration: none;
  transition: background 0.22s var(--ease), color 0.22s var(--ease),
              border-color 0.22s var(--ease);
}

.card__links a::after {
  content: '';
  flex: none;
  width: 7px; height: 7px;
  border-top: 1.5px solid currentColor;
  border-right: 1.5px solid currentColor;
  transform: rotate(45deg);
}

.card__links a:focus-visible {
  background: var(--white);
  border-color: var(--white);
  color: var(--ink);
}

/* Hover is a pointer-only idea. On a touch screen it latches after a tap and
   the button stays inverted until you tap somewhere else. */
@media (hover: hover) {
  .card__links a:hover {
    background: var(--white);
    border-color: var(--white);
    color: var(--ink);
  }
}

/* Touch feedback: a brief press instead of a stuck hover. */
.card__links a:active {
  background: var(--white);
  border-color: var(--white);
  color: var(--ink);
  transform: scale(0.985);
}

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

/* One line: mark and address sit on the same rule, wrapping only when the
   viewport can't hold them. */
.shore {
  display: flex;
  flex-direction: column;   /* wordmark on top, address beneath it */
  align-items: center;
  gap: 1.1rem;
  padding: clamp(2.25rem, 5vw, 3.25rem) var(--pad);
  text-align: center;
}

/* Deliberately small and quiet — a sign-off, not a second hero. The mark is
   white artwork, so the opacity alone lands it on the same grey as the address
   (#888) without needing a separate asset. On black that takes 0.53; it was
   0.5 when the ground was #111 and contributed 17 of its own. */
.shore__mark {
  width: clamp(3.25rem, 6vw, 4rem);
  height: auto;
  opacity: 0.53;
}

/* Caps sign-off, matching the treatment in the hub/editorial layouts. */
.shore__note {
  color: var(--dim);
  font-family: var(--display);
  font-size: 0.6875rem;
  font-style: normal;      /* <address> italicises by default */
  line-height: 1.7;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  min-width: 0;            /* flex items default to min-content; let it shrink */
  max-width: 100%;
}

/* Spaces around the dot in the markup are load-bearing: they give the line
   somewhere to break on narrow screens, and stop a screen reader running the
   address lines together. */
.shore__sep { padding: 0 0.25rem; opacity: 0.5; }

/* --------------------------------------------------------------------------
   Narrow screens
   -------------------------------------------------------------------------- */

@media (max-width: 30rem) {
  .hero__mark { width: 88px; height: 88px; }
  .card { border-radius: 14px; }
  .card__poster, .card__poster-link { border-radius: 14px; }

  /* The address is long; caps + wide tracking won't fit a phone on one line,
     so ease the tracking and let it break across two. */
  .shore__note { letter-spacing: 0.1em; }
  .shore__sep  { padding: 0 0.15rem; }
}

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

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

  /* Nothing should stay hidden waiting for a transition that won't run.
     These need the .js prefix to outrank the pre-reveal rules above. */
  .js .card { opacity: 1; transform: none; }
  .js .atoll__ring { stroke-dashoffset: 0; }
  .js .atoll__core { transform: scale(1); }
}
