/* ===========================================================================
   event-pin.css: the event pin, its tab and its dialog (brief 16), and the
   showcase that opens it big on arrival (brief 20).

   Built by shared/js/event-pin.js. Linked by the home page only, after
   pages.css. Every class starts ep-. Tokens only: not one raw colour.

   The pin is three fixed pieces inside one wrapper that takes no room:

     .ep-card   the tilted card (a compact ticket on a phone), holding
                .ep-pin, which opens the flyer, and .ep-small, which tucks it
     .ep-tab    the tab on the left edge that brings it back

   On a visit that arrives at the top of the page, three more come first
   (section 6b): .ep-scrim, .ep-show and .ep-show__small.

   event-pin.js writes three attributes on .ep:

     data-state   open | small       which of the two is showing
     data-phase   waiting            all hidden until the entrance
                  entering           GSAP owns them, so no transitions here
                  show               the showcase holds the stage: GSAP owns
                                     it and the pin, so no transitions here
                  in                 this file owns them, transitions on
     data-moving  while a tuck or a restore runs, so the way back takes the
                  same 450ms as the way in and not the hover's 300ms

   Mobile first, like the rest of the site: the phone ticket is the base, and
   the card is @media (min-width: 700px).

   Contrast, all measured in the default theme (the brief 16 report has the
   whole table): cream on char 17.5:1, gold on char 12.4:1, the cream-muted
   edges on char 10.1:1, and char against paper 17.5:1 cold to 15.2:1 hot,
   which pairings.json now guards as char-on-paper.
   =========================================================================== */

.ep {
  /* Under 700px the ticket and its tab sit above every film's scroll cue
     (Director, 2026-10-22), worked out from the films' own CSS. At 375x812
     and at 360x740 alike, --fs-label is its 11px floor (a 13.2px kicker line,
     line-height 1.2), --gutter is its 20px floor and --s-1 is 8px, so each
     cue's top sits this far above the bottom of the screen:
       film A  24 (--s-3) + 32 line (--s-4) + 8 gap + 13.2 word  =  77.2px
       film B  20 (--gutter) + 56 line (3.5rem) + 8 gap + 13.2   =  97.2px
       film C  24 (--s-3) + 36 line (2.25rem) + 8 gap + 13.2     =  81.2px
     The highest is film B's 97.2px; plus 8px of air is 105.2px, rounded up to
     106px. The -2.5deg tilt only lifts the ticket's right end, which is the
     end over the centred cue, so the air is never less than 8px there. */
  --ep-phone-bottom: calc(106px + env(safe-area-inset-bottom));

  /* The two offsets the card, the tab and the tuck all share. */
  --ep-left: 12px;
  --ep-bottom: var(--ep-phone-bottom);

  /* The tab's visible width: never wider than the page's gutter less 2px, so
     it can never sit on the film's words, which all start at --gutter.
     --gutter is clamp(1.25rem, 4vw, 4rem): 20px on a phone, so the tab shows
     18px there, and 57.6px at 1440 wide, so 44px on a desktop. */
  --ep-tab-w: min(44px, calc(var(--gutter) - 2px));

  /* The dish peek's shadow (afterfilm.css, .af-peek). */
  --ep-shadow: 0 1.5rem 3.75rem color-mix(in srgb, var(--char) 45%, transparent);
}

/* ---------------------------------------------------------------------------
   1. The card. On a phone, a compact ticket.
   --------------------------------------------------------------------------- */

.ep-card {
  position: fixed;
  left: var(--ep-left);
  bottom: var(--ep-bottom);
  z-index: var(--z-event);
  max-width: calc(100vw - 96px);

  /* Its own stacking context, so the glow below sits behind the card and
     in front of the page. */
  isolation: isolate;

  /* The resting tilt. The entrance lands exactly here. */
  transform: rotate(-2.5deg);
}

/* The glow: gold, breathing behind the card like a coal. Opacity only. The
   showcase (section 6b) glows the same way, from the same rule, with its
   halo held to 40px there. */
.ep-card::before,
.ep-show::before {
  content: "";
  position: absolute;
  inset: -24% -30%;
  z-index: -1;
  background: radial-gradient(
    closest-side,
    color-mix(in srgb, var(--gold) 35%, transparent),
    transparent
  );
  opacity: 0.35;
  pointer-events: none;
  animation: ep-breathe 3.6s ease-in-out infinite;
}

@keyframes ep-breathe {
  0%,
  100% {
    opacity: 0.35;
  }
  50% {
    opacity: 0.75;
  }
}

