/* ============================================================
   yourcustomprofile.app — mobile cost budget
   ------------------------------------------------------------
   A phone has one GPU slice and no compositor headroom. The
   profile ships ~19 `backdrop-filter` surfaces stacked over an
   animated canvas; every one of them re-reads and re-blurs the
   pixels behind it on every frame the canvas draws. Measured on
   a throttled 390px viewport that was the difference between
   single-digit and double-digit FPS, and it is the reason the
   icons appeared to jitter while scrolling.

   Nothing here removes information. It swaps blur for a solid
   translucent fill at the same colour, which reads nearly
   identical over a dark background, and it stops animating
   properties the compositor cannot handle cheaply.
   ============================================================ */

@media (max-width: 640px), (pointer: coarse) {
  /* The card keeps a hint of blur: it is the one surface where the
     background showing through matters. Half the radius is a quarter of
     the fill cost.
     The cap has to sit on `backdrop-filter` itself, not on `--card-blur`:
     the member's radius arrives as an inline custom property and would win
     over a media-query declaration. */
  .profile-card {
    --card-blur-soft: min(var(--card-blur, 22px), 12px);
  }
  /* Only the card itself needs the blur. `::before` is the masked 1px hairline
     ring: its interior is cut away by the mask, so the backdrop blur underneath
     it is computed over the whole element box and then thrown away — a full
     card-sized re-blur every frame, per card, for one visible pixel. Measured
     at −24% frame time on its own. */
  .profile-card {
    backdrop-filter: blur(var(--card-blur-soft, 12px));
    -webkit-backdrop-filter: blur(var(--card-blur-soft, 12px));
  }

  /* Controls, chips and pills sit on top of the canvas. A flat fill is
     indistinguishable from a 10px blur at this size — but only for the ones
     that are a plate. `orb` and the bare glyphs draw their own surface, so a
     flat fill turns a floating glow into a black rectangle; they are excluded
     from the floor below and keep their own transparent background. */
  .pa-btn,
  .pa-pill,
  .pa--vinyl,
  .pa--wave,
  .pa-cassette__shell,
  .pa-deck__shell,
  .quiet-zone,
  .quiet-zone__card,
  .pg-report,
  .pg-share,
  .pg-message,
  .pg-like,
  .pg-views,
  .pg-credit,
  .pg-badge--shape-glass,
  .pg-btn--fx-glass,
  .pg-presence__card,
  .audio-dock [data-player-fx="glass"] :is(.pa-pill, .pa-cassette__shell, .pa-deck__shell, .pa-orb__core) {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background-color: rgba(10, 12, 20, 0.86);
  }
  .pa-speaker__btn,
  .pa-dot__btn { background-color: transparent; }
  .pa--speaker,
  .pa--dot,
  .pa--orb { background-color: transparent; }

  /* Card motion is a pointer effect. A touch screen has no pointer that
     rests on the card, so the whole family is dead weight — and the
     `will-change: transform` it declares pins a layer for nothing. */
  .profile-card.is-card-reactive {
    transition: none;
    will-change: auto;
  }

  /* Long-running ambient loops are the other per-frame cost. Keep them,
     but drop the ones that force a re-raster on every tick (blur, filter,
     drop-shadow) and leave the pure transform/opacity loops alone. */
  .avatar__fx,
  .avatar .avatar__fx *,
  .avatar .avatar__fx *::before,
  .avatar .avatar__fx *::after {
    filter: none !important;
  }
  .avatar.frame-motion-pulse .avatar__fx { animation: none; }
}

/* Weak hardware, whatever the screen size. */
html.is-low-power .profile-card {
  --card-blur-soft: min(var(--card-blur, 22px), 10px);
}
html.is-low-power .profile-card {
  backdrop-filter: blur(var(--card-blur-soft, 10px));
  -webkit-backdrop-filter: blur(var(--card-blur-soft, 10px));
}

/* The background art is three full-viewport layers, each with a blur that has
   to be recomputed as the layer spins. On a phone the blur radius buys no
   visible detail — the layers are at 40–80% opacity over a dark field — so a
   smaller radius is free quality. Below that, `contain` keeps the filter off
   the compositor's critical path.
   These must target the same pseudo-element that owns the blur in profile-v3,
   or the declaration lands back on the animated layer and re-introduces the
   per-frame re-filter it was moved off. */
@media (max-width: 640px), (pointer: coarse) {
  .pg-art__layer::before {
    filter: blur(18px) saturate(1.25);
  }
  /* `#bgMedia` is deliberately not touched here. Its blur is the member's own
     `--bg-blur`, and a still image is rasterised once, so capping it would cost
     the member a setting and buy nothing. Only the *animated* art layers need
     the budget. */
}
html.is-low-power .pg-art__layer::before {
  filter: blur(10px) saturate(1.2);
}
html.is-low-power .pa,
html.is-low-power .pa-btn,
html.is-low-power .pa-pill,
html.is-low-power .quiet-zone,
html.is-low-power .pg-report,
html.is-low-power .pg-share,
html.is-low-power .pg-message,
html.is-low-power .pg-like,
html.is-low-power .pg-views,
html.is-low-power .pg-credit {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background-color: rgba(10, 12, 20, 0.86);
}

