Skip to content

Commit e563594

Browse files
Refine extension workflows and app surfaces
- expand Twitch panel request, moderation, demo, and hosted-build flows - preserve playlist ordering across VIP/current transitions and targeted edits - refresh shared app surfaces, homepage messaging, and pagination motion
1 parent ce68068 commit e563594

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+3622
-1341
lines changed

docs/twitch-panel-extension-beta-rollout-checklist.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ When you build the Hosted Test artifact, make sure the shell sets the final app
9595
VITE_TWITCH_EXTENSION_API_BASE_URL=https://your-app-host npm run build:extension:panel
9696
```
9797

98+
The build requires `VITE_TWITCH_EXTENSION_API_BASE_URL` or `APP_URL`. If neither is set, it fails instead of falling back to the Twitch asset host.
99+
98100
Upload the contents of:
99101

100102
```text
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE `channel_settings`
2+
ADD COLUMN `show_playlist_positions` integer DEFAULT false NOT NULL;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
ALTER TABLE playlist_items
2+
ADD COLUMN regular_position integer NOT NULL DEFAULT 1;
3+
4+
UPDATE playlist_items
5+
SET regular_position = position;
6+
7+
CREATE UNIQUE INDEX playlist_items_playlist_regular_position_uidx
8+
ON playlist_items (playlist_id, regular_position);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE playlist_items
2+
ADD COLUMN edited_at integer;

src/app.css

Lines changed: 197 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44

