/* Comments are whole lines, so a deploy can strip them line by line. */

/* TCBW Sans: the site's own name for its typeface, as the files are named by
   role, not by the family, so a later swap of typeface touches only these
   three rules and nothing that uses the name. */
@font-face {
  font-family: "TCBW Sans";
  src: url("sans-regular.woff2") format("woff2");
  font-weight: 400;
}

@font-face {
  font-family: "TCBW Sans";
  src: url("sans-medium.woff2") format("woff2");
  font-weight: 500;
}

@font-face {
  font-family: "TCBW Sans";
  src: url("sans-bold.woff2") format("woff2");
  font-weight: 700;
}

:root {
  /* Ink: the title, the pills' outline and the filled pill (Figma style B). */
  --ink: #111;
  /* The intro and the footer, a step softer than the ink. */
  --text: #222;
  /* The light half of the title and the filled pill's label (Figma style W). */
  --paper: #fff;
  /* The gradient the footage is blended onto: its top and bottom. */
  --bg-top: #e0e400;
  --bg-bottom: #a7e71e;
  /* The colour under everything, where a browser paints past the page (a
     bar, an overscroll): the footage's most common tint after the blend, a
     light periwinkle, so it does not flash the gradient's lime. The pages'
     theme-color says the same. */
  --ground: #a9c6ff;
  /* The screen's own edges on a phone (notch, status bar, home indicator;
     0 elsewhere). The page draws under them, viewport-fit=cover, so the
     footage reaches every edge, and keeps words and controls this far in. */
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);

  --font: "TCBW Sans", -apple-system, sans-serif;
  /* The title: size, line spacing, letter spacing, weight, and the width
     that breaks it into Tallinn / Craft beer / Weekend / 2027 / the dates. */
  --title-size: 72px;
  --title-leading: 64px;
  --title-tracking: -1px;
  --title-weight: 400;
  --title-width: 497px;
  /* The intro beside the title: size, line spacing, column width, the space
     between its paragraphs, and the weight of its first line. */
  --intro-size: 21px;
  --intro-leading: 24px;
  --intro-width: 365px;
  --intro-gap: 8px;
  --intro-lead-weight: 700;
  /* The line-up note, a sticker on the intro's top corner: its fill (the
     yellow of the Figma style Y), its type, its padding, its tilt, and how
     far it sits above the intro's first line and out past its right edge. */
  --sticker: #ffdd00;
  --sticker-size: 21px;
  --sticker-leading: 24px;
  --sticker-weight: 500;
  --sticker-pad-y: 6px;
  --sticker-pad-x: 14px;
  --sticker-tilt: -4deg;
  --sticker-rise: 48px;
  --sticker-out: 24px;
  /* The small credit in the corner: size and line spacing. */
  --credit-size: 12px;
  --credit-leading: 16px;

  /* The block of title, intro and pills: its width, how far the title and
     intro sit in from the pills' edge, and the space down to the pills. */
  --block-width: 923px;
  --block-inset: 4px;
  --block-gap: 59px;
  /* Lifts the block above the window's true centre by half of this, as the
     Figma frame does; a centred block reads as sitting low. */
  --block-lift: 40px;
  /* The credit's distance from the window's left and bottom edges. */
  --credit-x: 20px;
  --credit-y: 16px;

  /* The pills: label size, line spacing and weight, the outline's thickness,
     the space from the outline's outer edge to the label, the corners, and
     the space between two pills. */
  --pill-size: 40px;
  --pill-leading: 40px;
  --pill-weight: 500;
  --pill-line: 3px;
  --pill-pad-y: 8px;
  --pill-pad-x: 20px;
  --pill-radius: 48px;
  --pill-gap: 8px;
  /* How long a pill takes to fill on hover: a colour fade for something
     crossed now and then (Motion, Hover). */
  --pill-fade: 150ms;

  /* The keyboard focus ring: its thickness and its distance from the edge. */
  --focus-line: 2px;
  --focus-offset: 2px;

  /* The dark laid over the page under the gallery and the photo. */
  --veil: rgb(17 17 17 / 0.92);
  /* The gallery and the photo open and close on the Dialog recipe (Motion,
     Recipes): from this scale, over this long, on this curve. */
  --dialog-from: 0.96;
  --dialog-time: 250ms;
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  /* The gallery's distance from the window's edges, the narrowest a column
     of photos gets before the grid drops one, the photos' corners, and the
     space between two. */
  --gallery-edge: 20px;
  --thumb-min: 320px;
  --photo-radius: 24px;
  --thumb-gap: 8px;
  /* The room the full-size photo leaves on its sides for the arrows, and
     above and below for the close pill. */
  --viewer-side: 120px;
  --viewer-top: 96px;

  /* A page of text, like Volunteers: how far down its title starts, so it
     sits where the front page's does in a 1440×800 window, and the room
     under the last line. */
  --page-top: 163px;
  --page-bottom: 120px;
  /* How the page's width is shared between the title and the text, and the
     space between the two (the front page's, between its title and intro). */
  --page-split: 1fr 2fr;
  --page-gutter: 53px;
  /* The extra space above a heading in the text, a line's worth. */
  --section-gap: 24px;

  /* On a phone (see PHONE at the end): the words' distance from the
     screen's edges, the pills' (nearer the edges than the words), the space
     between the title and the intro once they stack, and how many columns
     the gallery has. */
  --phone-edge: 20px;
  --nav-edge: 8px;
  /* How far in from the top and bottom of a phone's screen the footage
     fades to the ground colour that Safari fills its bars with. */
  --edge-fade: 64px;
  --stack-gap: 24px;
  --phone-columns: 2;
}

