/* Hero ---------------------------------------------------------------- */

/* Every hero padding and title size that scales with the screen is written
   twice: the vw line first, as the fallback for a browser without container
   units, then its cqi twin. On the storefront the two are the same length —
   no layer here declares a container, so cqi resolves against the viewport.
   In the panel's crop screen the shop is drawn at a real screen's width
   inside .shop-view__inner, an inline-size container, and scaled down by a
   transform; there vw would measure the PANEL's window (a 390px phone frame
   would get a 1600px window's padding and title, and the text would push the
   4:3 box taller and re-crop the photo), while cqi measures the frame. */
.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: grid;
  align-items: center;
  min-height: 24rem;
  padding: clamp(2rem, 6vw, 4rem) 0;
  padding: clamp(2rem, 6cqi, 4rem) 0;
  border-radius: var(--radius-lg);
  background: var(--gradient-hero);
  color: var(--ink);
}

.hero--bleed {
  border-radius: 0;
}

/* The configurable min-height scale; the base with no size class is 24rem. */
.hero--size-s {
  min-height: 12rem;
}

.hero--size-m {
  min-height: 18rem;
}

.hero--size-l {
  min-height: 24rem;
}

.hero--size-xl {
  min-height: 32rem;
}

/* The two small steps also shrink the vertical padding: at the base padding
   the banner could not get under 8rem whatever its minimum said. XXS has no
   minimum at all — a strip exactly as tall as its text. */
.hero--size-xs {
  min-height: 8rem;
  padding: clamp(1.25rem, 3vw, 2rem) 0;
  padding: clamp(1.25rem, 3cqi, 2rem) 0;
}

.hero--size-xxs {
  min-height: 0;
  padding: clamp(1rem, 2vw, 1.5rem) 0;
  padding: clamp(1rem, 2cqi, 1.5rem) 0;
}

/* A page heading over a listing, not a banner: content-driven height. */
.hero--compact {
  min-height: 0;
  padding: clamp(1.5rem, 3vw, 2.5rem) 0;
  padding: clamp(1.5rem, 3cqi, 2.5rem) 0;
}

.hero--photo {
  background-size: cover;
  background-position: center;
}

/* A photo hero always renders light text, whatever the theme. */
.hero--photo {
  --ink: var(--hero-ink);
  --ink-soft: var(--hero-ink-soft);
}

/* A scrim over the photo, and a soft shadow under the text. The scrim
   (--gradient-overlay: 25% dark at the top, 62% at the bottom, the same in
   both themes, because the text on a photo is always light) is what keeps
   the hero at WCAG AA on any picture the shop uploads: at 62% the light
   --hero-ink keeps 4.5:1 even over a pure white photo, down where the lead
   sits (about 5.25:1). The maintainer picked it by eye from four variants on
   2026-09-29. The 15% to 45% before it left 2.72:1 on a white photo; the
   25% to 75% before that turned a pale backdrop's lower half black, and the
   shop wants to see its picture. The shadow under the eyebrow, the title and
   the lead is the one the scene heroes give their title; it softens the
   edges of the letters on a busy picture, the scrim carries the contrast. */
.hero--photo::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: var(--gradient-overlay);
}

.hero--photo .hero__eyebrow,
.hero--photo .hero__title,
.hero--photo .hero__lead {
  text-shadow:
    0 2px 24px color-mix(in srgb, var(--scrim-ink) 55%, transparent),
    0 1px 3px color-mix(in srgb, var(--scrim-ink) 40%, transparent);
}