55
:root {
66
color-scheme: dark;
7-
--bg: #08090b;
8-
--bg-elevated: #101216;
7+
--shell-gutter: 1.5rem;
8+
--bg: #0c1219;
9+
--bg-elevated: #121923;
910
--panel: rgba(18, 20, 26, 0.86);
1011
--panel-strong: #13161d;
1112
--panel-soft: #181c24;
@@ -27,6 +28,24 @@
2728
--border-strong: rgba(255, 255, 255, 0.14);
2829
--shadow: 0 28px 80px rgba(0, 0, 0, 0.45);
2930
--shadow-soft: 0 12px 32px rgba(0, 0, 0, 0.24);
31+
--app-background:
32+
linear-gradient(180deg, #121a24 0%, #0c1219 44%, #091017 100%),
33+
radial-gradient(circle at 7% 5%, rgba(255, 255, 255, 0.14), transparent 20%),
34+
radial-gradient(
35+
circle at top right,
36+
rgba(145, 159, 188, 0.11),
37+
transparent 22%
38+
),
39+
radial-gradient(
40+
circle at 78% 14%,
41+
rgba(127, 184, 171, 0.05),
42+
transparent 18%
43+
),
44+
radial-gradient(
45+
circle at 50% 120%,
46+
rgba(118, 136, 154, 0.07),
47+
transparent 30%
48+
);
3049
--glow:
3150
0 0 0 1px rgba(240, 102, 66, 0.1), 0 18px 50px rgba(218, 79, 54, 0.14);
3251
}
@@ -37,7 +56,9 @@
3756

3857
html {
3958
min-height: 100%;
40-
background: var(--bg);
59+
background-color: var(--bg);
60+
background-image: var(--app-background);
61+
background-attachment: fixed;
4162
scrollbar-gutter: stable;
4263
}
4364

@@ -56,7 +77,7 @@ body {
5677
min-height: 100vh;
5778
color: var(--text);
5879
font-family: "IBM Plex Sans", sans-serif;
59-
background: linear-gradient(180deg, #090a0d 0%, #08090b 100%);
80+
background: transparent;
6081
position: relative;
6182
isolation: isolate;
6283
}
@@ -71,56 +92,52 @@ body.overlay-mode {
7192
background: transparent;
7293
}
7394

74-
body::before {
95+
html::after {
7596
content: "";
7697
position: fixed;
7798
inset: 0;
78-
z-index: -2;
99+
z-index: -1;
79100
pointer-events: none;
101+
opacity: 0.22;
80102
background:
81103
radial-gradient(
82-
circle at top left,
83-
rgba(240, 102, 66, 0.18),
84-
transparent 24%
104+
circle at 10% 8%,
105+
rgba(255, 255, 255, 0.035),
106+
transparent 26%
85107
),
86-
radial-gradient(
87-
circle at top right,
88-
rgba(145, 159, 188, 0.11),
89-
transparent 22%
108+
repeating-linear-gradient(
109+
90deg,
110+
rgba(255, 255, 255, 0.016) 0,
111+
rgba(255, 255, 255, 0.016) 1px,
112+
transparent 1px,
113+
transparent 22px
90114
),
91-
radial-gradient(
92-
circle at 50% 120%,
93-
rgba(241, 210, 172, 0.08),
94-
transparent 28%
95-
);
96-
}
97-
98-
body::after {
99-
content: "";
100-
position: fixed;
101-
inset: 0;
102-
z-index: -1;
103-
pointer-events: none;
104-
opacity: 0.28;
105-
background:
106-
linear-gradient(rgba(255, 255, 255, 0.015), rgba(255, 255, 255, 0.015)),
107115
repeating-linear-gradient(
108116
0deg,
109-
rgba(255, 255, 255, 0.02) 0,
110-
rgba(255, 255, 255, 0.02) 1px,
117+
rgba(255, 255, 255, 0.012) 0,
118+
rgba(255, 255, 255, 0.012) 1px,
111119
transparent 1px,
112-
transparent 3px
120+
transparent 5px
113121
);
122+
background-size:
123+
100% 100%,
124+
340px 260px,
125+
340px 260px;
126+
background-position:
127+
0 0,
128+
0 0,
129+
0 0;
130+
background-repeat: no-repeat;
114131
mix-blend-mode: soft-light;
115132
}
116133

117-
body.overlay-mode::after,
118-
body.overlay-mode::before {
134+
html.overlay-mode::after,
135+
html.overlay-mode::before {
119136
display: none;
120137
}
121138

122-
body.extension-mode::after,
123-
body.extension-mode::before {
139+
html.extension-mode::after,
140+
html.extension-mode::before {
124141
display: none;
125142
}
126143

@@ -147,12 +164,139 @@ h6 {
147164
min-height: 100vh;
148165
}
149166

167+
#app :where([class*="rounded"]) {
168+
border-radius: 0;
169+
}
170+
171+
.app-shell {
172+
position: relative;
173+
isolation: isolate;
174+
}
175+
176+
.app-shell::before {
177+
content: "";
178+
position: absolute;
179+
top: 0;
180+
bottom: 0;
181+
left: var(--shell-gutter);
182+
width: 1px;
183+
background: linear-gradient(
184+
180deg,
185+
transparent 0,
186+
var(--border) 2.5rem,
187+
var(--border) calc(100% - 2.5rem),
188+
transparent 100%
189+
);
190+
pointer-events: none;
191+
z-index: 0;
192+
}
193+
194+
.app-shell > * {
195+
position: relative;
196+
z-index: 1;
197+
}
198+
199+
.app-shell__main {
200+
position: relative;
201+
z-index: 1;
202+
}
203+
204+
.page-section-stack > * {
205+
position: relative;
206+
}
207+
208+
.paginated-transition-frame {
209+
will-change: opacity, transform;
210+
}
211+
212+
.paginated-transition--exit-forward {
213+
animation: paginated-exit-forward 140ms cubic-bezier(0.4, 0, 0.2, 1) both;
214+
}
215+
216+
.paginated-transition--enter-forward {
217+
animation: paginated-enter-forward 220ms cubic-bezier(0.22, 1, 0.36, 1) both;
218+
}
219+
220+
.paginated-transition--exit-backward {
221+
animation: paginated-exit-backward 140ms cubic-bezier(0.4, 0, 0.2, 1) both;
222+
}
223+
224+
.paginated-transition--enter-backward {
225+
animation: paginated-enter-backward 220ms cubic-bezier(0.22, 1, 0.36, 1) both;
226+
}
227+
228+
@keyframes paginated-exit-forward {
229+
from {
230+
opacity: 1;
231+
transform: translateX(0);
232+
}
233+
234+
to {
235+
opacity: 0;
236+
transform: translateX(-14px);
237+
}
238+
}
239+
240+
@keyframes paginated-enter-forward {
241+
from {
242+
opacity: 0;
243+
transform: translateX(14px);
244+
}
245+
246+
to {
247+
opacity: 1;
248+
transform: translateX(0);
249+
}
250+
}
251+
252+
@keyframes paginated-exit-backward {
253+
from {
254+
opacity: 1;
255+
transform: translateX(0);
256+
}
257+
258+
to {
259+
opacity: 0;
260+
transform: translateX(14px);
261+
}
262+
}
263+
264+
@keyframes paginated-enter-backward {
265+
from {
266+
opacity: 0;
267+
transform: translateX(-14px);
268+
}
269+
270+
to {
271+
opacity: 1;
272+
transform: translateX(0);
273+
}
274+
}
275+
150276
html.extension-mode #app,
151277
body.extension-mode #app {
152278
min-height: 100%;
153279
width: 100%;
154280
}
155281

282+
@media (prefers-reduced-motion: reduce) {
283+
.paginated-transition-frame,
284+
.paginated-transition--exit-forward,
285+
.paginated-transition--enter-forward,
286+
.paginated-transition--exit-backward,
287+
.paginated-transition--enter-backward {
288+
animation: none;
289+
opacity: 1;
290+
transform: none;
291+
}
292+
}
293+
294+
@media (max-width: 960px) {
295+
:root {
296+
--shell-gutter: 0.875rem;
297+
}
298+
}
299+
156300
::selection {
157301
background: rgba(240, 102, 66, 0.3);
158302
color: var(--text);
@@ -278,6 +422,23 @@ body.extension-mode #app {
278422
container-type: inline-size;
279423
}
280424

425+
.search-panel > * {
426+
position: relative;
427+
}
428+
429+
@media (min-width: 961px) {
430+
.search-panel > * + *::before {
431+
content: "";
432+
position: absolute;
433+
top: -0.75rem;
434+
left: calc(-1 * var(--shell-gutter));
435+
right: 0;
436+
height: 1px;
437+
background: var(--border);
438+
pointer-events: none;
439+
}
440+
}
441+
281442
.dashboard-playlist__versions-wrap {
282443
width: 100%;
283444
min-width: 0;

src/components/blacklist-panel.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function BlacklistPanel(props: {
4040
Artists
4141
</p>
4242
{props.artists.length > 0 ? (
43-
<div className="overflow-hidden rounded-[20px] border border-(--border)">
43+
<div className="overflow-hidden border border-(--border)">
4444
{props.artists.map((artist, index) => (
4545
<div
4646
key={artist.artistId}
@@ -64,7 +64,7 @@ export function BlacklistPanel(props: {
6464
Songs
6565
</p>
6666
{props.songGroups?.length ? (
67-
<div className="overflow-hidden rounded-[20px] border border-(--border)">
67+
<div className="overflow-hidden border border-(--border)">
6868
{props.songGroups.map((song, index) => (
6969
<div
7070
key={song.groupedProjectId}
@@ -91,7 +91,7 @@ export function BlacklistPanel(props: {
9191
Versions
9292
</p>
9393
{props.songs.length > 0 ? (
94-
<div className="overflow-hidden rounded-[20px] border border-(--border)">
94+
<div className="overflow-hidden border border-(--border)">
9595
{props.songs.map((song, index) => (
9696
<div
9797
key={song.songId}
@@ -118,7 +118,7 @@ export function BlacklistPanel(props: {
118118
Charters
119119
</p>
120120
{props.charters?.length ? (
121-
<div className="overflow-hidden rounded-[20px] border border-(--border)">
121+
<div className="overflow-hidden border border-(--border)">
122122
{props.charters.map((charter, index) => (
123123
<div
124124
key={charter.charterId}
@@ -146,7 +146,7 @@ export function BlacklistPanel(props: {
146146
{...(props.defaultOpen ? { open: true } : {})}
147147
className="group"
148148
>
149-
<summary className="cursor-pointer list-none rounded-[28px] p-6 [&::-webkit-details-marker]:hidden">
149+
<summary className="cursor-pointer list-none p-6 [&::-webkit-details-marker]:hidden">
150150
<div className="flex items-start justify-between gap-4">
151151
<div className="min-w-0">
152152
<CardTitle>{props.title ?? "Channel blacklists"}</CardTitle>

0 commit comments

Comments
 (0)