Code Review, Architecture Analysis & PRD Assessment
A hidden element labeled "NVISION" triggers handleDevExport on click. This function:
api-platform.sticklight.com using credentials: 'include'This is a Sticklight platform dev utility left in production code. The credentials: 'include' flag transmits authentication cookies to a third-party domain — a cross-site credential exposure risk.
Remove handleDevExport and the hidden NVISION button entirely. ✓ Fixed — removed in deployment commit.
The component calls useReducedMotion(), then conditionally returns JSX early, and only after that calls useEffect and useCallback. React requires all hooks to be called unconditionally in the same order on every render.
Move the reduced-motion guard inside the useEffect/animation logic, or restructure so all hooks run unconditionally.
The form's handleSubmit calls setSent(true) and resets after 4 seconds. A comment reads: "In production, hook up to a real API". The form collects name, phone, email, company, and message — and silently discards all of it. Users see a success confirmation but their message is never delivered.
Connect to a real backend: Resend, SendGrid, Formspree, or a Cloudflare Worker that emails submissions.
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768; const NODE_COUNT = isMobile ? 30 : 60; const CONN_DIST = isMobile ? 120 : 160;
These values are computed once at module evaluation time, not inside any React hook. They never update on resize or orientation change.
Compute inside a useState/useEffect + resize listener, or use a media query hook.
Four legacy service pages use [email protected] via window.open(). All other contact surfaces (Footer, CTASection, ContactPage) use [email protected]. Messages sent to the wrong address may be permanently lost.
Align all CTA links to one authoritative address. Replace window.open('mailto:...') with <a href="mailto:...">.
Both components define SVG <defs> with hardcoded IDs. SVG id attributes must be document-unique. PlanBLogo.tsx already solves this with a per-instance uid via useRef.
Generate unique suffix per instance using useRef(() => Math.random().toString(36).slice(2)).
Runs an unbounded requestAnimationFrame loop with no prefers-reduced-motion check. Other components (NeuralCanvas, SVGOrbitSystem) respect this. The global MotionConfig only covers Framer Motion, not raw rAF loops.
Reads window.scrollY 60 times/second even when idle. A passive scroll event listener has zero cost at rest.
Uses role="status" with randomly generated values presented as real-time AI system metrics. Screen readers announce meaningless numbers as genuine data. Violates WCAG 3.3.2.
Uses role="menu" and role="menuitem" — these are for application context menus, not site navigation. Requires arrow-key/Home/End/type-ahead keyboard behavior that isn't implemented.
theme.css declares --font-sans: 'Heebo', 'Inter', system-ui but index.html has no font link. Typography silently falls back to system-ui on every platform.
Six legacy pages use direct Navbar import, bg-slate-950 background, cyan/blue accents, and inline footers — completely different from the modern pages' design system.
14 explicit routes, no <Route path="*"> fallback. Invalid URLs render a blank white page.
Canvas dimensions set once at mount. No resize listener. Device rotation or browser resize leaves canvas clipped.
Math.random() called inside whileInView produces different bar heights on every render/re-mount.
Fully implemented but not imported anywhere. PageLayout.tsx handles transitions directly.
Defined but never imported. TrustBar.tsx and NumbersStrip.tsx duplicate the same counter animation logic.
Uses mr-[0.3em] instead of logical me-[0.3em]. In RTL, margin-right adds space on the wrong side.
Only <title> and basic description. No Open Graph, Twitter Card, canonical link, or JSON-LD schema.
TrustBar respects reduced motion; LogoCloud (near-identical marquee) hardcodes repeat: Infinity.
Decorative background numbers positioned with physical left-8 instead of logical start-8.
Same tracking ID ev_050b10447f on two different elements. Sticklight analytics events will be misattributed.
The Plan B website was built on Sticklight (a Lovable-like AI code generation platform) using React 18 + Vite 6 + Tailwind CSS v4 + Framer Motion. The demo impressed the client, but the production code has structural problems.
| Metric | Value | Assessment |
|---|---|---|
| JS Bundle | 614 KB | Too large — 10x what's needed for static pages |
| Components | 60+ | Many duplicates (4 service components doing the same thing) |
| Issues Found | 22 | Including 1 critical security backdoor |
| SSR/SSG | None | Client-side only — blank page until 614KB JS loads |
| Code Splitting | None | All 14 pages bundled together |
| Contact Form | Fake | Silently discards submissions |
| Design Systems | Two | 6 legacy pages on completely different visual system |
| Build Config | Missing | No package.json, tsconfig, or vite.config shipped |
Sticklight is good for demos — which is exactly how it was used. But the output is:
{"path":"...","content":"..."} JSON, not actual source codedata-ev-id tracking attributes on every element, Sticklight API backdoor in Footer| Aspect | Current (Sticklight) | Recommended (Astro) |
|---|---|---|
| Components | 60+ generated | ~15 intentional |
| Bundle per page | 614KB everywhere | 0-50KB (JS only where needed) |
| SEO | Client-side, Google sees blank | Pre-rendered HTML, fully indexable |
| Content editing | Edit TSX files | Edit Markdown/MDX files |
| Contact form | Fake | Working from day 1 |
| Time to first paint | ~2-3 seconds | ~0.3 seconds |
| Footer change | Touch 6+ files | Touch 1 file |
Astro ships zero JavaScript by default. The animated hero and contact form become React "islands" — only those components load JS. Static pages (services, about, privacy) are pure HTML/CSS with zero runtime cost.
The demo served its purpose — the client said yes. Now the site needs to be rebuilt properly for production. The current React SPA approach is the wrong architecture for a marketing site, but adding lazy loading and fixing the 22 issues would make it acceptable. For the v2.0 SaaS platform (see PRD analysis), the architecture needs to be fundamentally different.
The PRD describes a completely different product from the current demo site. It's no longer a brochure — it's a full SaaS platform with multi-tenant customer portal, license management, billing, and AI agents.
The PRD is surprisingly well-architected for a non-developer-written document. The multi-tenant RLS approach, licensing handshake, and API versioning are industry-standard patterns done right. But there are gaps and risks.
Shared database with RLS is exactly the right call. Per-tenant databases are a maintenance nightmare. The schema design (Organizations → Profiles → Licenses) is clean. The RLS policy example is correct:
CREATE POLICY tenant_isolation_policy ON licenses
USING (org_id = auth.jwt() ->> 'org_id');
This means even if the frontend code has bugs, the database itself blocks cross-tenant data access. This is the Supabase/AWS standard approach.
The SIEM validation flow (POST to /v1/validate with license_key + machine_id → signed JWT valid for 24h) is well-designed. The 72-hour offline grace period is a good practical decision — it prevents customer disruption from temporary network issues while still enforcing license compliance.
All licensing API under /v1/ from day one. This means v2 can coexist without breaking deployed SIEM systems. The specific error codes (ERR_EXPIRED, ERR_QUOTA_EXCEEDED) are also correct — much better than generic 401/403.
The PRD explicitly states the frontend never touches the DB directly (only through Supabase Client with RLS). This is the right security posture for a multi-tenant platform.
The PRD mixes three different products into one:
These should be separate deployments:
| What | Tech | URL |
|---|---|---|
| Marketing site | Astro (static) | plan-b.systems |
| Customer portal | Next.js (SSR) | app.plan-b.systems |
| API | Edge Functions | api.plan-b.systems |
Mixing a marketing site with a SaaS dashboard in the same app creates conflicting performance requirements. The marketing site needs to be fast and SEO-friendly. The dashboard needs real-time data and authenticated routes.
The PRD suggests "Astro 5.0 or Next.js". For the marketing site, Astro is better. For the SaaS portal, Next.js is the clear winner:
Astro is wrong for the portal — it's designed for content sites, not authenticated multi-tenant dashboards.
The PRD says the bot should perform "Function Calling" like "generate a new license key". This is extremely dangerous without guardrails:
The PRD says: "When I'm at a client, I don't need presentations, the site IS the presentation." This means the marketing site needs to be spectacular — heavy animations, Three.js, premium feel. But the customer portal needs to be functional — fast, clear, data-dense.
These are opposing requirements. The marketing site should wow; the portal should work. Trying to make the portal "impressive" usually makes it slow and confusing. Separate the two.
The PRD correctly marks usage-based billing as uncertain. For an early-stage SaaS, start with fixed-tier pricing (Basic/Pro/Enterprise already defined in the schema). Add usage-based billing later when you have enough customers to justify the engineering cost. Stripe metered billing works but adds significant complexity in tracking, threshold alerts, and edge cases (what happens when a customer disputes usage data?).
The PRD mentions Vercel hosting but doesn't address:
/v1/validate endpoint will be hammered by SIEM systems every 24hThe current site says "cybersecurity/SIEM" but Plan B does AI integration, bots, agents, Claude Code courses, Monday automation, enterprise integration. The PRD doesn't address the content gap — whoever builds v2 needs a content brief, not just a tech spec.
| Layer | Technology | Why |
|---|---|---|
| Marketing site | Astro 5 + React islands | Fast, SEO-friendly, animations where needed |
| Customer portal | Next.js 15 (App Router) | SSR, auth middleware, API routes, real-time dashboard |
| Database | Supabase (PostgreSQL + RLS) | As specified — correct choice |
| Auth | Clerk (with Organizations) | Multi-tenant auth out of the box, better than Supabase Auth for this use case |
| API | Next.js API Routes + Edge | License validation needs low latency globally |
| AI Agent | Claude API + pgvector RAG | Function calling with strict role-based authorization |
| Billing | Stripe (fixed tiers first) | Start simple, add metered billing in v2.1 |
| Hosting | Vercel (portal) + CF Pages (marketing) | Best-in-class for each use case |
This is a solid foundation written by someone who understands enterprise software. The multi-tenant architecture, licensing handshake, and API design are correct. The main risk is scope — this is 3 products (marketing site + SaaS portal + API service) described as one. Separate them architecturally, start with the marketing site + licensing API, and add the customer portal as phase 2.
The biggest gap: the AI agent needs a security design document before implementation. Function calling that creates licenses and sends invoices requires explicit authorization flows, audit logging, and rate limiting — none of which are specified.
הקמת פלטפורמת SaaS מרכזית עבור Plan-b.systems שתשמש כחלון הראווה של החברה, מרכז ניהול לקוחות מרובי דיירים (Multi-tenant), ומנגנון הנפקת רישיונות עבור מערכות ה-SIEM וה-AI של החברה.
הערה למפתח: סעיפים 3.1 ו-3.2 נתונים לשיקול דעתך המקצועי, בתנאי שיעמדו בדרישות הביצועים וה-UX המפורטות.
אפשרויות מומלצות: Astro 5.0 או Next.js.
דרישת סף: תמיכה מלאה ב-Server Side Rendering וביצועי Lighthouse (Performance) מעל לציון 90.
אפשרויות מומלצות: Tailwind CSS בשילוב Shadcn UI או ספריה דומה.
דרישת סף: מימוש שפת עיצוב פרימיום (Dark Mode, Glassmorphism), תמיכה ב-RTL מלא ואנימציות חלקות.
בסיס נתונים: Supabase (PostgreSQL).
אבטחה: חובת שימוש ב-Row Level Security (RLS) למניעת זליגת מידע בין לקוחות שונים.
אירוח (Hosting): Vercel. (אופציה!)
אימות משתמשים: Supabase Auth או Clerk.
בידוד נתונים: ארכיטקטורה קשיחה שבה לקוח רואה אך ורק את הישויות (מפתחות, חשבוניות, לוגים) המשויכות לארגון שלו בלבד.
ניהול הרשאות: הפרדה בין מנהל לקוח (Admin) לבין משתמש קצה בארגון. אפשרות לתת גישה לקריאה בלבד ולתת גישה לבודקים שונים מטעם הלקוח.
ניהול רישיונות: הנפקת מפתחות API וניהול תוקף המנוי.
ממשק אימות (Validation Endpoint): חשיפת API מאובטח שישמש את מערכת ה-SIEM החיצונית לאימות הרישוי בזמן אמת.
תשלומים מתחדשים: חיוב חודשי מתחדש מבוסס שימוש או מנוי קבוע.
אינטגרציה: חיבור לספק תשלומים (כגון Stripe או ספק מקומי) דרך Webhooks. (נושא פתוח לדיון, גם ספק סליקה כגון טרנזילה יכול לעבוד.)
צ'אט חכם: שילוב בוט AI המחובר לנתוני הלקוח ומאפשר שאילתות על סטטוס הרישוי, השירותים וניתוח נתונים בסיסי. תשובות הבוט רק על המידע של אותו לקוח (מולטי טננט AI).
תנועה וגרפיקה: האתר חייב לכלול אלמנטים גרפיים מודרניים המשרים תחושת הייטק יוקרתית.
תגובתיות: פידבק ויזואלי מהיר לכל פעולת משתמש (לחיצה, טעינה, הצלחת פעולה).
אנחנו לא נפתח בסיס נתונים נפרד לכל לקוח (יקר ומסובך לתחזוקה), אלא נשתמש ב-Shared Database, Separate Schema/Row Isolation.
Table: Organizations (Tenants)
Table: Profiles (Users)
Table: Licenses (The SIEM Keys)
המתכנת חייב להגדיר ב-Postgres מדיניות שאומרת:
CREATE POLICY tenant_isolation_policy ON licenses USING (org_id = auth.jwt() ->> 'org_id');
זה מבטיח שגם אם יש באג בקוד ה-Frontend, ה-DB עצמו יחסום ניסיון של משתמש לראות רישיון שלא שייך ל-org_id שלו.
כאן אנחנו מגדירים איך ה-SIEM (שמותקן אצל הלקוח או בענן) מוודא שהוא רשאי לעבוד.
api.plan-b.systems/v1/validate.המנגנון יתבסס על Asymmetric Key Validation או Signed JWTs.
לא בטוח שנלך על זה.
במערכת SIEM, המחיר בדרך כלל נגזר מנפח דאטה.
ניתן לביצוע? כן. הוכח במערכות כמו Datadog ו-Sentry.
הבוט צריך להיות Context-Aware:
מגבלה: הבוט לא "לומד" לבד בזמן אמת, הוא משתמש רק במידע שסיפקנו לו ב-Prompt.
האתר הנוכחי יכול לשרת כאתר שיווקי זמני — אחרי תיקון 22 הבאגים. אבל הוא לא יכול להיות הבסיס לפלטפורמת SaaS כפי שה-PRD מתאר. אלה שני מוצרים שונים לחלוטין.
הבעלים רוצה שלושה דברים במקביל:
אתר עם אנימציות פרימיום, Three.js, תחושת הייטק — שאפשר לפתוח אצל לקוח במקום מצגת PowerPoint. זה מה שיש היום (בערך), ועם תיקונים זה יכול לעבוד.
מערכת שבה כל לקוח נכנס לאזור שלו, רואה את הרישיונות שלו, מנהל את המנויים שלו, מדבר עם בוט AI שמכיר אותו. זה לא אתר — זה מוצר תוכנה.
Endpoint שמערכות ה-SIEM של הלקוחות פונות אליו כל 24 שעות כדי לוודא שהרישיון עדיין תקף. זה שירות Backend טהור — אין לו שום קשר לעיצוב האתר.
| שאלה | תשובה |
|---|---|
| האם האתר הנוכחי יכול לשמש כאתר שיווקי? | כן — אחרי תיקון הבאגים, הסרת הBackdoor (כבר נעשה), חיבור טופס יצירת קשר אמיתי, ותיקוני SEO. |
| האם האתר הנוכחי יכול לשמש כפורטל לקוחות? | לא — אין בו שום תשתית ל-Auth, Multi-tenant, DB, או API. זה אתר סטטי עם אנימציות. |
| האם צריך לזרוק הכל ולבנות מחדש? | לא בהכרח — אפשר להשאיר את האתר השיווקי ולבנות את הפורטל בנפרד. |
תיקון האתר הנוכחי — הוא ישמש כאתר שיווקי זמני:
תוצאה: אתר שיווקי עובד שאפשר להראות ללקוחות.
בניית אתר שיווקי חדש ב-Astro — יחליף את האתר הנוכחי:
זה מתיישב לגמרי עם ה-PRD — ה-PRD אומר "Astro 5.0 או Next.js" וזה בדיוק מה שמוצע.
בניית פורטל לקוחות ב-Next.js על app.plan-b.systems:
ה-PRD הוא מסמך מצוין. הארכיטקטורה (Multi-tenant, RLS, Licensing Handshake) — הכל נכון. מה שצריך לשנות זה לא את מה לבנות, אלא את איך לבנות:
/v1/validate כל 24 שעות. ב-100 לקוחות זה 100 בקשות ביום. ב-10,000 לקוחות — צריך CDN/Edge.| אפשרות | מה זה אומר | המלצה |
|---|---|---|
| א. להישאר עם מה שיש | לתקן 22 באגים, לחבר טופס, להוסיף SEO | טוב לטווח קצר (1-2 חודשים). לא פתרון קבוע. |
| ב. Astro + Next.js (ההצעה שלי) | אתר שיווקי חדש + פורטל לקוחות נפרד | המלצה. מתיישב עם ה-PRD ומפריד נכון בין שיווק ל-SaaS. |
| ג. ללכת לפי ה-PRD כמות שהוא | לבנות הכל כפרויקט אחד | מסוכן. ניסיון לשים שיווק + SaaS + API באותו פרויקט ייצור מפלצת. |
ה-PRD הוא בסיס מצוין. הקונספט נכון — Plan B צריך יותר מאתר שיווקי. הוא צריך פורטל לקוחות, ניהול רישיונות, ובוט AI. אבל צריך לבנות את זה כשלושה מוצרים נפרדים, לא כפרויקט אחד מונוליטי.
התוכנית המומלצת: עכשיו — לתקן את האתר הנוכחי כך שיעבוד. במקביל — להתחיל לבנות אתר שיווקי ב-Astro. אחר כך — פורטל לקוחות ב-Next.js. ה-PRD מנחה את הכיוון, אנחנו רק מפרידים את הביצוע לשלבים הגיוניים.
22 initial code review issues + 10 multi-agent review fixes = 32 total. Zero animations harmed. Two build verifications passed, deployed to CF Pages.
The prefersReduced check caused an early return before hooks were called. React requires hooks to run unconditionally in the same order every render.
Moved the guard inside each useEffect and useCallback. All hooks now run unconditionally; the reduced-motion check only affects the effect body.
// Before: early return BEFORE hooks = BUG
if (prefersReduced) return <span>...</span>;
useEffect(() => { ... }); // hooks after conditional = crash
// After: guard inside effects
useEffect(() => {
if (prefersReduced) return;
// observer logic...
}, [delay, prefersReduced]);
const isMobile = window.innerWidth < 768 at module scope means SSR crashes and the value never updates.
Moved isMobile detection inside initNodes() using the canvas width parameter. connDist is now computed each frame inside the animation loop using sizeRef.current.w.
Replaced the continuous requestAnimationFrame loop with a passive scroll event listener. Zero CPU cost at rest.
// Before: rAF loop running 60fps forever
const tick = () => { ...; requestAnimationFrame(tick); };
// After: fires only on actual scroll
window.addEventListener('scroll', onScroll, { passive: true });
Homepage loaded eagerly (first paint). All 13 other pages now use React.lazy() + Suspense with a dark background fallback. Each page is a separate chunk — only downloaded when navigated to.
Build output confirms 13 separate page chunks (14-26KB each) instead of one 500KB+ bundle.
Pre-computed bar heights array using useMemo. Values are now stable across re-renders — no more layout thrashing or visual jitter.
Added window.addEventListener('resize', resize) with proper cleanup in the useEffect return.
Changed role="status" aria-label="..." to aria-hidden="true". Screen readers no longer announce meaningless random metrics.
Changed mobile menu from role="menu" to role="dialog" aria-label="תפריט ניווט". Removed all role="menuitem" from links. Navigation links should use role="link" (default for <a>), not application menu semantics.
Added useReducedMotion() hook. When reduced motion is preferred, the animation loop never starts — the waveform renders static.
Added useRef(Math.random().toString(36).slice(2,8)) to generate unique IDs per instance. All url(#...) references, gradient IDs, and filter IDs now use the unique suffix.
// SVGWaveform: wf-grad-{id}, wf-glow-{id}
// SVGBrainCircuit: bc-grad-stroke-{uid}, bc-glow-{uid},
// bc-particle-glow-{uid}, bc-node-grad-{uid}
Changed mr-[0.3em] to me-[0.3em] (logical margin-end property). Correct spacing in both LTR and RTL contexts.
All CTA buttons now use window.location.href='mailto:[email protected]' instead of window.open('mailto:[email protected]', '_blank'). Also fixed the window.open pattern (mailto shouldn't open in new tab).
Changed left-8 to start-8 on all decorative card number elements. Uses CSS logical property inset-inline-start — positions correctly in both RTL and LTR contexts.
https://plan-b.systemsAdded <Route path="*" element={<NotFound />} /> with a styled Hebrew 404 page and a "back to home" link.
Two elements had data-ev-id="ev_050b10447f". Removed the duplicate from the AI Demo section wrapper.
src/components/PageTransition.tsx — defined but never importedsrc/hooks/useCounter.ts — defined but never importedtsconfig.node.json: Added "composite": true and "emitDeclarationOnly": true (required by TypeScript project references)AIPage.tsx: Fixed GlowCard color prop type from string to the correct union type@types/node for Vite config path / __dirname supportdist/index.html 1.81 kB │ gzip: 0.77 kB dist/assets/index-*.css 161.45 kB │ gzip: 18.70 kB dist/assets/index-*.js (main) 402.53 kB │ gzip: 126.64 kB + 13 lazy-loaded page chunks (14-26 kB each) + 10 shared code chunks (0.3-6 kB each) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Total: 29 files — Build time: 2.78s
Key improvement: Homepage loads only the main bundle (402KB → 127KB gzipped). All other pages load on-demand as separate chunks.
After the initial 22 fixes, 4 parallel Sonnet-powered review agents (code-reviewer, silent-failure-hunter, type-design-analyzer, code-simplifier) identified 10 additional issues:
Code splitting with React.lazy() can fail if the user's browser has a cached HTML pointing to old chunk hashes after a deploy. Without an error boundary, the entire app crashes to a white screen.
Added a ChunkErrorBoundary class component wrapping Suspense. On chunk load failure, shows a Hebrew error page with a reload button instead of crashing.
class ChunkErrorBoundary extends Component<
{ children: ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() { return { hasError: true }; }
render() {
if (this.state.hasError) {
return (<div>...<button onClick={reload}>רענון</button></div>);
}
return this.props.children;
}
}
Added useReducedMotion() hook and if (prefersReduced) return; guard at the top of the particle animation useEffect. Also updated the dependency array to [prefersReduced] so the effect restarts if the preference changes.
Changed aria-haspopup="true" → aria-haspopup="dialog" on the hamburger button. ARIA spec requires the haspopup value to match the popup's role.
Added aria-modal="true" to the mobile menu dialog. This tells assistive tech the dialog is modal and content behind it should not be interacted with.
Changed mr-2 → me-2 on the ⌘K command bar button. Uses CSS margin-inline-end — correct in both LTR and RTL.
Restructured the useEffect: moved wave config before the reduced-motion check, added static path initialization before the early return. Reduced-motion users now see a static waveform instead of an empty container.
// Set static initial paths so reduced-motion users don't see an empty gap
waves.forEach((w, i) => {
const path = pathRefs.current[i];
if (path) path.setAttribute('d', buildWave(w.offset, w.amplitude, w.frequency, 0));
});
if (prefersReduced) return; // animation never starts
Removed the window.addEventListener('resize', resize) and simplified to a one-time canvas size set. The matrix rain is a ~4-second easter egg — resize handling was unnecessary complexity. Also fixed a dangling reference to the removed resize function in cleanup.
Changed catch (e) { } to catch { } — the error variable was unused. Added descriptive comment explaining why the catch is intentional (path geometry not ready during initial render).
The code-simplifier agent merged duplicate useEffect calls that had identical dependency arrays and similar logic.
After removing the resize listener (R7), the cleanup function still referenced resize. Removed window.removeEventListener('resize', resize) from the cleanup return — build now passes cleanly.
dist/index.html 1.81 kB │ gzip: 0.77 kB dist/assets/index-*.css 161.47 kB │ gzip: 18.71 kB dist/assets/index-*.js (main) 403.50 kB │ gzip: 126.98 kB + 13 lazy-loaded page chunks (14-26 kB each) + 10 shared code chunks (0.3-6 kB each) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Total: 29 files — Build time: 2.27s TypeScript: 0 errors, 0 warnings
Full compliance overhaul: Accessibility (IS 5568 / WCAG 2.0 AA) + Privacy (תיקון 13). 7 parallel agents, 44 files modified, 1035+ lines inserted. Cookie consent banner, accessibility toolbar, contrast fixes, reduced-motion support, form accessibility, footer RTL fixes.
CSS rules in accessibility.css toggle via html.a11y-* class selectors. The toolbar panel itself is excluded from all overrides via .a11y-toolbar-panel comprehensive reset. z-index 9999, floating on left side.
Each a11y feature is a CSS class toggled on document.documentElement: html.a11y-text-increase, html.a11y-high-contrast, html.a11y-grayscale, html.a11y-highlight-links, html.a11y-stop-animations, html.a11y-readable-font, html.a11y-large-cursor, html.a11y-line-spacing.
The toolbar panel is excluded from its own overrides with a comprehensive reset: font-size, filter, font-family, letter-spacing, word-spacing, line-height, cursor.
text-white/50 → text-white/75 — raised from ~2:1 to ~4.5:1 against dark bgtext-white/55 → text-white/80text-slate-400 → text-slate-300hover:text-white/50 → hover:text-white (AIPage tech stack fix)All changes verified against WCAG AA 4.5:1 minimum for normal text. Applied across HomePage, AIPage, ServicesPage, AboutPage, SIEMPage, LabPage, ContactPage, and all shared components.
useReducedMotion() disables tilt/glow/scale effectsaria-invalid + aria-describedby on all form fieldsrole="alert" on error messages for screen reader announcementsinline-flex → flex flex-col gap-3 — links were running together on one linedir="ltr" on +972 phone number linkdir="rtl" wrapper with dir="ltr" span for "PLAN-B Systems"dir="ltr" span<p role="heading"> to semantic <h2>Both new components integrated into PageLayout.tsx, rendered after <Footer />. They are global — appear on every page without per-page imports.
Built and deployed to Cloudflare Pages. All changes pushed to GitHub repo Nadav-Fux/Plan-B-Website on main branch. 7 parallel agents used for the compliance scan.
10 March 2026 — 6 Sonnet agents ran simultaneously to audit the full website, followed by implementation of critical fixes.
| Agent | Domain | Findings |
|---|---|---|
| Agent 1 | Accessibility (IS 5568 / WCAG 2.1 AA) | 8 issues |
| Agent 2 | Security Headers & Vulnerabilities | 5 issues |
| Agent 3 | Performance & Bundle Size | 6 issues |
| Agent 4 | SEO & Discoverability | 4 issues |
| Agent 5 | Code Quality & Dead Code | 5 issues |
| Agent 6 | Mobile Responsiveness | 4 issues |
The contact form had a fake handleSubmit using setTimeout — no data was ever sent. Replaced with real Web3Forms API integration:
https://api.web3forms.com/submit with access keyפנייה חדשה מ-{name} – plan-b.co.il✓ Fixed — deployed
Privacy policy claimed data was stored on "AWS EU servers" for "12 months" — none of this was true. Web3Forms sends email only, no database exists.
Fixed sections:
✓ Fixed — deployed
The SIEM page had its own Navbar import and a simplified custom footer (different links, no badges). It was missing: CookieConsent, AccessibilityToolbar, CustomCursor, CommandBar, ScrollProgress, EasterEgg.
<div> + <Navbar> + custom footer with <PageLayout> wrapperwindow.location.href='mailto:...' to navigate('/contact')✓ Fixed — deployed
The interactive particle starfield (canvas-based, 60 nodes, mouse-tracking physics, connection lines, colored traveling packets) was only on the homepage hero section.
<NeuralCanvas className="!fixed" /> to PageLayout — now renders on ALL pagesposition: fixed via getComputedStyle(), uses window.innerWidth/Height for correct viewport sizing✓ Fixed — deployed
ContactPage shows +972-3-123-4567 and CommandBar shows 03-123-4567. Both are placeholder numbers that don't work. Real number is 050-9959779 (appears in Footer and PrivacyPolicy).
✗ Open — queued for next session
These 5 pages render without the shared layout — missing Navbar, Footer, CookieConsent, AccessibilityToolbar, CustomCursor, NeuralCanvas. Same issue we fixed for SIEM.
✗ Open — queued for next session
new Date() for "last updated" — should be hardcoded✗ Open — queued for next session
No robots.txt, no sitemap.xml. Pages use default Vite favicon. No structured data (JSON-LD) for local business.
✗ Open — queued for next session
Missing Content-Security-Policy, X-Content-Type-Options, X-Frame-Options, Permissions-Policy. Cloudflare Pages supports a _headers file in the public directory.
✗ Open — queued for next session
Code quality audit found 23 component files that are imported nowhere. Dead code bloat.
✗ Open — queued for next session
Built with npm run build (Vite 6, 3.14s). Deployed to Cloudflare Pages via npx wrangler pages deploy dist --project-name plan-b-website --branch main. Committed to GitHub: 73336bd. GitHub Release v1.1.0 published.