/* =============================================================================
   style.css

   A corkboard with polaroids hanging on it.

   Everything you'd want to restyle lives in the TOKENS block below. Change a
   value there and it changes everywhere. Nothing further down hard-codes a
   colour, a gap, or a corner radius.

   NOTE — there is no dark theme any more. Daniel dropped it on 2026-08-31 when
   the board became a corkboard: cork is one physical surface, and a dark
   version of it is a different object rather than the same one dimmed. The
   toggle, its button, and the no-flash <head> script all went with it. To bring
   it back you would re-add a second token block and the button; nothing else
   below assumes a single theme.
   ============================================================================= */


/* -----------------------------------------------------------------------------
   1. TOKENS

   UI colours (links, buttons, form states) are Radix Colors
   (radix-ui.com/colors, MIT), copied in as plain hex — nothing is fetched at
   runtime. Radix ships each hue as a 12-step scale where every step has a fixed
   job, so picking a colour is picking a step number rather than guessing:

       1-2  backgrounds        6-7  borders, focus rings
       3-5  tinted backgrounds  9-10 solid fills      11  coloured text

   The board, the cork and the polaroids are not from Radix — they are chosen to
   look like the physical objects.
   -------------------------------------------------------------------------- */

:root {
  color-scheme: light;

  /* --- The cork ---------------------------------------------------------- */
  --cork:          #c19a63;   /* base board colour */
  --cork-dark:     #a67d46;   /* the shading at the edges */
  /* Grain strength is the opacity="0.62" inside the SVG in `body` below —
     a data: URI can't read a CSS variable. Lower it for a smoother board. */

  /* --- The polaroids ------------------------------------------------------
     A polaroid is a physical object, so these are warm white, not theme
     colours. --pol-lip is the deep bottom edge the caption gets written on. */
  --polaroid:      #fbfaf7;
  --pol-frame:     9px;       /* white border on the sides and top */
  --pol-lip:       36px;      /* the deep bottom edge */
  --pol-frame-lb:  15px;      /* ...on the enlarged one */
  --pol-lip-lb:    26px;
  /*
    How tall a picked-up photo can get. Written as vh minus a fixed amount
    because what sits around the photo — 15px of frame, a 26px lip, and the
    caption — is a fixed number of pixels, not a share of the screen. A pure vh
    value that looks right on a laptop runs off the bottom of a short window.

    The subtraction also has to cover what the tilt adds: a polaroid rotated up
    to 5deg (LB_TILT_MAX plus the sway) grows its bounding box by roughly its
    own width times sin(5deg).

    The max() is a floor, and it is not decoration. On a short, wide viewport —
    a phone turned sideways is 844x390 — the subtraction eats most of the screen
    and left a 149px photo, far worse than the 58vh this replaced. 58vh was
    known good everywhere, so it is the floor: this can only ever be bigger than
    what came before, never smaller.
  */
  --lb-photo-max:  max(calc(92vh - 210px), 58vh);

  /* Rows sit apart, each polaroid whole and its bottom edge showing.

     They used to overlap, so each row hid the edge above it. Daniel preferred
     them separate. Kept as a knob rather than deleted: set --overlap to about
     42px and --row-gap to 0px to get the overlapping board back. If you do,
     --overlap wants to be --pol-lip plus a few px for the tilt and no more —
     every px past the lip is bitten out of the photo above. */
  --overlap:       0px;
  --row-gap:       18px;      /* vertical space between rows */

  --ink:           #23262e;   /* sharpie */
  --ink-soft:      #5f6472;

  /* Sharpie. All system fonts — nothing is downloaded. Marker Felt (mac) and
     Ink Free / Segoe Script (Windows) are the marker-like ones; the rest are
     fallbacks so it degrades to *some* handwriting everywhere. */
  --font-hand: "Marker Felt", "Bradley Hand", "Ink Free", "Segoe Script",
               "Comic Sans MS", cursive;

  /* The hand used on an opened photo. Print rather than joined-up, rounded,
     monoline, with the letters sitting apart — the way someone actually
     letters a caption on a polaroid rather than writing running script.
     Noteworthy on a Mac; Segoe Print and Ink Free are the nearest equivalents
     on Windows. All system faces, nothing downloaded. */
  --font-caption: "Noteworthy", "Segoe Print", "Ink Free", "Bradley Hand",
                  "Comic Sans MS", cursive;

  /* Scrawlier than --font-hand, for the count written on the tile edge. The
     order is deliberate: Bradley Hand and Noteworthy are looser and less even
     than Marker Felt, which is the tidiest of the bunch. */
  --font-scrawl: "Bradley Hand", "Noteworthy", "Segoe Script", "Ink Free",
                 "Comic Sans MS", cursive;

  /* --- Paper panels (the text sections pinned to the board) --------------- */
  --paper:         #fdfcf9;
  --paper-edge:    #e6dfd0;

  /* --- Text and UI ------------------------------------------------------- */
  --text:          #1c2024;   /* slate 12 */
  --text-muted:    #60646c;   /* slate 11 */
  --border:        #d9d9e0;   /* slate 6  */
  --border-strong: #b9bbc6;   /* slate 8  */
  --surface:       #ffffff;
  --surface-2:     #f9f9fb;   /* slate 2  */

  --accent:        #5b5bd6;   /* iris 9  — solid fills, primary button */
  --accent-hover:  #5151cd;   /* iris 10 */
  --accent-ink:    #5753c6;   /* iris 11 — accent-coloured TEXT and focus */
  --accent-soft:   #f0f1fe;   /* iris 3  */
  --accent-line:   #b8baf8;   /* iris 7  */
  --on-accent:     #ffffff;

  --danger:        #ce2c31;   /* red 11  */
  --danger-soft:   #feebec;   /* red 3   */
  --danger-text:   #641723;   /* red 12  */
  --ok:            #208368;   /* jade 11 */
  --ok-soft:       #e6f7ed;   /* jade 3  */
  --ok-text:       #1d3b31;   /* jade 12 */

  /* --- Picking a photo up ------------------------------------------------ */
  /* Blur is what separates the chosen photo from the board; the wash is only
     there to take the edge off. Raise --lift-wash for more grey. */
  --lift-blur:     7px;
  --lift-wash:     rgba(70, 52, 30, 0.28);

  /* --- Placeholder tiles (temporary — the tint cycle is in main.js) ------- */
  --ph-bg:         #e8e3d4;
  --ph-fg:         #7a6a45;
  --ph-line:       #c9bfa4;

  /* --- Spacing ----------------------------------------------------------- */
  --gap:           18px;      /* space between polaroids, left to right */
  --space-1:       8px;
  --space-2:       16px;
  --space-3:       24px;
  --space-4:       40px;
  --space-5:       64px;

  /* --- Shape ------------------------------------------------------------- */
  --radius:        7px;
  --radius-lg:     12px;

  /* --- Type -------------------------------------------------------------- */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-size:     17px;      /* phones */
  --font-size-lg:  18px;      /* desktop */
  --line-height:   1.6;
  --measure:       68ch;      /* max line length for reading text */

  /* --- Layout ------------------------------------------------------------ */
  --page-max:      1200px;

  /* --- Motion ------------------------------------------------------------ */
  --speed:         200ms;
  --ease:          ease;

  /* --- Grid columns — change these three numbers to change the grid ------ */
  --cols-phone:    2;
  --cols-tablet:   3;
  --cols-wide:     4;

  /* --- Shadows ----------------------------------------------------------- */
  --shadow:        0 1px 2px rgba(60,40,15,.22), 0 4px 10px rgba(60,40,15,.20);
  --shadow-lift:   0 6px 14px rgba(60,40,15,.26), 0 18px 36px rgba(60,40,15,.30);
  --shadow-lb:     0 12px 30px rgba(30,20,8,.38), 0 40px 90px rgba(30,20,8,.45);
}


