Skip to content

Rework landing page and design systems, added pricing section#15

Open
carsonSgit wants to merge 17 commits into
masterfrom
demo-site
Open

Rework landing page and design systems, added pricing section#15
carsonSgit wants to merge 17 commits into
masterfrom
demo-site

Conversation

@carsonSgit
Copy link
Copy Markdown
Collaborator

No description provided.

carsonSgit and others added 14 commits April 19, 2026 20:53
…g with StyleManifest; remove unused components
… and update StyleManifest with alternating features; include background images
…ions, enhance layout, and update contributor information
…styles, and remove loading animations from Home component

Co-authored-by: Copilot <copilot@github.com>
…n, update grid dimensions, and improve visual variants in PricingSection
…ion, improve layout, and refine button animations
…cture, add yearly/monthly toggle, and enhance layout
@claude
Copy link
Copy Markdown

claude Bot commented Apr 30, 2026

PR Review: Rework landing page and design systems, added pricing section

Overview

This PR does three distinct things:

  1. Landing page redesign — replaces the animated loading screen, Dither/GSAP effects, and fancy components with a cleaner editorial layout using Inter + grid-based design
  2. New pricing section — adds a 4-tier pricing table with hover-activated braille animations
  3. Demo mode system — introduces DEMO_MODE=true env flag with comprehensive mock data, in-memory stores, and OpenRouter AI integration so the app runs without any real backends

The demo mode work in particular is well-architected and the mock data is thorough. A few issues worth addressing before merge:


Bugs / Correctness

app/api/ai-analysis/stats/route.tssuccessRate stripped only in demo mode

const { successRate: _s, ...rest } = mockStats.jobStats;
return NextResponse.json(rest);

The production path presumably returns successRate (it comes from getJobStats()). Any frontend component that reads successRate from this endpoint will silently receive undefined in demo mode. Either include it in the response, or confirm the frontend never reads it.

app/api/search/route.ts — filters are built but not applied in demo mode
The demo search matches events by text, then constructs a filters response object using docType, severityParam, eventTypeParam, etc. — but those filters are never applied to the matches array. A user filtering by severity in demo mode gets unfiltered results.

app/api/reports/route.ts — markdown → Tiptap conversion is lossy in demo
The production path uses @tiptap/html + marked to properly convert markdown. The demo stub wraps the entire markdown string as a single plain-text paragraph. Reports created via the AI chat createReport tool will render without headings or formatting.


Performance

components/landing/pricing-section.tsxMiniBrailleBg intervals always running
Each of the 4 pricing cards runs a setInterval at 50ms computing a 25×45 braille grid. The animation is CSS-hidden until hover (opacity-0 group-hover:opacity-100), but the JS computation runs constantly regardless. On a landing page with 4 cards that's ~90,000 loop iterations/sec just to keep the grids fresh for hover events.

Consider using IntersectionObserver to start/stop the interval, or only generating a static random grid on mount and re-randomizing on mouseenter.

Same note applies to ButtonBrailleBg on the Professional tier button.


Code Quality

font-[family-name:var(--font-inter)] is very verbose and pervasive
This string appears dozens of times throughout the new components. Configure Inter as a named font utility in tailwind.config (e.g. font-inter) so it's a single class everywhere and easier to swap later.

components/landing/style-manifest.tsx — unexpected re-export

export { BrowserComponent };

BrowserComponent is already importable directly from @/components/browser-component. Re-exporting it from the landing manifest creates a confusing second import path with no benefit.

components/landing/pricing-section.tsx — pricing buttons have no action
All four tier buttons render as <button> elements with no onClick, no href, and no form. The "Deploy for free", "Get Started", and "Contact Sales" buttons are purely decorative right now. Consider linking them or adding a TODO comment so they're not silently no-ops.

Unused font load in app/layout.tsx
Instrument_Serif is imported and added to the <body> class via ${instrumentSerif.variable}, but I can't find font-[family-name:var(--font-instrument-serif)] used anywhere in the new components (only Inter is used). If it's not used yet, it's loading an extra font file on every page load for no reason.


