/* ==========================================================================
   Angle of Repose — Day 9/30
   Palette is taken from the material, not from a UI kit: quartz and iron
   oxide for the sand, basalt for the surfaces, a single ember accent that
   appears nowhere except on the tool you are currently holding.
   ========================================================================== */

:root {
  --void:      #0E0B09;   /* the box */
  --sediment:  #161210;   /* floating chrome */
  --rule:      #2C2521;   /* hairlines */
  --bone:      #EDE3D4;   /* primary text */
  --dust:      #9A8C7C;   /* secondary text */
  --ember:     #E4763A;   /* the one accent */

  --display: "Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif;
  --label:   "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;

  --pad-l: max(0.9rem, env(safe-area-inset-left));
  --pad-r: max(0.9rem, env(safe-area-inset-right));
  --pad-b: max(0.6rem, env(safe-area-inset-bottom));
  --pad-t: max(0.9rem, env(safe-area-inset-top));

  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-io:  cubic-bezier(0.4, 0, 0.2, 1);
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  padding: 0;
}

body {
  position: fixed;
  inset: 0;
  overflow: hidden;
  overscroll-behavior: none;
  background: var(--void);
  color: var(--bone);
  font-family: var(--label);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

/* --------------------------------------------------------------------------
   The box
   -------------------------------------------------------------------------- */

/* Landmark wrappers only. Their children are all fixed, so these collapse to
   nothing. `display: contents` is deliberately avoided: it has a history of
   dropping the element out of the accessibility tree, which is the one thing
   these wrappers exist for. */
.stage, .colophon { margin: 0; }

.sim {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
  cursor: crosshair;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

/* Brush cursor. Sized from the real brush radius so the ring is a promise,
   not a decoration. Hidden on coarse pointers until the finger is down. */
.ring {
  position: fixed;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  border-radius: 50%;
  border: 1px solid rgba(237, 227, 212, 0.34);
  pointer-events: none;
  opacity: 0;
  z-index: 20;
  will-change: transform;
}

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

/* --------------------------------------------------------------------------
   Edge falloff

   The box can be filled to the brim, and lit sand is bright. Rather than
   trust the backdrop to stay dark under the masthead and the day mark, the
   top and bottom edges carry their own falloff. It reads as the light in the
   box dropping off at the rim, and it keeps every label legible at 60,000
   grains.
   -------------------------------------------------------------------------- */

body::before,
body::after {
  content: "";
  position: fixed;
  left: 0;
  right: 0;
  z-index: 25;
  pointer-events: none;
  transition: opacity 300ms var(--ease-io);
}

body::before {
  top: 0;
  height: 6.5rem;
  background: linear-gradient(to bottom,
    rgba(14, 11, 9, 0.94) 0%, rgba(14, 11, 9, 0.62) 42%, rgba(14, 11, 9, 0) 100%);
}

body::after {
  bottom: 0;
  height: 4.5rem;
  background: linear-gradient(to top,
    rgba(14, 11, 9, 0.97) 0%, rgba(14, 11, 9, 0.52) 48%, rgba(14, 11, 9, 0) 100%);
}

/* --------------------------------------------------------------------------
   Masthead
   -------------------------------------------------------------------------- */

.mast {
  position: fixed;
  z-index: 30;
  top: var(--pad-t);
  left: var(--pad-l);
  right: var(--pad-r);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  pointer-events: none;
  transition: opacity 240ms var(--ease-io);
}

.mast__name {
  margin: 0;
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(0.95rem, 3.4vw, 1.15rem);
  letter-spacing: -0.015em;
  color: var(--bone);
  opacity: 0.9;
}

.mast__read {
  margin: 0;
  font-size: 0.625rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dust);
  white-space: nowrap;
}

.mast__num {
  font-variant-numeric: tabular-nums;
  color: var(--bone);
  opacity: 0.78;
}

.mast__unit { opacity: 0.8; }

/* --------------------------------------------------------------------------
   First-run invitation
   -------------------------------------------------------------------------- */

.invite {
  position: fixed;
  z-index: 15;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 0 1.5rem 22vh;
  pointer-events: none;
  transition: opacity 420ms var(--ease-out);
}

.invite.is-gone { opacity: 0; }

.invite__line {
  margin: 0;
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(1.35rem, 6.2vw, 2.1rem);
  letter-spacing: -0.03em;
  color: var(--bone);
  opacity: 0.42;
  text-align: center;
}

/* --------------------------------------------------------------------------
   Deck — the two hairline strips of chrome
   -------------------------------------------------------------------------- */

.deck {
  position: fixed;
  z-index: 40;
  left: var(--pad-l);
  right: var(--pad-r);
  bottom: calc(var(--pad-b) + 1.85rem);
  max-width: 30rem;
  margin: 0 auto;
  background: color-mix(in srgb, var(--sediment) 86%, transparent);
  border: 1px solid var(--rule);
  border-radius: 12px;
  backdrop-filter: blur(14px) saturate(130%);
  -webkit-backdrop-filter: blur(14px) saturate(130%);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5);
  overflow: hidden;
  transition: opacity 240ms var(--ease-io), transform 380ms var(--ease-out);
}