/* The page holds still under the gallery (Motion, Recipes: Dialog):
   a modal dialog makes the page inert but would still let it scroll, and a
   scrollbar that goes would shift the page sideways. */
html {
  scrollbar-gutter: stable;
}

html:has(dialog:modal) {
  overflow: hidden;
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* The block is centred in the window at its Figma sizes; narrow windows wait
   for the mobile design rather than a guessed scale. */
body {
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left);
  font-family: var(--font);
  color: var(--text);
  background: var(--ground);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}


/* BACKGROUND
   The footage on top of the gradient with the "hue" blend mode, as in the
   Figma BG frame: only the video's hue survives, the gradient supplies the
   brightness and saturation. */

/* As tall as what is in view, so its top and bottom edges are where a phone
   browser's own bars begin. Safari does not draw the page behind those bars;
   it fills them with one colour from the page (--ground), and the phone's
   edge fade below meets that colour there. */
.bg {
  position: fixed;
  inset: 0;
  z-index: -1;
  isolation: isolate;
  background: linear-gradient(180deg, var(--bg-top), var(--bg-bottom));
}

.bg video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  mix-blend-mode: hue;
}


/* INTRO */

.intro {
  width: var(--block-width);
  padding-bottom: var(--block-lift);
  display: flex;
  flex-direction: column;
  gap: var(--block-gap);
}

/* Title and intro share a baseline at the bottom, so the intro grows upward
   when its copy changes. */
.intro-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  padding-inline: var(--block-inset);
}

h1 {
  width: var(--title-width);
  font-size: var(--title-size);
  line-height: var(--title-leading);
  letter-spacing: var(--title-tracking);
  font-weight: var(--title-weight);
  text-transform: uppercase;
  color: var(--ink);
}

h1 .light {
  color: var(--paper);
}

.lead {
  width: var(--intro-width);
  font-size: var(--intro-size);
  line-height: var(--intro-leading);
}

.lead > * + *,
.lead li + li {
  margin-top: var(--intro-gap);
}

/* The sticker is laid on top of the column, not in it, so the intro keeps
   the Figma frame's place and lines; the copy after it starts as if it
   were not there. */
