/* /sandbox — the whole stylesheet.
 *
 * The design is one hairline. #rule is a 1px line 34px from the bottom; every
 * other piece of chrome baseline-aligns to it and sits directly on the render
 * with no panel behind it, legible by text-shadow alone. There are no cards,
 * no gradients, no rounded corners and no shadows-as-decoration anywhere in
 * this file, and nothing here is shared with any other page on the site.
 */

:root {
  --ink: 232, 226, 214;
  --bar: 34px;
  /* text-shadow is load-bearing: it is the only thing separating the UI from a
     bright sky, since nothing has a background. */
  --lift: 0 1px 3px rgba(0, 0, 0, .55), 0 0 1px rgba(0, 0, 0, .9);
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  overflow: hidden;
  background: #0d0f11;
  /* All three are needed on iOS. touch-action alone still rubber-bands the
     page under a pour; see input.js for the third (non-passive preventDefault). */
  overscroll-behavior: none;
  -webkit-text-size-adjust: 100%;
}

body {
  font: 400 11px/1 ui-monospace, "SF Mono", SFMono-Regular, "Roboto Mono", Menlo, Consolas, monospace;
  letter-spacing: .08em;
  color: rgba(var(--ink), .78);
  -webkit-tap-highlight-color: transparent;
}

#gl {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
  cursor: crosshair;
}

#gl.is-orbiting {
  cursor: grabbing;
}

/* ---------------------------------------------------------------- hud shell */

#hud {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  transition: opacity 160ms linear;
}

#hud>* {
  pointer-events: auto;
}

#hud.is-idle> :not(#rule) {
  opacity: .25;
}

#hud.is-hidden {
  opacity: 0;
  visibility: hidden;
}

/* The line. */
#rule {
  position: absolute;
  left: 0;
  right: 0;
  bottom: var(--bar);
  height: 1px;
  background: rgba(var(--ink), .16);
}

/* ------------------------------------------------------------------- back */

#back {
  position: absolute;
  top: 14px;
  left: 16px;
  display: flex;
  gap: .6em;
  align-items: baseline;
  color: rgba(var(--ink), .4);
  text-decoration: none;
  text-shadow: var(--lift);
  transition: color 120ms linear;
}

#back span {
  opacity: 0;
  transition: opacity 120ms linear;
}

#back:hover,
#back:focus-visible {
  color: rgba(var(--ink), .82);
}

#back:hover span,
#back:focus-visible span {
  opacity: 1;
}

/* ------------------------------------------------------------------ strip */

#strip {
  position: absolute;
  left: 16px;
  bottom: 0;
  height: var(--bar);
  display: flex;
  align-items: center;
  gap: 6px;
}

#strip button {
  position: relative;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  background: var(--sw, #888);
  /* A 1px inset dark edge, so pale materials (snow, glass) still read as
     objects against a bright sky. Not a border — a border would change the
     box size and make the strip jitter on selection. */
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .5);
  cursor: pointer;
  font: inherit;
  color: rgba(var(--ink), .9);
  display: grid;
  place-items: center;
  transition: transform 90ms ease-out;
}

#strip button:hover {
  transform: translateY(-2px);
}

/* The selected swatch is marked in its own colour. No accent hue is ever
   introduced into this UI; the material you picked is the only colour in it. */
#strip button[aria-checked="true"]::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -6px;
  height: 2px;
  background: var(--sw, #888);
}

#strip button:focus-visible {
  outline: 1px solid rgba(var(--ink), .85);
  outline-offset: 2px;
}

/* The two tool swatches carry an inline SVG rather than a fill. */
#strip button.is-tool {
  background: transparent;
  box-shadow: inset 0 0 0 1px rgba(var(--ink), .28);
}

#strip button.is-tool svg {
  width: 14px;
  height: 14px;
  display: block;
  stroke: rgba(var(--ink), .8);
  fill: none;
  stroke-width: 1.25;
}

#strip button.is-tool[aria-checked="true"] {
  box-shadow: inset 0 0 0 1px rgba(var(--ink), .8);
}

#strip button.is-tool[aria-checked="true"]::after {
  background: rgba(var(--ink), .8);
}

#name {
  margin-left: 4px;
  color: rgba(var(--ink), .46);
  text-shadow: var(--lift);
  white-space: nowrap;
}

/* --------------------------------------------------------------- readouts */

#read,
#fx,
#world {
  position: absolute;
  bottom: 0;
  height: var(--bar);
  display: flex;
  align-items: center;
  text-shadow: var(--lift);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

#read {
  right: 16px;
  color: rgba(var(--ink), .46);
}

/* The word is the control. A page whose entire design is one hairline cannot
   carry a switch widget without becoming a different page. */
#fx {
  right: 0;
  gap: 10px;
  /* placed by hud.js relative to #read's measured width */
}