Minor

  • .gitignore — missing trailing newline after .cursor
  • components/landing/style-manifest.tsx L~2343FeatureBlock uses <Image alt=""> (empty alt). Fine for decorative backgrounds, but the image also sits behind the feature content card, so the empty alt is technically correct — just worth confirming intentionally.
  • lib/demo/mock-data.tsNOW is captured at module load, so timestamps drift in long-running dev servers. The file header notes this, which is good. Just make sure the UI doesn't show "2 minutes ago" when the server has been running for days.

What's Working Well

  • The single isDemoMode flag pattern is clean and all API routes follow a consistent early-return approach
  • Using globalThis.__demoReportStore / globalThis.__demoChatStore for in-process state is correct for Next.js serverless
  • BrailleLoader has good a11y (role="status", aria-label, aria-hidden on the glyph)
  • Removing GSAP and the animated loading screen meaningfully reduces bundle weight and first-load jank
  • The mock data is realistic and well-structured
  • Readonly<{...}> prop types on the new components is a nice correctness improvement

Summary

The demo mode system is solid and the landing redesign is cleaner. The main actionable items are: fix the successRate asymmetry in the stats API, apply search filters in demo mode, and address the continuous braille animation intervals as a performance concern. The rest are low-priority cleanups.

@claude
Copy link
Copy Markdown

claude Bot commented Apr 30, 2026

Code Review — PR #15: Landing Page Rework + Demo Mode

Overview

This PR does two distinct things:

  1. Landing page redesign — replaces the GSAP/Dither/loading-screen approach with a cleaner editorial layout using Inter + Instrument Serif, adds a new StyleManifest and PricingSection component, and removes several now-unused components.
  2. Demo mode infrastructure — introduces DEMO_MODE=true env var gating across every API route, with in-memory/fixture data for the full app surface (cameras, assets, events, reports, chat, AI tools).

Overall the work is solid and well-scoped. The demo mode layer is the more complex half, and there are a few issues worth addressing.


Demo Mode Infrastructure

Potential bug — isDemoMode evaluated at build time on the server

lib/demo/flag.ts exports:

export const isDemoMode = process.env.DEMO_MODE === "true";

This is evaluated once at module load. On serverless/edge runtimes (Vercel), that is fine — each cold start reads the env var. But the same constant is also imported in client components (e.g. stream-controls.tsx, site-header.tsx). Because Next.js inlines process.env.* values in client bundles at build time, isDemoMode on the client will reflect whatever value was present during the build, not the runtime. If the production bundle is built with DEMO_MODE unset and then deployed with it set (or vice versa), the UI and the API will disagree.

Recommendation: Prefix the variable with NEXT_PUBLIC_DEMO_MODE so Next.js explicitly knows it is client-visible and it gets baked in consistently, or keep client-side reads conditional on a context/prop rather than the raw env constant.

demoReportStore mutates across requests in a serverless context

mock-data.ts stores reports in a module-level array and chat-store.ts uses globalThis.__demoChatStore. In a serverless runtime each function invocation runs in its own isolate, so mutations don't persist across requests anyway. That's fine for a stateless demo, but the code comments suggest it's intentional in-memory state. A comment noting the ephemeral nature would prevent confusion, and the globalThis declaration (needed for Next.js HMR) could use a comment explaining why.

Demo mode bypasses auth/input validation early

Several API routes return demo responses before input validation (e.g. app/api/search/route.ts returns demo data before the docType validity check). This is fine for a demo, but worth noting so future reviewers don't think the validation is dead code.


Landing Page / UI

FeaturesAlternating name collision

style-manifest.tsx defines a private FeaturesAlternating function and simultaneously components/features-alternating.tsx (the old file) was deleted. The new local one is not exported — good. But FeatureBlock is also private and references a hardcoded backgrounds array of five paths (/assets/bg1.webpbg5.webp). These assets aren't created in this PR. If they don't exist, the feature section will show broken images.

Pricing buttons have no onClick handler

