.imposter {
  position: absolute;
  --inset-block-start: 3%;
  inset-block-start: var(--inset-block-start, 50%);
  inset-inline-start: var(--inset-inline-start, 50%);
  transform: translate(-50%, -50%);
}

.imposter.contain {
  --margin: 0px;
  overflow: auto;
  max-inline-size: calc(100% - (var(--margin) * 2));
  max-block-size: calc(100% - (var(--margin) * 2));

  /* "contain" (rather than "none") only stops scroll chaining to the page behind this element --
     it explicitly leaves the element's own local overscroll/rubber-band bounce in place, which is
     itself what let a fast fling reveal content like the fixed header at this element's boundary.
     "none" suppresses that local bounce too. */
  overscroll-behavior-y: none;
}

@keyframes toast-fade-in {
  from { opacity: 0 }
}

@keyframes toast-fade-out {
  to { opacity: 0 }
}

@keyframes toast-slide-in {
  from { transform: translateY(var(--travel-distance, 10px)) }
}

.notices,
.flash-toasts {
  position: absolute;
  display: flex;
  justify-content: center;
  top: 0;
  left: 0;
  right: 0;
  overflow: hidden;
  /* Purely informational (role="status"/"alert" .toast children, never anything
     interactive) -- without this, the container's own box keeps intercepting
     clicks meant for whatever sits underneath it (e.g. the nav bar), even once
     its .toast content has faded out, since the container itself is never
     removed from the DOM until the next page load. */
  pointer-events: none;
}

.toast {
  padding: var(--space-2xs) var(--space-xs);
  color: var(--color-light);
  background-color: var(--color-dark);
  border-radius: var(--border-radius-s);
  pointer-events: none;

  --duration: 1.5s;
  --travel-distance: 0;

  will-change: transform;
  animation:
    toast-fade-in .3s ease,
    toast-slide-in .3s ease,
    toast-fade-out .3s ease var(--duration) forwards;
}

@media (prefers-reduced-motion: no-preference) {
  .toast {
    --travel-distance: 5vh;
  }
}

/* Flash toasts (Devise/session notices) are page-level rather than
   scoped to the game grid, so they're fixed to the viewport, sit above
   everything (including modals), stack if both a notice and alert are
   present, and linger long enough to read a full sentence. */
.flash-toasts {
  position: fixed;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2xs);
  padding-block-start: max(var(--space-2xs), var(--safe-area-inset-top, 0px));
  z-index: 1000;
}

.flash-toasts .toast {
  --duration: 4s;
}

.toast.toast-alert {
  background-color: #b3261e;
}

/* Puzzle-solved toast: fades/slides in like any other toast, but never runs
   toast-fade-out, so it stays on screen instead of disappearing after
   --duration. Two classes beat .toast's single-class specificity regardless
   of source order. */
.toast.toast-persistent {
  animation:
    toast-fade-in .3s ease,
    toast-slide-in .3s ease;
}

.imposter.modal {
  /* A modal is always a full-viewport overlay, never anchored to a specific in-page element like
     the dropdown/drawer imposters are -- position: fixed anchors it to the viewport
     directly. Without this override, .imposter's inherited position: absolute falls back (since no
     ancestor here is itself positioned) to the initial containing block, and bottom: 0 then chases
     the document's own auto height, which grows to contain the modal, pushing it further down the
     page well past the visible viewport instead of overlaying it. */
  position: fixed;
  --margin: 1rem;
  inline-size: 100%;
  inset-block-start: inherit;
  bottom: var(--safe-area-inset-bottom, 0px);
  margin-top: var(--stack-margin);
  border: 0;
  background-color: var(--color-white);
  box-shadow: 0 0 25px 0 rgba(0, 0, 0, .18);
  z-index: 100;

  /* Modal starts hidden below page */
  transform: translate(-50%, 100%);
  opacity: 0;
}

@keyframes modal-slide-up {
  from {
    transform: translate(-50%, 100%);
    opacity: 0;
  }
  to {
    transform: translate(-50%, 0);
    opacity: 1;
  }
}

.imposter.modal.opening {
  animation: modal-slide-up 0.35s ease-out forwards;
}

@keyframes modal-slide-down {
  from {
    transform: translate(-50%, 0%);
    opacity: 1;
  }
  to {
    transform: translate(-50%, 100%);
    opacity: 0;
  }
}

.imposter.modal.closing {
  animation: modal-slide-down 0.25s ease-out forwards;
}

.imposter.modal * {
  font-size: var(--font-size-m);
  font-family: "Source Sans 3", sans-serif;
}

.imposter.drawer {
  position: fixed;
  inset-block-start: 0;
  inset-inline-start: 0;
  bottom: 0;
  inline-size: 16rem;
  max-inline-size: 85vw;
  block-size: 100%;
  border: 0;
  border-inline-end: var(--border-thin) solid var(--color-grey);
  background-color: var(--color-white);
  z-index: 100;

  /* A column flex container so the nav list (.sidebar-nav) can be told to take
     the remaining space and scroll on its own -- overflow lives there rather
     than on this element itself, so the close button can be absolutely
     positioned to overflow past this box's own right edge without being
     clipped (overflow-x and overflow-y are coupled per spec: setting one to
     anything but visible forces the other to clip too). */
  display: flex;
  flex-direction: column;

  /* Drawer starts hidden off-screen to the left. Its box-shadow is animated
     in/out alongside the slide (rather than left on permanently) because the
     shadow's blur radius otherwise bleeds past the drawer's right edge into
     the visible page even while the drawer itself is fully off-screen. */
  transform: translateX(-100%);
  box-shadow: none;
}

@keyframes drawer-slide-in {
  from { transform: translateX(-100%); box-shadow: none; }
  to { transform: translateX(0); box-shadow: 5px 0 25px 0 rgba(0, 0, 0, .18); }
}

.imposter.drawer.opening {
  animation: drawer-slide-in 0.25s ease-out forwards;
}

@keyframes drawer-slide-out {
  from { transform: translateX(0); box-shadow: 5px 0 25px 0 rgba(0, 0, 0, .18); }
  to { transform: translateX(-100%); box-shadow: none; }
}

.imposter.drawer.closing {
  animation: drawer-slide-out 0.2s ease-in forwards;
}

.drawer-backdrop {
  position: fixed;
  inset: 0;
  background-color: rgba(0, 0, 0, .5);
  z-index: 99;
}

.imposter.dropdown {
  --inset-block-start: 100%;
  inset-inline-end: 0%;
  inset-inline-start: inherit;
  /* ~80% wider than the old max-content box, for breathing room around the menu items. */
  inline-size: calc(var(--space-3xl) * 1.8);
  transform: none;
  background-color: var(--color-white);
  border: 1px solid var(--color-grey);
  box-shadow: 0px 5px 5px 0px rgba(0, 0, 0, .12);
  z-index: 10;
}

.imposter.dropdown a,
.imposter.dropdown span {
  padding: var(--space-2xs);
  border-bottom: 1px solid #c6c6c6;
  text-decoration: none;
}

.imposter.dropdown a:last-child {
  border-bottom: none;
}
