/* glyph-headline: small geometric glyphs that loop INSIDE a display headline, set like words.
   Pure CSS. Every glyph is an em-sized inline box, so it scales with the headline and sits on its line.
   Shapes are CSS masks filled with currentColor (inherit the headline colour, or set --gh-ink).
   Timing measured frame by frame from the source's 40 ms/frame GIFs (see part.json):
     .gh--ff      a train of right-pointing triangles slides 7 pitches per sweep (2480 ms of a 2560 ms loop)
     .gh--turn    a diamond turns a quarter turn clockwise every 800 ms (760 ms move, 40 ms hold)
     .gh--up      two stacked up-triangles step up one pitch: 40 ms wait, 480 ms move, 280 ms hold
     .gh--chip    a still photo chip, 1.35em x 0.75em, set in the line like a word
   Set glyphs BETWEEN words (a space on each side): the negative side margins are tuned to eat part of a word gap,
   so at the very start of a line the turn glyph overhangs the margin by ~0.3em. Wrap the word and its glyph in
   .gh-keep so a narrow screen never strands a glyph alone at a line start.
   Knobs (custom properties, set on the headline or on one glyph):
     --gh-size     glyph box (0.75em; the turn glyph uses --gh-turn-size, 0.85em)   --gh-ink   fill (currentColor)
     --gh-ease     ff + turn curve (cubic-bezier(.6,0,.15,.95))
     --gh-ease-up  up curve (cubic-bezier(.25,.2,.1,.95))
     --gh-ff-dur   2560ms   --gh-turn-dur 800ms   --gh-up-dur 800ms
     --gh-chip     the chip's background-image (url(...))
   The curve rides on the animation, not on a keyframe: browsers drop var() inside a keyframe's
   animation-timing-function (it silently becomes `ease`). Each keyframe segment gets the curve; the holds don't move.
   Reduced motion: every glyph holds its first frame. */
.gh {
  --_s: var(--gh-size, 0.75em);
  display: inline-block;
  position: relative;
  width: var(--_s);
  height: var(--_s);
  vertical-align: middle;
  /* the source's tuck: pulls the box up into the cap height and closes the word gap a little */
  margin: -0.2em 0 -0.1em -0.1em;
  overflow: hidden;
}
.gh-keep { white-space: nowrap; }
.gh::before { content: ""; position: absolute; background: var(--gh-ink, currentColor); }

/* fast-forward: triangles tile at half the box; the strip is 9 pitches wide, starts 7 pitches left, and slides
   right by exactly 7 pitches, so the last frame IS the first frame and the loop has no seam */
.gh--ff::before {
  top: 0; bottom: 0;
  left: calc(var(--_s) * -3.5);
  width: calc(var(--_s) * 4.5);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 50 100'%3E%3Cpath d='M0 0L50 50L0 100Z'/%3E%3C/svg%3E") 0 0 / calc(var(--_s) / 2) 100% repeat-x;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 50 100'%3E%3Cpath d='M0 0L50 50L0 100Z'/%3E%3C/svg%3E") 0 0 / calc(var(--_s) / 2) 100% repeat-x;
  animation: gh-ff var(--gh-ff-dur, 2560ms) var(--gh-ease, cubic-bezier(.6, 0, .15, .95)) infinite;
}
@keyframes gh-ff {
  0% { transform: translateX(0); }
  96.875% { transform: translateX(calc(var(--_s) * 3.5)); } /* 2480 / 2560 */
  100% { transform: translateX(calc(var(--_s) * 3.5)); }
}

/* turn: a square 0.6 of the box (the source diamond spans 169/200 of its frame), standing on a corner; a quarter
   turn lands on the same diamond, so it loops seamlessly. Slightly larger box, like the source's .is-square */
.gh--turn { --_s: var(--gh-turn-size, 0.85em); margin: -0.2em -0.2em -0.1em -0.3em; overflow: visible; }
.gh--turn::before {
  inset: 20%;
  transform: rotate(45deg);
  animation: gh-turn var(--gh-turn-dur, 800ms) var(--gh-ease, cubic-bezier(.6, 0, .15, .95)) infinite;
}
@keyframes gh-turn {
  0% { transform: rotate(45deg); }
  95% { transform: rotate(135deg); } /* 760 / 800 */
  100% { transform: rotate(135deg); }
}

/* up: triangles tile at half the box vertically; the strip is 3 pitches tall and climbs one pitch per beat */
.gh--up::before {
  left: 0; right: 0; top: 0;
  height: calc(var(--_s) * 1.5);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 50'%3E%3Cpath d='M0 50L50 0L100 50Z'/%3E%3C/svg%3E") 0 0 / 100% calc(var(--_s) / 2) repeat-y;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 50'%3E%3Cpath d='M0 50L50 0L100 50Z'/%3E%3C/svg%3E") 0 0 / 100% calc(var(--_s) / 2) repeat-y;
  animation: gh-up var(--gh-up-dur, 800ms) var(--gh-ease-up, cubic-bezier(.25, .2, .1, .95)) infinite;
}
@keyframes gh-up {
  0%, 5% { transform: translateY(0); } /* 40 ms wait */
  65% { transform: translateY(calc(var(--_s) / -2)); }                          /* 40 + 480 ms */
  100% { transform: translateY(calc(var(--_s) / -2)); }
}

/* chip: a photo set in the line like a word. 1.35em = the source's 1.8:1 photo at the 0.75em glyph height (the
   source boxes it at 1.75em with background-size contain and claws the empty 0.4em back with margin-right:-0.4em) */
.gh--chip { width: calc(var(--_s) * 1.8); border-radius: 0.06em; overflow: hidden; }
.gh--chip::before { inset: 0; background: var(--gh-chip, var(--gh-ink, currentColor)) center / cover no-repeat; }

@media (prefers-reduced-motion: reduce) {
  .gh::before { animation: none !important; }
}