/* -----------------------------------------------------------------------------
   2. Base
   -------------------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

/*
  THE CORKBOARD.

  Built entirely in CSS — no image file, nothing external to fetch. Two SVG
  turbulence tiles do the work, both as data: URIs:

    granules  the cork itself. feTurbulence makes an irregular cloud, and
              feComponentTransfer with type="discrete" cuts that cloud into
              five flat bands. Discrete is what gives granules hard edges and
              their own colour instead of a smooth brown blur.
    grain     a fine speckle laid over the top.

  An earlier version speckled the board with grids of radial-gradient dots.
  Regular spacing is exactly what a pegboard looks like, and three regular
  grids stacked still read as regular. Turbulence is irregular by nature.

  The knobs are inside the SVGs below. They are deliberately left un-encoded
  and readable — a data: URI cannot read a CSS variable, so this is the only
  place they can live:

    baseFrequency   granule size. LOWER = bigger granules. 0.05 here.
    tableValues     the five colours, as R / G / B rows, light to dark. Widen
                    the spread for more contrast between granules; narrow it
                    for a calmer board.
    opacity         (grain layer only) how strong the fine speckle is.
*/
body {
  margin: 0;
  color: var(--text);
  font-family: var(--font);
  font-size: var(--font-size);
  line-height: var(--line-height);
  -webkit-font-smoothing: antialiased;

  background-color: var(--cork);
  background-image:
    /* light from the top-left, shade into the bottom-right */
    radial-gradient(ellipse at 24% 8%,  rgba(255,241,214,.18), transparent 60%),
    radial-gradient(ellipse at 82% 94%, rgba(74,44,14,.26),   transparent 62%),
    /* fine grain */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' seed='5' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23g)' opacity='0.35'/%3E%3C/svg%3E"),
    /* the cork granules */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='440' height='440'%3E%3Cfilter id='m'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.05' numOctaves='3' seed='17' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0.33 0.33 0.33 0 0  0.33 0.33 0.33 0 0  0.33 0.33 0.33 0 0  0 0 0 0 1'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='discrete' tableValues='0.90 0.80 0.70 0.60 0.50'/%3E%3CfeFuncG type='discrete' tableValues='0.74 0.62 0.51 0.41 0.32'/%3E%3CfeFuncB type='discrete' tableValues='0.52 0.41 0.32 0.25 0.18'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='440' height='440' filter='url(%23m)'/%3E%3C/svg%3E");
  background-size:
    100% 100%, 100% 100%,
    220px 220px,
    440px 440px;
  background-attachment: fixed, fixed, scroll, scroll;
  background-blend-mode: normal, normal, overlay, normal;

  /* So the vines can size themselves to the whole page. Without this they
     resolve against the viewport and stop after the first screenful. */
  position: relative;
}

/*
  NO SCROLLBAR.

  Three declarations because no single one covers every browser: Firefox uses
  scrollbar-width, old Edge uses -ms-overflow-style, and Safari and Chrome need
  the ::-webkit-scrollbar pseudo-element.

  Scrolling itself is untouched — wheel, trackpad, touch, arrow keys, Page
  Up/Down, Home/End and the space bar all still work. Only the bar is hidden.

  The .lightbox__caption is in here too: it scrolls when a caption runs long,
  and a scrollbar down the side of a polaroid looks the same kind of wrong.
*/
html,
body,
.lightbox__caption {
  scrollbar-width: none;        /* Firefox */
  -ms-overflow-style: none;     /* old Edge */
}

html::-webkit-scrollbar,
body::-webkit-scrollbar,
.lightbox__caption::-webkit-scrollbar {
  display: none;                /* Safari, Chrome */
  width: 0;
  height: 0;
}

img { max-width: 100%; display: block; }

/* Step 11, not 9: 9 is a fill colour and is too light for text. */
a { color: var(--accent-ink); }
a:hover { text-decoration-thickness: 2px; }

h1, h2, h3 { line-height: 1.25; }

