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