/* The ticket: a 44 by 56 thumbnail, then the short name and the date on two
   lines, then room on the right for the small button. 64px tall. */
.ep-pin {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  height: 64px;
  max-width: 100%;
  margin: 0;
  padding: 4px 48px 4px 4px;
  border: 0;
  border-radius: 0;
  background: var(--char);
  color: var(--cream);
  text-align: left;
  cursor: pointer;

  /* A cream-muted edge (10.1:1 on char), so the ticket's outline reaches 3:1
     over the dark film as well as over paper, plus the dish peek's shadow. */
  box-shadow: 0 0 0 1px var(--cream-muted), var(--ep-shadow);
}

.ep-pin__print {
  display: block;
  flex: none;
  width: 44px;
  height: 56px;
}

.ep-pin__pic {
  display: block;
  width: 100%;
  height: 100%;
}

.ep-pin__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.ep-pin__cap {
  display: block;
  min-width: 0;
}

/* The short name in cream and the date in gold, both Bungee at label size. */
.ep-pin__word,
.ep-pin__date {
  display: block;
  font-family: var(--font-sign);
  font-weight: 400;
  font-size: var(--fs-label);
  line-height: 1.2;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ep-pin__word {
  color: var(--cream);         /* 17.5:1 on char */
}

.ep-pin__date {
  color: var(--gold);          /* 12.4:1 on char */
}

/* "Make it small": a 28px circle you can see, in a 44 by 44 target. On the
   ticket it sits inside the right end. */
.ep-small {
  position: absolute;
  top: 10px;
  right: 0;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--cream);
  cursor: pointer;
}

.ep-small__dot {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: 1px solid var(--cream-muted);   /* 10.1:1 on char */
  border-radius: var(--r-pill);
  background: var(--char);
}

.ep-small__dot svg {
  display: block;
}

/* ---------------------------------------------------------------------------
   2. The card, from 700px: the print pinned to the corner.

   Its width is measured, not guessed (Director, 2026-10-22): event-pin.js
   works out the room between the film's lowest words in the pin's column and
   the pin's bottom offset, at mount and on resize only, and writes the widest
   card that fits as --ep-card-w, between 140px and min(13vw, 196px). Where
   not even 140px fits but the 64px ticket does, it sets data-form="ticket" on
   .ep, and every card rule below steps aside, so the phone ticket's own rules
   from section 1 draw it, exactly as on a phone, at the desktop offsets.
   --------------------------------------------------------------------------- */

@media (min-width: 700px) {
  .ep {
    --ep-left: clamp(1rem, 2vw, 2rem);
    --ep-bottom: calc(clamp(1rem, 3vh, 2rem) + env(safe-area-inset-bottom));
  }

  /* The fallback is the same range event-pin.js clamps to, for the moment
     before it has measured. */
  .ep:not([data-form="ticket"]) .ep-card {
    width: var(--ep-card-w, clamp(140px, 13vw, 196px));
    max-width: none;
  }

  .ep:not([data-form="ticket"]) .ep-pin {
    display: block;
    width: 100%;
    height: auto;
    padding: 0;
    background: var(--paper);

    /* A 1px char edge, so the paper print reaches 15.2:1 or more against the
       paper sections it floats over (the paper border alone would be 1:1
       there). Over the dark film the paper border itself is the edge. */
    box-shadow: 0 0 0 1px var(--char), var(--ep-shadow);
  }

  /* The 6px paper print border round the picture. */
  .ep:not([data-form="ticket"]) .ep-pin__print {
    width: auto;
    height: auto;
    padding: 6px;
    background: var(--paper);
  }

  .ep:not([data-form="ticket"]) .ep-pin__pic,
  .ep:not([data-form="ticket"]) .ep-pin__img {
    height: auto;
  }

  /* The caption strip on char. Its two lines stay one line each, as on the
     ticket (section 1), because the card's height is worked out from them. */
  .ep:not([data-form="ticket"]) .ep-pin__cap {
    padding: 0.5rem 0.625rem 0.625rem;
    background: var(--char);
  }

  /* The small button on the card's top right corner. */
  .ep:not([data-form="ticket"]) .ep-small {
    top: -14px;
    right: -14px;
  }
}

/* ---------------------------------------------------------------------------
   3. The tab on the left edge.
   --------------------------------------------------------------------------- */

/* The button is the target: at least 24px wide and 44px tall. WCAG 2.5.8's
   spacing exception covers the 24px, because no other target is near it.
   Its face, inside, is what shows. */
