/* establishrecords.com — stylesheet */

@font-face {
  font-family: 'Kai ERL';
  src: url('/assets/fonts/kai-erl-bold.woff2?v=12279057') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: block;
}

* { box-sizing: border-box; }

html { background: #000; -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: #000;
  color: #ececec;
  font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif;
  font-weight: 300;
  min-height: 100vh;
  min-height: 100svh;
  overflow: hidden;
}

a { color: inherit; }

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

/* ---------- background video ---------- */

/* one per orientation, stacked; only the facing one is opaque, and the swap
   between them is a dissolve of the same moment framed two ways */
.bg {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: #000;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.bg.is-on { opacity: 1; }

/* The gradient stage, shown when the visitor switches the footage off. */
.grad {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: none;
  background: #0a0a0c;
}

body.video-off .grad { display: block; }
body.video-off .bg { display: none; }

/* Rotation cover: 2s hold while the other cut takes over. */
.blackout {
  position: fixed;
  inset: 0;
  z-index: 1;
  background: #000;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.55s ease;
}

/* on goes up instantly — it is the veil the page starts behind, and any fade
   in would show the frame it is meant to be hiding */
.blackout.on { opacity: 1; transition: none; }

/* ---------- the two marks: drawn white, blended as "difference",
     so each pixel renders as the full inversion of the footage. ---------- */

:root {
  /* the E's size — the whole mark is fractions of this. Scaled to 1.25x its
     original (25% larger) on the same responsive curve; the © line opts out
     of the bump by dividing that factor back out (see .rights). */
  --logo-size: calc(clamp(36px, calc(6.79vmin - 1.43px), 77px) * 1.25);
  /* the bottom edge the lockup sits on; the switch is lifted off it */
  --edge-bottom: max(31px, calc(env(safe-area-inset-bottom) + 25px));
  --edge-right: max(29px, calc(env(safe-area-inset-right) + 5px));
  /* how much of a device pixel the thin lines get; the // keeps a full one */
  --hair: 0.35;
  /* the motto's || is thinner again than the rest, and tighter */
  --hair-dbl: 0.22;
  /* the gap between the two hairlines of the ||. Tightened 0.035 -> 0.026;
     below about 0.02 the pair stop reading as two lines at 1x and merge into
     one thickish rule, so this is close to the floor. NOTE: fitMotto reads this
     back off the rail's used width, because getPropertyValue on a custom
     property returns unresolved calc() text -- see the note there. */
  --dbl: calc(var(--logo-size) * 0.026);
  /* the "tap for more" nudge is a chalk arrow a little under three E's across */
  --hint-size: calc(var(--logo-size) * 2.7);
}

.lockup {
  position: fixed;
  z-index: 2;
  left: max(29px, calc(env(safe-area-inset-left) + 5px));
  bottom: var(--edge-bottom);
  display: grid;
  grid-template-areas: "cn e art" ". en .";
  grid-template-columns: auto auto auto;
  column-gap: calc(var(--logo-size) * 0.16);
  justify-items: start;
  color: #fff;
  mix-blend-mode: difference;
  opacity: 0;
  transition: opacity 1.5s ease;
  user-select: none;
  pointer-events: none;
}

.lockup.in { opacity: 1; }

/* while the page is held shut the lockup is invisible but its buttons are
   not: without this a blind tap opens the panel behind the veil, and the
   visitor arrives to an expanded menu they never asked for */
.lockup:not(.in) .logo-btn { pointer-events: none; }

.cn-v {
  grid-area: cn;
  align-self: start;
  position: relative;
  /* Every nudge in the lockup is a fraction of the E, never a flat pixel
     count. A flat one is a different nudge at every size: 4px off a 59.68px E
     is the same optical shift the design was drawn with, but off the 36px E a
     phone clamps to it is half again as much, and it eats the gap the stroke
     is measured from. At 390 wide that 4px took the span between the
     characters and the E from 5.2px to 3.75px, so both arms came in ~28%
     tighter than the drawing — which is why the phone never matched. */
  left: calc(var(--logo-size) * 0.067);
  /* Measured: the E's ink starts 0.16em below its line box and the Kai
     glyph 0.01em below its own, so 0.15em levels the two tops; 0.0335em more
     settles the last of it. */
  --pad: calc(var(--logo-size) * 0.1835);
  --cn-gap: 0.52em;
  padding-top: var(--pad);
  /* the column hangs out of a zero-height box so its own length never sets the
     row's: at 0.52em of tracking it was within half a pixel of outgrowing the
     E at 320px wide, which would have shoved the wordmark down */
  height: 0;
  font-family: 'Kai ERL', 'Kaiti TC', 'DFKai-SB', 'BiauKai', serif;
  font-weight: 700;
  font-size: calc(var(--logo-size) * 0.15);
  line-height: 1;
  text-align: center;
}

.cn-v b {
  display: flex;
  flex-direction: column;
  gap: var(--cn-gap);
  font-weight: inherit;
}

/* One rule for both names, and one stroke to draw it: down the right of the
   four characters, round the corner, out along the top of the wordmark. It was
   two boxes meeting at that corner, which is not the same thing — abutting on
   a centreline they double-paint there and read as two lines crossing. A path
   turns instead, and one dash offset draws the whole run continuously.

   The width is honest here too. A sub-pixel width on a filled box is a no-op:
   Chromium rounds it up to a whole device pixel and paints it solid, so 0.3px
   and 1px rasterise identically. A stroke goes through the antialiaser. */
.name-rule {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}

/* The dash array is one length on and one length off, so the offset alone says
   where the stroke is: +len is waiting before the characters, 0 is drawn, and
   -len has walked it off past the wordmark. Which end it comes in at follows
   from which side of zero it starts on, so aimRule() in the runtime moves it
   between the three and the duration comes with each move. */
.name-rule path {
  stroke: currentColor;
  stroke-width: var(--hair);
  fill: none;
  /* a plain number, NOT var(--len): WebKit will not run a transition whose
     value resolves through a custom property -- it snaps to the end instead of
     drawing. aimRule() sets this inline as a number on every move, so the base
     only matters before the first fit; keeping it var-free is what lets Safari
     actually animate the draw/undraw. */
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 0.96s cubic-bezier(0.32, 0, 0.24, 1);
}

.cn-v i { display: block; font-style: normal; }

.en-sub b { display: block; font-weight: inherit; width: max-content; }

.logo-e {
  grid-area: e;
  position: relative;
  margin: 0;
  font-family: 'Rampart One', 'Open Sans', cursive;
  font-weight: 400;
  font-size: var(--logo-size);
  line-height: 1.05;
}

.en-sub {
  grid-area: en;
  position: relative;
  margin-top: calc(var(--logo-size) * 0.045);
  margin-left: calc(var(--logo-size) * 0.0335);
  font-family: 'Cormorant Infant', 'Cormorant Garamond', Georgia, serif;
  font-weight: 700;
  font-size: calc(var(--logo-size) * 0.15);
  /* 0.20em (the original) read loose; this is the compressed setting. The //
     menu no longer follows this number -- see width:0 below -- so it can be
     tuned freely without the menu moving. */
  letter-spacing: 0.15em;
  white-space: nowrap;
  /* The wordmark must NOT size its grid column. It shares column 2 with the E
     and is the wider of the two, so the column -- and therefore the artists'
     column beside it, and the // menu in it -- was being set by the wordmark:
     every time the tracking was tightened the menu slid left toward the E. A
     zero-width box with the words overflowing to the right keeps the wordmark
     exactly where it renders now (it still starts at the column's edge and is
     nowrap), while the column is sized by the E alone -- so the menu opens to
     the same place whatever the tracking is. fitRules measures the inner <b>
     for the stroke's far end, since this box no longer has the words' width. */
  width: 0;
}

/* ---------- the names are drawn back like a drape ----------
   The letters arrive along the mark's own axis: down the Chinese column,
   across the English wordmark. The characters lead both ways -- in first and
   out first -- so the pair always reads in one direction.

   Transitions, not keyframes. A keyframe cannot be interrupted: changing
   animation-name restarts it at its own 'from', so a visitor who touched the
   page half way through a drape saw it snap shut and play again from the top.
   A transition always starts from the value that is on screen, which is the
   whole of what interruptible means here. */

.cn-v b {
  clip-path: inset(0 0 100% 0);
  transition: clip-path 0.62s cubic-bezier(0.32, 0, 0.24, 1);
}

.en-sub b {
  clip-path: inset(0 100% 0 0);
  transition: clip-path 0.62s cubic-bezier(0.32, 0, 0.24, 1);
}

/* The entrance hands over at the corner too, the same way the exit does below.
   Here the stroke draws from the Chinese end over DRAW_MS (960ms), so the arm
   in play first is the VERTICAL one -- 0.384-0.392 of the path -- and the eased
   stroke reaches the corner at 0.285 of the time, i.e. 273-276ms across every
   viewport. So the column has until 0.28s and the wordmark takes the rest.
   Before this the column ran 0-0.62s and the wordmark 0.34-0.96s, so the
   wordmark began while nearly a third of the column was still arriving. */
/* ---- the names are driven by the STROKE'S TIP, not by a clock ----
   The stroke keeps its own non-linear curve; the names take their motion from
   where its tip has actually reached along the arm running beside them. The
   path is an L, and each name is parallel to one of its two arms: the column
   beside the vertical arm, the wordmark under the horizontal one. So each name
   reveals in proportion to the tip's travel down its own arm, and the two read
   as one motion instead of two things timed to finish together.

   The curves below are that relationship solved and fitted: for a name spanning
   path fraction [a,b], its easing is the stroke's own progress restricted to
   that span and renormalised, P(t) mapped through [a,b]. They track the tip to
   within 1.6% at every instant. Their shapes are not arbitrary and should not be
   hand-tweaked -- if the stroke's curve, DRAW_MS/UNDRAW_MS, or the arm ratio
   changes, re-solve them (§9).

   ENTRANCE: the tip runs the vertical arm first, so the column leads. It starts
   slow because the tip does (6% in at a quarter of its time), then accelerates
   with it; the wordmark starts fast (55% at a quarter) because the tip rounds
   the corner already at speed. */
body.names-in .cn-v b {
  clip-path: inset(-6% -6% -6% -6%);
  transition-timing-function: cubic-bezier(0.53, 0, 0.85, 0.76);
  transition-duration: 0.40s;
  transition-delay: 0s;
}
body.names-in .en-sub b {
  clip-path: inset(-6% -6% -6% -6%);
  transition-timing-function: cubic-bezier(0.11, 0.29, 0.33, 1);
  transition-duration: 1.00s;
  transition-delay: 0.40s;
}

/* the close (a) leaves from the English side: the wordmark goes first, the
   Chinese column last -- the reverse of the entrance, and synced to the stroke,
   whose English end lifts first (aimRule('off', .., 'names')). */
/* The two names hand over at the stroke's corner, so the drape and the stroke
   come apart together instead of drifting.

   The stroke undraws over UNDRAW_MS (760ms) from the English end, and the
   horizontal arm is a measured 0.608-0.616 of the path at every viewport. But
   the stroke is eased (the same cubic-bezier below), so it reaches the corner at
   0.39 of the TIME, not 0.61 of it -- solving 3s^2-2s^3 = 0.613 for the bezier
   gives 296-299ms across the whole range. So the wordmark has until 0.30s, and
   the Chinese column takes the rest.

   Before this the wordmark ran 0-0.5s and the column 0.26-0.76s: the column
   began while a quarter of the wordmark was still on screen, and neither
   matched the arm beside it. */
/* EXIT: the tip recedes from the English end, so the horizontal arm empties
   first and the wordmark leads; the column follows down the vertical arm. Same
   derivation as the entrance, solved for the receding direction. */
body.names-out .en-sub b {
  transition-timing-function: cubic-bezier(0.48, 0.03, 0.51, 0.39);
  transition-duration: 0.30s;
  transition-delay: 0s;
}
body.names-out .cn-v b {
  transition-timing-function: cubic-bezier(0.08, 0.22, 0.34, 1);
  transition-duration: 0.46s;
  transition-delay: 0.30s;
}



/* ---------- everything flies out of the E. ----------
   Each flyer rests where it belongs and, while the panel is shut, is
   translated back onto the logo and shrunk. One ease-out straight to the
   mark, no overshoot, with a half-second fade either way so nothing snaps
   in or out. visibility still carries the hit-testing: a pane at opacity 0
   would otherwise keep swallowing clicks. JS measures the vector into
   --fx/--fy. */

.fly {
  /* travel only: what flies out of the E arrives the size it will stay */
  transform: translate(var(--fx, 0px), var(--fy, 0px));
  opacity: 0;
  visibility: hidden;
  transition: transform 0.42s cubic-bezier(0.33, 0, 0.2, 1),
              opacity 0.5s ease,
              visibility 0s linear 0.5s;
}

body.panel-open .fly {
  transform: none;
  opacity: 1;
  visibility: visible;
  transition: transform 0.42s cubic-bezier(0.33, 0, 0.2, 1),
              opacity 0.5s ease,
              visibility 0s linear 0s;
}

/* measuring pass: resting geometry, never painted */
body.measuring .fly {
  transform: none !important;
  opacity: 0 !important;
  visibility: hidden !important;
  transition: none !important;
}

/* the closed position has to be committed silently before the panel opens,
   or the browser has nothing to animate away from */
body.no-anim .fly,
body.no-anim .redirect,
body.no-anim .artist-cn { transition: none !important; }

.logo-btn {
  font: inherit;
  color: inherit;
  line-height: inherit;
  margin: 0;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  pointer-events: auto;
}

/* the switch rests in the bottom-right corner, mirroring the menu button.
   Measured: the artist row and the E's ink band centre 0.67em above the
   lockup's bottom edge, and the track's own centre is 0.11em above the
   switch's — so 0.56em of lift levels the three. The wordmark, which is
   what sits between that band and the edge, is not part of the line. */
.bg-toggle {
  position: fixed;
  bottom: calc(var(--edge-bottom) + var(--logo-size) * 0.56);
  right: var(--edge-right);
  display: flex;
  align-items: center;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  mix-blend-mode: difference;
  z-index: 3;
}

.bg-toggle .track {
  position: relative;
  display: block;
  width: calc(var(--logo-size) * 0.5);
  height: calc(var(--logo-size) * 0.22);
  border: 1.5px solid #fff;
  border-radius: 999px;
}

.bg-toggle .knob {
  position: absolute;
  top: 50%;
  left: calc(var(--logo-size) * 0.035);
  width: calc(var(--logo-size) * 0.12);
  height: calc(var(--logo-size) * 0.12);
  background: #fff;
  border-radius: 50%;
  transform: translateY(-50%);
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.bg-toggle[aria-checked="true"] .knob {
  transform: translate(calc(var(--logo-size) * 0.225), -50%);
}

/* ---------- artists, one E. clear of the logo so they never sit over
     the English wordmark below ---------- */

.artists {
  grid-area: art;
  align-self: start;
  /* Anchored to the E, not to the wordmark. The column beside the E used to be
     opened up by the wordmark below it, so this margin was a negative step back
     into that column -- and the menu drifted left whenever the wordmark's
     tracking was tightened. The wordmark no longer sizes the column (.en-sub is
     width:0), so the column is the E's own width and this is a straight gap
     past it. 0.40376 puts the // exactly where it sat at the original 0.20em
     tracking: 0.564 of the E's type size clear of the E's right edge, now fixed
     there whatever the tracking does. */
  margin-left: calc(var(--logo-size) * 0.40376);
  /* sit on the band the E's ink occupies, not its line box */
  margin-top: calc(var(--logo-size) * 0.16);
  height: calc(var(--logo-size) * 0.87);
  display: flex;
  align-items: center;
  gap: calc(var(--logo-size) * 0.42);
}

.artist-row {
  display: flex;
  align-items: center;
  height: 100%;
  gap: calc(var(--logo-size) * 0.9);
}

/* two hairlines the height of the E, between it and what the panel opened */
.slash {
  position: relative;
  flex: none;
  align-self: stretch;
  width: calc(var(--logo-size) * 0.24);
}

.slash::before,
.slash::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: currentColor;
  /* rise against run was 3.7:1 at 15deg; half again as vertical is 5.6:1 */
  transform: skewX(-10.1deg);
}

.slash::before { left: 30%; }
.slash::after { left: 56%; }

/* ---------- the motto, in the cell the artists use ----------
   The two are never on screen together: the panel opening shows the names,
   and the names showing is what puts the motto away. */

.motto {
  /* hung off the E itself, not off the grid: the wordmark is what pushes the
     artists' column out, and the wordmark is away whenever this is showing.
     top and height are rewritten by fitMotto() from the E's measured ink. */
  position: absolute;
  left: 100%;
  top: calc(var(--logo-size) * 0.2);
  height: calc(var(--logo-size) * 0.68);
  margin-left: calc(var(--logo-size) * 0.22);
  display: flex;
  align-items: center;
}

/* ---- the || divider is a portal: an overflow-clipped slot the two hairlines
   sink through. They sit at rest filling the slot; translated down they cross
   the slot's bottom line and vanish there (not off-screen), and rise back up
   from that same line. This is the second stage of the motto's motion. ---- */
.motto-rail {
  position: absolute;
  left: 0;
  top: 0;
  /* exactly the divider's height, so the slot's bottom edge sits on the
     divider's own baseline: an invisible HORIZON in open air, not the far edge
     of some larger frame. The pair fall through it and are gone -- the lower
     part of each bar is already past the line while its top is still coming
     down, so it reads as dropping out of sight mid-air rather than sliding out
     of frame. Height is an explicit length (fitMotto sets --motto-h to the E's
     measured ink height), NOT a percentage: Safari would not resolve the
     rail% -> bar% chain and the bars collapsed to nothing. */
  height: var(--motto-h, calc(var(--logo-size) * 0.68));
  width: calc(var(--dbl) + 2px);
  overflow: hidden;
  pointer-events: none;
}

/* The travelling half of the portal: a plain block the height of the divider,
   holding the two hairlines. The rail it sits in is TWICE this tall -- the
   divider occupies the top half and the lower half is runway -- so the sunk
   position is 200% of this block's own height, which puts its top on the rail's
   bottom edge and clips it away. (100% only parks it in the runway, where it is
   still inside the rail and therefore still on screen.) The pair travel that
   whole runway in view before the clip line takes them: the sink.
   The transform lives on this HTML block (percentage transforms on a normal
   element are reliable everywhere) rather than on the SVG or a pseudo-element. */
.motto-slide {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: var(--motto-h, calc(var(--logo-size) * 0.68));
  transform: translate3d(0, 100%, 0);   /* one own-height down = fully under the horizon */
  transition: transform 0.5s cubic-bezier(0.32, 0, 0.24, 1) 0.34s;
}

/* The bars are SVG strokes, not scaled boxes. A 1px box at scaleX(0.22) is a
   0.22px-wide box -- below a device pixel, so Safari dropped it entirely and
   Chrome rendered a ghost. A STROKE goes through the antialiaser at any width
   (§14), which is how the name rule draws its own hairline on every browser.
   fitMotto() sets the two lines' x and y from the measured divider. */
.motto-bars {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.motto-bars line {
  stroke: currentColor;
  stroke-width: var(--hair-dbl);
  vector-effect: non-scaling-stroke;
  shape-rendering: geometricPrecision;
}

/* the words are set into the left of the box, with the right left open */
.motto-box {
  position: relative;                 /* the frame SVG overlays it */
  height: 100%;
  margin-left: calc(var(--logo-size) * 0.34 + var(--dbl));
  /* The frame used to be a box-shadow spread (to get a sub-pixel hairline that
     border-width floors away). Safari renders that spread on only one edge here
     -- box, transform and mix-blend together break it -- so the frame is now an
     SVG <rect> stroke instead, the same antialiased-hairline trick the name
     rule uses, which Safari draws on all four sides. See .motto-frame. */
  border: 0;
  /* the right-hand air is 0.78em less the 5pt asked for, again as the fraction
     that 5pt was on a 36px E, so the box keeps its shape at every size */
  padding: calc(var(--logo-size) * 0.09) calc(var(--logo-size) * 0.594815)
           calc(var(--logo-size) * 0.09) calc(var(--logo-size) * 0.17);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  font-family: 'Figtree', 'Helvetica Neue', Arial, sans-serif;
  font-weight: 600;
  font-size: calc(var(--logo-size) * 0.145);
  line-height: 1.1;
  letter-spacing: 0.02em;
  text-align: left;
  white-space: nowrap;
  /* the box grows out of / retracts into the || -- scaleX from the divider,
     which sits 0.34em to its left, so it collapses onto the divider, not onto
     its own edge. First stage of the motion. */
  transform-origin: calc(var(--logo-size) * -0.34) center;
  transform: scaleX(0);
  opacity: 0;
  transition: transform 0.36s cubic-bezier(0.32, 0, 0.24, 1), opacity 0.24s ease;
}

.motto-box i { display: block; font-style: normal; }

/* the box frame: an SVG rect stroked at the hairline width. It fills the box
   and, because it is a child of the box, scales out of the divider with it on
   the scaleX grow; non-scaling-stroke keeps the line a true hairline through
   that scale rather than squashing it. A stroke goes through the antialiaser on
   every browser (a box-shadow spread does not, on Safari). */
.motto-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}

.motto-frame rect {
  fill: none;
  stroke: currentColor;
  stroke-width: var(--hair);
  vector-effect: non-scaling-stroke;
  /* force uniform antialiasing on every edge: at 1:1 Safari was snapping the
     right edge to a crisp pixel column (so it read brighter than the AA'd
     others) -- geometricPrecision AAs all four the same way */
  shape-rendering: geometricPrecision;
}

/* the motto is its own sequence now, on its own class -- it enters from the
   bare E (the names have already left and returned to the E) and leaves back to
   it, rather than crossfading with the names. */
/* ENTRANCE (motto-on): the || rise from the portal first, then the box expands
   out of them. EXIT (motto-on removed, the rules above): the box retracts into
   the || first, then the || sink back through the portal -- the exact reverse. */
body.motto-on .motto-slide {
  transform: translate3d(0, 0, 0);
  transition-delay: 0s;
}

body.motto-on .motto-box {
  transform: scaleX(1);
  opacity: 1;
  transition-delay: 0.34s;
}

/* ---------- the "tap for more" nudge ----------
   Three quick taps off the E with the menu shut, and a hand-drawn chalk arrow
   blinks in over the logo. It holds for fifteen seconds, then fades -- or
   leaves at once the moment the visitor opens the menu or taps away again.
   Positioned by placeHint() in JS so the arrowhead lands just above the E. */
.hint {
  position: fixed;
  z-index: 4;
  left: -9999px;                 /* parked until placeHint() measures the E */
  top: -9999px;
  width: var(--hint-size);
  height: var(--hint-size);
  color: #fff;
  mix-blend-mode: difference;    /* the mark's own trick: legible on any frame */
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.5s ease; /* the fade out when the nudge is dropped */
}

.hint-arrow {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;             /* the chalk displacement spills its box */
}

.hint-label {
  position: absolute;
  left: 44%;
  top: -1%;
  font-family: 'Caveat', 'Segoe Script', 'Bradley Hand', cursive;
  font-weight: 600;
  font-size: calc(var(--logo-size) * 0.5);
  line-height: 1;
  white-space: nowrap;
  transform: rotate(-5deg);
}

/* the blink-in: three quick flashes, then held on (both fills the end state).
   Removing .hint-on reverts to opacity 0 above, which the transition fades. */
body.hint-on .hint { animation: hint-blink 0.75s linear both; }

@keyframes hint-blink {
  0%   { opacity: 0; }
  8%   { opacity: 1; }
  16%  { opacity: 0; }
  24%  { opacity: 1; }
  32%  { opacity: 0; }
  40%  { opacity: 1; }
  100% { opacity: 1; }
}

/* the tap-away exit: the same rapid flash the arrow entered on, but ending on
   the dark beat -- it blinks and is gone on the last one, rather than fading. */
body.hint-bye .hint { animation: hint-flash-out 0.5s linear both; }

@keyframes hint-flash-out {
  0%   { opacity: 1; }
  16%  { opacity: 0; }
  32%  { opacity: 1; }
  48%  { opacity: 0; }
  64%  { opacity: 1; }
  80%  { opacity: 0; }
  100% { opacity: 0; }
}

/* ---------- and then the mark stands alone, with the line that names who
     owns it. top is written by fitRights() from the E's measured ink, so the
     text sits on the letter's bottom edge rather than near it. ---------- */

.rights {
  /* the far end of the page, mirroring the mark: the lockup sits --edge-right's
     worth in from the left, so the line sits the same in from the right, and the
     two hang off opposite edges at one shared margin. The background switch is
     the only other thing out here and it is hidden through this beat, so nothing
     is in the way of the true edge. top is written by fitRights() from the E's
     measured ink. */
  position: fixed;
  right: var(--edge-right);
  white-space: nowrap;
  font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif;
  font-weight: 300;
  /* the mark grew 25% but this line did not: 0.15 / 1.25 = 0.12 holds the ©
     text at exactly the size it was before the bump */
  font-size: calc(var(--logo-size) * 0.12);
  letter-spacing: 0.04em;
  line-height: 1;
  opacity: 0;
  visibility: hidden;
  transition: opacity 1.5s ease, visibility 0s linear 1.5s;
}

/* in after the motto has cleared */
body.rights-on .rights {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.6s ease 0.45s, visibility 0s linear 0s;
}

/* and out on its own, longer, count */
body.rights-out .rights {
  opacity: 0;
  visibility: visible;
  transition: opacity 1.5s ease, visibility 0s linear 1.5s;
}

.artist { position: relative; display: block; }

.pick {
  display: block;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  pointer-events: auto;
  opacity: 0.55;
  transition: opacity 0.3s ease;
}

.pick.is-active,
.pick:hover,
.pick:focus-visible { opacity: 1; }

.pick img {
  display: block;
  height: calc(var(--logo-size) * 0.87);
  width: auto;
}

/* the way out to the artist's own site, revealed on the first press */
.redirect {
  position: absolute;
  bottom: 100%;
  left: 50%;
  margin-bottom: calc(var(--logo-size) * 0.12);
  display: block;
  pointer-events: auto;
  opacity: 0;
  visibility: hidden;
  transform: translate(-50%, calc(var(--logo-size) * 0.18)) scale(0.3);
  transition: transform 0.34s cubic-bezier(0.33, 0, 0.2, 1),
              opacity 0.5s ease,
              visibility 0s linear 0.5s;
}

.artist.show-link .redirect {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0) scale(1);
  transition: transform 0.34s cubic-bezier(0.33, 0, 0.2, 1),
              opacity 0.5s ease,
              visibility 0s linear 0s;
}

