/* The opening curtain — see includes/splash.php.
 *
 * Deliberately the inverse of the portal it opens: the page behind is white and
 * rose, so the card is deep plum and gold. A light overlay on a light page is a
 * loading state; a dark one is a title card, and the reveal into daylight is
 * most of the effect.
 *
 * Everything here animates transform, opacity or a masked gradient. Nothing
 * animates a filter, a box-shadow or a layout property — this runs on the
 * phones distributors actually build their IDs on, and the first four seconds
 * of a visit is the worst possible moment to drop frames.
 */

/* Hidden until the boot script says otherwise, so a browser with no JavaScript
   never meets a curtain it cannot draw back. */
.sj-splash { display: none; }

.sj-splash-on .sj-splash {
  display: flex;
  position: fixed;
  inset: 0;
  /* Above the drawer, the backdrop, the lightbox and the toasts — this covers
     the application, not a part of it. */
  z-index: 200;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: #1b1030;
  /* Not inherited: app.css sizes body text for reading, and this is signage. */
  color: #fff;
  isolation: isolate;
}

/* The page underneath must not scroll while it is covered — a curtain you can
   scroll behind is a curtain with a page moving under it.
 *
 * A second class rather than the one above, because the two have to end at
 * different moments: scrolling is returned the instant the curtain starts to
 * lift, while .sj-splash-on has to stay until the lift has finished playing —
 * dropping it early would set the overlay to display:none mid-animation and
 * make the card vanish instead of withdraw. */
.sj-splash-lock,
.sj-splash-lock body { overflow: hidden; }

/* The card takes focus on arrival so a screen reader is inside it rather than
   reading the directory behind it. That is a placement, not an interaction, and
   ringing the entire screen to announce it would be absurd. */
.sj-splash:focus { outline: none; }

/* app.css rings focus in brand pink, which is the right colour on a white page
   and nearly invisible on plum — and next to this button's own pink-lit border
   it reads as a second border rather than as focus. Gold, and further out. */
.sj-splash :focus-visible {
  outline: 2px solid rgba(255, 226, 178, .95);
  outline-offset: 4px;
}

/* ---------- the scene ---------- */

.sj-splash-scene {
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* The plum floor everything else is lit against. Warmer and lighter at the
     top, where the crest sits, so the light has a direction and the whole card
     does not settle into one flat violet. */
  background:
    radial-gradient(110% 85% at 50% -12%, #7a2853 0%, #4a1a48 38%, #2a1338 68%, #180e2c 100%);
}

/* Three slow colour fields, the same rose/peach/gold as the ID artwork, drifting
   out of phase so the light never settles into a fixed arrangement. */
.sj-splash-glow {
  position: absolute;
  border-radius: 50%;
  will-change: transform;
  transform: translate3d(0, 0, 0);
}

.sj-splash-glow.g1 {
  width: 78vw;
  height: 78vw;
  top: -30vw;
  left: -18vw;
  background: radial-gradient(circle at 40% 40%, rgba(254, 81, 130, .42), rgba(254, 81, 130, 0) 66%);
  animation: sj-splash-drift-a 22s ease-in-out infinite alternate;
}

.sj-splash-glow.g2 {
  width: 66vw;
  height: 66vw;
  right: -22vw;
  top: 8vh;
  background: radial-gradient(circle at 55% 45%, rgba(232, 178, 106, .34), rgba(232, 178, 106, 0) 64%);
  animation: sj-splash-drift-b 26s ease-in-out infinite alternate;
}

.sj-splash-glow.g3 {
  width: 90vw;
  height: 70vw;
  bottom: -34vw;
  left: 10vw;
  background: radial-gradient(circle at 50% 50%, rgba(160, 84, 190, .32), rgba(160, 84, 190, 0) 68%);
  animation: sj-splash-drift-c 30s ease-in-out infinite alternate;
}

/* A single wide sheen turning behind the crest. A conic gradient rotated by
   transform, which is one composited layer — the alternative, animating the
   gradient's angle, re-rasterises the whole thing every frame. */
.sj-splash-silk {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 150vmax;
  height: 150vmax;
  margin: -75vmax 0 0 -75vmax;
  opacity: .16;
  background: conic-gradient(
    from 0deg,
    rgba(255, 255, 255, 0) 0deg,
    rgba(255, 214, 165, .55) 42deg,
    rgba(255, 255, 255, 0) 96deg,
    rgba(255, 255, 255, 0) 190deg,
    rgba(255, 168, 196, .45) 236deg,
    rgba(255, 255, 255, 0) 300deg
  );
  /* Faded to nothing at the edges so the cone never shows a hard boundary
     against the plum. */
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 0%, transparent 62%);
  mask-image: radial-gradient(circle at 50% 50%, #000 0%, transparent 62%);
  will-change: transform;
  animation: sj-splash-turn 44s linear infinite;
}

.sj-splash-motes { position: absolute; inset: 0; }

/* Gold light rising. Positions, sizes and timings come from the PHP, so no two
   loads are arranged the same way. */
.sj-splash-mote {
  position: absolute;
  left: var(--m-x);
  top: var(--m-y);
  width: var(--m-size);
  height: var(--m-size);
  border-radius: 50%;
  background: rgba(255, 226, 178, .95);
  box-shadow: 0 0 8px 1px rgba(255, 206, 140, .55);
  opacity: 0;
  will-change: transform, opacity;
  animation: sj-splash-rise var(--m-duration) linear var(--m-delay) infinite;
}

/* Darkened corners, so the eye is pulled to the middle rather than wandering
   the colour fields. */
.sj-splash-vignette {
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 100% at 50% 45%, transparent 42%, rgba(12, 6, 24, .72) 100%);
}

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

