/* TryGC overlay containment fix.
 *
 * Symptom: clicking a card opens its panel off-screen -- the user has to scroll
 * up (or down) to find it.
 *
 * Cause, measured on live 2026-08-22 (/tasks, 1440x900): the overlay is
 * `fixed inset-0`, which should mean "cover the viewport". It doesn't, because
 * of its ancestor:
 *
 *     .page-enter{will-change:opacity,transform;contain:paint;
 *                 animation:.16s cubic-bezier(.16,1,.3,1) both page-enter}
 *     @keyframes page-enter{0%{opacity:0;transform:translateY(4px)}
 *                           to{opacity:1;transform:translateY(0)}}
 *
 * All three of `contain:paint`, `will-change:transform` and a retained
 * `transform` make an element the containing block for its `position:fixed`
 * descendants. `animation-fill-mode: both` is what makes it permanent: the
 * animation lasts 160 ms but `both` keeps the `to` state -- transform:
 * translateY(0) -- applied for the life of the page.
 *
 * So `inset-0` resolved against the 4066 px page wrapper instead of the 900 px
 * viewport: the overlay measured 4066 px tall with its top at -712 px, and the
 * panel (items-start) pinned to the top of *that* box, i.e. 712 px above the
 * fold. Hence the scrolling.
 *
 * The retained end state (opacity:1; transform:translateY(0)) is identical to
 * the element's own resting style, so dropping the retention changes nothing
 * visually once the 160 ms animation has finished -- it only stops the element
 * being a containing block.
 *
 * `backwards` (not `none`) keeps the 0% state applied before the animation
 * starts, which preserves the fade-in and avoids a one-frame flash of
 * fully-opaque content.
 *
 * Loaded last so it wins over the Tailwind build output without !important on
 * the animation shorthand.
 */

.page-enter {
  /* Do not retain the animated end state -- that is what pinned `transform` on. */
  animation-fill-mode: backwards !important;

  /* `paint` containment also establishes a fixed-position containing block. */
  contain: none !important;

  /* Keep the compositor hint for the property that cannot break `fixed`.
   * (opacity creates a stacking context, never a containing block.) */
  will-change: opacity !important;
}

/* Belt and braces: once an overlay is open, no ancestor inside the app shell
 * should be able to re-establish a containing block underneath it. This only
 * applies while a dialog is actually mounted, so it costs nothing otherwise. */
.gc-app-content:has([role="dialog"]) .page-enter {
  transform: none !important;
  filter: none !important;
  perspective: none !important;
}

/* A dialog that is taller than the viewport should scroll inside itself rather
 * than push its own header out of view. The panel already declares
 * max-h-[calc(100dvh-1.5rem)]; this makes the guarantee unconditional for any
 * overlay that did not opt in. */
[role="dialog"] > aside,
[role="dialog"] > div[class*="max-w-"] {
  max-height: calc(100dvh - 1.5rem);
}