/* A hero with a Shot: the photo is an <img> (.hero__shot below), not a
   background, and the box takes its height from the crop's own shape, not
   from a minimum. Measured against imagor: a rectangle of the box's shape
   comes out byte-identical under any alignment, so `cover` on that img has
   nothing left to cut — unlike a background on a min-height box, which the
   browser re-crops differently at every window width.

   The selector is doubled (".hero.hero--shot", not just ".hero--shot") on
   purpose: .hero--size-*, .hero--compact and .collection-hero each set their
   own min-height at one-class specificity, and .collection-hero sits later
   in this file than .hero--size-*, so a single-class .hero--shot could still
   lose to whichever of the three comes last for that hero — leaving the box
   taller than the crop and the photo cut a second time regardless. Two
   classes outrank all three regardless of file order.

   Text is never clipped. A 6:1 box at 1024px is about 170px tall, less than
   the eyebrow, title, lead and button need with the hero's padding, and
   .hero's own `overflow: hidden` would cut them off. `min-height: auto` on a
   box with an aspect-ratio means "at least as tall as its content", but only
   on a box that is not a scroll container, which `hidden` makes it and
   `clip` does not. So the box keeps the crop's shape while the text fits,
   and grows to fit the text when it does not — only then does `cover`
   re-crop the photo. That is the honest trade: the words beat pixel-exact
   framing, and only when they would not otherwise fit. `clip` still clips
   to the rounded corners, as `hidden` did. */
.hero.hero--shot {
  min-height: auto;
  overflow: clip;
}

/* A browser without `overflow: clip` (Safari before 16) drops it, and
   .hero's `hidden` would win again and cut the text off. It gets `visible`
   instead: nothing hangs outside the box but the text it grows around. What
   the clip used to trim, each layer now does itself: the picture rounds its
   own corners (.hero__shot below), the scrim (.hero--photo::before)
   inherits the radius, and the holo title's one-off sheen (.hero--holo's
   ::after, inset far outside the box to sweep across it) is left out rather
   than painted across the page. */
@supports not (overflow: clip) {
  .hero.hero--shot {
    overflow: visible;
  }

  .hero.hero--shot::before {
    border-radius: inherit;
  }

  .hero.hero--shot::after {
    display: none;
  }
}

/* The smaller the banner, the wider its shape, so a smaller size is always
   a shorter box: at a 1400px page 6:1 (XS/S) is about 233px, 4:1 (M) 350px
   and 8:3 (L/XL) 525px. */
.hero--shot-6x1 { aspect-ratio: 6 / 1; }
.hero--shot-4x1 { aspect-ratio: 4 / 1; }
.hero--shot-8x3 { aspect-ratio: 8 / 3; }
/* The phone's own shape. No banner is ever sized this on the storefront —
   a screen under 640px flattens every one of the three above to 4:3 already
   (below) — but the panel's crop screen needs a box that is 4:3 at ANY
   window width, for the phone frame it scales down by a fixed transform
   rather than a media query. */
.hero--shot-4x3 { aspect-ratio: 4 / 3; }

/* Those heights hold for the text each shape is meant to carry: an eyebrow
   and a one-line title on 6:1 at 1024px or wider, an eyebrow, a title and a
   one-line lead on 4:1. .hero's own padding is 4rem top and bottom from
   1067px up — 128px of a 6:1 box 213px tall at 1280px — so the two thin
   shapes set their own, top and bottom only (the sides stay .hero's), on two
   classes to outrank .hero--size-xs whatever the file order. 8:3 keeps
   .hero's. More text than that makes the box grow, as above.

   On 6:1 the title and the lead also step down to the text scale's S step —
   the values .hero--text-s uses — for the default text size: M, or none
   stated. The attribute or the third class makes these outweigh
   .hero.hero--text-m. A text size the admin picked on purpose (XS, S, L,
   XL) is left as picked; the box grows for it.

   All of it only above 640px, the same breakpoint as the 4:3 override
   below: on a phone every banner is the same 4:3 box, so a 6:1 banner looks
   there exactly as a 4:1 or 8:3 one does — .hero's padding, the size and
   text classes' type. */
