From 67de9c84be07df294c20d5dd33087e141a8f36cb Mon Sep 17 00:00:00 2001 From: Chad Curtis Date: Thu, 14 May 2026 02:01:11 -0500 Subject: [PATCH] Blend community banner into tabs and tighten its layout Wrap the hero banner and tab strip in a shared image+gradient backdrop so the banner image continues underneath the tabs and fades into the page background, removing the hard seam between them. The gradient holds heavy darkness through the tab strip (kept legible with light tab text + drop-shadowed underline) and drops to the page bg only at the very bottom edge. Reduce banner cognitive load: move the description behind an Info button next to the title (drop the inline line-clamp and its ResizeObserver-based clipping detection), promote the avatar stack above the title row, and shorten the banner aspect ratio (2:1 mobile, 21:9 desktop). --- src/components/CommunityDetailPage.tsx | 135 ++++++++++++------------- 1 file changed, 64 insertions(+), 71 deletions(-) diff --git a/src/components/CommunityDetailPage.tsx b/src/components/CommunityDetailPage.tsx index 295ec4fc..ca22903a 100644 --- a/src/components/CommunityDetailPage.tsx +++ b/src/components/CommunityDetailPage.tsx @@ -1,4 +1,4 @@ -import { useMemo, useCallback, useState, useLayoutEffect, useRef } from 'react'; +import { useMemo, useCallback, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { nip19 } from 'nostr-tools'; import { @@ -6,6 +6,7 @@ import { Activity as ActivityIcon, CalendarDays, Crown, + Info, MessageCircle, MoreVertical, Pencil, @@ -208,29 +209,9 @@ export function CommunityDetailPage({ event }: { event: NostrEvent }) { return description.replace(new RegExp(`\\s*${descriptionUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*$`), '').trim(); }, [description, descriptionUrl]); - // Detect whether the banner description is visually clipped by the - // line-clamp. If it is — or if there's a stripped website URL not shown - // inline — clicking the description opens a modal with the full text. - const descriptionRef = useRef(null); - const [descriptionClipped, setDescriptionClipped] = useState(false); - useLayoutEffect(() => { - const el = descriptionRef.current; - if (!el) { - setDescriptionClipped(false); - return; - } - const measure = () => { - setDescriptionClipped(el.scrollHeight > el.clientHeight + 1); - }; - measure(); - // Re-measure on viewport changes — the line-clamp boundary shifts with - // container width. - const ro = new ResizeObserver(measure); - ro.observe(el); - return () => ro.disconnect(); - }, [descriptionText]); - - const descriptionExpandable = descriptionClipped || !!descriptionUrl; + // Whether to render the description info button next to the title — true + // whenever there's any description text or a stripped trailing URL. + const descriptionExpandable = !!descriptionText || !!descriptionUrl; /** * Synthesize a kind-1 pseudo-event so we can hand the description off to @@ -517,18 +498,38 @@ export function CommunityDetailPage({ event }: { event: NostrEvent }) { return (
- {/* ── Hero banner — image fills the area, title/description/members overlaid ── */} -
- {image ? ( - {name} - ) : ( -
- -
- )} - {/* Gradient overlay — modeled on the adventure detail pattern so - overlaid text stays legible against any background image. */} -
+ + + {/* ── Hero banner + tabs share a single image/gradient backdrop so the + banner image continues underneath the tab strip and fades into the + page background — eliminating the seam between the two. ── */} +
+ {/* Shared backdrop — image (or fallback gradient) + darkening overlay + that spans the full height of (banner + tabs) and fades to the + page background at its bottom edge. */} +
+ {image ? ( + + ) : ( +
+ )} + {/* Darkening overlay that fades to the page background at the + bottom of the tab strip — makes tab text legible and erases the + hard seam between banner and tabs. Stops push the heavy darkness + down so it sits behind the tabs, not over the banner. */} +
+
+ + {/* Banner — fixed aspect ratio, title/description/buttons overlaid */} +
+ {!image && ( +
+ +
+ )} + {/* Extra top/bottom darkening on the hero specifically (above the + shared overlay) so overlaid title/description stay legible. */} +
{/* Top bar — back button (left) + follow toggle (right) */}
@@ -562,34 +563,11 @@ export function CommunityDetailPage({ event }: { event: NostrEvent }) { )}
- {/* Title + description + bottom row (member stack + actions) */} -
-

{name}

- {descriptionText && ( - descriptionExpandable ? ( - - ) : ( -

- {descriptionText} -

- ) - )} -
+ {/* Member stack sits ABOVE the title; the title row carries the Info + button (left of name) and action buttons (right). Description has + moved behind an Info button to reduce banner clutter. */} +
+
{/* Avatar stack — clickable to open full members dialog */} +
+
+
+

{name}

+ {descriptionExpandable && ( + + )} +
{/* Banner action row — MembersOnly + Share + overflow menu (Unfollow / Edit) */} -
+
@@ -648,31 +641,31 @@ export function CommunityDetailPage({ event }: { event: NostrEvent }) {
{/* ── Tabs ── */} - - - + Chat Activity Pulse +
+ {/* ── /shared banner+tabs backdrop wrapper ── */} {/* Sublabel for the currently-active tab. Only rendered when the tab has a descriptor to show — keeps the rest of the tab strip