/* ============================================================
   yourcustomprofile.app — catalogue type and link treatments
   Two lanes from the rank-gated catalogue (content/catalog.mjs):
   `textFx` (22) and `buttonFx` (19).

   Contract. `textFx` classes go on `.profile-name`, `.profile-title__text`
   and `.profile-bio`, exactly like text-fx.css; `buttonFx` classes go on
   `.pg-link` / `.pg-social` as `.pg-btn--fx-<id>`. Nothing here changes
   layout — no margins, no padding beyond a link's own box, no font-size
   beyond what the utility needs to stay legible.

   The rule for type: a treatment may change how a letter is drawn, never how
   the sentence is read. So no effect reduces contrast below the page's own
   body contrast, and every effect stays inside the element's own box.
   ============================================================ */


/* ---------------------------------------------------------------- the ink
   `.profile-name` paints its fill with a clipped gradient and a transparent
   `color`, which is what makes the gradient visible at all — but it also
   means `currentColor` is transparent anywhere inside the name. Every text
   effect that draws an extra layer reads this variable instead of
   `currentColor`, so a decoration can never inherit "invisible". */
:is(.profile-name, .profile-title__text, .profile-bio) {
  --tf-ink: var(--text-0, #f5f6fb);
  /* Ink is near-white, so decoration drawn *in ink* over ink disappears.
     Anything that must read as a second material — a rule, a lattice, a
     reflection — draws in this instead. */
  --tf-mark: color-mix(in srgb, var(--accent, #8b5cf6) 72%, #0b1220 28%);
}

/* ══════════════════════════════════════════════════════════════ type ══ */

/* ------------------------------------------------------ ink bleed text
   Letters wick outward once: a soft, uneven second impression.
   Only shows on the name; on body copy it would cost legibility. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-inkBleedText {
  text-shadow:
    0 0 1px color-mix(in srgb, var(--tf-ink) 60%, transparent),
    1px 0 2px color-mix(in srgb, var(--tf-ink) 34%, transparent),
    -1px 1px 3px color-mix(in srgb, var(--tf-ink) 22%, transparent);
  filter: blur(0.15px);
}

/* ------------------------------------------------------- brush stroke
   A single dry-brush descender under the name. The underline is the
   effect; the letters themselves stay untouched so the name stays read. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-brushStroke {
  /* The rule used to sit in a pseudo below a `padding-bottom`. The padding grew
     the name's box, so selecting the effect pushed the badges, the title and
     the whole card down by 5px. A painted strip along the element's own bottom
     edge is the same mark without moving anything. */
  background-image: linear-gradient(92deg,
    transparent 0 1%, var(--tf-ink) 3% 26%, transparent 28% 31%,
    var(--tf-ink) 33% 62%, transparent 64% 67%, var(--tf-ink) 69% 96%, transparent 98%);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 100% 0.09em;
}
:is(.profile-name).text-fx-brushStroke {
  background-size: 100% 0.07em;
}
.text-fx-brushStroke::after {
  content: none;
}

/* ---------------------------------------------------------- pixel type
   A hard 8-bit outline: stepped shadows, no blur anywhere. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-pixelType {
  text-shadow:
    2px 0 0 #0b0b0f, -2px 0 0 #0b0b0f, 0 2px 0 #0b0b0f, 0 -2px 0 #0b0b0f,
    2px 2px 0 #0b0b0f, -2px 2px 0 #0b0b0f, 2px -2px 0 #0b0b0f, -2px -2px 0 #0b0b0f,
    4px 4px 0 color-mix(in srgb, var(--tf-ink) 55%, transparent);
  paint-order: stroke fill;
  -webkit-font-smoothing: none;
}

/* ------------------------------------------------------ comic outline
   Heavy ink, offset fill: the fill sits two pixels off the outline like a
   badly registered comic page. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-comicOutline {
  -webkit-text-stroke: 1.6px #0a0a0c;
  paint-order: stroke fill;
  text-shadow: 3px 3px 0 color-mix(in srgb, var(--accent-2) 70%, transparent);
}

/* ------------------------------------------------------------ riso text
   Two inks, slightly apart, multiplied. The second ink is a hard copy. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-risoText {
  text-shadow: 2px 2px 0 color-mix(in srgb, var(--accent-2) 62%, transparent);
  mix-blend-mode: normal;
}
.profile-bio.text-fx-risoText {
  text-shadow: 1px 1px 0 color-mix(in srgb, var(--accent-2) 40%, transparent);
}

/* -------------------------------------------------------- letterpress
   Pressed into the page: dark above, light below, no shadow blur. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-pressText {
  text-shadow:
    0 -1px 0 rgba(255, 255, 255, 0.16),
    0 1px 0 rgba(0, 0, 0, 0.62),
    0 2px 1px rgba(0, 0, 0, 0.32);
}

/* --------------------------------------------------------- handwritten
   A hand that is not quite steady. Slight per-letter rotation needs the
   split-letter markup the engine already emits for liquid text, so this
   falls back to a wobbling baseline when letters are not split. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-handwritten {
  letter-spacing: 0.01em;
  transform: skewX(-1.4deg);
  text-shadow: 0.4px 0.6px 0 color-mix(in srgb, var(--tf-ink) 40%, transparent);
}

/* ------------------------------------------------------------- stencil
   Bridges cut for the spray: the counters are knocked out with a mask so
   the letterform reads as cut, not painted. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-stencilText {
  -webkit-mask:
    repeating-linear-gradient(0deg, #000 0 0.62em, transparent 0.62em 0.7em),
    linear-gradient(#000 0 0);
  mask:
    repeating-linear-gradient(0deg, #000 0 0.62em, transparent 0.62em 0.7em),
    linear-gradient(#000 0 0);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

/* ------------------------------------------------------------ engraved
   Cut, with a lit lower edge: the light comes from below, which is what
   makes an engraving read as a groove rather than a shadow. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-engraved {
  color: color-mix(in srgb, var(--tf-ink) 78%, #000 22%);
  text-shadow:
    0 -1px 0 rgba(0, 0, 0, 0.6),
    0 1px 0 rgba(255, 255, 255, 0.34);
}
.profile-bio.text-fx-engraved {
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.45);
}

/* ------------------------------------------------------------- ribbon
   Uneven, one letter at a time: alternating weight and a dry break every
   few characters, done with a background clip so it survives any font. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-typewriterRibbon {
  background: repeating-linear-gradient(
    96deg,
    var(--tf-ink) 0 6px,
    color-mix(in srgb, var(--tf-ink) 68%, transparent) 6px 11px,
    var(--tf-ink) 11px 17px
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ------------------------------------------------------------ halftone
   A print screen over the letters: a dot lattice clipped to the text. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-halftoneText {
  background:
    radial-gradient(circle, var(--tf-ink) 0 34%, transparent 36%) 0 0 / 4px 4px,
    var(--tf-ink);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ---------------------------------------------------------------- foil
   A cold foil sweep, once, on the reveal. Static state is a metallic
   gradient so it still reads when motion is off. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-foilText {
  background: linear-gradient(100deg, #8a6a1f, #f5e3a0 28%, #b8902f 46%, #fdf6d8 60%, #a1761c 82%, #8a6a1f);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: saturate(0.95);
}

/* -------------------------------------------------------------- duotone
   Two inks, no gradient: the first half of the word one colour, the second
   the other, split by a hard stop. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-duotoneText {
  background: linear-gradient(90deg, var(--accent) 0 50%, var(--accent-2) 50% 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* --------------------------------------------------------------- plaque
   Engraved on a brass plate: the plate is a background, the type is cut. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-plaqueText {
  /* A plate needs a margin of brass around the letters. Padding supplied it and
     also grew the box by 5px, which pushed everything below the name down on
     every selection. A spread shadow is the same brass margin drawn outside
     the box, so the plate lands without reflowing the card. */
  padding: 0;
  border-radius: 3px;
  background: linear-gradient(150deg, #e9d08a, #b98f3d 46%, #8a6522 82%, #d7b766);
  color: #3b2c08;
  text-shadow: 0 1px 0 rgba(255, 245, 210, 0.5);
  box-shadow:
    0 0 0 0.3em #b98f3d,
    inset 0 0 0 1px rgba(60, 46, 16, 0.5);
}

/* -------------------------------------------------------------- catalog
   Typed, with a filed edge: monospace figures and a punched notch on the
   left, which is what a card in a drawer looks like. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-catalogText {
  font-family: var(--font-mono);
  letter-spacing: -0.02em;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.3);
}
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-catalogText::before {
  content: "";
  display: inline-block;
  width: 0.22em;
  height: 0.22em;
  margin-right: 0.22em;
  border-radius: 50%;
  background: color-mix(in srgb, var(--tf-ink) 34%, transparent);
  vertical-align: 0.1em;
}

/* --------------------------------------------------------------- marble
   Veined, honed: two grey threads through the fill, no gloss. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-marbleText {
  background:
    linear-gradient(118deg, transparent 34%, rgba(180, 180, 190, 0.55) 36%, transparent 38%),
    linear-gradient(118deg, transparent 62%, rgba(210, 210, 218, 0.4) 64%, transparent 66%),
    linear-gradient(160deg, #f4f3f0, #d9d7d2 58%, #bfbcb6);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* -------------------------------------------------------------- caustic
   Water light moving under it: two offset radials clipped to the glyphs. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-causticText {
  background:
    radial-gradient(circle at 30% 40%, rgba(147, 197, 253, 0.9), transparent 40%),
    radial-gradient(circle at 72% 62%, rgba(56, 189, 248, 0.8), transparent 42%),
    var(--tf-ink);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  background-size: 200% 200%, 180% 180%, 100% 100%;
}

/* -------------------------------------------------------------- eclipse
   A hard shadow, one light source: sharp offset, no blur, no second light. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-eclipseText {
  color: #f5f5f4;
  text-shadow: 0.06em 0.06em 0 #0a0a0c;
}
.profile-bio.text-fx-eclipseText {
  text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.6);
}

/* ------------------------------------------------------------- terminal
   A caret that never leaves: a block cursor after the name, blinking only
   when motion is allowed. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-terminalText {
  font-family: var(--font-mono);
  letter-spacing: -0.03em;
}
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-terminalText::after {
  content: "";
  display: inline-block;
  width: 0.42em;
  height: 0.92em;
  margin-left: 0.14em;
  background: var(--tf-ink);
  vertical-align: -0.08em;
  opacity: 0.9;
}

/* ----------------------------------------------------------------- scan
   A bar passing behind the letters: the bar is a background layer, the
   glyphs are the mask, so nothing is ever painted over the text. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-scanText {
  position: relative;
  isolation: isolate;
}
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-scanText::before {
  content: "";
  position: absolute;
  inset: -10% 0;
  z-index: -1;
  background: linear-gradient(180deg, transparent 40%, color-mix(in srgb, var(--accent) 55%, transparent) 50%, transparent 60%);
  background-size: 100% 300%;
  background-position: 0 -100%;
}

/* ----------------------------------------------------------------- gilt
   Brushed gold, matte: the gradient runs across the grain, not with it. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-giltText {
  background: repeating-linear-gradient(
    105deg,
    #b8934a 0 3px, #e6c884 3px 5px, #a88338 5px 8px, #d8b26a 8px 10px
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: saturate(0.9);
}

/* ----------------------------------------------------------------- onyx
   Black on black, edge-lit: the fill is near-black and the light is a
   single hairline on the lower edge. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-onyxText {
  color: #101014;
  text-shadow:
    0 1px 0 rgba(255, 255, 255, 0.22),
    0 0 1px rgba(255, 255, 255, 0.18);
}

/* ------------------------------------------------------------ third wave
   One idea per rank, and no idea repeated from above. The rule from the top
   of this file still holds: a treatment changes how a letter is drawn, never
   how the sentence is read. */

/* ------------------------------------------------------------------ smudge
   A litho pull wiped once: the ink dragged at the tail of each word. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-smudgeText {
  text-shadow:
    1.5px 0 1.5px color-mix(in srgb, var(--tf-ink) 34%, transparent),
    3px 0 3px color-mix(in srgb, var(--tf-ink) 16%, transparent);
  filter: blur(0.2px);
}

/* ------------------------------------------------------------ column rule
   A hairline set before the name, the way a printed column opens. Drawn with
   a pseudo so it costs the name no space at the start of the line. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-columnRuleText {
  padding-left: 0.7em;
}
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-columnRuleText::before {
  content: "";
  position: absolute;
  left: 0.12em;
  top: 0.14em;
  bottom: 0.14em;
  width: 1.5px;
  background: var(--tf-ink);
  opacity: 0.55;
}

/* ----------------------------------------------------------- museum label
   Small caps, ticked at both ends. The ticks are the point: they say
   "this is a specimen, and here is where its name starts and stops". */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-labelText {
  font-variant: small-caps;
  letter-spacing: 0.03em;
}
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-labelText::before,
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-labelText::after {
  content: "";
  position: absolute;
  top: 42%;
  width: 0.22em;
  height: 1.5px;
  background: var(--tf-ink);
  opacity: 0.5;
}
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-labelText::before { left: -0.45em; }
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-labelText::after { right: -0.45em; }

/* ------------------------------------------------------------- case glass
   A reflection band crossing the letters at one angle, as if the name were
   behind a pane. A background layer, so the glyphs stay solid underneath. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-caseText {
  /* The name is white, so a white reflection over it is invisible. What glass
     actually gives you on a bright glyph is the dark edge of the reflection,
     so the travelling band is drawn darker than the ink. Two layers, because
     `background-clip: text` needs one opaque layer to carry the glyph. */
  background-image:
    linear-gradient(108deg,
      transparent 33%,
      rgba(12, 18, 32, 0.34) 44%,
      transparent 55%),
    linear-gradient(var(--tf-ink), var(--tf-ink));
  background-size: 240% 100%, 100% 100%;
  /* The window over a 240%-wide layer is 41.7% of it, so the band has to sit
     near the middle of the layer to be on screen at rest. Starting at 100%
     parked it off the right edge and the effect looked like plain white. */
  background-position: 50% 0, 0 0;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* ----------------------------------------------------------------- prism
   Three plates, one misaligned: the name is drawn three times, offset in
   opposing directions, with only the middle one solid. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-prismText {
  text-shadow:
    -1.5px 0 0 color-mix(in srgb, var(--fx-2, #38bdf8) 48%, transparent),
    1.5px 0 0 color-mix(in srgb, var(--fx-1, #a78bfa) 48%, transparent);
}

/* ----------------------------------------------------------------- flare
   A streak through the middle: one horizontal pass of light, not a glow. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-flareText {
  text-shadow:
    0 0 1px #fff,
    0 0 8px color-mix(in srgb, var(--fx-1, #fbbf24) 55%, transparent);
  /* The streak is a second layer over a solid name. Clipping a gradient that
     is mostly transparent would leave most of the name transparent too. */
  /* A white streak on a white name is invisible, so the body of the name is
     held a shade below full ink and the streak is the only pure white. */
  background-image:
    linear-gradient(180deg, transparent 43%, #fffdf4 50%, transparent 57%),
    linear-gradient(#c3cadb, #c3cadb);
  background-size: 100% 220%, 100% 100%;
  background-position: 50% 50%, 0 0;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* -------------------------------------------------------------- iridesce
   A thin film: the hue shifts with the viewing angle, but the luminance does
   not, so the letters never fall below the plate's contrast. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-iridesceText {
  background-image: linear-gradient(
    100deg,
    #9ad7ff, #b7a6ff 24%, #ffb8e0 48%, #b8f5e0 72%, #9ad7ff
  );
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 4px rgba(168, 200, 255, 0.28));
}

/* ----------------------------------------------------------------- glitch
   Two slices, offset four pixels. The slice is the letter's own shadow, so
   nothing is clipped and the name never breaks its line box. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-glitchText {
  text-shadow:
    -3px 0 0 color-mix(in srgb, #22d3ee 62%, transparent),
    3px 0 0 color-mix(in srgb, #f472b6 62%, transparent);
}

/* ------------------------------------------------------------------- grid
   A lattice over the letters: the name sits on graph paper, and the paper
   shows through where the letters are thin. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-gridText {
  /* A lattice of the ink colour over ink is nothing. The rules are drawn in
     the accent so the paper shows through the letters. */
  background-image:
    repeating-linear-gradient(90deg,
      color-mix(in srgb, var(--tf-mark) 74%, transparent) 0 1px, transparent 1px 7px),
    repeating-linear-gradient(0deg,
      color-mix(in srgb, var(--tf-mark) 52%, transparent) 0 1px, transparent 1px 7px),
    linear-gradient(var(--tf-ink), var(--tf-ink));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* ----------------------------------------------------------------- signal
   A carrier bar sweeping. A pseudo so it can sit above the glyphs without
   touching the glyph fill. */
/* The bar used to be an absolutely positioned ::after. It painted its whole
   box, so the sweep read as a grey rectangle sliding across the name instead
   of light crossing the letters, and on the title and bio it also sat over
   the surrounding line. Same correction as `caseText`: the bar is a background
   layer and the glyphs are the mask, so the light can only land on ink. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-signalText {
  background-image:
    linear-gradient(90deg,
      transparent 38%,
      rgba(255, 255, 255, 0.55) 50%,
      transparent 62%),
    linear-gradient(var(--tf-ink), var(--tf-ink));
  background-size: 300% 100%, 100% 100%;
  background-position: 50% 0, 0 0;
  background-repeat: no-repeat, no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

/* ----------------------------------------------------------------- bevel
   A cut edge with three faces: light above, dark at the base, and a flat
   middle. That is the whole illusion. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-bevelText {
  color: color-mix(in srgb, var(--tf-ink) 82%, #fff 18%);
  text-shadow:
    0 -1px 0 rgba(255, 255, 255, 0.5),
    0 1px 0 rgba(0, 0, 0, 0.55),
    0 2px 1px rgba(0, 0, 0, 0.35);
}

/* ------------------------------------------------------------------ inlay
   A second material in the counters: the fill is one tone, the inside of the
   letters catches another. Achieved with a two-stop background clipped to the
   glyphs, so the counters read as a different substance. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-inlayText {
  background-image: linear-gradient(172deg, #d8c48a 0 46%, #7a5f24 52%, #f0e0ac 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: saturate(0.85);
}

/* ----------------------------------------------------------------- relief
   Raised, lit from the left: the highlight is on the leading edge only, and
   the shadow falls only one way. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-reliefText {
  text-shadow:
    -1px -1px 0 rgba(255, 255, 255, 0.4),
    1px 1px 0 rgba(0, 0, 0, 0.5),
    2px 2px 2px rgba(0, 0, 0, 0.28);
}

/* ═══════════════════════════════════════════════ smaller targets ══ */

/* These effects were drawn for the display name, which starts at 2rem and is
   set at a heavy weight. The title and the bio run at a third of that, so a
   2px outline reads as a muddy smear, a 17px stripe crosses a whole 15px line,
   and a plate that is a tight inset around a name becomes a full-width gold
   slab under a paragraph. Same treatment, re-sized: the geometry scales with
   the type instead of being carried over from the display size. */

:is(.profile-title__text, .profile-bio).text-fx-pixelType {
  text-shadow:
    1px 0 0 #0b0b0f, -1px 0 0 #0b0b0f, 0 1px 0 #0b0b0f, 0 -1px 0 #0b0b0f,
    1px 1px 0 #0b0b0f, -1px 1px 0 #0b0b0f, 1px -1px 0 #0b0b0f, -1px -1px 0 #0b0b0f,
    2px 2px 0 color-mix(in srgb, var(--tf-ink) 55%, transparent);
}

:is(.profile-title__text, .profile-bio).text-fx-comicOutline {
  -webkit-text-stroke: 0.9px #0a0a0c;
  text-shadow: 1.5px 1.5px 0 color-mix(in srgb, var(--accent-2) 70%, transparent);
}

/* A plate on a single line is a plate. On a running paragraph it cannot be:
   `box-decoration-break` needs an inline box, and forcing `.profile-bio` (a
   `<p>`) inline pulls every following block up onto the same line — a layout
   change, which this file must never make. So the title, which is already an
   inline span, gets the plate repeated per line, and the bio gets the same
   brass-and-cut-ink material applied to the letters themselves. */
:is(.profile-title__text, .profile-bio).text-fx-plaqueText {
  padding: 0;
}
:is(.profile-title__text).text-fx-plaqueText {
  /* No padding and no `display` change: the plate is the element's own box
     with a spread shadow standing in for the inset. A padded box would push
     the title down and widen it, which is a layout change. */
  border-radius: 2px;
  box-shadow:
    0 0 0 0.16em #b98f3d,
    inset 0 0 0 1px rgba(60, 46, 16, 0.5);
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}
.profile-bio.text-fx-plaqueText {
  /* A block-level plate would be a full-column brass slab under the card, so
     the plate itself is cancelled — background *and* the spread shadow that
     draws its margin — and the material moves onto the letters. */
  background: none;
  box-shadow: none;
  color: #e9d08a;
  text-shadow:
    0 1px 0 rgba(60, 46, 16, 0.85),
    0 -1px 0 rgba(255, 245, 210, 0.28);
}

/* On a paragraph the painted strip sits on the block's bottom edge, under the
   last line only, and runs the full column — a rule beside the text rather than
   under it. A real text decoration follows every line and stops where the words
   stop, which is what a dry-brush descender looks like on running copy. */
.profile-bio.text-fx-brushStroke {
  background-image: none;
  text-decoration-line: underline;
  /* The brush mark is not a ruled underline — its character is where the ink
     lifts. A dashed decoration is the only form a real text decoration can take
     that keeps those gaps while still following every line and stopping at the
     last word, so the dry stroke survives the change of medium. */
  text-decoration-style: dashed;
  text-decoration-color: color-mix(in srgb, var(--tf-ink) 42%, transparent);
  text-decoration-thickness: 0.055em;
  text-underline-offset: 0.17em;
  text-decoration-skip-ink: none;
}

/* The typewriter reveals by growing the element's `width`, which needs an
   inline-block and `nowrap`. On the bio — a block-level paragraph — that
   collapsed the column to one 36px word and reflowed the page. Title and bio
   reveal with a mask instead: the glyphs are laid out at full width the whole
   time and only the visible window moves, so nothing around them shifts. */
:is(.profile-title__text, .profile-bio).text-fx-typewriter {
  display: revert;
  max-width: none;
  overflow: visible;
  white-space: normal;
  border-right: 0;
  -webkit-mask-image: linear-gradient(90deg, #000 0 100%, transparent 0);
  mask-image: linear-gradient(90deg, #000 0 100%, transparent 0);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: 0% 100%;
  mask-size: 0% 100%;
  animation: tf-type-reveal calc(3.4s / var(--fx-speed)) steps(30, end) infinite;
}

:is(.profile-title__text, .profile-bio).text-fx-typewriterRibbon {
  background: repeating-linear-gradient(
    96deg,
    var(--tf-ink) 0 3px,
    color-mix(in srgb, var(--tf-ink) 68%, transparent) 3px 5.5px,
    var(--tf-ink) 5.5px 8.5px
  );
}

:is(.profile-title__text, .profile-bio).text-fx-halftoneText {
  background:
    radial-gradient(circle, var(--tf-ink) 0 34%, transparent 36%) 0 0 / 3px 3px,
    var(--tf-ink);
}

:is(.profile-title__text, .profile-bio).text-fx-giltText {
  background: repeating-linear-gradient(
    105deg,
    #b8934a 0 1.5px, #e6c884 1.5px 2.5px, #a88338 2.5px 4px, #d8b26a 4px 5px
  );
}

:is(.profile-title__text, .profile-bio).text-fx-glitchText {
  text-shadow:
    -1.5px 0 0 color-mix(in srgb, #22d3ee 62%, transparent),
    1.5px 0 0 color-mix(in srgb, #f472b6 62%, transparent);
}

:is(.profile-title__text, .profile-bio).text-fx-prismText {
  text-shadow:
    -1px 0 0 color-mix(in srgb, var(--fx-2, #38bdf8) 48%, transparent),
    1px 0 0 color-mix(in srgb, var(--fx-1, #a78bfa) 48%, transparent);
}

:is(.profile-title__text, .profile-bio).text-fx-flareText {
  text-shadow:
    0 0 1px #fff,
    0 0 5px color-mix(in srgb, var(--fx-1, #fbbf24) 55%, transparent);
}

/* The column rule is a hairline in the name's own left padding. `padding-left`
   on the title and the bio is safe here — it indents the text inside the box
   and does not move the box, so nothing below it shifts. What is not safe is
   the 1.5px width the name uses: at body size that renders as a hard black
   bar over the first letter, so the rule is thinned and pulled further in. */
:is(.profile-title__text, .profile-bio).text-fx-columnRuleText {
  padding-left: 0.62em;
}
:is(.profile-title__text, .profile-bio).text-fx-columnRuleText::before {
  left: 0.1em;
  width: 1px;
  opacity: 0.45;
}

/* The specimen ticks and the brush rule are drawn against the element's own
   box. The title is an inline span, so its box is exactly the words and the
   ticks bracket them. The bio is a paragraph: its box is the full column, so
   the closing tick would sit at the far margin instead of after the last
   word. The opening mark stays — it marks where the label starts — and the
   closing one is dropped rather than left pointing at nothing. */
:is(.profile-title__text, .profile-bio).text-fx-labelText::before,
:is(.profile-title__text, .profile-bio).text-fx-labelText::after {
  height: 1px;
  opacity: 0.4;
}
/* `.profile-title__text` is an inline span, so its box is the words and the two
   ticks bracket them exactly. `.profile-bio` is a paragraph whose box is the
   whole column: a closing tick would land at the far margin, pointing at
   nothing, and an opening tick at `-0.45em` would hang outside the card. So the
   bio keeps the small-caps setting and one tick, indented inside its own box —
   `text-indent` moves the text within the box and moves nothing else. */
.profile-bio.text-fx-labelText {
  text-indent: 0.7em;
}
.profile-bio.text-fx-labelText::before {
  left: 0.12em;
}
.profile-bio.text-fx-labelText::after {
  content: none;
}

:is(.profile-title__text).text-fx-brushStroke {
  padding-bottom: 0;
  /* `padding-bottom` on the display name buys room for the rule below the
     descender. On the title it is a layout change: every effect here may redraw
     a letter, none may move a neighbour. So the rule is a background strip
     inside the element's own box instead. */
  background-image: linear-gradient(92deg,
    transparent 0 1%, var(--tf-ink) 3% 26%, transparent 28% 31%,
    var(--tf-ink) 33% 62%, transparent 64% 67%, var(--tf-ink) 69% 96%, transparent 98%);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 100% 0.06em;
}
.profile-title__text.text-fx-brushStroke::after {
  content: none;
}

/* ═══════════════════════════════════════════════════════════ buttons ══ */

/* Every rule below overrides only the decoration layers of `.pg-link` /
   `.pg-social`. Position, size and hit area stay with the base rule, so a
   member can swap a link effect without the row reflowing. */

/* ------------------------------------------------------ brush underline
   Drawn by hand, once, on hover. Static state has no underline at all. */
.pg-btn--fx-brushUnderline {
  position: relative;
}
.pg-btn--fx-brushUnderline::after {
  content: "";
  position: absolute;
  left: 10%;
  right: 10%;
  bottom: 6px;
  height: 2px;
  background: linear-gradient(92deg, transparent, currentColor 12% 34%, transparent 40% 44%, currentColor 50% 78%, transparent 88%);
  opacity: 0;
  transition: opacity 0.2s ease;
}
.pg-btn--fx-brushUnderline:hover::after,
.pg-btn--fx-brushUnderline:focus-visible::after { opacity: 0.75; }

/* ---------------------------------------------------------------- ink fill
   Fills from one corner: a radial that grows, not a flat colour swap. */
.pg-btn--fx-inkFill {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.pg-btn--fx-inkFill::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: color-mix(in srgb, var(--accent) 34%, transparent);
  clip-path: circle(0% at 12% 100%);
  transition: clip-path 0.34s ease;
}
.pg-btn--fx-inkFill:hover::before,
.pg-btn--fx-inkFill:focus-visible::before { clip-path: circle(140% at 12% 100%); }

/* ------------------------------------------------------------- pixel pop
   Snaps, never eases: a hard two-state translate with no transition. */
.pg-btn--fx-pixelPop {
  /* The resting state is a hard 1px offset, the same idea as the hover step,
     so the choice is visible before the pointer arrives. */
  box-shadow: 2px 2px 0 0 color-mix(in srgb, var(--accent) 34%, transparent);
}
.pg-btn--fx-pixelPop:hover,
.pg-btn--fx-pixelPop:focus-visible {
  translate: -2px -2px;
  box-shadow: 2px 2px 0 0 color-mix(in srgb, var(--accent) 70%, #000 30%);
  transition: none;
}

/* ---------------------------------------------------------- sticker peel
   A corner lifts on hover: one triangle of the top-right corner rotates up
   and a pale underside shows. */
.pg-btn--fx-stickerPeel {
  position: relative;
  overflow: hidden;
}
.pg-btn--fx-stickerPeel::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 0 0 18px 18px;
  border-color: transparent transparent rgba(255, 255, 255, 0.22) transparent;
  transition: border-width 0.22s ease;
}
.pg-btn--fx-stickerPeel:hover::after {
  border-width: 0 0 26px 26px;
}

/* ------------------------------------------------------------ riso offset
   A second copy, misprinted: a hard shadow in the second ink that shifts
   on hover, the way a bad two-pass print does. */
.pg-btn--fx-risoOffset {
  box-shadow: 3px 3px 0 0 color-mix(in srgb, var(--accent-2) 55%, transparent);
  transition: box-shadow 0.2s ease;
}
.pg-btn--fx-risoOffset:hover {
  box-shadow: 1px 1px 0 0 color-mix(in srgb, var(--accent-2) 75%, transparent);
}

/* ---------------------------------------------------------- letterpress
   Presses in, no glow: the button loses its light edge and gains a shadow
   inside. */
.pg-btn--fx-letterpress {
  /* Pressed paper at rest: a hairline impression along the bottom edge. */
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.1), inset 0 1px 1px rgba(0, 0, 0, 0.28);
}
.pg-btn--fx-letterpress:hover,
.pg-btn--fx-letterpress:focus-visible {
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.55), inset 0 -1px 0 rgba(255, 255, 255, 0.12);
  translate: 0 1px;
}

/* ----------------------------------------------------------- margin note
   A rule in the margin: a vertical stroke appears at the left edge. */
.pg-btn--fx-marginNote {
  position: relative;
}
.pg-btn--fx-marginNote::before {
  content: "";
  position: absolute;
  left: 6px;
  top: 24%;
  bottom: 24%;
  width: 2px;
  background: currentColor;
  opacity: 0.28;
  transition: top 0.2s ease, bottom 0.2s ease, opacity 0.2s ease;
}
.pg-btn--fx-marginNote:hover::before {
  top: 14%;
  bottom: 14%;
  opacity: 0.7;
}

/* ------------------------------------------------------------- torn edge
   Paper torn on two sides: the horizontal edges are irregular, the vertical
   edges stay clean so the row still aligns. */
.pg-btn--fx-tornEdge {
  -webkit-mask:
    radial-gradient(circle 3px at 50% 0, transparent 96%, #000 100%) top center / 9px 6px repeat-x,
    radial-gradient(circle 3px at 50% 100%, transparent 96%, #000 100%) bottom center / 9px 6px repeat-x,
    linear-gradient(#000 0 0);
  mask:
    radial-gradient(circle 3px at 50% 0, transparent 96%, #000 100%) top center / 9px 6px repeat-x,
    radial-gradient(circle 3px at 50% 100%, transparent 96%, #000 100%) bottom center / 9px 6px repeat-x,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: add;
  mask-composite: add;
}

/* ---------------------------------------------------------- thread stitch
   A running stitch border: dashes offset by half a stitch on each long side. */
.pg-btn--fx-threadStitch {
  border: 0;
  box-shadow: none;
  background-image:
    repeating-linear-gradient(90deg, currentColor 0 6px, transparent 6px 12px),
    repeating-linear-gradient(90deg, currentColor 0 6px, transparent 6px 12px);
  background-size: 100% 1px, 100% 1px;
  background-position: 0 4px, 6px calc(100% - 4px);
  background-repeat: no-repeat;
  border-radius: 6px;
}
.pg-btn--fx-threadStitch::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  z-index: -1;
}

/* --------------------------------------------------------------- wax tab
   A small seal at the corner: a red disc, flat, offset out of the box. */
.pg-btn--fx-waxTab {
  position: relative;
  overflow: visible;
}
.pg-btn--fx-waxTab::after {
  content: "";
  position: absolute;
  top: -5px;
  right: -5px;
  width: 16px;
  height: 16px;
  border-radius: 48% 52% 46% 54% / 52% 46% 54% 48%;
  background: radial-gradient(circle at 36% 30%, #f87171, #b91c1c 60%, #7f1d1d);
  box-shadow: 0 2px 5px -2px rgba(0, 0, 0, 0.8);
}

/* ------------------------------------------------------------ foil sweep
   One cold sweep on hover: a narrow band of light crossing the label. */
.pg-btn--fx-foilSweep {
  position: relative;
  overflow: hidden;
}
.pg-btn--fx-foilSweep::after {
  content: "";
  position: absolute;
  top: -50%;
  bottom: -50%;
  left: -40%;
  width: 32%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.5), transparent);
  rotate: 12deg;
  transition: left 0.5s ease;
}
.pg-btn--fx-foilSweep:hover::after { left: 118%; }

/* ---------------------------------------------------------- duotone shift
   Two inks swap on hover: the label and the plate trade places. */
.pg-btn--fx-duotoneShift {
  /* A second ink in one corner: the shift is already latent at rest. */
  box-shadow: inset 3px 0 0 0 color-mix(in srgb, var(--accent-2) 62%, transparent);
  transition: background-color 0.22s ease, color 0.22s ease, box-shadow 0.22s ease;
}
.pg-btn--fx-duotoneShift:hover,
.pg-btn--fx-duotoneShift:focus-visible {
  background: var(--accent-2);
  color: #0b0b0f;
}

/* ----------------------------------------------------------- plaque press
   A plate that depresses: the inner bevel inverts. */
.pg-btn--fx-plaquePress {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.28), inset 0 -1px 0 rgba(0, 0, 0, 0.4);
}
.pg-btn--fx-plaquePress:hover {
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.5), inset 0 -1px 0 rgba(255, 255, 255, 0.1);
}

/* -------------------------------------------------------------- card pull
   An index card sliding out: the row widens slightly to the right and the
   shadow lengthens. Only the right side moves, so nothing to its left
   shifts. */
.pg-btn--fx-cardPull {
  /* The card is already slightly out of the stack at rest. */
  translate: 2px 0;
  box-shadow: -2px 2px 8px -5px rgba(0, 0, 0, 0.6);
  transition: translate 0.24s ease, box-shadow 0.24s ease;
}
.pg-btn--fx-cardPull:hover {
  translate: 5px 0;
  box-shadow: -5px 3px 12px -6px rgba(0, 0, 0, 0.7);
}

/* ----------------------------------------------------------- caustic shine
   Water light across the label: two soft bands that drift while hovered. */
.pg-btn--fx-causticShine {
  position: relative;
  overflow: hidden;
}
.pg-btn--fx-causticShine::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 22% 40%, rgba(147, 197, 253, 0.4), transparent 40%),
    radial-gradient(circle at 74% 66%, rgba(56, 189, 248, 0.32), transparent 42%);
  opacity: 0.55;
  pointer-events: none;
}

/* ------------------------------------------------------------- HUD target
   Brackets close in on hover: four corner ticks that step inward. */
.pg-btn--fx-hudTarget {
  position: relative;
  border-radius: 2px;
}
.pg-btn--fx-hudTarget::before,
.pg-btn--fx-hudTarget::after {
  content: "";
  position: absolute;
  width: 12px;
  height: 12px;
  border: 1.5px solid color-mix(in srgb, var(--accent) 80%, transparent);
  transition: inset 0.2s ease, width 0.2s ease, height 0.2s ease;
}
.pg-btn--fx-hudTarget::before {
  top: 3px;
  left: 3px;
  border-right: 0;
  border-bottom: 0;
}
.pg-btn--fx-hudTarget::after {
  right: 3px;
  bottom: 3px;
  border-left: 0;
  border-top: 0;
}
.pg-btn--fx-hudTarget:hover::before,
.pg-btn--fx-hudTarget:hover::after {
  width: 8px;
  height: 8px;
}

/* ------------------------------------------------------------ circuit trace
   A pulse runs the edge: one lit segment travels the bottom rule. */
.pg-btn--fx-circuitTrace {
  position: relative;
}
.pg-btn--fx-circuitTrace::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1.5px;
  background: linear-gradient(90deg, transparent, #34d399 50%, transparent);
  background-size: 40% 100%;
  background-repeat: no-repeat;
  opacity: 0.35;
}
.pg-btn--fx-circuitTrace:hover::after { opacity: 1; }

/* -------------------------------------------------------------- gilt edge
   A gold rule, brushed: matte, on the bottom edge only. */
.pg-btn--fx-giltEdge {
  position: relative;
}
.pg-btn--fx-giltEdge::after {
  content: "";
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 0;
  height: 2px;
  background: repeating-linear-gradient(92deg, #b8934a 0 2px, #d8b26a 2px 3px, #a88338 3px 4px);
  opacity: 0.8;
}

/* -------------------------------------------------------------- seal break
   The seal cracks on click: two halves separate for a moment. The element
   keeps its focus ring; the crack is decoration only. */
.pg-btn--fx-sealBreak {
  position: relative;
}
.pg-btn--fx-sealBreak::after {
  content: "";
  position: absolute;
  right: 6px;
  top: 50%;
  width: 14px;
  height: 14px;
  translate: 0 -50%;
  border-radius: 50%;
  background: radial-gradient(circle at 36% 30%, #fca5a5, #b91c1c 62%, #7f1d1d);
  opacity: 0.9;
}
.pg-btn--fx-sealBreak:active::after {
  background: radial-gradient(circle at 36% 30%, #fca5a5, #b91c1c 62%, #7f1d1d);
  clip-path: polygon(0 0, 46% 0, 42% 44%, 50% 100%, 0 100%);
}

/* ---------------------------------------------------------- third wave
   Sixteen more link treatments, one per rank per idea. Every one of them
   keeps the link's own box: nothing here changes padding, height or the hit
   area, because a row of links that grows on hover is a row that shifts. */

/* ------------------------------------------------------------ gutter slide
   The label shifts one column, as if it were set in a margin. The text moves
   inside its own box; the box does not. */
.pg-btn--fx-gutterSlide .pg-link__label {
  /* The gutter exists at rest; the hover only widens the travel. */
  transform: translateX(2px);
  transition: transform 220ms cubic-bezier(0.2, 0.7, 0.2, 1);
}
.pg-btn--fx-gutterSlide:hover .pg-link__label,
.pg-btn--fx-gutterSlide:focus-visible .pg-link__label {
  transform: translateX(6px);
}

/* --------------------------------------------------------------- proof tick
   A tick lands in the margin: the plate is proofed, so the mark appears on
   the side away from the text. */
.pg-btn--fx-proofTick { position: relative; }
.pg-btn--fx-proofTick::after {
  content: "✓";
  position: absolute;
  right: 9px;
  top: 50%;
  translate: 0 -50%;
  font-size: 0.82em;
  color: color-mix(in srgb, currentColor 62%, transparent);
  opacity: 0;
  transition: opacity 180ms ease;
  pointer-events: none;
}
.pg-btn--fx-proofTick:hover::after,
.pg-btn--fx-proofTick:focus-visible::after { opacity: 1; }

/* ------------------------------------------------------------------ staple
   A staple bites the corner: two short legs straddling the edge. */
.pg-btn--fx-staple { position: relative; }
.pg-btn--fx-staple::after {
  content: "";
  position: absolute;
  left: 10px;
  top: -1px;
  width: 16px;
  height: 7px;
  border: 1.6px solid rgba(198, 205, 216, 0.75);
  border-bottom: 0;
  border-radius: 1px 1px 0 0;
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.5);
  pointer-events: none;
}

/* ---------------------------------------------------------------- mat inset
   The label sinks into a mat: an inner shadow opens above the text, and the
   plate loses a little height in the eye without moving a pixel. */
.pg-btn--fx-matInset {
  box-shadow:
    inset 0 2px 5px rgba(0, 0, 0, 0.45),
    inset 0 -1px 0 rgba(255, 255, 255, 0.12);
}

/* --------------------------------------------------------------- label tilt
   A label lifted at one corner: the corner is what moves, not the whole row. */
.pg-btn--fx-labelTilt {
  transform-origin: 12% 90%;
  /* A label glued slightly crooked: the resting tilt is the effect. */
  transform: rotate(-0.35deg);
  transition: transform 200ms cubic-bezier(0.2, 0.7, 0.2, 1);
}
.pg-btn--fx-labelTilt:hover,
.pg-btn--fx-labelTilt:focus-visible { transform: rotate(-0.6deg) translateY(-1px); }

/* ----------------------------------------------------------------- pin drop
   A pin lands above the label: a head and a shaft, dropped on hover. */
.pg-btn--fx-pinDrop { position: relative; }
.pg-btn--fx-pinDrop::after {
  content: "";
  position: absolute;
  left: 50%;
  top: -5px;
  width: 3px;
  height: 6px;
  border-radius: 50% 50% 1px 1px;
  background: linear-gradient(180deg, #e2e8f0, #7c8798);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
  opacity: 0;
  translate: -50% -3px;
  transition: opacity 160ms ease, translate 200ms cubic-bezier(0.2, 0.9, 0.2, 1);
}
.pg-btn--fx-pinDrop:hover::after,
.pg-btn--fx-pinDrop:focus-visible::after { opacity: 1; translate: -50% 0; }

/* --------------------------------------------------------------- light lead
   Light runs ahead of the pointer: a short bright segment enters from the
   side the pointer comes from. Direction is fixed left-to-right so the rule
   stays one line. */
.pg-btn--fx-lightLead { position: relative; overflow: hidden; }
.pg-btn--fx-lightLead::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 34%;
  background: linear-gradient(90deg, transparent, rgba(255, 246, 214, 0.32), transparent);
  translate: -100% 0;
  opacity: 0;
  pointer-events: none;
}
.pg-btn--fx-lightLead:hover::after,
.pg-btn--fx-lightLead:focus-visible::after {
  opacity: 1;
  translate: 260% 0;
  transition: translate 620ms ease, opacity 180ms ease;
}

/* -------------------------------------------------------------- prism shift
   The label splits into three: red and cyan ghosts either side, the solid
   name in the middle. */
.pg-btn--fx-prismShift {
  /* A permanent half-step of separation, so the fringes read at rest. */
  text-shadow:
    -0.7px 0 0 color-mix(in srgb, #22d3ee 34%, transparent),
    0.7px 0 0 color-mix(in srgb, #f472b6 34%, transparent);
}
.pg-btn--fx-prismShift:hover,
.pg-btn--fx-prismShift:focus-visible {
  text-shadow:
    -1.4px 0 0 color-mix(in srgb, #22d3ee 62%, transparent),
    1.4px 0 0 color-mix(in srgb, #f472b6 62%, transparent);
}

/* ---------------------------------------------------------------- flare line
   One hard streak across the plate, not a glow. */
.pg-btn--fx-flareLine { position: relative; overflow: hidden; }
.pg-btn--fx-flareLine::after {
  content: "";
  position: absolute;
  left: -20%;
  right: -20%;
  top: 50%;
  height: 1.5px;
  background: linear-gradient(90deg, transparent, #fff6d6 46%, #ffe6a8 54%, transparent);
  opacity: 0;
  pointer-events: none;
}
.pg-btn--fx-flareLine:hover::after,
.pg-btn--fx-flareLine:focus-visible::after { opacity: 0.85; }

/* ---------------------------------------------------------------- glass lift
   A pane rises off the label: a translucent sheet with a lit top edge. */
.pg-btn--fx-glassLift { position: relative; }
.pg-btn--fx-glassLift::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.2), transparent 58%);
  opacity: 0;
  translate: 0 1px;
  transition: opacity 200ms ease, translate 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
  pointer-events: none;
}
.pg-btn--fx-glassLift:hover::after,
.pg-btn--fx-glassLift:focus-visible::after { opacity: 1; translate: 0 -1px; }

/* ----------------------------------------------------------------- vent open
   Slots open along the edge: the same machined openings as the card frame,
   at link scale. */
.pg-btn--fx-ventOpen { position: relative; }
.pg-btn--fx-ventOpen::after {
  content: "";
  position: absolute;
  left: 10px;
  right: 10px;
  bottom: 2px;
  height: 2px;
  background: repeating-linear-gradient(90deg,
    currentColor 0 5px, transparent 5px 10px);
  opacity: 0.24;
  transition: opacity 200ms ease;
  pointer-events: none;
}
.pg-btn--fx-ventOpen:hover::after,
.pg-btn--fx-ventOpen:focus-visible::after { opacity: 0.6; }

/* -------------------------------------------------------------- signal pulse
   One pulse, then silence: a single ring leaves the plate on hover and does
   not repeat while the pointer stays. */
.pg-btn--fx-signalPulse { position: relative; }
.pg-btn--fx-signalPulse::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 1px solid color-mix(in srgb, currentColor 55%, transparent);
  opacity: 0;
  pointer-events: none;
}
.pg-btn--fx-signalPulse:hover::after,
.pg-btn--fx-signalPulse:focus-visible::after {
  opacity: 1;
  animation: tf-pulse 900ms ease-out 1;
}

/* ----------------------------------------------------------------- mesh push
   A lattice deforms under the pointer: the grid is a background layer, and
   the deformation is a tiny scale on that layer only. */
.pg-btn--fx-meshPush {
  background-image:
    linear-gradient(90deg, color-mix(in srgb, currentColor 12%, transparent) 0 1px, transparent 1px 9px),
    linear-gradient(0deg, color-mix(in srgb, currentColor 10%, transparent) 0 1px, transparent 1px 9px);
}
.pg-btn--fx-meshPush:hover,
.pg-btn--fx-meshPush:focus-visible { background-size: 9px 9px, 9px 9px; }

/* ---------------------------------------------------------------- inlay rise
   A second material rises: a band lifts from the bottom edge of the plate. */
.pg-btn--fx-inlayRise { position: relative; overflow: hidden; }
.pg-btn--fx-inlayRise::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: linear-gradient(90deg, #8a6a2f, #e6cd8f 40%, #8a6a2f 70%, #d9b665);
  translate: 0 3px;
  transition: translate 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
  pointer-events: none;
}
.pg-btn--fx-inlayRise:hover::after,
.pg-btn--fx-inlayRise:focus-visible::after { translate: 0 0; }

/* --------------------------------------------------------------- bevel press
   The bevel closes as it is pressed: the light edge flips below the dark one,
   which is what happens when a raised surface is pushed in. */
.pg-btn--fx-bevelPress {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.3),
    inset 0 -1px 0 rgba(0, 0, 0, 0.45);
  transition: box-shadow 140ms ease;
}
.pg-btn--fx-bevelPress:hover,
.pg-btn--fx-bevelPress:focus-visible {
  box-shadow:
    inset 0 1px 0 rgba(0, 0, 0, 0.4),
    inset 0 -1px 0 rgba(255, 255, 255, 0.24);
}

/* --------------------------------------------------------------- velvet push
   The surface takes the pressure: a soft dark bloom under the pointer, no
   highlight, because velvet does not shine. */
.pg-btn--fx-velvetPush { position: relative; }
.pg-btn--fx-velvetPush::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(ellipse at 50% 120%, rgba(0, 0, 0, 0.42), transparent 68%);
  opacity: 0;
  transition: opacity 240ms ease;
  pointer-events: none;
}
.pg-btn--fx-velvetPush:hover::after,
.pg-btn--fx-velvetPush:focus-visible::after { opacity: 1; }

/* -------------------------------------------------------------- motion */

@media (prefers-reduced-motion: no-preference) {
  :is(.profile-name, .profile-title__text, .profile-bio).text-fx-terminalText::after { animation: tf-caret 1.1s steps(1, end) infinite; }
  :is(.profile-name, .profile-title__text, .profile-bio).text-fx-scanText::before { animation: tf-scan 3.2s linear infinite; }
  :is(.profile-name, .profile-title__text, .profile-bio).text-fx-causticText { animation: tf-caustic 12s ease-in-out infinite alternate; }
  :is(.profile-name, .profile-title__text, .profile-bio).text-fx-foilText { background-size: 220% 100%; animation: tf-foil 9s ease-in-out infinite; }
  .pg-btn--fx-circuitTrace::after { animation: tf-trace 2.6s linear infinite; }
  :is(.profile-name, .profile-title__text, .profile-bio).text-fx-brushStroke::after { animation: tf-brush 7s ease-in-out infinite; }
  :is(.profile-name, .profile-title__text, .profile-bio).text-fx-caseText { animation: tf-case 11s ease-in-out infinite; }
  :is(.profile-name, .profile-title__text, .profile-bio).text-fx-iridesceText { animation: tf-iridesce 16s linear infinite; }
  :is(.profile-name, .profile-title__text, .profile-bio).text-fx-signalText { animation: tf-signal 4.2s linear infinite; }
  :is(.profile-name, .profile-title__text, .profile-bio).text-fx-gridText { animation: tf-grid 18s linear infinite; }
}

@keyframes tf-type-reveal {
  0% { -webkit-mask-size: 0% 100%; mask-size: 0% 100%; }
  12% { -webkit-mask-size: 100% 100%; mask-size: 100% 100%; }
  78% { -webkit-mask-size: 100% 100%; mask-size: 100% 100%; }
  100% { -webkit-mask-size: 0% 100%; mask-size: 0% 100%; }
}
@keyframes tf-case {
  0% { background-position: 118% 0, 0 0; }
  100% { background-position: -18% 0, 0 0; }
}
@keyframes tf-iridesce {
  to { background-position: -220% 0; }
}
@keyframes tf-signal {
  /* A 300%-wide layer under percentage positioning moves at twice the travel
     it looks like it should, so the sweep is expressed in the window's own
     coordinates: 95% parks the bar just off the left, 5% just off the right.
     The 50% rest position keeps it mid-word when motion is off. */
  0% { background-position: 95% 0, 0 0; }
  100% { background-position: 5% 0, 0 0; }
}
@keyframes tf-grid {
  to { background-position: 14px 14px, -14px 14px, 0 0; }
}
@keyframes tf-pulse {
  0% { opacity: 0.8; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.06); }
}
@keyframes tf-caret {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}
@keyframes tf-scan {
  0% { background-position: 0 -100%; }
  100% { background-position: 0 200%; }
}
@keyframes tf-caustic {
  0% { background-position: 0% 0%, 100% 100%, 0 0; }
  100% { background-position: 30% 20%, 70% 60%, 0 0; }
}
@keyframes tf-foil {
  0%, 100% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
}
@keyframes tf-trace {
  0% { background-position: -40% 0; }
  100% { background-position: 140% 0; }
}
@keyframes tf-brush {
  0%, 100% { opacity: 0.75; transform: scaleX(1); }
  50% { opacity: 0.55; transform: scaleX(0.98); }
}

html[data-motion="low"] :is(.profile-name, .profile-title__text, .profile-bio, .pg-link, .pg-social)[class*="text-fx-"]::after,
html[data-motion="low"] .pg-link[class*="pg-btn--fx-"]::after,
html[data-motion="low"] .pg-social[class*="pg-btn--fx-"]::after,
html[data-motion="low"] :is(.profile-name, .profile-title__text, .profile-bio)[class*="text-fx-"]::before,
html.is-slow-device :is(.profile-name, .profile-title__text, .profile-bio)[class*="text-fx-"]::after,
html.is-slow-device .pg-link[class*="pg-btn--fx-"]::after,
html.is-slow-device .pg-social[class*="pg-btn--fx-"]::after {
  animation: none !important;
}

/* The caret is a cursor, not a decoration: it stays visible when motion is
   off, because a terminal with no cursor is a broken terminal. */
html[data-motion="low"] :is(.profile-name, .profile-title__text, .profile-bio).text-fx-terminalText::after {
  animation: none !important;
  opacity: 0.9;
}

/* ══════════════════════════════════════════════════════════ name fonts ══ */

/* Ten faces, each chosen for one world rather than for variety. They set the
   name only; the title, bio and widgets keep the member's body face, which is
   the difference between a display choice and a page that changes costume. */

/* The Japanese gothics are quiet at display size, so they take a touch of
   negative tracking to keep the line from looking slack next to Latin type. */
.name-font-zen { font-family: "Zen Kaku Gothic New", "Outfit", var(--font-sans); font-weight: 500; letter-spacing: -0.015em; }
.name-font-shippori { font-family: "Shippori Mincho", "Noto Serif JP", Georgia, serif; font-weight: 600; letter-spacing: 0.005em; }
.name-font-notoSerifJp { font-family: "Noto Serif JP", "Shippori Mincho", Georgia, serif; font-weight: 600; letter-spacing: 0.01em; }
.name-font-biz { font-family: "BIZ UDPGothic", "Zen Kaku Gothic New", var(--font-sans); font-weight: 700; letter-spacing: -0.01em; }
/* Dot Gothic and Press Start are bitmap faces: they blur if they are dragged
   around by a fractional font-size, so both are pinned to whole sizes. */
.name-font-dotGothic{ font-family: "DotGothic16", "Silkscreen", var(--font-mono); --name-font-size: 1.9rem; letter-spacing: 0.01em; -webkit-font-smoothing: none; }
.name-font-pressStart{ font-family: "Press Start 2P", "Silkscreen", var(--font-mono); --name-font-size: 1.35rem; line-height: 1.5; letter-spacing: 0; -webkit-font-smoothing: none; }
.name-font-cinzel { font-family: "Cinzel", "Playfair Display", Georgia, serif; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; }
.name-font-marcellus { font-family: "Marcellus", "Cormorant Garamond", Georgia, serif; font-weight: 400; letter-spacing: 0.02em; }
.name-font-gloock { font-family: "Gloock", "Playfair Display", Georgia, serif; font-weight: 400; letter-spacing: -0.02em; }
.name-font-bodoni { font-family: "Bodoni Moda", "Playfair Display", Didot, serif; font-weight: 600; letter-spacing: -0.025em; font-optical-sizing: auto; }

/* All ten fall back to a face the page already owns, so a blocked font
   request changes the silhouette, never the address. */

/* Five more faces, all of them already requested by the page, so these add no
   network cost. They are the display choices for the upper halls: a wonky
   serif, an open geometric, a technical sans, a typewriter and an outline. */
.name-font-instrument { font-family: "Instrument Serif", "Playfair Display", Georgia, serif; font-weight: 400; letter-spacing: -0.012em; }
.name-font-syne { font-family: "Syne", "Space Grotesk", var(--font-sans); font-weight: 700; letter-spacing: -0.03em; }
.name-font-specialElite { font-family: "Special Elite", "Courier New", var(--font-mono); font-weight: 400; letter-spacing: 0.015em; }
.name-font-fraunces { font-family: "Fraunces", "Playfair Display", Georgia, serif; font-weight: 600; letter-spacing: -0.02em; font-optical-sizing: auto; }
.name-font-unbounded { font-family: "Unbounded", "Space Grotesk", var(--font-sans); font-weight: 600; letter-spacing: -0.025em; }
.name-font-orbitron { font-family: "Orbitron", "JetBrains Mono", var(--font-mono); font-weight: 700; letter-spacing: 0.02em; }
/* Monoton is an outline face. It needs the size, or the double stroke fills in. */
.name-font-monoton{ font-family: "Monoton", "Bungee Shade", cursive; font-weight: 400; --name-font-size: clamp(1.7rem, 4.6vw, 2.5rem); letter-spacing: 0.01em; }

/* ------------------------------------------------------------- second shelf

   Eighteen faces the profile did not carry before. Each one is a real family,
   loaded by the page's own stylesheet link, with a fallback that keeps the
   silhouette if the request fails. `font-size` is set only where the face's
   own advance would otherwise push the name past the card edge. */

/* wide, mechanical, one width */
.name-font-michroma{ font-family: "Michroma", "Syncopate", var(--font-sans); font-weight: 400; letter-spacing: 0.04em; --name-font-size: clamp(1.5rem, 4.2vw, 1.75rem); }
/* mincho with a decorated serif */
.name-font-kaiseiDecol { font-family: "Kaisei Decol", "Shippori Mincho", Georgia, serif; font-weight: 700; letter-spacing: 0.005em; }
/* old-style mincho */
.name-font-zenOldMincho { font-family: "Zen Old Mincho", "Shippori Mincho", Georgia, serif; font-weight: 700; letter-spacing: 0.01em; }
/* squared terminals */
.name-font-chakraPetch { font-family: "Chakra Petch", "Sora", var(--font-sans); font-weight: 700; letter-spacing: -0.008em; }
/* small caps at display size */
.name-font-cormorantSc{ font-family: "Cormorant SC", "Cormorant Garamond", Georgia, serif; font-weight: 600; letter-spacing: 0.03em; --name-font-size: clamp(1.5rem, 4.2vw, 2.55rem); }
/* pixel face that still reads */
.name-font-pixelify{ font-family: "Pixelify Sans", "Silkscreen", var(--font-mono); font-weight: 600; letter-spacing: 0.01em; --name-font-size: clamp(1.5rem, 4.2vw, 1.95rem); }
/* heavy gothic */
.name-font-delaGothic { font-family: "Dela Gothic One", "Zen Kaku Gothic New", var(--font-sans); font-weight: 400; letter-spacing: -0.012em; }
/* twice as wide */
.name-font-syncopate{ font-family: "Syncopate", "Michroma", var(--font-sans); font-weight: 700; letter-spacing: 0.12em; --name-font-size: clamp(1.5rem, 4.2vw, 1.5rem); }
/* text face at headline size */
.name-font-newsreader { font-family: "Newsreader", "Playfair Display", Georgia, serif; font-weight: 600; letter-spacing: -0.018em; }
/* round and slightly bouncy */
.name-font-rocknroll { font-family: "RocknRoll One", "Zen Kaku Gothic New", var(--font-sans); font-weight: 400; letter-spacing: 0.005em; }
/* cut stencil counters */
.name-font-sairaStencil{ font-family: "Saira Stencil One", "Archivo", var(--font-sans); font-weight: 400; letter-spacing: 0.02em; --name-font-size: clamp(1.5rem, 4.2vw, 1.9rem); }
/* light mincho, wide spacing */
.name-font-hinaMincho { font-family: "Hina Mincho", "Shippori Mincho", Georgia, serif; font-weight: 400; letter-spacing: 0.035em; }
/* rounded techno */
.name-font-audiowide{ font-family: "Audiowide", "Orbitron", var(--font-sans); font-weight: 400; letter-spacing: 0.015em; --name-font-size: clamp(1.5rem, 4.2vw, 1.75rem); }
/* brush strokes, kept sharp */
.name-font-yujiSyuku { font-family: "Yuji Syuku", "Shippori Mincho", Georgia, serif; font-weight: 400; letter-spacing: 0.02em; }
/* roman capitals with flourishes */
.name-font-cinzelDecorative{ font-family: "Cinzel Decorative", "Cinzel", Georgia, serif; font-weight: 700; letter-spacing: 0.05em; --name-font-size: clamp(1.5rem, 4.2vw, 1.85rem); }
/* antique mincho, high contrast */
.name-font-zenAntique { font-family: "Zen Antique", "Shippori Mincho", Georgia, serif; font-weight: 400; letter-spacing: 0.015em; }
/* pop face with a heavy beat */
.name-font-mochiyPop{ font-family: "Mochiy Pop One", "Zen Kaku Gothic New", var(--font-sans); font-weight: 400; letter-spacing: -0.005em; --name-font-size: clamp(1.5rem, 4.2vw, 1.9rem); }
/* editorial grotesque */
.name-font-bricolage { font-family: "Bricolage Grotesque", "Archivo", var(--font-sans); font-weight: 700; letter-spacing: -0.028em; }