.sj-splash-stage {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 32px 24px;
  /* Nudged above centre: optical centre sits a little high, and a stack that is
     mathematically centred reads as slightly low. */
  transform: translateY(-2vh);
}

.sj-splash-crest {
  position: relative;
  display: grid;
  place-items: center;
  margin-bottom: 26px;
  animation: sj-splash-crest-in 1.1s var(--ease, cubic-bezier(.4, 0, .2, 1)) both;
}

/*
 * The window the logo is seen through.
 *
 * assets/logo.png is a 1000×1000 file whose mark occupies a band roughly 200px
 * tall across the middle — the rest is empty. Sized to its own proportions it
 * would be a square of mostly nothing, so the frame takes an aspect ratio close
 * to the mark's own and object-fit: cover fills it, which crops the empty
 * padding away from the top and bottom.
 *
 * 3.6 rather than the mark's true 4.17: the ratio decides how tall a slice of
 * the original is kept, and a slice cut to the mark exactly would shave the
 * ascender of the S the first time anybody re-exports the logo a few pixels
 * larger. This leaves about 30px of clearance at each edge.
 */
.sj-splash-mark {
  position: relative;
  z-index: 2;
  display: block;
  width: clamp(230px, 54vw, 396px);
  aspect-ratio: 3.6;
  overflow: hidden;
}

.sj-splash-mark img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The mark sits a fraction right of the file's centre; nudged back so it is
     optically centred under the heading rather than mathematically centred in
     a square it does not fill. */
  object-position: 51.6% 49%;
  filter: drop-shadow(0 8px 26px rgba(255, 120, 165, .5));
}

/* The light the mark is lit by, breathing slightly so the crest is never quite
   static even once the entrance has finished. Wider than it is tall, following
   the shape of the thing it is lighting. */
.sj-splash-halo {
  position: absolute;
  z-index: 1;
  width: 150%;
  height: 320%;
  border-radius: 50%;
  background:
    radial-gradient(closest-side, rgba(255, 150, 185, .38) 0%, rgba(255, 150, 185, 0) 100%);
  animation: sj-splash-breathe 4.6s ease-in-out infinite;
}

.sj-splash-eyebrow {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 0 0 14px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .34em;
  text-transform: uppercase;
  color: rgba(255, 222, 232, .82);
  animation: sj-splash-up .9s var(--ease, cubic-bezier(.4, 0, .2, 1)) .35s both;
}

/* Rules that grow out from the words rather than sitting there at full width —
   the line draws, then the wordmark arrives under it. */
.sj-splash-eyebrow::before,
.sj-splash-eyebrow::after {
  content: '';
  width: clamp(28px, 9vw, 64px);
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 214, 165, .85));
  transform-origin: right center;
  animation: sj-splash-rule .9s var(--ease, cubic-bezier(.4, 0, .2, 1)) .35s both;
}

.sj-splash-eyebrow::after {
  background: linear-gradient(90deg, rgba(255, 214, 165, .85), transparent);
  transform-origin: left center;
}

