/* Layout 1: clean dark card */

:root {
  --bg: #0b0d14;
  --surface: #161a25;
  --border: #2a3040;
  --text: #eef0f5;
  --dim: #9aa3b5;
  --accent: #8a93a6;   /* ACCENT_DEFAULT: neutral slate, shared with Layout 2; overridden from <html> when a Bio box colour is set */
  --accent2: #a4abba;  /* lighter partner (accent_tint of the above) */
  --btn-opacity: 1;   /* link-tile fill opacity, overridden from <html> */
  --box-opacity: 1;   /* profile-box fill + border opacity, overridden from <html> */
  --box-line: var(--border);  /* profile-box border colour; the 'outline' style swaps it for the accent */
}

* { box-sizing: border-box; }

/* Visible keyboard focus for interactive elements; :where() keeps specificity at 0. */
:where(a, button, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent, #8a93a6);
  outline-offset: 2px;
}

body {
  margin: 0;
  min-height: 100vh;
  /* On a phone 100vh is the height with the browser bars *hidden*, so a short
     page came out taller than the screen on load and scrolled for no reason.
     svh = the height with the bars showing; 100vh stays as the fallback. Same
     as Layout 2's .container. */
  min-height: 100svh;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
  padding: 28px 20px;
  /* viewport-fit=cover: grow into the notch strips; env() is 0 off-notch. */
  padding: max(28px, env(safe-area-inset-top)) max(20px, env(safe-area-inset-right)) max(28px, env(safe-area-inset-bottom)) max(20px, env(safe-area-inset-left));
}

/* Optional page background: fixed layers behind everything, plus a readability scrim. */
/* Sized to the *large* viewport (lvh = browser bars hidden) instead of inset: 0 /
   100%: on a real phone the address bar / toolbar slide away while scrolling,
   and a 100%-tall fixed layer resized (and re-cropped its cover image / video)
   with them, so the background visibly jumped. At 100lvh it never resizes - the
   bars just overlap its edge. 100vh is the fallback for browsers without lvh. */
.page-bg,
.page-bg-scrim {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
  height: 100lvh;
}
.page-bg {
  z-index: -2;
  border: 0;
  object-fit: cover;               /* <video> */
  background-size: cover;          /* <div>   */
  background-position: center;
  background-repeat: no-repeat;
}
.page-bg-scrim {
  z-index: -1;
  background: rgba(11, 13, 20, 0.62);
}

/* auto vertical margins centre the card between the rails on tall viewports and
   collapse when the page scrolls. */
.card { width: 100%; max-width: 420px; text-align: center; position: relative; margin-block: auto; }

.cf-turnstile:not(.turnstile-row) {
  position: fixed;
  left: 0;
  bottom: 0;
  width: 0 !important;
  height: 0 !important;
  overflow: hidden;
}

/* Identity box: avatar, name, bio and badges in one bordered panel; with the
   view count below it forms .profile-col (a sticky left column on wide screens). */
.profile {
  border: 1px solid;
  border-color: color-mix(in srgb, var(--box-line) calc(var(--box-opacity) * 100%), transparent);
  background: color-mix(in srgb, var(--surface) calc(var(--box-opacity) * 100%), transparent);
  border-radius: 16px;
  padding: 28px 24px 24px;
  transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

/* Profile-box style, set on <html>. Skins the identity card and its sub-boxes
   (badge strip, location line, view-count pill) as a matched set. 'rounded' is
   the default; --box-opacity fades fill + border; 'outline' swaps the border
   for the accent. */
[data-box-style="sharp"]   .profile,
[data-box-style="sharp"]   .badges,
[data-box-style="sharp"]   .location-line,
[data-box-style="sharp"]   .view-count { border-radius: 4px; }

[data-box-style="outline"] { --box-line: color-mix(in srgb, var(--accent) 45%, var(--border)); }
[data-box-style="outline"] .profile,
[data-box-style="outline"] .badges,
[data-box-style="outline"] .location-line,
[data-box-style="outline"] .view-count { background: transparent; }

[data-box-style="shadow"]  .profile,
[data-box-style="shadow"]  .badges,
[data-box-style="shadow"]  .location-line,
[data-box-style="shadow"]  .view-count { border-color: transparent; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.32); }
[data-box-style="shadow"]  .profile { box-shadow: 0 12px 34px rgba(0, 0, 0, 0.4); }

/* Profile-box hover feedback: a data-box-hover base motion (lift / zoom / none,
   motion-gated) plus stacking add-ons data-box-outline and data-box-glow.
   Mouse only - a tap would leave them stuck on. */
@media (hover: hover) {
  [data-box-outline] .profile:hover { border-color: var(--accent); }
  [data-box-glow]    .profile:hover {
    box-shadow: 0 0 18px color-mix(in srgb, var(--accent) 45%, transparent),
                0 0 46px color-mix(in srgb, var(--accent) 22%, transparent);
  }
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  [data-box-hover="lift"] .profile:hover { transform: translateY(-1px); }
  [data-box-hover="zoom"] .profile:hover { transform: scale(1.02); }
}

/* 'tilt' is pointer-driven from layout.js; give it a bit more smoothing. */
[data-box-hover="tilt"] .profile {
  transition: transform 0.2s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}

/* Neon glow (data-box-neon on <html>): an always-on accent halo around the
   identity card, motion-gated pulse. */
[data-box-neon="1"] .profile {
  box-shadow: 0 0 18px color-mix(in srgb, var(--accent) 42%, transparent),
              0 0 50px color-mix(in srgb, var(--accent) 20%, transparent);
}
@media (prefers-reduced-motion: no-preference) {
  [data-box-neon="1"] .profile { animation: fx-neon-box 2.6s ease-in-out infinite; }
}
@keyframes fx-neon-box {
  0%, 100% { box-shadow: 0 0 15px color-mix(in srgb, var(--accent) 38%, transparent),
                         0 0 42px color-mix(in srgb, var(--accent) 18%, transparent); }
  50%      { box-shadow: 0 0 28px color-mix(in srgb, var(--accent) 55%, transparent),
                         0 0 66px color-mix(in srgb, var(--accent) 28%, transparent); }
}

/* The location + view-count pills follow the same box effects - neon, hover
   outline, hover glow - at a smaller scale to suit their size. (Not the hover
   motion: lift / zoom / tilt on a small pill just reads as fidgety.) */
@media (hover: hover) {
  [data-box-outline] :is(.location-line, .view-count):hover { border-color: var(--accent); }
  [data-box-glow]    :is(.location-line, .view-count):hover {
    box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 45%, transparent),
                0 0 24px color-mix(in srgb, var(--accent) 22%, transparent);
  }
}
[data-box-neon="1"] :is(.location-line, .view-count) {
  box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 42%, transparent),
              0 0 26px color-mix(in srgb, var(--accent) 20%, transparent);
}
@media (prefers-reduced-motion: no-preference) {
  [data-box-neon="1"] :is(.location-line, .view-count) { animation: fx-neon-pill 2.6s ease-in-out infinite; }
}
@keyframes fx-neon-pill {
  0%, 100% { box-shadow: 0 0 8px color-mix(in srgb, var(--accent) 38%, transparent),
                         0 0 22px color-mix(in srgb, var(--accent) 18%, transparent); }
  50%      { box-shadow: 0 0 14px color-mix(in srgb, var(--accent) 55%, transparent),
                         0 0 34px color-mix(in srgb, var(--accent) 28%, transparent); }
}

.profile-col { margin-bottom: 22px; }
.link-col { min-width: 0; }

@media (min-width: 820px) {
  .card {
    max-width: 760px;
    display: grid;
    grid-template-columns: 300px minmax(0, 1fr);
    gap: 26px;
    align-items: start;
  }
  /* Held in the vertical middle of the screen when the links are the taller
     column: sticky top = half the room left around the profile (--pc-h is its
     height, kept up to date by layout.js). Sticky also pushes it down to that
     line on load, but never past the end of the links. Never above 28px from
     the top (a profile taller than the screen); without JS --pc-h falls back to
     100vh, which works out to the old pinned-to-the-top 28px. */
  .profile-col {
    position: sticky;
    top: max(28px, calc(50vh - var(--pc-h, 100vh) / 2));
    top: max(28px, calc(50svh - var(--pc-h, 100svh) / 2));
    margin-bottom: 0;
  }
  /* Centre the links against the profile box when they're the shorter column. */
  .link-col { align-self: center; }
}

.avatar-wrap {
  position: relative;
  isolation: isolate;   /* contains the orbit ring's z-index:-1, like #profile-block does in Layout 2 */
  -webkit-tap-highlight-color: transparent;   /* no grey/blue flash when tapped (spin + page-number tooltip) */
  display: block;
  width: 88px;
  height: 88px;
  margin: 0 auto 18px;
}
.avatar,
.avatar-img {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  border-radius: 50%;
}
.avatar {
  display: grid;
  place-items: center;
  font-size: 2rem;
  font-weight: 800;
  color: #fff;
  background: linear-gradient(135deg, var(--accent), var(--accent2));
}
.avatar-img { object-fit: cover; }

/* orbit / spin: the photo is clickable for the fast-spin easter egg (layout.js) */
[data-avatar-anim="orbit"] .avatar-wrap,
[data-avatar-anim="spin"] .avatar-wrap { cursor: pointer; }

/* Profile-photo animation (data-avatar-anim on <html>): orbit / spin add a ring;
   pulse / float / glow transform or light up the photo. */
@media (prefers-reduced-motion: no-preference) {
  [data-avatar-anim="pulse"] .avatar,
  [data-avatar-anim="pulse"] .avatar-img { animation: av-pulse 2.4s ease-in-out infinite; }

  [data-avatar-anim="float"] .avatar,
  [data-avatar-anim="float"] .avatar-img { animation: av-float 3s ease-in-out infinite; }

  [data-avatar-anim="glow"] .avatar,
  [data-avatar-anim="glow"] .avatar-img { animation: av-glow 2.6s ease-in-out infinite; }

  [data-avatar-anim="spin"] .avatar-wrap::before {
    content: "";
    position: absolute;
    inset: -6px;
    border-radius: 50%;
    border: 3px solid transparent;
    border-top-color: var(--avatar-col, var(--accent));
    border-right-color: var(--avatar-col, var(--accent));
    animation: av-spin 1.5s linear infinite;
  }

  /* Click / tap the photo: one fast whip-around, same as the orbit ring. */
  [data-avatar-anim="spin"] .avatar-wrap.fast-orbit::before {
    animation: av-spin-fast 0.5s linear forwards;
  }

  /* Orbit: a soft accent ball straddling the photo rim (offset scaled for the 88px avatar). */
  [data-avatar-anim="orbit"] .avatar-wrap::after {
    content: "";
    position: absolute;
    inset: -2px;
    border-radius: 50%;
    background: radial-gradient(circle at 6px 6px, var(--avatar-col, var(--accent)) 10%, transparent 15%);
    animation: av-spin 3s linear infinite;
    z-index: -1;
    filter: blur(1px);
    box-shadow: none;
  }

  /* Click / tap the photo: one quick whip-around, then back to the slow orbit
     (.fast-orbit toggled from layout.js; distinct keyframe name forces a restart). */
  [data-avatar-anim="orbit"] .avatar-wrap.fast-orbit::after {
    animation: av-spin-fast 0.5s linear forwards;
  }
}

@keyframes av-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.06); } }
@keyframes av-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }
@keyframes av-spin      { to { transform: rotate(360deg); } }
@keyframes av-spin-fast { to { transform: rotate(360deg); } }  /* separate name so the click spin restarts */
@keyframes av-glow {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--avatar-col, var(--accent)) 40%, transparent); }
  50%      { box-shadow: 0 0 20px 5px color-mix(in srgb, var(--avatar-col, var(--accent)) 40%, transparent); }
}

h1 { margin: 0 0 16px; font-size: 1.6rem; overflow-wrap: anywhere; }

/* Display-name text effect (data-name-effect on <html>). Static bases stay
   outside the media query; only the movement is motion-gated. */