/* On a phone the soundtrack player joins the fixed bottom band as the action
   row, stacked directly above it.
   The player used to live in the stage's second grid row, so its screen
   position was a consequence of the card height. On any profile whose card is
   taller than the viewport — a 513px card on a 740px screen is enough — the
   player landed at the very bottom of the screen, exactly where the fixed action
   row sits, and the four buttons drew on top of it. Padding on the stage cannot
   fix that: the row is viewport-anchored and the player is not, so the two only
   agree by coincidence. Pinning both makes an overlap impossible at every card
   height and scroll position, and a player that stays reachable while the page
   scrolls is what a floating player is for anyway.
   `left: 50%` + `translate` reproduces the stage's centring without needing the
   grid row, and the width mirrors the dock's own `min(620px, 100%)` at phone
   scale. */
@media (max-width: 780px), (pointer: coarse) {
  .audio-dock {
    position: fixed;
    left: 50%;
    right: auto;
    margin: 0;
    bottom: calc(max(12px, env(safe-area-inset-bottom, 0px)) + 92px);
    translate: -50% 0;
    width: min(360px, calc(100vw - 36px));
    max-width: none;
    z-index: 12;
  }
  /* The four minimal shells are one control, not a plate. Forcing them into the
     360px row turned a 34px speaker into a 354px bar with 320px of nothing
     inside it — the exact silhouette the member did not pick in the studio.
     They shrink-wrap and stay centred; every plate model keeps the full row.
     The orb joins them: it is a floating glow, not a slab, so a 354px row left
     its label and meter stranded at the left of an empty band. */
  .audio-dock[data-player="speaker"],
  .audio-dock[data-player="dot"],
  .audio-dock[data-player="chip"],
  .audio-dock[data-player="ticker"],
  .audio-dock[data-player="orb"] {
    width: fit-content;
    max-width: min(360px, calc(100vw - 36px));
  }
  .audio-dock[data-player="speaker"] > .pa,
  .audio-dock[data-player="dot"] > .pa,
  .audio-dock[data-player="chip"] > .pa,
  .audio-dock[data-player="ticker"] > .pa,
  .audio-dock[data-player="orb"] > .pa {
    width: auto;
    min-width: 0;
    max-width: 100%;
    justify-content: center;
  }
  .audio-dock[data-player="orb"] .pa-orb__label { flex: 0 1 auto; }
  .audio-dock[data-player="speaker"] .pa-speaker__label,
  .audio-dock[data-player="dot"] .pa-dot__title { max-width: 22ch; }
  .audio-dock[hidden] { display: none; }

  /* The stage no longer owes the player a row, so the card stack stops being
     pushed around by a player whose size varies per model and skin. The
     reserve is the fixed band exactly: 42px credit strip + 92px action row +
     the player's own height, so the card's last row can always be scrolled
     clear of it. */
  /* The stage owes the band its exact height, measured into `--band-h` by the
     page (player model and skin change it; a hidden player removes it). */
  .pg-stage {
    padding-bottom: calc(var(--band-h, 190px) + 8px);
  }
}

/* The mobile bottom band is viewport-anchored (the player and the four action
   buttons) while the card is not. On any card taller than the screen the last
   rows scroll underneath the band, and because the band is four separate
   floating circles plus one plate, the bio, the widgets and the social icons
   show through the gaps between them: two broken layers instead of one bar.
   This element is the single opaque surface the band floats on. It carries no
   information and takes no pointer events, so the only thing it changes is
   that whatever scrolls under the band is hidden, exactly as a fixed bar is
   expected to behave.
   `--band-h` is the band's own height (credit strip 42 + action row 50 +
   player 93 + gutters) and `--band-pad` the reserve the stage owes it; both
   are recomputed on resize by the page so a hidden player, a taller player
   model or a safe-area inset all agree with the surface. */
.pg-band {
  display: none;
}