.ep-tab {
  position: fixed;
  left: 0;
  bottom: var(--ep-bottom);
  z-index: var(--z-event);
  display: flex;
  align-items: stretch;
  justify-content: flex-start;
  width: max(24px, var(--ep-tab-w));
  min-height: 44px;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--gold);
  cursor: pointer;
}

/* Gold on char, 12.4:1, with a cream-muted edge (10.1:1) so the tab still
   reads as a control over the dark film. The corners on the open side are
   the sign board's 6px. */
.ep-tab__face {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--ep-tab-w);
  min-height: 44px;
  padding: 0.875rem 0;
  border: 1px solid var(--cream-muted);
  border-left: 0;
  border-radius: 0 var(--r-tag) var(--r-tag) 0;
  background: var(--char);

  font-family: var(--font-sign);
  font-weight: 400;
  /* The word, written up the face, scaled to fit: label size, or smaller if
     the face is ever too narrow for it. */
  font-size: min(var(--fs-label), calc(var(--ep-tab-w) - 6px));
  line-height: 1;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

/* The short name written up the edge. On the word rather than the button,
   because the button's own transform is the tuck movement. */
.ep-tab__word {
  display: block;
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   4. Focus. Design system section 10: 2px gold, 3px offset.

   The pin floats over the paper sections as well as the film, and gold on
   paper is only 1.2 to 1.4 to 1, so a gold ring alone would all but vanish
   there. The 3px gap inside each ring is therefore filled with char: the ring
   always sits on char (12.4:1), and the char band stands out from paper
   (15.2:1 or more). Over the dark film the band simply disappears.
   --------------------------------------------------------------------------- */

.ep-pin:focus-visible,
.ep-show:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
}

/* The showcase's paper print is its own edge; the band goes round it. */
.ep-show:focus-visible {
  box-shadow: 0 0 0 3px var(--char);
}

/* The ticket keeps its cream-muted edge inside the band; the card, from
   700px, has a char edge already, so the band simply widens it. */
.ep-pin:focus-visible {
  box-shadow: 0 0 0 1px var(--cream-muted), 0 0 0 3px var(--char), var(--ep-shadow);
}

@media (min-width: 700px) {
  .ep:not([data-form="ticket"]) .ep-pin:focus-visible {
    box-shadow: 0 0 0 3px var(--char), var(--ep-shadow);
  }
}

/* The tab's ring goes round the face you can see, like the small button's. */
.ep-tab:focus-visible {
  outline: none;
}

.ep-tab:focus-visible .ep-tab__face {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
  box-shadow: 0 0 0 3px var(--char);
}

/* The small button's ring goes round the circle you can see, not round its
   larger invisible target. The showcase's small button is the same control. */
.ep-small:focus-visible,
.ep-show__small:focus-visible {
  outline: none;
}

.ep-small:focus-visible .ep-small__dot,
.ep-show__small:focus-visible .ep-small__dot {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
  box-shadow: 0 0 0 3px var(--char);
}

/* ---------------------------------------------------------------------------
   5. Open and small.

   Tucking: the card shrinks into the tab's place at the bottom left and
   fades, and the tab slides in from the edge, both over 450ms on
   --ease-smoke. Restoring is the same in reverse. visibility follows the
   fade, so whichever one is gone cannot be reached by Tab or a screen reader.
   --------------------------------------------------------------------------- */

.ep[data-state="open"] .ep-card {
  transition: transform 300ms var(--ease-smoke), opacity 450ms var(--ease-smoke),
    visibility 0s linear 0s;
}

.ep[data-state="open"] .ep-tab {
  opacity: 0;
  visibility: hidden;
  transform: translateX(-100%);
  transition: transform 450ms var(--ease-smoke), opacity 450ms var(--ease-smoke),
    visibility 0s linear 450ms;
}

.ep[data-state="small"] .ep-card {
  opacity: 0;
  visibility: hidden;
  /* Its centre to the tab's: the middle of the tab's face, a little above
     the shared bottom line, at a fifth of the size. */
  transform: translate(calc(-50% - var(--ep-left) + var(--ep-tab-w) / 2), calc(50% - 2.5rem)) scale(0.2);
  transition: transform 450ms var(--ease-smoke), opacity 450ms var(--ease-smoke),
    visibility 0s linear 450ms;
}

.ep[data-state="small"] .ep-card::before {
  animation-play-state: paused;
}

.ep[data-state="small"] .ep-tab {
  transition: transform 450ms var(--ease-smoke), opacity 450ms var(--ease-smoke),
    visibility 0s linear 0s;
}

/* The way back takes as long as the way in. */
.ep[data-moving] .ep-card {
  transition-duration: 450ms, 450ms, 0s;
}

