/* The certificate reveal: the moment the participant taps "sim, está correto"
   and the sheet arrives. It is the owner's number one animation priority.

   ─────────────────────────────────────────────────────────────────────────
   THIS SHEET NEVER ENTERS THE PDF.

   Chromium prints `certificates/templates/*` wrapped in the `certificate`
   layout, and the CSS that goes embedded in that HTML is exactly what
   `CertificatesHelper::CERTIFICATE_STYLESHEETS` lists — only
   `certificate.css`. This sheet enters through a <link> that the layout only
   emits when it is not printing. In other words: the guarantee that no
   animation reaches the paper does not depend on the discipline of whoever
   writes the CSS, it depends on the file not existing for Chromium. That is
   why the reveal lives here and not there.

   The rule that upholds it: nothing in this sheet touches `.certificate`.
   Everything that moves is a wrapper around the sheet — `.reveal__frame` and
   up — or an ornament sibling to it. The geometry of the printed sheet stays
   intact.
   ─────────────────────────────────────────────────────────────────────────

   Resting state = final state. Each animation starts from a hidden state
   declared inside the @keyframes, never in the base rule. If the animations do
   not run — `prefers-reduced-motion`, an old browser, a phone that choked —
   what is left is the visible certificate and the download button. Degradation
   is the default behaviour, not a special case.

   And the download never waits for the animation: `.reveal__actions` is born
   visible and clickable, and no rule here touches `pointer-events` or
   `visibility`. */

.reveal {
  --backdrop: #0d1015;
  --paper-shadow: rgba(0, 0, 0, 0.55);
  --neon: #7ce7ff;
  --ruby: #c8202f;
  --gold: #f2c14e;
  --accent: #e8e4e3;

  box-sizing: border-box;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(1.1rem, 3vw, 2rem);
  padding: clamp(1rem, 3vw, 2.5rem) 0.75rem;
  /* The sparks of the `stamp` variant leave the sheet; `clip` cuts the excess
     horizontally without turning the page into a scroll container. */
  overflow-x: clip;
}

.reveal * { box-sizing: border-box; }

/* The dark background belongs to the reveal screen, not to the certificate:
   `certificate.css` is left untouched, and this rule wins because its <link>
   comes later. Only the certificate page on screen loads this sheet, so the
   selector needs no more qualification than the <body>'s class. */