.redirect svg {
  display: block;
  width: calc(var(--logo-size) * 0.26);
  height: calc(var(--logo-size) * 0.26);
}

/* the artist's name in Kai, revealed on a further press — below the mark,
   mirroring the way out that sits above it */
.artist-cn {
  position: absolute;
  top: 100%;
  left: 50%;
  margin-top: calc(var(--logo-size) * 0.12);
  display: flex;
  gap: 0.34em;
  font-family: 'Kai ERL', 'Kaiti TC', 'DFKai-SB', 'BiauKai', serif;
  font-weight: 700;
  /* set by fitRules to the wordmark's own ink height, so the two read the same
     size however the two faces are cut */
  font-size: var(--artist-cn-size, calc(var(--logo-size) * 0.15));
  line-height: 1;
  text-align: center;
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  transform: translate(-50%, calc(var(--logo-size) * -0.18)) scale(0.4);
  transform-origin: center top;
  transition: transform 0.34s cubic-bezier(0.33, 0, 0.2, 1),
              opacity 0.5s ease,
              visibility 0s linear 0.5s;
}

.artist-cn i { display: block; font-style: normal; }

.artist.show-cn .artist-cn {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0) scale(1);
  transition: transform 0.34s cubic-bezier(0.33, 0, 0.2, 1),
              opacity 0.5s ease,
              visibility 0s linear 0s;
}

