/* ============================================================
   yourcustomprofile.app — text effects (v9)
   The class lives on the text element itself, so every rule uses
   :is(.profile-name, .profile-title__text, .profile-bio).text-fx-*.
   All loops are continuous and deliberately short (1.4–4s) so the
   treatment never reads as a one-off burst.
     --fx-1 / --fx-2   the two colours
     --fx-speed        animation speed (1 = normal, 2 = fast)
     --fx-strength     how hard the effect pushes

   Ordering contract: the member's own colours are applied *before*
   the effect blocks and through a low-specificity selector, so a
   background-clip:text effect always paints over a flat colour.
   ============================================================ */

:is(.profile-name, .profile-title__text, .profile-bio) {
  position: relative;
  --fx-1: var(--accent);
  --fx-2: var(--accent-2);
  --fx-speed: 1;
  --fx-strength: 1;
}

/* Member colour. renderBio() writes --bio-color instead of an inline
   `color`, because an inline colour beats `background-clip: text` and
   used to flatten every gradient effect into a solid hue. This rule is
   intentionally declared first so the effect rules below win by order. */
.profile-bio {
  color: var(--bio-color, var(--text-1));
}
.profile-title__text {
  color: var(--title-color, inherit);
}
/* Accent-off keeps its brushed-silver name, unless a clip effect asks
   for the transparent fill. */