.lead {
  position: relative;
}

.sticker {
  position: absolute;
  top: calc(-1 * var(--sticker-rise));
  right: calc(-1 * var(--sticker-out));
  margin: 0;
  padding: var(--sticker-pad-y) var(--sticker-pad-x);
  border-radius: var(--pill-radius);
  background: var(--sticker);
  color: var(--ink);
  font-size: var(--sticker-size);
  line-height: var(--sticker-leading);
  font-weight: var(--sticker-weight);
  white-space: nowrap;
  transform: rotate(var(--sticker-tilt));
}

.lead > .sticker + * {
  margin-top: 0;
}

.lead b,
.lead h2 {
  font-weight: var(--intro-lead-weight);
}

.lead h2 {
  font-size: inherit;
  line-height: inherit;
}

.lead > h2:not(:first-child) {
  margin-top: var(--section-gap);
}

/* The bullets hang outside the column, so every line starts on the same
   edge as the paragraphs around it. */
.lead ul {
  list-style-position: outside;
}

.lead a,
.credit a {
  color: inherit;
  text-decoration: underline;
  text-decoration-skip-ink: none;
  text-underline-position: from-font;
}


/* PILLS
   The pills are one strip of hit area (Motion, Hover: neighbours touch): the
   space between two is the links' padding, not a gap, so a click between
   them lands on one. The row's negative margin keeps the first pill's
   outline in line with the block's edge. */

.pills {
  display: flex;
  margin-inline: calc(var(--pill-gap) / -2);
}

.pill {
  padding-inline: calc(var(--pill-gap) / 2);
  text-decoration: none;
}

/* Gallery opens a dialog, so it is a button; it wears the pill the links do. */
button.pill {
  font: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}

/* The face of the pill, inside the link's wider hit box. The outline sits
   inside the Figma size, so the padding gives back its thickness. Multiply
   is on every pill, not only the filled ones: the filled pill's white label
   then shows the footage through it, and an outlined pill's ink looks the
   same either way, so a hover can fade in and out without the blend mode
   snapping half-way. */
.pill > span {
  display: block;
  padding: calc(var(--pill-pad-y) - var(--pill-line)) calc(var(--pill-pad-x) - var(--pill-line));
  border: var(--pill-line) solid var(--ink);
  border-radius: var(--pill-radius);
  font-size: var(--pill-size);
  line-height: var(--pill-leading);
  font-weight: var(--pill-weight);
  white-space: nowrap;
  color: var(--ink);
  mix-blend-mode: multiply;
  transition: background-color var(--pill-fade) ease, color var(--pill-fade) ease;
}

.pill-primary > span {
  background-color: var(--ink);
  color: var(--paper);
}

/* Only where there is a mouse: a tap on a touch screen would leave it filled. */
@media (hover: hover) and (pointer: fine) {
  .pill:hover > span {
    background-color: var(--ink);
    color: var(--paper);
  }
}

/* The pill draws its own focus ring, on the face: the link's box is wider
   than the pill by the half gaps, so a ring on it would float off the
   outline. */
.pill:focus-visible {
  outline: none;
}

.pill:focus-visible > span {
  outline: var(--focus-line) solid var(--ink);
  outline-offset: var(--focus-offset);
}

.lead a:focus-visible,
.credit a:focus-visible {
  outline: var(--focus-line) solid var(--ink);
  outline-offset: var(--focus-offset);
}

/* The pill on the dark veil: drawn in paper, and without Multiply, which on
   a dark ground would make paper vanish. Its hover fills with paper. */
.pill-light > span {
  border-color: var(--paper);
  color: var(--paper);
  mix-blend-mode: normal;
}

@media (hover: hover) and (pointer: fine) {
  .pill-light:hover > span {
    background-color: var(--paper);
    color: var(--ink);
  }
}