@supports not (backdrop-filter: blur(2px)) {
  .deck { background: var(--sediment); }
}

/* --- material row --- */

.deck__mats {
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  border-bottom: 1px solid var(--rule);
}

.mat {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-height: 56px;
  padding: 0.55rem 0.25rem;
  background: none;
  border: 0;
  border-radius: 0;
  color: var(--dust);
  font-family: var(--label);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color 160ms var(--ease-io), transform 130ms var(--ease-out);
}

.mat:hover { color: color-mix(in srgb, var(--bone) 72%, var(--dust)); }
.mat:active { transform: scale(0.96); }
.mat.is-on { color: var(--bone); }

.mat:focus-visible {
  outline: 2px solid var(--ember);
  outline-offset: -3px;
}

/* Each swatch is the material's real colour range, sampled from the same
   values the simulation renders with. */
.mat__swatch {
  width: 22px;
  height: 6px;
  border-radius: 3px;
  transition: opacity 160ms var(--ease-io), transform 200ms var(--ease-out);
  opacity: 0.62;
}

.mat.is-on .mat__swatch {
  opacity: 1;
  transform: scaleX(1.14);
}

.mat__swatch--sand  { background: linear-gradient(90deg, #7A4E22, #C98B3E, #E8C177); }
/* Dark to light, like the others — but water's light end is its surface film
   rather than its brightest grain, so this reads as a section through a pool. */
.mat__swatch--water { background: linear-gradient(90deg, #0E2A3F, #2E7690, #90BEC6); }
.mat__swatch--stone { background: linear-gradient(90deg, #2E2823, #4A4038, #6B5E52); }
.mat__swatch--erase {
  background: none;
  border: 1px solid var(--dust);
  height: 6px;
}

/* The marker is the only thing that moves between tools, and it is the only
   place the ember accent is allowed to land. */
.deck__marker {
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 25%;
  height: 2px;
  background: var(--ember);
  transform: translateX(0);
  transition: transform 240ms var(--ease-out);
  pointer-events: none;
}

/* --- brush + clear row --- */

.deck__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.4rem 0.55rem 0.4rem 0.35rem;
}

.deck__brush { display: flex; }

.brush {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 8px;
  cursor: pointer;
  transition: transform 130ms var(--ease-out);
}

.brush:active { transform: scale(0.92); }

.brush:focus-visible {
  outline: 2px solid var(--ember);
  outline-offset: -3px;
}

.brush__dot {
  width: var(--d);
  height: var(--d);
  border-radius: 50%;
  border: 1px solid var(--dust);
  background: none;
  transition: border-color 160ms var(--ease-io), background-color 160ms var(--ease-io);
}

.brush:hover .brush__dot { border-color: color-mix(in srgb, var(--bone) 70%, var(--dust)); }

.brush.is-on .brush__dot {
  border-color: var(--bone);
  background: var(--bone);
}

.clear {
  min-height: 44px;
  padding: 0 1.05rem;
  background: none;
  border: 1px solid var(--rule);
  border-radius: 8px;
  color: var(--dust);
  font-family: var(--label);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color 160ms var(--ease-io), border-color 160ms var(--ease-io),
              transform 130ms var(--ease-out);
}

.clear:hover {
  color: var(--bone);
  border-color: color-mix(in srgb, var(--bone) 30%, var(--rule));
}

.clear:active { transform: scale(0.96); }

.clear:focus-visible {
  outline: 2px solid var(--ember);
  outline-offset: 2px;
}

.clear[disabled] {
  opacity: 0.38;
  cursor: default;
  transform: none;
}

/* --------------------------------------------------------------------------
   Day badge — same corner, same face, same size on all thirty
   -------------------------------------------------------------------------- */

.day {
  position: fixed;
  z-index: 55;
  right: max(0.9rem, env(safe-area-inset-right));
  bottom: max(0.5rem, env(safe-area-inset-bottom));
  margin: 0;
  font-family: var(--label);
  font-size: 0.625rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--bone);
  opacity: 0.72;
  pointer-events: none;
  transition: opacity 240ms var(--ease-io);
  /* The day mark outlives the scrims, so it carries its own contrast: a tight
     halo that is invisible against the empty box and cuts a hole for itself in
     a pile of lit sand. Without it the mark is unreadable at the brim. */
  text-shadow:
    0 0 2px  rgba(14, 11, 9, 0.98),
    0 1px 2px rgba(14, 11, 9, 0.98),
    0 0 7px  rgba(14, 11, 9, 0.92),
    0 0 16px rgba(14, 11, 9, 0.7);
}

/* --------------------------------------------------------------------------
   The bare switch — mirror of the day mark, opposite corner, same line
   -------------------------------------------------------------------------- */

.bare {
  position: fixed;
  z-index: 56;
  left: max(0.9rem, env(safe-area-inset-left));
  bottom: max(0.5rem, env(safe-area-inset-bottom));
  display: grid;
  place-items: center;
  width: 14px;
  height: 13px;          /* the day mark's own box, so the two sit on one line */
  padding: 0;
  background: none;
  border: 0;
  color: var(--bone);
  opacity: 0.72;
  cursor: pointer;
  transition: opacity 200ms var(--ease-io);
}

/* A thumb-sized target without a thumb-sized footprint. The region is pushed
   out to the two viewport edges, where nobody is trying to place a grain. */
.bare::before {
  content: "";
  position: absolute;
  left: -1.1rem;
  bottom: -0.7rem;
  width: 54px;
  height: 50px;
}

.bare__glyph {
  display: block;
  filter: drop-shadow(0 0 2px rgba(14, 11, 9, 0.98))
          drop-shadow(0 1px 4px rgba(14, 11, 9, 0.92))
          drop-shadow(0 0 12px rgba(14, 11, 9, 0.7));
  transition: transform 380ms var(--ease-out);
}

.bare:hover { opacity: 1; }
.bare:active .bare__glyph { transform: scale(0.88); }

.bare:focus-visible {
  outline: 2px solid var(--ember);
  outline-offset: 6px;
  border-radius: 2px;
  opacity: 1;
}

/* --------------------------------------------------------------------------
   Bare state

   Everything floating over the box leaves except the day mark and the switch
   that brings it all back. The switch has to stay: a control with no way home
   is a trap, not a clean view. It dims to the quietest mark on the page.
   -------------------------------------------------------------------------- */

body.is-bare::before,
body.is-bare::after { opacity: 0; }

body.is-bare .mast {
  opacity: 0;
  visibility: hidden;
  transition: opacity 240ms var(--ease-io), visibility 0s linear 240ms;
}

body.is-bare .invite { opacity: 0; }

body.is-bare .deck {
  opacity: 0;
  pointer-events: none;
  transform: translateY(calc(100% + var(--pad-b) + 2.4rem));
}

/* Quieter than the day mark, but only just. Any dimmer and the way back
   disappears into a lit pile — tested against sand at the brim. */
body.is-bare .bare { opacity: 0.62; }
body.is-bare .bare:hover,
body.is-bare .bare:focus-visible { opacity: 1; }

/* With the scrims gone the day mark is on its own. Give it back what the
   scrim was contributing. */
body.is-bare .day { opacity: 0.88; }

/* Up means the tray comes back. */
body.is-bare .bare__glyph { transform: rotate(180deg); }
body.is-bare .bare:active .bare__glyph { transform: rotate(180deg) scale(0.88); }

/* --------------------------------------------------------------------------
   Load-in. Orientation only: the chrome arrives after the box exists.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
  /* `backwards`, not `both`: once the load-in ends the animation must stop
     holding opacity, or it outranks the invitation's own fade-out. */
  .mast, .deck, .invite, .day, .bare {
    animation: rise 620ms var(--ease-out) backwards;
  }
  .mast  { animation-delay: 80ms; }
  .invite { animation-delay: 220ms; }
  .deck  { animation-delay: 160ms; }
  /* The two corner marks arrive together. */
  .day   { animation-delay: 300ms; }
  .bare  { animation-delay: 300ms; }

  @keyframes rise {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: translateY(0); }
  }
}

/* The simulation itself is the point of the page, so its motion stays. The
   chrome's motion does not. */
@media (prefers-reduced-motion: reduce) {
  .deck__marker,
  .invite,
  .mat,
  .mat__swatch,
  .brush,
  .brush__dot,
  .clear,
  .deck,
  .mast,
  .day,
  .bare,
  .bare__glyph,
  body::before,
  body::after {
    transition-duration: 1ms;
  }

  /* The visibility hand-off is timed off the fade, so it has to collapse too. */
  body.is-bare .mast { transition: opacity 1ms, visibility 0s linear 1ms; }
}

/* --------------------------------------------------------------------------
   Small screens
   -------------------------------------------------------------------------- */

@media (max-width: 360px) {
  .mat { font-size: 0.5625rem; letter-spacing: 0.1em; }
  .mat__swatch { width: 18px; }
  .clear { padding: 0 0.7rem; letter-spacing: 0.16em; }
}

/* Landscape phones: the deck must not eat the box. */
@media (max-height: 460px) {
  .deck { bottom: calc(var(--pad-b) + 1.6rem); }
  .mat { min-height: 46px; gap: 0.3rem; }
  .invite { padding-bottom: 14vh; }
}
