/* Montserrat, served from this site's own fonts folder (one file covers every weight). */
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/montserrat.woff2") format("woff2");
}

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

html,
body {
  margin: 0;
}

/* Nothing on the page can be highlighted, and pictures cannot be dragged off it.
   (protect.js switches off the right-click menu.) */
body {
  -webkit-user-select: none;
  user-select: none;
}

img {
  -webkit-user-drag: none;
}

/* Page background: background.png, filling the window and staying put while the page scrolls. */
html {
  min-height: 100%;
  /* The page never scrolls sideways. */
  overflow-x: hidden;
  background: #e9d3a4 url("images/background.png") center / cover no-repeat fixed;
}

/* ---------- Nav bar: black box with white text in the top right corner ---------- */

.site-nav {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  max-width: 100%;
  padding: 0 0.8em;
  background: #000;
  /* Montserrat Bold, loaded from Google Fonts in index.html. */
  font-family: "Montserrat", Arial, sans-serif;
  font-weight: 700;
  /* 12px on a normal window, shrinking a little on narrower ones so the bar stays clear of the logo.
     The box is sized from the text, so changing this resizes both. */
  font-size: clamp(10px, 0.85vw, 12px);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.site-nav a {
  padding: 0.75em 0.85em;
  color: #fff;
  text-decoration: none;
  white-space: nowrap;
}

/* Pinkish red when pointed at or tabbed to. */
.site-nav a:hover,
.site-nav a:focus-visible {
  color: #dd6384;
}

/* ---------- Nav bar as a scroll, centered under the logo ---------- */

/* These rules come after the ones above and replace the black box in the corner. The scroll is
   drawn from scroll.png: its two patterned ends keep their shape and the cream middle stretches
   to fit the links. On load the two rods start together in the middle and roll apart, the
   paper appears between them, and then the links fade in. */
.site-nav {
  /* Height of the scroll. Everything else is worked out from it. */
  --sh: clamp(30px, 3vw, 46px);
  --rod: calc(var(--sh) * 24 / 131);

  position: relative;
  top: auto;
  right: auto;
  flex: none;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: center;
  height: var(--sh);
  /* Tucked up under "A Classic Online Adventure". More negative = higher. */
  margin-top: calc(var(--logo-h) * -0.04);
  padding: 0;
  background: none;
}

.scroll-paper {
  position: absolute;
  inset: 0;
  border-style: solid;
  border-width: 0 calc(var(--sh) * 90 / 131) 0 calc(var(--sh) * 78 / 131);
  border-image: url("images/scroll.png") 0 90 0 78 fill stretch;
  animation: scroll-paper 0.9s ease-out 0.4s both;
}

/* The scroll (paper, patterned ends and rods) is slightly see-through; the links stay solid.
   1 = solid, lower = more see-through. */
.scroll-paper,
.scroll-rod {
  opacity: 0.75;
}

/* Yellowish tint over the cream middle of the scroll (the patterned ends are left alone).
   The colour is the tint; a stronger or paler yellow goes here. */
.scroll-paper::after {
  content: "";
  position: absolute;
  inset: 0;
  background: #f6ebc4;
  mix-blend-mode: multiply;
}

/* The rod is the left end of scroll.png; the right-hand one is the same rod mirrored. */
.scroll-rod {
  position: absolute;
  top: 0;
  width: var(--rod);
  height: 100%;
  background: url("images/scroll.png") 0 0 / auto 100% no-repeat;
}

.scroll-rod-left {
  left: 0;
  animation: scroll-rod-left 0.9s ease-out 0.4s both;
}

.scroll-rod-right {
  right: 0;
  transform: scaleX(-1);
  animation: scroll-rod-right 0.9s ease-out 0.4s both;
}

.scroll-links {
  position: relative;
  display: flex;
  padding: 0 calc(var(--sh) * 98 / 131) 0 calc(var(--sh) * 86 / 131);
  animation: scroll-links 0.45s ease-out 1.3s both;
}

.site-nav a {
  color: #2b1d12;
}

.site-nav a:hover,
.site-nav a:focus-visible {
  color: #c62828;
}

@keyframes scroll-paper {
  from { clip-path: inset(0 50%); }
  to { clip-path: inset(0 0); }
}

@keyframes scroll-rod-left {
  from { left: calc(50% - var(--rod)); }
  to { left: 0; }
}

@keyframes scroll-rod-right {
  from { right: calc(50% - var(--rod)); }
  to { right: 0; }
}

@keyframes scroll-links {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Already rolled open earlier in this visit (set by the small script at the top of each page):
   show it open straight away instead of rolling it out again. */
.nav-opened .scroll-paper,
.nav-opened .scroll-rod,
.nav-opened .scroll-links {
  animation: none;
}

/* People who have asked their device for less motion get the scroll already open. */
@media (prefers-reduced-motion: reduce) {
  .scroll-paper,
  .scroll-rod,
  .scroll-links {
    animation: none;
  }
}

/* ---------- Hero: full-width banner with the logo centered on it ---------- */

/* Height of the banner: 40% of the window on the front page, shorter on the other pages
   (see "body.inner" under Inner pages) so their content fits without scrolling. */
body {
  --hero-h: 40vh;
  /* What the logo is sized from. The same as the banner's height, except while the Login /
     Register menu is open, when the banner fills the window but the logo must not. */
  --logo-basis: 40vh;
}

/* The banner picture is drawn by two layers fixed to the top of the window, behind the hero's
   logo and nav. Together they show the picture everywhere EXCEPT inside the brush strokes of
   the torn edge, which are left truly see-through: the page scrolls up behind the banner and
   stays visible inside the strokes.
     .hero-cut  blacks out everything behind the banner except the brush-stroke shapes
     .hero-pic  then adds the picture onto that black, leaving the brush strokes untouched
   The strokes are the bottom strip of banner.png. The filters turn them pure white (or pure
   black) and everything else the opposite. Do not give these two layers a parent with a
   z-index, transform or filter: they have to blend with the page itself. */
.hero-cut,
.hero-pic {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 3;
  height: var(--hero-h);
  overflow: hidden;
  background: #000;
  pointer-events: none;
}

.hero-cut {
  mix-blend-mode: multiply;
}

.hero-pic {
  mix-blend-mode: screen;
}

.hero-cut::after,
.hero-pic::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 5vw;
  background: url("images/banner.png") center bottom / 100% auto no-repeat;
}

/* White strokes on black. */
.hero-cut::after {
  filter: grayscale(1) brightness(0.58) contrast(10);
}

/* Black strokes, laid over the picture. */
.hero-pic::after {
  filter: grayscale(1) brightness(0.58) contrast(10) invert(1);
  mix-blend-mode: multiply;
}

/* The hero itself: the logo, nav, fireflies and butterfly, on top of the picture. */
.hero {
  /* Height of the torn edge along the bottom of the hero. */
  --tear: 4vw;
  /* Space kept free at the top for the nav bar. Only needed on narrow screens, where the bar
     runs the full width (see the bottom of this file). */
  --nav-h: 0px;
  /* Logo size: fills most of the space between the nav bar and the torn edge, never wider than
     the screen. 1.8133 is the logo image's width divided by its height. */
  --logo-w: min(84vw, calc((var(--logo-basis) - var(--tear) - var(--nav-h)) * 0.66 * 1.8133));
  --logo-h: calc(var(--logo-w) / 1.8133);

  /* Stays at the top of the window while the rest of the page scrolls up behind it. */
  position: sticky;
  top: 0;
  z-index: 3;
  width: 100%;
  height: var(--hero-h);
  overflow: hidden;
}

/* Full width at the picture's true proportions (no squashing). The hero is shorter than the
   picture, so it shows a band of it: the sky, tree and rooftops. Raise or lower the band
   with the second number (0% = top of the picture, 100% = bottom). */
.hero-banner {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 30%;
}

/* The space above the torn edge; the logo block is centered in it. */
.hero-content {
  position: absolute;
  top: var(--nav-h);
  left: 0;
  right: 0;
  bottom: var(--tear);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

/* Logo plus its two taglines, as one block.
   Inside logo.png the NINJORA lettering runs from 15.4% to 93.5% of the image's width, so its
   middle is 4.45% right of the image's middle (the flame takes up the left side). Shifting the
   block left by that amount puts the lettering exactly on the center of the page. */
.hero-lockup {
  position: relative;
  width: var(--logo-w);
  transform: translateX(-4.45%);
}

.hero-logo {
  display: block;
  width: 100%;
  height: auto;
  /* Dark, tight shadow hugging the logo. In vh so it scales with the logo. */
  filter:
    drop-shadow(0 0.15vh 0.1vh rgba(0, 0, 0, 0.9))
    drop-shadow(0 0.35vh 0.45vh rgba(0, 0, 0, 0.75));
}

.hero-tagline {
  margin: 0;
  color: #fff;
  line-height: 1;
  white-space: nowrap;
  text-shadow:
    0 0.08em 0.08em rgba(0, 0, 0, 0.95),
    0 0.14em 0.3em rgba(0, 0, 0, 0.8);
}

/* Japanese line: sits directly on top of the lettering, starting right beside the kunai.
   "left" moves it sideways (smaller = further left; below about 40% it runs into the kunai).
   The numbers are positions measured inside logo.png. */
.hero-tagline-jp {
  position: absolute;
  left: 40.5%;
  top: calc(var(--logo-h) * 0.4525 - 1em);
  font-family: "Yu Mincho", "YuMincho", "Hiragino Mincho ProN", "Noto Serif JP", serif;
  font-weight: 700;
  /* Sized purely from the logo's width, with no minimum, so the line shrinks and grows with
     the logo and always ends before the A. */
  font-size: calc(var(--logo-w) * 0.0268);
  letter-spacing: 0.06em;
}

/* English line: tucked up right under the lettering, in front of the swoosh, on a see-through
   black box. "left" is where the middle of the box sits across the logo image: 54.45% is the
   exact middle of the lettering, and it is set slightly left of that (smaller = further left).
   The 0.822 in "top" is how far down the logo the box sits: smaller = higher, but much higher
   and it covers the hook of the J. */
.hero-tagline-en {
  position: absolute;
  left: 53.5%;
  top: calc(var(--logo-h) * 0.822);
  transform: translateX(-50%);
  /* letter-spacing also adds a gap after the last letter; the extra left padding balances it
     so the text sits in the middle of the box */
  padding: 0.32em 2.4em 0.28em 2.62em;
  /* The box: black, fully see-through at the left and right ends, getting steadily darker all
     the way in to the center, where it is 75% solid. The 0.75 sets how dark the center is. */
  background: linear-gradient(
    to right,
    rgba(0, 0, 0, 0) 0%,
    rgba(0, 0, 0, 0.3) 12%,
    rgba(0, 0, 0, 0.55) 28%,
    rgba(0, 0, 0, 0.75) 50%,
    rgba(0, 0, 0, 0.55) 72%,
    rgba(0, 0, 0, 0.3) 88%,
    rgba(0, 0, 0, 0) 100%
  );
  font-family: Georgia, "Times New Roman", serif;
  /* Same as the Japanese line: sized purely from the logo's width, no minimum. */
  font-size: calc(var(--logo-w) * 0.032);
  letter-spacing: 0.22em;
}

/* ---------- Login / Register box, top right corner of the banner ---------- */

/* Dark grey, 50% see-through, with white text. */
.account-box {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 2;
  padding: 0.75em 1.1em;
  background: rgba(40, 40, 40, 0.5);
  color: #fff;
  /* Same lettering as "A Classic Online Adventure": white serif, widely spaced, dark shadow. */
  font-family: Georgia, "Times New Roman", serif;
  font-size: 13px;
  letter-spacing: 0.22em;
  text-shadow:
    0 0.08em 0.08em rgba(0, 0, 0, 0.95),
    0 0.14em 0.3em rgba(0, 0, 0, 0.8);
}

.account-box a {
  color: #fff;
  text-decoration: none;
}

.account-box a:hover,
.account-box a:focus-visible {
  color: #a8e6a1;
}

/* ---------- Fireflies: glowing dots that float up the banner, behind the logo ---------- */

.fireflies {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* Each firefly starts just below the banner, floats up past the top, and starts again.
   Its position, size, speed and sideways drift are set on it in index.html. */
.fireflies span {
  position: absolute;
  left: var(--x);
  bottom: -4%;
  /* The dot and its glow are one soft gradient, four times the dot's size. Kept cheap on
     purpose: no shadows or blur, and only its position and opacity are animated, which the
     graphics card handles without redrawing the page. */
  width: calc(var(--s) * 4);
  height: calc(var(--s) * 4);
  background: radial-gradient(circle, #fffde0 0%, #ffe872 14%, rgba(255, 224, 96, 0.45) 28%, rgba(255, 220, 80, 0) 68%);
  will-change: transform, opacity;
  animation:
    firefly-rise var(--d) linear calc(var(--w) - var(--fireflies-so-far, 0s)) infinite,
    firefly-glow 2.4s ease-in-out calc(var(--w) - var(--fireflies-so-far, 0s)) infinite alternate;
}

/* Up the full height of the banner (40vh) and a little beyond, wandering side to side. */
@keyframes firefly-rise {
  0% { transform: translate(0, 0); }
  35% { transform: translate(var(--drift), -16vh); }
  70% { transform: translate(calc(var(--drift) * -0.5), -32vh); }
  100% { transform: translate(calc(var(--drift) * 0.6), -46vh); }
}

/* The glow pulses brighter and dimmer. */
@keyframes firefly-glow {
  from { opacity: 0.35; }
  to { opacity: 1; }
}

/* People who have asked their device for less motion get no fireflies. */
@media (prefers-reduced-motion: reduce) {
  .fireflies {
    display: none;
  }
}

/* ---------- Butterfly: flies around the banner and lands on the title ---------- */

/* butterfly.js moves it, sizes it and picks the frame. The picture is the game's own sprite
   sheet: 8 facing directions across, 2 wing positions down. It is drawn in front of the logo
   and the torn edge, and behind the nav bar. */
.butterfly {
  position: absolute;
  left: 0;
  top: 0;
  /* In front of everything on the banner, the nav bar included. Clicks pass straight through it. */
  z-index: 3;
  width: 32px;
  height: 32px;
  background: url("images/butterfly.png") 0 0 / 800% 200% no-repeat;
  image-rendering: pixelated;
  pointer-events: none;
  will-change: transform;
  /* Hidden until the script has given it a position. */
  visibility: hidden;
}

/* ---------- Selling points, on the white page below the banner ---------- */

/* The headline starts 8% in from the left edge of the page, and the content column takes all
   the remaining width to its right. The first column is zero wide: it only holds the
   character, which hangs out of it to the left, into the page margin, so it never pushes the
   headline sideways. */
.feature {
  /* Size of the headline letters. The width of the text under it is worked out from this. */
  --title-size: clamp(26px, 3vw, 44px);

  display: grid;
  /* minmax(0, 1fr): the last column takes only the space that is left, whatever is inside it. */
  grid-template-columns: 0 auto minmax(0, 1fr);
  align-items: start;
  padding: 56px 8vw;
}

/* The character, in the page margin to the left of the headline. Its width is 6.3% of the
   window width (the margin it stands in is 8%), so it scales with the page. Raise or lower
   the 6.3vw to resize it; above about 7.5vw it no longer fits the margin. */
.feature-char {
  display: block;
  justify-self: end;
  margin-right: 6px;
  width: 6.3vw;
  height: auto;
}

/* Black body text under the headline. It fills the left block, so it runs wider than the headline. */
.feature-text {
  margin: 1.1em 0 0;
  color: #000;
  font-family: "Montserrat", Arial, sans-serif;
  font-weight: 500;
  font-size: clamp(14px, 0.75vw, 18px);
  line-height: 1.7;
}

/* The first paragraph sits close under the headline. Less negative (or positive) = bigger gap. */
.feature-title-wrap + .feature-text {
  margin-top: -0.2em;
}

.feature-text-centered {
  text-align: center;
}

/* Body text directly under a headline row (Master Jutsu, Learn Trade Skills) sits close to it.
   This is the gap; raise it for more space. */
.feature-title-row + .feature-text {
  margin-top: 0;
}

/* Under Learn Trade Skills the icons are taller than the headline and push the text down;
   this pulls it back up against the letters. */
.feature-centered .feature-title-row + .feature-text {
  margin-top: -10px;
}

/* Space between the headline and the content to its right. */
.feature-content {
  margin-left: 3vw;
}

/* The left block: headline with the grey text under it. Its width is what the text wraps to.
   It is set as a multiple of the headline's letter size: the headline itself is about 13.7
   letter-sizes wide, so 19 makes the text run about 40% past the end of WORLD at every window
   size. Raise the 19 to let the text run further right. */
.feature-heading {
  justify-self: start;
  width: calc(var(--title-size) * 19);
}

/* Exactly as wide as the headline, so the small Japanese line above it can be centered over it. */
.feature-title-wrap {
  width: fit-content;
}

/* Small Japanese line, centered on top of the headline, 1 pixel above its capitals. */
.feature-kicker {
  margin: 0;
  /* letter-spacing also adds a gap after the last character; this balances it so the line stays centered */
  padding-left: 0.3em;
  color: #000;
  font-family: "Yu Gothic", "YuGothic", "Hiragino Kaku Gothic ProN", "Noto Sans JP", Meiryo, sans-serif;
  font-weight: 700;
  font-size: clamp(11px, 1vw, 15px);
  line-height: 1;
  letter-spacing: 0.3em;
  text-align: center;
  white-space: nowrap;
}

/* Section headline: black Montserrat Bold, all capitals.
   A line of text has empty space above its capitals; the negative top margin cancels that
   space so the Japanese line ends up 1 pixel above the letters. Less negative = bigger gap. */
.feature-title {
  margin: -0.23em 0 0;
  white-space: nowrap;
  color: #000;
  font-family: "Montserrat", Arial, sans-serif;
  font-weight: 700;
  font-size: var(--title-size);
  line-height: 1.15;
  text-transform: uppercase;
}

/* Dividing line after a block of body text: black in the middle, fading out to nothing at
   both ends. "height" is its thickness; the margin is the space above and below it. */
.feature-rule {
  height: 2px;
  margin: 32px 0;
  border: 0;
  background: linear-gradient(to right, rgba(166, 124, 82, 0), #a67c52 25%, #a67c52 75%, rgba(166, 124, 82, 0));
}

/* A headline with a very thick black border around it. The first number in "border" is the thickness. */
/* A bordered headline with a picture to its left, the pair pushed to the right-hand end of
   the line above them. */
.feature-title-row {
  display: flex;
  justify-content: flex-end;
  align-items: flex-start;
}

/* A block centered across the whole page, below the two-column section. */
.feature-centered {
  --title-size: clamp(26px, 3vw, 44px);
  padding: 0 8vw 72px;
}

/* Its body text: centered, and no wider than the body text in the section above. */
.feature-centered .feature-text {
  max-width: calc(var(--title-size) * 19);
  margin-left: auto;
  margin-right: auto;
}

/* The same pair the other way round: headline at the left, picture to its right. */
.feature-title-row-left {
  justify-content: center;
  align-items: center;
  gap: 24px;
}

/* A small row of pixel-art icons beside a headline, shown whole at its real size. */
.feature-title-icons {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  min-width: 0;
  image-rendering: pixelated;
}

/* The picture is exactly as tall as the bordered headline beside it: the border (12 + 12),
   the padding (16 + 12), the Japanese line and the headline letters. If the border or padding
   on .feature-title-boxed change, change the 52px to match. */
.feature-title-pic {
  display: block;
  height: calc(52px + clamp(11px, 1vw, 15px) + var(--title-size) * 0.92);
  /* Fills the space left of the headline, showing a crop of skilltree.png.
     650px = how wide the whole picture is drawn (its real width is 925px); smaller shows more of it.
     79% 65% = which part shows: the lit Water Affinity node and its tooltip's title bar. The
     first number moves the crop right (higher) or left, the second down (higher) or up. */
  flex: 1;
  min-width: 0;
  background: #17161b url("images/skilltree.png") 79% 65% / 650px auto no-repeat;
  /* Fades out to nothing at its left end. 45% = how far in from the left the fade runs. */
  -webkit-mask-image: linear-gradient(to right, transparent, #000 45%);
  mask-image: linear-gradient(to right, transparent, #000 45%);
}

.feature-title-boxed {
  padding: 16px 26px 12px;
  border: 12px solid #000;
}

/* ---------- "Continue" with a down arrow, centered at the bottom of the window ---------- */

/* Only shown while there is more page below to scroll to (continue.js adds "is-shown"). */
.continue {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 18px;
  z-index: 5;
  margin: 0;
  pointer-events: none;
  visibility: hidden;
  color: #c62828;
  font-family: "Montserrat", Arial, sans-serif;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.04em;
  text-transform: lowercase;
  text-align: center;
}

.continue.is-shown {
  visibility: visible;
}

/* Behind "continue" and its arrow, the page content fades out into the parchment. This is a
   strip of the page background across the bottom of the window (same picture and settings as
   the page background, so the two line up), see-through at the top and solid at the bottom.
   "height" is how tall the fade is. */
.continue::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -18px;
  z-index: -1;
  height: 150px;
  background: #e9d3a4 url("images/background.png") center / cover no-repeat fixed;
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 65%);
  mask-image: linear-gradient(to bottom, transparent, #000 65%);
}

/* The arrow: a black triangle pointing down, under the word. */
.continue::after {
  content: "";
  display: block;
  width: 0;
  height: 0;
  margin: 4px auto 0;
  border-left: 20px solid transparent;
  border-right: 20px solid transparent;
  border-top: 18px solid currentColor;
}

/* ---------- Hover preview: the whole picture, shown at the mouse (preview.js) ---------- */

/* The whole picture, scaled down. 460px is how wide it is shown; raise or lower it to resize. */
.hover-preview {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 10;
  width: 460px;
  max-width: 60vw;
  max-height: 85vh;
  object-fit: contain;
  border: 3px solid #000;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6);
  pointer-events: none;
  visibility: hidden;
}

.hover-preview.is-shown {
  visibility: visible;
}

/* ---------- Picture button that plays a clip ---------- */

/* The picture, with brush-stroke edges and a play button in the middle. Its width is 26% of
   the window width (or the whole column, if that is narrower); raise or lower the 26vw to
   resize it. Do not put a transform or a filter on this box itself: the brush edges would
   stop lining up with the page background. */
.clip {
  /* How deep the brush strokes reach into each side, as a share of the picture's height. */
  --stroke: 10%;

  position: relative;
  display: block;
  width: min(100%, 26vw);
  /* Pushed to the right-hand end of its column, level with the page's right margin. */
  margin-left: auto;
  /* The picture's width / height. The brush strokes are worked out from it. */
  --ratio: 811 / 604;
  aspect-ratio: var(--ratio);
  isolation: isolate;
  /* Trims the stroke layers to the picture, so no stray hairlines show around its corners. */
  overflow: hidden;
}

.clip-thumb {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The brush-stroke edges. They are the same strokes as the torn edge under the banner (the
   bottom strip of banner.png), laid along all four sides and filled with the page background,
   so the picture looks painted onto the page. As under the banner it takes two layers:
   "clip-cut" blacks the stroke shapes out of the picture, then "clip-fill" paints the page
   background into them. */
.clip-edges {
  position: absolute;
  /* 1px past the picture on every side, so no hairline of the picture shows along its outer edge. */
  inset: -1px;
  overflow: hidden;
  background: #000;
  pointer-events: none;
}

/* One strip of strokes per side. The filter turns the strokes white and everything else
   black, and "lighten" merges the four strips where they meet in the corners. */
.clip-edges span {
  position: absolute;
  left: 0;
  height: var(--stroke);
  /* repeat-x: on a wide picture the strokes are shorter than the side, so they carry on to the corners. */
  background: url("images/banner.png") 0 100% / calc(var(--stroke) * 20 / (var(--ratio))) auto repeat-x;
  filter: grayscale(1) brightness(0.58) contrast(10);
  mix-blend-mode: lighten;
}

/* Bottom, then top (the same kind of strip, upside down). Each side shows a different
   stretch of the strokes, picked by background-position-x. */
.clip-edges span:nth-child(1) {
  bottom: 0;
  width: 100%;
  background-position-x: 8%;
}

.clip-edges span:nth-child(2) {
  top: 0;
  width: 100%;
  background-position-x: 63%;
  transform: scaleY(-1);
}

/* Left and right: the strip is as long as the picture is tall and is turned on its side. */
.clip-edges span:nth-child(3),
.clip-edges span:nth-child(4) {
  width: calc(100% / (var(--ratio)));
  background-size: calc(var(--stroke) * 20) auto;
  transform-origin: 0 0;
}

.clip-edges span:nth-child(3) {
  top: 0;
  background-position-x: 92%;
  transform: translateX(var(--stroke)) rotate(90deg);
}

.clip-edges span:nth-child(4) {
  top: 100%;
  left: 100%;
  background-position-x: 37%;
  transform: translateX(calc(var(--stroke) * -1)) rotate(-90deg);
}

.clip-cut {
  filter: invert(1);
  mix-blend-mode: multiply;
}

.clip-fill {
  mix-blend-mode: screen;
}

/* Same picture and settings as the page background, so the two line up exactly. */
.clip-fill::after {
  content: "";
  position: absolute;
  inset: 0;
  background: #e9d3a4 url("images/background.png") center / cover no-repeat fixed;
  mix-blend-mode: multiply;
}

/* A picture with the same brush-stroke edges that is not a button (no clip to play). */
.clip-still {
  flex: none;
  width: 36vw;
  margin-left: 0;
}

/* A picture with brush-stroke edges and a headline beside it, centered on the page. */
.feature-story {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 24px 3vw;
}

@media (max-width: 760px) {
  .clip-still {
    width: 100%;
  }

  .feature-story .feature-title {
    white-space: normal;
  }
}

/* Smaller line under a headline. */
.feature-sub {
  /* Lines of text carry empty space under and over their letters; the negative margin cancels
     it so these capitals sit 1px under the headline's. Less negative = bigger gap. */
  margin: calc(1px - var(--title-size) * 0.217) 0 0;
  padding: 0.4em 1em 0.35em;
  /* The same fading black box as behind "A Classic Online Adventure": see-through at both
     ends, darkest in the middle, with white text on it. */
  background: linear-gradient(
    to right,
    rgba(0, 0, 0, 0) 0%,
    rgba(0, 0, 0, 0.3) 12%,
    rgba(0, 0, 0, 0.55) 28%,
    rgba(0, 0, 0, 0.75) 50%,
    rgba(0, 0, 0, 0.55) 72%,
    rgba(0, 0, 0, 0.3) 88%,
    rgba(0, 0, 0, 0) 100%
  );
  color: #fff;
  font-family: "Montserrat", Arial, sans-serif;
  font-weight: 700;
  font-size: calc(var(--title-size) * 0.45);
  line-height: 1;
  text-align: center;
  text-transform: uppercase;
}

/* A second headline, with its Japanese line, under a smaller line. The 50px is the gap between
   the smaller line's capitals and the Japanese line. */
.feature-kicker-after-sub {
  margin-top: 50px;
}

.feature-title-after-sub {
  text-align: center;
}

/* A picture beside a headline. Its height is 16% of the window width; raise or lower the 16vw to resize it. */
.feature-side-pic {
  display: block;
  flex: none;
  height: clamp(120px, 16vw, 300px);
  width: auto;
  max-width: 100%;
}

/* A fading line right across the block, directly under the picture and touching it. The
   picture stands on the line, so the block's contents are lined up along their bottoms. */
.feature-story:has(.feature-rule-touching) {
  align-items: flex-end;
  row-gap: 0;
}

.feature-rule-touching {
  flex: 0 0 100%;
  margin: 0;
}

/* ---------- News box: a bordered panel with the Discord link ---------- */

/* The text sits on smudge.png, a red brush stroke stretched to fit behind it. The padding
   keeps the text on the solid part of the stroke, clear of its ragged ends. */
.news-box {
  max-width: 1250px;
  /* Pulled up toward the line above it (more negative = closer) and shifted left of center
     (more negative = further left). */
  position: relative;
  left: -6vw;
  margin: -80px auto 0;
  /* Top and bottom padding place the text on the solid red band in the middle of the stroke. */
  padding: 66px 12% 60px;
  background: url("images/smudge.png") center / 100% 100% no-repeat;
  text-align: center;
}

.news-text {
  /* The negative part cancels the empty space under the last line of letters, leaving 1px
     between them and the button. */
  margin: 0 0 calc(8px - 0.39em);
  color: #fff;
  font-family: "Montserrat", Arial, sans-serif;
  font-weight: 700;
  font-size: clamp(12px, 0.85vw, 15px);
  line-height: 1.5;
}

/* The link as a button in the nav bar's old style: black box, white bold capitals,
   pinkish red when pointed at. */
.news-link {
  display: block;
  width: fit-content;
  margin: 0 auto;
  padding: 0.4em 1.6em;
  background: #000;
  color: #fff;
  font-family: "Montserrat", Arial, sans-serif;
  font-weight: 700;
  font-size: clamp(13px, 0.95vw, 16px);
  letter-spacing: 0.06em;
  text-decoration: none;
  text-transform: uppercase;
  transition: background-color 0.15s;
}

.news-link:hover,
.news-link:focus-visible {
  background: #c62828;
}

/* Play button: a black disc with a white ring and arrow, in the middle of the picture. */
.clip-play {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 12%;
  aspect-ratio: 1;
  border: 2px solid #fff;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.72);
  transform: translate(-50%, -50%);
  transition: background-color 0.15s, transform 0.15s;
}

.clip-play::before {
  content: "";
  position: absolute;
  left: 54%;
  top: 50%;
  width: 34%;
  height: 40%;
  background: #fff;
  clip-path: polygon(0 0, 100% 50%, 0 100%);
  transform: translate(-50%, -50%);
}

/* Pointed at or tabbed to: the nav bar's pinkish red, and the disc grows a little. */
.clip:hover .clip-play,
.clip:focus-visible .clip-play {
  background: #dd6384;
  transform: translate(-50%, -50%) scale(1.08);
}

.clip:focus-visible {
  outline: 2px solid #dd6384;
  outline-offset: 4px;
}

@media (max-width: 760px) {
  .clip {
    width: min(100%, 420px);
  }
}

/* ---------- Pop-up video player, opened by the picture button ---------- */

/* As large as fits the window while keeping the clip's 1914 x 992 shape, with room left above
   it for the Close button. */
.clip-dialog {
  width: min(92vw, calc((100vh - 120px) * 1914 / 992));
  max-width: none;
  max-height: none;
  padding: 0;
  border: 0;
  overflow: visible;
  background: #000;
}

.clip-dialog::backdrop {
  background: rgba(0, 0, 0, 0.85);
}

.clip-video {
  display: block;
  width: 100%;
  aspect-ratio: 1914 / 992;
  background: #000;
}

/* Close button, sitting on the player's top right corner, in the nav bar's style. */
.clip-close {
  position: absolute;
  right: 0;
  bottom: 100%;
  padding: 0.75em 0.85em;
  border: 0;
  background: #000;
  color: #fff;
  font-family: "Montserrat", Arial, sans-serif;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
}

.clip-close:hover,
.clip-close:focus-visible {
  color: #dd6384;
}

/* ---------- Inner pages (Download, Rankings, Support, Privacy) ---------- */

/* One centered column under the banner, in the same black-on-parchment style as the front page. */
.page {
  --title-size: clamp(26px, 3vw, 44px);
  max-width: 1100px;
  margin: 0 auto;
  padding: 26px 6vw 34px;
  color: #000;
  font-family: "Montserrat", Arial, sans-serif;
  font-weight: 500;
  font-size: 15px;
  line-height: 1.7;
}

.page-title {
  margin: 0 auto 16px;
}

/* Every page except the front page has a shorter banner, leaving more room for its content. */
body.inner {
  --hero-h: 28vh;
  --logo-basis: 28vh;
}

.inner .site-footer {
  padding: 14px 6vw 16px;
}

/* Moving between the front page and any other page, the banner glides from one height to the
   other instead of jumping. The small script at the top of each page adds "banner-shrink"
   (arriving from the front page) or "banner-grow" (arriving back on it). */
@property --hero-h {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}

/* Pages swapped in without a reload (transition.js): the height simply glides. */
body {
  transition: --hero-h 0.6s ease, --logo-basis 0.6s ease;
}

/* Pages loaded the ordinary way: the same glide, played as the page opens. */
.banner-shrink body {
  animation: banner-shrink 0.7s ease;
}

.banner-grow body {
  animation: banner-grow 0.7s ease;
}

@keyframes banner-shrink {
  from { --hero-h: 40vh; --logo-basis: 40vh; }
  to { --hero-h: 28vh; --logo-basis: 28vh; }
}

@keyframes banner-grow {
  from { --hero-h: 28vh; --logo-basis: 28vh; }
  to { --hero-h: 40vh; --logo-basis: 40vh; }
}

@media (prefers-reduced-motion: reduce) {
  .banner-shrink body,
  .banner-grow body {
    animation: none;
  }
}

/* A panel: thick black frame with a faint paper tint. */
.panel {
  flex: 1 1 320px;
  min-width: 0;
  padding: 24px 28px;
  border: 6px solid #000;
  background: rgba(255, 250, 235, 0.45);
}

.panel-row {
  /* Scales the Download page's two panels and everything in them. 1 = full size, lower = smaller. */
  zoom: 0.74;
  display: flex;
  flex-wrap: wrap;
  gap: 28px;
  align-items: flex-start;
}

.panel-heading {
  margin: 22px 0 10px;
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.panel > .panel-heading:first-child {
  margin-top: 0;
}

.panel-text {
  margin: 0 0 16px;
}

.panel-note {
  margin: 10px 0 0;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-align: center;
  text-transform: uppercase;
}

.panel-pic {
  display: block;
  max-width: 100%;
  margin: 0 0 12px;
  border: 2px solid #000;
}

.spec-table,
.rank-table {
  width: 100%;
  border-collapse: collapse;
}

.spec-table th,
.spec-table td,
.rank-table th,
.rank-table td {
  padding: 7px 6px;
  border-bottom: 1px solid rgba(0, 0, 0, 0.25);
  text-align: left;
}

.spec-table td,
.rank-table td:nth-child(n + 3),
.rank-table th:nth-child(n + 3) {
  text-align: right;
}

.rank-table th {
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* The top three players. */
.rank-table .is-top td {
  font-weight: 700;
  color: #9c1f1f;
}

.rank-sub {
  font-size: 12px;
  opacity: 0.7;
}

/* Board tabs: black boxes with white capitals; the open one is red. */
.rank-tabs {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-bottom: 18px;
}

.rank-tab {
  padding: 0.55em 1.2em;
  border: 0;
  background: #000;
  color: #fff;
  font-family: inherit;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}

.rank-tab:hover,
.rank-tab.is-active {
  background: #c62828;
}

/* Long text pages (Support, Privacy), carried over from the old site. */
.doc {
  padding: 28px 34px;
  border: 6px solid #000;
  background: rgba(255, 250, 235, 0.45);
}

.doc h2 {
  margin: 28px 0 8px;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.doc p,
.doc ul,
.doc ol {
  margin: 0 0 14px;
}

.doc a {
  color: #9c1f1f;
  font-weight: 700;
}

.doc .lede {
  font-size: 16px;
}

.doc .channels {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 16px;
  margin: 22px 0;
}

.doc .channel {
  display: flex;
  gap: 12px;
  padding: 14px 16px;
  border: 3px solid #000;
}

.doc .channel .ic {
  font-size: 22px;
  line-height: 1.2;
}

.doc .channel .name {
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.doc .channel .desc {
  margin: 4px 0 8px;
  font-size: 13px;
  line-height: 1.5;
}

/* Staff page: one block per person, separated by the tan line. */
.staff-member {
  margin-top: 22px;
  padding-top: 20px;
  border-top: 2px solid #a67c52;
}

/* The opening note and its signature sit on a black box that is 40% see-through, with white
   text. The last number in "background" is how solid the box is. */
.doc .staff-letter {
  padding: 22px 26px 10px;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
}

/* "Ninjora" at the start of the note: the pastel green of the Login / Register highlight. */
.doc .staff-intro b {
  color: #a8e6a1;
}

/* The opening note: first line indented. */
.doc .staff-intro {
  text-indent: 2em;
}

/* The signature, placed as on a letter: it starts a little past the middle of the page, well
   clear of the right edge. margin-left is where it starts. */
.doc .staff-signature {
  /* Pulled up toward the note. More negative = closer. */
  margin-top: -10px;
  margin-left: 62%;
  font-weight: 700;
  text-align: left;
}

.doc .staff-name {
  margin: 0;
  font-size: 22px;
}

.doc .staff-role {
  margin: 2px 0 10px;
  color: #9c1f1f;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* Media page: pictures and clips with the brush-stroke edges, in a grid. */
.media-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 36px 32px;
  align-items: start;
}

.media-item {
  margin: 0;
}

.media-grid .clip {
  width: 100%;
  margin: 0;
}

.media-item figcaption {
  margin-top: 8px;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.06em;
  text-align: center;
  text-transform: uppercase;
}

/* ---------- Page changes: the content under the banner fades out, then in ---------- */

/* Every page fades its content in as it opens... */
main,
.site-footer {
  animation: page-in 0.35s ease;
  transition: opacity 0.25s ease;
}

@keyframes page-in {
  from { opacity: 0; }
}

/* ...and it fades out while the next page is fetched (transition.js adds the class, then
   takes it away again once the new content is in place). */
.page-leaving main,
.page-leaving .site-footer {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  main,
  .site-footer {
    animation: none;
  }
}

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

.site-footer {
  padding: 26px 6vw 34px;
  border-top: 2px solid #a67c52;
  font-family: "Montserrat", Arial, sans-serif;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.06em;
  text-align: center;
  text-transform: uppercase;
}

.site-footer-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px 26px;
}

.site-footer a {
  color: #000;
  text-decoration: none;
}

.site-footer a:hover,
.site-footer a:focus-visible {
  color: #c62828;
}

.site-footer-copy {
  margin: 12px 0 0;
  font-weight: 500;
}

/* Narrow screens: no room for columns, so the character, headline and content stack. */
@media (max-width: 760px) {
  .feature {
    grid-template-columns: 1fr;
    row-gap: 24px;
    padding: 40px 6vw;
  }

  .feature-char {
    justify-self: start;
    margin-right: 0;
    width: 110px;
  }

  .feature-heading {
    width: auto;
  }

  .feature-content {
    margin-left: 0;
  }

  .feature-title {
    white-space: normal;
  }
}

/* Narrow screens: there is no room for the nav bar beside the logo, so it becomes a strip across
   the whole top and the logo sits below it. */
@media (max-width: 960px) {
  .site-nav {
    /* a touch smaller on phones so all the links stay on one line */
    font-size: min(11px, 2.1vw);
    letter-spacing: 0.02em;
  }

  .site-nav a {
    padding: 0.75em 0.45em;
  }
}

/* Tall, narrow screens: the picture's own torn edge is in view, so use that one. */
@media (max-aspect-ratio: 1/1) {
  .hero {
    --tear: 3.7vh;
  }

  .hero-banner {
    object-position: center bottom;
  }

  .hero-cut::after,
  .hero-pic::after {
    display: none;
  }
}

/* ---------- Login / Register panel ---------- */

@property --logo-basis {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}

/* Opening (auth.js adds "auth-open"): over 1 second the banner grows to fill the window and the
   page content fades away; the menu then fades in. Closing ("auth-closing") is the same in reverse. */
.auth-open body,
.auth-closing body {
  transition: --hero-h 1s ease, --logo-basis 1s ease;
}

.auth-open body {
  --hero-h: 100vh;
  --logo-basis: 34vh;
}

html.auth-open {
  overflow: hidden;
}

/* With the banner filling the window, the whole picture would show, including the white torn
   strip painted into its bottom. Drawing it a little taller pushes that strip out of view. */
.hero-banner {
  transition: height 1s ease;
}

.auth-open .hero-banner {
  height: 124%;
}

.auth-open main,
.auth-open .site-footer,
.auth-closing main,
.auth-closing .site-footer {
  transition: opacity 1s ease;
}

.auth-open main,
.auth-open .site-footer {
  opacity: 0;
  pointer-events: none;
}

.auth-open .continue {
  display: none;
}

/* The menu sits under the nav bar. Closed, it takes up no room. */
.auth-panel {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  gap: 14px;
  max-width: 92vw;
  max-height: 0;
  margin-top: 0;
  overflow: hidden;
  opacity: 0;
  visibility: hidden;
  transition: max-height 1s ease, margin-top 1s ease, opacity 0.3s ease, visibility 0s 1s;
}

/* It makes room for itself during the second the banner grows, and fades in at the end of it. */
.auth-open .auth-panel {
  max-height: 60vh;
  margin-top: 2.5vh;
  opacity: 1;
  visibility: visible;
  transition: max-height 1s ease, margin-top 1s ease, opacity 0.3s ease 0.7s, visibility 0s;
}

/* Same dark grey, half see-through box as the corner Login / Register box. */
.auth-box {
  width: min(340px, 86vw);
  padding: 18px 22px 16px;
  background: rgba(40, 40, 40, 0.6);
  color: #fff;
  font-family: "Montserrat", Arial, sans-serif;
  font-weight: 500;
  font-size: 13px;
  text-align: center;
}

.auth-title {
  margin: 0 0 12px;
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 400;
  font-size: 18px;
  letter-spacing: 0.22em;
  text-shadow: 0 0.08em 0.08em rgba(0, 0, 0, 0.95), 0 0.14em 0.3em rgba(0, 0, 0, 0.8);
}

.auth-box input {
  display: block;
  width: 100%;
  margin: 0 0 8px;
  padding: 9px 10px;
  border: 0;
  border-radius: 0;
  background: rgba(255, 255, 255, 0.92);
  color: #000;
  font: inherit;
  /* The page switches text selection off; typing boxes need it back. */
  -webkit-user-select: text;
  user-select: text;
}

.auth-error {
  min-height: 1.4em;
  margin: 0 0 6px;
  color: #ffb4b4;
  font-size: 12px;
  font-weight: 700;
}

.auth-submit {
  display: block;
  width: 100%;
  padding: 0.8em 1em;
  border: 0;
  background: #000;
  color: #fff;
  font: inherit;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-decoration: none;
  text-transform: uppercase;
  cursor: pointer;
}

.auth-submit:hover,
.auth-submit:focus-visible {
  color: #a8e6a1;
}

.auth-submit:disabled {
  opacity: 0.6;
  cursor: default;
}

.auth-name {
  margin: 0 0 12px;
  font-weight: 700;
  font-size: 16px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.auth-alt {
  margin: 12px 0 0;
  font-size: 12px;
}

.auth-box a:not(.auth-submit),
.auth-back {
  color: #fff;
  text-decoration: underline;
}

.auth-box a:not(.auth-submit):hover,
.auth-back:hover {
  color: #a8e6a1;
}

.auth-back {
  margin-top: 10px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

@media (prefers-reduced-motion: reduce) {
  .auth-open body,
  .auth-closing body,
  .auth-panel,
  .auth-open .auth-panel {
    transition: none;
  }
}

/* The second button in the menu (Download Game, under Launch Game). */
.auth-second {
  margin-top: 8px;
  background: rgba(0, 0, 0, 0.55);
}

/* The current event, shown to the left of the menu for signed-in players. */
.auth-event-pill {
  display: inline-block;
  margin-bottom: 8px;
  padding: 3px 12px;
  background: #d4a84b;
  color: #000;
  font-weight: 700;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.auth-event-pill.is-live {
  background: #a8e6a1;
}

.auth-event-pill.is-ended {
  background: #9aa3ad;
}

.auth-event .auth-title {
  margin-bottom: 6px;
}

.auth-event-date {
  margin: 0 0 8px;
  color: #ffb4b4;
  font-size: 12px;
  font-weight: 700;
}

.auth-event-text {
  max-height: 22vh;
  margin: 0 0 10px;
  overflow-y: auto;
  line-height: 1.5;
}

.auth-event-count {
  margin: 0;
  font-weight: 700;
  font-size: 15px;
  font-variant-numeric: tabular-nums;
}

/* The small person icon before the username in the corner box. */
.account-icon {
  width: 1.05em;
  height: 1.05em;
  margin-right: 0.5em;
  vertical-align: -0.12em;
  fill: currentColor;
}

/* Small line inside a menu button, under its main words: "(Recommended)". */
.auth-note {
  display: block;
  margin-top: 2px;
  font-weight: 500;
  font-size: 10px;
  letter-spacing: 0.08em;
}

/* The player's character portrait in the menu, under their name: the game's 128px picture,
   kept pixel-sharp. */
.auth-avatar {
  display: block;
  width: 128px;
  height: 128px;
  margin: 0 auto 12px;
  image-rendering: pixelated;
}

.auth-avatar[hidden] {
  display: none;
}

/* The portrait and the full-body character, side by side under the player's name. */
.auth-looks {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 4px;
  margin: 0 0 12px;
}

.auth-looks[hidden],
.auth-character[hidden] {
  display: none;
}

.auth-looks .auth-avatar {
  margin: 0;
}

/* One 128px frame of the game's sprite, pixel-sharp. The figure only fills the middle of the
   frame, so the frame is trimmed at the sides. */
.auth-character {
  display: block;
  width: 72px;
  height: 128px;
  object-fit: cover;
  object-position: center;
  image-rendering: pixelated;
}

/* Portrait centered under the name; the character stands at double size beside it, drawn in
   front and overlapping the portrait's right edge a little. The character's picture is one
   128px sprite frame shown at 256px; the figure fills only the middle of that frame.
   "left" on .auth-character moves the figure sideways (smaller = further over the portrait). */
.auth-looks {
  position: relative;
  display: block;
  height: 200px;
  margin: 0 0 12px;
}

.auth-looks .auth-avatar {
  position: absolute;
  left: calc(50% - 64px);
  top: 62px;
}

.auth-looks .auth-character {
  position: absolute;
  left: calc(50% - 62px);
  top: -52px;
  width: 256px;
  height: 256px;
  object-fit: fill;
  pointer-events: none;
}

/* Bigger portrait: double size (256px), like the character, so both keep whole pixels. The
   character stands at its right, feet level with the portrait's bottom edge. */
.auth-looks {
  height: 256px;
}

.auth-looks .auth-avatar {
  left: calc(50% - 128px);
  top: 0;
  width: 256px;
  height: 256px;
}

.auth-looks .auth-character {
  left: calc(50% - 30px);
  top: 6px;
}

/* A refresh with the menu open: it comes back already open, without replaying the animation. */
.auth-instant body,
.auth-instant main,
.auth-instant .site-footer,
.auth-instant .hero-banner,
.auth-instant .auth-panel {
  transition: none !important;
}

/* ---------- Media page: admin tools (only shown to administrators; see media.js) ---------- */

.media-admin {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  margin: 0 0 22px;
}

.media-add {
  cursor: pointer;
}

.media-status {
  font-weight: 700;
  font-size: 13px;
}

.media-tools {
  display: flex;
  gap: 6px;
  margin-top: 8px;
}

.media-tools input {
  flex: 1;
  min-width: 0;
  padding: 6px 8px;
  border: 2px solid #000;
  background: rgba(255, 250, 235, 0.8);
  font: inherit;
  font-size: 13px;
  -webkit-user-select: text;
  user-select: text;
}

.media-tools button {
  padding: 6px 10px;
  border: 0;
  background: #000;
  color: #fff;
  font: inherit;
  font-weight: 700;
  font-size: 12px;
  cursor: pointer;
}

.media-tools button:hover {
  background: #c62828;
}

/* ---------- Media page: one big item with a strip of thumbnails under it (media.js) ---------- */

.media-viewer {
  max-width: 980px;
  margin: 0 auto;
}

.media-stage .media-stage-clip {
  width: 100%;
  margin: 0;
}

.media-caption-text {
  margin: 10px 0 0;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.06em;
  text-align: center;
  text-transform: uppercase;
}

.media-strip {
  position: relative;
  margin-top: 18px;
}

.media-strip-track {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  scrollbar-width: none;
}

.media-strip-track::-webkit-scrollbar {
  display: none;
}

.media-thumb {
  position: relative;
  flex: none;
  width: 148px;
  height: 92px;
  padding: 0;
  border: 3px solid transparent;
  background: #000;
  cursor: pointer;
  opacity: 0.7;
  transition: opacity 0.15s, border-color 0.15s;
}

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

.media-thumb:hover {
  opacity: 1;
}

.media-thumb.is-current {
  border-color: #000;
  opacity: 1;
}

/* A small play mark on clip thumbnails. */
.media-thumb.is-video::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  border-left: 14px solid #fff;
  border-top: 9px solid transparent;
  border-bottom: 9px solid transparent;
  transform: translate(-40%, -50%);
  filter: drop-shadow(0 1px 2px #000);
}

/* The strip's ends: pointing at one scrolls that way. Only there when the strip can scroll that way. */
.media-strip-edge {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 70px;
  display: none;
  cursor: pointer;
}

.media-strip-edge.is-left {
  left: 0;
  background: linear-gradient(to right, rgba(233, 211, 164, 0.95), rgba(233, 211, 164, 0));
}

.media-strip-edge.is-right {
  right: 0;
  background: linear-gradient(to left, rgba(233, 211, 164, 0.95), rgba(233, 211, 164, 0));
}

.media-strip.can-left .media-strip-edge.is-left,
.media-strip.can-right .media-strip-edge.is-right {
  display: block;
}

/* An arrow in each end zone. */
.media-strip-edge::after {
  content: "";
  position: absolute;
  top: 50%;
  border-top: 12px solid transparent;
  border-bottom: 12px solid transparent;
  transform: translateY(-50%);
}

.media-strip-edge.is-left::after {
  left: 10px;
  border-right: 14px solid #000;
}

.media-strip-edge.is-right::after {
  right: 10px;
  border-left: 14px solid #000;
}

.media-viewer .media-tools {
  max-width: 600px;
  margin: 10px auto 0;
}

/* Media page fits the window without scrolling: the MEDIA title sits at the left, beside the
   viewer, and the big item is sized so that it, its caption and the thumbnail strip fit in the
   height left under the banner. 270px is everything else on the page (padding, strip, caption,
   footer); raise it if anything still spills off the bottom. */
.page:has(.media-viewer) {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  column-gap: 44px;
  max-width: 1500px;
}

.page:has(.media-viewer) .page-title {
  margin: 0;
}

.media-viewer {
  max-width: none;
  margin: 0;
}

.media-stage .media-stage-clip {
  width: 100%;
  max-width: calc((72vh - 270px) * var(--ratio, 16 / 9));
  margin: 0 auto;
}

@media (max-width: 760px) {
  .page:has(.media-viewer) {
    display: block;
  }

  .page:has(.media-viewer) .page-title {
    margin: 0 auto 16px;
  }
}

/* Thumbnails centered under the big item (auto margins keep a scrolling strip's start reachable),
   and the big item a little larger now that it has the room. */
.media-strip-track > :first-child {
  margin-left: auto;
}

.media-strip-track > :last-child {
  margin-right: auto;
}

.media-stage .media-stage-clip {
  max-width: calc((72vh - 235px) * var(--ratio, 16 / 9));
}

/* ---------- Media page, final layout ----------
   Left column: the MEDIA title with the thumbnails under it, running down.
   Right column: the big item, as large as fits the window, with its caption under it.
   The viewer's own box steps aside (display: contents) so its parts can sit in the page's grid.
   190px in the max-width line is everything else stacked above and below the big item
   (page padding, caption, footer); raise it if anything spills off the bottom. */
.page:has(.media-viewer) {
  grid-template-columns: 200px minmax(0, 1fr);
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "title stage"
    "strip stage"
    ".     under";
  column-gap: 36px;
  max-width: 1600px;
}

.page:has(.media-viewer) .page-title { grid-area: title; justify-self: start; }
.media-viewer { display: contents; }
.media-viewer .media-stage { grid-area: stage; }
.media-viewer .media-caption-text,
.media-viewer .media-tools { grid-area: under; }

.media-stage .media-stage-clip {
  max-width: calc((72vh - 190px) * var(--ratio, 16 / 9));
}

/* The thumbnails, one under another, filling the left column; they scroll when they overflow. */
.media-viewer .media-strip {
  grid-area: strip;
  align-self: stretch;
  position: relative;
  min-height: 0;
  margin-top: 18px;
}

.media-viewer .media-strip-track {
  flex-direction: column;
  max-height: calc(72vh - 190px - 80px);
  overflow-x: hidden;
  overflow-y: auto;
}

.media-viewer .media-strip-track > :first-child,
.media-viewer .media-strip-track > :last-child {
  margin: 0;
}

.media-viewer .media-thumb {
  width: 100%;
  height: 112px;
}

/* The scroll zones sit at the top and bottom of the column instead of its ends. */
.media-viewer .media-strip-edge {
  left: 0;
  right: 0;
  width: auto;
  height: 46px;
}

.media-viewer .media-strip-edge.is-left {
  top: 0;
  bottom: auto;
  background: linear-gradient(to bottom, rgba(233, 211, 164, 0.95), rgba(233, 211, 164, 0));
}

.media-viewer .media-strip-edge.is-right {
  top: auto;
  bottom: 0;
  background: linear-gradient(to top, rgba(233, 211, 164, 0.95), rgba(233, 211, 164, 0));
}

.media-viewer .media-strip-edge::after {
  left: 50%;
  right: auto;
  top: auto;
  border: 0;
  border-left: 12px solid transparent;
  border-right: 12px solid transparent;
  transform: translateX(-50%);
}

.media-viewer .media-strip-edge.is-left::after {
  top: 8px;
  border-bottom: 14px solid #000;
}

.media-viewer .media-strip-edge.is-right::after {
  bottom: 8px;
  border-top: 14px solid #000;
}

@media (max-width: 760px) {
  .page:has(.media-viewer) {
    display: block;
  }

  .media-viewer { display: block; }

  .media-viewer .media-strip-track {
    flex-direction: row;
    max-height: none;
    overflow-x: auto;
    overflow-y: hidden;
  }

  .media-viewer .media-thumb {
    width: 148px;
    height: 92px;
  }
}

/* The big item sits right beside the thumbnails, and uses the height that was still spare. */
.page:has(.media-viewer) {
  grid-template-columns: 200px auto;
  justify-content: center;
}

.media-stage .media-stage-clip {
  max-width: calc((72vh - 160px) * var(--ratio, 16 / 9));
  width: calc((72vh - 160px) * var(--ratio, 16 / 9));
}

.media-viewer .media-strip-track {
  max-height: calc(72vh - 160px - 80px);
}

/* The big item has a FIXED area that never changes size or moves, whichever item is shown:
   --stage-h tall and 16:9 wide (or narrower on a narrow window). Each picture or clip is
   fitted inside that area at its own shape, never stretched and never larger than the area,
   and centered in it. The thumbnails, title and caption therefore never move either. */
.page:has(.media-viewer) {
  --stage-h: calc(72vh - 160px);
  --stage-w: min(calc(var(--stage-h) * 16 / 9), calc(100vw - 12vw - 236px));
  grid-template-columns: 200px var(--stage-w);
}

.media-viewer .media-stage {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--stage-w);
  height: var(--stage-h);
}

.media-stage .media-stage-clip {
  flex: none;
  width: min(var(--stage-w), calc(var(--stage-h) * var(--ratio, 16 / 9)));
  max-width: none;
  margin: 0;
}

/* The caption line keeps its height even when empty, so nothing below it jumps. */
.media-viewer .media-caption-text {
  min-height: 1.4em;
}

/* The page must never scroll here, and nothing may move between items: a little more room is
   left for the caption line (which is now always present), and the scrollbar's space is kept
   either way so the layout cannot shift sideways by a scrollbar's width. */
html:has(.media-viewer) {
  scrollbar-gutter: stable;
}

.page:has(.media-viewer) {
  --stage-h: calc(72vh - 185px);
}

.media-viewer .media-strip-track {
  max-height: calc(var(--stage-h) - 80px);
}

/* The MEDIA title centered above the thumbnails. */
.page:has(.media-viewer) .page-title {
  justify-self: center;
  text-align: center;
}

/* ---------- Music play/pause button, top left corner of the banner (music.js) ---------- */

/* Same dark grey, half see-through box as the Login / Register box in the other corner. */
.music-toggle {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 34px;
  padding: 0;
  border: 0;
  background: rgba(40, 40, 40, 0.5);
  color: #fff;
  cursor: pointer;
  opacity: 0.75;
  transition: opacity 0.15s, color 0.15s;
}

.music-toggle:hover,
.music-toggle:focus-visible {
  opacity: 1;
  color: #a8e6a1;
}

.music-toggle svg {
  width: 16px;
  height: 16px;
  fill: currentColor;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.8));
}

/* Showing the pause sign while music plays, the play sign while it does not. */
.music-toggle .music-icon-pause,
.music-toggle.is-playing .music-icon-play {
  display: none;
}

.music-toggle.is-playing .music-icon-pause {
  display: block;
}

/* ---------- Music control: play/pause button with a volume slider beside it ---------- */

/* The button and slider share one dark grey box in the top left corner. The slider stays
   tucked away and fades and slides out while the box is pointed at or tabbed into. */
.music-control {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  background: rgba(40, 40, 40, 0.5);
}

.music-control .music-toggle {
  position: static;
  background: none;
}

.music-volume {
  width: 0;
  margin: 0;
  opacity: 0;
  accent-color: #a8e6a1;
  cursor: pointer;
  transition: width 0.25s ease, margin 0.25s ease, opacity 0.25s ease;
}

.music-control:hover .music-volume,
.music-control:focus-within .music-volume {
  width: 96px;
  margin: 0 12px 0 2px;
  opacity: 1;
}

/* ---------- Music control, refined ----------
   One slim dark glass strip in the corner. While music plays, three small bars dance in the
   button (pointing at it shows the pause sign); when it is off, a play sign. The volume slider
   is a hairline track filled in pastel green, with a small round knob, and slides out beside the
   button while the strip is pointed at. */
.music-control {
  height: 34px;
  padding: 0 2px;
  border-bottom-right-radius: 10px;
  background: rgba(16, 16, 18, 0.42);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.06) inset;
  transition: background 0.25s ease;
}

.music-control:hover,
.music-control:focus-within {
  background: rgba(16, 16, 18, 0.62);
}

.music-control .music-toggle {
  width: 36px;
  height: 34px;
  opacity: 0.85;
}

.music-control .music-toggle svg {
  width: 13px;
  height: 13px;
}

/* The dancing bars. */
.music-bars {
  display: none;
  align-items: flex-end;
  gap: 2px;
  height: 12px;
}

.music-bars i {
  display: block;
  width: 3px;
  height: 100%;
  border-radius: 1px;
  background: currentColor;
  transform-origin: bottom;
  animation: music-bar 0.9s ease-in-out infinite alternate;
}

.music-bars i:nth-child(2) { animation-duration: 0.7s; animation-delay: -0.3s; }
.music-bars i:nth-child(3) { animation-duration: 1.1s; animation-delay: -0.6s; }

@keyframes music-bar {
  from { transform: scaleY(0.25); }
  to { transform: scaleY(1); }
}

/* Playing: the bars, or the pause sign while pointed at. Not playing: the play sign. */
.music-toggle.is-playing .music-bars { display: flex; }
.music-toggle.is-playing .music-icon-pause { display: none; }
.music-toggle.is-playing:hover .music-bars,
.music-toggle.is-playing:focus-visible .music-bars { display: none; }
.music-toggle.is-playing:hover .music-icon-pause,
.music-toggle.is-playing:focus-visible .music-icon-pause { display: block; }

/* The slider. */
.music-volume {
  -webkit-appearance: none;
  appearance: none;
  height: 3px;
  border-radius: 3px;
  background: linear-gradient(to right, #a8e6a1 var(--fill, 20%), rgba(255, 255, 255, 0.22) var(--fill, 20%));
  outline: none;
}

.music-volume::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 0 3px rgba(168, 230, 161, 0.25), 0 1px 3px rgba(0, 0, 0, 0.6);
  cursor: pointer;
}

.music-volume::-moz-range-thumb {
  width: 11px;
  height: 11px;
  border: 0;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 0 3px rgba(168, 230, 161, 0.25), 0 1px 3px rgba(0, 0, 0, 0.6);
  cursor: pointer;
}

.music-volume::-moz-range-track {
  background: transparent;
}

.music-volume:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 4px rgba(168, 230, 161, 0.55);
}

.music-control:hover .music-volume,
.music-control:focus-within .music-volume {
  width: 88px;
  margin: 0 14px 0 4px;
}

@media (prefers-reduced-motion: reduce) {
  .music-bars i { animation: none; transform: scaleY(0.6); }
}

/* The slider pops out when the play/pause button is pointed at: it springs open with a small
   overshoot and fades in, and lingers for a moment after the pointer leaves so it is easy to
   reach. (The button and slider share one box, so moving onto the slider keeps it open.) */
.music-volume {
  transform: scaleX(0.6);
  transform-origin: left center;
  transition:
    width 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) 0.45s,
    margin 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) 0.45s,
    transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) 0.45s,
    opacity 0.2s ease 0.45s;
}

.music-control:hover .music-volume,
.music-control:focus-within .music-volume {
  transform: scaleX(1);
  transition-delay: 0s;
}