.pill-light:focus-visible > span {
  outline-color: var(--paper);
}


/* GALLERY
   Two full-window dialogs, the grid and the photo. Unlike the recipe's
   centred dialog they fill the window, so their box is the window. */

.gallery,
.viewer {
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  border: 0;
  background: transparent;
  color: var(--paper);
  overscroll-behavior: contain;
  opacity: 0;
  transform: scale(var(--dialog-from));
  transition:
    opacity var(--dialog-time) var(--ease-out),
    transform var(--dialog-time) var(--ease-out),
    overlay var(--dialog-time) allow-discrete,
    display var(--dialog-time) allow-discrete;
}

.gallery[open],
.viewer[open] {
  opacity: 1;
  transform: none;
}

.gallery::backdrop,
.viewer::backdrop {
  background: var(--veil);
  opacity: 0;
  transition:
    opacity var(--dialog-time) var(--ease-out),
    overlay var(--dialog-time) allow-discrete,
    display var(--dialog-time) allow-discrete;
}

.gallery[open]::backdrop,
.viewer[open]::backdrop {
  opacity: 1;
}

@starting-style {
  .gallery[open],
  .viewer[open] {
    opacity: 0;
    transform: scale(var(--dialog-from));
  }

  .gallery[open]::backdrop,
  .viewer[open]::backdrop {
    opacity: 0;
  }
}

/* Reduced motion keeps the fade and drops the growing. */
@media (prefers-reduced-motion: reduce) {
  .gallery,
  .viewer,
  .gallery[open],
  .viewer[open] {
    transform: none;
  }

  @starting-style {
    .gallery[open],
    .viewer[open] {
      transform: none;
    }
  }
}

/* The close pill sits on the photos in the corner and stays there while the
   grid scrolls under it. The bar sticks but has no height, so the grid
   starts at the top with no room kept for the pill. */
.gallery-bar {
  position: sticky;
  top: 0;
  z-index: 1;
  height: 0;
}

/* The dialogs cover the whole screen, safe edges too, so their own
   controls and photos keep in from those edges. */
.gallery-bar .pill {
  position: absolute;
  top: calc(var(--gallery-edge) + var(--safe-top));
  right: calc(var(--gallery-edge) - var(--pill-gap) / 2 + var(--safe-right));
}

/* On a photo an outline alone can vanish into a light sky, so this one is
   filled with the veil's ink; its hover still turns to paper. */
.gallery-bar .pill > span {
  background-color: var(--ink);
}

@media (hover: hover) and (pointer: fine) {
  .gallery-bar .pill:hover > span {
    background-color: var(--paper);
  }
}

/* Columns of uneven height, so each photo keeps its own shape and a portrait
   is not cropped to its middle. CSS columns, not a script: the browser
   balances them, and the order runs down each column, which the viewer's
   arrows follow too. The thumbnails touch (Motion, Hover: neighbours
   touch): the space between two is their padding, so a click between them
   still opens one. */
.gallery-grid {
  columns: var(--thumb-min);
  column-gap: 0;
  padding:
    calc(var(--gallery-edge) - var(--thumb-gap) / 2 + var(--safe-top))
    calc(var(--gallery-edge) - var(--thumb-gap) / 2 + var(--safe-right))
    calc(var(--gallery-edge) - var(--thumb-gap) / 2 + var(--safe-bottom))
    calc(var(--gallery-edge) - var(--thumb-gap) / 2 + var(--safe-left));
}

.thumb {
  display: block;
  width: 100%;
  padding: calc(var(--thumb-gap) / 2);
  border: 0;
  background: none;
  cursor: zoom-in;
  break-inside: avoid;
}

/* The width and height in the markup hold each photo's place before it
   loads, so the columns do not jump as the lazy ones arrive. */
.thumb img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--photo-radius);
}

/* The thumbnail draws its ring on the photo, not on its wider hit box. */
.thumb:focus-visible {
  outline: none;
}

