* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
/* Body copy uses a system stack; monospace is kept only where it's deliberate. */
button, input, select, textarea { font-family: inherit; }

/* Accent default. --accent / --accent2 are overridden from <html> when the page
   sets a Bio box colour; with none set they resolve here to ACCENT_DEFAULT (a
   neutral slate, shared with Layout 1) rather than a colour that looks chosen.
   The var(--accent, #8a93a6) fallbacks dotted through the file are only a
   belt-and-braces match for this and never take effect while :root is applied. */
:root {
    --accent: #8a93a6;
    --accent2: #a4abba;
}

/* Visible keyboard focus for interactive elements; :where() keeps specificity
   at 0; --accent resolves to the slate default above. */
:where(a, button, summary, [tabindex]):focus-visible {
    outline: 2px solid var(--accent, #8a93a6);
    outline-offset: 2px;
}

.cf-turnstile:not(.turnstile-row) {
    position: fixed;
    left: 0;
    bottom: 0;
    width: 0 !important;
    height: 0 !important;
    overflow: hidden;
}

body {
    background-color: #000;
    color: #fff;
    overflow-x: hidden;
    overflow-y: auto;   /* the page scrolls (like Layout 1) - #profile-block no longer does */
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

.container {
    width: 100%;
    min-height: 100vh;
    min-height: 100dvh;
    min-height: 100svh;   /* smallest-viewport height: fills the screen without reflowing as the URL bar collapses */
    position: relative;
    /* Mirrors Layout 1's <body>: a plain top-down flex column (NOT
       justify-content: center) with a gap and safe-area padding. The card
       group is centred by auto block-margins on it (see #profile-block
       below), exactly like Layout 1's .card { margin-block: auto } - so it
       floats mid-screen and collapses to a normal scrolling stack once the
       content outgrows the screen. */
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 22px;
    /* 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));
}

/* .bio-card: the shared look for every card in the stack - #profile-block
   (identity: avatar, name, bio, social handles, view count/location) and
   #custom-block (custom buttons, when there are any). Both sit in normal
   flow (position: relative, no scroll of their own) - like Layout 1's
   .card, the page scrolls instead of the box (see body / .container
   above). The group stays centred on short pages via the auto block-margins
   on #profile-block / #custom-block below; on tall ones those collapse and
   .container's gap is what separates each card from whatever comes next
   (another card, or the end of the page). */
.bio-card {
    position: relative;
    width: 820px;
    max-width: 94vw;
    /* Outer spacing / safe-area is handled by .container (gap + padding),
       like Layout 1's .card; block-margins here stay free for the auto
       centring on #profile-block / #custom-block below. */
    margin: 0 auto;
    overflow: visible;
    /* --box-opacity fades fill / border / blur; data-box-style reshapes the card. */
    background: color-mix(in srgb, rgba(0, 0, 0, 0.7) calc(var(--box-opacity, 1) * 100%), transparent);
    border: 1px solid rgba(255, 255, 255, 0.2);
    padding: 20px;
    border-radius: 16px;   /* matches Layout 1's rounded profile box */
    z-index: 20;
    display: flex;
    flex-direction: column;
    gap: 15px;
    -webkit-backdrop-filter: blur(calc(var(--box-opacity, 1) * 10px));
    backdrop-filter: blur(calc(var(--box-opacity, 1) * 10px));
    transform-style: preserve-3d;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;   /* no transform: GSAP drives it (layout.js) */
    pointer-events: auto;
}
/* Entrance: each card drops in, staggered by --i (render.php). */
@media (prefers-reduced-motion: no-preference) {
    .bio-card { animation: l2-card-in 1s cubic-bezier(0.25, 0.46, 0.45, 0.94) calc(var(--i, 0) * 0.15s) backwards; }
}
@keyframes l2-card-in {
    from { opacity: 0; transform: translateY(-50px); }
}
/* Only the identity card gets a floor - it needs the room for the avatar +
   name to look right even with a short bio and few handles. #custom-block
   (and any other .bio-card) just sizes to its own content - a forced
   min-height there left empty space below a short button list. */
#profile-block {
    min-height: 332px;
}

/* Centre the card group vertically, exactly like Layout 1's
   .card { margin-block: auto }. The auto margins absorb the free space above
   the first card and below the last, then collapse to 0 once the page is
   tall enough to scroll. #profile-block is always the first card; the bottom
   auto lands on whichever card comes last. */
#profile-block { margin-top: auto; }
#profile-block:last-child,
.custom-block:last-child { margin-bottom: auto; }

.profile-header {
    display: flex;
    align-items: flex-start;
    gap: 15px;
}

.profile-container {
    position: relative;
    width: 150px;
    height: 150px;
    -webkit-tap-highlight-color: transparent;   /* no grey/blue flash when tapped (spin + page-number tooltip) */
}

/* Profile-photo animation (data-avatar-anim on <html>): orbit / spin add a ring;
   pulse / float / glow act on the photo. */
@media (prefers-reduced-motion: no-preference) {
    /* Orbit: a soft accent ball circling the photo rim. */
    [data-avatar-anim="orbit"] .profile-container::after {
        content: '';
        position: absolute;
        top: -2px;
        left: -2px;
        right: -2px;
        bottom: -2px;
        border-radius: 50%;
        background: radial-gradient(circle at 10px 10px,
                var(--avatar-col, var(--accent, #8a93a6)) 10%,
                transparent 15%);
        animation: l2-ring-turn 3s linear infinite;
        z-index: -1;
        filter: blur(1px);
        box-shadow: none;
    }
    [data-avatar-anim="orbit"] .profile-container.fast-orbit::after {
        animation: l2-ring-whirl 0.5s linear forwards;
    }

    [data-avatar-anim="spin"] .profile-container::before {
        content: '';
        position: absolute;
        inset: -5px;
        border-radius: 50%;
        border: 3px solid transparent;
        border-top-color: var(--avatar-col, var(--accent, #8a93a6));
        border-right-color: var(--avatar-col, var(--accent, #8a93a6));
        animation: l2-ring-turn 1.5s linear infinite;
        z-index: -1;
    }
    /* Click / tap the photo: one fast whip-around, same as the orbit ring. */
    [data-avatar-anim="spin"] .profile-container.fast-orbit::before {
        animation: l2-ring-whirl 0.5s linear forwards;
    }
    [data-avatar-anim="pulse"] .profile-picture { animation: av-pulse 2.4s ease-in-out infinite; }
    [data-avatar-anim="float"] .profile-picture { animation: av-float 3s ease-in-out infinite; }
    [data-avatar-anim="glow"]  .profile-picture { animation: av-glow 2.6s ease-in-out infinite; }
}

@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-glow {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--avatar-col, var(--accent, #8a93a6)) 45%, transparent); }
    50%      { box-shadow: 0 0 22px 5px color-mix(in srgb, var(--avatar-col, var(--accent, #8a93a6)) 45%, transparent); }
}

.profile-picture {
    width: 150px;
    height: 150px;
    border-radius: 50%;
    object-fit: cover;
    cursor: pointer;
    pointer-events: auto;
    opacity: 1 !important;
}

.profile-info {
    flex: 1;
    display: flex;
    flex-direction: column;
}

.name-and-badges {
    display: flex;
    flex-direction: column;   /* badges sit below the name, not squeezed beside it */
    align-items: flex-start;
    gap: 10px;
    margin-bottom: 10px;
}

#profile-name {
    font-family: 'Courier New', ui-monospace, SFMono-Regular, monospace;
    font-size: 32px;
    font-weight: bold;
    letter-spacing: 3px;
    color: #fff;
    overflow-wrap: anywhere;
    filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.7));
    /* picks up the page accent when one is set; identical white glow otherwise */
    filter: drop-shadow(0 0 8px color-mix(in srgb, var(--accent, #fff), transparent 30%));
    opacity: 1 !important;
}

/* Display-name text effect (data-name-effect on <html>). Typewriter and neon
   are separate toggles. Static bases sit outside the media query so
   reduced-motion visitors still get a styled name. */
[data-name-effect="gradient"] #profile-name,
[data-name-effect="shine"] #profile-name {
    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"] #profile-name {
    background-image: linear-gradient(92deg,
        var(--accent, #8a93a6),
        color-mix(in srgb, var(--accent2, #a4abba), #fff 42%));
}
[data-name-effect="shine"] #profile-name {
    /* 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"] #profile-name {
    text-shadow: 1.5px 0 #ff2e97, -1.5px 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"] #profile-name {
    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"] #profile-name  { animation: name-shine 3.6s linear infinite; }
    [data-name-effect="glitch"] #profile-name { animation: name-glitch 1.9s steps(2, end) infinite; }
    [data-name-neon="1"]:not([data-name-effect="glitch"]) #profile-name { 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"] #profile-name {
        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: 1.5px 0 #ff2e97, -1.5px 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 #profile-bio, reusing the name-* keyframes. */
[data-bio-effect="gradient"] #profile-bio,
[data-bio-effect="shine"] #profile-bio {
    background: linear-gradient(92deg, var(--accent, #8a93a6), var(--accent2, #a4abba));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}
[data-bio-effect="gradient"] #profile-bio {
    background-image: linear-gradient(92deg,
        var(--accent, #8a93a6),
        color-mix(in srgb, var(--accent2, #a4abba), #fff 42%));
}
[data-bio-effect="shine"] #profile-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"] #profile-bio {
    text-shadow: 1.5px 0 #ff2e97, -1.5px 0 var(--accent, #8a93a6);
}
[data-bio-neon="1"] #profile-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"] #profile-bio  { animation: name-shine 3.6s linear infinite; }
    [data-bio-effect="glitch"] #profile-bio { animation: name-glitch 1.9s steps(2, end) infinite; }
    [data-bio-neon="1"]:not([data-bio-effect="glitch"]) #profile-bio { animation: bio-neon 2.2s ease-in-out infinite; }
    [data-bio-effect="shine"][data-bio-neon="1"] #profile-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"] #profile-name,
    [data-name-effect="shine"] #profile-name,
    [data-bio-effect="gradient"] #profile-bio,
    [data-bio-effect="shine"] #profile-bio {
        background: none;
        color: #fff;
        -webkit-text-fill-color: #fff;
    }
}
@media (forced-colors: active) {
    [data-name-effect] #profile-name,
    [data-bio-effect] #profile-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] #profile-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] #profile-bio,
[data-bio-effect="shine"][data-bio-col] #profile-bio {
    color: transparent;
    -webkit-text-fill-color: transparent;
}
/* Emoji (e_emoji()) keep their own colours under the text effects. */
#profile-bio .emo { -webkit-text-fill-color: var(--bio-col, rgba(255, 255, 255, 0.8)); 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] #profile-name {
    --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] #profile-name,
[data-name-effect="shine"][data-name-col] #profile-name {
    color: transparent;
    -webkit-text-fill-color: transparent;
}
/* Emoji (e_emoji()) keep their own colours under the name effects. */
#profile-name .emo { -webkit-text-fill-color: var(--name-col, #fff); text-shadow: none; }

/* Link / social icons: swap the flat-white <img> for a masked <span> painted
   in the chosen colour. */
[data-icon-col] .social-link .social-icon,
[data-icon-col] .social-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). */
.social-link .social-icon.is-brand,
[data-icon-col] .social-icon.is-brand,
.social-link .social-icon.is-custom,
[data-icon-col] .social-icon.is-custom {
    filter: none;
    background: none;
    -webkit-mask: none;
    mask: none;
}
/* A handle's own icon image (Pro): fitted, slightly rounded. */
.social-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. */
[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); }

.badge-group {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.badge-container {
    position: relative;
    pointer-events: auto;
    opacity: 1 !important;
}

.badge-container .tooltip {
    visibility: hidden;
    white-space: nowrap;
    background-color: rgba(0, 0, 0, 0.8);
    color: #fff;
    text-align: center;
    border-radius: 6px;
    padding: 5px 10px;
    position: absolute;
    z-index: 100;
    bottom: 125%;
    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;
    opacity: 0;
    transition: opacity 0.3s;
}

.badge-container:hover .tooltip,
.badge-container.tip-open .tooltip {   /* tap to reveal on touch */
    visibility: visible;
    opacity: 1 !important;
}

.badge-box {
    background: color-mix(in srgb, rgba(255, 255, 255, 0.15) calc(var(--box-opacity, 1) * 100%), transparent);
    padding: 2px;
    border-radius: 15px;
    display: inline-block;
}

.badge-box-inner {
    background: color-mix(in srgb, rgba(0, 0, 0, 0.5) calc(var(--box-opacity, 1) * 100%), transparent);
    padding: 5px;
    border-radius: 12px;
    display: flex;
    gap: 8px;
    box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.2);
}

.badge {
    width: 22px;
    height: 22px;
    transition: transform 0.3s;
    pointer-events: auto;
    opacity: 1 !important;
    /* force the monochrome badge SVGs flat white */
    filter: brightness(0) invert(1);
}
/* "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 hover animation (data-badge-anim on <html>): plays on each glyph on
   hover, default 'pop', motion-gated. */
@media (prefers-reduced-motion: no-preference) {
    .badge:hover                            { transform: scale(1.2); }
    [data-badge-anim="tilt"]   .badge:hover { transform: rotate(-12deg) scale(1.1); }
    [data-badge-anim="bounce"] .badge:hover { transform: none; animation: bd-bounce 0.5s ease; }
    [data-badge-anim="spin"]   .badge:hover { transform: none; animation: bd-spin 0.6s linear; }
    [data-badge-anim="wobble"] .badge:hover { transform: none; animation: bd-wobble 0.6s ease; }
}
[data-badge-anim="none"] .badge:hover { 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); }
}

#profile-bio {
    color: rgba(255, 255, 255, 0.8);
    font-size: 18px;
    line-height: 1.5;
    margin-bottom: 10px;
    opacity: 1 !important;
    white-space: pre-line;
    overflow-wrap: anywhere;
}

.bio-separator {
    width: 100%;
    height: 1px;
    background: rgba(255, 255, 255, 0.2);
    margin: 20px 0;
    opacity: 1 !important;
}

.profile-footer {
    display: flex;
    flex-direction: column;
    gap: 5px;
    width: 100%;
}
/* Pushes the handles down to sit flush with the bottom of #profile-block
   when the bio is short (an empty flex item above absorbs the slack up to
   #profile-block's own min-height). #custom-block has no content before its
   .profile-footer, so this same rule there would push its only child all
   the way to the bottom instead, leaving a big empty gap above it - scope
   it to #profile-block alone. */
#profile-block .profile-footer {
    margin-top: auto;
}
/* No social handles (no footer): pin the meta row to the card floor. */
.bio-separator + .profile-meta {
    margin-top: auto;
}

.social-links {
    display: flex;
    justify-content: center;
    gap: 12px;
    margin-top: 0;
    pointer-events: auto;
    opacity: 1 !important;
}

/* Size / colour come from the page's own inline rule. */
.social-icon {
    transition: transform 0.15s ease;
    pointer-events: auto;
    opacity: 1 !important;
}


.bold-card .bio-card {
    --box-line: rgba(0, 195, 255, 0.5);
    --box-line: color-mix(in srgb, var(--accent, #8a93a6) 50%, transparent);
    border-color: color-mix(in srgb, var(--box-line) calc(var(--box-opacity, 1) * 100%), transparent);
}

/* Profile-box style options (data-box-style on <html>); 'rounded' is the default. */
[data-box-style="sharp"] .bio-card        { border-radius: 4px; }
[data-box-style="sharp"] .badge-box       { border-radius: 6px; }
[data-box-style="sharp"] .badge-box-inner { border-radius: 4px; }

[data-box-style="outline"] .bio-card {
    --box-line: color-mix(in srgb, var(--accent, #8a93a6) 78%, transparent);
    background: transparent;
}
[data-box-style="outline"] .badge-box,
[data-box-style="outline"] .badge-box-inner { background: transparent; }

[data-box-style="shadow"] .bio-card {
    border-color: transparent;
    box-shadow: 0 22px 55px rgba(0, 0, 0, 0.55);
}

/* Profile-box hover add-ons: 'outline' / 'glow' are CSS; the base motion
   (lift / zoom / tilt) is driven from layout.js. Mouse only. */
@media (hover: hover) {
    [data-box-outline] .bio-card:hover { border-color: var(--accent, #8a93a6); }
    [data-box-glow] .bio-card:hover {
        box-shadow: 0 0 22px color-mix(in srgb, var(--accent, #8a93a6) 45%, transparent),
                    0 0 60px color-mix(in srgb, var(--accent, #8a93a6) 22%, transparent);
    }
}

/* Neon glow (data-box-neon on <html>): an always-on accent halo around the card
   (motion-gated pulse). */
[data-box-neon="1"] .bio-card {
    box-shadow: 0 0 20px color-mix(in srgb, var(--accent, #8a93a6) 42%, transparent),
                0 0 55px color-mix(in srgb, var(--accent, #8a93a6) 20%, transparent);
}
@media (prefers-reduced-motion: no-preference) {
    [data-box-neon="1"] .bio-card {
        animation: l2-card-in 1s cubic-bezier(0.25, 0.46, 0.45, 0.94) calc(var(--i, 0) * 0.15s) backwards,
                   fx-neon-box 2.6s ease-in-out infinite;
    }
}
@keyframes fx-neon-box {
    0%, 100% { box-shadow: 0 0 16px color-mix(in srgb, var(--accent, #8a93a6) 38%, transparent),
                           0 0 46px color-mix(in srgb, var(--accent, #8a93a6) 18%, transparent); }
    50%      { box-shadow: 0 0 30px color-mix(in srgb, var(--accent, #8a93a6) 55%, transparent),
                           0 0 72px color-mix(in srgb, var(--accent, #8a93a6) 28%, transparent); }
}

/* Brief shake the typewriter adds now and then while the Glitch name effect is on (layout.js). */
.is-jolting {
    animation: l2-jolt 0.2s ease-in-out;
}

@keyframes l2-jolt {
    0% {
        transform: translate(0);
    }

    20% {
        transform: translate(-2px, 2px);
    }

    40% {
        transform: translate(-2px, -2px);
    }

    60% {
        transform: translate(2px, 2px);
    }

    80% {
        transform: translate(2px, -2px);
    }

    100% {
        transform: translate(0);
    }
}

@keyframes l2-ring-turn {
    0% {
        transform: rotate(0deg);
    }

    100% {
        transform: rotate(360deg);
    }
}

@keyframes l2-ring-whirl {
    0% {
        transform: rotate(0deg);
    }

    100% {
        transform: rotate(360deg);
    }
}

/* Tablets (<=900px): stacked, centred 600px card. */
@media (max-width: 900px) {

    .bio-card {
        width: min(90vw, 600px);
        max-width: 600px;
        padding: 15px;
    }

    .profile-header {
        flex-direction: column;
        align-items: center;
        gap: 10px;
    }

    .profile-container {
        width: 120px;
        height: 120px;
    }

    .profile-picture {
        width: 120px;
        height: 120px;
    }

    .profile-info {
        align-items: center;
        text-align: center;
    }

    .name-and-badges {
        flex-direction: column;
        align-items: center;
        gap: 8px;
    }

    #profile-name {
        font-size: 24px;
        letter-spacing: 2px;
    }

    .badge-group {
        justify-content: center;
        gap: 6px;
    }

    .badge {
        width: 18px;
        height: 18px;
    }

    .badge-container .tooltip {
        font-size: 12px;
        bottom: 110%;
    }

    #profile-bio {
        font-size: 16px;
        line-height: 1.45;
    }

    .bio-separator {
        margin: 15px 0;
    }

    .social-links {
        gap: 10px;
        justify-content: center;
    }

    .social-icon {
        width: 32px;
        height: 32px;
    }

}

/* Phones (<=640px). */
@media (max-width: 640px) {
    .bio-card {
        width: min(90vw, 400px);
        max-width: 400px;
    }
    #profile-bio { font-size: 15px; }
}

/* 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, #04121a);
}
.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: rgba(10, 12, 18, 0.72);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
    border: 1px solid color-mix(in srgb, var(--accent, #8a93a6) 50%, rgba(255, 255, 255, 0.2));
    box-shadow: 0 14px 40px rgba(0, 0, 0, 0.5);
    color: #fff;
    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: 15px; font-weight: 700; }
.page-toast-text {
    margin: 3px 0 0;
    color: rgba(255, 255, 255, 0.75);
    font-size: 14px;
    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: rgba(255, 255, 255, 0.7);
    cursor: pointer;
}
.page-toast-close:hover { background: rgba(255, 255, 255, 0.12); color: #fff; }
.page-toast-bar {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 3px;
    background: var(--accent, #8a93a6);
    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: #fff; font-weight: 700; text-decoration: none; }
.page-toast-link:hover { color: var(--accent, #8a93a6); }
@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-group { gap: 8px; }
    .badge-container { padding: 4px; margin: -4px; }
}

/* ---- Page parts: background, social chips, picture buttons, meta row.
   Keep last in the file. ---- */

/* Optional page background: fixed layers 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 (it means the same on most phones). */
.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;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
.page-bg-scrim {
  z-index: -1;
  background: rgba(0, 0, 0, 0.55);
}

.profile-picture.avatar-fallback {
  display: grid; place-items: center;
  font-family: 'Courier New', monospace; font-size: 3.4rem; font-weight: bold;
  color: #fff; background: linear-gradient(135deg, var(--accent, #8a93a6), var(--accent2, #a4abba));
}

/* Social row: icon + label chips. */
.social-links { flex-wrap: wrap; gap: 10px; }
.social-link {
  position: relative;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 14px; border-radius: 12px; text-decoration: none;
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.25);
  transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}
.social-link .social-icon { width: 24px; height: 24px; filter: brightness(0) invert(1); }
.social-label { font-size: 13px; }

/* Hover tooltip on the link chips; the profile photo's "Page #N" tooltip shares the look. */
.social-link .tooltip,
.profile-container .tooltip {
  visibility: hidden;
  position: absolute;
  left: 50%;
  bottom: calc(100% + 6px);
  transform: translateX(calc(-50% + var(--tip-shift, 0px)));   /* --tip-shift: tip-edge.js */
  max-width: 240px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 5px 10px;
  border-radius: 6px;
  background-color: rgba(0, 0, 0, 0.8);
  color: #fff;
  font-size: 12px;
  z-index: 100;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s;
}
.social-link--copy { cursor: pointer; }   /* no-URL social: click copies the handle (layout.js) */
.social-link:hover .tooltip,
.social-link.tip-open .tooltip,
.social-link.is-copied .tooltip,
.profile-container:hover .tooltip,
.profile-container.tip-open .tooltip { visibility: visible; opacity: 1; }   /* tap to reveal on touch */

/* Touch has no hover: show the handle inline as dim text instead of a popup. */
@media (pointer: coarse) {
  .social-link:not(.social-link--custom) .tooltip {
    visibility: visible;
    opacity: 1;
    position: static;
    transform: none;
    max-width: 42vw;
    padding: 0;
    background: none;
    color: rgba(255, 255, 255, 0.55);
    font-size: 12px;
  }
  [data-btn-labels="0"] .social-link:not(.social-link--custom) .tooltip { display: none; }
  /* An invite code etc. isn't worth showing inline (PLATFORM_HANDLE_HIDDEN). */
  .social-link--nohandle .tooltip { display: none; }
}

/* Link-button style + label switches, set on <html>. */
/* Picture buttons (.link-card) share every link customisation below; 'pill'
   on a square card means extra-round corners rather than a capsule. */
[data-btn-style="pill"]    .social-link { border-radius: 999px; }
[data-btn-style="pill"]    .link-card { border-radius: 24px; }
[data-btn-style="sharp"]   :is(.social-link, .link-card) { border-radius: 4px; }
[data-btn-style="outline"] :is(.social-link, .link-card) {
  background: transparent;
  border-color: color-mix(in srgb, var(--accent, #8a93a6) 45%, #2a3040);
}
[data-btn-style="shadow"]  :is(.social-link, .link-card) { border-color: transparent; box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45); }
/* Icon only: social chips lose their label; custom buttons keep their text. */
[data-btn-labels="0"] .social-link:not(.social-link--custom) { padding: 8px; gap: 0; }
[data-btn-labels="0"] .social-link:not(.social-link--custom) .social-label { display: none; }

/* Link-tile hover add-ons (data-link-outline / data-link-glow). Mouse only. */
@media (hover: hover) {
  [data-link-outline] :is(.social-link, .link-card):hover { border-color: var(--icon-col, var(--accent, #8a93a6)); }
  [data-link-glow] :is(.social-link, .link-card):hover {
    box-shadow: 0 0 16px color-mix(in srgb, var(--icon-col, var(--accent, #8a93a6)) 42%, transparent),
                0 0 40px color-mix(in srgb, var(--icon-col, var(--accent, #8a93a6)) 20%, transparent);
  }
}

/* Neon glow (data-link-neon on <html>): an always-on halo around every link tile. */
[data-link-neon="1"] :is(.social-link, .link-card) {
  box-shadow: 0 0 13px color-mix(in srgb, var(--icon-col, var(--accent, #8a93a6)) 42%, transparent),
              0 0 32px color-mix(in srgb, var(--icon-col, var(--accent, #8a93a6)) 20%, transparent);
}
@media (prefers-reduced-motion: no-preference) {
  [data-link-neon="1"] :is(.social-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, #8a93a6)) 38%, transparent),
                         0 0 26px color-mix(in srgb, var(--icon-col, var(--accent, #8a93a6)) 18%, transparent); }
  50%      { box-shadow: 0 0 19px color-mix(in srgb, var(--icon-col, var(--accent, #8a93a6)) 55%, transparent),
                         0 0 44px color-mix(in srgb, var(--icon-col, var(--accent, #8a93a6)) 28%, transparent); }
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  [data-link-hover="lift"] .social-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(.social-link, .link-card):hover { transform: scale(1.03); }
  [data-link-hover="pop"]  :is(.social-link, .link-card):hover { transform: scale(1.06); }
  [data-link-hover="pop"]  :is(.social-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(.social-link, .link-card):hover { transform: scale(1.06); }
  [data-link-hover="spin"] :is(.social-icon, .link-card--icon .link-card-media):hover { animation: bd-spin 0.6s linear; }
  [data-link-hover="wobble"] :is(.social-link, .link-card):hover { transform: scale(1.06); }
  [data-link-hover="wobble"] :is(.social-icon, .link-card--icon .link-card-media):hover { animation: bd-wobble 0.6s ease; }
  [data-link-hover="spin"] :is(.social-icon, .link-card--icon .link-card-media):hover,
  [data-link-hover="wobble"] :is(.social-icon, .link-card--icon .link-card-media):hover { scale: 1.4; }
}
/* 'tilt': depth comes from a perspective property on the container
   (preserve-3d on the parent flattens a child perspective()). */
[data-link-hover="tilt"] .social-links { perspective: 900px; }
/* Picture cards sit one or two levels down, so their own parent carries it. */
[data-link-hover="tilt"] :is(.link-card-row, .link-card-pair) { perspective: 900px; }

.social-fallback {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 8px 14px; border-radius: 12px; font-size: 13px; color: #fff;
  background: rgba(255, 255, 255, 0.12); border: 1px solid rgba(255, 255, 255, 0.25);
}

/* Custom buttons: a social chip with no icon, bold label + arrow. */
.social-link--custom .social-label { font-weight: 600; }
.social-link--custom::after { content: "\2197"; font-size: 13px; opacity: 0.75; }

/* Break the custom buttons onto their own line. */
.social-links-break { flex-basis: 100%; height: 6px; }

/* Section heading (Profile > Custom buttons, Pro): a full-row small-caps
   title over the buttons below it. The first one sits flush at the top. */
.link-group-title {
  flex: 1 1 100%;
  margin: 14px 0 2px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: center;
  color: #fff;
  opacity: 0.72;
  overflow-wrap: anywhere;
}
.link-group-title:first-child { margin-top: 0; }

/* Rich-media custom button: a picture with a caption underneath, no icon.
   Its own shape (not a chip), so it always takes its own full-width row
   inside the wrapping .social-links flex row. It follows the same link
   customisation as the chips (button style, hover motion, outline, glow,
   neon - see the :is(.social-link, .link-card) rules above). */
/* The flex-wrap algorithm judges line-breaks by an item's *clamped*
   hypothetical size, so a max-width on .link-card itself would shrink what
   the wrap check sees too - letting the next chip join its line and then
   get stretched to its height by .social-links' default align-items:
   stretch (this is what went wrong the first time). Splitting the two
   concerns fixes it: .link-card-row always claims the full row (forcing
   the break, no max-width to confuse the wrap check); .link-card, centred
   inside it, is the one that's actually capped. */
.link-card-row {
  display: flex;
  justify-content: center;
  flex: 1 1 100%;
}
/* Two back-to-back picture buttons (custom_links_grouped()): two equal
   columns. Same split as above - the cap goes on this inner box, never on
   .link-card-row, or the chips after it would join its line and stretch. */
.link-card-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  width: 100%;
  max-width: 692px;   /* 2 x .link-card's 340px + the gap, so each card stays full size on desktop */
}
.link-card-pair .link-card-caption { padding: 10px 12px; }
.link-card {
  display: block;
  width: 100%;
  /* capped: the 820px desktop card would make a ~780px square image */
  max-width: 340px;
  border-radius: 12px;
  overflow: hidden;
  text-decoration: none;
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.25);
  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; }
.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: 14px;
  text-align: center;
  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 custom
   chip with the picture as its icon, inline with the other chips. */
.link-card-row:has(> .link-card--icon) { flex: 0 0 auto; }
.link-card.link-card--icon {
  display: inline-flex; align-items: center; gap: 8px;
  width: auto; max-width: none; padding: 8px 14px; overflow: visible;
}
.link-card--icon .link-card-media { width: 24px; height: 24px; aspect-ratio: auto; flex: 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 { padding: 0; font-size: 13px; text-align: left; }
a.link-card--icon::after { content: "\2197"; font-size: 13px; opacity: 0.75; }
[data-btn-style="pill"] .link-card--icon { border-radius: 999px; }

/* View count + optional location, at the bottom of the card. #profile-block
   is normal in-flow content now (the page scrolls, not the card - see the
   comment on it above), so this is just a plain row at the end of the
   card's own content, no different from .profile-footer above it. */
.profile-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 14px;
  padding-top: 10px;
}
.view-count,
.location-line {
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  flex: 0 1 auto;
  font-size: 12px; letter-spacing: 0.04em;
  color: rgba(255, 255, 255, 0.55);
}
.view-count     { text-transform: uppercase; }
.location-line  { margin-left: auto; }   /* stays bottom-right even when the view count is hidden */
.view-count > span,
.location-line > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.meta-icon {
  width: 14px; height: 14px; flex: none; display: inline-block;
  background-color: var(--accent, #8a93a6);
  -webkit-mask: var(--src) center / contain no-repeat;
  mask: var(--src) center / contain no-repeat;
}

/* Scroll room under the last card so it clears the fixed music pill. */
.container:has(~ .music-player) { padding-bottom: max(104px, calc(env(safe-area-inset-bottom) + 76px)); }