The pricing tier buttons (Get Started, Deploy for free, Contact Sales) render as <button> elements but have no onClick, href, or type="button" attribute. They will silently do nothing when clicked. This is likely a placeholder for future integration, but should either be wired up or rendered as disabled/greyed-out with a tooltip.

MiniBrailleBg — animation interval not paused when off-screen

Each pricing card runs a setInterval at 50 ms (20 fps). With four cards that's 80 interval ticks per second running even when the cards are not in view. Consider using IntersectionObserver to pause/resume, or at minimum checking document.visibilityState. The ButtonBrailleBg inside the popular card adds another 50 ms interval.

Inline <style> keyframes in MiniBrailleBg

<style>{`@keyframes braille-gradient-shift { … }`}</style>

This injects a <style> tag into the DOM on every render of every pricing card that's hovered. The animation name is not scoped, so repeated mounts/unmounts will insert duplicate @keyframes rules. Move this to globals.css or use a CSS module.

{/* Spacer */} comment in JSX

app/page.tsx line ~208:

<div className="w-[100px]" /> {/* Spacer */}

The comment is outside the self-closing tag so it renders as a text node. Minor cosmetic issue.


Code Quality

any type on event variable in ai-tools.ts

let event: any = null;

The rest of the file has typed data. The AIAnalysisEvent type is already imported via mock-data.ts — use it here.

effectiveModel is set to "openrouter" in demo path

In app/api/chat/route.ts the demo branch sets effectiveModel = "openrouter", but then effectiveModel is only used to check effectiveModel !== "stateful-argus". This is safe, but the intent is slightly obscured — a comment that effectiveModel is only used for the Letta-guard would help, or it could be omitted since the demo branch already can't reach that model.

NavMain receives labelClassName="hidden" from sidebar

Hiding section labels entirely may affect accessibility (screen-reader users lose the group structure). Consider aria-hidden or sr-only class instead of hidden if visual hiding is the goal.


.gitignore

The new entries are missing a newline at end of file (flagged by git). Minor, but will cause a linting warning.


No test coverage

No tests are included for the new demo mode paths. Given that these branches are active on the public deployment, a few unit tests for the flag and mock data helpers would improve confidence — especially for the report store mutation logic and the chat store.


Summary

Area Status
Demo mode architecture ✅ Clean, well-centralized
isDemoMode client/server split ⚠️ Needs NEXT_PUBLIC_ prefix or context
Pricing buttons ⚠️ No-op click handlers
Braille animation performance ⚠️ Always-running intervals
Inline @keyframes ⚠️ Duplicate injection risk
Background images for features ❓ Verify assets exist
Type safety (any) 🔧 Minor
Test coverage ❌ None for new paths

The demo mode layer is the right approach for a public deployment — the main thing to nail down is the build-time vs runtime env var split for client components.

@claude
Copy link
Copy Markdown

claude Bot commented May 1, 2026

Code Review — PR #15: Landing page rework, design system, pricing section

Great scope for a single PR — the changes are coherent and the direction is clearly an improvement. Notes below are ordered roughly by severity.

Overview

This PR does three things:

  1. Landing page redesign — removes the animated intro (Dither/DecryptedText), replaces with a clean editorial layout, adds a pricing section and a new footer. Drops GSAP FeaturesAlternating in favour of a static FeatureBlock grid.
  2. Dashboard design system — introduces Surface, PageContainer, and PageHeader abstractions to replace ad-hoc Card wrappers and raw container divs across every dashboard page.
  3. Demo mode — adds lib/demo/flag.ts, lib/demo/mock-data.ts, and lib/demo/openrouter.ts to stub all real API/DB calls with fixture data when demo mode is enabled.

Bugs / Correctness

1. Pricing CTA buttons are dead

All four pricing tier buttons in pricing-section.tsx are bare button elements with no onClick and no href. Clicking "Get Started" or "Contact Sales" does nothing. Either wire them up or mark them disabled with a tooltip.

2. Missing background image assets

FeaturesAlternating in style-manifest.tsx references /assets/bg1.webp through /assets/bg5.webp as Image fill backgrounds. These files do not appear to be added in this PR. If absent from public/assets/, feature blocks will render with broken images. Either add the assets or fall back gracefully.