@media (max-width: 780px), (pointer: coarse) {
  .pg-band {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    /* Inside `.pg-stage` (z-index 10), so the scrim only has to beat the card
       (z-index 1) and stay under the docked player (z-index 12). The four
       action buttons are body children at 44 and paint above the stage
       regardless. */
    z-index: 2;
    display: block;
    height: calc(var(--band-h, 190px) + env(safe-area-inset-bottom, 0px));
    /* A bar, not a veil. The bottom 100px must be solid: the action row and the
       credit strip are floating circles, and anything scrolling behind them
       shows through the gaps between them. The part above that belongs to the
       player, which is its own plate, so the scrim feathers out instead of
       painting a hard rectangle over the member's background art.
       The solid section is a near-black mixed from the page's own surface
       colours rather than pure #080a10, so it reads as the same material as
       the rest of the page instead of a cut-out hole. */
    background:
      linear-gradient(to top,
        var(--bg-0, #080a10) 0,
        var(--bg-0, #080a10) 96px,
        color-mix(in srgb, var(--bg-0, #080a10) 0%, transparent) 100%);
    pointer-events: none;
  }
  /* No soundtrack docked: the band is the credit strip and the action row. */
  .profile-page:not(.has-player) .pg-band {
    height: calc(var(--band-h, 104px) + env(safe-area-inset-bottom, 0px));
  }
}

/* The mobile player floats over the card, so a shell designed to sit on a dark
   page (rgba .35–.72 over a backdrop blur) lets the member's bio, links and
   social icons show straight through it — the two layers then read as one
   broken panel. On a phone the floating plate has to be an opaque plate.
   The colour is applied to `background-color`, not the `background` shorthand,
   so a skin that paints a gradient keeps it; and the selector repeats
   `.audio-dock` to reach the same specificity as the skin rule in
   profile-v3.css (this file loads after it, so the later rule wins).

   The floor belongs to models that *own a slab*. The four minimal shells
   (speaker, dot, chip, ticker) are a single control on the page with no plate
   of their own; painting the flex row behind them turned a 34px speaker into a
   full-width black bar. They keep a transparent dock and centre their own
   control instead. */
@media (max-width: 640px), (pointer: coarse) {
  .audio-dock.audio-dock [data-player-fx]:not([data-player-fx="clean"]):is(.pa--vinyl, .pa--wave) {
    background-color: rgb(8, 10, 16);
  }
  .audio-dock.audio-dock [data-player-fx]:not([data-player-fx="clean"]) :is(.pa-pill, .pa-cassette__shell, .pa-deck__shell, .pa-chip__shell) {
    background-color: rgb(8, 10, 16);
  }
  /* The plain (clean) shells keep their own gradient, but it is translucent by
     design. On the fixed plate it needs a floor under it too — only for the
     models whose plate is the whole row. */
  .audio-dock.audio-dock:not([data-player="speaker"]):not([data-player="dot"]):not([data-player="chip"]):not([data-player="ticker"]):not([data-player="orb"]) > .pa {
    background-color: rgb(8, 10, 16);
  }
}

/* ============================================================
   measured slow device
   ------------------------------------------------------------
   `html.is-slow-device` is set by watchFrameBudget() in motion.js
   after the profile has been on screen for ~2.6s and the real
   frame cadence came back worse than the 30 fps budget. It is
   the only tier that reacts to the device the visitor actually
   holds: the core/memory test in lowPowerDevice() reports a
   mid-range phone as an 8-core 4 GB machine and never fires.

   Everything here removes per-frame *variability*, not identity.
   The member's colours, gradients, frame sculpture, avatar
   effect, text effects, layout, video and music all stay. What
   goes is the work that has to be redone every single frame.
   ============================================================ */

html.is-slow-device .pg-art {
  /* Three viewport-sized layers spinning behind everything. Even with the
     filter cached (see profile-v3), continuous transform of a 3.4x-viewport
     blended surface is the largest remaining per-frame cost. Freezing the
     layers keeps the exact composition — same colours, same positions — and
     removes the redraw. Measured: −22 ms/frame at 390px. */
  contain: paint;
}
html.is-slow-device .pg-art__layer {
  animation: none !important;
}
/* The grain is a full-viewport repeating pattern with `mix-blend-mode: overlay`,
   so every frame it forces the layer under it to be re-blended across the whole
   screen. At 0.16 opacity the texture is barely visible on a phone anyway. */
html.is-slow-device .pg-art__grain {
  display: none;
}

/* A card-sized blur over a playing video has to be recomputed for every video
   frame. On a device that already missed its budget, the flat fill is the
   correct trade: the radial gradient backdrop for a clear card over footage is
   already opaque where the text sits, so nothing loses contrast. */
html.is-slow-device .profile-card,
html.is-slow-device .profile-card::before {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}

/* Ambient loops with drop-shadow/blur are re-rastered per tick; the orbs are
   pure decoration and read identically standing still. */
html.is-slow-device .pg-orb {
  animation: none !important;
}
html.is-slow-device .avatar__fx,
html.is-slow-device .avatar .avatar__fx * {
  filter: none !important;
}
html.is-slow-device .avatar.frame-motion-pulse .avatar__fx {
  animation: none !important;
}
html.is-slow-device .profile-card__frame {
  animation: none !important;
}

/* The name's `liquid` and the bio's `flicker` are per-glyph loops: every glyph
   is its own layer, and a text-shadow on an animated glyph re-rasters the text
   every tick. The static forms are the same gradient and the same shadow. */
html.is-slow-device .profile-name.text-fx-liquid .fx-char,
html.is-slow-device .text-fx-flicker .fx-char {
  animation: none !important;
}
html.is-slow-device .text-fx-glow {
  animation: none !important;
}

/* Card motion is a pointer effect and already off for coarse pointers; this
   covers a slow desktop that still has one. */
html.is-slow-device .profile-card.is-card-reactive {
  transition: none !important;
  will-change: auto !important;
}