/* Cycling words. Each one shows its current value and clicking advances it;
   there is no menu, because a menu would need a panel and this page has none. */
#world {
  left: 16px;
  bottom: var(--bar);
  height: 26px;
  gap: 12px;
  color: rgba(var(--ink), .46);
}

#world button,
#fx button {
  border: 0;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  padding: 10px 0;
  cursor: pointer;
  color: rgba(var(--ink), .22);
  border-bottom: 1px solid transparent;
  text-shadow: var(--lift);
  transition: color 120ms linear;
}

#fx button[aria-pressed="true"] {
  color: rgba(var(--ink), .78);
  border-bottom-color: rgba(var(--ink), .78);
}

#world button {
  color: rgba(var(--ink), .52);
  padding: 6px 0;
}

#world button:hover {
  color: rgba(var(--ink), .85);
}

#world button:focus-visible,
#fx button:focus-visible {
  outline: 1px solid rgba(var(--ink), .7);
  outline-offset: 2px;
}

/* ------------------------------------------------------------- hint, ring */

#hint {
  position: absolute;
  left: 50%;
  bottom: calc(var(--bar) + 22px);
  transform: translateX(-50%);
  color: rgba(var(--ink), .5);
  text-shadow: var(--lift);
  pointer-events: none;
  transition: opacity 400ms linear;
}

#hint.is-gone {
  opacity: 0;
}

/* The hold-to-pour feedback: a ring whose stroke fills over the 140ms hold
   threshold. Positioned by transform only — never by left/top — so a finger
   drag does not thrash layout. */
#ring {
  position: absolute;
  top: 0;
  left: 0;
  width: 32px;
  height: 32px;
  margin: -16px 0 0 -16px;
  pointer-events: none;
  opacity: 0;
  will-change: transform;
}

#ring.is-on {
  opacity: 1;
}

#ring circle {
  fill: none;
  stroke: rgba(var(--ink), .85);
  stroke-width: 1.5;
  stroke-dasharray: 88;
  stroke-dashoffset: 88;
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
}

/* ------------------------------------------------------- boot / no-webgl2 */

#boot,
#nojs,
#fail {
  position: fixed;
  inset: 0;
  z-index: 4;
  display: grid;
  place-items: center;
  text-align: center;
  padding: 2rem;
  line-height: 2;
  color: rgba(var(--ink), .6);
  background: #0d0f11;
}

#boot {
  background: none;
  align-content: center;
  pointer-events: none;
  transition: opacity 260ms linear;
}

#boot.is-gone {
  opacity: 0;
}

#fail {
  color: rgba(var(--ink), .7);
}

/* ------------------------------------------------------------------ touch */

@media (pointer: coarse) {
  :root {
    --bar: 44px;
  }

  #strip {
    left: 0;
    right: 0;
    /* Left-aligned, not centred. Fourteen swatches do not fit across a phone,
       and centring them cuts BOTH ends off — including erase, which is the one
       you reach for most. Starting at the left keeps the common materials in
       place and lets the rest be swiped to. A second row is not an option: it
       would cover the world, which is the one thing this UI must not do. */
    justify-content: flex-start;
    padding: 0 12px calc(env(safe-area-inset-bottom, 0px) / 2);
    gap: 6px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
  }

  #strip::-webkit-scrollbar {
    display: none;
  }

  #strip button {
    width: 28px;
    height: 28px;
    flex: 0 0 auto;
  }

  #strip button:hover {
    transform: none;
  }

  #name {
    display: none;
  }

  #rule {
    bottom: calc(var(--bar) + env(safe-area-inset-bottom, 0px));
  }

  /* The strip owns the bar on touch, so everything else stacks above it in two
     rows: the two control groups share a line, the readout gets its own. They
     were all on one line at first and simply overlapped. */
  #world,
  #fx {
    bottom: calc(var(--bar) + env(safe-area-inset-bottom, 0px) + 2px);
    height: 32px;
  }

  #world {
    left: 12px;
    right: auto;
    gap: 14px;
  }

  #fx {
    /* Anchored to the right edge here. hud.js only measures and places it
       against the readout on a pointer device, where they share a line. */
    right: 12px;
    left: auto;
    gap: 12px;
  }

  #read {
    right: 12px;
    bottom: calc(var(--bar) + env(safe-area-inset-bottom, 0px) + 36px);
    height: 24px;
  }

  #world button,
  #fx button {
    /* 44px of hit area without changing the type size. */
    padding: 16px 2px;
  }

  #hint {
    bottom: calc(var(--bar) + env(safe-area-inset-bottom, 0px) + 76px);
  }
}

@media (prefers-reduced-motion: reduce) {

  #hud,
  #hint,
  #boot,
  #strip button,
  #fx button,
  #back,
  #back span {
    transition-duration: 1ms;
  }
}