.sj-splash-word {
  margin: 0;
  font-weight: 500;
}

.sj-splash-word-main {
  display: block;
  font-family: 'Playfair Display', Georgia, serif;
  font-style: italic;
  font-weight: 600;
  font-size: clamp(38px, 9.5vw, 76px);
  line-height: 1.02;
  letter-spacing: -.01em;

  /* Foil, not flat gold: a gradient through the letterforms with a highlight
     that crosses them once, slowly, on the same clock as the progress bar. */
  background: linear-gradient(
    100deg,
    #ffe7c4 0%,
    #fff8ee 18%,
    #ffd9a8 34%,
    #ffffff 50%,
    #ffd9a8 66%,
    #fff8ee 82%,
    #ffe7c4 100%
  );
  background-size: 260% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;

  animation:
    sj-splash-up 1s var(--ease, cubic-bezier(.4, 0, .2, 1)) .5s both,
    sj-splash-foil var(--sj-splash-time, 5400ms) ease-in-out .5s infinite;
}

.sj-splash-tag {
  margin: 18px 0 0;
  /* Wide enough to stay on one line where there is room, and balanced where
     there is not — a three-word orphan under a two-line tagline undoes the
     symmetry the rest of the card is built on. */
  max-width: min(44ch, 88vw);
  text-wrap: balance;
  font-size: clamp(13px, 3.2vw, 15px);
  line-height: 1.6;
  color: rgba(255, 228, 238, .74);
  animation: sj-splash-up .9s var(--ease, cubic-bezier(.4, 0, .2, 1)) .86s both;
}

.sj-splash-enter {
  margin-top: 30px;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 13px 28px;
  border: 1px solid rgba(255, 214, 165, .5);
  border-radius: 999px;
  background: rgba(255, 255, 255, .06);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  color: #fff4e4;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background .3s var(--ease, cubic-bezier(.4, 0, .2, 1)),
              border-color .3s var(--ease, cubic-bezier(.4, 0, .2, 1)),
              transform .3s var(--ease, cubic-bezier(.4, 0, .2, 1));
  animation: sj-splash-up .9s var(--ease, cubic-bezier(.4, 0, .2, 1)) 1.04s both;
}

.sj-splash-enter:hover {
  background: rgba(255, 255, 255, .14);
  border-color: rgba(255, 226, 190, .9);
  transform: translateY(-1px);
}

.sj-splash-enter:active { transform: translateY(0); }

.sj-splash-enter-ico {
  transition: transform .3s var(--ease, cubic-bezier(.4, 0, .2, 1));
}

.sj-splash-enter:hover .sj-splash-enter-ico { transform: translateX(4px); }

/* The hairline that says how long this lasts. Not a loading bar — nothing is
   loading — but the same promise: this ends, and here is when. */
.sj-splash-bar {
  width: min(220px, 52vw);
  height: 1px;
  margin-top: 34px;
  background: rgba(255, 255, 255, .16);
  overflow: hidden;
  animation: sj-splash-up .8s var(--ease, cubic-bezier(.4, 0, .2, 1)) 1.2s both;
}

.sj-splash-bar span {
  display: block;
  height: 100%;
  width: 100%;
  background: linear-gradient(90deg, rgba(254, 129, 168, .9), rgba(255, 226, 178, .95));
  transform-origin: left center;
  /* scaleX rather than width: a width animation lays the element out again on
     every frame, a transform does not. */
  animation: sj-splash-fill var(--sj-splash-time, 5400ms) linear both;
}

/* ---------- sound ---------- */

/*
 * The invitation, shown only when the browser has refused to start the music.
 *
 * Under the progress bar rather than beside the button in the corner: a
 * soundtrack nobody knows about is a soundtrack nobody turns on, and the corner
 * is the one part of the card nobody is looking at. It asks for a tap anywhere
 * because that is both true and the easiest thing to do — any press on the card
 * counts, and the first one is treated as an answer to this rather than as a
 * request to skip.
 */
.sj-splash-hint {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 16px 0 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: rgba(255, 233, 198, .92);
  /* Slow, and never far from full: a hint that blinks is an alert, and this is
     an offer — but one nobody acts on if they do not notice it. */
  animation:
    sj-splash-fade .8s ease both,
    sj-splash-hint-pulse 3.4s ease-in-out 1s infinite;
}

.sj-splash-hint-ico {
  width: 14px;
  height: 14px;
  opacity: .85;
}

.sj-splash-sound {
  position: absolute;
  z-index: 2;
  top: max(18px, env(safe-area-inset-top));
  right: max(18px, env(safe-area-inset-right));
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, .18);
  border-radius: 50%;
  background: rgba(255, 255, 255, .07);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  color: rgba(255, 236, 214, .9);
  cursor: pointer;
  transition: background .25s var(--ease, cubic-bezier(.4, 0, .2, 1)),
              border-color .25s var(--ease, cubic-bezier(.4, 0, .2, 1)),
              color .25s var(--ease, cubic-bezier(.4, 0, .2, 1));
  /* No delay, unlike the entrance stagger the rest of the card uses.
   *
   * This button is not part of that entrance: it starts hidden and is revealed
   * by splash.js at whatever moment the music actually starts, which on a phone
   * is the moment somebody taps. A delayed fade with fill-mode both holds the
   * from-state, so a delay here means the control sits at opacity 0 — clickable,
   * because opacity does not affect hit testing, but invisible — for over a
   * second after the tap it is meant to be acknowledging, on a card that is only
   * up for a few. Tapped late enough, it never appeared at all: a corner that
   * responds to a press while showing nothing. It fades in when it arrives,
   * which is what the hint beside it already does. */
  animation: sj-splash-fade .45s ease both;
}

.sj-splash-sound:hover {
  background: rgba(255, 255, 255, .15);
  border-color: rgba(255, 226, 190, .7);
}

.sj-splash-sound .sj-icon { width: 19px; height: 19px; }

/* One button, two glyphs, whichever matches the state. Swapping the icon's path
   data in JS would put the drawing in the script; this keeps it in the markup
   where the rest of the portal's icons live. */
.sj-splash-sound .is-off { display: none; }
.sj-splash-sound[aria-pressed="false"] .is-on { display: none; }
.sj-splash-sound[aria-pressed="false"] .is-off { display: block; }
.sj-splash-sound[aria-pressed="false"] { color: rgba(255, 255, 255, .5); }

/* A faint ring pulsing out while the music plays, so the control shows what it
   is doing without a label saying so. */
.sj-splash-sound[aria-pressed="true"]::after {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: 50%;
  border: 1px solid rgba(255, 214, 165, .6);
  animation: sj-splash-pulse 2.6s ease-out infinite;
  pointer-events: none;
}

/* ---------- the reveal ---------- */

/* The curtain does not fade so much as lift: it scales past the viewport and
   blurs as it goes, which reads as the card withdrawing rather than the page
   dissolving. The stage leaves slightly ahead of the backdrop so the words are
   gone before the colour is. */
.sj-splash.is-out {
  animation: sj-splash-lift 1s cubic-bezier(.65, 0, .35, 1) both;
  pointer-events: none;
}

.sj-splash.is-out .sj-splash-stage {
  animation: sj-splash-stage-out .55s cubic-bezier(.65, 0, .35, 1) both;
}

/* The page arriving underneath. Applied to <body> for the length of the lift,
   so the directory settles in rather than being uncovered all at once. */
.sj-splash-reveal .site-header,
.sj-splash-reveal main,
.sj-splash-reveal .site-foot {
  animation: sj-splash-page-in 1.1s cubic-bezier(.22, 1, .36, 1) both;
}

.sj-splash-reveal main { animation-delay: .08s; }
.sj-splash-reveal .site-foot { animation-delay: .16s; }

/* ---------- keyframes ---------- */

@keyframes sj-splash-drift-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(6vw, 5vh, 0) scale(1.14); }
}

@keyframes sj-splash-drift-b {
  from { transform: translate3d(0, 0, 0) scale(1.06); }
  to   { transform: translate3d(-7vw, 6vh, 0) scale(.94); }
}

@keyframes sj-splash-drift-c {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(5vw, -6vh, 0) scale(1.12); }
}

@keyframes sj-splash-turn {
  to { transform: rotate(1turn); }
}

@keyframes sj-splash-breathe {
  0%, 100% { transform: scale(1); opacity: .8; }
  50%      { transform: scale(1.1); opacity: 1; }
}

@keyframes sj-splash-rise {
  0%   { transform: translate3d(0, 0, 0); opacity: 0; }
  18%  { opacity: var(--m-opacity); }
  78%  { opacity: var(--m-opacity); }
  100% { transform: translate3d(var(--m-drift), var(--m-rise), 0); opacity: 0; }
}