@media (min-width: 640px) {
  .hero.hero--shot-6x1 {
    padding-block: clamp(1rem, 2.5vw, 1.5rem);
    padding-block: clamp(1rem, 2.5cqi, 1.5rem);
  }

  .hero.hero--shot-4x1 {
    padding-block: clamp(1.25rem, 3vw, 2rem);
    padding-block: clamp(1.25rem, 3cqi, 2rem);
  }

  .hero.hero--shot-6x1:not([class*="hero--text-"]) .hero__title,
  .hero.hero--shot-6x1.hero--text-m .hero__title {
    font-size: var(--display-s);
  }

  .hero.hero--shot-6x1:not([class*="hero--text-"]) .hero__lead,
  .hero.hero--shot-6x1.hero--text-m .hero__lead {
    font-size: var(--text-base);
  }
}

/* On a narrow screen every banner takes the same shape: a 6:1 strip on a
   phone is a line no title fits into. */
@media (max-width: 639px) {
  .hero--shot-6x1,
  .hero--shot-4x1,
  .hero--shot-8x3 {
    aspect-ratio: 4 / 3;
  }
}

/* border-radius: inherit on both, so the photo keeps the hero's rounded
   corners on its own where `overflow: clip` is not there to clip them. */
.hero__shot {
  position: absolute;
  inset: 0;
  z-index: -1;
  display: block;
  border-radius: inherit;
}

.hero__shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

/* Coloured grounds: four fixed duotone covers, the palette the photo
   placeholders paint with (--hero-a and --hero-b are the two accents,
   --hero-base the dark ground they are mixed into). Fixed, not themed, for
   the same reason the placeholder's cover is: a colour the shop picked should
   not turn pastel in the light theme. Light ink, as on a photo, and a light
   eyebrow — the pink one would sink into the pink cover. A photo still wins:
   its inline background image sits over whatever ground the colour paints. */
.hero--color-pink   { --hero-a: var(--duotone-pink-a);   --hero-b: var(--duotone-pink-b);   --hero-base: var(--duotone-pink-base); }
.hero--color-azure  { --hero-a: var(--duotone-azure-a);  --hero-b: var(--duotone-azure-b);  --hero-base: var(--duotone-azure-base); }
.hero--color-violet { --hero-a: var(--duotone-violet-a); --hero-b: var(--duotone-violet-b); --hero-base: var(--duotone-violet-base); }
.hero--color-mint   { --hero-a: var(--duotone-mint-a);   --hero-b: var(--duotone-mint-b);   --hero-base: var(--duotone-mint-base); }

.hero--color-pink,
.hero--color-azure,
.hero--color-violet,
.hero--color-mint {
  --ink: var(--hero-ink);
  --ink-soft: var(--hero-ink-soft);
  /* A coloured ground is dark in both themes, so the holo title keeps the
     pastel foil the light theme would otherwise deepen. */
  --holo-foil: var(--holo-foil-pastel);
  --holo-glow: var(--holo-glow-pastel);
  background:
    radial-gradient(80% 55% at 50% 115%, color-mix(in srgb, var(--hero-a) 55%, transparent) 0%, transparent 55%),
    radial-gradient(70% 50% at 18% 0%, color-mix(in srgb, var(--hero-b) 45%, transparent) 0%, transparent 50%),
    radial-gradient(120% 80% at 88% 8%, color-mix(in srgb, var(--white) 12%, transparent), transparent 55%),
    linear-gradient(150deg, color-mix(in srgb, var(--hero-a) 40%, var(--hero-base)) 0%, color-mix(in srgb, var(--hero-b) 36%, var(--hero-base)) 100%);
}

.hero--color-pink .hero__eyebrow,
.hero--color-azure .hero__eyebrow,
.hero--color-violet .hero__eyebrow,
.hero--color-mint .hero__eyebrow {
  color: var(--hero-ink-soft);
}

.hero__content {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: 40rem;
}

.hero--center .hero__content {
  margin-inline: auto;
  text-align: center;
  align-items: center;
}

.hero--right .hero__content {
  margin-inline-start: auto;
  text-align: right;
  align-items: flex-end;
}

.hero__eyebrow {
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: calc(var(--label-tracking) * 1.5);
  text-transform: var(--label-transform);
  color: var(--primary);
}

/* Over a photo the eyebrow takes the title's light ink: pink small text
   misses WCAG AA contrast on a light picture (decided 2026-09-29). */
.hero--photo .hero__eyebrow {
  color: var(--hero-ink);
}

.hero__title {
  font-size: var(--display-m);
  line-height: var(--leading-hero);
}

/* The stage's outsized title belongs to the banner that fills the top of the
   home page, which asks for no size. A hero section always carries one, and at
   the small end that title was taller than the banner holding it, so those keep
   the responsive clamp every other hero uses. */
.hero--stage:not([class*="hero--size-"]) .hero__title {
  font-size: var(--display-xl);
}

.hero__lead {
  font-size: var(--text-lg);
  color: var(--ink-soft);
  max-width: 30rem;
}

.hero__cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

/* The text scale: the title and the lead grow together, and the two large
   steps widen the text block so the bigger title still gets a few words per
   line. M is exactly the base above, so a hero that names no step looks the
   same as one that names M. Qualified with .hero so each step outweighs the
   stage's outsized title, which is :not()-guarded and therefore as heavy as
   a plain descendant selector — these come later and so win. */
.hero.hero--text-xs .hero__title {
  font-size: var(--display-xs);
}

.hero.hero--text-xs .hero__lead {
  font-size: var(--text-base);
}

.hero.hero--text-s .hero__title {
  font-size: var(--display-s);
}

.hero.hero--text-s .hero__lead {
  font-size: var(--text-base);
}

.hero.hero--text-m .hero__title {
  font-size: var(--display-m);
}

.hero.hero--text-m .hero__lead {
  font-size: var(--text-lg);
}

.hero.hero--text-l .hero__title {
  font-size: var(--display-l);
}

.hero.hero--text-l .hero__lead {
  font-size: var(--text-xl);
}

.hero.hero--text-l .hero__content {
  max-width: 48rem;
}

.hero.hero--text-xl .hero__title {
  font-size: var(--display-xl);
}

.hero.hero--text-xl .hero__lead {
  font-size: var(--text-xl);
}

.hero.hero--text-xl .hero__content {
  max-width: 56rem;
}

/* Spotlights (hero variant 4) -------------------------------------------
   Two followspots from the top corners, crossing over the content and
   lighting a haze on the floor; each sweeps a few degrees either way. The
   cones take the ground's colours, so a violet ground gets violet light.
   Mounted like the atmosphere: first child of the hero, aria-hidden. */
.spotlights {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

.spotlights__cone {
  position: absolute;
  top: -12%;
  width: 36%;
  height: 160%;
  transform-origin: 50% 0;
  transform: rotate(var(--spot-angle));
  filter: blur(10px);
  opacity: 0.85;
  mix-blend-mode: screen;
  --spot-colour: var(--hero-a, var(--primary));
  background: conic-gradient(from 165deg at 50% 0%,
    transparent 0deg,
    color-mix(in srgb, var(--spot-colour) 55%, transparent) 7deg,
    color-mix(in srgb, var(--spot-colour) 78%, var(--white) 0%) 15deg,
    color-mix(in srgb, var(--spot-colour) 55%, transparent) 23deg,
    transparent 30deg);
  mask-image: linear-gradient(180deg, #000 0%, #000 55%, transparent 100%);
  /* About five seconds of sweep, then the cones hold where they are — the
     same rule, and the same arithmetic, as the atmosphere in base.css. */
  animation: spot-sweep 9s ease-in-out 0.56 forwards;
}

.spotlights__cone--left {
  left: 2%;
  --spot-angle: -24deg;
}

.spotlights__cone--right {
  right: 2%;
  --spot-angle: 24deg;
  --spot-colour: var(--hero-b, var(--secondary));
  animation-delay: -4.5s;
  animation-iteration-count: 1.06;
}

.spotlights__haze {
  position: absolute;
  bottom: -12%;
  left: 50%;
  width: 90%;
  height: 50%;
  transform: translateX(-50%);
  filter: blur(28px);
  background: radial-gradient(55% 65% at 50% 100%,
    color-mix(in srgb, var(--hero-a, var(--primary)) 38%, transparent) 0%,
    color-mix(in srgb, var(--hero-b, var(--secondary)) 18%, transparent) 45%,
    transparent 75%);
  animation: floor-breathe 7s ease-in-out 0.71 forwards;
}

@keyframes spot-sweep {
  0%, 100% { transform: rotate(calc(var(--spot-angle) - 6deg)); }
  50% { transform: rotate(calc(var(--spot-angle) + 6deg)); }
}

/* Lightstick ocean (hero variant 7) ---------------------------------------
   The sea of glowing sticks a fandom raises: three layers of dots, far to
   near, each bobbing at its own pace so they drift against each other. Dots
   are a repeating radial gradient rather than elements, in the ground's two
   colours; the top of each layer fades so the sea has a horizon. */
.ocean {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

.ocean__layer {
  position: absolute;
  left: -10%;
  right: -10%;
  bottom: -8%;
  --ocean-a: var(--hero-a, var(--primary-tint));
  --ocean-b: var(--hero-b, var(--secondary-tint));
  background-image:
    radial-gradient(circle, var(--ocean-a) 0 var(--dot), color-mix(in srgb, var(--ocean-a) 45%, transparent) calc(var(--dot) + 1px), transparent calc(var(--dot) * 3)),
    radial-gradient(circle, var(--ocean-b) 0 calc(var(--dot) * 0.8), color-mix(in srgb, var(--ocean-b) 45%, transparent) calc(var(--dot) * 0.8 + 1px), transparent calc(var(--dot) * 2.6));
  mask-image: linear-gradient(180deg, transparent 0%, #000 55%, #000 100%);
  mix-blend-mode: screen;
  /* Five seconds of bobbing, then the sea holds (see the spotlights). */
  animation: ocean-bob 6s ease-in-out 0.83 forwards;
}

.ocean__layer--far {
  height: 58%;
  --dot: 1.5px;
  background-size: 26px 20px, 35px 27px;
  background-position: 0 0, 13px 10px;
  filter: blur(1px);
  opacity: 0.5;
  animation-duration: 8s;
  animation-iteration-count: 0.63;
}

.ocean__layer--mid {
  height: 46%;
  --dot: 2.2px;
  background-size: 44px 34px, 59px 45px;
  background-position: 7px 3px, 29px 20px;
  filter: blur(1.2px);
  opacity: 0.7;
  animation-duration: 6.5s;
  animation-delay: -2.4s;
  animation-iteration-count: 1.14;
}

.ocean__layer--near {
  height: 34%;
  --dot: 3.2px;
  background-size: 72px 56px, 95px 74px;
  background-position: 18px 9px, 47px 37px;
  filter: blur(2.2px);
  opacity: 0.9;
  animation-duration: 5.2s;
  animation-delay: -1.1s;
  animation-iteration-count: 1.17;
}

@keyframes ocean-bob {
  0%, 100% { transform: translate(0, 0); }
  33% { transform: translate(7px, -5px); }
  66% { transform: translate(-5px, 3px); }
}

/* The ink title over either scene gets a soft shadow so the light behind
   it never eats its edges. */
.hero--spotlights .hero__title,
.hero--ocean .hero__title {
  text-shadow: 0 2px 24px color-mix(in srgb, var(--scrim-ink) 45%, transparent);
}

/* In the light theme the scenes darken the pale ground instead of lighting
   it, as the atmosphere does — except on a coloured ground, which is dark in
   both themes and keeps the lights. */
[data-theme="light"] .spotlights__cone,
[data-theme="light"] .ocean__layer {
  mix-blend-mode: multiply;
  opacity: 0.3;
}

.hero--color-pink .spotlights__cone,
.hero--color-azure .spotlights__cone,
.hero--color-violet .spotlights__cone,
.hero--color-mint .spotlights__cone {
  mix-blend-mode: screen;
  opacity: 0.85;
}

.hero--color-pink .ocean__layer,
.hero--color-azure .ocean__layer,
.hero--color-violet .ocean__layer,
.hero--color-mint .ocean__layer {
  mix-blend-mode: screen;
  opacity: 0.8;
}

/* Neon title (hero variant 5) ---------------------------------------------
   The title as a neon tube: the page's ink for the glass, the ground's first
   colour for the gas around it, a short tube underneath. One flicker when it
   comes on, then it holds — the only motion here answers the page load. */
.hero__title--neon {
  font-family: var(--font-brand);
  font-weight: 700;
  letter-spacing: var(--tracking-brand);
  color: var(--ink);
  --neon: var(--hero-a, var(--primary));
  text-shadow:
    0 0 4px color-mix(in srgb, var(--ink) 60%, transparent),
    0 0 12px var(--neon),
    0 0 28px color-mix(in srgb, var(--neon) 70%, transparent),
    0 0 56px color-mix(in srgb, var(--neon) 40%, transparent);
  animation: neon-flicker 1.8s linear 1 both;
}

.hero__title--neon::after {
  content: "";
  display: block;
  width: 4rem;
  height: 3px;
  margin-top: var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--neon);
  box-shadow: 0 0 10px var(--neon), 0 0 24px color-mix(in srgb, var(--neon) 60%, transparent);
}

.hero--center .hero__title--neon::after {
  margin-inline: auto;
}

.hero--right .hero__title--neon::after {
  margin-inline-start: auto;
}

/* Two dips, not three: three inside 0.65 s is the WCAG 2.3.1 limit of three
   flashes a second, and a flicker is no place to sit on a limit. */
@keyframes neon-flicker {
  0%, 17%, 21%, 52%, 56%, 100% { opacity: 1; }
  19%, 54% { opacity: 0.3; }
}

/* Holo title (hero variant 6) ---------------------------------------------
   Photocard foil: the title clipped to an iridescent gradient that drifts
   slowly, and one sheen sweeping the whole banner as it loads — the tilt
   that shows a holo card is holo. The light theme deepens the foil so it
   still reads on a pale ground; a coloured ground, dark in both themes,
   keeps the pastel foil. */
.hero__title--holo {
  font-family: var(--font-brand);
  font-weight: 700;
  letter-spacing: var(--tracking-brand);
  background-image: var(--holo-foil);
  background-size: 300% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: var(--holo-glow);
  /* Drifts for five seconds, then the foil holds (see the spotlights). */
  animation: holo-drift 9s ease-in-out 0.56 forwards;
}

.hero--holo::after {
  content: "";
  position: absolute;
  inset: -20% -40%;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(115deg, transparent 40%, color-mix(in srgb, var(--white) 12%, transparent) 47%, color-mix(in srgb, var(--white) 26%, transparent) 50%, color-mix(in srgb, var(--white) 12%, transparent) 53%, transparent 60%);
  transform: translateX(-70%);
  animation: holo-sheen 2.4s var(--ease-out) 0.4s 1 both;
}

@keyframes holo-drift {
  from { background-position: 0% 50%; }
  to { background-position: 100% 50%; }
}

@keyframes holo-sheen {
  from { transform: translateX(-70%); }
  to { transform: translateX(70%); }
}

/* Collection hero: group or era header. Shorter than the home page's banner,
   which is the whole top of its page; this one has a listing under it. */
.collection-hero {
  min-height: 16rem;
}

/* Qualified with .hero so it outweighs the stage's outsized title, which is
   :not()-guarded and therefore heavier than a plain descendant selector. The
   collection banner sits above a listing and states its own size. */
.hero.collection-hero .hero__title {
  font-size: var(--display-s);
}
