Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/web/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
scheduleForExport?: ScheduleForExport;
scheduleType?: "production" | "run-of-show";
collaborationToolbar?: Omit<CollaborationToolbarProps, "position">;
/** Vertical offset in px from the top of the viewport (e.g. to sit below an announcement bar). Defaults to 0. */
topOffset?: number;
}

const Header: React.FC<HeaderProps> = ({
Expand All @@ -29,13 +31,14 @@
scheduleForExport,
scheduleType,
collaborationToolbar,
topOffset = 0,
}) => {
const navigate = useNavigate();
const location = useLocation();
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
const [user, setUser] = useState<any>(null);

Check failure on line 41 in apps/web/src/components/Header.tsx

View workflow job for this annotation

GitHub Actions / typescript-checks

Unexpected any. Specify a different type
const isSharedRoute = location.pathname.includes("/shared/");
const profileMenuRef = useRef<HTMLDivElement>(null);

Expand Down Expand Up @@ -113,7 +116,8 @@

return (
<header
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ease-in-out ${
style={{ top: topOffset }}
className={`fixed left-0 right-0 z-50 transition-all duration-300 ease-in-out ${
scrolled ? "bg-gray-900/95 shadow-md backdrop-blur-sm" : "bg-transparent"
}`}
>
Expand Down
92 changes: 56 additions & 36 deletions apps/web/src/pages/Landing.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useLayoutEffect, useRef, useState } from "react";
import { Helmet } from "react-helmet";
import { CalendarClock, Download, Server, X, ExternalLink } from "lucide-react";
import Header from "../components/Header";
Expand All @@ -12,47 +12,30 @@ import { useDeprecationNotice } from "@/hooks/useDeprecationNotice";

const SUNSET_BANNER_KEY = "sounddocs-sunset-banner-dismissed";

const SunsetBanner: React.FC = () => {
const [visible, setVisible] = useState<boolean>(() => {
try {
return localStorage.getItem(SUNSET_BANNER_KEY) !== "true";
} catch {
return true;
}
});

const dismiss = (): void => {
try {
localStorage.setItem(SUNSET_BANNER_KEY, "true");
} catch {
// localStorage unavailable — dismiss in-memory only
}
setVisible(false);
};

if (!visible) return null;
interface SunsetBannerProps {
onDismiss: () => void;
}

const SunsetBanner = React.forwardRef<HTMLDivElement, SunsetBannerProps>(({ onDismiss }, ref) => {
return (
<div
role="banner"
ref={ref}
role="region"
aria-label="SoundDocs sunset notice"
className="w-full bg-gray-900 border-b border-amber-500/30"
className="fixed top-0 inset-x-0 z-[60] bg-gray-950/95 backdrop-blur-sm border-b border-amber-500/30"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Banner overlays modal 🐞 Bug ≡ Correctness

Landing’s fixed SunsetBanner uses z-[60], which is higher than DeprecationNoticeModal’s z-50
overlay, so the banner can render above (and remain clickable during) the modal. This breaks
expected modal isolation when the deprecation modal opens on the landing page.
Agent Prompt
### Issue description
The Landing page’s fixed SunsetBanner uses `z-[60]`, but `DeprecationNoticeModal` uses `z-50`, so the banner can appear above the modal overlay and remain interactive while the modal is open.

### Issue Context
This is a stacking-order regression introduced by moving the banner to a fixed layer with a higher z-index.

### Fix
Ensure the modal overlay z-index is higher than the banner (and header). For example, update `DeprecationNoticeModal` overlay wrapper from `z-50` to something > 60 (e.g. `z-[70]` / `z-[80]`). Optionally standardize modal z-index across the app.

### Fix Focus Areas
- apps/web/src/pages/Landing.tsx[19-26]
- apps/web/src/components/DeprecationNoticeModal.tsx[23-27]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

>
<div className="max-w-7xl mx-auto px-4 py-3 flex flex-col sm:flex-row sm:items-center gap-3">
<div className="relative max-w-7xl mx-auto px-4 py-2.5 pr-10 sm:pr-4 flex flex-col sm:flex-row sm:items-center gap-x-6 gap-y-1.5">
{/* Retirement notice */}
<div className="flex items-start sm:items-center gap-2 text-amber-100 flex-1 min-w-0">
<CalendarClock
className="h-4 w-4 text-amber-400 flex-shrink-0 mt-0.5 sm:mt-0"
aria-hidden="true"
/>
<div className="flex items-center gap-2 text-amber-100 min-w-0">
<CalendarClock className="h-4 w-4 text-amber-400 flex-shrink-0" aria-hidden="true" />
<p className="text-sm leading-snug">
<span className="font-semibold text-white">SoundDocs retires September 1, 2026.</span>{" "}
Keep your work:
<span className="text-amber-200/90">Keep your work:</span>
</p>
</div>

{/* Action links */}
<div className="flex flex-wrap items-center gap-x-4 gap-y-2 text-sm flex-shrink-0">
<div className="flex flex-wrap items-center gap-x-5 gap-y-1.5 text-sm sm:ml-auto">
<a
href="https://eventools.io/?mode=signup"
target="_blank"
Expand Down Expand Up @@ -82,26 +65,63 @@ const SunsetBanner: React.FC = () => {

{/* Dismiss */}
<button
onClick={dismiss}
onClick={onDismiss}
aria-label="Dismiss sunset notice"
className="self-start sm:self-center ml-auto sm:ml-2 p-1 rounded hover:bg-gray-800 transition-colors flex-shrink-0"
className="absolute right-2 top-1.5 sm:static sm:ml-1 p-1 rounded hover:bg-gray-800 transition-colors flex-shrink-0"
>
<X className="h-4 w-4 text-gray-400 hover:text-white" aria-hidden="true" />
</button>
</div>
</div>
);
};
});
SunsetBanner.displayName = "SunsetBanner";

const Landing: React.FC = () => {
const { isOpen: showDeprecationModal, onClose: handleDismissDeprecation } =
useDeprecationNotice();

const [sunsetVisible, setSunsetVisible] = useState<boolean>(() => {
try {
return localStorage.getItem(SUNSET_BANNER_KEY) !== "true";
} catch {
return true;
}
});
const sunsetRef = useRef<HTMLDivElement>(null);
const [bannerHeight, setBannerHeight] = useState(0);

// On page load or when changing themes, best practice for accessibility
useEffect(() => {
document.title = "SoundDocs | Pro Audio & Event Production Documentation";
}, []);

// Measure the fixed sunset banner so the header + content clear it at every
// breakpoint (it wraps to multiple lines on small screens). useLayoutEffect
// runs before paint to avoid an initial overlap flash.
useLayoutEffect(() => {
if (!sunsetVisible) {
setBannerHeight(0);
return;
}
const el = sunsetRef.current;
if (!el) return;
const measure = () => setBannerHeight(el.offsetHeight);
measure();
const observer = new ResizeObserver(measure);
observer.observe(el);
return () => observer.disconnect();
}, [sunsetVisible]);

const dismissSunset = (): void => {
try {
localStorage.setItem(SUNSET_BANNER_KEY, "true");
} catch {
// localStorage unavailable — dismiss in-memory only
}
setSunsetVisible(false);
};

return (
<>
<Helmet>
Expand All @@ -115,9 +135,9 @@ const Landing: React.FC = () => {
content="audio analyzer, acoustiq, rta, spl meter, transfer function, math traces, measurement averaging, coherence weighting, frequency analysis, event production, pixel map, LED wall designer, video mapping, audio documentation, video documentation, lighting documentation, production schedule, run of show, stage plot, patch list, mic plot, corporate mic plot, theater mic plot, wireless mic management, audio engineer, video technician, lighting designer, production management, input list, output list, sound documentation, video signal flow, lighting plot, DMX chart, audio production software, stage layout tool, concert planning, FOH, monitor engineer tools, event timeline, cue sheet, typical rates, technical formulas, audio formulas, video formulas, lighting formulas, reference guides, pinouts, frequency bands, decibel chart, industry glossaries, measurement mathematics, transfer function analysis, room averaging, system optimization"
/>
</Helmet>
<Header />
<SunsetBanner />
<main>
{sunsetVisible && <SunsetBanner ref={sunsetRef} onDismiss={dismissSunset} />}
<Header topOffset={bannerHeight} />
<main style={bannerHeight ? { paddingTop: bannerHeight } : undefined}>
<Hero />
<Features />
<TrustedBy />
Expand Down
Loading