/* establishrecords.com — stylesheet */

@font-face {
  font-family: 'Kai ERL';
  src: url('/assets/fonts/kai-erl-bold.woff2?v=8f10fbc8') 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 {
  --logo-size: clamp(36px, calc(6.79vmin - 1.43px), 77px);
  /* 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;
  --dbl: calc(var(--logo-size) * 0.035);
}

.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;
  stroke-dashoffset: var(--len, 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; }

.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);
  letter-spacing: 0.20em;
  white-space: nowrap;
}

/* ---------- 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);
}

body.names-in .cn-v b { clip-path: inset(-6% -6% -6% -6%); transition-delay: 0s; }
body.names-in .en-sub b { clip-path: inset(-6% -6% -6% -6%); transition-delay: 0.34s; }

/* leaving is quicker, and in the same order it arrived */
body.names-out .cn-v b { transition-duration: 0.5s; transition-delay: 0s; }
body.names-out .en-sub b { transition-duration: 0.5s; transition-delay: 0.26s; }



/* ---------- 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;
  /* the grid keeps this clear of the English wordmark below the E; the margin
     is the breathing room past it, less the 20pt asked for — held as the
     fraction that 20pt came to on the phone it was judged on (a 36px E), so
     the same gap now scales instead of shrinking to nothing beside a large E.
     Past 10pt it goes negative: the column the wordmark opens up is wider than
     the room asked for, so the pair steps back into it rather than sitting
     past its edge. */
  margin-left: calc(var(--logo-size) * -0.24074);
  /* 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;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.45s ease, visibility 0s linear 0.45s;
}

/* two hairlines rather than one solid rule; they draw from the middle out and
   run past the box at either end. scaleX because a sub-pixel width on a filled
   box is a no-op — see the name rule above. */
.motto::before,
.motto::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 1px;
  height: 0;
  background: currentColor;
  transform: translateY(-50%) scaleX(var(--hair-dbl));
  transition: height 0.42s cubic-bezier(0.32, 0, 0.24, 1);
}

.motto::before { left: 0; }
.motto::after { left: var(--dbl); }

/* the words are set into the left of the box, with the right left open */
.motto-box {
  height: 100%;
  margin-left: calc(var(--logo-size) * 0.34 + var(--dbl));
  /* border-width is floored at 1px, so the outline is a shadow spread — that
     one does honour sub-pixel and renders as a true hairline on a 2x/3x screen */
  border: 0;
  box-shadow: 0 0 0 calc(1px * var(--hair)) currentColor;
  /* 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;
}

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

/* 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. */
body.motto-on .motto {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.45s ease, visibility 0s linear 0s;
}

body.motto-on .motto::before,
body.motto-on .motto::after { height: 128%; transition-delay: 0.05s; }

/* ---------- 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;
  font-size: calc(var(--logo-size) * 0.15);
  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, .motto::before { 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; }
}
