diff --git a/app/festival-submission/teknav-app.jsx b/app/festival-submission/teknav-app.jsx
new file mode 100644
index 0000000..27cf085
--- /dev/null
+++ b/app/festival-submission/teknav-app.jsx
@@ -0,0 +1,201 @@
+// teknav-app.jsx — Main App Router (ES module)
+import { useEffect } from 'react';
+import { createRoot } from 'react-dom/client';
+import {
+ NavProvider, AuthProvider, ToastProvider,
+ Header, Footer, EmptyState, useNav, InstallPrompt, PushNotificationPrompt, useAuth,
+} from './teknav-ui.jsx';
+import { HomePage } from './teknav-home.jsx';
+import { EnglishHomePage } from './teknav-en.jsx';
+import { ArticleListPage, ArticleDetail } from './teknav-articles.jsx';
+import { CategoryPage, TagPage, GlossaryPage, SearchPage, AuthorsListPage, AuthorProfilePage, SeriesPage, TopicHubPage, NewsletterArchivePage, NewsletterIssuePage, JobsPage, CoursesPage, MembershipPage, MembershipSuccessPage, PrivacyPolicyPage, TermsOfUsePage, AboutPage, ContactPage, ToolsPage, NlpNormalizerPage, RagCalculatorPage, TekRagHubPage, ComparisonPage, DirectoryListPage, CompanyProfilePage, ComparisonsListPage, StoriesListPage } from './teknav-pages.jsx';
+import { LoginPage } from './teknav-auth.jsx';
+import { AdminPanel, WriterWorkspace } from './teknav-admin.jsx';
+import { UserProfilePage } from './teknav-profile.jsx';
+import { SeoManager } from './src/lib/seo.jsx';
+
+function App() {
+ const { page } = useNav();
+
+ const route = () => {
+ if (page === '/' || page === '') return ;
+ if (page === '/en' || page === '/en/') return ;
+ if (page === '/articles') return ;
+ if (page.startsWith('/article/')) return ;
+ if (page.startsWith('/category/')) return ;
+ if (page.startsWith('/tag/')) return ;
+ if (page.startsWith('/glossary/')) return ;
+ if (page.startsWith('/topics/')) return ;
+ if (page.startsWith('/series/')) return ;
+ if (page === '/newsletter') return ;
+ if (page === '/jobs') return ;
+ if (page === '/courses') return ;
+ if (page === '/membership') return ;
+ if (page === '/tools') return ;
+ if (page === '/tools/nlp-normalizer') return ;
+ if (page === '/tools/rag-calculator') return ;
+ if (page === '/tekrag-persian') return ;
+ if (page.startsWith('/newsletter/')) return ;
+ if (page === '/search') return ;
+ if (page.startsWith('/search?q=')) {
+ const q = decodeURIComponent(page.replace('/search?q=', ''));
+ return ;
+ }
+ if (page === '/authors') return ;
+ if (page.startsWith('/author/')) return ;
+ if (page.startsWith('/profile/@')) return ;
+ if (page === '/login') return ;
+ if (page === '/writer') return ;
+ if (page === '/admin') return ;
+ if (page === '/privacy') return ;
+ if (page === '/terms') return ;
+ if (page === '/about') return ;
+ if (page === '/contact') return ;
+ if (page === '/compare') return ;
+ if (page.startsWith('/compare/')) return ;
+ if (page === '/stories') return ;
+ if (page === '/directory') return ;
+ if (page.startsWith('/directory/')) return ;
+ // 404
+ return (
+
+
+
+ );
+ };
+
+ // /embed/* is served by embed.html (second Vite entry) — not handled in this SPA
+
+ const isAdmin = page === '/admin';
+ const isLogin = page === '/login';
+
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+function NavConsumer({ isAdmin, isLogin, route }) {
+ const { page } = useNav();
+ const isAdminPage = page === '/admin';
+ const isLoginPage = page === '/login';
+ const isEnglishPage = page === '/en' || page === '/en/';
+ return (
+ <>
+ {!isAdminPage && !isLoginPage && !isEnglishPage && }
+ {route()}
+ {!isAdminPage && !isLoginPage && !isEnglishPage && }
+ >
+ );
+}
+
+// Mount — wrap everything in providers from the outside
+function Root() {
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+function InnerApp() {
+ const { page } = useNav();
+ const { user } = useAuth();
+
+ useEffect(() => {
+ const report = (payload) => {
+ try {
+ navigator.sendBeacon?.('/api/v1/errors', new Blob([JSON.stringify(payload)], { type: 'application/json' })) ||
+ fetch('/api/v1/errors', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), keepalive: true });
+ } catch {}
+ };
+ const onError = (event) => report({
+ message: event.message || 'window.error',
+ source: event.filename,
+ stack: event.error?.stack,
+ path: window.location.pathname + window.location.hash,
+ userAgent: navigator.userAgent,
+ });
+ const onRejection = (event) => report({
+ message: String(event.reason?.message || event.reason || 'unhandledrejection'),
+ stack: event.reason?.stack,
+ path: window.location.pathname + window.location.hash,
+ userAgent: navigator.userAgent,
+ });
+ window.addEventListener('error', onError);
+ window.addEventListener('unhandledrejection', onRejection);
+ return () => {
+ window.removeEventListener('error', onError);
+ window.removeEventListener('unhandledrejection', onRejection);
+ };
+ }, []);
+
+ const isAdmin = page === '/admin';
+ const isLogin = page === '/login';
+ const isEnglish = page === '/en' || page === '/en/';
+
+ const renderPage = () => {
+ if (page === '/' || page === '') return ;
+ if (page === '/en' || page === '/en/') return ;
+ if (page === '/articles') return ;
+ if (page.startsWith('/article/')) return ;
+ if (page.startsWith('/category/')) return ;
+ if (page.startsWith('/tag/')) return ;
+ if (page.startsWith('/glossary/')) return ;
+ if (page.startsWith('/topics/')) return ;
+ if (page.startsWith('/series/')) return ;
+ if (page === '/newsletter') return ;
+ if (page === '/jobs') return ;
+ if (page === '/courses') return ;
+ if (page === '/membership') return ;
+ if (page === '/tools') return ;
+ if (page === '/tools/nlp-normalizer') return ;
+ if (page === '/tools/rag-calculator') return ;
+ if (page === '/tekrag-persian') return ;
+ if (page === '/membership/success' || page.startsWith('/membership/success?')) return ;
+ if (page.startsWith('/newsletter/')) return ;
+ if (page === '/search') return ;
+ if (page.startsWith('/search?q=')) return ;
+ if (page === '/authors') return ;
+ if (page.startsWith('/author/')) return ;
+ if (page.startsWith('/profile/@')) return ;
+ if (page === '/login') return ;
+ if (page === '/writer') return ;
+ if (page === '/admin') return ;
+ if (page === '/privacy') return ;
+ if (page === '/terms') return ;
+ if (page === '/about') return ;
+ if (page === '/contact') return ;
+ if (page.startsWith('/compare/')) return ;
+ return (
+
+
+
+ );
+ };
+ // /embed/* is served by embed.html (second Vite entry) — not handled in this SPA
+
+ return (
+ <>
+
+ {!isAdmin && !isLogin && !isEnglish && }
+ {renderPage()}
+ {!isAdmin && !isLogin && !isEnglish && }
+
+
+ >
+ );
+}
+
+const container = document.getElementById('root');
+const reactRoot = createRoot(container);
+reactRoot.render();