.logo-btn:focus-visible,
.pick:focus-visible,
.redirect:focus-visible,
.bg-toggle:focus-visible { outline: 1.5px solid #fff; outline-offset: 4px; }

/* ---------- 404 ---------- */

.err-stage {
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 24px;
}

.err-logo {
  margin: 0 0 18px;
  font-family: 'Rampart One', 'Open Sans', cursive;
  font-size: clamp(72px, 16vmin, 130px);
  line-height: 1;
  color: #2e2e2e;
}

.err-note { margin: 0; font-size: 13px; letter-spacing: 0.08em; color: #8a8a8a; }

.err-note a { color: #c9c9c9; }

.site-footer {
  position: fixed;
  left: 16px;
  right: 16px;
  bottom: max(16px, env(safe-area-inset-bottom));
  font-size: 11px;
  letter-spacing: 0.06em;
  color: #5c5c5c;
  text-align: center;
}

.cr-short { display: none; }

@media (max-width: 430px) {
  .cr-full { display: none; }
  .cr-short { display: inline; }
}

@media (prefers-reduced-motion: reduce) {
  .lockup, .fly, .redirect, .artist-cn, .blackout, .motto-box, .motto-slide { transition-duration: 0.01s; }
  /* the drape still opens and closes, it just does not travel */
  .cn-v b, .en-sub b, .name-rule path { transition-duration: 0.01s !important; transition-delay: 0s !important; }
  /* no flashing for the nudge: it fades in and holds, and fades back out on
     dismissal, rather than blinking either way */
  body.hint-on .hint { animation: none; opacity: 1; }
  body.hint-bye .hint { animation: none; opacity: 0; }
}
