/* The Edge Music iPhone app's layer (EdgeVentureMusicNative/HYBRID_PLAN.md). Every
   rule is scoped to html[data-app], which only the app's web view carries, so a
   browser never matches any of it. Linked in <head> on every page the app can show,
   so it applies from the first paint (player.html links platform-experience.css at
   the end of <body>, too late for the top of the screen).

   The app draws the page edge to edge: the notch/status bar and the home indicator
   sit over the page, and env(safe-area-inset-*) says how much. The shell also makes
   every page viewport-fit=cover, so the insets are live on the PHP pages too.

   Layout on a phone, from the top: the status bar over the header's own background,
   the page, the mini player, then the command bar as a tab bar over the home
   indicator. Measured on an iPhone 13 mini with the app's layout probe
   (ios/Tests/LayoutProbeTests.swift). */

html[data-app]{
  --app-tab-height:52px;
  --app-tab-space:calc(var(--app-tab-height) + env(safe-area-inset-bottom));
  --app-bar-height:76px;
  color-scheme:dark;
  -webkit-text-size-adjust:100%;
  text-size-adjust:100%;
  /* Long-press belongs to the app: no link previews (the shell turns those off
     too), no image menus, no selection handles on controls. Text a listener may
     want to copy stays selectable. */
  -webkit-touch-callout:none;
}
html[data-app] :is(input,textarea,[contenteditable],.spaces-message p,.thread-messages,.message-body,.bio,.privacy-copy,article p){
  -webkit-touch-callout:default;-webkit-user-select:text;user-select:text;
}
html[data-app] :is(button,a,label,summary,img,svg,[role=button],[role=tab],[role=menuitem],[role=menuitemradio],header,nav,.bar,.card,.stage,.platform-view-bar,.evm-command-bar){
  -webkit-user-select:none;user-select:none;
}
html[data-app] img{-webkit-user-drag:none}

/* ---------- Feel: curves, scrolling, presses (assets/app-motion.js does the gestures) ----------
   One set of curves for every sheet, press and slide. The scrims take the world's own --bg, so each of
   the 12 worlds keeps its tint behind a sheet. Scrolling done in code is instant, as in a native app:
   a tab switch or a closing sheet never glides the page (an explicit smooth scroll still can). */
