/*
 * Dashboard listing visuals.
 *
 * Everything is built on Mantine tokens via light-dark(), so both colour
 * schemes are handled by one rule set — there are no hardcoded surface colours
 * that would break a mode.
 *
 * Motion rules:
 *   - transform/opacity/filter only, so effects stay on the compositor.
 *   - every animation and transition is disabled under prefers-reduced-motion
 *     at the bottom of this file.
 * Glass rules:
 *   - backdrop-filter is progressive enhancement; the @supports block near the
 *     top provides opaque surfaces for browsers without it.
 */

/* ------------------------------------------------------------------ hero */

.hero {
  position: relative;
  overflow: hidden;
  border-radius: var(--mantine-radius-lg);
  padding: clamp(20px, 3vw, 34px);
  border: 1px solid light-dark(rgba(15, 23, 42, 0.08), rgba(255, 255, 255, 0.1));
  background: light-dark(
    rgba(255, 255, 255, 0.72),
    rgba(255, 255, 255, 0.035)
  );
  backdrop-filter: blur(18px) saturate(140%);
  box-shadow:
    0 1px 0 0 light-dark(rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.06)) inset,
    0 18px 40px -28px light-dark(rgba(15, 23, 42, 0.45), rgba(0, 0, 0, 0.8));
}

/* Aurora wash. Two slow-drifting blooms, blurred so they read as light. */
.hero::before {
  content: "";
  position: absolute;
  inset: -40%;
  z-index: 0;
  pointer-events: none;
  filter: blur(70px);
  opacity: light-dark(0.5, 0.42);
  background:
    radial-gradient(28% 32% at 18% 30%, var(--mantine-color-indigo-5), transparent 70%),
    radial-gradient(26% 30% at 72% 20%, var(--mantine-color-cyan-5), transparent 70%),
    radial-gradient(30% 34% at 52% 88%, var(--mantine-color-grape-5), transparent 70%);
  animation: auroraDrift 28s ease-in-out infinite alternate;
  will-change: transform;
}

.heroInner {
  position: relative;
  z-index: 1;
}

@keyframes auroraDrift {
  0% {
    transform: translate3d(-3%, -2%, 0) scale(1);
  }
  50% {
    transform: translate3d(3%, 2%, 0) scale(1.06);
  }
  100% {
    transform: translate3d(-2%, 3%, 0) scale(1.02);
  }
}

/* ------------------------------------------------------------------ card */

.card {
  position: relative;
  overflow: hidden;
  display: block;
  height: 100%;
  color: inherit;
  text-decoration: none;
  border-radius: var(--mantine-radius-lg);
  border: 1px solid light-dark(rgba(15, 23, 42, 0.08), rgba(255, 255, 255, 0.09));
  background: light-dark(
    rgba(255, 255, 255, 0.74),
    rgba(255, 255, 255, 0.04)
  );
  backdrop-filter: blur(14px) saturate(135%);
  box-shadow:
    0 1px 0 0 light-dark(rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0.05)) inset,
    0 10px 26px -20px light-dark(rgba(15, 23, 42, 0.4), rgba(0, 0, 0, 0.75));
  transition:
    transform 260ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 260ms cubic-bezier(0.22, 1, 0.36, 1),
    border-color 260ms ease;
  will-change: transform;
}

.card:hover,
.card:focus-visible {
  transform: translateY(-5px);
  border-color: light-dark(rgba(15, 23, 42, 0.16), rgba(255, 255, 255, 0.2));
  box-shadow:
    0 1px 0 0 light-dark(rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.08)) inset,
    0 26px 48px -26px light-dark(rgba(15, 23, 42, 0.5), rgba(0, 0, 0, 0.9));
}

/*
 * The theme sets focusRing: "never" globally, which would leave keyboard users
 * with no indication on a custom card. Restore an explicit ring here.
 */
.card:focus-visible {
  outline: 2px solid var(--mantine-primary-color-filled);
  outline-offset: 3px;
}

.cover {
  position: relative;
  overflow: hidden;
  aspect-ratio: 21 / 9;
  border-radius: calc(var(--mantine-radius-lg) - 1px) calc(var(--mantine-radius-lg) - 1px) 0 0;
}

.coverArt {
  position: absolute;
  inset: 0;
  transform: scale(1.02);
  transition: transform 480ms cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}

.card:hover .coverArt,
.card:focus-visible .coverArt {
  transform: scale(1.08);
}

/* Scrim so the monogram and badges stay legible on any generated hue. */
.coverScrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(0, 0, 0, 0.05) 0%,
    rgba(0, 0, 0, 0.18) 55%,
    rgba(0, 0, 0, 0.42) 100%
  );
}

/*
 * Deliberately understated: a watermark, not a headline. At full size and full
 * opacity the initials read as placeholder art and fight the dashboard name
 * directly below them.
 */
.monogram {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: clamp(1.35rem, 2.2vw, 1.7rem);
  font-weight: 600;
  letter-spacing: 0.16em;
  color: rgba(255, 255, 255, 0.55);
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.3);
  user-select: none;
}

/* Diagonal sheen that sweeps once on hover. */
.card::after {
  content: "";
  position: absolute;
  top: 0;
  left: -120%;
  width: 60%;
  height: 100%;
  z-index: 2;
  pointer-events: none;
  transform: skewX(-18deg);
  background: linear-gradient(
    90deg,
    transparent,
    light-dark(rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.1)),
    transparent
  );
  opacity: 0;
  transition: none;
}

.card:hover::after,
.card:focus-visible::after {
  animation: sheen 900ms cubic-bezier(0.22, 1, 0.36, 1);
}