body.certificate-body {
  background: radial-gradient(120% 90% at 50% 0%, #1b2130 0%, #0d1015 62%, #07090c 100%);
}

/* ─── The sheet and its box ──────────────────────────────────────────────── */

.reveal__stage {
  display: flex;
  justify-content: center;
}

/* The sheet is A4 landscape in millimetres. `zoom` shrinks the box along with
   the content, which `scale` does not do — the same choice as the preview.

   Here the sheet is the whole page, so it is not constrained by width alone: on
   a short screen it is the height that rules, and someone turning their phone
   sideways to see the certificate bigger is exactly the most likely case. Two
   ladders, then, and the zoom is the smaller of the two.

   Without `min()` in `zoom` what is left is the width ladder, which is the one
   that matters on a phone held upright — the event's target. */
.reveal__sheet {
  --z-base: 1;
  --z-width: var(--z-base);
  --z-height: 1;

  position: relative;
  width: 297mm;
  zoom: var(--z-width);
}

@supports (zoom: min(1, 0.5)) {
  .reveal__sheet { zoom: min(var(--z-width), var(--z-height)); }
}

@media (max-width: 1160px) { .reveal__sheet { --z-base: 0.92; } }
@media (max-width: 1060px) { .reveal__sheet { --z-base: 0.83; } }
@media (max-width: 960px)  { .reveal__sheet { --z-base: 0.74; } }
@media (max-width: 860px)  { .reveal__sheet { --z-base: 0.65; } }
@media (max-width: 760px)  { .reveal__sheet { --z-base: 0.56; } }
@media (max-width: 660px)  { .reveal__sheet { --z-base: 0.47; } }
@media (max-width: 560px)  { .reveal__sheet { --z-base: 0.40; } }
@media (max-width: 480px)  { .reveal__sheet { --z-base: 0.35; } }
@media (max-width: 420px)  { .reveal__sheet { --z-base: 0.31; } }
@media (max-width: 380px)  { .reveal__sheet { --z-base: 0.28; } }
@media (max-width: 340px)  { .reveal__sheet { --z-base: 0.25; } }

@media (max-height: 1080px) { .reveal__sheet { --z-height: 0.93; } }
@media (max-height: 1000px) { .reveal__sheet { --z-height: 0.81; } }
@media (max-height: 900px)  { .reveal__sheet { --z-height: 0.71; } }
@media (max-height: 820px)  { .reveal__sheet { --z-height: 0.61; } }
@media (max-height: 740px)  { .reveal__sheet { --z-height: 0.50; } }
@media (max-height: 660px)  { .reveal__sheet { --z-height: 0.40; } }

/* Below 620px of height the page becomes two columns (see the end of the
   sheet), and then what limits the sheet is almost only the height. */
@media (max-height: 620px) { .reveal__sheet { --z-height: 0.63; } }
@media (max-height: 560px) { .reveal__sheet { --z-height: 0.58; } }
@media (max-height: 520px) { .reveal__sheet { --z-height: 0.53; } }
@media (max-height: 480px) { .reveal__sheet { --z-height: 0.48; } }
@media (max-height: 440px) { .reveal__sheet { --z-height: 0.43; } }
@media (max-height: 400px) { .reveal__sheet { --z-height: 0.38; } }
@media (max-height: 360px) { .reveal__sheet { --z-height: 0.33; } }
@media (max-height: 320px) { .reveal__sheet { --z-height: 0.28; } }

/* Everything that moves lives here. `.certificate`, inside, does not get a
   single rule from this sheet. */
.reveal__frame {
  position: relative;
  overflow: hidden;
  transform-origin: 50% 45%;
  box-shadow:
    0 1mm 3mm rgba(0, 0, 0, 0.35),
    0 4mm 18mm var(--paper-shadow);
}

.reveal--playing .reveal__frame { will-change: transform, opacity; }

/* ─── Ornaments, all at rest by default ─────────────────────────────────── */

.reveal__veil,
.reveal__beam,
.reveal__grid,
.reveal__glow,
.reveal__sheen,
.reveal__wave,
.reveal__sparks {
  position: absolute;
  pointer-events: none;
}

/* The veil is an opaque lid in the colour of the backdrop. Revealing is it
   going down: pure transform, no clip-path and no repainting the sheet every
   frame. At rest it is already entirely below the sheet. */
.reveal__veil {
  inset: 0;
  background: var(--backdrop);
  transform: translateY(100%);
}

.reveal--playing .reveal__veil { will-change: transform; }

/* The beam runs glued to the bottom edge of the veil: both start from the top
   with the same curve, so the light never comes off the line of the reveal. */
.reveal__beam {
  left: 0;
  right: 0;
  top: 0;
  height: 2.4mm;
  opacity: 0;
  background: linear-gradient(to bottom, rgba(124, 231, 255, 0), var(--neon) 55%, #fff);
  box-shadow: 0 0 9mm 1.5mm rgba(124, 231, 255, 0.5);
}

.reveal__grid {
  inset: 0;
  opacity: 0;
  background: repeating-linear-gradient(
    to bottom,
    rgba(124, 231, 255, 0.16) 0 1px,
    rgba(124, 231, 255, 0) 1px 4px
  );
}

.reveal__glow {
  inset: 0;
  opacity: 0;
  background: radial-gradient(60% 60% at 50% 45%, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0) 70%);
}

.reveal__sheen {
  top: -10%;
  bottom: -10%;
  left: 0;
  width: 38%;
  opacity: 0;
  background: linear-gradient(100deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0));
}

/* The wave and the sparks stay outside the frame so its `overflow: hidden`
   does not clip them at the edge of the sheet. */
.reveal__wave {
  left: 50%;
  top: 45%;
  width: 70mm;
  height: 70mm;
  margin: -35mm 0 0 -35mm;
  border: 1.4mm solid var(--ruby);
  border-radius: 50%;
  opacity: 0;
}

.reveal__sparks {
  inset: 0;
  opacity: 0;
}

.reveal__sparks i {
  position: absolute;
  left: 50%;
  top: 45%;
  width: 3mm;
  height: 3mm;
  margin: -1.5mm 0 0 -1.5mm;
  border-radius: 0.6mm;
  background: var(--ruby);
  opacity: 0;
}