.thumb:focus-visible img {
  outline: var(--focus-line) solid var(--paper);
  outline-offset: var(--focus-offset);
}

/* The strip is three windows wide and rests on its middle third; a swipe
   moves it from there. Sideways drags are the page's, pinching stays the
   browser's (Motion, Touch). */
.viewer {
  overflow: hidden;
  touch-action: pinch-zoom;
}

.viewer-track {
  position: absolute;
  inset: 0;
  width: 300%;
  display: flex;
  transform: translateX(calc(-100% / 3));
}

.viewer-slot {
  flex: 0 0 calc(100% / 3);
  display: grid;
  place-items: center;
}

/* Sized by the window, not by %: the slot's row has no height of its own,
   so a percentage height is ignored and a portrait ran off the screen. */
.viewer-slot img {
  max-width: calc(100vw - 2 * var(--viewer-side) - var(--safe-left) - var(--safe-right));
  max-height: calc(100dvh - 2 * var(--viewer-top) - var(--safe-top) - var(--safe-bottom));
  border-radius: var(--photo-radius);
}

/* While a finger drags, nothing on the page selects (Motion, Touch). */
.dragging {
  user-select: none;
  -webkit-user-select: none;
}

.viewer-close,
.viewer-prev,
.viewer-next {
  position: absolute;
}

.viewer-close {
  top: calc(var(--gallery-edge) + var(--safe-top));
  right: calc(var(--gallery-edge) - var(--pill-gap) / 2 + var(--safe-right));
}

.viewer-prev,
.viewer-next {
  top: 50%;
  translate: 0 -50%;
}

.viewer-prev {
  left: calc(var(--gallery-edge) - var(--pill-gap) / 2 + var(--safe-left));
}

.viewer-next {
  right: calc(var(--gallery-edge) - var(--pill-gap) / 2 + var(--safe-right));
}


/* A PAGE OF TEXT
   Volunteers: the title and a way back stay put on the left while the text
   scrolls on the right, in the front page's two columns. */

.page {
  align-self: start;
  width: var(--block-width);
  padding: var(--page-top) var(--block-inset) var(--page-bottom);
  display: grid;
  grid-template-columns: var(--page-split);
  column-gap: var(--page-gutter);
  align-items: start;
}

.page h1,
.page .lead {
  width: auto;
}

/* Sticky makes the column a layer of its own, and a pill's Multiply inside
   it would only blend with the empty column, so its hover filled solid
   black. The column takes the Multiply instead: the ink looks the same, and
   the filled pill's paper label shows the footage again. */
.page-side {
  position: sticky;
  top: var(--page-top);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--block-gap);
  mix-blend-mode: multiply;
}

/* The back pill lines up with the title, as the front page's pills line up
   with the block's edge. */
.page-side .pills {
  margin-inline-start: calc(-1 * var(--block-inset) - var(--pill-gap) / 2);
}


/* CREDIT */

.credit {
  position: fixed;
  left: calc(var(--credit-x) + var(--safe-left));
  bottom: calc(var(--credit-y) + var(--safe-bottom));
  font-size: var(--credit-size);
  line-height: var(--credit-leading);
}


/* PHONE
   Last in the file, so its overrides win over the rules above.
   A proposal, not yet a design: below the desktop block's width the page
   stacks, title over intro over pills, at sizes to tune here. The desktop
   values above stay the Figma frame's. 960px is the block (923px) with
   room either side; a media query cannot read a custom property, so the
   number is written once, here. */