/* One focus style, used everywhere. Never remove an outline without one. */
:focus-visible {
  outline: 2px solid var(--accent-ink);
  outline-offset: 2px;
  border-radius: 3px;
}

.wrap {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 0 var(--space-2);
}

.section { margin: var(--space-5) 0; }

.section > h2 {
  margin: 0 0 var(--space-1);
  font-size: 1.35rem;
}
/* Short accent rule above each section heading. Delete this block to remove. */
.section > h2::before {
  content: "";
  display: block;
  width: 28px;
  height: 3px;
  margin-bottom: var(--space-1);
  border-radius: 2px;
  background: var(--accent);
}

.section__lead { margin: 0 0 var(--space-3); color: var(--text-muted); }

.prose p { max-width: var(--measure); }

/*
  Paper panels. The text sections sit on paper laid on the board rather than
  directly on cork, which is busy and hard to read against.
*/
.paper {
  background: var(--paper);
  border: 1px solid var(--paper-edge);
  border-radius: 3px;
  padding: var(--space-3);
  box-shadow: var(--shadow);
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: var(--space-1);
  background: var(--surface);
  color: var(--text);
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  z-index: 20;
}
.skip-link:focus { left: var(--space-2); }

/*
  Draft copy. Styled as ordinary text on purpose — the point is to see the
  design at a realistic length.

  Everything marked .draft is Daniel's OWN material, given in answer to four
  questions, tidied into sentences. No fact in it was invented. It is still
  meant to be replaced with Daniel's own wording; the class is here so every
  instance can be found by searching the HTML for "draft".
*/
.draft { }

/* Anything still waiting on Daniel's words is tinted so it stands out. */
.todo {
  max-width: var(--measure);   /* don't let the tint stretch the whole page */
  color: var(--text-muted);
  border-left: 3px solid var(--accent-line);
  background: var(--accent-soft);
  padding: var(--space-1) var(--space-2);
  border-radius: 0 var(--radius) var(--radius) 0;
}

.notice {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-2);
  background: var(--surface-2);
  color: var(--text-muted);
}


/* -----------------------------------------------------------------------------
   3. Header
   -------------------------------------------------------------------------- */

/* The title sits straight on the cork, so it gets a shadow to stay legible. */
.site-header {
  padding: var(--space-4) 0 var(--space-3);
}

.site-header__inner {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
}

.site-title {
  margin: 0;
  font-family: var(--font-hand);
  font-size: 2.1rem;
  font-weight: 400;
  letter-spacing: 0.01em;
  color: #fffdf7;
  text-shadow: 0 1px 2px rgba(50,32,12,.55), 0 2px 10px rgba(50,32,12,.35);
}

.site-tagline {
  margin: 6px 0 0;
  font-family: var(--font-hand);
  font-size: 1.05rem;
  color: #fdf3e2;
  text-shadow: 0 1px 2px rgba(50,32,12,.55);
}


/* -----------------------------------------------------------------------------
   4. The board — polaroids hanging on cork

   Each polaroid sits on its own, whole, with its deep bottom edge and the
   scribble on it visible.

   Rows used to overlap so that each row covered the bottom edge of the one
   above it, the way stacked photos on a real board do. Daniel preferred them
   separate, so --overlap is 0 and --row-gap does the spacing. The negative
   margin is still wired up behind --overlap because a CSS gap cannot go below
   zero, so returning to the overlapping look is a two-value change.

   Tiles are position:relative with no z-index, so later tiles paint over
   earlier ones automatically if they ever do overlap.
   -------------------------------------------------------------------------- */

.grid {
  display: grid;
  grid-template-columns: repeat(var(--cols-phone), 1fr);
  column-gap: var(--gap);
  row-gap: var(--row-gap);
}

@media (min-width: 640px) {
  .grid { grid-template-columns: repeat(var(--cols-tablet), 1fr); }
}
@media (min-width: 1000px) {
  .grid { grid-template-columns: repeat(var(--cols-wide), 1fr); }
}

/*
  Each tile is a whole polaroid: white frame, photo, deep bottom edge.

  --tilt is set per tile by main.js so the board looks hand-arranged. It comes
  from the photo's position, not Math.random, so a photo hangs at the same angle
  on every visit instead of jumping about.
*/
.tile {
  position: relative;
  display: block;
  width: 100%;
  padding: var(--pol-frame);
  padding-bottom: var(--pol-lip);
  margin: 0 0 calc(-1 * var(--overlap));
  border: 0;
  border-radius: 2px;              /* polaroids have almost square corners */
  background: var(--polaroid);
  cursor: pointer;
  box-shadow: var(--shadow);

  /* translate / rotate / scale as SEPARATE properties, not one `transform`.
     That is what lets a photo keep swinging while you hover it: the sway
     animation owns `rotate`, hover owns `translate` and `scale`, and neither
     overwrites the other. With a single `transform` the animation wins the
     whole property outright and the lift never happens. */
  transform-origin: top center;
  translate: var(--dx, 0px) var(--dy, 0px);
  rotate: var(--tilt, 0deg);
  transition: translate var(--speed) var(--ease),
              rotate var(--speed) var(--ease),
              scale var(--speed) var(--ease),
              box-shadow var(--speed) var(--ease);
}

.tile img {
  width: 100%;
  aspect-ratio: 4 / 3;
  height: auto;
  object-fit: cover;
  object-position: center;
  border-radius: 1px;
  background: var(--ph-bg);
}

/*
  How many photos sit behind this cover, written on the bottom edge next to the
  scribble — as if counted by hand afterwards rather than printed on.

  Sloppier than the caption font on purpose: a looser face, at its own angle per
  tile (--badge-tilt, set in main.js). It should read as written, not typeset.
*/
.tile__badge {
  position: absolute;
  right: calc(var(--pol-frame) + 7px);
  bottom: calc(var(--pol-lip) * 0.28);
  height: 16px;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-family: var(--font-scrawl);
  /* Bigger than it needs to be for legibility. A loose face only looks loose
     at size — set small, any font tidies itself up. */
  font-size: 1.35rem;
  line-height: 1;
  color: var(--ink);
  opacity: 0.82;              /* same as the scribble, so they read as one hand */
  rotate: var(--badge-tilt, -6deg);
  /* off the baseline as well as off square, so they do not sit on one ruled
     line across the board */
  translate: 0 var(--badge-drop, 0px);
  pointer-events: none;       /* the whole tile is the button */
}

/* Stroked, not filled — same weight and round caps as the scribble, so the
   mark and the number look drawn by the same hand on the same day. */
.tile__badge-icon {
  width: 15px;
  height: 15px;
  display: block;
  overflow: visible;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.1;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.tile__badge-count { display: block; }

/* A photo whose crop is ruined by 4:3 can be given this class. */
.tile--tall img { aspect-ratio: 4 / 6; }

/*
  A wiggle of sharpie on every polaroid's bottom edge — the cartoon shorthand
  for "there is writing here", standing in for the caption you get when you
  pick the photo up.

  Drawn on every tile, all the time. Every bottom edge is visible now that the
  rows no longer cover each other, so every scribble shows without hovering.

  Six different scribbles, one per tile, assigned in main.js. Two things this
  is deliberately NOT:

    not repeating   each scribble is a single stroke drawn once, not a tile
                    stepped across the width, so there is no seam and no
                    stride to notice.
    not a sine wave every bump has its own height, width and direction, and
                    the whole line drifts slightly off level. The paths are
                    smooth curves drawn THROUGH scattered points rather than
                    a formula evaluated across a range, which is what stops
                    them looking mechanical.

  They are also deliberately different LENGTHS — the longest is close to twice
  the shortest — so the right-hand end lands somewhere different on each photo,
  the way a line of handwriting does. Nothing is stretched to fit: each is drawn
  at its natural width via `background-size: auto 100%`.
*/
.tile::after {
  content: "";
  position: absolute;
  left: calc(var(--pol-frame) + 3px);
  /* stops well short of the right edge — that space is where the count is
     written, and a scribble running into it would look like a mistake */
  right: calc(var(--pol-frame) + 40px);
  bottom: calc(var(--pol-lip) * 0.28);
  height: 16px;
  background-repeat: no-repeat;      /* never tiled — see above */
  background-position: left center;
  background-size: auto 100%;        /* natural width, so the stroke never
                                        stretches thin at narrow tile sizes */
  opacity: 0.82;
  pointer-events: none;
}

.tile--sc0::after {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='158' height='16' viewBox='0 0 158 16'%3E%3Cpath d='M2.0 6.5 C3.9 7.4 9.7 12.0 13.4 11.9 C17.2 11.8 20.9 6.0 24.6 5.8 C28.3 5.6 32.6 11.4 35.8 10.8 C39.0 10.2 40.1 2.3 43.6 2.0 C47.1 1.7 52.3 8.9 56.7 9.0 C61.1 9.0 65.2 2.0 69.8 2.1 C74.4 2.3 79.6 9.8 84.1 9.9 C88.6 10.1 93.1 3.1 96.8 2.8 C100.5 2.4 102.8 8.1 106.4 8.0 C110.1 7.8 114.3 1.4 118.6 1.7 C122.9 2.1 128.6 9.9 132.2 10.2 C135.9 10.5 137.8 3.5 140.7 3.6 C143.6 3.6 147.0 10.0 149.6 10.5 C152.1 11.0 154.9 7.3 156.0 6.7' fill='none' stroke='%2323262e' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.tile--sc1::after {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='16' viewBox='0 0 96 16'%3E%3Cpath d='M2.0 13.3 C3.7 12.2 8.7 7.0 12.3 6.5 C16.0 5.9 20.3 10.7 24.1 10.1 C27.9 9.5 31.6 2.6 35.1 2.9 C38.6 3.1 41.4 10.6 45.0 11.4 C48.6 12.2 52.9 7.4 56.8 7.7 C60.7 8.0 65.1 13.5 68.4 13.2 C71.7 12.9 74.0 5.9 76.6 5.7 C79.1 5.6 80.9 12.1 83.8 12.3 C86.7 12.5 92.3 8.0 94.0 7.2' fill='none' stroke='%2323262e' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.tile--sc2::after {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='132' height='16' viewBox='0 0 132 16'%3E%3Cpath d='M2.0 9.5 C3.8 9.1 9.4 7.4 13.0 7.2 C16.6 6.9 19.8 8.8 23.4 8.1 C26.9 7.4 30.2 2.2 34.2 2.7 C38.2 3.2 42.7 10.9 47.4 11.2 C52.0 11.5 57.8 4.3 62.1 4.3 C66.5 4.2 69.9 11.3 73.5 11.0 C77.1 10.7 80.3 2.9 84.0 2.4 C87.8 1.8 92.6 6.9 96.0 7.6 C99.3 8.3 100.9 5.9 104.2 6.6 C107.4 7.2 112.1 12.0 115.5 11.6 C118.9 11.2 122.1 5.1 124.5 4.1 C126.9 3.2 129.1 5.6 130.0 5.8' fill='none' stroke='%2323262e' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.tile--sc3::after {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='112' height='16' viewBox='0 0 112 16'%3E%3Cpath d='M2.0 2.9 C4.0 4.0 9.7 9.7 13.9 9.7 C18.1 9.7 22.5 2.3 27.1 2.9 C31.7 3.6 36.9 12.9 41.5 13.6 C46.1 14.3 50.1 7.0 54.8 6.8 C59.4 6.6 65.2 12.8 69.2 12.4 C73.2 11.9 75.2 4.7 78.8 4.1 C82.3 3.6 87.0 9.1 90.5 9.1 C94.1 9.0 96.9 4.0 100.1 4.0 C103.4 4.0 108.4 8.3 110.0 9.1' fill='none' stroke='%2323262e' stroke-width='2.3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.tile--sc4::after {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='145' height='16' viewBox='0 0 145 16'%3E%3Cpath d='M2.0 2.8 C4.5 4.5 12.6 12.7 16.9 13.0 C21.3 13.3 24.7 4.9 28.1 4.8 C31.5 4.6 33.9 11.7 37.5 12.1 C41.2 12.5 45.8 7.0 49.9 7.1 C54.1 7.2 58.4 13.4 62.2 12.7 C65.9 12.0 68.6 3.4 72.5 2.7 C76.4 2.1 82.2 8.0 85.5 8.7 C88.8 9.4 88.8 6.6 92.1 6.8 C95.4 7.0 100.7 10.1 105.4 9.6 C110.0 9.1 114.9 3.6 119.8 3.9 C124.6 4.2 130.7 10.7 134.6 11.5 C138.5 12.3 141.6 9.2 143.0 8.8' fill='none' stroke='%2323262e' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.tile--sc5::after {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='86' height='16' viewBox='0 0 86 16'%3E%3Cpath d='M2.0 6.1 C3.5 7.0 7.0 12.1 10.8 11.7 C14.6 11.2 21.2 3.8 24.8 3.5 C28.5 3.2 29.9 9.8 32.7 9.7 C35.4 9.6 38.0 2.4 41.2 2.9 C44.5 3.5 47.9 12.8 52.2 13.0 C56.5 13.2 62.4 4.7 67.0 4.3 C71.6 3.8 76.9 9.6 79.7 10.4 C82.6 11.2 83.3 9.2 84.0 8.9' fill='none' stroke='%2323262e' stroke-width='2.35' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* =============================================================================
   SWAY — some of the photos move, as if in a draught.

   Signed off 2026-08-31. Delete this block and the matching one in main.js to
   remove it, or set SWAY_ENABLED there to false. Which photos move, how far
   and how fast are all decided in main.js; this is only the mechanism.
   ============================================================================= */

/* Swings from the top edge, the way a photo stuck to a board lifts at the
   bottom corner rather than pivoting around its middle. */
.tile--sway {
  transform-origin: top center;
  animation: sway var(--sway-dur, 6s) var(--sway-delay, 0s) ease-in-out infinite;
}

/* The keyframes have to repeat the translate and the tilt: an animation
   overrides the element's own transform outright, so leaving them out would
   snap every swaying photo back to square and un-nudged. */
@keyframes sway {
  0%, 100% { rotate: calc(var(--tilt, 0deg) - var(--sway-amp, 2deg)); }
  50%      { rotate: calc(var(--tilt, 0deg) + var(--sway-amp, 2deg)); }
}

/* ============================== end SWAY =================================== */

/* Hover lifts the polaroid off the board and straightens it. Pointer devices
   only — on a phone a tap would leave it stuck in the hover state. */
@media (hover: hover) and (pointer: fine) {
  .tile:hover {
    translate: var(--dx, 0px) calc(var(--dy, 0px) - 7px);
    scale: 1.03;
    /* A swaying photo ignores this and keeps swinging: an animation outranks
       a normal declaration, so `rotate` stays with the animation while the
       lift above still applies. A still photo straightens. */
    rotate: 0deg;
    box-shadow: var(--shadow-lift);
    z-index: 3;
  }
}

/* Keyboard focus gets the same lift, so tabbing through is as legible as
   hovering. The ring itself comes from the global :focus-visible rule. */
.tile:focus-visible {
  translate: var(--dx, 0px) calc(var(--dy, 0px) - 7px);
  scale: 1.03;
  /* A swaying photo ignores this and keeps swinging: an animation outranks
     a normal declaration, so `rotate` stays with the animation while the
     lift above still applies. A still photo straightens. */
  rotate: 0deg;
  box-shadow: var(--shadow-lift);
  z-index: 3;
}

/* Keeps the gap below the board right whatever --overlap is set to. */
.section--board { margin-bottom: calc(var(--space-5) + var(--overlap)); }


/* -----------------------------------------------------------------------------
   5. Picking one up — the enlarged polaroid
   -------------------------------------------------------------------------- */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Belt and braces. The angles are scaled down on narrow screens so the
     swinging polaroid stays inside the viewport, but nothing here should ever
     be able to push the page sideways — the scrollbar is hidden, so sideways
     overflow would be invisible and still pannable. */
  overflow: hidden;
}
.lightbox[hidden] { display: none; }

/*
  The board goes out of focus behind the photo you picked up, rather than being
  greyed out. backdrop-filter is the whole effect; the wash on top is only
  there to take the edge off. Browsers without backdrop-filter still get the
  wash, so the overlay is never invisible.
*/
.lightbox__backdrop {
  position: absolute;
  inset: 0;
  background: var(--lift-wash);
  -webkit-backdrop-filter: blur(var(--lift-blur));
  backdrop-filter: blur(var(--lift-blur));
}

/*
  The panel is only a centring wrapper. It is deliberately position:static so
  the close and arrow buttons anchor to the viewport instead of to the photo —
  otherwise they would jump about as each photo changes the polaroid's width.
*/
.lightbox__panel {
  position: static;
  display: flex;
  align-items: center;
  justify-content: center;
  max-width: 100%;
  max-height: 100%;
  padding: var(--space-2);
}

/*
  THE ENLARGED POLAROID.

  width: fit-content makes the white frame hug the photo, so a portrait photo
  gets a portrait polaroid rather than a wide white slab with a picture in it.

  --lb-tilt is set by main.js to a fresh random angle every time a photo opens
  or you arrow to the next one — this one should feel different each time.
*/
.lightbox__figure {
  position: relative;
  margin: 0;
  display: flex;
  flex-direction: column;
  width: fit-content;
  max-width: min(88vw, 1140px);
  padding: var(--pol-frame-lb);
  padding-bottom: var(--pol-lip-lb);
  background: var(--polaroid);
  border-radius: 2px;
  box-shadow: var(--shadow-lb);
  transform-origin: top center;
  rotate: var(--lb-tilt, -2deg);
}

/* main.js adds is-lifting on every open and every arrow press, and lb-sway
   only for photos that were moving on the board — so the close-up matches how
   the photo behaved outside it.

   lb-pop animates opacity and scale; lb-sway animates rotate. Different
   properties, so they run at the same time without fighting. */
.lightbox__figure.is-lifting {
  animation: lb-pop 300ms cubic-bezier(0.2, 0.9, 0.3, 1.2);
}
.lightbox__figure.lb-sway {
  animation: lb-sway var(--lb-sway-dur, 7s) ease-in-out infinite;
}
.lightbox__figure.lb-sway.is-lifting {
  animation: lb-pop 300ms cubic-bezier(0.2, 0.9, 0.3, 1.2),
             lb-sway var(--lb-sway-dur, 7s) ease-in-out infinite;
}

/* Grows into place. Its angle is left alone so lb-sway can own `rotate`. */
@keyframes lb-pop {
  from { opacity: 0; scale: 0.72; }
  to   { opacity: 1; scale: 1; }
}

/* Deliberately tiny — a fifth of the swing the board uses. A big sway is fine
   to glance at across a wall and tiring on the thing you are trying to read. */
@keyframes lb-sway {
  0%, 100% { rotate: calc(var(--lb-tilt, -2deg) - var(--lb-sway-amp, 0.4deg)); }
  50%      { rotate: calc(var(--lb-tilt, -2deg) + var(--lb-sway-amp, 0.4deg)); }
}

.lightbox__imagewrap {
  display: flex;
  align-items: center;
  justify-content: center;
}

/*
  The photo is capped in viewport units, and the polaroid is sized from the
  photo — not the other way round.

  Doing it the other way (cap the polaroid, let the photo fill it) fights
  width:fit-content: the browser needs the photo's width to size the frame, and
  the photo's width depends on the height the frame allows. A portrait photo
  came out taller than the screen. Capping the photo breaks the loop.

  --lb-photo-max is the one number to change to make photos bigger or smaller.
*/
.lightbox__image,
.lightbox__video {
  /*
    The 1100px ceiling is not arbitrary: `full` images are 1600px on their long
    side, so past roughly this size a 2x screen is upscaling and the photo goes
    soft — bigger but worse. The vw figure leaves room for the arrows, which sit
    16px from each edge and are 44px wide.
  */
  max-width: min(84vw, 1100px);
  max-height: var(--lb-photo-max);
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 1px;
}

/* The video sizes the polaroid exactly like a photo does, so a video entry and
   a photo entry produce the same shaped frame. */
.lightbox__video { display: block; }

/* The only hint that the picture is clickable — no overlay, no play symbol.
   Mouse only, matching the pointerdown guard in main.js. */
@media (hover: hover) and (pointer: fine) {
  .lightbox__video { cursor: pointer; }
}
.lightbox__image[hidden],
.lightbox__video[hidden] { display: none; }

/*
  THE BOTTOM EDGE, written on.

  width:0 + min-width:100% stops the caption widening the polaroid: a long
  sentence wraps inside the frame instead of stretching it, so the photo alone
  decides how wide the polaroid is.
*/
.lightbox__caption {
  width: 0;
  min-width: 100%;
  max-height: 26vh;
  overflow-y: auto;
  padding: var(--space-2) 2px 0;
  font-family: var(--font-caption);
  /* Noteworthy ships a real Bold, and nothing heavier — measured, 900 renders
     identically to 700 — so 700 is the actual top of this face rather than a
     number that only looks bigger in the stylesheet. The weight is what gives
     it the thick, laid-down look of a marker. */
  font-weight: 700;
  color: var(--ink);
}

.lightbox__line {
  margin: 0;
  max-width: none;
  font-size: 1.18rem;
  line-height: 1.3;
}

/* The date sits under the sentence rather than trailing off the end of it, so
   the caption reads as a caption and the date reads as a date. It shares the
   line with the counter to avoid spending a third line on the polaroid edge. */
.lightbox__meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin: 6px 0 0;
  font-variant-numeric: tabular-nums;
}

/* The date is part of what was written on the polaroid, not a footnote — same
   ink and nearly the size of the sentence, rather than a faint grey aside. */
.lightbox__date {
  color: var(--ink);
  font-size: 1.06rem;
  opacity: 0.9;
}

/* The counter is the only thing here that is not handwriting, so it stays
   quiet and out of the way. */
/* The counter is the one thing on this edge that is not handwriting, so it
   stays light and small while everything around it thickens. */
.lightbox__counter {
  color: var(--ink-soft);
  font-size: 0.8rem;
  font-weight: 400;
}

/* A photo with no date should not leave the counter stranded in the middle. */
.lightbox__date:empty { display: none; }

.lightbox__counter { margin-left: auto; }

/* A photo with no title yet shouldn't leave a gap where the title would be. */
.lightbox__line:empty { display: none; }

.lightbox__btn {
  position: absolute;
  z-index: 2;
  min-width: 44px;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 50%;
  padding: 0;
  line-height: 0;
  cursor: pointer;
  box-shadow: var(--shadow);
  /* scale, not transform: the arrows already use `translate` for their own
     positioning, and keeping the two apart means the press effect cannot
     fight it. */
  transition: border-color var(--speed) var(--ease),
              background-color var(--speed) var(--ease),
              scale 90ms var(--ease);
}

/*
  One icon treatment for every button. Drawing them on a shared 24x24 grid is
  what fixes the off-centre look: a text glyph carries its own font metrics and
  baseline, so it lands wherever the typeface decides, while an SVG centred on
  (12,12) lands in the middle of the circle every time.
*/
.lightbox__btn svg {
  display: block;
  width: 21px;
  height: 21px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The play triangle is the one solid shape — an outlined triangle reads thin
   and hollow at this size next to the chevrons. Marked with a class rather
   than matched on its path data, which would break the moment the shape is
   nudged. */
.lightbox__btn svg .icon--solid {
  fill: currentColor;
}

/* A small give on press. Nothing moves on the page; it just confirms the hit. */
.lightbox__btn:active { scale: 0.93; }
.lightbox__btn:hover {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-ink);
}

.lightbox__close { top: var(--space-2); right: var(--space-2); }
.lightbox__nav   { top: 50%; translate: 0 -50%; }
.lightbox__nav--prev { left: var(--space-2); }
.lightbox__nav--next { right: var(--space-2); }

/* The `hidden` attribute has to beat .lightbox__btn's display:flex. */
.lightbox__btn[hidden] { display: none; }

/*
  The play/pause/replay button, phone only.

  On anything with a keyboard, Space does this job and the button would just be
  clutter in a design that has none — so it is hidden outright above the phone
  breakpoint, the same one the arrows use. It sits centred along the bottom,
  between the two arrows.
*/
.lightbox__playpause {
  bottom: var(--space-2);
  left: 50%;
  translate: -50% 0;
}

/* Above phone width there is no close button and no play button: Esc closes,
   clicking the board behind closes, and Space drives the video. Both would just
   be clutter in a design that otherwise has none. */
@media (min-width: 601px) {
  .lightbox__playpause,
  .lightbox__close { display: none; }
}

/* It only holds focus; it is not something you operate, so no ring. The real
   controls keep theirs. */
.lightbox__figure:focus { outline: none; }

/* On a phone the arrows sit in the bottom corners, clear of the polaroid. */
@media (max-width: 600px) {
  /*
    A phone wastes more vertical space than a laptop did: at 50vh a landscape
    photo used 46% of the screen with 229px empty above and below.

    The subtraction is larger than the desktop one because the arrows move to
    the bottom on a phone, so roughly 70px at the foot is spoken for before the
    frame, lip and caption get their share.
  */
  :root { --lb-photo-max: max(calc(100vh - 190px), 50vh); }

  /*
    Two changes so the close-up can swing without hitting the sides.

    Pivoting from the top throws the bottom corner sideways by about the
    polaroid's whole height times the sine of the angle. Pivoting from the
    centre halves that — the top and bottom each move half as far — and it does
    so by geometry rather than by a margin that happens to fit, which matters
    because the tilt is drawn at random every time a photo is opened.

    On the board the top pivot stays: those photos read as stuck to the cork.
    A single centred close-up has no such cue, so nothing is lost.
  */
  .lightbox__figure {
    max-width: 90vw;
    transform-origin: center;
  }
  .lightbox__nav { top: auto; bottom: var(--space-2); translate: none; }
}




/* -----------------------------------------------------------------------------
   6. Form + buttons
   -------------------------------------------------------------------------- */

.form { max-width: 520px; }

.field { margin-bottom: var(--space-3); }
.field label {
  display: block;
  margin-bottom: 6px;
  font-size: 0.9rem;
  font-weight: 600;
}

.field input,
.field textarea {
  width: 100%;
  padding: 10px 12px;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font: inherit;
  font-size: 1rem;
}
.field input:hover,
.field textarea:hover { border-color: var(--border-strong); }

.field input:focus,
.field textarea:focus { border-color: var(--accent); }

.field textarea { resize: vertical; min-height: 120px; }

.field [aria-invalid="true"] {
  border-color: var(--danger);
  background: var(--danger-soft);
}

.field__error {
  margin: 6px 0 0;
  color: var(--danger);
  font-size: 0.87rem;
}

/* Honeypot: off-screen, never shown, never focusable via Tab. */
.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 var(--space-3);
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font: inherit;
  font-size: 1rem;
  text-decoration: none;
  cursor: pointer;
  transition: border-color var(--speed) var(--ease), opacity var(--speed) var(--ease);
}
.btn:hover {
  border-color: var(--accent-line);
  background: var(--surface-2);
  color: var(--accent-ink);
}

.btn--primary {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
  transition: background-color var(--speed) var(--ease);
}
.btn--primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--on-accent);
}

.btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

.form-status {
  margin: var(--space-2) 0 0;
  padding: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.form-status[data-state="ok"] {
  border-color: var(--ok);
  background: var(--ok-soft);
  color: var(--ok-text);
}
.form-status[data-state="error"] {
  border-color: var(--danger);
  background: var(--danger-soft);
  color: var(--danger-text);
}


/* -----------------------------------------------------------------------------
   7. Footer
   -------------------------------------------------------------------------- */

/*
  The footer is a strip of paper on the board rather than text floating on the
  cork — the same move the other sections make, so the page ends on something
  deliberate instead of trailing off. Narrow, centred, and slightly turned, so
  it belongs to the board rather than sitting under it.
*/
.site-footer {
  padding: var(--space-4) 0 var(--space-5);
}

.site-footer__strip {
  max-width: 340px;
  margin: 0 auto;
  padding: var(--space-2) var(--space-3);
  background: var(--paper);
  border: 1px solid var(--paper-edge);
  border-radius: 3px;
  box-shadow: var(--shadow);
  rotate: -0.7deg;
  text-align: center;
}

.site-footer__name {
  margin: 0;
  font-family: var(--font-hand);
  font-size: 1.15rem;
  color: var(--ink);
}

/* Hairline between the name and whatever sits under it. */
.site-footer__line {
  margin: var(--space-1) 0 0;
  padding-top: var(--space-1);
  border-top: 1px solid var(--paper-edge);
  color: var(--text-muted);
  font-size: 0.85rem;
}


/* -----------------------------------------------------------------------------
   Vines down the edges

   Only on a screen wide enough to have empty board either side of the content.
   The page is 1200px at most, so below about 1440px there is no room and they
   simply do not appear — they never crowd the photos or push anything around.

   Few large leaves rather than many small ones, all angled inward so they reach
   toward the page. One SVG tile repeated down, mirrored on the right, three
   colours like the cork, inline as a data: URI so nothing is downloaded.

   The strip is wider than the free margin on purpose: the inner tips pass
   BEHIND the polaroids, which is what makes them look like they are growing in
   from the edge rather than sitting in a column beside it.
   -------------------------------------------------------------------------- */

.board-vines { display: none; }

@media (min-width: 1440px) {
  .board-vines {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 0;              /* behind the polaroids, above the cork */
    pointer-events: none;    /* never intercepts a click meant for a photo */
    overflow: hidden;
  }

  .board-vines::before,
  .board-vines::after {
    content: "";
    position: absolute;
    top: -60px;              /* start above the fold so a leaf is already there */
    bottom: 0;
    width: 150px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='150' height='560' viewBox='0 0 150 560'%3E%3Cpath d='M24 560 C 8 459, 45 392, 27 291 C 9 190, 42 123, 24 0' fill='none' stroke='%2355643f' stroke-width='3.4' stroke-linecap='round'/%3E%3Cg transform='translate(27.8,493.9) rotate(-26.7)'%3E%3Cpath d='M0 0 C 19.6 -37.4, 58.8 -32.8, 89.0 0 C 60.5 22.1, 23.1 26.4, 0 0 Z' fill='%235f7040'/%3E%3Cpath d='M5.3 2.1 Q 44.5 -7.1, 83.7 -1.4' fill='none' stroke='%2355643f' stroke-width='1.5' stroke-linecap='round' opacity='0.5'/%3E%3C/g%3E%3Cg transform='translate(27.8,364.2) rotate(31.3)'%3E%3Cpath d='M0 0 C 20.5 -39.2, 61.6 -34.4, 93.4 0 C 63.5 23.2, 24.3 27.6, 0 0 Z' fill='%237f9059'/%3E%3Cpath d='M5.6 2.2 Q 46.7 -7.5, 87.8 -1.5' fill='none' stroke='%2355643f' stroke-width='1.5' stroke-linecap='round' opacity='0.5'/%3E%3C/g%3E%3Cg transform='translate(17.2,194.9) rotate(-32.8)'%3E%3Cpath d='M0 0 C 20.7 -39.5, 62.1 -34.6, 94.1 0 C 64.0 23.3, 24.5 27.8, 0 0 Z' fill='%235f7040'/%3E%3Cpath d='M5.6 2.3 Q 47.0 -7.5, 88.4 -1.5' fill='none' stroke='%2355643f' stroke-width='1.5' stroke-linecap='round' opacity='0.5'/%3E%3C/g%3E%3Cg transform='translate(17.2,74.8) rotate(34.8)'%3E%3Cpath d='M0 0 C 14.5 -27.7, 43.5 -24.2, 65.8 0 C 44.8 16.3, 17.1 19.5, 0 0 Z' fill='%237f9059'/%3E%3Cpath d='M4.0 1.6 Q 32.9 -5.3, 61.9 -1.1' fill='none' stroke='%2355643f' stroke-width='1.5' stroke-linecap='round' opacity='0.5'/%3E%3C/g%3E%3Cg transform='translate(25.5,488.1) rotate(47.9)'%3E%3Cpath d='M0 0 C 6.2 -11.9, 18.7 -10.4, 28.4 0 C 19.3 7.0, 7.4 8.4, 0 0 Z' fill='%235f7040'/%3E%3Cpath d='M1.7 0.7 Q 14.2 -2.3, 26.6 -0.5' fill='none' stroke='%2355643f' stroke-width='1.5' stroke-linecap='round' opacity='0.5'/%3E%3C/g%3E%3Cg transform='translate(25.5,214.4) rotate(-65.8)'%3E%3Cpath d='M0 0 C 5.7 -10.8, 17.0 -9.5, 25.7 0 C 17.5 6.4, 6.7 7.6, 0 0 Z' fill='%235f7040'/%3E%3Cpath d='M1.5 0.6 Q 12.9 -2.1, 24.2 -0.4' fill='none' stroke='%2355643f' stroke-width='1.5' stroke-linecap='round' opacity='0.5'/%3E%3C/g%3E%3C/svg%3E");
    background-repeat: repeat-y;
    background-position: top center;
    opacity: 0.72;
  }

  .board-vines::before { left: 0; }

  /* Mirrored, so the two sides lean towards the middle instead of marching
     the same way. */
  .board-vines::after {
    right: 0;
    transform: scaleX(-1);
  }
}


/* -----------------------------------------------------------------------------
   8. Bigger screens
   -------------------------------------------------------------------------- */

@media (min-width: 700px) {
  body { font-size: var(--font-size-lg); }
  .site-title { font-size: 2.5rem; }
}

/* The cork tiles are a fixed pixel size, so on a narrow screen they cover
   proportionally more of it and the granules read chunkier than they do on a
   desktop. Shrinking the tiles keeps the grain looking the same size. Delete
   this block to have one granule size everywhere. */
@media (max-width: 600px) {
  /* Tiles are ~135px of usable width here vs ~226px on a desktop, so the
     longer scribbles would run into the frame. Reducing the height scales
     their widths down with it, keeping every one intact. */
  .tile::after { height: 12px; }

  body {
    background-size:
      100% 100%, 100% 100%,
      150px 150px,
      300px 300px;
  }
}


/* -----------------------------------------------------------------------------
   9. Reduced motion — no transforms, no animation

   The tilts stay: a crooked photo is a still picture, not motion. What goes is
   the movement between states.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .tile:hover, .tile:focus-visible {
    translate: var(--dx, 0px) var(--dy, 0px);
    rotate: var(--tilt, 0deg);
    scale: 1;
  }
  /* No swinging for anyone whose system asks for less motion, on the board or
     in the close-up. */
  .tile--sway,
  .lightbox__figure { animation: none; }
}