/* Hover and keyboard focus, on a fine pointer: the card straightens and lifts
   4px over 300ms. Never under reduced motion. */
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .ep[data-phase="in"][data-state="open"] .ep-card:hover,
  .ep[data-phase="in"][data-state="open"] .ep-card:focus-within {
    transform: translateY(-4px) rotate(0deg);
  }
}

/* ---------------------------------------------------------------------------
   6. The entrance phases. Last, so they win over section 5.
   --------------------------------------------------------------------------- */

/* Until the entrance, neither piece is seen or reached. */
.ep[data-phase="waiting"] .ep-card,
.ep[data-phase="waiting"] .ep-tab {
  opacity: 0;
  visibility: hidden;
}

/* While GSAP moves them, CSS must not chase every frame with a transition. */
.ep:not([data-phase="in"]) .ep-card,
.ep:not([data-phase="in"]) .ep-tab {
  transition: none;
}

/* ---------------------------------------------------------------------------
   6b. The showcase: the flyer big on arrival (brief 20).

   Built by event-pin.js only for a visit that arrives at the top of the page,
   inside .ep and before the pin: the veil, the big flyer, its small button.
   Its box is measured in px at mount and in every ScrollTrigger refresh,
   from window.innerHeight at that moment (no 100vh anywhere), and written on
   .ep as --ep-show-l, --ep-show-b, --ep-show-w and --ep-show-h: left,
   bottom, width and height. It is anchored at the bottom, like the pin it
   lands on, so a phone's toolbar moves the two together. From the entrance
   on GSAP owns opacity, visibility and the transform of all three, so
   nothing here transitions.

   The layers (Director, 2026-10-22): the veil sits at --z-event, with the
   pin, under the top bar (--z-nav, 50), which stays above it and usable.
   The showcase and its small button sit at --z-show (56), above the
   thermometer (--z-thermo, 55), whose labels a phone's showcase crosses, so
   nothing draws over the big flyer. showcaseBox() keeps it 24px clear of the
   top bar, and the menu overlay (--z-overlay, 80) still covers it.
   --------------------------------------------------------------------------- */

/* The soft dark veil: char at 55 percent over the whole window. It never
   takes a click. */
.ep-scrim {
  position: fixed;
  inset: 0;
  z-index: var(--z-event);
  background: color-mix(in srgb, var(--char) 55%, transparent);
  pointer-events: none;
}

/* The big flyer. The button is the target and carries the name; the print
   inside it is what shows, so the glow (section 1) can sit between the two. */
.ep-show {
  position: fixed;
  left: var(--ep-show-l);
  bottom: var(--ep-show-b);
  z-index: var(--z-show);
  width: var(--ep-show-w);
  height: var(--ep-show-h);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  cursor: pointer;

  /* Its own stacking context, so the glow sits behind the print and in
     front of the page. */
  isolation: isolate;

  /* The resting tilt. The entrance lands exactly here. */
  transform: rotate(-1.5deg);
}

/* The showcase's glow is the pin's (section 1), breathing the same way, but
   its halo reaches no more than 40px beyond the print on any side, in px,
   because a percentage of a box this big would be a flood of gold. Same
   selector weight as the shared rule and later, so this inset wins. */
.ep-show::before {
  inset: -40px;
}

/* The 8px paper print border round the whole flyer (event-pin.js's
   SHOW_BORDER), with the card's own print shadow: the 1px char edge and the
   dish peek's shadow. */
.ep-show__pic {
  width: 100%;
  height: 100%;
  padding: 8px;
  background: var(--paper);
  box-shadow: 0 0 0 1px var(--char), var(--ep-shadow);
}

/* Char under the picture, so a picture that arrives late shows a dark print
   inside the paper border, never bare white paper. Once drawn it covers
   this ground entirely. */
.ep-show__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--char);
}

/* "Make it small": the pin's own 28px circle (.ep-small__dot, section 1) in
   a 44 by 44 target, at the showcase's top right corner exactly as the
   card's sits on the card: the target 14px out over the top and the right
   edge, which puts its left edge 30px in from the right and its bottom edge
   30px below the top. Its transform-origin is the showcase's centre, seen
   from the target's own top left, so the same x, y, scale and tilt move it
   exactly with the showcase, and it rests at the same tilt. */
.ep-show__small {
  position: fixed;
  left: calc(var(--ep-show-l) + var(--ep-show-w) - 30px);
  bottom: calc(var(--ep-show-b) + var(--ep-show-h) - 30px);
  z-index: var(--z-show);
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--cream);
  cursor: pointer;
  transform: rotate(-1.5deg);
  transform-origin: calc(30px - var(--ep-show-w) / 2) calc(var(--ep-show-h) / 2 + 14px);
}