[data-name-effect="gradient"] h1,
[data-name-effect="shine"] h1 {
  background: linear-gradient(92deg, var(--accent, #8a93a6), var(--accent2, #a4abba));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* Lighten the far gradient stop so the sweep reads even when --accent2 is only
   a slight tint of --accent. Not applied to 'shine'. */
[data-name-effect="gradient"] h1 {
  background-image: linear-gradient(92deg,
      var(--accent, #8a93a6),
      color-mix(in srgb, var(--accent2, #a4abba), #fff 42%));
}
[data-name-effect="shine"] h1 {
  /* Two background-image layers: a moving white streak over a static accent
     gradient that always fills the text. */
  background-image:
      linear-gradient(100deg, transparent 44%, rgba(255, 255, 255, 0.92) 50%, transparent 56%),
      linear-gradient(92deg, var(--accent, #8a93a6), var(--accent2, #a4abba));
  background-size: 250% 100%, 100% 100%;
  background-repeat: no-repeat;
  background-position: 50% 0, 0 0;   /* streak centred when motion is off */
}
[data-name-effect="glitch"] h1 {
  text-shadow: 1px 0 #ff2e97, -1px 0 var(--accent, #8a93a6);
}
/* Neon glow (data-name-neon on <html>): a drop-shadow that layers over any base
   effect. The pulse is skipped while the glitch base runs. */
[data-name-neon="1"] h1 {
  filter: drop-shadow(0 0 5px var(--accent, #8a93a6)) drop-shadow(0 0 13px var(--accent, #8a93a6));
}
@media (prefers-reduced-motion: no-preference) {
  [data-name-effect="shine"] h1  { animation: name-shine 3.6s linear infinite; }
  [data-name-effect="glitch"] h1 { animation: name-glitch 1.9s steps(2, end) infinite; }
  [data-name-neon="1"]:not([data-name-effect="glitch"]) h1 { animation: name-neon 2.2s ease-in-out infinite; }
  /* Re-declare shine + neon as one animation list where both are combined. */
  [data-name-effect="shine"][data-name-neon="1"] h1 {
    animation: name-shine 3.6s linear infinite, name-neon 2.2s ease-in-out infinite;
  }
}
@keyframes name-shine {
  0%   { background-position: 120% 0, 0 0; }   /* streak off the left edge */
  100% { background-position: -30% 0, 0 0; }   /* streak off the right edge */
}
@keyframes name-glitch {
  0%, 100% { text-shadow: 1px 0 #ff2e97, -1px 0 var(--accent, #8a93a6); transform: translate(0); }
  25%      { text-shadow: -2px 0 #ff2e97, 2px 0 var(--accent, #8a93a6); transform: translate(1px, -1px); }
  50%      { text-shadow: 2px 0 #ff2e97, -2px 0 var(--accent, #8a93a6); transform: translate(-1px, 1px); }
  75%      { text-shadow: -1px 0 #ff2e97, 1px 0 var(--accent, #8a93a6); transform: translate(1px, 0); }
}
@keyframes name-neon {
  0%, 100% { filter: drop-shadow(0 0 4px var(--name-col, var(--accent, #8a93a6))) drop-shadow(0 0 11px var(--name-col, var(--accent, #8a93a6))); }
  50%      { filter: drop-shadow(0 0 7px var(--name-col, var(--accent, #8a93a6))) drop-shadow(0 0 22px var(--name-col, var(--accent, #8a93a6))); }
}

/* Bio text effect (data-bio-effect / data-bio-neon on <html>): the display-name
   set applied to .bio, reusing the name-* keyframes. */
[data-bio-effect="gradient"] .bio,
[data-bio-effect="shine"] .bio {
  background: linear-gradient(92deg, var(--accent, #8a93a6), var(--accent2, #a4abba));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
[data-bio-effect="gradient"] .bio {
  background-image: linear-gradient(92deg,
      var(--accent, #8a93a6),
      color-mix(in srgb, var(--accent2, #a4abba), #fff 42%));
}
[data-bio-effect="shine"] .bio {
  background-image:
      linear-gradient(100deg, transparent 44%, rgba(255, 255, 255, 0.92) 50%, transparent 56%),
      linear-gradient(92deg, var(--accent, #8a93a6), var(--accent2, #a4abba));
  background-size: 250% 100%, 100% 100%;
  background-repeat: no-repeat;
  background-position: 50% 0, 0 0;
}
[data-bio-effect="glitch"] .bio {
  text-shadow: 1px 0 #ff2e97, -1px 0 var(--accent, #8a93a6);
}
[data-bio-neon="1"] .bio {
  filter: drop-shadow(0 0 4px var(--accent, #8a93a6)) drop-shadow(0 0 11px var(--accent, #8a93a6));
}
@media (prefers-reduced-motion: no-preference) {
  [data-bio-effect="shine"] .bio  { animation: name-shine 3.6s linear infinite; }
  [data-bio-effect="glitch"] .bio { animation: name-glitch 1.9s steps(2, end) infinite; }
  [data-bio-neon="1"]:not([data-bio-effect="glitch"]) .bio { animation: bio-neon 2.2s ease-in-out infinite; }
  [data-bio-effect="shine"][data-bio-neon="1"] .bio {
    animation: name-shine 3.6s linear infinite, bio-neon 2.2s ease-in-out infinite;
  }
}
/* Own keyframes so the bio glow follows --bio-col, not --name-col. */
@keyframes bio-neon {
  0%, 100% { filter: drop-shadow(0 0 4px var(--bio-col, var(--accent, #8a93a6))) drop-shadow(0 0 11px var(--bio-col, var(--accent, #8a93a6))); }
  50%      { filter: drop-shadow(0 0 7px var(--bio-col, var(--accent, #8a93a6))) drop-shadow(0 0 22px var(--bio-col, var(--accent, #8a93a6))); }
}

/* Restore a solid fill for 'gradient' / 'shine' where background-clip: text
   isn't honoured (old engines, forced-colors mode). */
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  [data-name-effect="gradient"] h1,
  [data-name-effect="shine"] h1,
  [data-bio-effect="gradient"] .bio,
  [data-bio-effect="shine"] .bio {
    background: none;
    color: var(--text);
    -webkit-text-fill-color: var(--text);
  }
}
@media (forced-colors: active) {
  [data-name-effect] h1,
  [data-bio-effect] .bio {
    background: none !important;
    color: CanvasText !important;
    -webkit-text-fill-color: CanvasText !important;
  }
}

/* Bio colour override (data-bio-col + --bio-col): retarget --accent / --accent2
   locally so the effects run in the picked colour. */
[data-bio-col] .bio {
  --accent: var(--bio-col);
  --accent2: color-mix(in srgb, var(--bio-col) 78%, #fff);
  color: var(--bio-col);
  -webkit-text-fill-color: var(--bio-col);
}
[data-bio-effect="gradient"][data-bio-col] .bio,
[data-bio-effect="shine"][data-bio-col] .bio {
  color: transparent;
  -webkit-text-fill-color: transparent;
}
/* Emoji (e_emoji()) keep their own colours under the text effects. */
.bio .emo { -webkit-text-fill-color: var(--bio-col, var(--dim)); text-shadow: none; }

/* ---- Per-element colour overrides (name/icon/badge/bg) ----
   data-*-col on <html> flags "a colour is set"; --*-col carries the hex. */

/* Display name: retarget --accent / --accent2 locally so the effects run in the
   picked colour; gradient + shine re-assert `transparent` below. */
[data-name-col] h1 {
  --accent: var(--name-col);
  --accent2: color-mix(in srgb, var(--name-col) 78%, #fff);
  color: var(--name-col);
  -webkit-text-fill-color: var(--name-col);
}
[data-name-effect="gradient"][data-name-col] h1,
[data-name-effect="shine"][data-name-col] h1 {
  color: transparent;
  -webkit-text-fill-color: transparent;
}
/* Emoji (e_emoji()) keep their own colours under the name effects. */
h1 .emo { -webkit-text-fill-color: var(--name-col, var(--text)); text-shadow: none; }

/* Link / handle icons: swap the flat-white <img> for a masked <span> painted
   in the chosen colour. */
[data-icon-col] .link-icon {
  display: inline-block;
  filter: none;
  background-color: var(--icon-col);
  -webkit-mask: var(--src) center / contain no-repeat;
  mask: var(--src) center / contain no-repeat;
}

/* "Brand colours" platform icons: the SVG's own fills - no white filter, and
   no icon-colour mask even when one is set (it still tints the glow). */
.link-icon.is-brand,
[data-icon-col] .link-icon.is-brand,
.link-icon.is-custom,
[data-icon-col] .link-icon.is-custom {
  filter: none;
  background: none;
  -webkit-mask: none;
  mask: none;
}
/* A handle's own icon image (Pro): fitted, slightly rounded. */
.link-icon.is-custom { object-fit: contain; border-radius: 4px; }

/* Badge glyphs: same masked-span treatment. */
[data-badge-col] .badge {
  display: inline-block;
  filter: none;
  background-color: var(--badge-col);
  -webkit-mask: var(--src) center / contain no-repeat;
  mask: var(--src) center / contain no-repeat;
}

/* Neon glow (data-badge-neon on <html>): an always-on accent glow around each
   badge glyph (motion-gated pulse), carried on .badge-container.
   Follows the Badges colour when set, else the accent. */
[data-badge-neon="1"] .badge-container {
  filter: drop-shadow(0 0 4px var(--badge-col, var(--accent, #8a93a6)))
          drop-shadow(0 0 10px var(--badge-col, var(--accent, #8a93a6)));
}
@media (prefers-reduced-motion: no-preference) {
  [data-badge-neon="1"] .badge-container { animation: fx-neon-badge 2.2s ease-in-out infinite; }
}
@keyframes fx-neon-badge {
  0%, 100% { filter: drop-shadow(0 0 3px var(--badge-col, var(--accent, #8a93a6)))
                     drop-shadow(0 0 8px var(--badge-col, var(--accent, #8a93a6))); }
  50%      { filter: drop-shadow(0 0 6px var(--badge-col, var(--accent, #8a93a6)))
                     drop-shadow(0 0 16px var(--badge-col, var(--accent, #8a93a6))); }
}

/* Solid page background: sits under .page-bg so an image/video still covers it. */
[data-bg-col] body { background: var(--bg-col); }

/* Optional bio: a line or two under the name, before badges/links. */
.bio {
  max-width: 36ch;
  margin: -6px auto 22px;
  color: var(--dim);
  font-size: 0.95rem;
  line-height: 1.55;
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* Badge strip: an inset box sharing the profile-box style + --box-opacity. */
.badges {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 12px;
  margin: 18px 0 0;
  padding: 12px 14px;
  border: 1px solid;
  border-color: color-mix(in srgb, var(--box-line) calc(var(--box-opacity) * 100%), transparent);
  border-radius: 12px;
  background: color-mix(in srgb, var(--bg) calc(var(--box-opacity) * 100%), transparent);
}
.badge {
  width: 22px;
  height: 22px;
  filter: brightness(0) invert(1); /* dark monochrome SVGs -> flat white */
  transition: transform 0.15s ease;
}
/* "Badge colours": the SVG's own fills, no white filter / colour mask. */
.badge.is-brand,
[data-badge-col] .badge.is-brand { filter: none; background: none; -webkit-mask: none; mask: none; }

.badge-container { position: relative; display: inline-flex; }

/* Badge hover animation (data-badge-anim on <html>): plays on each glyph on
   hover, default 'pop', motion-gated. */
@media (prefers-reduced-motion: no-preference) {
  .badge-container:hover .badge                            { transform: scale(1.18); }
  [data-badge-anim="tilt"]   .badge-container:hover .badge { transform: rotate(-12deg) scale(1.08); }
  [data-badge-anim="bounce"] .badge-container:hover .badge { transform: none; animation: bd-bounce 0.5s ease; }
  [data-badge-anim="spin"]   .badge-container:hover .badge { transform: none; animation: bd-spin 0.6s linear; }
  [data-badge-anim="wobble"] .badge-container:hover .badge { transform: none; animation: bd-wobble 0.6s ease; }
}
[data-badge-anim="none"] .badge-container:hover .badge { transform: none; animation: none; }
@keyframes bd-bounce {
  0%, 100% { transform: translateY(0) scale(1); }
  35%      { transform: translateY(-5px) scale(1.12); }
  65%      { transform: translateY(1px) scale(1.05); }
}
@keyframes bd-spin { from { transform: rotate(0); } to { transform: rotate(360deg); } }
@keyframes bd-wobble {
  0%, 100% { transform: rotate(0); }
  20%      { transform: rotate(-13deg); }
  45%      { transform: rotate(9deg); }
  70%      { transform: rotate(-5deg); }
  88%      { transform: rotate(2deg); }
}

/* Badge tooltips; the profile photo's "Page #N" tooltip shares the look. */
.badge-container .tooltip,
.avatar-wrap .tooltip {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(calc(-50% + var(--tip-shift, 0px)));   /* --tip-shift: tip-edge.js */
  max-width: calc(100vw - 16px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 5px 10px;
  border-radius: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 0.75rem;
  font-weight: 500;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.15s ease;
  pointer-events: none;
  z-index: 10;
}
.badge-container:hover .tooltip,
.badge-container.tip-open .tooltip,
.avatar-wrap:hover .tooltip,
.avatar-wrap.tip-open .tooltip { opacity: 1; visibility: visible; }   /* tap to reveal on touch */

/* Link tiles get the same hover tooltip as the badges, above the row. */
.link .tooltip {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(calc(-50% + var(--tip-shift, 0px)));
  max-width: min(90vw, 340px);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 5px 10px;
  border-radius: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 0.75rem;
  font-weight: 500;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.15s ease;
  pointer-events: none;
  z-index: 100;
}
.link:hover .tooltip,
.link.tip-open .tooltip,
.link.is-copied .tooltip { opacity: 1; visibility: visible; }   /* tap to reveal on touch; "Copied!" flash */
.link--copy { cursor: pointer; }   /* no-URL social: click copies the handle (layout.js) */
/* Lift the hovered tile (and its tooltip) above the later sibling tiles. */
.link:hover,
.link.tip-open { z-index: 20; }

.links { display: flex; flex-direction: column; gap: 12px; }
.link {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 14px 18px;
  border-radius: 12px;
  text-decoration: none;
  background: color-mix(in srgb, var(--surface) calc(var(--btn-opacity) * 100%), transparent);
  border: 1px solid var(--border);
  color: var(--text);
  transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}
/* Link-tile hover effect: a data-link-hover base motion plus data-link-outline /
   data-link-glow add-ons; 'tilt' is driven from layout.js. Mouse only. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  /* Picture buttons (.link-card) share these, and the style / add-on rules below. */
  [data-link-hover="lift"] .link:hover { transform: translateY(-1px); }
  [data-link-hover="lift"] .link-card:hover { transform: translateY(-2px); }   /* a big card needs a bigger nudge to read */
  [data-link-hover="zoom"] :is(.link, .link-card):hover { transform: scale(1.03); }
  /* 'pop': the tile scales up, the icon scales further on icon hover. */
  [data-link-hover="pop"] :is(.link, .link-card):hover { transform: scale(1.06); }
  [data-link-hover="pop"] :is(.link-icon, .link-card--icon .link-card-media):hover { transform: scale(1.4) rotateY(15deg); }
  /* 'spin' / 'wobble': tile scale-up as 'pop', plus a spin/wobble on icon hover. */
  [data-link-hover="spin"] :is(.link, .link-card):hover { transform: scale(1.06); }
  [data-link-hover="spin"] :is(.link-icon, .link-card--icon .link-card-media):hover { animation: bd-spin 0.6s linear; }
  [data-link-hover="wobble"] :is(.link, .link-card):hover { transform: scale(1.06); }
  [data-link-hover="wobble"] :is(.link-icon, .link-card--icon .link-card-media):hover { animation: bd-wobble 0.6s ease; }
  [data-link-hover="spin"] :is(.link-icon, .link-card--icon .link-card-media):hover,
  [data-link-hover="wobble"] :is(.link-icon, .link-card--icon .link-card-media):hover { scale: 1.4; }
}
.link .handle {
  color: var(--dim);
  font-size: 0.9rem;
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Link-button style + label switches, set on <html>; defaults need no rule. */
/* 'pill' on a square picture card means extra-round corners rather than a capsule. */
[data-btn-style="pill"]    .link { border-radius: 999px; }
[data-btn-style="pill"]    .link-card { border-radius: 24px; }
[data-btn-style="sharp"]   :is(.link, .link-card) { border-radius: 4px; }
[data-btn-style="outline"] :is(.link, .link-card) {
  background: transparent;
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}
[data-btn-style="shadow"]  :is(.link, .link-card) { border-color: transparent; box-shadow: 0 6px 16px rgba(0, 0, 0, 0.38); }
@media (hover: hover) {
  [data-btn-style="shadow"]  :is(.link, .link-card):hover { box-shadow: 0 10px 22px rgba(0, 0, 0, 0.5); }

  /* 'outline' add-on, re-asserted after the button-style rules. */
  [data-link-outline] :is(.link, .link-card):hover { border-color: var(--icon-col, var(--accent)); }

  /* 'glow' add-on: a soft halo, re-asserted after the button-style rules. */
  [data-link-glow] :is(.link, .link-card):hover {
    box-shadow: 0 0 16px color-mix(in srgb, var(--icon-col, var(--accent)) 42%, transparent),
                0 0 40px color-mix(in srgb, var(--icon-col, var(--accent)) 20%, transparent);
  }
}

/* Neon glow (data-link-neon on <html>): an always-on halo around every link
   tile (motion-gated pulse). */
[data-link-neon="1"] :is(.link, .link-card) {
  box-shadow: 0 0 13px color-mix(in srgb, var(--icon-col, var(--accent)) 42%, transparent),
              0 0 32px color-mix(in srgb, var(--icon-col, var(--accent)) 20%, transparent);
}
@media (prefers-reduced-motion: no-preference) {
  [data-link-neon="1"] :is(.link, .link-card) { animation: fx-neon-link 2.4s ease-in-out infinite; }
}
@keyframes fx-neon-link {
  0%, 100% { box-shadow: 0 0 11px color-mix(in srgb, var(--icon-col, var(--accent)) 38%, transparent),
                         0 0 26px color-mix(in srgb, var(--icon-col, var(--accent)) 18%, transparent); }
  50%      { box-shadow: 0 0 19px color-mix(in srgb, var(--icon-col, var(--accent)) 55%, transparent),
                         0 0 44px color-mix(in srgb, var(--icon-col, var(--accent)) 28%, transparent); }
}

/* Icon only: the social links become a centred, wrapping grid of square icon
   buttons; custom and picture buttons keep their text, one per row. */
[data-btn-labels="0"] .links {
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
}
[data-btn-labels="0"] .link:not(.link--custom) {
  flex: none;
  width: 56px;
  height: 56px;
  padding: 0;
  justify-content: center;
}
[data-btn-labels="0"] .link:not(.link--custom) .link-left { gap: 0; }
/* Hide the platform label, not the icon (a bare <span> when icon_color is set). */
[data-btn-labels="0"] .link:not(.link--custom) .link-left span:not(.link-icon),
[data-btn-labels="0"] .link:not(.link--custom) .handle { display: none; }
[data-btn-labels="0"] .link:not(.link--custom) .link-icon { width: 26px; height: 26px; }
[data-btn-labels="0"] .links > :is(.link--custom, .link-card) { flex: 1 1 100%; }

.link-left { display: flex; align-items: center; gap: 10px; min-width: 0; }
.link-left span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Force the monochrome icon SVGs to flat white so colour icons match. */
.link-icon { width: 24px; height: 24px; flex: none; object-fit: contain; filter: brightness(0) invert(1); transition: transform 0.15s ease, scale 0.15s ease; }

/* Custom buttons: the social-link shell with a bold label + arrow, no handle. */
.link--custom .link-left span { font-weight: 600; }
.link--custom .handle { font-size: 1rem; }

/* Blank row between the social links and the custom buttons below them. */
.link-sep { height: 10px; }
[data-btn-labels="0"] .link-sep { flex-basis: 100%; height: 0; }

/* Section heading (Profile > Custom buttons, Pro): a small-caps title over the
   buttons below it. Full row in the icon-only (wrapping) mode too. */
.link-group-title {
  margin: 10px 0 -2px;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: center;
  color: var(--dim);
  overflow-wrap: anywhere;
}
.links > .link-group-title:first-child { margin-top: 0; }
[data-btn-labels="0"] .link-group-title { flex-basis: 100%; }

/* Rich-media custom button: a picture with a caption underneath, no icon. Its
   own shape, but it follows the same link customisation as the .link tiles
   (button style, hover motion, outline, glow, neon - see the
   :is(.link, .link-card) rules above; 'tilt' is wired in layout.js). */
.link-card {
  display: block;
  border-radius: 12px;
  overflow: hidden;
  background: color-mix(in srgb, var(--surface) calc(var(--btn-opacity) * 100%), transparent);
  border: 1px solid var(--border);
  text-decoration: none;
  color: var(--text);
  transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.link-card--nolink { cursor: default; }
.link-card-media { display: block; aspect-ratio: 1 / 1; background: var(--bg); }
.link-card-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.link-card-caption {
  display: block;
  padding: 12px 16px;
  font-weight: 600;
  font-size: 0.95rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Icon-sized picture (<= 128px, flagged by layout.js once it loads): blowing a
   tiny image up to a full card looks broken, so it becomes a normal .link-style
   tile with the picture as its icon instead. */
.link-card.link-card--icon {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 18px;
  overflow: visible;
}
.link-card--icon .link-card-media { width: 24px; height: 24px; aspect-ratio: auto; flex: none; background: none; transition: transform 0.15s ease, scale 0.15s ease; }
.link-card--icon .link-card-media img { object-fit: contain; }
.link-card--icon .link-card-caption { flex: 1; min-width: 0; padding: 0; text-align: left; }
a.link-card--icon::after { content: "\2197"; color: var(--dim); font-size: 1rem; }
[data-btn-style="pill"] .link-card--icon { border-radius: 999px; }

.empty { color: var(--dim); }

/* Optional location line + view-count pill - matched pills under .profile,
   sharing the profile-box style + --box-opacity. */
.location-line {
  display: flex;               /* block-level so it forces .view-count onto its own line */
  align-items: center;
  justify-content: center;
  gap: 7px;                    /* gap / size / spacing match .view-count so the two pills read as a pair */
  width: max-content;          /* pill hugs its text, then margin:auto centres it */
  max-width: 100%;
  margin: 14px auto 0;
  padding: 7px 14px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--surface) calc(var(--box-opacity) * 100%), transparent);
  border: 1px solid;
  border-color: color-mix(in srgb, var(--box-line) calc(var(--box-opacity) * 100%), transparent);
  color: var(--dim);
  font-size: 0.8rem;
  line-height: 1;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.location-line span { overflow-wrap: anywhere; letter-spacing: 0.04em; }
.meta-icon {
  width: 15px;
  height: 15px;
  flex: none;
  display: inline-block;
  background-color: var(--accent);
  -webkit-mask: var(--src) center / contain no-repeat;
  mask: var(--src) center / contain no-repeat;
}

/* Sits just below .profile, sharing its style + --box-opacity. */
.view-count {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 14px auto 0;
  padding: 7px 14px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--surface) calc(var(--box-opacity) * 100%), transparent);
  border: 1px solid;
  border-color: color-mix(in srgb, var(--box-line) calc(var(--box-opacity) * 100%), transparent);
  color: var(--dim);
  font-size: 0.8rem;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.view-count-num {
  font-weight: 700;
  color: var(--text);
  letter-spacing: 0.01em;
}
.view-count-label { letter-spacing: 0.04em; text-transform: lowercase; }

/* Phones / single column: location + view count share one centred row (the
   .card's text-align: center lines them up); a long location still wraps the
   view count onto the next line. The two-column desktop keeps them stacked. */
@media (max-width: 819.98px) {
  .location-line { display: inline-flex; }
  .location-line,
  .view-count { margin: 14px 4px 0; vertical-align: middle; }
}

@media (max-width: 560px) {
  /* Scroll room under the card so its last row clears the fixed music pill
     (centred along the bottom edge on phones). */
  body:has(> .music-player) { padding-bottom: max(100px, calc(env(safe-area-inset-bottom) + 72px)); }
}

/* Background-music widget: a fixed bottom-left pill, starts paused; behaviour in
   layout.js. [data-state] / [data-muted] swap the SVG glyphs. */
.music-player {
  position: fixed;
  left: 14px;
  bottom: 14px;
  bottom: calc(14px + env(safe-area-inset-bottom));   /* clear the iOS home indicator */
  z-index: 45;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-radius: 999px;
  background: rgba(12, 14, 20, 0.74);
  border: 1px solid rgba(255, 255, 255, 0.16);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: #fff;
}
.music-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  color: inherit;
  cursor: pointer;
  transition: background 0.15s ease;
}
.music-btn:hover { background: rgba(255, 255, 255, 0.22); }
.music-player .i-pause,
.music-player .i-muted { display: none; }
.music-player[data-state="playing"] .i-play { display: none; }
.music-player[data-state="playing"] .i-pause { display: inline; }
.music-player[data-muted="1"] .i-vol { display: none; }
.music-player[data-muted="1"] .i-muted { display: inline; }
.music-vol {
  width: 74px;
  accent-color: var(--accent, #8a93a6);
}
@media (max-width: 480px) {
  /* Centre the control along the bottom edge on phones. */
  .music-player { left: 50%; transform: translateX(-50%); }
  .music-vol { width: 52px; }
}

/* Click-to-enter gate, built in layout.js when a music URL is set: a blurred
   overlay whose first click hides it and starts the audio. */
.music-gate {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  background: rgba(10, 12, 18, 0.35);
  -webkit-backdrop-filter: blur(14px) saturate(0.9);
  backdrop-filter: blur(14px) saturate(0.9);
}
.music-gate.is-dismissed { display: none; }
.music-gate-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  padding: 0 24px;
  animation: music-gate-fade 0.6s ease both;
}
.music-gate-label {
  font-family: 'Courier New', monospace;
  font-size: 24px;
  color: #fff;
  text-align: center;
  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.75);
}
.music-gate-actions { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }
.music-gate-btn {
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 600;
  padding: 11px 20px;
  border-radius: 999px;
  cursor: pointer;
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.3);
  background: rgba(255, 255, 255, 0.08);
  transition: background 0.15s ease, border-color 0.15s ease, filter 0.15s ease;
}
.music-gate-btn:hover { background: rgba(255, 255, 255, 0.18); }
.music-gate-play {
  border-color: var(--accent, #8a93a6);
  background: var(--accent, #8a93a6);
  color: var(--accent-ink, #fff);
}
.music-gate-play:hover { background: var(--accent, #8a93a6); filter: brightness(1.1); }
@keyframes music-gate-fade {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .music-gate-box { animation: none; }
}
/* On-page toasts: notice at the top, promo at the bottom. Below the music gate. */
.page-toast {
  position: fixed;
  top: max(16px, env(safe-area-inset-top));
  left: 50%;
  z-index: 55;
  width: min(420px, calc(100vw - 32px));
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 14px 16px 18px;
  overflow: hidden;
  border-radius: 14px;
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border));
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.45);
  color: var(--text);
  transform: translate(-50%, -16px);
  opacity: 0;
  transition: transform 0.3s ease, opacity 0.3s ease;
}
.page-toast[hidden] { display: none; }
.page-toast.is-in { transform: translate(-50%, 0); opacity: 1; }
.page-toast.is-leaving { transform: translate(-50%, -16px); opacity: 0; }
.page-toast-body { flex: 1; min-width: 0; }
.page-toast-title { display: block; font-size: 0.95rem; font-weight: 700; }
.page-toast-text {
  margin: 3px 0 0;
  color: var(--dim);
  font-size: 0.88rem;
  line-height: 1.5;
  white-space: pre-line;          /* keep the owner's line breaks */
  overflow-wrap: anywhere;
}
.page-toast-title + .page-toast-text { margin-top: 4px; }
.page-toast-close {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  margin: -4px -4px 0 0;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--dim);
  cursor: pointer;
}
.page-toast-close:hover { background: color-mix(in srgb, var(--text) 10%, transparent); color: var(--text); }
.page-toast-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: var(--accent);
  transform-origin: left;
}
/* The promo: from the bottom, clear of the fixed music pill when there is one. */
.page-toast--bottom {
  top: auto;
  bottom: max(16px, calc(env(safe-area-inset-bottom) + 16px));
  transform: translate(-50%, 16px);
}
/* The ~165px music pill sits bottom-left (14px in); the 420px centred box only
   reaches it below ~800px, so only lift the box above it there. */
@media (max-width: 800px) {
  body:has(> .music-player) .page-toast--bottom { bottom: calc(env(safe-area-inset-bottom) + 76px); }
}
.page-toast--bottom.is-in { transform: translate(-50%, 0); }
.page-toast--bottom.is-leaving { transform: translate(-50%, 16px); }
.page-toast-brand { flex: none; width: 30px; height: 30px; margin-top: 2px; }
.page-toast-link { color: var(--text); font-weight: 700; text-decoration: none; }
.page-toast-link:hover { color: var(--accent2); }
@media (prefers-reduced-motion: reduce) {
  .page-toast,
  .page-toast.is-in,
  .page-toast.is-leaving { transition: opacity 0.2s ease; transform: translate(-50%, 0); }
}

/* "You are leaving this page" confirm (layout.js) - the music gate's overlay,
   box and buttons, plus the destination line. Above the music gate if both show. */
.leave-gate { z-index: 70; }
.leave-gate-host {
  margin: -8px 0 0;
  max-width: min(90vw, 420px);
  color: rgba(255, 255, 255, 0.8);
  font-size: 0.95rem;
  text-align: center;
  overflow-wrap: anywhere;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.7);
}

/* Custom cursor effect (data-cursor-fx on <html>): a pointer-trailing particle
   stream built in layout.js, one variant per value, hidden for reduced motion.
   --dx / --dy / --r are per-dot randoms. */
.cursor-fx-layer {
  position: fixed;
  inset: 0;
  z-index: 50;
  pointer-events: none;
  overflow: hidden;
}
.cursor-fx-dot {
  position: fixed;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  border-radius: 50%;
  pointer-events: none;
  will-change: transform, opacity;
}

/* Trail - a plain fading dot in the accent. */
.cursor-fx-dot--trail {
  background: var(--accent, #8a93a6);
  box-shadow: 0 0 8px var(--accent, #8a93a6);
  animation: cfx-trail 0.6s ease-out forwards;
}
@keyframes cfx-trail {
  from { opacity: 0.85; transform: scale(1); }
  to   { opacity: 0; transform: scale(0.3); }
}

/* Glitter - small bright sparks that fall and shrink. */
.cursor-fx-dot--glitter {
  width: 6px; height: 6px; margin: -3px 0 0 -3px;
  background: #fff;
  box-shadow: 0 0 6px 1px var(--accent, #8a93a6);
  animation: cfx-glitter 0.9s ease-in forwards;
}
@keyframes cfx-glitter {
  from { opacity: 1; transform: translate(0, 0) scale(1); }
  to   { opacity: 0; transform: translate(calc(var(--dx, 0) * 16px), calc(20px + var(--dy, 0) * 8px)) scale(0.2); }
}

/* Sparkle - a four-point star (the span + its ::before cross) that spins out. */
.cursor-fx-dot--sparkle {
  width: 3px; height: 15px; margin: -7.5px 0 0 -1.5px;
  border-radius: 2px;
  background: var(--accent, #8a93a6);
  box-shadow: 0 0 6px var(--accent, #8a93a6);
  animation: cfx-sparkle 0.7s ease-out forwards;
}
.cursor-fx-dot--sparkle::before {
  content: "";
  position: absolute;
  inset: 0;
  background: inherit;
  border-radius: 2px;
  transform: rotate(90deg);
}
@keyframes cfx-sparkle {
  from { opacity: 1; transform: rotate(var(--r, 0deg)) scale(1); }
  to   { opacity: 0; transform: rotate(calc(var(--r, 0deg) + 140deg)) scale(0.1); }
}

/* Neon glow - a bright orb with a heavy multi-layer halo. */
.cursor-fx-dot--neon {
  width: 12px; height: 12px; margin: -6px 0 0 -6px;
  background: color-mix(in srgb, var(--accent, #8a93a6) 70%, #fff);
  box-shadow:
    0 0 10px var(--accent, #8a93a6),
    0 0 22px var(--accent, #8a93a6),
    0 0 34px var(--accent, #8a93a6);
  animation: cfx-neon 0.55s linear forwards;
}
@keyframes cfx-neon {
  from { opacity: 0.95; transform: scale(1.1); }
  to   { opacity: 0; transform: scale(0.35); }
}

/* Glitch - an RGB-split shard that jitters sideways in steps. */
.cursor-fx-dot--glitch {
  width: 14px; height: 4px; margin: -2px 0 0 -7px;
  border-radius: 0;
  background: var(--accent, #8a93a6);
  box-shadow: 3px 0 0 rgba(255, 0, 90, 0.75), -3px 0 0 rgba(0, 255, 240, 0.75);
  animation: cfx-glitch 0.4s steps(4, end) forwards;
}
@keyframes cfx-glitch {
  from { opacity: 0.9; transform: translateX(calc(var(--dx, 0) * 4px)) scaleX(1); }
  to   { opacity: 0; transform: translateX(calc(var(--dx, 0) * 22px)) scaleX(0.2); }
}

/* Bubbles - a hollow ring that floats up and swells. */
.cursor-fx-dot--bubbles {
  width: 12px; height: 12px; margin: -6px 0 0 -6px;
  background: transparent;
  border: 1.5px solid color-mix(in srgb, var(--accent, #8a93a6) 80%, #fff);
  animation: cfx-bubble 1s ease-out forwards;
}
@keyframes cfx-bubble {
  from { opacity: 0.9; transform: translateY(0) scale(0.6); }
  to   { opacity: 0; transform: translateY(-28px) scale(1.3); }
}

/* Ripple - an expanding accent ring. */
.cursor-fx-dot--ripple {
  width: 10px; height: 10px; margin: -5px 0 0 -5px;
  background: transparent;
  border: 2px solid var(--accent, #8a93a6);
  animation: cfx-ripple 0.7s ease-out forwards;
}
@keyframes cfx-ripple {
  from { opacity: 0.7; transform: scale(0.3); }
  to   { opacity: 0; transform: scale(4); }
}

/* Ember - a warm spark that drifts up and flickers out (fixed palette). */
.cursor-fx-dot--ember {
  width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px;
  background: radial-gradient(circle at 50% 40%, #fff3b0, #ff9d3c 45%, #ff4d2e 82%);
  box-shadow: 0 0 8px 2px rgba(255, 120, 40, 0.8);
  animation: cfx-ember 0.9s ease-out forwards;
}
@keyframes cfx-ember {
  from { opacity: 1; transform: translate(0, 0) scale(1); }
  to   { opacity: 0; transform: translate(calc(var(--dx, 0) * 12px), -24px) scale(0.2); }
}

@media (prefers-reduced-motion: reduce) {
  .cursor-fx-layer { display: none; }
}

/* Bigger tap targets on touch; glyphs keep their size. */
@media (pointer: coarse) {
  .music-btn { width: 40px; height: 40px; }
  .music-vol { width: 72px; height: 32px; }
  .page-toast-close { width: 40px; height: 40px; margin: -10px -10px 0 0; }
  .badge-container { padding: 5px; margin: -5px; }
}