html.is-accent-off .profile-name:not([class*="text-fx-gradient"]):not([class*="text-fx-rainbow"]):not(.text-fx-holo):not(.text-fx-fire):not(.text-fx-chrome):not(.text-fx-hologram):not(.text-fx-aurora):not(.text-fx-matrix):not(.text-fx-circuit):not(.text-fx-sunset):not(.text-fx-liquid):not(.text-fx-ice) {
  background: linear-gradient(178deg, var(--text-0) 28%, #aeb6c6);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

:is(.profile-name, .profile-title__text, .profile-bio) .fx-char {
  display: inline-block;
  /* No `will-change` here on purpose. Every glyph of the name and bio runs its
     own animation, so declaring `will-change: transform, opacity, filter` on
     each one pinned 20+ independent compositor layers for a handful of pixels
     of movement — layer memory the GPU then has to keep resident while the
     particle canvas draws behind it. The animated properties here are transform
     and opacity, which the compositor promotes on its own the moment an
     animation touches them, so the hint bought nothing and cost plenty. */
}

/* A word wrapper keeps the glyphs of one word together so a split line
   only ever breaks between words, never inside them. */
:is(.profile-name, .profile-title__text, .profile-bio) .fx-word {
  display: inline-block;
  white-space: nowrap;
}

/* Split glyphs paint on their own. `.profile-name` clips a gradient to
   the text box on the parent, so once the letters become inline-block
   children they inherit a transparent fill and no background of their
   own — wave/bounce/flicker/sparkle collapsed into invisible blobs.
   Give each glyph the same gradient the parent would have used. */
.profile-name .fx-char {
  background: linear-gradient(178deg, var(--text-0) 30%, color-mix(in srgb, var(--accent) 62%, var(--text-0)));
  -webkit-background-clip: text;
  background-clip: text;
}
.profile-bio .fx-char {
  color: var(--bio-color, var(--text-1));
}
/* When a line is split into glyphs, the parent must stop clipping its
   own gradient: it would paint a second, unskewed copy behind the
   letters. The per-glyph gradient above is then the only paint. This
   has to outrank the effect blocks (sparkle ships its own clipped
   dot texture), so it is marked important. */
.profile-name:has(.fx-char) {
  background-image: none !important;
  -webkit-background-clip: border-box !important;
  background-clip: border-box !important;
}
/* Clip effects must keep the transparent fill on the glyphs too. */
:is(.profile-name, .profile-title__text, .profile-bio):is(
  .text-fx-gradient, .text-fx-rainbow, .text-fx-gradientArc, .text-fx-holo,
  .text-fx-fire, .text-fx-chrome, .text-fx-hologram, .text-fx-aurora,
  .text-fx-matrix, .text-fx-circuit, .text-fx-sunset, .text-fx-liquid,
  .text-fx-blood, .text-fx-sparkle
) .fx-char {
  background: inherit;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

/* Effects that clip a gradient to the glyphs share this reset so the
   paint is always transparent and never leaks a fallback colour. */
:is(.profile-name, .profile-title__text, .profile-bio):is(
  .text-fx-gradient, .text-fx-rainbow, .text-fx-gradientArc, .text-fx-holo,
  .text-fx-fire, .text-fx-chrome, .text-fx-hologram, .text-fx-aurora,
  .text-fx-matrix, .text-fx-circuit, .text-fx-sunset, .text-fx-liquid,
  .text-fx-blood
) {
  color: transparent !important;
  -webkit-text-fill-color: transparent;
  -webkit-background-clip: text;
  background-clip: text;
  background-repeat: repeat;
}

/* ------------------------------------------------------------- glow */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-glow {
  text-shadow:
    0 0 calc(7px * var(--fx-strength)) color-mix(in srgb, var(--fx-1) 92%, transparent),
    0 0 calc(26px * var(--fx-strength)) color-mix(in srgb, var(--fx-1) 48%, transparent);
  animation: fx-glow-breathe calc(2.4s / var(--fx-speed)) ease-in-out infinite;
}
@keyframes fx-glow-breathe {
  0%, 100% {
    text-shadow:
      0 0 calc(5px * var(--fx-strength)) color-mix(in srgb, var(--fx-1) 80%, transparent),
      0 0 calc(18px * var(--fx-strength)) color-mix(in srgb, var(--fx-1) 38%, transparent);
  }
  50% {
    text-shadow:
      0 0 calc(11px * var(--fx-strength)) color-mix(in srgb, var(--fx-1) 100%, transparent),
      0 0 calc(38px * var(--fx-strength)) color-mix(in srgb, var(--fx-2) 55%, transparent);
  }
}

/* ---------------------------------------------------------- outline */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-outline {
  /* the display name ships with a clipped gradient fill; a hollow effect
     has to drop it or the stroke would sit on top of solid paint */
  background-image: none;
  color: transparent !important;
  -webkit-text-fill-color: transparent;
  -webkit-text-stroke: 1.4px color-mix(in srgb, var(--fx-1) 92%, white);
  paint-order: stroke fill;
  animation: fx-outline-cycle calc(3.2s / var(--fx-speed)) linear infinite;
}
@keyframes fx-outline-cycle {
  0%, 100% { -webkit-text-stroke-color: color-mix(in srgb, var(--fx-1) 92%, white); filter: drop-shadow(0 0 0 transparent); }
  50% { -webkit-text-stroke-color: color-mix(in srgb, var(--fx-2) 92%, white); filter: drop-shadow(0 0 8px color-mix(in srgb, var(--fx-2) 45%, transparent)); }
}

/* ------------------------------------------------------------ shadow
   Hard, opaque extrusion — reads as a chiselled block of type. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-shadow {
  text-shadow:
    2px 2px 0 color-mix(in srgb, var(--fx-2) 78%, #05060b),
    4px 4px 0 color-mix(in srgb, var(--fx-2) 55%, #05060b),
    6px 6px 0 color-mix(in srgb, var(--fx-1) 40%, #05060b),
    8px 8px 18px rgba(0, 0, 0, 0.55);
  animation: fx-shadow-drift calc(2.8s / var(--fx-speed)) ease-in-out infinite;
}
@keyframes fx-shadow-drift {
  0%, 100% {
    text-shadow:
      2px 2px 0 color-mix(in srgb, var(--fx-2) 78%, #05060b),
      4px 4px 0 color-mix(in srgb, var(--fx-2) 55%, #05060b),
      6px 6px 0 color-mix(in srgb, var(--fx-1) 40%, #05060b),
      8px 8px 18px rgba(0, 0, 0, 0.55);
  }
  50% {
    text-shadow:
      3px 3px 0 color-mix(in srgb, var(--fx-2) 85%, #05060b),
      6px 6px 0 color-mix(in srgb, var(--fx-2) 68%, #05060b),
      10px 10px 0 color-mix(in srgb, var(--fx-1) 50%, #05060b),
      14px 14px 26px rgba(0, 0, 0, 0.62);
  }
}

/* An 8px stacked shadow under 17px copy overlaps the next line and
   turns the paragraph into a smear. Body copy extrudes by 1-2px. */
:is(.profile-title__text, .profile-bio).text-fx-shadow {
  text-shadow:
    1px 1px 0 color-mix(in srgb, var(--fx-2) 78%, #05060b),
    2px 2px 0 color-mix(in srgb, var(--fx-2) 52%, #05060b),
    3px 3px 0 color-mix(in srgb, var(--fx-1) 34%, #05060b),
    4px 4px 8px rgba(0, 0, 0, 0.5);
  animation-name: fx-shadow-drift-tight;
}
@keyframes fx-shadow-drift-tight {
  0%, 100% {
    text-shadow:
      1px 1px 0 color-mix(in srgb, var(--fx-2) 78%, #05060b),
      2px 2px 0 color-mix(in srgb, var(--fx-2) 52%, #05060b),
      3px 3px 0 color-mix(in srgb, var(--fx-1) 34%, #05060b),
      4px 4px 8px rgba(0, 0, 0, 0.5);
  }
  50% {
    text-shadow:
      1.5px 1.5px 0 color-mix(in srgb, var(--fx-2) 85%, #05060b),
      3px 3px 0 color-mix(in srgb, var(--fx-2) 66%, #05060b),
      4.5px 4.5px 0 color-mix(in srgb, var(--fx-1) 44%, #05060b),
      6px 6px 12px rgba(0, 0, 0, 0.6);
  }
}

/* -------------------------------------------------------------- neon
   Steady lit tube: crisp white core, coloured gas bloom, faint amber
   filament drift on the second colour. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-neon {
  color: color-mix(in srgb, var(--fx-1) 68%, white) !important;
  -webkit-text-fill-color: currentColor;
  text-shadow:
    0 0 2px #fff,
    0 0 5px #fff,
    0 0 12px var(--fx-1),
    0 0 28px var(--fx-1),
    0 0 52px color-mix(in srgb, var(--fx-1) 62%, transparent),
    1.5px 0 0 color-mix(in srgb, var(--fx-2) 55%, transparent),
    -1.5px 0 0 color-mix(in srgb, var(--fx-2) 40%, transparent);
  animation: fx-neon-buzz calc(2.1s / var(--fx-speed)) ease-in-out infinite;
}
@keyframes fx-neon-buzz {
  0%, 100% { opacity: 1; filter: brightness(1.05); }
  44% { opacity: 1; filter: brightness(1.2); }
  46% { opacity: 0.8; filter: brightness(0.92); }
  48% { opacity: 1; filter: brightness(1.18); }
  72% { opacity: 0.94; }
  74% { opacity: 1; }
}

/* ---------------------------------------------------------- gradient
   Two-colour ribbon, horizontal travel. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-gradient {
  background-image: linear-gradient(100deg, var(--fx-1), var(--fx-2), var(--fx-1), var(--fx-2));
  background-size: 300% 100%;
  animation: fx-gradient-flow calc(2.6s / var(--fx-speed)) linear infinite;
}
@keyframes fx-gradient-flow {
  0% { background-position: 0% 50%; }
  100% { background-position: 300% 50%; }
}

/* ----------------------------------------------------------- rainbow
   Full-spectrum ribbon. Deliberately more saturated and faster than
   the two-colour gradient so the two never read as the same thing. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-rainbow {
  background-image: linear-gradient(
    90deg,
    #ff4d4d, #ffb300, #ffe600, #52ff7a, #00d9ff, #4d6bff, #c04dff, #ff4dc4, #ff4d4d
  );
  background-size: 300% 100%;
  filter: saturate(1.15);
  animation: fx-gradient-flow calc(3.4s / var(--fx-speed)) linear infinite;
}

/* ------------------------------------------------------------ glitch */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-glitch {
  text-shadow: 2px 0 var(--fx-2), -2px 0 color-mix(in srgb, var(--fx-1) 80%, transparent);
  animation: fx-glitch-shift calc(1.9s / var(--fx-speed)) steps(1, end) infinite;
}
/* ::after belongs to the member-id tooltip, so the RGB copy lives on ::before. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-glitch::before {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.85;
  mix-blend-mode: screen;
  color: var(--fx-1);
  text-shadow: -3px 0 var(--fx-2);
  animation: fx-glitch-tear calc(1.9s / var(--fx-speed)) steps(1, end) infinite;
}
@keyframes fx-glitch-shift {
  0%, 100% { transform: translate(0); }
  8% { transform: translate(-1px, 0); }
  10% { transform: translate(2px, -1px); }
  12% { transform: translate(0); }
  47% { transform: translate(1px, 1px); }
  49% { transform: translate(-2px, 0); }
  51% { transform: translate(0); }
  83% { transform: translate(2px, 0); }
  85% { transform: translate(-1px, -1px); }
  87% { transform: translate(0); }
}
/* A 2-3px channel split is a whole stroke width at 17px. Body copy
   glitches by sub-pixel offsets so the words stay readable while the
   line still tears. */
:is(.profile-title__text, .profile-bio).text-fx-glitch {
  text-shadow: 1px 0 color-mix(in srgb, var(--fx-2) 85%, transparent), -1px 0 color-mix(in srgb, var(--fx-1) 70%, transparent);
  animation-name: fx-glitch-shift-tight;
}
:is(.profile-title__text, .profile-bio).text-fx-glitch::before {
  opacity: 0.7;
  text-shadow: -1.5px 0 var(--fx-2);
}
@keyframes fx-glitch-shift-tight {
  0%, 100% { transform: translate(0); }
  8% { transform: translate(-0.5px, 0); }
  10% { transform: translate(1px, -0.5px); }
  12% { transform: translate(0); }
  47% { transform: translate(0.5px, 0.5px); }
  49% { transform: translate(-1px, 0); }
  51% { transform: translate(0); }
  83% { transform: translate(1px, 0); }
  85% { transform: translate(-0.5px, -0.5px); }
  87% { transform: translate(0); }
}
@keyframes fx-glitch-tear {
  0%, 100% { clip-path: inset(0 0 100% 0); transform: translate(0); }
  7% { clip-path: inset(8% 0 64% 0); transform: translate(3px, 0); }
  11% { clip-path: inset(56% 0 20% 0); transform: translate(-3px, 0); }
  15% { clip-path: inset(0 0 100% 0); transform: translate(0); }
  45% { clip-path: inset(30% 0 42% 0); transform: translate(-2px, 0); }
  49% { clip-path: inset(70% 0 6% 0); transform: translate(3px, 0); }
  53% { clip-path: inset(0 0 100% 0); transform: translate(0); }
  82% { clip-path: inset(14% 0 58% 0); transform: translate(2px, 0); }
  86% { clip-path: inset(0 0 100% 0); transform: translate(0); }
}

/* -------------------------------------------------------- typewriter */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-typewriter {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  vertical-align: bottom;
  border-right: 2px solid var(--fx-1);
  animation:
    fx-type-blink calc(0.9s / var(--fx-speed)) step-end infinite,
    fx-type-grow calc(3.4s / var(--fx-speed)) steps(30, end) infinite;
}
@keyframes fx-type-blink {
  0%, 100% { border-color: transparent; }
  50% { border-color: var(--fx-1); }
}
@keyframes fx-type-grow {
  0% { width: 0; }
  12% { width: 100%; }
  78% { width: 100%; }
  100% { width: 0; }
}

/* -------------------------------------------------------------- wave
   Split: letters ride a sine. Unsplit fallback: the whole line rocks. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-wave .fx-char {
  animation: fx-char-wave calc(1.8s / var(--fx-speed)) ease-in-out infinite;
  animation-delay: calc(var(--i) * -0.11s / var(--fx-speed));
}
/* Body copy sets at ~17px with a 1.55 line box, so a 6px lift would
   cross into the next line. Body amplitudes ride the local line height
   instead of a fixed pixel value. */
:is(.profile-title__text, .profile-bio).text-fx-wave .fx-char {
  animation-name: fx-char-wave-tight;
}
@keyframes fx-char-wave-tight {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  25% { transform: translateY(calc(-0.16em * var(--fx-strength))) rotate(-3deg); }
  50% { transform: translateY(0) rotate(0deg); }
  75% { transform: translateY(calc(0.13em * var(--fx-strength))) rotate(3deg); }
}
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-wave {
  animation: fx-wave-line calc(3.6s / var(--fx-speed)) ease-in-out infinite;
  transform-origin: 50% 100%;
}
@keyframes fx-char-wave {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  25% { transform: translateY(calc(-6px * var(--fx-strength))) rotate(-3deg); }
  50% { transform: translateY(0) rotate(0deg); }
  75% { transform: translateY(calc(5px * var(--fx-strength))) rotate(3deg); }
}
@keyframes fx-wave-line {
  0%, 100% { transform: translateY(0) skewY(0deg); }
  25% { transform: translateY(calc(-2px * var(--fx-strength))) skewY(-1.1deg); }
  50% { transform: translateY(0) skewY(0deg); }
  75% { transform: translateY(calc(2px * var(--fx-strength))) skewY(1.1deg); }
}

/* ------------------------------------------------------------ bounce
   Split: hop per glyph. Unsplit fallback: whole line hops and squashes. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-bounce .fx-char {
  animation: fx-char-bounce calc(1.25s / var(--fx-speed)) cubic-bezier(0.28, 0.84, 0.42, 1) infinite;
  animation-delay: calc(var(--i) * -0.08s / var(--fx-speed));
}
/* 14px of hop is right for a display name and wrong for 17px body copy:
   the glyphs of adjacent lines collided. Body copy hops by line height. */
:is(.profile-title__text, .profile-bio).text-fx-bounce .fx-char {
  animation-name: fx-char-bounce-tight;
}
@keyframes fx-char-bounce-tight {
  0%, 18%, 46%, 100% { transform: translateY(0) scale(1); }
  30% { transform: translateY(calc(-0.34em * var(--fx-strength))) scale(1.06); }
  60% { transform: translateY(0) scale(0.98); }
  68% { transform: translateY(calc(-0.1em * var(--fx-strength))) scale(1.01); }
}
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-bounce {
  animation: fx-bounce-line calc(1.25s / var(--fx-speed)) cubic-bezier(0.28, 0.84, 0.42, 1) infinite;
  transform-origin: 50% 100%;
}
@keyframes fx-char-bounce {
  0%, 18%, 46%, 100% { transform: translateY(0) scale(1); }
  30% { transform: translateY(calc(-14px * var(--fx-strength))) scale(1.06); }
  60% { transform: translateY(0) scale(0.98); }
  68% { transform: translateY(calc(-4px * var(--fx-strength))) scale(1.01); }
}
@keyframes fx-bounce-line {
  0%, 18%, 46%, 100% { transform: translateY(0) scale(1, 1); }
  30% { transform: translateY(calc(-7px * var(--fx-strength))) scale(1.02, 1.04); }
  42% { transform: translateY(0) scale(1.04, 0.96); }
  60% { transform: translateY(0) scale(1, 1); }
}

/* ----------------------------------------------------------- flicker
   Split: each glyph drops out on its own clock. Unsplit fallback:
   the tube itself stutters, with a coloured gas halo. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-flicker .fx-char {
  animation: fx-char-flicker calc(2.2s / var(--fx-speed)) steps(1, end) infinite;
  animation-delay: calc(var(--i) * -0.19s / var(--fx-speed));
  text-shadow: 0 0 12px color-mix(in srgb, var(--fx-1) 65%, transparent);
}
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-flicker {
  text-shadow:
    0 0 10px color-mix(in srgb, var(--fx-1) 55%, transparent),
    0 0 26px color-mix(in srgb, var(--fx-2) 30%, transparent);
  animation: fx-flicker-line calc(2.4s / var(--fx-speed)) steps(1, end) infinite;
}
/* Flicker is a stutter, so it is expressed purely in opacity.
   It used to animate `filter: brightness()` as well, on *every glyph* of the
   name and bio. A filter on an animated element cannot be composited: each of
   those glyphs became its own raster layer that had to be re-rendered on every
   animation tick. On a phone that was 20+ full text re-rasters per frame, and
   it was the single largest remaining cost after the particle field.
   The brightness stops in the old keyframes (0.7–1.2) moved the glyph by a few
   percent against a dark background, so the same stops expressed as opacity
   read the same and cost only a compositor property. */
@keyframes fx-char-flicker {
  0%, 100% { opacity: 1; }
  17% { opacity: 0.3; }
  19% { opacity: 1; }
  42% { opacity: 0.8; }
  44% { opacity: 1; }
  63% { opacity: 0.44; }
  65% { opacity: 1; }
  81% { opacity: 0.88; }
}
@keyframes fx-flicker-line {
  0%, 100% { opacity: 1; }
  9% { opacity: 0.52; }
  11% { opacity: 1; }
  28% { opacity: 0.94; }
  30% { opacity: 0.6; }
  32% { opacity: 1; }
  57% { opacity: 0.88; }
  59% { opacity: 1; }
  77% { opacity: 0.68; }
  79% { opacity: 1; }
}

/* ----------------------------------------------------- gradient arc
   Conic wheel of colour rather than a linear ribbon. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-gradientArc {
  background-image: conic-gradient(from 0deg, var(--fx-1), var(--fx-2), #fff, var(--fx-1));
  background-size: 200% 200%;
  animation:
    fx-arc-spin calc(3.2s / var(--fx-speed)) linear infinite,
    fx-arc-hue calc(6.4s / var(--fx-speed)) linear infinite;
}
@keyframes fx-arc-spin {
  0% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
  100% { background-position: 200% 50%; }
}
@keyframes fx-arc-hue {
  0% { filter: hue-rotate(0deg) saturate(1.25); }
  100% { filter: hue-rotate(360deg) saturate(1.25); }
}

/* -------------------------------------------------------------- holo
   Iridescent foil: diagonal pinstripes riding a pastel spectrum. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-holo {
  background-image:
    repeating-linear-gradient(115deg, rgba(255, 255, 255, 0.16) 0 2px, transparent 2px 9px),
    linear-gradient(
      105deg,
      #7dd3fc 0%, #f0abfc 18%, #fef08a 34%, #86efac 50%, #c4b5fd 66%, #7dd3fc 82%, #f0abfc 100%
    );
  background-size: 26px 100%, 320% 100%;
  filter: saturate(1.55) brightness(1.1);
  animation:
    fx-holo-sweep calc(2.4s / var(--fx-speed)) linear infinite,
    fx-holo-shift calc(5s / var(--fx-speed)) linear infinite;
}
@keyframes fx-holo-sweep {
  0% { background-position: 0 0, 0% 50%; }
  100% { background-position: 26px 0, 320% 50%; }
}
@keyframes fx-holo-shift {
  0%, 100% { filter: saturate(1.55) brightness(1.1) hue-rotate(0deg); }
  50% { filter: saturate(1.75) brightness(1.2) hue-rotate(28deg); }
}

/* -------------------------------------------------------------- fire
   Vertical flame stack that licks upward, warm bloom underneath. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-fire {
  background-image: linear-gradient(
    0deg,
    #ff2d00 0%, #ff5a00 16%, #ff8a00 32%, #ffc400 48%, #ffeba0 62%, #fffdf0 70%, #ff7a00 88%, #ff2d00 100%
  );
  background-size: 100% 280%;
  animation: fx-fire-rise calc(1.7s / var(--fx-speed)) linear infinite;
}
@keyframes fx-fire-rise {
  0% {
    background-position: 0% 100%;
    filter: drop-shadow(0 0 7px rgba(255, 94, 0, 0.55));
  }
  50% {
    background-position: 0% 42%;
    filter: drop-shadow(0 1px 14px rgba(255, 140, 0, 0.85)) brightness(1.12);
  }
  100% {
    background-position: 0% 0%;
    filter: drop-shadow(0 0 7px rgba(255, 94, 0, 0.55));
  }
}

/* --------------------------------------------------------------- ice
   Pale crystal with a hard specular edge that drifts; the letters look
   cut rather than lit, which is what separates it from glow/neon. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-ice {
  color: #ecfdff !important;
  -webkit-text-fill-color: currentColor;
  text-shadow:
    0 0 4px rgba(165, 243, 252, 0.95),
    0 0 18px rgba(56, 189, 248, 0.7),
    1px 1px 0 rgba(255, 255, 255, 0.95),
    -1px -1px 0 rgba(125, 211, 252, 0.9),
    0 2px 0 rgba(14, 116, 144, 0.55);
  animation: fx-ice-shimmer calc(2.9s / var(--fx-speed)) ease-in-out infinite;
}
@keyframes fx-ice-shimmer {
  0%, 100% { filter: hue-rotate(0deg) brightness(1) contrast(1); }
  33% { filter: hue-rotate(16deg) brightness(1.14) contrast(1.05); }
  66% { filter: hue-rotate(-10deg) brightness(1.04) contrast(0.98); }
}

/* ----------------------------------------------------------- circuit
   Etched traces that travel across the glyphs, with a terminal glow. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-circuit {
  /* A transparent-background grid paints only the traces, so the glyph
     interiors fell through to the page. Fill the letters with the
     conductor, then lay the grid on top and run a pulse band through
     both. */
  background-image:
    linear-gradient(180deg, transparent 0 36%, #ffffff 44%, #ffffff 48%, transparent 56% 100%),
    repeating-linear-gradient(90deg, transparent 0 7px, color-mix(in srgb, var(--fx-2) 55%, transparent) 7px 8px),
    repeating-linear-gradient(0deg, transparent 0 7px, color-mix(in srgb, var(--fx-1) 45%, transparent) 7px 8px),
    linear-gradient(180deg, #ecfeff, color-mix(in srgb, var(--fx-2) 62%, #ffffff) 45%, color-mix(in srgb, var(--fx-1) 68%, #0f172a));
  background-size: 100% 260%, 16px 16px, 16px 16px, 100% 100%;
  background-repeat: repeat, repeat, repeat, no-repeat;
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--fx-1) 65%, transparent));
  animation: fx-circuit-run calc(1.2s / var(--fx-speed)) linear infinite;
}
@keyframes fx-circuit-run {
  0% { background-position: 0 -160%, 0 0, 0 0, 0 0; }
  100% { background-position: 0 260%, 16px 0, 0 16px, 0 0; }
}
/* A 16px trace grid is finer than a 17px glyph is tall, so body copy
   turns to static. Small text keeps a solid conductive fill and a
   single bright pulse travelling through it — still a circuit, still
   readable at 17px. */
:is(.profile-title__text, .profile-bio).text-fx-circuit {
  background-image:
    linear-gradient(
      180deg,
      transparent 0 34%,
      color-mix(in srgb, var(--fx-2) 85%, #ffffff) 42%,
      #ffffff 46%,
      color-mix(in srgb, var(--fx-2) 85%, #ffffff) 50%,
      transparent 58% 100%
    ),
    linear-gradient(180deg, #ecfeff, color-mix(in srgb, var(--fx-2) 60%, #ffffff) 44%, color-mix(in srgb, var(--fx-1) 62%, #0f172a));
  background-size: 100% 260%, 100% 100%;
  background-repeat: repeat, no-repeat;
  animation-name: fx-circuit-run-small;
}
@keyframes fx-circuit-run-small {
  0% { background-position: 0 -160%, 0 0; }
  100% { background-position: 0 260%, 0 0; }
}

/* ------------------------------------------------------------ blood
   Crimson that darkens at the baseline and pools downward — read as
   wet, not just red. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-blood {
  background-image: linear-gradient(178deg, #fff1f2 0%, #fda4af 22%, #e11d48 62%, #7f1d1d 100%);
  background-size: 100% 200%;
  animation: fx-blood-drip calc(2.6s / var(--fx-speed)) ease-in-out infinite;
}
@keyframes fx-blood-drip {
  0%, 100% {
    background-position: 0% 0%;
    filter: drop-shadow(0 1px 1px rgba(159, 18, 57, 0.85)) drop-shadow(0 0 8px rgba(220, 38, 38, 0.5));
  }
  50% {
    background-position: 0% 30%;
    filter: drop-shadow(0 3px 2px rgba(127, 29, 29, 0.95)) drop-shadow(0 0 16px rgba(248, 113, 113, 0.6));
  }
}

/* ------------------------------------------------------------ chrome
   Polished, almost mirror-sharp bands with a hard specular sweep
   (no soft bloom — that is what makes it metal, not glow). */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-chrome {
  background-image: linear-gradient(
    180deg,
    #ffffff 0%, #e2e8f0 10%, #94a3b8 22%, #1e293b 31%, #f8fafc 40%, #cbd5e1 48%,
    #475569 58%, #f1f5f9 66%, #64748b 78%, #0f172a 88%, #e2e8f0 100%
  );
  background-size: 100% 300%;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.8)) drop-shadow(0 0 2px rgba(255, 255, 255, 0.35));
  animation: fx-chrome-sweep calc(2.8s / var(--fx-speed)) ease-in-out infinite;
}
@keyframes fx-chrome-sweep {
  0%, 100% { background-position: 0% 100%; }
  50% { background-position: 0% 0%; }
}

/* ---------------------------------------------------------- hologram
   Projector glyph: thin scanlines slide up, a bright scan bar crosses
   the text, and the whole thing hums slightly out of focus. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-hologram {
  background-image:
    repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.5) 0 1px, transparent 1px 4px),
    linear-gradient(180deg, color-mix(in srgb, var(--fx-1) 55%, transparent), transparent 42%, color-mix(in srgb, var(--fx-2) 55%, transparent));
  background-size: 100% 4px, 100% 220%;
  filter: drop-shadow(0 0 10px color-mix(in srgb, var(--fx-1) 80%, transparent));
  animation:
    fx-holo-scan calc(1.4s / var(--fx-speed)) linear infinite,
    fx-holo-hum calc(2.6s / var(--fx-speed)) steps(1, end) infinite;
}
@keyframes fx-holo-scan {
  0% { background-position: 0 0, 0% 100%; }
  100% { background-position: 0 4px, 0% 0%; }
}
@keyframes fx-holo-hum {
  0%, 100% { opacity: 1; }
  38% { opacity: 0.9; }
  40% { opacity: 1; }
  76% { opacity: 0.86; }
  78% { opacity: 1; }
}

/* ------------------------------------------------------------ aurora
   Soft northern-light blobs, green/violet, drifting radially. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-aurora {
  background-image:
    radial-gradient(ellipse at 20% 60%, rgba(52, 211, 153, 0.98), transparent 55%),
    radial-gradient(ellipse at 72% 30%, rgba(129, 140, 248, 0.95), transparent 55%),
    radial-gradient(ellipse at 50% 82%, rgba(232, 121, 249, 0.9), transparent 55%),
    linear-gradient(90deg, var(--fx-1), var(--fx-2));
  background-size: 200% 200%, 200% 200%, 200% 200%, 100% 100%;
  filter: blur(0.35px) saturate(1.25) brightness(1.08);
  animation: fx-aurora-move calc(3.6s / var(--fx-speed)) ease-in-out infinite;
}
@keyframes fx-aurora-move {
  0%, 100% { background-position: 0% 40%, 100% 30%, 50% 90%, 0 0; }
  33% { background-position: 35% 80%, 70% 15%, 15% 55%, 0 0; }
  66% { background-position: 70% 25%, 30% 70%, 85% 35%, 0 0; }
}

/* ------------------------------------------------------------ matrix
   Code rain: falling columns of green with the glyph body acting as
   the mask, plus a vertical sweep that makes the columns move. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-matrix {
  /* Two full-density grids at once buried the letterforms. Thin, wider
     traces plus a stronger gradient body keep the code-rain read while
     the glyph shape stays legible. */
  background-image:
    repeating-linear-gradient(90deg, rgba(6, 78, 59, 0.34) 0 1px, transparent 1px 11px),
    repeating-linear-gradient(0deg, rgba(6, 78, 59, 0.22) 0 1px, transparent 1px 14px),
    linear-gradient(180deg, #ffffff, #d1fae5 16%, #6ee7a0 42%, #34d399 68%, #22c55e 100%);
  background-size: 11px 100%, 100% 14px, 100% 100%;
  filter: drop-shadow(0 0 7px rgba(34, 197, 94, 0.95));
  animation: fx-matrix-fall calc(0.9s / var(--fx-speed)) linear infinite;
}
@keyframes fx-matrix-fall {
  0% { background-position: 0 0, 0 0, 0 0; }
  100% { background-position: 0 0, 0 28px, 0 0; }
}
/* Body copy is ~17px, so a 6px column grid lands inside a single stroke
   and the words turn to noise. Small text keeps the falling rows only,
   spaced to the line box so the letterforms survive. */
:is(.profile-title__text, .profile-bio).text-fx-matrix {
  background-image:
    repeating-linear-gradient(0deg, rgba(74, 222, 128, 0.5) 0 1px, transparent 1px 11px),
    linear-gradient(180deg, #f0fdf4, #4ade80 42%, #14532d);
  background-size: 100% 11px, 100% 100%;
}
@keyframes fx-matrix-fall-small {
  0% { background-position: 0 0, 0 0; }
  100% { background-position: 0 18px, 0 0; }
}
:is(.profile-title__text, .profile-bio).text-fx-matrix {
  animation-name: fx-matrix-fall-small;
}

/* --------------------------------------------------------- blurGlow
   Defocused bloom: the type itself is soft, light spills far past the
   glyph edges. Distinct from glow, which keeps the letters razor sharp. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-blurGlow {
  filter: blur(0.6px);
  text-shadow:
    0 0 calc(15px * var(--fx-strength)) color-mix(in srgb, var(--fx-1) 90%, transparent),
    0 0 calc(46px * var(--fx-strength)) color-mix(in srgb, var(--fx-2) 60%, transparent);
  animation: fx-soft-breathe calc(2.7s / var(--fx-speed)) ease-in-out infinite;
}
@keyframes fx-soft-breathe {
  0%, 100% {
    filter: blur(0.5px) saturate(1.1);
    text-shadow:
      0 0 calc(12px * var(--fx-strength)) color-mix(in srgb, var(--fx-1) 80%, transparent),
      0 0 calc(34px * var(--fx-strength)) color-mix(in srgb, var(--fx-2) 48%, transparent);
  }
  50% {
    filter: blur(1.4px) saturate(1.45);
    text-shadow:
      0 0 calc(22px * var(--fx-strength)) color-mix(in srgb, var(--fx-1) 100%, transparent),
      0 0 calc(58px * var(--fx-strength)) color-mix(in srgb, var(--fx-2) 72%, transparent);
  }
}

/* ------------------------------------------------------------ double
   Chromatic ghost: a cyan/magenta offset pair behind the glyphs, plus
   a faint copy that swings. No hard extrusion, unlike Drop shadow. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-double {
  color: color-mix(in srgb, var(--fx-1) 40%, white) !important;
  -webkit-text-fill-color: currentColor;
  text-shadow:
    -3px 0 0 color-mix(in srgb, var(--fx-2) 85%, transparent),
    3px 0 0 color-mix(in srgb, var(--fx-1) 85%, transparent),
    -7px 1px 10px color-mix(in srgb, var(--fx-2) 40%, transparent),
    7px 1px 10px color-mix(in srgb, var(--fx-1) 40%, transparent);
  animation: fx-double-swing calc(2.3s / var(--fx-speed)) ease-in-out infinite;
}
/* 3px of RGB split on a 17px glyph reads as a printing misregistration
   smear. Body copy splits by a fraction of an em instead. */
:is(.profile-title__text, .profile-bio).text-fx-double {
  text-shadow:
    -1px 0 0 color-mix(in srgb, var(--fx-2) 70%, transparent),
    1px 0 0 color-mix(in srgb, var(--fx-1) 70%, transparent),
    -3px 1px 5px color-mix(in srgb, var(--fx-2) 34%, transparent),
    3px 1px 5px color-mix(in srgb, var(--fx-1) 34%, transparent);
  animation-name: fx-double-swing-tight;
}
@keyframes fx-double-swing-tight {
  0%, 100% { text-shadow: -1px 0 0 color-mix(in srgb, var(--fx-2) 70%, transparent), 1px 0 0 color-mix(in srgb, var(--fx-1) 70%, transparent), -3px 1px 5px color-mix(in srgb, var(--fx-2) 34%, transparent), 3px 1px 5px color-mix(in srgb, var(--fx-1) 34%, transparent); }
  50% { text-shadow: -2px 0 0 color-mix(in srgb, var(--fx-2) 82%, transparent), 2px 0 0 color-mix(in srgb, var(--fx-1) 82%, transparent), -6px 1px 8px color-mix(in srgb, var(--fx-2) 42%, transparent), 6px 1px 8px color-mix(in srgb, var(--fx-1) 42%, transparent); }
}
@keyframes fx-double-swing {
  0%, 100% { text-shadow: -3px 0 0 color-mix(in srgb, var(--fx-2) 85%, transparent), 3px 0 0 color-mix(in srgb, var(--fx-1) 85%, transparent), -7px 1px 10px color-mix(in srgb, var(--fx-2) 40%, transparent), 7px 1px 10px color-mix(in srgb, var(--fx-1) 40%, transparent); }
  50% { text-shadow: -6px -1px 0 color-mix(in srgb, var(--fx-2) 92%, transparent), 6px 1px 0 color-mix(in srgb, var(--fx-1) 92%, transparent), -14px 2px 16px color-mix(in srgb, var(--fx-2) 52%, transparent), 14px 2px 16px color-mix(in srgb, var(--fx-1) 52%, transparent); }
}

/* ------------------------------------------------------------ emboss
   Struck into the surface: dark lower lip, lit upper lip, no colour
   travel at all — the only "material" text effect. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-emboss {
  color: color-mix(in srgb, var(--text-1) 88%, #05060b) !important;
  -webkit-text-fill-color: currentColor;
  text-shadow:
    0 1px 0 color-mix(in srgb, var(--text-0) 82%, transparent),
    0 -1px 1px rgba(0, 0, 0, 0.9),
    0 2px 3px rgba(0, 0, 0, 0.65),
    0 -2px 6px rgba(255, 255, 255, 0.08);
  animation: fx-emboss-sheen calc(3.4s / var(--fx-speed)) ease-in-out infinite;
}
@keyframes fx-emboss-sheen {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(1.28) contrast(1.08); }
}

/* ------------------------------------------------------------ sunset
   Vertical dusk horizon: warm at the bottom, violet at the top, with
   the gradient band rising and falling like the sun. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-sunset {
  background-image: linear-gradient(
    0deg,
    #ff4d6d 0%, #ff7a45 22%, #ffb703 40%, #ffd166 52%, #c084fc 72%, #7b2cbf 88%, #2a0a4a 100%
  );
  background-size: 100% 300%;
  animation: fx-sunset-rise calc(3.2s / var(--fx-speed)) ease-in-out infinite;
}
@keyframes fx-sunset-rise {
  0%, 100% { background-position: 0% 100%; filter: drop-shadow(0 0 10px rgba(255, 122, 89, 0.35)); }
  50% { background-position: 0% 20%; filter: drop-shadow(0 0 16px rgba(255, 170, 80, 0.55)); }
}

/* ------------------------------------------------------------ liquid
   Molten mercury: a hard specular band travelling through a metal
   body, with a slow skew wobble so the surface reads as fluid. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-liquid {
  background-image: linear-gradient(
    110deg,
    var(--fx-1) 0%, #fff 18%, color-mix(in srgb, var(--fx-2) 92%, #fff) 32%,
    var(--fx-1) 48%, #fff 62%, var(--fx-2) 78%, var(--fx-1) 100%
  );
  background-size: 320% 100%;
  filter: drop-shadow(0 2px 5px color-mix(in srgb, var(--fx-1) 55%, transparent));
  animation: fx-liquid-flow calc(2.2s / var(--fx-speed)) ease-in-out infinite;
}
@keyframes fx-liquid-flow {
  0% { background-position: 0% 50%; transform: skewX(0deg) scaleY(1); }
  25% { transform: skewX(-2.2deg) scaleY(1.02); }
  50% { background-position: 160% 50%; transform: skewX(0deg) scaleY(1); }
  75% { transform: skewX(2.2deg) scaleY(0.99); }
  100% { background-position: 320% 50%; transform: skewX(0deg) scaleY(1); }
}

/* ----------------------------------------------------------- sparkle
   Split: glyphs glint on a staggered clock. Unsplit fallback: glitter
   specks are baked into the glyph mask and race across it. */
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-sparkle .fx-char {
  /* Glyphs carry their own bright fill. Inheriting the parent's dotted
     sparkle texture left the split body copy almost black, because the
     dots are only a few pixels of a 46px tile. */
  background: linear-gradient(180deg, #ffffff, color-mix(in srgb, var(--fx-2) 45%, #e2e8f0));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  animation: fx-char-sparkle calc(1.6s / var(--fx-speed)) ease-in-out infinite;
  animation-delay: calc(var(--i) * -0.13s / var(--fx-speed));
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--fx-2) 70%, transparent));
}
:is(.profile-name, .profile-title__text, .profile-bio).text-fx-sparkle {
  background-image:
    radial-gradient(circle at 12% 30%, #fff 0 1.6px, transparent 2.6px),
    radial-gradient(circle at 34% 68%, color-mix(in srgb, var(--fx-2) 90%, #fff) 0 1.4px, transparent 2.4px),
    radial-gradient(circle at 58% 24%, #fff 0 1.8px, transparent 2.8px),
    radial-gradient(circle at 79% 62%, color-mix(in srgb, var(--fx-1) 90%, #fff) 0 1.4px, transparent 2.4px),
    linear-gradient(180deg, #fff, #e2e8f0);
  background-size: 46px 46px, 58px 58px, 52px 52px, 64px 64px, 100% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent !important;
  filter: drop-shadow(0 0 8px color-mix(in srgb, var(--fx-1) 55%, transparent));
  animation: fx-sparkle-race calc(2.4s / var(--fx-speed)) linear infinite;
}
@keyframes fx-char-sparkle {
  0%, 100% { transform: scale(1) rotate(0deg); filter: brightness(1); }
  18% { transform: scale(1.18) rotate(-4deg); filter: brightness(1.75); }
  36% { transform: scale(0.96) rotate(3deg); filter: brightness(0.95); }
  54% { transform: scale(1.08) rotate(0deg); filter: brightness(1.45); }
}
@keyframes fx-sparkle-race {
  0% { background-position: 0 0, 0 0, 0 0, 0 0, 0 0; }
  100% { background-position: 46px 23px, -58px 18px, 52px -26px, -64px 32px, 0 0; }
}

/* --- caret must not inherit a clipped text effect --------------------- */
.text-fx-typewriter .profile-title__caret { display: none; }

/* The "low" mode freeze. The guard has to sit on the *ancestor* for all three
   targets: written as `:is(.profile-name, html[data-motion="low"] .profile-title__text, …)`
   the name was matched unconditionally, so `animation: none !important` landed
   on every name effect for every visitor and all 28 hand-written effects plus
   the 35 catalogue ones were frozen at their first frame. The title and the
   bio carried the guard and kept animating, which is why the bug looked like
   "some effects work". */
html[data-motion="low"] :is(.profile-name, .profile-title__text, .profile-bio)[class*="text-fx-"],
html[data-motion="low"] :is(.profile-name, .profile-title__text, .profile-bio)[class*="text-fx-"]::before,
html[data-motion="low"] :is(.profile-name, .profile-title__text, .profile-bio) .fx-char {
  animation: none !important;
}
html[data-motion="low"] :is(.profile-name, .profile-title__text, .profile-bio).text-fx-glitch::before { display: none; }
html[data-motion="low"] :is(.profile-name, .profile-title__text, .profile-bio).text-fx-typewriter { white-space: normal; border-right: 0; }

/* ============================================================
   Member id on hover
   Resting on the display name reveals the account handle.
   Glitch uses ::before for its RGB copy, so the tooltip keeps ::after.
   ============================================================ */

/* The chip sits *above* the name. Below the baseline there is only the 12px
   gap the badge row starts in, so the old `bottom: -0.15em` put a 9px label
   straight into that gap and it read as a smudge. Above the name there is
   real clearance to the avatar. `em` is unsafe here (the name is ~45px), so
   every offset is in px/rem.

   The pseudo must also un-inherit the name's paint: `.profile-name` is
   `background-clip: text; color: transparent` and the text-fx variants add
   their own transparent fill / text-shadow / filter. Without the explicit
   colour + fill + `background-clip: border-box` resets the chip inherits a
   transparent glyph fill and disappears. */
.profile-name {
  position: relative;
}
/* The member chip is a real element, not `::after`. Both pseudo-elements of
   the name are already spoken for — text effects draw bands and strokes on
   them, and two templates draw their own ornaments there — so a chip on
   `::after` was silently replaced by whichever effect the member picked. A
   child element cannot be overwritten by a selector. */
.profile-name__chip {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 2px);
  z-index: 5;
  padding: 2px 8px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: rgba(10, 12, 22, 0.96);
  background-clip: border-box;
  -webkit-background-clip: border-box;
  /* On narrow screens the chip has to overlap the avatar's bottom edge;
     the shadow keeps the pill separated from the photo behind it. */
  box-shadow: 0 8px 20px -10px rgba(0, 0, 0, 0.9);
  color: var(--text-0) !important;
  -webkit-text-fill-color: var(--text-0) !important;
  text-shadow: none;
  -webkit-text-stroke: 0;
  filter: none;
  mix-blend-mode: normal;
  font-family: var(--font-mono);
  font-size: 0.6875rem;   /* 11px */
  font-weight: 500;
  font-style: normal;
  font-variant: normal;
  line-height: 1.35;
  letter-spacing: 0.07em;
  text-transform: none;
  text-indent: 0;
  white-space: nowrap;
  max-width: 60vw;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0;
  transform: translate(-50%, 4px);
  pointer-events: none;
  transition: opacity 0.2s var(--ease-out), transform 0.2s var(--ease-out);
}
.profile-name:hover .profile-name__chip,
.profile-name:focus-visible .profile-name__chip,
.profile-name:focus-within .profile-name__chip {
  opacity: 1;
  transform: translate(-50%, 0);
}
/* A profile without a username must not paint a bare "@", and must not
   become a tab stop (see renderIdentity). */
.profile-name[data-member-id=""] .profile-name__chip { display: none; }
/* The name is a real tab stop now (see renderIdentity), so it needs a ring
   of its own; the outline is painted outside the clipped text box. */
.profile-name:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent-2) 72%, transparent);
  outline-offset: 7px;
  border-radius: 6px;
}

/* Typewriter is the one effect that clips its own box: it hides the text
   past an animated width with `overflow: hidden`, and Chromium clips the
   absolutely positioned chip with it. While the name is hovered or focused
   the clip is lifted; the animation keeps running so the name does not
   move. */
.profile-name.text-fx-typewriter:hover,
.profile-name.text-fx-typewriter:focus-visible,
.profile-name.text-fx-typewriter:focus-within {
  overflow: visible;
}
html[data-motion="low"] .profile-name__chip { transition: none; }
@media (prefers-reduced-motion: reduce) {
  .profile-name__chip { transition: none; }
}