.reveal__sparks i:nth-child(3n)   { background: var(--gold); }
.reveal__sparks i:nth-child(5n)   { background: #f4efee; }

.reveal__sparks i:nth-child(1)  { --a: 8deg;   --d: 34mm; --t: 0ms; }
.reveal__sparks i:nth-child(2)  { --a: 32deg;  --d: 46mm; --t: 40ms; }
.reveal__sparks i:nth-child(3)  { --a: 57deg;  --d: 30mm; --t: 15ms; }
.reveal__sparks i:nth-child(4)  { --a: 79deg;  --d: 52mm; --t: 60ms; }
.reveal__sparks i:nth-child(5)  { --a: 104deg; --d: 38mm; --t: 10ms; }
.reveal__sparks i:nth-child(6)  { --a: 128deg; --d: 48mm; --t: 55ms; }
.reveal__sparks i:nth-child(7)  { --a: 151deg; --d: 32mm; --t: 25ms; }
.reveal__sparks i:nth-child(8)  { --a: 176deg; --d: 44mm; --t: 70ms; }
.reveal__sparks i:nth-child(9)  { --a: 199deg; --d: 36mm; --t: 5ms; }
.reveal__sparks i:nth-child(10) { --a: 223deg; --d: 50mm; --t: 45ms; }
.reveal__sparks i:nth-child(11) { --a: 248deg; --d: 33mm; --t: 20ms; }
.reveal__sparks i:nth-child(12) { --a: 272deg; --d: 45mm; --t: 65ms; }
.reveal__sparks i:nth-child(13) { --a: 296deg; --d: 31mm; --t: 30ms; }
.reveal__sparks i:nth-child(14) { --a: 331deg; --d: 42mm; --t: 50ms; }

/* ─── Variant 1: `holographic` ───────────────────────────────────────────────
   Genuinely futuristic. A cyan beam sweeps the sheet from top to bottom and the
   certificate materialises behind it, while the whole sheet comes out of a tilt
   in perspective and settles flat. Scan lines on top fade away at the end. Only
   transform and opacity — nothing repaints. */

.reveal--holographic { --accent: var(--neon); }

.reveal--playing.reveal--holographic .reveal__frame {
  animation: holo-settle 1150ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

.reveal--playing.reveal--holographic .reveal__veil {
  animation: holo-sweep 1000ms cubic-bezier(0.42, 0, 0.25, 1) both;
}

.reveal--playing.reveal--holographic .reveal__beam {
  animation: holo-beam 1000ms cubic-bezier(0.42, 0, 0.25, 1) both;
}

.reveal--playing.reveal--holographic .reveal__grid {
  animation: holo-grid 1500ms ease-out both;
}

@keyframes holo-settle {
  from { transform: perspective(1400px) rotateX(11deg) scale(0.965); }
  to   { transform: perspective(1400px) rotateX(0deg) scale(1); }
}

@keyframes holo-sweep {
  from { transform: translateY(0); }
  to   { transform: translateY(100%); }
}

@keyframes holo-beam {
  0%   { transform: translateY(-2.4mm); opacity: 0; }
  8%   { opacity: 1; }
  90%  { opacity: 1; }
  100% { transform: translateY(207.6mm); opacity: 0; }
}

@keyframes holo-grid {
  0%   { opacity: 0.5; }
  55%  { opacity: 0.35; }
  100% { opacity: 0; }
}

/* ─── Variant 2: `stamp` ─────────────────────────────────────────────────────
   Ceremonial. The certificate falls from above and lands, with a flash, a
   shockwave in the GURUPI red and a burst of sparks. It is the variant built to
   become a photo. */

.reveal--stamp { --accent: var(--gold); }

.reveal--playing.reveal--stamp .reveal__frame {
  animation: stamp-strike 660ms cubic-bezier(0.2, 0.9, 0.25, 1) both;
}

.reveal--playing.reveal--stamp .reveal__glow {
  animation: stamp-flash 520ms 380ms ease-out both;
}

.reveal--playing.reveal--stamp .reveal__wave {
  animation: stamp-wave 900ms 400ms cubic-bezier(0.1, 0.8, 0.2, 1) both;
}

.reveal--playing.reveal--stamp .reveal__sparks { opacity: 1; }

.reveal--playing.reveal--stamp .reveal__sparks i {
  animation: stamp-spark 950ms cubic-bezier(0.12, 0.7, 0.3, 1) both;
  animation-delay: calc(400ms + var(--t));
}

@keyframes stamp-strike {
  0%   { opacity: 0; transform: scale(1.3) translateY(-1.5%); }
  55%  { opacity: 1; }
  78%  { transform: scale(0.985); }
  100% { opacity: 1; transform: scale(1); }
}

@keyframes stamp-flash {
  0%   { opacity: 0; }
  12%  { opacity: 0.7; }
  100% { opacity: 0; }
}

@keyframes stamp-wave {
  0%   { opacity: 0.85; transform: scale(0.15); }
  100% { opacity: 0; transform: scale(1.55); }
}

@keyframes stamp-spark {
  0%   { opacity: 0; transform: rotate(var(--a)) translateY(0) scale(0.4); }
  12%  { opacity: 1; }
  100% { opacity: 0; transform: rotate(var(--a)) translateY(calc(var(--d) * -1)) scale(0.9); }
}

/* ─── Variant 3: `curtain` ───────────────────────────────────────────────────
   The cheap one. It rises and appears, with a diagonal sheen crossing the
   paper. Half a second, two layers, no animated shadow: it is the one a weak
   phone runs without thinking, and it is the degradation floor of the other
   two. */

.reveal--curtain { --accent: #e8e4e3; }

.reveal--playing.reveal--curtain .reveal__frame {
  animation: curtain-rise 560ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

.reveal--playing.reveal--curtain .reveal__sheen {
  animation: curtain-sheen 950ms 240ms cubic-bezier(0.4, 0, 0.2, 1) both;
}

@keyframes curtain-rise {
  from { opacity: 0; transform: translateY(3.5%) scale(0.99); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes curtain-sheen {
  0%   { opacity: 0; transform: translateX(-60%) skewX(-16deg); }
  15%  { opacity: 0.55; }
  85%  { opacity: 0.5; }
  100% { opacity: 0; transform: translateX(180%) skewX(-16deg); }
}

/* ─── What the participant came for ──────────────────────────────────────── */

.reveal__actions {
  width: min(30rem, 100%);
  text-align: center;
  color: #e9e6e4;
}

/* It rises along with the rest, but never disappears: the opacity does not go
   below 0.35, nothing touches pointer-events, and there is no delay. The
   download button is clickable in the reveal's first frame. */
.reveal--playing .reveal__actions {
  animation: actions-in 450ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes actions-in {
  from { opacity: 0.35; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

.reveal__message {
  margin: 0 0 1rem;
  font-size: clamp(1.15rem, 4.5vw, 1.6rem);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--accent);
}

.reveal__download {
  display: block;
  padding: 0.95rem 1.25rem;
  font-family: inherit;
  font-size: 1.0625rem;
  font-weight: 700;
  text-decoration: none;
  color: #fff;
  background: #9c1420;
  border-radius: 8px;
  box-shadow: 0 6px 18px rgba(156, 20, 32, 0.35);
  transition: transform 120ms ease, box-shadow 120ms ease;
}

.reveal__download:active {
  transform: scale(0.985);
  box-shadow: 0 3px 10px rgba(156, 20, 32, 0.3);
}

.reveal__download:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}

.reveal__hint {
  margin: 0.75rem 0 0;
  font-size: 0.9375rem;
  color: #9aa1ab;
}

/* ─── Short screen: phone on its side, cramped laptop ────────────────────────
   Stacked there would be no paper left, so the sheet goes to one side and the
   actions to the other. The sheet now has less width available, and the `calc`
   discounts that from the same step already chosen by the screen's width.

   This comes here at the end, after the rules it overrides. */

@media (max-height: 620px) {
  .reveal {
    flex-direction: row;
    align-items: center;
    gap: 1.25rem;
  }

  .reveal__sheet { --z-width: calc(var(--z-base) * 0.66); }

  .reveal__actions { width: min(15rem, 32%); }

  .reveal__message {
    font-size: 1.0625rem;
    margin-bottom: 0.75rem;
  }

  .reveal__download { padding: 0.75rem 1rem; }

  .reveal__hint {
    margin-top: 0.5rem;
    font-size: 0.8125rem;
  }
}

/* ─── For whoever asked the world to hold still ─────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .reveal,
  .reveal *,
  .reveal *::before,
  .reveal *::after {
    animation: none !important;
    transition: none !important;
  }

  /* The resting state is already the final state, so it is enough to get the
     ornaments out of the way: what is left is the certificate and the button,
     exactly as they should be. */
  .reveal__veil,
  .reveal__beam,
  .reveal__grid,
  .reveal__glow,
  .reveal__sheen,
  .reveal__wave,
  .reveal__sparks {
    display: none !important;
  }
}