/* Until the entrance, none of the three is seen or reached. */
.ep[data-phase="waiting"] .ep-scrim,
.ep[data-phase="waiting"] .ep-show,
.ep[data-phase="waiting"] .ep-show__small {
  opacity: 0;
  visibility: hidden;
}

/* Once the pin holds the stage the showcase is hidden, and its glow rests. */
.ep[data-phase="in"] .ep-show::before {
  animation-play-state: paused;
}

/* ---------------------------------------------------------------------------
   7. The dialog: the whole flyer, with every word as real text.

   A native <dialog> opened with showModal(), so the browser gives it the top
   layer, the focus trap, the inert page behind it and Escape. The body fills
   the whole box and is what scrolls, so the close button stays put and a
   click whose target is the dialog itself can only be on the backdrop.
   --------------------------------------------------------------------------- */

/* Centred on both axes at every size. The browser's own sheet centres a
   modal dialog with margin: auto, but base.css's reset (* { margin: 0 })
   is an author rule and beats it, which left the dialog at the top left. This
   rule (one class, so it outranks the reset's *) puts the centring back
   explicitly: fixed to the viewport, all four insets 0, a height that fits
   its content, and auto margins, which then share the free space equally on
   both axes. */
.ep-dialog {
  position: fixed;
  inset: 0;
  margin: auto;
  width: calc(100vw - 24px);
  height: fit-content;
  max-width: none;
  max-height: 92vh;
  padding: 0;
  border: 1px solid var(--cream-muted);   /* 10.1:1 on char */
  background: var(--char);
  color: var(--cream);                    /* 17.5:1 on char */
  overflow: hidden;
}

.ep-dialog[open] {
  display: flex;
  flex-direction: column;
}

.ep-dialog::backdrop {
  background: color-mix(in srgb, var(--char) 72%, transparent);
}

/* The nav overlay's close button, moved into the dialog's top right corner,
   inside the top padding so it never sits on the words. It keeps a char
   background so text scrolling under it on a phone cannot show through. */
.ep-close {
  top: 0;
  right: var(--s-1);
  z-index: 1;
  background: var(--char);
}

/* Phones: --s-3 round the sides and the bottom. The top keeps --s-5 (48px),
   because the 44px close button sits in that band and must not cover the
   flyer under it. */
.ep-dialog__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--s-5) var(--s-3) var(--s-3);
}

/* Phones: one column, the picture first. */
.ep-dialog__media {
  margin-bottom: var(--s-4);
}

.ep-dialog__pic {
  display: block;
}

.ep-dialog__img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 82vh;
  object-fit: contain;
  object-position: left top;
}

.ep-dialog__words {
  max-width: 34ch;
}

.ep-dialog__kicker {
  color: var(--gold);                     /* 12.4:1 on char */
}

.ep-dialog__title {
  margin-top: var(--s-1);
  color: var(--cream);
}

.ep-dialog__date {
  margin-top: var(--s-2);
  color: var(--gold);
}

.ep-dialog__line {
  margin-top: var(--s-3);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

/* "Menu items" at label size and the price at the design system's price
   size, both Bungee. */
.ep-dialog__price {
  margin-top: var(--s-3);
  font-family: var(--font-sign);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

.ep-dialog__pricelabel {
  font-size: var(--fs-label);
}

.ep-dialog__money {
  font-size: var(--fs-d3);
}

.ep-dialog__items {
  margin-top: var(--s-2);
  padding-left: 1.25em;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--cream);
}

.ep-dialog__items li {
  margin-top: 0.25rem;
}

.ep-dialog__actions {
  margin-top: var(--s-4);
}

/* From 700px: two columns, the flyer left and the words right. */
@media (min-width: 700px) {
  .ep-dialog {
    width: min(calc(100vw - 3rem), 68rem);
  }

  .ep-dialog__body {
    padding: var(--s-5);
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 34ch);
    gap: var(--s-4);
    align-items: start;
  }

  .ep-dialog__media {
    margin-bottom: 0;
  }
}

/* ---------------------------------------------------------------------------
   8. Reduced motion: no glow loop, no hover lift (section 5 asks for
   no-preference), an instant tuck and restore. Everything still works.
   --------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .ep-card::before,
  .ep-show::before {
    animation: none;
  }

  .ep[data-state] .ep-card,
  .ep[data-state] .ep-tab {
    transition: none;
  }
}