3. Demo markdown conversion is lossy

In demo mode, POST /api/reports wraps raw markdown as a single plain-text paragraph node rather than running it through marked + generateJSON. Markdown headings/lists/bold will appear as literal characters in the Tiptap editor.

Performance

4. MiniBrailleBg — 4 concurrent setInterval timers at 20 fps

Each pricing card mounts a MiniBrailleBg that runs setInterval at 50ms unconditionally, computing a 25x45 grid every tick. With four cards on screen that is 4x20 fps of continuous setState calls whether or not the card is hovered. CSS opacity: 0 hides them visually but timers keep running. Suggested fix: gate the interval on a hover state or IntersectionObserver.

5. ButtonBrailleBg animates continuously

Same issue — ButtonBrailleBg runs its timer even when the parent card is not hovered.

Accessibility

6. CtaLink disabled link is still keyboard-reachable

When href is set and disabled is true, CtaLink renders a Link with aria-disabled and pointer-events-none. That blocks mouse clicks but the element remains focusable via Tab and activatable via Enter. Adding tabIndex={-1} when disabled would also block keyboard navigation.

7. SurfaceTitle renders as a div, not a heading

SurfaceTitle outputs a div. PageHeader correctly uses h1 for the page-level title, so Surface sections would benefit from h2/h3 for screen-reader navigation.

Minor

8. .gitignore missing newline at EOF

9. Mobile nav links condensed to long single lines — Biome should fix this on format.

10. useChartAnimation module-level Set survives HMR — chart animations will not replay after a hot-module reload in development.

Positives

  • The Surface / PageContainer / PageHeader abstraction is clean and well-typed with data-slot attributes matching shadcn conventions.
  • Demo mode coverage is thorough — every API route, realtime hook, and AI tool is accounted for. The globalThis.__demoChatStore pattern is the right approach for persisting state across Next.js API invocations.
  • useChartAnimation elegantly solves the chart-animates-every-time-you-switch-tabs annoyance.
  • BrailleLoader has proper role="status" and aria-label.
  • Removing the 3.5-second blocking loading screen is a meaningful UX win.

Summary