@keyframes sj-splash-crest-in {
  from { opacity: 0; transform: scale(.72); }
  to   { opacity: 1; transform: scale(1); }
}

@keyframes sj-splash-up {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes sj-splash-rule {
  from { opacity: 0; transform: scaleX(0); }
  to   { opacity: 1; transform: scaleX(1); }
}

@keyframes sj-splash-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* One pass of the highlight across the letterforms, held at each end so it
   reads as a single sweep rather than a loop. */
@keyframes sj-splash-foil {
  0%, 12%   { background-position: 130% 50%; }
  70%, 100% { background-position: -30% 50%; }
}

@keyframes sj-splash-fill {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

@keyframes sj-splash-hint-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .7; }
}

@keyframes sj-splash-pulse {
  0%   { transform: scale(1); opacity: .7; }
  70%  { transform: scale(1.55); opacity: 0; }
  100% { transform: scale(1.55); opacity: 0; }
}

@keyframes sj-splash-lift {
  0%   { opacity: 1; transform: scale(1); filter: blur(0); }
  100% { opacity: 0; transform: scale(1.09); filter: blur(10px); }
}

@keyframes sj-splash-stage-out {
  from { opacity: 1; transform: translateY(-2vh) scale(1); }
  to   { opacity: 0; transform: translateY(-5vh) scale(.97); }
}

@keyframes sj-splash-page-in {
  from { opacity: 0; transform: translateY(18px) scale(.99); }
  to   { opacity: 1; transform: none; }
}

/* ---------- smaller screens ---------- */

@media (max-width: 560px) {
  .sj-splash-crest { margin-bottom: 20px; }
  .sj-splash-eyebrow { letter-spacing: .26em; gap: 10px; }
  .sj-splash-tag { margin-top: 16px; }
  .sj-splash-enter { margin-top: 24px; }
  .sj-splash-bar { margin-top: 26px; }

  /* The silk is a 150vmax layer being rotated; on a phone that is a very large
     surface for very little of the effect, and it is the first thing to go. */
  .sj-splash-silk { display: none; }
}

/* A landscape phone has no vertical room for the full stack — the tagline is
   the line that can go, since the heading above already says what this is. */
@media (max-height: 480px) {
  .sj-splash-crest { margin-bottom: 12px; }
  .sj-splash-mark { width: clamp(190px, 34vw, 260px); }
  .sj-splash-tag { display: none; }
  .sj-splash-enter { margin-top: 18px; }
  .sj-splash-bar { margin-top: 18px; }
}

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

/* The card stays — it is a title, and titles are not motion — but everything
   that drifts, turns, rises or breathes stops, and the entrance becomes a plain
   fade. The progress bar keeps filling: it is the only thing on screen saying
   how long this lasts, and freezing it would leave somebody waiting on a card
   with no indication it ever ends. splash.js shortens the hold to match. */
@media (prefers-reduced-motion: reduce) {
  .sj-splash-glow,
  .sj-splash-silk,
  .sj-splash-motes,
  .sj-splash-halo,
  .sj-splash-sound[aria-pressed="true"]::after {
    animation: none;
  }

  .sj-splash-motes { display: none; }

  .sj-splash-crest,
  .sj-splash-eyebrow,
  .sj-splash-eyebrow::before,
  .sj-splash-eyebrow::after,
  .sj-splash-word-main,
  .sj-splash-tag,
  .sj-splash-enter,
  .sj-splash-bar,
  .sj-splash-hint,
  .sj-splash-sound {
    animation: sj-splash-fade .4s ease both;
  }

  .sj-splash-word-main {
    /* No sweep to clip, so the foil becomes a flat gold. */
    background: none;
    color: #ffe7c4;
    -webkit-text-fill-color: #ffe7c4;
  }

  .sj-splash-bar span { animation: sj-splash-fill var(--sj-splash-time, 5400ms) linear both; }

  .sj-splash.is-out { animation: sj-splash-fade-out .4s ease both; }
  .sj-splash.is-out .sj-splash-stage { animation: none; }

  .sj-splash-reveal .site-header,
  .sj-splash-reveal main,
  .sj-splash-reveal .site-foot { animation: none; }
}

@keyframes sj-splash-fade-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}