html[data-app]{
  --app-ease-sheet:cubic-bezier(.32,.72,0,1);--app-ease-out:cubic-bezier(.2,.85,.2,1);--app-ease-in:cubic-bezier(.4,0,1,1);--app-ease-spring:cubic-bezier(.34,1.36,.64,1);
  --app-scrim:color-mix(in srgb,var(--bg,#050713) 70%,transparent);--app-scrim-blur:11px;
  scroll-behavior:auto;-webkit-tap-highlight-color:transparent;
}
@supports (transition-timing-function:linear(0,1)){
  html[data-app]{--app-ease-spring:linear(0,.009,.035 2.1%,.141 4.4%,.723 12.9%,.938 16.7%,1.017 19.8%,1.061 23.1%,1.078 26.8%,1.073 30.7%,1.023 43.7%,.998 55.6%,1)}
}
/* A tap is not a hover: on a touch screen the lift would stay after the finger leaves. Transforms only;
   hover colours vary per world and stay. An iPad with a trackpad keeps its lifts. */
@media (hover:none){
  html[data-app] :is(.card,.shelf-row .card,.mood-card,.personal-mix,.save,.share-track,.spotlight-actions button,.world-card-action,.collection-hero-actions button,.collection-empty-actions button,.playlist-tile,.feedback-btn,.tune-btn,.stage-tool,.queue-item,.queue-retune,.local-actions button,.pulse-choice,.load-more,.theme-btn,.collection-btn,.account-btn,.hub-link,.top-actions a,.top-actions button,.small-btn,.danger-btn,.profile-actions button,.editor-actions button,.profile-nav button,.world-select button,.person-card,.avatar-option,.artist-tile,.playlist-card,.world-card,.path-card,.system-card,.submission,.platform-card,.theme-menu button,.hub-theme-menu button,.platform-nav a,.hub-route-stack a):hover{transform:none}
  html[data-app] .personal-mix:hover::after,html[data-app] .personal-mix:hover .personal-mix-action::after,html[data-app] :is(.theme-menu,.hub-theme-menu) button:hover .sw{transform:none}
  html[data-app] .seek:hover:not(.drag) .knob{transform:translate(-50%,-50%)}
}
/* Press: a short shrink with the separate scale property (it composes with each control's own transforms)
   and a spring back. :where() keeps these at (0,1,1), under each control's own rules; the colour timings
   stay slow enough that a world switch still washes across the controls. Tab items keep their icon press. */
html[data-app] :where(button,[role=button],.card,.mood-card,.personal-mix,.playlist-tile,.artist-tile,.person-card,.pulse-choice,.dashboard-space,.dashboard-personal>a,.discover-shortcuts>a,.hub-link,.profile-action,.evm-command-menu>a):not(:where(.evm-command-bar>*,.evm-command-more>summary,:disabled,[aria-disabled=true])){
  transition-property:scale,opacity,color,background-color,border-color,box-shadow,transform,filter,border-radius;
  transition-duration:.42s,.2s,.25s,.3s,.3s,.3s,.25s,.2s,.45s;
  transition-timing-function:var(--app-ease-spring),ease,ease,ease,ease,ease,var(--app-ease-out),ease,ease;
}
html[data-app] :where(button,[role=button],.hub-link,.profile-action,.dashboard-personal>a,.discover-shortcuts>a):not(:where(.evm-command-bar>*,.evm-command-more>summary,:disabled,[aria-disabled=true])):active{scale:.965;transition-duration:.08s}
/* Cards and full-width rows press less, a beat later, so a scroll that starts on one never flickers it;
   pressing a card's own play or save button presses only that button. */
html[data-app] :where(.card,.mood-card,.personal-mix,.playlist-tile,.artist-tile,.person-card,.pulse-choice,.dashboard-space,.evm-command-menu>a,.evm-command-menu>button):active{scale:.98;transition-duration:.1s;transition-delay:30ms}
html[data-app] :where(.card,.mood-card,.personal-mix,.playlist-tile,.artist-tile,.person-card,.dashboard-space):active:has(:is(button,a,input,select):active){scale:none}
html[data-app] :is(.evm-command-menu>a,.evm-command-menu>button):active{background:color-mix(in srgb,var(--a2,#7ce6dc) 14%,transparent)}
html[data-app] :is(summary,a:not([class])):not(:where(.evm-command-bar>*,.evm-command-more>summary,.evm-command-menu>a)):active{opacity:.6}
/* Full pages are not zoom-locked (the player is): a field under 16px would zoom the page on focus. */
html[data-app]:not(:has(#appHeader)) :is(input:not([type=checkbox],[type=radio],[type=range],[type=color],[type=file]),select,textarea){font-size:max(16px,1em)}

/* ---------- Top: nothing interactive under the status bar ---------- */

/* The player's header is sticky; its padding carries its own blurred world
   background up behind the clock, like a navigation bar. */
html[data-app] header#appHeader{padding-top:calc(13px + env(safe-area-inset-top))}
@media(max-width:640px){html[data-app] header#appHeader{padding-top:calc(10px + env(safe-area-inset-top))}}
html[data-app]{scroll-padding-top:calc(82px + env(safe-area-inset-top))}
/* In-page surfaces (Spaces, Hub, Artists…) hide the header; their return bar is
   then the first thing on screen, and sticky. */
html[data-app] .platform-view-bar{padding-top:calc(8px + env(safe-area-inset-top))}
html[data-app] .platform-navigation-status{top:calc(8px + env(safe-area-inset-top))}
html[data-app] body.platform-embedded{--messenger-top-clearance:calc(64px + env(safe-area-inset-top))}
html[data-app] body.platform-embedded .hub-feedback:has(.notice:not(:empty)){top:calc(70px + env(safe-area-inset-top))}
html[data-app] body:not(.platform-embedded) .hub-feedback:has(.notice:not(:empty)){top:calc(10px + env(safe-area-inset-top))}

/* A page loaded on its own (Account settings, Privacy, a share or video page)
   has no sticky player header: pad the page and keep a strip of the world's
   colour behind the status bar so content never scrolls under the clock. */
html[data-app]:not(:has(#appHeader)) body{padding-top:env(safe-area-inset-top)}
html[data-app]:not(:has(#appHeader))::after{
  content:"";position:fixed;z-index:2147483000;top:0;left:0;right:0;height:env(safe-area-inset-top);pointer-events:none;
  background:color-mix(in srgb,var(--bg,#070b18) 88%,transparent);
}
/* The watch and share pages' world panels become fixed sheets on phones. */
@media(max-width:640px){html[data-app] .theme-panel{top:calc(12px + env(safe-area-inset-top))!important;max-height:calc(100dvh - 24px - env(safe-area-inset-top) - var(--app-tab-space))!important}}
@media(max-width:600px){html[data-app] .share-theme-panel{top:calc(12px + env(safe-area-inset-top));max-height:calc(100dvh - 24px - env(safe-area-inset-top) - var(--app-tab-space))}}

/* Sheets and dialogs stay between the status bar and the home indicator, and
   their close buttons are full-size targets. */
html[data-app] :is(.account-overlay,.pulse-overlay,.playlist-overlay,.language-overlay){
  padding:max(10px,env(safe-area-inset-top)) max(10px,env(safe-area-inset-right)) max(10px,env(safe-area-inset-bottom)) max(10px,env(safe-area-inset-left));
}
html[data-app] :is(.account-panel,.pulse-panel,.playlist-picker,.language-panel){
  max-height:calc(100dvh - max(10px,env(safe-area-inset-top)) - max(10px,env(safe-area-inset-bottom)));
}
html[data-app] :is(.account-close,.pulse-close,.picker-close,.language-close){min-width:44px;min-height:44px}
/* …and on top: Tune's content is stacked at z-index 1 (player.html .pulse-panel>*), so its eyebrow line
   used to sit over the lower half of × and swallow the tap. */
html[data-app] :is(.account-close,.pulse-close,.picker-close,.language-close){z-index:4}
html[data-app] :is(.evm-dialog,.voice-dialog,.party-dialog,.account-sync-dialog){
  margin-top:calc(12px + env(safe-area-inset-top));margin-bottom:calc(12px + env(safe-area-inset-bottom));
  max-height:calc(100dvh - 24px - env(safe-area-inset-top) - env(safe-area-inset-bottom));
}

/* ---------- Sheets: one system (assets/app-motion.js adds the grabbers, the drag and the close) ----------
   On a phone, Account, Language, Tune, the playlist picker and the evm-dialogs (Search, Queue, New releases,
   Get started, Customize home, Diagnostics) rise from the bottom edge with the world's top corners. The
   backdrop moves to ::before so it can fade (and dim with a drag) without fading the sheet; a tap on it is
   still a tap on the overlay, so the page's own backdrop close runs. Sign-in and Language build their
   content on these; they do not add sheet mechanics of their own. */
html[data-app] .app-grabber{display:none}
html[data-app] :is(.pulse-panel,.language-panel):focus{outline:none}
@media(max-width:640px){
  html[data-app] :is(.account-overlay,.pulse-overlay,.playlist-overlay,.language-overlay){
    place-items:end stretch;padding:max(10px,env(safe-area-inset-top)) 0 0;background:transparent;-webkit-backdrop-filter:none;backdrop-filter:none;
  }
  html[data-app] :is(.account-overlay,.pulse-overlay,.playlist-overlay,.language-overlay)::before{
    content:"";position:fixed;inset:0;z-index:-1;background:var(--app-scrim);
    -webkit-backdrop-filter:blur(var(--app-scrim-blur));backdrop-filter:blur(var(--app-scrim-blur));
    opacity:var(--app-sheet-dim,1);transition:opacity .38s var(--app-ease-sheet);
  }
  html[data-app] :is(.account-overlay,.pulse-overlay,.playlist-overlay,.language-overlay):not([hidden])::before{animation:app-fade-in .3s ease-out}
  html[data-app] :is(.account-overlay,.pulse-overlay,.playlist-overlay,.language-overlay).app-dragging::before{transition:none}
  html[data-app] :is(.account-overlay,.pulse-overlay,.playlist-overlay,.language-overlay).app-closing::before{animation:app-fade-out var(--app-close-ms,.26s) ease-in forwards}
  html[data-app] :is(.account-panel,.pulse-panel,.playlist-picker,.language-panel){
    width:100%;max-width:none;margin:0;max-height:calc(100dvh - max(10px,env(safe-area-inset-top)));
    border-bottom-width:0;border-left-width:0;border-right-width:0;border-radius:var(--chrome-radius,22px) var(--chrome-radius,22px) 0 0;
    padding-bottom:calc(22px + env(safe-area-inset-bottom));overscroll-behavior:contain;
  }
  /* The fill is none, so a drag's inline transform wins as soon as the rise is over. */
  html[data-app] :is(.account-overlay,.pulse-overlay,.playlist-overlay,.language-overlay):not([hidden])>:is(.account-panel,.pulse-panel,.playlist-picker,.language-panel){animation:app-sheet-up .5s var(--app-ease-sheet)}
  /* Sign-in is a full-height form sheet, so its fields sit above the keyboard. */
  html[data-app] .account-panel{min-height:calc(100dvh - max(10px,env(safe-area-inset-top)))}
  html[data-app] .app-grabber{display:block;position:absolute;z-index:3;top:7px;left:50%;width:38px;height:5px;margin-left:-19px;border-radius:3px;background:color-mix(in srgb,var(--fg,#fff) 34%,transparent);pointer-events:none}
  /* The dialogs' grabber rides in their sticky head, which sticks at the sheet's top edge, so the grabber
     stays put while the sheet scrolls. */
  html[data-app] .evm-dialog{
    margin:auto 0 0;width:100%;max-width:none;max-height:calc(100dvh - env(safe-area-inset-top) - 10px);
    border-bottom-width:0;border-left-width:0;border-right-width:0;border-radius:var(--chrome-radius,22px) var(--chrome-radius,22px) 0 0;
    padding:0 16px calc(16px + env(safe-area-inset-bottom));overscroll-behavior:contain;
  }
  html[data-app] .evm-dialog-head{top:0;padding:20px 0 12px}
  html[data-app] .evm-dialog:has(.evm-search-input){height:calc(100dvh - env(safe-area-inset-top) - 10px)}
  html[data-app] .evm-dialog[open]{animation:app-sheet-up .5s var(--app-ease-sheet)}
  html[data-app] .evm-dialog[open]::backdrop{opacity:var(--app-sheet-dim,1);animation:app-fade-in .3s ease-out}
  html[data-app] .evm-dialog.app-closing::backdrop{animation:app-fade-out var(--app-close-ms,.26s) ease-in forwards}
}
/* While a finger holds a sheet, nothing eases behind it. */
html[data-app] .app-dragging{transition:none}
@keyframes app-sheet-up{from{transform:translate3d(0,100%,0)}}
@keyframes app-fade-in{from{opacity:0}}
@keyframes app-fade-out{to{opacity:0}}
@keyframes app-pop-in{from{opacity:0;transform:translateY(10px) scale(.92)}}
@keyframes app-late-in{from{opacity:0;visibility:hidden;transform:translate(-50%,-6px)}to{opacity:1;visibility:visible;transform:translate(-50%,0)}}
@keyframes app-spin{to{transform:rotate(1turn)}}

/* ---------- Bottom: the command bar becomes the tab bar ---------- */

/* Website-only destinations (App Store rules 3.1.1 and 2.3.10), repeated from
   platform-experience.css for the full pages that do not load it. */
html[data-app] :is(a[href^="creator.php"],a[href^="apps.php"],a[href^="devices.php"],#localBtn,#localLibrary,[data-evm-install]){display:none!important}

html[data-app] .evm-command-bar{
  --app-tab-icon:linear-gradient(transparent,transparent);
  position:fixed;z-index:40;left:0;right:0;bottom:0;top:auto;
  display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);grid-template-columns:none;align-items:stretch;gap:0;
  min-height:0;height:var(--app-tab-space);margin:0;
  padding:0 max(6px,env(safe-area-inset-right)) env(safe-area-inset-bottom) max(6px,env(safe-area-inset-left));
  overflow:visible;flex-wrap:nowrap;border-top:1px solid color-mix(in srgb,var(--line,#354258) 80%,transparent);border-bottom:0;
  background:color-mix(in srgb,var(--bg2,#0b1020) 86%,transparent);
  -webkit-backdrop-filter:blur(22px) saturate(1.5);backdrop-filter:blur(22px) saturate(1.5);
  box-shadow:0 -10px 34px -18px color-mix(in srgb,var(--a1,#8768ef) 45%,transparent);
}
@media(min-width:700px){html[data-app] .evm-command-bar{padding-inline:max(6px,calc((100vw - 620px)/2))}}
html[data-app] .evm-command-bar>a,html[data-app] .evm-command-bar>button,html[data-app] .evm-command-more>summary{
  position:relative;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;gap:3px;
  width:100%;min-width:0;min-height:0;height:var(--app-tab-height);margin:0;padding:7px 2px 0;
  border:0;border-radius:14px;background:none;box-shadow:none;
  color:color-mix(in srgb,var(--fg,#f3f6ff) 60%,transparent);
  font:600 10px/1.15 Inter,system-ui,sans-serif;letter-spacing:.01em;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  -webkit-tap-highlight-color:transparent;transition:color .18s ease;
}
html[data-app] .evm-command-bar>a:hover,html[data-app] .evm-command-bar>button:hover{background:none;border:0}
html[data-app] .evm-command-more{position:static;width:100%;min-width:0;margin:0;grid-column:auto}
html[data-app] .evm-command-more>summary::after{content:none}
/* Icons: one mask per tab, painted in the tab's colour. */
html[data-app] .evm-command-bar>a::before,html[data-app] .evm-command-bar>button::before,html[data-app] .evm-command-more>summary::before{
  content:"";display:block;flex:none;width:25px;height:25px;background:currentColor;
  -webkit-mask:var(--app-tab-icon) center/contain no-repeat;mask:var(--app-tab-icon) center/contain no-repeat;
  transition:transform .2s ease,filter .2s ease;
}
html[data-app] .evm-command-bar>a[href*="panel=home"]{--app-tab-icon:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 2.6 2.4 10.2l1.2 1.6 1.4-1.1V20a1.5 1.5 0 0 0 1.5 1.5H10v-6.2h4v6.2h3.5A1.5 1.5 0 0 0 19 20v-9.3l1.4 1.1 1.2-1.6z'/></svg>")}
html[data-app] .evm-command-bar>.evm-search-launch{--app-tab-icon:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M10.5 2.5a8 8 0 0 1 6.3 12.9l4.9 4.9-1.4 1.4-4.9-4.9A8 8 0 1 1 10.5 2.5zm0 2a6 6 0 1 0 0 12 6 6 0 0 0 0-12z'/></svg>");font-size:0}
html[data-app] .evm-command-bar>.evm-search-launch{gap:0}
html[data-app] .evm-command-bar>.evm-search-launch::after{content:"Search";margin-top:3px;font:600 10px/1.15 Inter,system-ui,sans-serif}
html[data-app] .evm-command-bar>a[href*="panel=library"]{--app-tab-icon:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 2.8v12.7a3.5 3.5 0 1 1-2-3.16V7.1l-8 1.6v8.8A3.5 3.5 0 1 1 8 14.34V5.2z'/></svg>")}
html[data-app] .evm-command-bar>a[href$="spaces.php"]{--app-tab-icon:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M4 3h11a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H9l-4.5 3.5V14H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zm15 5h1a2 2 0 0 1 2 2v6.5a2 2 0 0 1-2 2h-.5V21L15 18.5h-4.5a2 2 0 0 1-1.73-1H15a4 4 0 0 0 4-4z'/></svg>")}
html[data-app] .evm-command-more>summary{--app-tab-icon:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M5 10a2 2 0 1 1 0 4 2 2 0 0 1 0-4zm7 0a2 2 0 1 1 0 4 2 2 0 0 1 0-4zm7 0a2 2 0 1 1 0 4 2 2 0 0 1 0-4z'/></svg>")}
/* Where you are, and a press you can feel. */
html[data-app] .evm-command-bar>a[aria-current],html[data-app] .evm-command-bar>a.app-tab-origin,html[data-app] .evm-command-more[open]>summary{color:var(--a2,#7ce6dc);background:none;border:0}
html[data-app] .evm-command-bar>a[aria-current]::before,html[data-app] .evm-command-bar>a.app-tab-origin::before,html[data-app] .evm-command-more[open]>summary::before{filter:drop-shadow(0 0 7px color-mix(in srgb,var(--a2,#7ce6dc) 70%,transparent))}
html[data-app] .evm-command-bar>:is(a,button):active::before,html[data-app] .evm-command-more>summary:active::before{transform:scale(.86)}
/* The tab you land on answers with a small spring in its icon. */
html[data-app] .evm-command-bar>a[aria-current]::before{animation:app-tab-pop .42s var(--app-ease-spring)}
@keyframes app-tab-pop{from{transform:scale(.8)}}
/* More opens as a sheet above the tab bar. */
html[data-app] .evm-command-menu{
  position:fixed;z-index:100;top:auto;left:auto;right:max(10px,env(safe-area-inset-right));bottom:calc(var(--app-tab-space) + 8px);
  width:min(290px,calc(100vw - 20px));max-height:calc(100dvh - var(--app-tab-space) - env(safe-area-inset-top) - 24px);overflow:auto;overscroll-behavior:contain;
  padding:8px;border-radius:20px;background:color-mix(in srgb,var(--bg2,#152034) 97%,transparent);border:1px solid color-mix(in srgb,var(--line,#354258) 80%,transparent);
  box-shadow:0 18px 60px #000a;
  transform-origin:100% 100%;animation:app-pop-in .36s var(--app-ease-spring);
}
html[data-app] .evm-command-menu>a,html[data-app] .evm-command-menu>button{min-height:46px;font-size:14px;font-weight:600;border-radius:12px}
/* Downloads (build 8 and later): the app's own library of saved songs. Its glyph is masked in the text colour,
   like the tab bar's icons. */
html[data-app] .evm-command-menu>.evm-menu-downloads{gap:10px}
html[data-app] .evm-command-menu>.evm-menu-downloads::before{content:"";flex:none;width:20px;height:20px;background:currentColor;
  -webkit-mask:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill-rule='evenodd' d='M12 2a10 10 0 1 1 0 20 10 10 0 0 1 0-20zm0 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16zm-1 3h2v6.6l2.3-2.3 1.4 1.4-4.7 4.7-4.7-4.7 1.4-1.4 2.3 2.3z'/></svg>") center/contain no-repeat;
  mask:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill-rule='evenodd' d='M12 2a10 10 0 1 1 0 20 10 10 0 0 1 0-20zm0 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16zm-1 3h2v6.6l2.3-2.3 1.4 1.4-4.7 4.7-4.7-4.7 1.4-1.4 2.3 2.3z'/></svg>") center/contain no-repeat}
/* While More is open the bar (and its sheet) sit above the floating layers:
   the minimized video (76), toasts (70) and the voice strip (94). */
html[data-app] .evm-command-bar:has(.evm-command-more[open]){z-index:97}
/* The watch-party strip is at 950: step it aside while More is open. */
html[data-app] body:has(.evm-command-more[open]) .party-strip{visibility:hidden}
/* Behind More, a world-tinted dim layer (app-motion.js) takes the tap that closes it, so the card or song
   under the finger never starts. It sits under the open bar (97) and over the mini player, toasts, the
   minimized video and the voice strip. */
html[data-app] .app-scrim{position:fixed;inset:0;z-index:96;background:color-mix(in srgb,var(--bg,#050713) 45%,transparent);touch-action:none;animation:app-fade-in .22s ease-out}
html[data-app] .app-scrim[hidden]{display:none}
html[data-app] .app-scrim.app-closing{animation:app-fade-out .16s ease-in forwards}

/* The mini player rides on the tab bar. */
html[data-app] .bar{bottom:var(--app-tab-space);height:var(--app-bar-height);padding-bottom:0;box-shadow:0 -8px 30px -16px color-mix(in srgb,var(--a1) 40%,transparent);transition:transform .25s,opacity .2s}
html[data-app] .bar:not(.show){opacity:0;pointer-events:none}
html[data-app] body:has(#bar){padding-bottom:calc(var(--app-bar-height) + var(--app-tab-space) + 12px)}
html[data-app] body:not(:has(#bar)){padding-bottom:calc(var(--app-tab-space) + 16px)}
html[data-app] body:not(.has-voice-strip):not(.has-party-strip) main#appMain{padding-bottom:16px}
html[data-app] body:not(.has-voice-strip):not(.has-party-strip) #platformView{padding-bottom:16px}
/* The voice and party strips float above the mini player and the tab bar
   (both strips add the home-indicator inset themselves, except the one below). */
html[data-app]{--evm-player-clearance:var(--app-tab-height)}
html[data-app] .voice-strip:not(.voice-strip-overlay){bottom:calc(var(--app-tab-space) + 4px)}
html[data-app]:has(#bar.show):not(:has(body.has-party-strip)){--evm-player-clearance:calc(var(--app-bar-height) + var(--app-tab-height))}
html[data-app]:has(#bar.show):not(:has(body.has-party-strip)) .voice-strip:not(.voice-strip-overlay){bottom:calc(var(--app-bar-height) + var(--app-tab-space) + 4px)}
/* Everything that floated above the mini player floats above the tab bar too. */
html[data-app] .toast{bottom:calc(var(--app-bar-height) + var(--app-tab-space) + 14px)}
html[data-app] .player-share-fallback{bottom:calc(var(--app-bar-height) + var(--app-tab-space) + 10px)}
html[data-app] .local-video-overlay.minimized{bottom:calc(var(--app-bar-height) + var(--app-tab-space) + 8px)}
html[data-app] body.platform-embedded{--messenger-player-clearance:calc(var(--player-bar-height,76px) + var(--app-tab-height))}
html[data-app] body:not(.platform-embedded){--messenger-player-clearance:var(--app-tab-height);--messenger-top-clearance:env(safe-area-inset-top)}
html[data-app] body:not(.platform-embedded) #hubThemeMenu{--menu-bottom-clearance:calc(var(--app-tab-height) + 12px)}
html[data-app] #themeMenu{--menu-bottom-clearance:calc(var(--player-bar-height,76px) + var(--app-tab-space) + 12px)}

/* ---------- Pages outside the player: the same mini player (assets/app-now-playing.js) ----------
   The music keeps playing through Account settings, Privacy, Terms, the video and share pages and a surface loaded
   on its own. This strip is the player's #bar there: the same place on the tab bar, height, edge, shadow, play
   button and per-world gradient (player.html .bar and .ctrls .pp, and the app's .bar above; tests/app-shell.test.mjs
   keeps them equal), so going between the player and one of these pages looks like the page changing under a fixed
   tab bar and mini player. No blur, as the player's bar has none on phones. It rises from behind the tab bar (z 40)
   when music starts later and sinks back when it is gone. */
html[data-app] .evm-app-np{
  position:fixed;z-index:39;left:0;right:0;bottom:var(--app-tab-space);box-sizing:border-box;height:var(--app-bar-height);
  display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:10px;margin:0;
  padding:0 max(12px,env(safe-area-inset-right)) 0 max(12px,env(safe-area-inset-left));
  background:linear-gradient(100deg,color-mix(in srgb,var(--bg2) 92%,var(--a3) 8%),color-mix(in srgb,var(--bg2) 86%,var(--a1) 14%));
  border-top:1px solid color-mix(in srgb,var(--a2) 42%,var(--line));
  box-shadow:0 -8px 30px -16px color-mix(in srgb,var(--a1) 40%,transparent);
  color:var(--fg);font:400 16px/1.25 Inter,system-ui,sans-serif;text-align:left;
  transition:transform .25s cubic-bezier(.2,.9,.2,1),opacity .2s ease,background .55s ease,border-color .55s ease;
}
html[data-app] .evm-app-np[hidden]{display:none}
html[data-app] .evm-app-np.is-leaving{transform:translate3d(0,115%,0);opacity:0;pointer-events:none}
html[data-app] .evm-app-np.is-entering{animation:app-np-rise .42s var(--app-ease-out) backwards}
@keyframes app-np-rise{from{transform:translate3d(0,115%,0);opacity:0}}
/* Privacy and Terms have no tab bar: the strip sits on the home indicator instead. */
html[data-app] body:not(:has(.evm-command-bar)) .evm-app-np{bottom:0;height:calc(var(--app-bar-height) + env(safe-area-inset-bottom));padding-bottom:env(safe-area-inset-bottom)}
@media(min-width:700px){html[data-app] .evm-app-np{padding-inline:max(12px,calc((100vw - 620px)/2))}}
html[data-app] .evm-app-np-open{display:flex;align-items:center;gap:10px;min-width:0;min-height:56px;color:inherit;text-decoration:none;-webkit-tap-highlight-color:transparent}
html[data-app] .evm-app-np-open:focus-visible{outline:2px solid var(--a2);outline-offset:3px;border-radius:9px}
html[data-app] .evm-app-np-art{
  --app-np-h:218;--app-np-h2:286;
  position:relative;flex:none;display:grid;place-items:center;width:46px;height:46px;border-radius:9px;overflow:hidden;background:var(--bg2);
  transition:scale .42s var(--app-ease-spring),box-shadow .3s ease;
}
html[data-app] .evm-app-np-art img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;transition:opacity .24s ease}
html[data-app] .evm-app-np-art img:not(.is-loaded){opacity:0}
html[data-app] .evm-app-np-art img[hidden]{display:none}
/* No artwork, or it failed: the player's placeholder (window.__ph) in small, from the same title hues. */
html[data-app] .evm-app-np-art.is-fallback{
  background:linear-gradient(135deg,hsl(var(--app-np-h) 92% 58%/.34),transparent 38%,hsl(var(--app-np-h2) 94% 58%/.34)),url("edgeverse-orbit-cover-v1-optimized.webp") center/cover,var(--bg2);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.12);
}
html[data-app] .evm-app-np-initials{display:none}
html[data-app] .evm-app-np-art.is-fallback .evm-app-np-initials{
  display:grid;place-items:center;width:27px;height:27px;border:1px solid rgba(255,255,255,.48);border-radius:50%;background:rgba(4,7,20,.48);
  box-shadow:0 0 0 4px rgba(255,255,255,.055);color:#fff;font:800 10.5px/1 var(--display,Inter),system-ui,sans-serif;letter-spacing:-.04em;text-shadow:0 1px 8px #000;
}
/* The player's own #bar tile at the same size: its initials are sized for the full player (up to 11vw), which in a
   46px thumb showed a clipped giant letter. Now both bars draw the same small badge. */
html[data-app] .np .thumb .art-fallback-initials{width:27px;height:27px;aspect-ratio:auto;box-shadow:0 0 0 4px rgba(255,255,255,.055);font-size:10.5px;letter-spacing:-.04em;-webkit-backdrop-filter:none;backdrop-filter:none}
html[data-app][data-theme="cyber"] .evm-app-np-art.is-fallback{filter:saturate(1.24) contrast(1.12)}
html[data-app][data-theme="anime"] .evm-app-np-art.is-fallback{filter:hue-rotate(12deg) saturate(1.08)}
html[data-app][data-theme="girly"] .evm-app-np-art.is-fallback{filter:hue-rotate(285deg) saturate(1.12) brightness(1.05)}
html[data-app][data-theme="miami"] .evm-app-np-art.is-fallback{filter:hue-rotate(308deg) saturate(1.25)}
html[data-app][data-theme="cuban"] .evm-app-np-art.is-fallback{filter:hue-rotate(71deg) saturate(1.12) sepia(.12)}
html[data-app] .evm-app-np-meta{display:grid;gap:2px;flex:1;min-width:0;transition:opacity .2s ease}
html[data-app] .evm-app-np-title{overflow:hidden;color:var(--fg);font-size:.84rem;font-weight:600;line-height:1.25;white-space:nowrap;text-overflow:ellipsis}
html[data-app] .evm-app-np-artist{overflow:hidden;color:var(--muted);font-size:.72rem;line-height:1.25;white-space:nowrap;text-overflow:ellipsis}
html[data-app] .evm-app-np-artist[hidden]{display:none}
html[data-app] .evm-app-np-toggle{
  position:relative;display:grid;place-items:center;width:46px;height:46px;min-width:46px;margin:0 2px 0 0;padding:0;border:0;border-radius:50%;
  background:linear-gradient(135deg,var(--a1),var(--a3));color:var(--on-accent,#080a14);cursor:pointer;
  box-shadow:0 0 calc(20px*var(--app-np-glow,var(--glow,1))) color-mix(in srgb,var(--a1) 55%,transparent);
}
html[data-app] .evm-app-np-toggle svg{display:block;fill:var(--on-accent,#080a14);pointer-events:none}
html[data-app] .evm-app-np-toggle:focus-visible{outline:2px solid var(--a2);outline-offset:3px}
/* Presses: the play button gives the way the player's does; the artwork and words answer a tap on the row. */
html[data-app] .evm-app-np-toggle:active{scale:.9;transition-duration:.08s}
html[data-app] .evm-app-np-open:active .evm-app-np-art{scale:.94;transition-duration:.1s}
html[data-app] .evm-app-np-open:active .evm-app-np-meta{opacity:.7}
/* Loading or buffering: the player's ring round the button and glow round the artwork. */
html[data-app] .evm-app-np.is-buffering .evm-app-np-art{box-shadow:0 0 0 2px color-mix(in srgb,var(--a2) 55%,transparent),0 0 18px -4px var(--a2)}
html[data-app] .evm-app-np.is-buffering .evm-app-np-toggle::after{content:"";position:absolute;inset:-5px;border:2px solid color-mix(in srgb,var(--a2) 28%,transparent);border-top-color:var(--a2);border-radius:50%;animation:app-spin .72s linear infinite;pointer-events:none}
/* The world's own bar: the player's six painted gradients and each world's glow; the four gaming worlds keep the base
   gradient (gaming-worlds.css paints no .bar) and their play-button style and --glow. */
html[data-app][data-theme="cyber"] .evm-app-np{--app-np-glow:.9}
html[data-app][data-theme="anime"] .evm-app-np{--app-np-glow:.85;background:linear-gradient(100deg,rgba(34,25,76,.94),rgba(107,59,157,.78),rgba(242,111,225,.3))}
html[data-app][data-theme="girly"] .evm-app-np{--app-np-glow:.72;background:linear-gradient(100deg,rgba(55,20,55,.95),rgba(124,42,105,.82),rgba(255,71,133,.32))}
html[data-app][data-theme="miami"] .evm-app-np{--app-np-glow:.8;background:linear-gradient(100deg,rgba(42,15,74,.95),rgba(163,38,103,.78),rgba(255,126,95,.35))}
html[data-app][data-theme="cuban"] .evm-app-np{--app-np-glow:.55;background:linear-gradient(100deg,rgba(7,35,45,.97),rgba(12,74,72,.83),rgba(230,57,70,.24))}
html[data-app][data-theme="aurora"] .evm-app-np{--app-np-glow:.68;background:linear-gradient(100deg,rgba(5,34,43,.97),rgba(20,81,86,.77),rgba(139,199,255,.23))}
html[data-app][data-theme="verdant"] .evm-app-np{--app-np-glow:.54;background:linear-gradient(100deg,rgba(7,31,22,.97),rgba(18,75,43,.8),rgba(93,222,125,.22))}
html[data-app]:is([data-theme="fps"],[data-theme="rpg"],[data-theme="moba"],[data-theme="racing"]) .evm-app-np-toggle{color:#101a18;background:linear-gradient(120deg,var(--a1),var(--a2))}
html[data-app]:is([data-theme="fps"],[data-theme="rpg"],[data-theme="moba"],[data-theme="racing"]) .evm-app-np-toggle svg{fill:currentColor}
/* Room for it: the page's end ("Return to your music"), the voice strip, the Hub's messages and world menu, and the
   watch and share world panels all clear the strip while it shows. The player's own rules above (:has(#bar)) do the
   same for #bar. */
html[data-app] body:not(:has(#bar)):has(>.evm-app-np:not([hidden])){padding-bottom:calc(var(--app-bar-height) + var(--app-tab-space) + 12px)}
html[data-app] body:not(:has(#bar)):not(:has(.evm-command-bar)):has(>.evm-app-np:not([hidden])){padding-bottom:calc(var(--app-bar-height) + env(safe-area-inset-bottom) + 16px)}
html[data-app]:has(.evm-app-np:not([hidden])){--evm-player-clearance:calc(var(--app-bar-height) + var(--app-tab-height))}
html[data-app]:has(.evm-app-np:not([hidden])) .voice-strip:not(.voice-strip-overlay){bottom:calc(var(--app-bar-height) + var(--app-tab-space) + 4px)}
html[data-app]:has(.evm-app-np:not([hidden])) body:not(.platform-embedded){--messenger-player-clearance:calc(var(--app-bar-height) + var(--app-tab-height))}
html[data-app]:has(.evm-app-np:not([hidden])) body:not(.platform-embedded) #hubThemeMenu{--menu-bottom-clearance:calc(var(--app-bar-height) + var(--app-tab-height) + 12px)}
@media(max-width:640px){html[data-app]:has(.evm-app-np:not([hidden])) .theme-panel{max-height:calc(100dvh - 24px - env(safe-area-inset-top) - var(--app-tab-space) - var(--app-bar-height))!important}}
@media(max-width:600px){html[data-app]:has(.evm-app-np:not([hidden])) .share-theme-panel{max-height:calc(100dvh - 24px - env(safe-area-inset-top) - var(--app-tab-space) - var(--app-bar-height))}}

/* ---------- Things hidden in the app leave no holes ---------- */
@media(max-width:640px){html[data-app] .catalog-switch{grid-template-columns:repeat(3,minmax(0,1fr))}}

/* ---------- The full player fits the screen ---------- */
@media(max-width:540px){
  html[data-app] #stage.stage{padding-top:calc(62px + env(safe-area-inset-top));padding-bottom:calc(14px + env(safe-area-inset-bottom))}
  html[data-app] #stage .stage-close{top:calc(4px + env(safe-area-inset-top))}
  html[data-app] #stage .stage-world-tools{top:calc(4px + env(safe-area-inset-top))}
  html[data-app] #stage .stage-dl{top:calc(62px + env(safe-area-inset-top))}
}
/* Short phones (the 13 mini is 812pt): a smaller picture and one row of
   feedback icons, so every control is on screen without scrolling. The buttons
   keep their aria-labels. */
@media(max-width:540px) and (max-height:900px){
  html[data-app] #stage.stage{gap:10px}
  html[data-app] #stage .stage-art{height:clamp(170px,calc(100dvh - env(safe-area-inset-top) - env(safe-area-inset-bottom) - 500px),270px)}
  html[data-app] #stage .stage-deck{padding:14px 14px 12px;gap:10px}
  html[data-app] #stage .stage-feedback{gap:6px;padding-top:10px;flex-wrap:nowrap;justify-content:space-between}
  html[data-app] #stage .stage-feedback .feedback-btn{flex:1 1 auto;min-width:36px;padding-inline:6px;justify-content:center}
  html[data-app] #stage .stage-feedback .feedback-btn>span:not(.react-count){display:none}
}
/* The full player is a sheet at every width: it rises from the mini player and a drag down hands it back
   (app-motion.js follows the finger, then the page's own Minimize runs). Closed, it is not painted at all. */
html[data-app] #stage.stage{opacity:1;transform:translate3d(0,100%,0);visibility:hidden;transition:transform .42s var(--app-ease-sheet),visibility 0s linear .42s,background .6s ease}
html[data-app] #stage.stage.show{transform:none;visibility:visible;transition:transform .5s var(--app-ease-sheet),visibility 0s,background .6s ease}
html[data-app] #stage.stage.app-dragging{transition:none}
html[data-app] #stage .app-stage-grabber{position:absolute;z-index:6;top:max(6px,calc(env(safe-area-inset-top) - 13px));left:50%;width:38px;height:5px;margin-left:-19px;border-radius:3px;background:color-mix(in srgb,var(--fg,#fff) 42%,transparent);pointer-events:none}

/* ---------- In-page surfaces: an iOS navigation bar ----------
   "‹ Home" (app-motion.js names the screen Back returns to) and a centred title; a tab's first screen has
   no back button. The content slides in from the right, and from the left on Back; the bar stays still. */
@media(max-width:640px){
  html[data-app] .platform-view-bar{
    box-sizing:border-box;display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);align-items:center;gap:6px;
    min-height:calc(52px + env(safe-area-inset-top));padding-left:max(6px,env(safe-area-inset-left));padding-right:max(6px,env(safe-area-inset-right));padding-bottom:4px;
    border-bottom:.5px solid color-mix(in srgb,var(--line,#354258) 70%,transparent);
  }
  html[data-app] .platform-view-back{
    grid-column:1;justify-self:start;display:inline-flex;align-items:center;gap:0;max-width:100%;min-height:44px;margin:0;padding:0 10px 0 4px;
    border:0;border-radius:10px;background:none;box-shadow:none;color:var(--a2,#7ce6dc);
    font:500 17px/1.1 Inter,system-ui,sans-serif;letter-spacing:-.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  }
  html[data-app] .platform-view-back::before{
    content:"";flex:none;width:13px;height:21px;margin-right:5px;background:currentColor;
    -webkit-mask:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13 21'><path d='M10.8 1.8 2.6 10.5l8.2 8.7' fill='none' stroke='black' stroke-width='2.7' stroke-linecap='round' stroke-linejoin='round'/></svg>") center/contain no-repeat;
    mask:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13 21'><path d='M10.8 1.8 2.6 10.5l8.2 8.7' fill='none' stroke='black' stroke-width='2.7' stroke-linecap='round' stroke-linejoin='round'/></svg>") center/contain no-repeat;
  }
  html[data-app] .platform-view-back:focus-visible{outline:2px solid var(--a2,#7ce6dc);outline-offset:2px}
  html[data-app] .platform-view-bar>span{
    grid-column:2;max-width:56vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fg,#f3f6ff);
    font:700 16px/1.2 var(--display,Inter),system-ui,sans-serif;letter-spacing:0;text-transform:none;text-align:center;
  }
  html[data-app] #platformView.app-tab-root .platform-view-back{visibility:hidden}
}
/* "Opening Spaces…" only on a slow load: invisible and untappable for 0.35 s, then a small pill with a
   ring in the world's accent. Its text, role=status, aria-busy and Cancel are the page's own. */
html[data-app] .platform-navigation-status:not([hidden]){animation:app-late-in .22s var(--app-ease-out) .35s both}
html[data-app] .platform-navigation-status{
  gap:10px;padding:4px 4px 4px 14px;border-radius:999px;border-color:color-mix(in srgb,var(--a2,#7ce6dc) 45%,var(--line,#354258));
  background:color-mix(in srgb,var(--bg2,#0b1428) 94%,transparent);font:600 13px/1.3 Inter,system-ui,sans-serif;
}
html[data-app] .platform-navigation-status>span{display:inline-flex;align-items:center;gap:9px}
html[data-app] .platform-navigation-status>span::before{content:"";flex:none;width:12px;height:12px;box-sizing:border-box;border:2px solid color-mix(in srgb,var(--a2,#7ce6dc) 28%,transparent);border-top-color:var(--a2,#7ce6dc);border-radius:50%;animation:app-spin .8s linear infinite}
html[data-app] .platform-navigation-status button{min-height:44px;min-width:44px;padding:0 14px;border-radius:999px}

/* ---------- Artwork fades in (app-motion.js marks each loaded or failed image) ----------
   The thumb's own fill shows meanwhile, and a failed image is swapped for the placeholder, so nothing
   stays invisible. */
html[data-app].app-img-fade .thumb img{transition:opacity .24s ease}
html[data-app].app-img-fade .thumb img:not(.is-loaded){opacity:0}

/* ---------- One control per job ----------
   The tab bar and the in-page back bar now carry navigation, so the copies the
   website needs (it has neither) go, and the better implementation of each job
   stays (owner, 2026-09-24: "remove duplicates, keep the best implementation").
   Mapped control by control before hiding anything; markup is kept, only hidden,
   because scripts still look some of it up by id. */

/* Player header: My music is the tab (it also sets history, the highlight and the
   playlist deep links), and search is the Search tab (it covers the collection plus
   albums, videos, artists, people and Spaces). The account control has a job of its
   own, the account: signed out it is the "Sign in" pill, signed in the avatar opens
   the Account sheet (identity, sync, Delete account…, Sign out). Home stays the
   dashboard, so it is still one control per job. */
html[data-app] header#appHeader>:is(#collectionBtn,.search){display:none}
/* The phone header is a grid with a column per button: the account sits next to
   the title in a column as wide as it is, so the world and station buttons never
   move when sign-in resolves; only the title gives up width. */
@media(max-width:640px){
  html[data-app] header#appHeader{grid-template-columns:minmax(0,1fr) auto repeat(2,44px)}
  /* Row 1 explicitly: the account comes last in the markup, and grid
     auto-placement never goes back to an earlier column in the same row. */
  html[data-app] header#appHeader>.account-btn{grid-column:2;grid-row:1}
  html[data-app] header#appHeader>.theme-wrap{grid-column:3;grid-row:1}
  html[data-app] header#appHeader>.tune-btn{grid-column:4;grid-row:1}
}
/* Home and the signed-in dashboard: links that are exactly a tab. */
html[data-app] .discover-shortcuts>a[href="spaces.php"],
html[data-app] .dashboard-spaces>:is(#dashboardMusic,a.dashboard-space[href="spaces.php"]){display:none}

/* In-page surfaces: the back bar is the header. Their own brand row and world
   picker go; the world is changed with the player's world button, which also
   saves it to the account and tells the app. Full pages keep theirs. */
html[data-app] body.platform-embedded #hubShell header.platform-header,
html[data-app] body.platform-embedded .artist-shell:has(>#spacesMain)>.artist-top,
html[data-app] body.platform-embedded #platformView .artist-shell:is(.artist-directory,[data-artist-key])>.artist-top{display:none}
/* The Hub: Spaces is a tab, the floating Messages button (with the unread badge)
   opens the same dock as "Your messages", and the footer links are the tab bar. */
html[data-app] #hubShell .hub-nav>a[href="spaces.php"],
html[data-app] #messagesShortcut,
html[data-app] #hubShell footer>nav{display:none}
/* Wide screens only (iPad): the Hub's second way back and its route stack. */
html[data-app] #hubShell .worldbar-status>a[href="player.html"],
html[data-app] #hubShell .hub-route-stack{display:none}
html[data-app] #hubShell .hub-command{grid-template-columns:minmax(0,1fr)}
/* Full-page Artists: Home is the tab; the world pill stays (the page's only one). */
html[data-app] body.evm-navigation-ready:not(.platform-embedded) .artist-shell.artist-directory>.artist-top>.brand{display:none}
html[data-app] body.evm-navigation-ready:not(.platform-embedded) .artist-shell.artist-directory>.artist-top{grid-template-columns:minmax(0,1fr);justify-content:flex-end}
/* Account settings: the tab bar and the page's own links ("Return to your music",
   "your profile", "Privacy Policy") replace the header row. */
html[data-app] .artist-shell:has(>#accountSettingsMain)>.artist-top{display:none}
/* Privacy (no tab bar): the brand already goes home; "Your account" stays. */
html[data-app] .artist-shell:has(>#legalMain)>.artist-top nav{font-size:0}
html[data-app] .artist-shell:has(>#legalMain)>.artist-top nav>a[href="player.html"]{display:none}

/* ---------- Account: sign in and out like an app ----------
   The header's account control waits, invisible, for the first account status, so a
   signed-in launch never flashes "Sign in"; if the server never answers it appears
   after 2.5 s anyway. Signed out it is a filled "Sign in" pill in the world's primary
   style (the same as the sheet's submit button) with corners that follow the world;
   signed in it is the listener's own avatar, which pops once as the sign-in sheet
   uncovers it. The web keeps its own button. */
html[data-app] header#appHeader>#accountBtn{position:relative;overflow:visible}
html[data-app] header#appHeader>#accountBtn::after{content:none}
html[data-app] header#appHeader>#accountBtn[data-state=pending]{visibility:hidden;opacity:0;animation:app-account-late .2s ease 2.5s forwards}
html[data-app] header#appHeader>#accountBtn:not([data-state=pending]){animation:app-account-reveal .3s var(--app-ease-out) backwards}
html[data-app] header#appHeader>#accountBtn:not(.signed-in){
  width:auto;min-width:44px;height:36px;min-height:36px;align-self:center;justify-content:center;gap:6px;padding:0 14px 0 11px;
  border:0;border-radius:var(--chrome-radius,18px);background:var(--accent-grad,linear-gradient(135deg,var(--a1,#8768ef),var(--a2,#7ce6dc)));color:var(--on-accent,#080a14);
  font:800 13px/1 Inter,system-ui,sans-serif;letter-spacing:.005em;
  box-shadow:inset 0 1px color-mix(in srgb,#fff 34%,transparent),0 6px 18px -8px color-mix(in srgb,var(--a1,#8768ef) 70%,transparent);
}
/* A 44 pt target around the 36 pt pill. */
html[data-app] header#appHeader>#accountBtn:not(.signed-in)::before{content:"";position:absolute;inset:-4px -3px}
html[data-app] header#appHeader>#accountBtn:not(.signed-in) #accountLabel{display:inline}
html[data-app] header#appHeader>#accountBtn:not(.signed-in) .account-mini-avatar{display:none}
html[data-app] header#appHeader>#accountBtn:not(.signed-in) .account-dot{
  display:block;flex:none;width:16px;height:16px;border-radius:0;background:currentColor;box-shadow:none;
  -webkit-mask:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 12a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9zm0 2c-4.4 0-8 2.3-8 5.2V21h16v-1.8c0-2.9-3.6-5.2-8-5.2z'/></svg>") center/contain no-repeat;mask:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 12a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9zm0 2c-4.4 0-8 2.3-8 5.2V21h16v-1.8c0-2.9-3.6-5.2-8-5.2z'/></svg>") center/contain no-repeat;
}
html[data-app] header#appHeader>#accountBtn.signed-in{width:44px;min-width:44px;height:44px;padding:0;justify-content:center;border:0;background:none;box-shadow:none}
html[data-app] header#appHeader>#accountBtn.signed-in :is(.account-dot,#accountLabel){display:none}
html[data-app] header#appHeader>#accountBtn.signed-in .account-mini-avatar{
  display:grid;width:34px;border:0;font-size:.74rem;letter-spacing:-.02em;
  box-shadow:0 0 0 2px color-mix(in srgb,var(--bg,#050713) 80%,transparent),0 0 0 3.5px color-mix(in srgb,var(--a2,#7ce6dc) 62%,transparent),0 6px 16px -6px color-mix(in srgb,var(--a2,#7ce6dc) 60%,transparent);
}
html[data-app] header#appHeader>#accountBtn.just-signed-in .account-mini-avatar{animation:app-avatar-in .6s var(--app-ease-spring) both}
@keyframes app-account-late{to{opacity:1;visibility:visible}}
@keyframes app-account-reveal{from{opacity:0;transform:scale(.92)}}
@keyframes app-avatar-in{from{opacity:0;transform:scale(.45)}}
/* The Account sheet. Sign-in is the full-height form sheet (above); your account and
   "Sign out?" are medium sheets, and the page glides between their heights. While the
   keyboard is up the intro lines step aside, so the fields and Submit stay above it. */
@media(max-width:640px){
  html[data-app] .account-overlay:is([data-view=member],[data-view=signout])>.account-panel{min-height:0}
  html[data-app] .account-overlay.app-keyboard :is(.account-eyebrow,.account-copy,.account-help){display:none}
  html[data-app] .account-overlay.app-keyboard .account-panel>h2{margin-top:2px}
  html[data-app] .account-overlay.app-keyboard .account-tabs{margin:12px 0 10px}
}
/* More's first row is the account (avatar and name, or Sign in). */
html[data-app] .evm-command-menu>.evm-menu-account{min-height:60px}
/* Account settings: the words around the website-only linked-devices link go with it. */
html[data-app] .account-devices-link{display:none}

/* ---------- Languages: content on the shared sheet ----------
   The sheet itself (rise, grabber, drag, close) is the one system above. Here: the header and the Clear / Done
   footer stay put while the list between them scrolls, and the footer clears the home indicator. Rows are a grouped
   list, so a press highlights the row instead of shrinking it. Opened over Tune or the Account sheet it stacks the
   way iOS stacks sheets: one scrim, and the sheet underneath steps back and dims until the language sheet goes. */
html[data-app] .language-option:active{scale:none}
html[data-app] .language-chip{position:relative;min-height:38px}
html[data-app] .language-chip::after{content:"";position:absolute;inset:-4px -2px}
@media(max-width:640px){
  html[data-app] .language-panel{padding:0}
  html[data-app] .language-head{padding:26px max(20px,env(safe-area-inset-right)) 2px max(20px,env(safe-area-inset-left))}
  html[data-app] .language-close{top:10px;right:max(10px,env(safe-area-inset-right))}
  html[data-app] .language-body{padding:4px max(16px,env(safe-area-inset-right)) 18px max(16px,env(safe-area-inset-left))}
  html[data-app] .language-bottom{padding:12px max(16px,env(safe-area-inset-right)) calc(12px + env(safe-area-inset-bottom)) max(16px,env(safe-area-inset-left))}
  html[data-app] .language-overlay.language-overlay--stacked::before{background:color-mix(in srgb,var(--bg,#050713) 30%,transparent);-webkit-backdrop-filter:none;backdrop-filter:none}
  html[data-app] :is(.account-panel,.pulse-panel){transform-origin:50% 0;transition:transform .25s var(--app-ease-out),border-color .35s ease,background .55s ease,box-shadow .35s ease,border-radius .55s ease,scale .5s var(--app-ease-sheet),filter .5s var(--app-ease-sheet)}
  /* The rule above ties .app-dragging's specificity and comes later, so the drag override is restated here. */
  html[data-app] :is(.account-panel,.pulse-panel).app-dragging{transition:none}
  html[data-app] :is(.account-overlay,.pulse-overlay).sheet-behind>:is(.account-panel,.pulse-panel){scale:.94;filter:brightness(.7)}
  /* The first-run card's two buttons are full-size targets side by side. */
  html[data-app] .language-nudge-actions button{min-height:44px}
}

/* ---------- Reduce Motion (last, so it wins) ----------
   player.html's own block covers only the player; this covers every app page. Nothing slides or springs,
   but every sheet, More and the full player still open and close, by tap and by drag (app-motion.js
   closes them at once). animation-delay is kept, so the loading pill still waits its 0.35 s. */
/* Voice chat is not part of the app (no microphone access; privacy.php says so): never offer it there. */
html[data-app] :is(#spacesVoice,#partyVoice){display:none!important}
@media (prefers-reduced-motion:reduce){
  html[data-app] *,html[data-app] *::before,html[data-app] *::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important;transition-delay:0s!important}
  html[data-app] .evm-dialog::backdrop{animation:none}
  html[data-app] :where(button,[role=button],.card,.mood-card,.personal-mix,.playlist-tile,.artist-tile,.person-card,.pulse-choice,.dashboard-space,.dashboard-personal>a,.discover-shortcuts>a,.hub-link,.profile-action,.evm-command-menu>a):active{scale:none;opacity:.7}
}