@media (max-width: 960px) {
  :root {
    --title-size: 44px;
    --title-leading: 40px;
    --title-tracking: -0.6px;
    --intro-size: 17px;
    --intro-leading: 21px;
    --sticker-size: 17px;
    --sticker-leading: 20px;
    --sticker-pad-x: 12px;
    --block-gap: 32px;
    --block-lift: 0px;
    /* A pill 44px tall, the smallest a finger hits without aiming. */
    --pill-size: 22px;
    --pill-leading: 24px;
    --pill-line: 2px;
    --pill-pad-y: 10px;
    --pill-pad-x: 12px;
    --pill-gap: 6px;
    --photo-radius: 16px;
    /* The gallery's edges are as wide as the space between two photos. */
    --gallery-edge: var(--thumb-gap);
    --viewer-side: 76px;
    --viewer-top: 84px;
    --page-top: 0px;
    --page-bottom: 80px;
  }

  /* The credit moves into the flow at the bottom, as a fixed one would
     cover the words on a short phone. */
  body {
    grid-template-rows: 1fr auto;
  }

  /* The footage fades into the ground at the top and bottom edges, so the
     solid bars Safari paints there continue it instead of cutting it off.
     The fade eases out in steps; a straight one reads as a band. It sits
     in the background's own layer, over the blended footage, under the
     words. */
  .bg::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background:
      linear-gradient(to bottom,
        var(--ground) 0,
        color-mix(in srgb, var(--ground) 70%, transparent) calc(var(--edge-fade) * 0.25),
        color-mix(in srgb, var(--ground) 35%, transparent) calc(var(--edge-fade) * 0.5),
        color-mix(in srgb, var(--ground) 10%, transparent) calc(var(--edge-fade) * 0.75),
        transparent var(--edge-fade)),
      linear-gradient(to top,
        var(--ground) 0,
        color-mix(in srgb, var(--ground) 70%, transparent) calc(var(--edge-fade) * 0.25),
        color-mix(in srgb, var(--ground) 35%, transparent) calc(var(--edge-fade) * 0.5),
        color-mix(in srgb, var(--ground) 10%, transparent) calc(var(--edge-fade) * 0.75),
        transparent var(--edge-fade));
  }

  /* The pills come first, at the top, and the block starts there rather
     than in the middle of the screen. */
  .intro {
    align-self: start;
    width: 100%;
    padding: 0 0 var(--phone-edge);
  }

  .intro .pills {
    order: -1;
  }

  .intro-row {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--stack-gap);
    padding-inline: var(--phone-edge);
  }

  h1,
  .lead {
    width: auto;
  }

  /* On a phone the title's last line runs under where the sticker would
     sit, so it takes its own place between the title and the intro. */
  .sticker {
    position: static;
    width: fit-content;
    margin: 0 0 var(--stack-gap);
  }

  /* The pills wrap onto rows, their outlines the nav edge from the screen's.
     The rows touch as the pills do, so the space between two rows is the
     pills' padding too. */
  .pills {
    flex-wrap: wrap;
    margin: 0;
    padding: calc(var(--nav-edge) - var(--pill-gap) / 2);
  }

  .pills .pill {
    padding-block: calc(var(--pill-gap) / 2);
  }

  .credit {
    position: static;
    justify-self: start;
    padding: 0 var(--phone-edge) var(--phone-edge);
  }

  .gallery-grid {
    columns: var(--phone-columns);
  }

  /* Volunteers stacks too: the way back at the top as on the front page,
     then the title and the text. Nothing sticks, since there is no column
     beside it to stay next to. */
  .page {
    width: 100%;
    grid-template-columns: 1fr;
    row-gap: var(--block-gap);
    padding: var(--page-top) var(--phone-edge) var(--page-bottom);
  }

  .page-side {
    position: static;
  }

  .page-side .pills {
    order: -1;
    margin-inline: calc(-1 * var(--phone-edge));
  }
}

/* A touch screen swipes between photos, so the arrows step aside and the
   photo takes the width, its sides as far in as the grid's (Motion, Touch:
   only touch swipes). */
@media (hover: none) and (pointer: coarse) {
  :root {
    --viewer-side: var(--thumb-gap);
  }

  .viewer-prev,
  .viewer-next {
    display: none;
  }
}