@keyframes sheen {
  0% {
    left: -120%;
    opacity: 0;
  }
  35% {
    opacity: 1;
  }
  100% {
    left: 130%;
    opacity: 0;
  }
}

.cardBody {
  position: relative;
  z-index: 1;
  padding: 14px 16px 16px;
}

/* Two-line clamp for the description. */
.clamp2 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: 2.4em;
}

/* --------------------------------------------------------- entrance stagger */

/*
 * `backwards`, NOT `forwards`. A forwards-filled animation keeps applying its
 * final `transform: none` after it ends, which outranks `.card:hover`'s
 * translateY in the cascade — the hover lift silently never fires. `backwards`
 * applies the FROM state during the stagger delay (so the card is hidden until
 * its turn) and then hands control back to the element's own rules.
 */
.enter {
  animation: fadeSlideIn 460ms cubic-bezier(0.22, 1, 0.36, 1) backwards;
  animation-delay: var(--stagger, 0ms);
}

@keyframes fadeSlideIn {
  from {
    opacity: 0;
    transform: translateY(14px) scale(0.985);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* ------------------------------------------------------------- empty state */

.emptyPanel {
  position: relative;
  overflow: hidden;
  border-radius: var(--mantine-radius-lg);
  border: 1px dashed light-dark(rgba(15, 23, 42, 0.16), rgba(255, 255, 255, 0.16));
  background: light-dark(rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.03));
  backdrop-filter: blur(10px);
  padding: clamp(28px, 5vw, 56px);
  text-align: center;
}

.emptyGlow {
  position: absolute;
  inset: -30%;
  z-index: 0;
  pointer-events: none;
  filter: blur(70px);
  opacity: light-dark(0.32, 0.26);
  background:
    radial-gradient(26% 30% at 30% 30%, var(--mantine-color-indigo-5), transparent 70%),
    radial-gradient(24% 28% at 70% 70%, var(--mantine-color-cyan-5), transparent 70%);
  animation: auroraDrift 34s ease-in-out infinite alternate;
}

.emptyInner {
  position: relative;
  z-index: 1;
}

/* ------------------------------------------------ backdrop-filter fallback */
/*
 * Older browsers (and Firefox with the flag off) get opaque surfaces instead of
 * translucent ones — otherwise the "glass" reads as washed-out, unreadable text
 * over whatever is behind it.
 */
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .hero,
  .card,
  .emptyPanel {
    background: var(--mantine-color-body);
  }
}

/* ------------------------------------------------------- reduced motion */
/*
 * Required, not optional: everything decorative stops. Hover still gives a
 * non-motion affordance through the border/shadow change.
 */
@media (prefers-reduced-motion: reduce) {
  .hero::before,
  .emptyGlow {
    animation: none;
  }

  .enter {
    opacity: 1;
    animation: none;
  }

  .card,
  .coverArt {
    transition: none;
  }

  .card:hover,
  .card:focus-visible {
    transform: none;
  }

  .card:hover .coverArt,
  .card:focus-visible .coverArt {
    transform: scale(1.02);
  }

  .card:hover::after,
  .card:focus-visible::after {
    animation: none;
  }
}

/* ------------------------------------------------------------- dropzone */

.dropzone {
  border: 1.5px dashed light-dark(rgba(15, 23, 42, 0.18), rgba(255, 255, 255, 0.18));
  background: light-dark(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.025));
  transition: border-color 180ms ease, background 180ms ease;
}

.dropzone:hover {
  border-color: light-dark(rgba(15, 23, 42, 0.3), rgba(255, 255, 255, 0.3));
  background: light-dark(rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0.05));
}

@media (prefers-reduced-motion: reduce) {
  .dropzone {
    transition: none;
  }
}

/* --------------------------------------------------------------- viewer */

.viewerShell {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  background: var(--mantine-color-body);
}

/*
 * Soft no-export affordances, applied to OUR chrome only.
 *
 * These are deterrents, not protection. They cannot reach inside the frame
 * (it is cross-origin by design), and a viewer can still screenshot, use
 * devtools, or hit the serve URL directly. The real control is that a bundle
 * is static, baked-in data with no live-system access — not this CSS.
 */
.viewerChrome {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 14px;
  border-bottom: 1px solid light-dark(rgba(15, 23, 42, 0.08), rgba(255, 255, 255, 0.1));
  background: light-dark(rgba(255, 255, 255, 0.78), rgba(255, 255, 255, 0.04));
  backdrop-filter: blur(14px) saturate(135%);
  user-select: none;
  -webkit-user-select: none;
}

@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .viewerChrome {
    background: var(--mantine-color-body);
  }
}

.frameShell {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7));
}

.frame {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

.frameOverlay {
  position: absolute;
  inset: 0;
  z-index: 2;
  justify-content: center;
  pointer-events: none;
}

/*
 * Public preview watermark. Fixed, non-interactive, low opacity — it marks the
 * view as a shared preview without covering the content. Carries a display-only
 * label; the raw share token is never rendered here or anywhere client-side.
 */
.watermark {
  position: fixed;
  right: 14px;
  bottom: 12px;
  z-index: 5;
  pointer-events: none;
  user-select: none;
  opacity: 0.42;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 5px 10px;
  border-radius: 999px;
  color: light-dark(rgba(15, 23, 42, 0.75), rgba(255, 255, 255, 0.8));
  background: light-dark(rgba(255, 255, 255, 0.7), rgba(0, 0, 0, 0.45));
  border: 1px solid light-dark(rgba(15, 23, 42, 0.1), rgba(255, 255, 255, 0.14));
  backdrop-filter: blur(6px);
}

/* Nothing in the viewer chrome should end up on paper. */
@media print {
  .viewerChrome,
  .watermark {
    display: none !important;
  }
}