The two issues that should block merge are dead CTA buttons (#1) and potentially missing background assets (#2). The MiniBrailleBg timer problem (#4) is worth addressing before the page gets real traffic. Everything else is low-priority polish.

Generated with Claude Code

@claude
Copy link
Copy Markdown

claude Bot commented May 22, 2026

PR Review: Rework landing page and design systems, added pricing section

Nice cohesive design refresh. The new Surface/PageContainer/PageHeader primitives meaningfully reduce duplication across dashboard pages, and the landing page reads well. A few items worth addressing before merge.

🔴 Likely bugs

1. isDemoMode doesn't work in client code (high impact)
lib/demo/flag.ts uses process.env.DEMO_MODE, which is not NEXT_PUBLIC_ prefixed. Next.js inlines non-public env vars only on the server; in the client bundle they're replaced with undefined, so isDemoMode is always false on the client. This PR newly relies on it in client-side code:

  • components/stream/stream-controls.tsx (new): disabled={isDemoMode || …} — Enable Camera will NOT be disabled in the demo build.
  • app/(dashboard)/stream/page.tsx: {isDemoMode && <DemoDisabledNotice />} — notice will never render.
  • hooks/use-stats-realtime.ts (new gate): if (!enabled || isDemoMode) return; — the realtime subscription will still spin up in demo mode and try to hit demo.invalid.supabase.co.
  • components/site-header.tsx: the <DemoBadge /> won't show when SiteHeader is rendered inside a client page subtree.

Fix: rename to NEXT_PUBLIC_DEMO_MODE (and update vercel.json / docs) so the value survives into the client bundle, or split into separate server/client flags.

2. Demo Claude model mappings are out of date
app/api/chat/route.ts lines 72–79 — claude-sonnet-4.5anthropic/claude-3.5-sonnet, claude-haiku-4.5anthropic/claude-3.5-haiku. Demo users picking Sonnet 4.5 silently get 3.5. OpenRouter does expose Claude 4.x slugs; map to those (e.g. anthropic/claude-sonnet-4.5).

3. <AvatarImage src=\"assets/admin.png\"> in user-nav.tsx
Path is relative (no leading /). On a deep route like /watch/123 it resolves to /watch/123/assets/admin.png and 404s. This PR only re-flowed the JSX, but worth fixing while you're in the file.

🟡 Code quality

4. Broad any usage in app/api/chat/route.ts
let model;, providerOptions: any, streamConfig: any — the AI SDK exports concrete types for these (LanguageModel, etc.). Easy to type properly and worth doing — the switch over selectedModel already has the shape of a discriminated union.

5. createReport does dynamic imports on every call (lib/ai-tools.ts)
Every invocation await import(\"marked\"), \"@tiptap/*\". On serverless this adds latency to a tool call that's already on the critical chat path. Hoist these to top-level imports — app/api/reports/route.ts already imports them statically, so there's no bundle-split reason to defer.

6. alert() for user-facing errors
watch/page.tsx (×4) and stream/page.tsx (×5) use alert() on fetch failures. The app already mounts <Toaster /> from sonner in app/layout.tsx — use toast.error(...) for consistency with the rest of the surface.

7. Duplicated headings
On database/page.tsx and search/page.tsx the <PageHeader title=\"…\" /> is immediately followed by a <SurfaceTitle> with the same text. Either drop the inner title or differentiate them.

🟠 Performance

8. MiniBrailleBg on hover is expensive
25 × 45 = 1,125 cells, full grid rebuild + React re-render every 50ms while hovered (~22 reconciles/sec) for a decorative background. Consider rendering into a <canvas> or a single <pre> with textContent writes via a ref, skipping React.

9. ButtonBrailleBg runs unconditionally
Animates 400 cells at 20 fps for the lifetime of the pricing section, even when the user has scrolled past or the tab is hidden. At minimum gate it with an IntersectionObserver / document.visibilityState.

10. useChartAnimation uses module-level mutable state
playedChartIds is module-scoped, so once a user leaves /stats and comes back, charts re-mount but their IDs are still in the set → no replay. Probably intentional within a session, but worth a comment; if "play once per page-load" is the intent, sessionStorage is more discoverable than a hidden global.

🔐 Security / correctness

11. Stream key in URL query string
app/(dashboard)/stream/page.tsx line 186 puts the Mux stream key in a WebSocket URL query param. The code redacts it for the console log (good) but query params land in server access logs and proxies. Prefer sending the key as the first WS message after open.

12. next.config.ts keeps ignoreBuildErrors / ignoreDuringBuilds
Out of scope for this PR, but with this much new code the TS/ESLint bypasses are increasingly risky. Worth a follow-up to clear them.

♿ Accessibility

13. Click-to-watch results in search/page.tsx
<Surface onClick={handleResultClick}> makes the whole card a click target but doesn't add role=\"button\", tabIndex={0}, or keyboard handlers. The biome-ignore on pricing-section.tsx (decorative hover) is fine, but search results are real interactions.

14. <img> instead of <Image> in search results
You're already using next/image elsewhere; the inline thumbnails would benefit from optimization and explicit dimensions to avoid CLS.

✅ Things I liked

  • The new Surface/PageContainer/PageHeader primitives are small, composable, and used consistently. Big readability win across the dashboard pages.
  • Landing page hero copy is sharp and the typography hierarchy is much cleaner than before.
  • The (Demo …) badge integration is well-thought-out (modulo bug Add claude GitHub actions 1761371115609 #1).
  • Pricing dialog states "Coming soon" instead of pretending to take payment — appropriately honest.
  • Good use of Suspense boundaries around useSearchParams consumers.

🧪 Test coverage

No tests were added or modified; the repo doesn't appear to have a test suite, so this is consistent with existing practice. The env-flag plumbing (#1) would be a great candidate for the first unit test.

Overall: design refresh is in good shape — the blocker is the DEMO_MODE flag plumbing (#1), since several new gates silently no-op in production demo builds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant