From 76953fd022e8727048cd7c69d6ee4bf843e74444 Mon Sep 17 00:00:00 2001 From: Stephen DeLorme Date: Wed, 2 Sep 2026 19:06:00 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20stacked=20phone=20layout=20=E2=80=94=20?= =?UTF-8?q?player,=20title,=20chat=20and=20controls=20in=20one=20column?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On a portrait phone the full-bleed player letterboxed a 16:9 clip into a ~210px band with ~70% of the screen black, and the header buttons overflowed the right edge by 15px at 375px wide (CHAT and SOUND were cut off). The chat overlay also hid the video entirely while open. Below md the page is now a flex column: header, aspect-video player, lower third, live chat (inline, always visible when enabled) or an "Up next" list when chat is off, and the bottom bar. The wrapper around the player column is `display: contents` on phones so those pieces are laid out directly by
; from md up it turns back into the positioned column and every piece becomes the same TV-style overlay as before. Desktop layout is unchanged. Also on phones: viewer count moves from the header chip into the lower third, the sound button becomes a short πŸ”Š/πŸ”‡ toggle, inputs use 16px so iOS Safari stops zooming the page on focus, and the bottom bar pads past the home indicator (viewport-fit=cover + safe-area-inset-bottom). Everywhere: the TAP FOR SOUND hint is centered on the player instead of sitting on top of the title when it wraps to two lines. The /c/ replay page gets the same stacked treatment. --- app/c/[id]/clip-client.tsx | 78 ++++++++++++--------- app/chat-panel.tsx | 32 +++------ app/layout.tsx | 11 ++- app/stream-client.tsx | 140 ++++++++++++++++++++++--------------- 4 files changed, 146 insertions(+), 115 deletions(-) diff --git a/app/c/[id]/clip-client.tsx b/app/c/[id]/clip-client.tsx index a397262..8c73e5a 100644 --- a/app/c/[id]/clip-client.tsx +++ b/app/c/[id]/clip-client.tsx @@ -30,61 +30,73 @@ export default function ClipClient({ }, []); return ( -
-
!soundOn && setSound(true)}> -
- +
{/* ---- top bar ---- */} -
-
+
+
INFINITE - + REPLAY
-
+
+ {/* ---- player ---- */} +
!soundOn && setSound(true)} + > +
+ {/* ---- lower third ---- */} -
-
- From the archive -
-
- {clip.title} +
+
+
+ From the archive +
+
+ {clip.title} +
+
{credit}
-
{credit}
{/* ---- bottom bar ---- */} -
+
void; onFund: (text: string, author: ChatAuthor) => void; fundDisabled: boolean; }) { @@ -68,7 +63,7 @@ export default function ChatPanel({ useEffect(() => { const el = list.current; if (el && stickToBottom.current) el.scrollTop = el.scrollHeight; - }, [messages, open]); + }, [messages]); const onScroll = useCallback(() => { const el = list.current; @@ -112,7 +107,7 @@ export default function ChatPanel({ return (
@@ -207,7 +193,7 @@ export default function ChatPanel({ maxLength={CHAT_MAX_LEN} placeholder={`Yell at the TV as ${identity.name}…`} aria-label="Chat message" - className="min-w-0 flex-1 border-2 border-teal/60 bg-void px-2 py-1.5 text-sm outline-none focus:border-teal" + className="min-w-0 flex-1 border-2 border-teal/60 bg-void px-2 py-1.5 text-base outline-none focus:border-teal md:text-sm" /> - )}
+ {/* ---- player ---- */} +
+
+ {/* ---- activity toasts ---- */}
{toasts.map((a) => ( @@ -384,32 +387,55 @@ export default function StreamClient() { {/* ---- config error banner ---- */} {now?.configError && ( -
+
⚠ STATION MISCONFIGURED: {now.configError}
)} {/* ---- lower third ---- */} -
- {now?.clip && ( + {now?.clip && ( +
Now showing
-
+
{now.clip.title}
{now.clip.kind === "paid" ? `submitted by ${now.clip.credit || "an anonymous weirdo"}` : "written by the house AI"} + {now.viewers && now.viewers.count > 0 && ( + Β· {now.viewers.count} watching + )}
- )} -
+
+ )} + + {/* ---- phone-only: up next fills the space under the player when chat is off ---- */} + {!chatOn && ( +
+ {now?.upNext && now.upNext.length > 0 && ( + <> +
+ Up next +
+
    + {now.upNext.map((c, i) => ( +
  1. + {i + 1}. {c.title} +
  2. + ))} +
+ + )} +
+ )} {/* ---- bottom bar ---- */} -
+