android: the page ends at the clock — the bars go, the hero keeps the top edge
The bar was anonymous until you scrolled, then faded in carrying the title the page already had in 27px type two centimetres below it. Kyle's rule for what replaces it: the page is cut off where it meets the Android status bar and dissolves there, and — the part that decides the whole shape of this — **backgrounds may pass under the clock, words may not**. A mask on the scroller cannot tell a photograph from a sentence. It takes both. So the artist hero is painted twice: the page's own copy inside the masked scroller, cut away in that band with everything else, and a second copy behind the scroller (heroart.svelte.js), unmasked, showing through exactly where the mask took the first one away. Same image, same box, same focal point, same scale, offset by the same scroll — one picture to the eye, and only the name dissolves. The second copy carries `overflow: hidden` for the same reason .hero does: without it the 1.04 scale renders 268px in a 258px box and five pixels of photograph appear under the hero's foot, below the black the scrim fades to. That also retires an attempt that did not survive contact. The mask first stepped aside for the hero on a scroll-driven `--fade-on`, which meant rebuilding a mask-image on the scroller every frame; measured at 60 Hz median and p90 either way, but it is work for nothing, and the flip that replaced it put the artist's name out of existence in one step. With the photograph protected by being OUTSIDE the mask, neither is needed: the mask is a constant and nothing recomputes it. No page has a bar now. A tab page's ⋯ — which is the app's own menu, the queue and the sleep timer and Settings — moved beside that page's own large title in `.phead`; Search and You grew the row they did not have, Community kept the one it had. A pushed page floats a back chevron and a ⋯ over the page instead, on flat discs with no backdrop blur, because the bar's own note records what three blurred bands did to a 60 Hz scroll on the Pixel 6a and these would have been a fourth. Three numbers now come from one place. `--chrome-d` and `--chrome-gap` are the disc and its margin; `.screen.has-chrome` reserves exactly that much at the top of a pushed page, and `--fade-top` reaches exactly the chrome's foot. They were independent for one build and it showed immediately: the page began underneath its own buttons, so "Transfers" sat behind the back chevron before you had scrolled at all, and the fade stopped 17px short of the discs so they stood on crisp text and a record's floating ⋯ landed on a track row's own ⋯. The walk followed. Nineteen steps reached a page's overflow through `.gbar .iconbtn`, which now matches nothing: the four on a tab page go to `.phead .iconbtn.muted`, the rest to `.fchrome.more`, and the album's back chevron to `.fchrome.back`. The `title` field read the bar's own text, which is a worse source than it looks — it only existed when chrome happened to be drawn. The screen carries its meta as data attributes now and the three probes read those, spelled exactly as `.gb-t`'s textContent was (the title, then the kicker when it differs) so every expectation written against it still holds. Checked on the device rather than argued: no `.gbar` and no `.gb-t` anywhere, `.phead .iconbtn.muted` present on all four tab pages and opening "SoulStream · Queue · Sleep timer · Settings", both `.fchrome` controls present on a record, and the title probe answering "Random Access MemoriesDaft Punk". One thing this leaves, and it is a real cost rather than an oversight: a tab page's ⋯ scrolls away with its header. The bar used to carry it the whole way down, so on Home the sleep timer now wants a scroll back to the top.
This commit is contained in:
1 parent
cdb861fcbd
commit
1f9b30c51f
6 files changed
+140
-76
No files matched your search
@@ -16,6 +16,7 @@
|
|||||||
import Sheet from "./Sheet.svelte";
|
import Sheet from "./Sheet.svelte";
|
||||||
import HalfSheet from "./HalfSheet.svelte";
|
import HalfSheet from "./HalfSheet.svelte";
|
||||||
import Viewer from "./Viewer.svelte";
|
import Viewer from "./Viewer.svelte";
|
||||||
|
import { heroArt } from "./heroart.svelte.js";
|
||||||
import SyncRoom from "./SyncRoom.svelte";
|
import SyncRoom from "./SyncRoom.svelte";
|
||||||
import Home from "./screens/Home.svelte";
|
import Home from "./screens/Home.svelte";
|
||||||
import Search from "./screens/Search.svelte";
|
import Search from "./screens/Search.svelte";
|
||||||
@@ -121,6 +122,10 @@
|
|||||||
reading. A pushed page has one because it carries the back chevron,
|
reading. A pushed page has one because it carries the back chevron,
|
||||||
and it is static — there and titled from the first frame. */
|
and it is static — there and titled from the first frame. */
|
||||||
const showBar = $derived(!root);
|
const showBar = $derived(!root);
|
||||||
|
/* A hero page paints its picture a second time behind the scroller
|
||||||
|
(heroart.svelte.js): the mask cuts the page where it meets the clock,
|
||||||
|
and that copy fills the cut back in — so the photograph passes under
|
||||||
|
the status bar and the name does not. The mask itself never moves. */
|
||||||
const playing = $derived(player.status === "playing" || player.status === "buffering");
|
const playing = $derived(player.status === "playing" || player.status === "buffering");
|
||||||
const played = $derived(player.duration ? Math.min(1, player.position / player.duration) : 0);
|
const played = $derived(player.duration ? Math.min(1, player.position / player.duration) : 0);
|
||||||
|
|
||||||
@@ -334,10 +339,18 @@
|
|||||||
|
|
||||||
<div class="pane">
|
<div class="pane">
|
||||||
<div class="viewport">
|
<div class="viewport">
|
||||||
|
{#if meta.hero && heroArt.src}
|
||||||
|
<div class="hero-behind" aria-hidden="true" style:--hero-shift="{-scrolled}px">
|
||||||
|
<i style:background-image={`url("${heroArt.src}")`}></i>
|
||||||
|
</div>
|
||||||
|
<div class="hero-wash" aria-hidden="true"></div>
|
||||||
|
{/if}
|
||||||
{#key nav.seq}
|
{#key nav.seq}
|
||||||
<div
|
<div
|
||||||
class="screen tr-{nav.tr} stagger"
|
class="screen tr-{nav.tr} stagger"
|
||||||
class:under-bar={!root && !meta.hero && !meta.flush}
|
class:has-chrome={!root && !meta.hero && !meta.flush}
|
||||||
|
data-title={meta.title}
|
||||||
|
data-kicker={meta.kicker && meta.kicker !== meta.title ? meta.kicker : ""}
|
||||||
class:has-hero={meta.hero}
|
class:has-hero={meta.hero}
|
||||||
bind:this={screenEl}
|
bind:this={screenEl}
|
||||||
onscroll={(e) => { scrolled = e.currentTarget.scrollTop; }}
|
onscroll={(e) => { scrolled = e.currentTarget.scrollTop; }}
|
||||||
@@ -383,14 +396,11 @@
|
|||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- the bar a pushed page carries: back, the title, and the page's ⋯.
|
<!-- no bar on any screen. A pushed page floats its two controls over
|
||||||
A tab page has none — see showBar. -->
|
the page instead; the title is the page's own, in 27px type. -->
|
||||||
{#if showBar}
|
{#if showBar}
|
||||||
<div class="gbar">
|
<button class="fchrome back" onclick={back} aria-label="Back"><Icon name="chevron-left" size={24} /></button>
|
||||||
<button class="iconbtn" onclick={back} aria-label="Back"><Icon name="chevron-left" size={24} /></button>
|
<button class="fchrome more" onclick={() => openHalf("more")} aria-label="More"><Icon name="more" size={21} /></button>
|
||||||
<span class="gb-t">{meta.title}{#if meta.kicker && meta.kicker !== meta.title}<small>{meta.kicker}</small>{/if}</span>
|
|
||||||
<button class="iconbtn muted" onclick={() => openHalf("more")} aria-label="More"><Icon name="more" size={21} /></button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/* The artist hero's picture, published for the copy that lives BEHIND the
|
||||||
|
scroller.
|
||||||
|
|
||||||
|
The page meets the Android status bar and is cut off there — but the rule is
|
||||||
|
that backgrounds may pass under the clock and words may not, and a mask on
|
||||||
|
the scroller cannot tell a photograph from a sentence: it takes both. So the
|
||||||
|
hero is painted twice. The page's own copy is inside the masked scroller and
|
||||||
|
is cut away in that band along with everything else; this one sits behind
|
||||||
|
the scroller, unmasked, and shows through exactly where the mask took the
|
||||||
|
first copy away. Same image, same box, same scale, offset by the same
|
||||||
|
scroll — so the two are one picture, and only the name dissolves. */
|
||||||
|
export const heroArt = $state({ src: "" });
|
||||||
@@ -62,6 +62,16 @@
|
|||||||
--inset-left: env(safe-area-inset-left, 0px);
|
--inset-left: env(safe-area-inset-left, 0px);
|
||||||
--inset-right: env(safe-area-inset-right, 0px);
|
--inset-right: env(safe-area-inset-right, 0px);
|
||||||
--top-h: calc(var(--bar-h) + var(--inset-top));
|
--top-h: calc(var(--bar-h) + var(--inset-top));
|
||||||
|
/* The floating chrome a pushed page carries: a 38px disc, 6px below the
|
||||||
|
status bar. Both the reserve at the top of a page and the fade are
|
||||||
|
measured from these, so none of the three can drift apart. */
|
||||||
|
--chrome-d: 38px;
|
||||||
|
--chrome-gap: 6px;
|
||||||
|
/* How far the page takes to disappear where it meets the status bar.
|
||||||
|
It has to reach the FOOT of the chrome: any shallower and the chevron
|
||||||
|
and the ⋯ stand on crisp text, which is how the page's ⋯ came to sit
|
||||||
|
on a track row's own ⋯. Two extra pixels so it finishes just clear. */
|
||||||
|
--fade-top: calc(var(--chrome-gap) + var(--chrome-d) + 2px);
|
||||||
|
|
||||||
/* ── motion: real springs, sampled ────────────────────────────────────
|
/* ── motion: real springs, sampled ────────────────────────────────────
|
||||||
Material 3 Expressive specifies motion as springs; CSS takes a sampled
|
Material 3 Expressive specifies motion as springs; CSS takes a sampled
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||||
import { albumsBy, tracksOf, isFollowed, toggleFollow } from "../lib.svelte.js";
|
import { albumsBy, tracksOf, isFollowed, toggleFollow } from "../lib.svelte.js";
|
||||||
import { art, wantArt } from "../art.svelte.js";
|
import { art, wantArt } from "../art.svelte.js";
|
||||||
|
import { heroArt } from "../heroart.svelte.js";
|
||||||
import { player, playEntries, playAlbum } from "../player.svelte.js";
|
import { player, playEntries, playAlbum } from "../player.svelte.js";
|
||||||
import { push, openHalf } from "../nav.svelte.js";
|
import { push, openHalf } from "../nav.svelte.js";
|
||||||
import { catalog, lookupArtist, artistKey, fans, yearOf, looseTitle, loadExtras, loadPopular, loadHeroSeed, loadOverview, picture } from "../catalog.svelte.js";
|
import { catalog, lookupArtist, artistKey, fans, yearOf, looseTitle, loadExtras, loadPopular, loadHeroSeed, loadOverview, picture } from "../catalog.svelte.js";
|
||||||
@@ -78,6 +79,8 @@
|
|||||||
$effect(() => { const id = artistId; if (id != null) untrack(() => { loadPopular(id); loadHeroSeed(id); loadOverview(id); }); });
|
$effect(() => { const id = artistId; if (id != null) untrack(() => { loadPopular(id); loadHeroSeed(id); loadOverview(id); }); });
|
||||||
const seedSrc = $derived(seedPath ? art[seedPath] : "");
|
const seedSrc = $derived(seedPath ? art[seedPath] : "");
|
||||||
const heroSrc = $derived(heroUrl ? art[heroUrl] : "");
|
const heroSrc = $derived(heroUrl ? art[heroUrl] : "");
|
||||||
|
/* the copy behind the scroller, which is the one allowed under the clock */
|
||||||
|
$effect(() => { heroArt.src = heroSrc || seedSrc || ""; return () => { heroArt.src = ""; }; });
|
||||||
const extras = $derived(artistId != null ? catalog.extras[artistId] ?? null : null);
|
const extras = $derived(artistId != null ? catalog.extras[artistId] ?? null : null);
|
||||||
const socials = $derived(page?.socials?.length ? page.socials : (extras?.socials ?? []));
|
const socials = $derived(page?.socials?.length ? page.socials : (extras?.socials ?? []));
|
||||||
|
|
||||||
|
|||||||
+72
-43
@@ -83,11 +83,17 @@
|
|||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
padding-top: var(--inset-top);
|
padding-top: var(--inset-top);
|
||||||
|
/* The page is cut off at the status bar: it fades out into the ground
|
||||||
|
over --fade-top rather than sliding under the clock. Pinned to the
|
||||||
|
scroller's box, not to its content, so it never moves and costs no
|
||||||
|
layout — and at rest it hides nothing, because padding-top has
|
||||||
|
already put the first pixel below it. */
|
||||||
|
-webkit-mask-image: linear-gradient(to bottom, transparent 0, transparent var(--inset-top), #000 calc(var(--inset-top) + var(--fade-top)), #000 100%);
|
||||||
|
mask-image: linear-gradient(to bottom, transparent 0, transparent var(--inset-top), #000 calc(var(--inset-top) + var(--fade-top)), #000 100%);
|
||||||
}
|
}
|
||||||
.screen::-webkit-scrollbar { width: 0; display: none; }
|
.screen::-webkit-scrollbar { width: 0; display: none; }
|
||||||
/* a pushed screen whose content starts at the very top must clear the bar;
|
/* a pushed screen whose content starts at the very top must clear the bar;
|
||||||
heroes bleed behind it and the record head has its own clearance */
|
heroes bleed behind it and the record head has its own clearance */
|
||||||
.screen.under-bar { padding-top: var(--top-h); }
|
|
||||||
/* the pad that clears the mini player + nav bar */
|
/* the pad that clears the mini player + nav bar */
|
||||||
.endpad { height: calc(var(--nav-h) + var(--inset-bottom) + 24px); }
|
.endpad { height: calc(var(--nav-h) + var(--inset-bottom) + 24px); }
|
||||||
.has-mini .endpad { height: calc(var(--mini-h) + var(--nav-h) + var(--inset-bottom) + 16px); }
|
.has-mini .endpad { height: calc(var(--mini-h) + var(--nav-h) + var(--inset-bottom) + 16px); }
|
||||||
@@ -98,47 +104,28 @@
|
|||||||
and no room for a hierarchy trail, so the trail collapses to a KICKER
|
and no room for a hierarchy trail, so the trail collapses to a KICKER
|
||||||
under the title, and the arrows to one chevron that only appears when
|
under the title, and the arrows to one chevron that only appears when
|
||||||
there is somewhere to go back to. It runs up under the status bar. */
|
there is somewhere to go back to. It runs up under the status bar. */
|
||||||
.gbar {
|
/* The chrome a pushed page carries once the bar is gone: a back chevron and
|
||||||
|
the page's ⋯, floating over the artwork with nothing behind them but their
|
||||||
|
own disc. No backdrop blur — the bar's own note measured what three blurred
|
||||||
|
bands did to a scroll on the Pixel 6a, and these would be a fourth. */
|
||||||
|
.fchrome {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0; left: 0; right: 0;
|
top: calc(var(--inset-top) + var(--chrome-gap));
|
||||||
z-index: 40;
|
z-index: 45;
|
||||||
height: var(--top-h);
|
width: var(--chrome-d);
|
||||||
padding: var(--inset-top) 8px 0 6px;
|
height: var(--chrome-d);
|
||||||
display: flex;
|
padding: 0;
|
||||||
align-items: center;
|
border: 0;
|
||||||
gap: 10px;
|
border-radius: var(--r-pill);
|
||||||
/* SOLID by default on the phone. The desktop's glass — the 28% tint over
|
display: grid;
|
||||||
a 12px backdrop blur — was measured on the Pixel 6a (tests/mobile/
|
place-items: center;
|
||||||
perf.mjs): every backdrop blur re-blurs its band on every frame that
|
color: #fff;
|
||||||
scrolls or animates under it, and the three of them (this bar, the mini
|
background: rgba(0, 0, 0, 0.46);
|
||||||
bar, the nav) turned a 60 Hz scroll into 30 Hz and dropped every other
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08), 0 2px 10px rgba(0, 0, 0, 0.35);
|
||||||
frame of the player's opening. The blur is a switch (You → Appearance →
|
|
||||||
Glass) so the two can be compared; the design was built to stand on
|
|
||||||
the solid floor first. */
|
|
||||||
background: rgba(18, 18, 22, 0.92);
|
|
||||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05);
|
|
||||||
}
|
|
||||||
html[data-glass="blur"] .gbar { background: rgba(20, 20, 24, 0.28); backdrop-filter: blur(12px) saturate(1.7); -webkit-backdrop-filter: blur(12px) saturate(1.7); }
|
|
||||||
html[data-material="solid"] .gbar { background: rgba(24, 24, 24, 0.94); backdrop-filter: none; -webkit-backdrop-filter: none; }
|
|
||||||
.gbar .gb-t {
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 800;
|
|
||||||
letter-spacing: -0.2px;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
min-width: 0;
|
|
||||||
flex: 1;
|
|
||||||
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
|
|
||||||
}
|
|
||||||
.gbar .gb-t small {
|
|
||||||
display: block;
|
|
||||||
font-size: 10.5px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: rgba(255, 255, 255, 0.55);
|
|
||||||
letter-spacing: 0.2px;
|
|
||||||
margin-top: -1px;
|
|
||||||
}
|
}
|
||||||
|
.fchrome:active { background: rgba(0, 0, 0, 0.62); }
|
||||||
|
.fchrome.back { left: calc(10px + var(--inset-left)); }
|
||||||
|
.fchrome.more { right: calc(10px + var(--inset-right)); color: rgba(255, 255, 255, 0.82); }
|
||||||
|
|
||||||
/* the round chrome button — the desktop bar's .arrow at touch size */
|
/* the round chrome button — the desktop bar's .arrow at touch size */
|
||||||
.iconbtn {
|
.iconbtn {
|
||||||
@@ -568,8 +555,8 @@ html[data-material="solid"] .card, html[data-material="solid"] .group { backgrou
|
|||||||
/* ── the artist hero ──────────────────────────────────────────────────────
|
/* ── the artist hero ──────────────────────────────────────────────────────
|
||||||
The desktop hero is a wide banner beside the identity block. A phone has
|
The desktop hero is a wide banner beside the identity block. A phone has
|
||||||
no beside, so the portrait goes full-bleed and the name sits ON it. */
|
no beside, so the portrait goes full-bleed and the name sits ON it. */
|
||||||
.hero { position: relative; height: 300px; display: flex; align-items: flex-end; overflow: hidden; margin-bottom: -6px; }
|
.hero { position: relative; height: 258px; display: flex; align-items: flex-end; overflow: hidden; margin-bottom: -6px; }
|
||||||
.hero-img { position: absolute; inset: 0; background-size: cover; background-position: center 22%; transform: scale(1.04); }
|
.hero-img { position: absolute; inset: 0; background-size: cover; background-position: center 24%; transform: scale(1.04); }
|
||||||
/* the blurred SEED: the picture the library already has, soft and
|
/* the blurred SEED: the picture the library already has, soft and
|
||||||
oversized, until a sharp one exists */
|
oversized, until a sharp one exists */
|
||||||
.hero-img.seed { filter: blur(16px) saturate(1.15); transform: scale(1.12); }
|
.hero-img.seed { filter: blur(16px) saturate(1.15); transform: scale(1.12); }
|
||||||
@@ -579,6 +566,43 @@ html[data-material="solid"] .card, html[data-material="solid"] .group { backgrou
|
|||||||
from { opacity: 0; filter: blur(16px) saturate(1.15); transform: scale(1.12); }
|
from { opacity: 0; filter: blur(16px) saturate(1.15); transform: scale(1.12); }
|
||||||
to { opacity: 1; filter: blur(0) saturate(1); transform: scale(1.04); }
|
to { opacity: 1; filter: blur(0) saturate(1); transform: scale(1.04); }
|
||||||
}
|
}
|
||||||
|
/* The hero's picture again, behind the scroller and outside its mask, so the
|
||||||
|
photograph is allowed under the clock while the words are not. Its geometry
|
||||||
|
has to match .hero/.hero-img exactly — 258px tall, the same 24% focal point,
|
||||||
|
the same 1.04 scale — or the seam shows. */
|
||||||
|
.hero-behind {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 258px;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
/* the clip .hero gives its own picture; without it the 1.04 below renders
|
||||||
|
268px in a 258px box and five pixels of photograph appear under the
|
||||||
|
hero's foot, below the black the scrim fades to */
|
||||||
|
overflow: hidden;
|
||||||
|
transform: translateY(var(--hero-shift, 0px));
|
||||||
|
}
|
||||||
|
.hero-behind i {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: block;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center 24%;
|
||||||
|
transform: scale(1.04);
|
||||||
|
}
|
||||||
|
/* the clock's own backing, pinned where the clock is rather than to the photo */
|
||||||
|
.hero-wash {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
height: calc(var(--inset-top) + 26px);
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
|
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.47), transparent);
|
||||||
|
}
|
||||||
.hero-scrim {
|
.hero-scrim {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@@ -1207,6 +1231,12 @@ html[data-material="solid"] .peer { background: var(--bg-elevated); box-shadow:
|
|||||||
.countline { display: flex; align-items: center; justify-content: space-between; padding: 0 var(--pad) 10px; }
|
.countline { display: flex; align-items: center; justify-content: space-between; padding: 0 var(--pad) 10px; }
|
||||||
/* a hero bleeds up under the status bar; the scrim keeps the chrome legible */
|
/* a hero bleeds up under the status bar; the scrim keeps the chrome legible */
|
||||||
.screen.has-hero { padding-top: 0; }
|
.screen.has-hero { padding-top: 0; }
|
||||||
|
/* A pushed page starts BELOW the floating chevron and ⋯, or its first line is
|
||||||
|
under a button — which is what the bar's own padding used to prevent. Sized
|
||||||
|
to the chrome that is there now: the inset, its 6px of top margin, the 38px
|
||||||
|
disc, and 8px of air under it. A hero page is the exception and keeps none,
|
||||||
|
because its photograph is meant to be behind them. */
|
||||||
|
.screen.has-chrome { padding-top: calc(var(--inset-top) + var(--chrome-gap) + var(--chrome-d) + 8px); }
|
||||||
/* not a stray: `.lane` is width: 100%, and the mini bar's lane is ALSO
|
/* not a stray: `.lane` is width: 100%, and the mini bar's lane is ALSO
|
||||||
positioned with both left: 16px and right: 16px — an over-constrained
|
positioned with both left: 16px and right: 16px — an over-constrained
|
||||||
absolute box drops `right`, so without this it comes out 32px wider than
|
absolute box drops `right`, so without this it comes out 32px wider than
|
||||||
@@ -1434,7 +1464,6 @@ html[data-motion="off"] *, html[data-motion="off"] *::before, html[data-motion="
|
|||||||
left/right and .toast's left/right to win at equal specificity. */
|
left/right and .toast's left/right to win at equal specificity. */
|
||||||
|
|
||||||
/* the side insets: the cutout and the gesture pill, from the edges */
|
/* the side insets: the cutout and the gesture pill, from the edges */
|
||||||
.gbar { padding-left: calc(6px + var(--inset-left)); padding-right: calc(8px + var(--inset-right)); }
|
|
||||||
.nav { padding-left: var(--inset-left); padding-right: var(--inset-right); }
|
.nav { padding-left: var(--inset-left); padding-right: var(--inset-right); }
|
||||||
.mini { left: calc(8px + var(--inset-left)); right: calc(8px + var(--inset-right)); }
|
.mini { left: calc(8px + var(--inset-left)); right: calc(8px + var(--inset-right)); }
|
||||||
.screen { padding-left: var(--inset-left); padding-right: var(--inset-right); }
|
.screen { padding-left: var(--inset-left); padding-right: var(--inset-right); }
|
||||||
|
|||||||
+25
-25
@@ -169,7 +169,7 @@ const PROBE = `(function () {
|
|||||||
function inside(b) { return !!b && b.height > 0 && b.top >= -1 && b.bottom <= innerHeight + 1 && b.left >= -1 && b.right <= innerWidth + 1; }
|
function inside(b) { return !!b && b.height > 0 && b.top >= -1 && b.bottom <= innerHeight + 1 && b.left >= -1 && b.right <= innerWidth + 1; }
|
||||||
return {
|
return {
|
||||||
screen: e ? e.className : '',
|
screen: e ? e.className : '',
|
||||||
title: (document.querySelector('.gb-t') || {}).textContent || '',
|
title: (function () { var q = document.querySelector('.screen'); return q ? (q.dataset.title || '') + (q.dataset.kicker || '') : ''; })(),
|
||||||
mini: !!document.querySelector('.mini'),
|
mini: !!document.querySelector('.mini'),
|
||||||
sheet: (document.querySelector('.sheet') || {}).className || '',
|
sheet: (document.querySelector('.sheet') || {}).className || '',
|
||||||
half: (document.querySelector('.halfsheet') || {}).className || '',
|
half: (document.querySelector('.halfsheet') || {}).className || '',
|
||||||
@@ -465,7 +465,7 @@ const PROBE = `(function () {
|
|||||||
actsIn: inside(box('.screen .hero-acts') || box('.screen .rec-acts')),
|
actsIn: inside(box('.screen .hero-acts') || box('.screen .rec-acts')),
|
||||||
recCols: (function () { var c = box('.screen .rec-cover'), m = box('.screen .rec-meta'); return c && m ? (c.bottom <= m.top + 1 ? 'stacked' : 'beside') : ''; })(),
|
recCols: (function () { var c = box('.screen .rec-cover'), m = box('.screen .rec-meta'); return c && m ? (c.bottom <= m.top + 1 ? 'stacked' : 'beside') : ''; })(),
|
||||||
/* the glass bar above plus the floating bars below, as a share of the screen */
|
/* the glass bar above plus the floating bars below, as a share of the screen */
|
||||||
chromePct: (function () { var g = box('.gbar'), low = box('.mini') || box('.nav'); return Math.round((((g ? g.height : 0) + (low ? innerHeight - low.top : 0)) / innerHeight) * 100); })(),
|
chromePct: (function () { var g = box('.fchrome.back'), low = box('.mini') || box('.nav'); return Math.round((((g ? g.height : 0) + (low ? innerHeight - low.top : 0)) / innerHeight) * 100); })(),
|
||||||
miniLeft: Math.round((box('.mini') || { left: -1 }).left),
|
miniLeft: Math.round((box('.mini') || { left: -1 }).left),
|
||||||
/* ── the long session: what the app is still HOLDING ─────────────
|
/* ── the long session: what the app is still HOLDING ─────────────
|
||||||
Minutes is all a walk has ever been. These are the numbers that
|
Minutes is all a walk has ever been. These are the numbers that
|
||||||
@@ -707,9 +707,9 @@ const MOCK_STEPS = [
|
|||||||
{ do: `history.back()`, wait: 900 },
|
{ do: `history.back()`, wait: 900 },
|
||||||
/* the artist's photo: the page's ⋯ → a picture from this phone becomes
|
/* the artist's photo: the page's ⋯ → a picture from this phone becomes
|
||||||
the hero, the page is asked again, then the catalog's own come back */
|
the hero, the page is asked again, then the catalog's own come back */
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, name: "artist-more", wait: 700 },
|
{ do: `document.querySelector('.fchrome.more').click()`, name: "artist-more", wait: 700 },
|
||||||
{ do: `__click('.halfsheet .row', 'Choose hero photo')`, name: "artist-hero-set", wait: 1400 },
|
{ do: `__click('.halfsheet .row', 'Choose hero photo')`, name: "artist-hero-set", wait: 1400 },
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, name: "artist-more-custom", wait: 800 },
|
{ do: `document.querySelector('.fchrome.more').click()`, name: "artist-more-custom", wait: 800 },
|
||||||
{ do: `__click('.halfsheet .row', 'Remove custom hero')`, name: "artist-hero-cleared", wait: 1400 },
|
{ do: `__click('.halfsheet .row', 'Remove custom hero')`, name: "artist-hero-cleared", wait: 1400 },
|
||||||
/* an artist whose exhaustive page the mock keeps for four seconds: what
|
/* an artist whose exhaustive page the mock keeps for four seconds: what
|
||||||
is on screen a second and a half in can only be the two fast lanes */
|
is on screen a second and a half in can only be the two fast lanes */
|
||||||
@@ -769,7 +769,7 @@ const MOCK_STEPS = [
|
|||||||
/* the two guest rows the record's own anchor had no count for */
|
/* the two guest rows the record's own anchor had no count for */
|
||||||
{ name: "cat-album-listeners", wait: 900 },
|
{ name: "cat-album-listeners", wait: 900 },
|
||||||
/* the record page's own sheet from the bar */
|
/* the record page's own sheet from the bar */
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, name: "cat-album-sheet", wait: 700 },
|
{ do: `document.querySelector('.fchrome.more').click()`, name: "cat-album-sheet", wait: 700 },
|
||||||
{ do: `document.querySelector('.scrim').click()`, wait: 500 },
|
{ do: `document.querySelector('.scrim').click()`, wait: 500 },
|
||||||
{ do: `document.querySelectorAll('.tracks .trk')[1].click()`, name: "cat-album-row", wait: 5500 },
|
{ do: `document.querySelectorAll('.tracks .trk')[1].click()`, name: "cat-album-row", wait: 5500 },
|
||||||
{ do: `__ss.next()`, name: "cat-album-next", wait: 7000 },
|
{ do: `__ss.next()`, name: "cat-album-next", wait: 7000 },
|
||||||
@@ -833,10 +833,10 @@ const MOCK_STEPS = [
|
|||||||
{ do: `(function () { __ssMock.fail('playlist_set_order', ''); __ss.player.error = ''; return true; })()`, wait: 400 },
|
{ do: `(function () { __ssMock.fail('playlist_set_order', ''); __ss.player.error = ''; return true; })()`, wait: 400 },
|
||||||
/* the bulk actions from the page's sheet: Add to library files the
|
/* the bulk actions from the page's sheet: Add to library files the
|
||||||
streamed row (its cache is complete); Download all finds it cached too */
|
streamed row (its cache is complete); Download all finds it cached too */
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 600 },
|
{ do: `document.querySelector('.fchrome.more').click()`, wait: 600 },
|
||||||
{ do: `__click('.halfsheet .row', 'Add to library')`, name: "playlist-bulk-cached", wait: 1500 },
|
{ do: `__click('.halfsheet .row', 'Add to library')`, name: "playlist-bulk-cached", wait: 1500 },
|
||||||
{ do: `__click('.bulkline .bulkx', null)`, wait: 400 },
|
{ do: `__click('.bulkline .bulkx', null)`, wait: 400 },
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 600 },
|
{ do: `document.querySelector('.fchrome.more').click()`, wait: 600 },
|
||||||
{ do: `__click('.halfsheet .row', 'Download all tracks')`, name: "playlist-bulk-download", wait: 1500 },
|
{ do: `__click('.halfsheet .row', 'Download all tracks')`, name: "playlist-bulk-download", wait: 1500 },
|
||||||
{ do: `__click('.bulkline .bulkx', null)`, wait: 400 },
|
{ do: `__click('.bulkline .bulkx', null)`, wait: 400 },
|
||||||
/* the repair: the unknown row finds a library track and takes its identity */
|
/* the repair: the unknown row finds a library track and takes its identity */
|
||||||
@@ -844,14 +844,14 @@ const MOCK_STEPS = [
|
|||||||
{ do: `__click('.halfsheet .row', 'Find a local match')`, name: "playlist-match", wait: 500 },
|
{ do: `__click('.halfsheet .row', 'Find a local match')`, name: "playlist-match", wait: 500 },
|
||||||
{ do: `(function () { var i = document.querySelector('.plmatch input'); i.value = 'caribou track 4'; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, name: "playlist-match-results", wait: 700 },
|
{ do: `(function () { var i = document.querySelector('.plmatch input'); i.value = 'caribou track 4'; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, name: "playlist-match-results", wait: 700 },
|
||||||
{ do: `__click('.halfsheet .sheet-rows .row', 'Track 4')`, name: "playlist-matched", wait: 1000 },
|
{ do: `__click('.halfsheet .sheet-rows .row', 'Track 4')`, name: "playlist-matched", wait: 1000 },
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, name: "playlist-more", wait: 700 },
|
{ do: `document.querySelector('.fchrome.more').click()`, name: "playlist-more", wait: 700 },
|
||||||
{ do: `__click('.halfsheet .row', 'Unpin')`, name: "playlist-unpinned", wait: 900 },
|
{ do: `__click('.halfsheet .row', 'Unpin')`, name: "playlist-unpinned", wait: 900 },
|
||||||
/* the cover: the custom one removed (the mosaic), then a photo chosen */
|
/* the cover: the custom one removed (the mosaic), then a photo chosen */
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 600 },
|
{ do: `document.querySelector('.fchrome.more').click()`, wait: 600 },
|
||||||
{ do: `__click('.halfsheet .row', 'Remove custom cover')`, name: "cover-removed", wait: 1000 },
|
{ do: `__click('.halfsheet .row', 'Remove custom cover')`, name: "cover-removed", wait: 1000 },
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 600 },
|
{ do: `document.querySelector('.fchrome.more').click()`, wait: 600 },
|
||||||
{ do: `__click('.halfsheet .row', 'Choose cover photo')`, name: "cover-set", wait: 1000 },
|
{ do: `__click('.halfsheet .row', 'Choose cover photo')`, name: "cover-set", wait: 1000 },
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 600 },
|
{ do: `document.querySelector('.fchrome.more').click()`, wait: 600 },
|
||||||
{ do: `__click('.halfsheet .row', 'Rename')`, name: "playlist-rename", wait: 700 },
|
{ do: `__click('.halfsheet .row', 'Rename')`, name: "playlist-rename", wait: 700 },
|
||||||
{ do: `(function () { var i = document.querySelector('.halfsheet .prompt .field'); i.value = 'Late night drive II'; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, wait: 300 },
|
{ do: `(function () { var i = document.querySelector('.halfsheet .prompt .field'); i.value = 'Late night drive II'; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, wait: 300 },
|
||||||
{ do: `__click('.halfsheet .prompt-acts .btn-fill', 'Rename')`, name: "playlist-renamed", wait: 1000 },
|
{ do: `__click('.halfsheet .prompt-acts .btn-fill', 'Rename')`, name: "playlist-renamed", wait: 1000 },
|
||||||
@@ -865,7 +865,7 @@ const MOCK_STEPS = [
|
|||||||
{ do: `__click('.halfsheet .row', 'New Playlist')`, name: "playlist-added", wait: 1200 },
|
{ do: `__click('.halfsheet .row', 'New Playlist')`, name: "playlist-added", wait: 1200 },
|
||||||
{ do: `__click('.segtabs button', 'Playlists')`, wait: 700 },
|
{ do: `__click('.segtabs button', 'Playlists')`, wait: 700 },
|
||||||
{ do: `__click('.plgrid .release', 'New Playlist')`, name: "playlist-filled", wait: 1200 },
|
{ do: `__click('.plgrid .release', 'New Playlist')`, name: "playlist-filled", wait: 1200 },
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 600 },
|
{ do: `document.querySelector('.fchrome.more').click()`, wait: 600 },
|
||||||
{ do: `__click('.halfsheet .row', 'Delete playlist')`, name: "playlist-delete-confirm", wait: 700 },
|
{ do: `__click('.halfsheet .row', 'Delete playlist')`, name: "playlist-delete-confirm", wait: 700 },
|
||||||
{ do: `__click('.halfsheet .prompt-acts .btn-fill', 'Delete')`, name: "playlist-deleted", wait: 1200 },
|
{ do: `__click('.halfsheet .prompt-acts .btn-fill', 'Delete')`, name: "playlist-deleted", wait: 1200 },
|
||||||
/* the import doors: a pasted tracklist reviewed and created with the
|
/* the import doors: a pasted tracklist reviewed and created with the
|
||||||
@@ -1209,7 +1209,7 @@ const MOCK_STEPS = [
|
|||||||
{ do: `document.querySelectorAll('.nav button')[2].click()`, wait: 800 },
|
{ do: `document.querySelectorAll('.nav button')[2].click()`, wait: 800 },
|
||||||
{ do: `__click('.segtabs button', 'Playlists')`, wait: 800 },
|
{ do: `__click('.segtabs button', 'Playlists')`, wait: 800 },
|
||||||
{ do: `__click('.plgrid .release', 'Late night drive')`, name: "export-open", wait: 1200 },
|
{ do: `__click('.plgrid .release', 'Late night drive')`, name: "export-open", wait: 1200 },
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 700 },
|
{ do: `document.querySelector('.fchrome.more').click()`, wait: 700 },
|
||||||
{ do: `__click('.halfsheet .row', 'Export')`, name: "export-sheet", wait: 800 },
|
{ do: `__click('.halfsheet .row', 'Export')`, name: "export-sheet", wait: 800 },
|
||||||
{ do: `__click('.halfsheet .row', 'CSV')`, name: "export-written", wait: 1000 },
|
{ do: `__click('.halfsheet .row', 'CSV')`, name: "export-written", wait: 1000 },
|
||||||
/* a sheet dismissed: the command answers null and the phone says
|
/* a sheet dismissed: the command answers null and the phone says
|
||||||
@@ -1218,13 +1218,13 @@ const MOCK_STEPS = [
|
|||||||
than any wait here, and a wait that had to outlast it would be the
|
than any wait here, and a wait that had to outlast it would be the
|
||||||
fragile kind. */
|
fragile kind. */
|
||||||
{ do: `__ssMock.answer('playlist_export', null); __ss.notice.text = ''; true`, wait: 300 },
|
{ do: `__ssMock.answer('playlist_export', null); __ss.notice.text = ''; true`, wait: 300 },
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 700 },
|
{ do: `document.querySelector('.fchrome.more').click()`, wait: 700 },
|
||||||
{ do: `__click('.halfsheet .row', 'Export')`, wait: 700 },
|
{ do: `__click('.halfsheet .row', 'Export')`, wait: 700 },
|
||||||
{ do: `__click('.halfsheet .row', 'M3U8')`, name: "export-cancelled", wait: 1000 },
|
{ do: `__click('.halfsheet .row', 'M3U8')`, name: "export-cancelled", wait: 1000 },
|
||||||
/* and a refusal — what Android answers when the document is somewhere
|
/* and a refusal — what Android answers when the document is somewhere
|
||||||
the app cannot write — read back as a sentence rather than as Rust */
|
the app cannot write — read back as a sentence rather than as Rust */
|
||||||
{ do: `__ssMock.forget('playlist_export'); __ssMock.fail('playlist_export', 'permission denied (os error 13)'); true`, wait: 300 },
|
{ do: `__ssMock.forget('playlist_export'); __ssMock.fail('playlist_export', 'permission denied (os error 13)'); true`, wait: 300 },
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 700 },
|
{ do: `document.querySelector('.fchrome.more').click()`, wait: 700 },
|
||||||
{ do: `__click('.halfsheet .row', 'Export')`, wait: 700 },
|
{ do: `__click('.halfsheet .row', 'Export')`, wait: 700 },
|
||||||
{ do: `__click('.halfsheet .row', 'Backup (JSON)')`, name: "export-refused", wait: 1200 },
|
{ do: `__click('.halfsheet .row', 'Backup (JSON)')`, name: "export-refused", wait: 1200 },
|
||||||
{ do: `__ssMock.clear(); __ss.player.error = ''; __ss.lib.error = ''; history.back(); true`, wait: 900 },
|
{ do: `__ssMock.clear(); __ss.player.error = ''; __ss.lib.error = ''; history.back(); true`, wait: 900 },
|
||||||
@@ -1821,7 +1821,7 @@ const PHONE_STEPS = [
|
|||||||
{ do: `document.querySelectorAll('.segtabs button')[0].click()`, wait: 400 },
|
{ do: `document.querySelectorAll('.segtabs button')[0].click()`, wait: 400 },
|
||||||
{ do: `__click('.grid2 .release', ${albumText})`, name: "album", wait: 1400 },
|
{ do: `__click('.grid2 .release', ${albumText})`, name: "album", wait: 1400 },
|
||||||
{ do: `document.querySelector('.rec-acts .disc').click()`, name: "album-playing", wait: 3200 },
|
{ do: `document.querySelector('.rec-acts .disc').click()`, name: "album-playing", wait: 3200 },
|
||||||
{ do: `document.querySelector('.gbar .iconbtn').click()`, name: "library-ambience", wait: 1400 },
|
{ do: `document.querySelector('.fchrome.back').click()`, name: "library-ambience", wait: 1400 },
|
||||||
{ do: `document.querySelectorAll('.nav button')[0].click()`, name: "home-ambience", wait: 1200 },
|
{ do: `document.querySelectorAll('.nav button')[0].click()`, name: "home-ambience", wait: 1200 },
|
||||||
{ do: `document.querySelector('.mini').click()`, name: "sheet", wait: 1400 },
|
{ do: `document.querySelector('.mini').click()`, name: "sheet", wait: 1400 },
|
||||||
{ do: `document.querySelector('.np-art').click()`, name: "viewer", wait: 1200 },
|
{ do: `document.querySelector('.np-art').click()`, name: "viewer", wait: 1200 },
|
||||||
@@ -1910,11 +1910,11 @@ const PHONE_STEPS = [
|
|||||||
cancel path is what gets walked, and NOTHING is written to this
|
cancel path is what gets walked, and NOTHING is written to this
|
||||||
phone's storage. The playlist is the "Walk test" one the walk itself
|
phone's storage. The playlist is the "Walk test" one the walk itself
|
||||||
makes, and deletes three steps below. */
|
makes, and deletes three steps below. */
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 800 },
|
{ do: `document.querySelector('.fchrome.more').click()`, wait: 800 },
|
||||||
{ do: `__click('.halfsheet .row', 'Export')`, name: "export-door", wait: 800 },
|
{ do: `__click('.halfsheet .row', 'Export')`, name: "export-door", wait: 800 },
|
||||||
{ do: `__click('.halfsheet .row', 'M3U8')`, name: "export-picker", wait: 2600 },
|
{ do: `__click('.halfsheet .row', 'M3U8')`, name: "export-picker", wait: 2600 },
|
||||||
{ adb: ["shell", "input", "keyevent", "4"], name: "export-dismissed", wait: 2000 },
|
{ adb: ["shell", "input", "keyevent", "4"], name: "export-dismissed", wait: 2000 },
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, name: "playlist-more", wait: 800 },
|
{ do: `document.querySelector('.fchrome.more').click()`, name: "playlist-more", wait: 800 },
|
||||||
{ do: `__click('.halfsheet .row', 'Delete playlist')`, wait: 700 },
|
{ do: `__click('.halfsheet .row', 'Delete playlist')`, wait: 700 },
|
||||||
{ do: `__click('.halfsheet .prompt-acts .btn-fill', 'Delete')`, name: "playlist-deleted", wait: 1500 },
|
{ do: `__click('.halfsheet .prompt-acts .btn-fill', 'Delete')`, name: "playlist-deleted", wait: 1500 },
|
||||||
/* the paste door on the real core: two lines that resolve to a record
|
/* the paste door on the real core: two lines that resolve to a record
|
||||||
@@ -1937,7 +1937,7 @@ const PHONE_STEPS = [
|
|||||||
{ do: `__click('.halfsheet .row', 'Find a local match')`, wait: 600 },
|
{ do: `__click('.halfsheet .row', 'Find a local match')`, wait: 600 },
|
||||||
{ do: `(function () { var i = document.querySelector('.plmatch input'); i.value = 'aerodynamic'; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, name: "import-match-results", wait: 1200 },
|
{ do: `(function () { var i = document.querySelector('.plmatch input'); i.value = 'aerodynamic'; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, name: "import-match-results", wait: 1200 },
|
||||||
{ do: `__click('.halfsheet .sheet-rows .row', 'Aerodynamic')`, name: "import-matched", wait: 1500 },
|
{ do: `__click('.halfsheet .sheet-rows .row', 'Aerodynamic')`, name: "import-matched", wait: 1500 },
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 800 },
|
{ do: `document.querySelector('.fchrome.more').click()`, wait: 800 },
|
||||||
{ do: `__click('.halfsheet .row', 'Delete playlist')`, wait: 700 },
|
{ do: `__click('.halfsheet .row', 'Delete playlist')`, wait: 700 },
|
||||||
{ do: `__click('.halfsheet .prompt-acts .btn-fill', 'Delete')`, name: "import-deleted", wait: 1500 },
|
{ do: `__click('.halfsheet .prompt-acts .btn-fill', 'Delete')`, name: "import-deleted", wait: 1500 },
|
||||||
/* the pasted-links door on the real core: one track link, and the wait
|
/* the pasted-links door on the real core: one track link, and the wait
|
||||||
@@ -2009,7 +2009,7 @@ const PHONE_STEPS = [
|
|||||||
{ name: "artist", wait: 4500 },
|
{ name: "artist", wait: 4500 },
|
||||||
/* the page's ⋯ on the real core: the hero door is there and knows the
|
/* the page's ⋯ on the real core: the hero door is there and knows the
|
||||||
artist's id — it opens the system picker, so the walk only looks */
|
artist's id — it opens the system picker, so the walk only looks */
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, name: "artist-more", wait: 900 },
|
{ do: `document.querySelector('.fchrome.more').click()`, name: "artist-more", wait: 900 },
|
||||||
{ do: `document.querySelector('.scrim').click()`, wait: 600 },
|
{ do: `document.querySelector('.scrim').click()`, wait: 600 },
|
||||||
/* a discography card the library does NOT hold opens the catalog record */
|
/* a discography card the library does NOT hold opens the catalog record */
|
||||||
{ do: `(function () { var c = [].slice.call(document.querySelectorAll('.grid2 .release')); if (!c.length) throw new Error('no discography grid'); var pick = c.find(function (x) { return x.textContent.indexOf('in your library') < 0; }) || c[c.length - 1]; pick.click(); })()`, name: "catalog-album", wait: 3500 },
|
{ do: `(function () { var c = [].slice.call(document.querySelectorAll('.grid2 .release')); if (!c.length) throw new Error('no discography grid'); var pick = c.find(function (x) { return x.textContent.indexOf('in your library') < 0; }) || c[c.length - 1]; pick.click(); })()`, name: "catalog-album", wait: 3500 },
|
||||||
@@ -2040,7 +2040,7 @@ const PHONE_STEPS = [
|
|||||||
{ do: `__click('.screen button.row', null)`, wait: 500 },
|
{ do: `__click('.screen button.row', null)`, wait: 500 },
|
||||||
{ wait: 2500 },
|
{ wait: 2500 },
|
||||||
{ do: `__click('.sec .all', 'All')`, name: "dg-page", wait: 1600 },
|
{ do: `__click('.sec .all', 'All')`, name: "dg-page", wait: 1600 },
|
||||||
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, name: "dg-sheet", wait: 1000 },
|
{ do: `document.querySelector('.fchrome.more').click()`, name: "dg-sheet", wait: 1000 },
|
||||||
{ do: `__click('.halfsheet .chip', 'A–Z')`, name: "dg-az", wait: 1000 },
|
{ do: `__click('.halfsheet .chip', 'A–Z')`, name: "dg-az", wait: 1000 },
|
||||||
{ do: `document.querySelector('.scrim').click()`, wait: 700 },
|
{ do: `document.querySelector('.scrim').click()`, wait: 700 },
|
||||||
{ do: `history.back()`, wait: 800 },
|
{ do: `history.back()`, wait: 800 },
|
||||||
@@ -3043,7 +3043,7 @@ async function main() {
|
|||||||
/* the chrome that is actually on screen: a mini bar under a raised
|
/* the chrome that is actually on screen: a mini bar under a raised
|
||||||
sheet and a nav under one are translated away on purpose */
|
sheet and a nav under one are translated away on purpose */
|
||||||
var out = [];
|
var out = [];
|
||||||
['.gbar', '.screen', '.mini:not(.hidden)', '.nav:not(.gone)'].forEach(function (sel) {
|
['.fchrome.back', '.screen', '.mini:not(.hidden)', '.nav:not(.gone)'].forEach(function (sel) {
|
||||||
var b = box(sel);
|
var b = box(sel);
|
||||||
if (b && b.height > 0 && !inside(b)) out.push(sel);
|
if (b && b.height > 0 && !inside(b)) out.push(sel);
|
||||||
});
|
});
|
||||||
@@ -3076,7 +3076,7 @@ async function main() {
|
|||||||
and a check of the left alone would pass by the inset being 0 */
|
and a check of the left alone would pass by the inset being 0 */
|
||||||
miniLeft: Math.round((box('.mini') || { left: -1 }).left),
|
miniLeft: Math.round((box('.mini') || { left: -1 }).left),
|
||||||
miniRight: Math.round(innerWidth - (box('.mini') || { right: innerWidth + 1 }).right),
|
miniRight: Math.round(innerWidth - (box('.mini') || { right: innerWidth + 1 }).right),
|
||||||
chromePct: (function () { var g = box('.gbar'), low = box('.mini') || box('.nav'); return Math.round((((g ? g.height : 0) + (low ? innerHeight - low.top : 0)) / innerHeight) * 100); })(),
|
chromePct: (function () { var g = box('.fchrome.back'), low = box('.mini') || box('.nav'); return Math.round((((g ? g.height : 0) + (low ? innerHeight - low.top : 0)) / innerHeight) * 100); })(),
|
||||||
outside: out.join('|'),
|
outside: out.join('|'),
|
||||||
sheet: !!document.querySelector('.sheet.up'),
|
sheet: !!document.querySelector('.sheet.up'),
|
||||||
npOut: [].filter.call(document.querySelectorAll('.sheet.up .np-frame, .sheet.up .np-meta, .sheet.up .np-scrub, .sheet.up .np-transport, .sheet.up .np-foot'), function (x) { var b = x.getBoundingClientRect(); return b.height > 0 && !inside(b); }).length,
|
npOut: [].filter.call(document.querySelectorAll('.sheet.up .np-frame, .sheet.up .np-meta, .sheet.up .np-scrub, .sheet.up .np-transport, .sheet.up .np-foot'), function (x) { var b = x.getBoundingClientRect(); return b.height > 0 && !inside(b); }).length,
|
||||||
@@ -3322,7 +3322,7 @@ async function main() {
|
|||||||
|
|
||||||
if (resOk) {
|
if (resOk) {
|
||||||
/* the hero's page: an artist's (tabs, Popular) or a record's */
|
/* the hero's page: an artist's (tabs, Popular) or a record's */
|
||||||
const HERO = `(function () { var e = document.querySelector('.screen'); return { screen: e ? e.className : '', title: (document.querySelector('.gb-t') || {}).textContent || '', tabs: document.querySelectorAll('.disco-tabs button').length, pop: document.querySelectorAll('.row.pop').length, socs: document.querySelectorAll('.stats .soc').length, top: (document.querySelector('.top .top-name') || {}).textContent || '' }; })()`;
|
const HERO = `(function () { var e = document.querySelector('.screen'); return { screen: e ? e.className : '', title: (function () { var q = document.querySelector('.screen'); return q ? (q.dataset.title || '') + (q.dataset.kicker || '') : ''; })(), tabs: document.querySelectorAll('.disco-tabs button').length, pop: document.querySelectorAll('.row.pop').length, socs: document.querySelectorAll('.stats .soc').length, top: (document.querySelector('.top .top-name') || {}).textContent || '' }; })()`;
|
||||||
const topName = (await cdp.evaluate(HERO)).top;
|
const topName = (await cdp.evaluate(HERO)).top;
|
||||||
await cdp.evaluate(HELPERS);
|
await cdp.evaluate(HELPERS);
|
||||||
await cdp.evaluate(`__click('.top .btn-ghost', null)`);
|
await cdp.evaluate(`__click('.top .btn-ghost', null)`);
|
||||||
@@ -3384,7 +3384,7 @@ async function main() {
|
|||||||
await sleep(700);
|
await sleep(700);
|
||||||
await cdp.evaluate(HELPERS);
|
await cdp.evaluate(HELPERS);
|
||||||
await cdp.evaluate(`__click('.halfsheet .row', 'Browse their files')`);
|
await cdp.evaluate(`__click('.halfsheet .row', 'Browse their files')`);
|
||||||
const BRW = `(function () { return { title: (document.querySelector('.gb-t') || {}).textContent || '', dirs: document.querySelectorAll('.brw-dir').length, busy: !!document.querySelector('.imp-wait'), err: [].some.call(document.querySelectorAll('.screen .stub'), function (x) { return /shares/.test(x.textContent); }), head: (document.querySelector('.brw-head .m') || {}).textContent || '' }; })()`;
|
const BRW = `(function () { return { title: (function () { var q = document.querySelector('.screen'); return q ? (q.dataset.title || '') + (q.dataset.kicker || '') : ''; })(), dirs: document.querySelectorAll('.brw-dir').length, busy: !!document.querySelector('.imp-wait'), err: [].some.call(document.querySelectorAll('.screen .stub'), function (x) { return /shares/.test(x.textContent); }), head: (document.querySelector('.brw-head .m') || {}).textContent || '' }; })()`;
|
||||||
let brw = null;
|
let brw = null;
|
||||||
for (let i = 0; i < 40; i++) { await sleep(500); brw = await cdp.evaluate(BRW); if (brw.dirs > 0 || brw.err) break; }
|
for (let i = 0; i < 40; i++) { await sleep(500); brw = await cdp.evaluate(BRW); if (brw.dirs > 0 || brw.err) break; }
|
||||||
await still(path.join(OUT, `${prefix}net-browse.png`));
|
await still(path.join(OUT, `${prefix}net-browse.png`));
|
||||||
|
|||||||
Reference in new issue
Block a user