diff --git a/README.md b/README.md index 2c8901e..a1ce21d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This contains everything you need to run your app locally. -View your app in AI Studio: https://ai.studio/apps/drive/1uP9tButqScpBrwNQpEZ2zZlMyFj07HNy +View your app in AI Studio: https://ai.studio/apps/2b60b327-c6bb-444a-9f8d-507be656501a ## Run Locally diff --git a/components/Footer.tsx b/components/Footer.tsx index 02677cc..e9d5ad6 100644 --- a/components/Footer.tsx +++ b/components/Footer.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Linkedin, Mail, ShoppingCart, BookOpen, TreeDeciduous, Globe, ShoppingBag } from 'lucide-react'; +import { Linkedin, Mail, ShoppingCart, BookOpen, TreeDeciduous, ShoppingBag } from 'lucide-react'; import { Link } from 'react-router-dom'; import { useLanguage } from '../contexts/LanguageContext'; diff --git a/components/Illustrations.tsx b/components/Illustrations.tsx index ba12e83..7c58f0d 100644 --- a/components/Illustrations.tsx +++ b/components/Illustrations.tsx @@ -163,19 +163,20 @@ export const TrunkPath: React.FC<{ className?: string }> = ({ className = "" }) */ export const TheGrowingTree: React.FC<{ progress: number; className?: string }> = ({ progress, className = "" }) => { // progress goes from 0.0 to 1.0 + const safeProgress = (isNaN(progress) || !isFinite(progress)) ? 0 : progress; // Calculations for staggered animation // Trunk grows continuously: 0 -> 1 - const trunkProgress = Math.min(1, Math.max(0, progress * 1.1)); + const trunkProgress = Math.min(1, Math.max(0, safeProgress * 1.1)); // Roots spread at start: 0 -> 0.2 - const rootsProgress = Math.min(1, Math.max(0, progress * 5)); + const rootsProgress = Math.min(1, Math.max(0, safeProgress * 5)); // Branches spread in middle: 0.4 -> 0.8 - const branchesProgress = Math.min(1, Math.max(0, (progress - 0.4) * 2.5)); + const branchesProgress = Math.min(1, Math.max(0, (safeProgress - 0.4) * 2.5)); // Leaves/Canopy at end: 0.7 -> 1.0 - const canopyProgress = Math.min(1, Math.max(0, (progress - 0.7) * 3.3)); + const canopyProgress = Math.min(1, Math.max(0, (safeProgress - 0.7) * 3.3)); // Stroke Dash Array Helpers const draw = (val: number, max: number) => ({ @@ -196,12 +197,12 @@ export const TheGrowingTree: React.FC<{ progress: number; className?: string }> {/* 2. TRUNK (Spanning Level 1 to 4) */} - 0.5 ? "text-brand-600 transition-colors duration-1000" : "text-slate-300 transition-colors duration-1000"}> + 0.5 ? "text-brand-600 transition-colors duration-1000" : "text-slate-300 transition-colors duration-1000"}> {/* Main Central Trunk Line */} 0.5 ? 4 : 2} + strokeWidth={safeProgress > 0.5 ? 4 : 2} strokeLinecap="round" fill="none" style={{ diff --git a/components/Navbar.tsx b/components/Navbar.tsx index 5c2199d..ff24e69 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -1,118 +1,155 @@ - -import React, { useState } from 'react'; -import { useLocation, Link } from 'react-router-dom'; -import { Menu, X, TreeDeciduous, BookOpen, Globe } from 'lucide-react'; +import React, { useState, useEffect } from 'react'; +import { Link, useLocation } from 'react-router-dom'; +import { Menu, X, TreePine } from 'lucide-react'; import { useLanguage } from '../contexts/LanguageContext'; const Navbar: React.FC = () => { const [isOpen, setIsOpen] = useState(false); + const [scrolled, setScrolled] = useState(false); const location = useLocation(); const { language, setLanguage, ui } = useLanguage(); - const isActive = (path: string) => { - return location.pathname === path - ? 'text-brand-700 font-semibold bg-brand-50 rounded-lg px-3 py-2' - : 'text-slate-600 hover:text-brand-700 hover:bg-slate-50 rounded-lg px-3 py-2 transition-all'; - }; + useEffect(() => { + const handleScroll = () => { + setScrolled(window.scrollY > 20); + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); - const toggleLanguage = () => { - setLanguage(language === 'de' ? 'en' : 'de'); + // Close mobile menu when route changes + useEffect(() => { + setIsOpen(false); + }, [location.pathname]); + + const navLinks = [ + { name: ui('nav.home'), path: '/' }, + { name: ui('nav.framework'), path: '/framework' }, + { name: ui('nav.app'), path: '/app' }, + { name: ui('nav.blog'), path: '/blog' }, + ]; + + const isActive = (path: string) => { + if (path === '/' && location.pathname !== '/') return false; + return location.pathname.startsWith(path); }; return ( - -
- - -
+ {/* Mobile Menu Button */} + - {/* Mobile menu */} + {/* Mobile Navigation */} {isOpen && ( -
-
- setIsOpen(false)} className="block px-3 py-3 text-base font-medium text-slate-700 hover:text-brand-600 hover:bg-slate-50 rounded-lg">{ui("nav.home")} - setIsOpen(false)} className="block px-3 py-3 text-base font-medium text-slate-700 hover:text-brand-600 hover:bg-slate-50 rounded-lg">{ui("nav.framework")} - setIsOpen(false)} className="block px-3 py-3 text-base font-medium text-slate-700 hover:text-brand-600 hover:bg-slate-50 rounded-lg">{ui("nav.assessment")} - setIsOpen(false)} className="block px-3 py-3 text-base font-medium text-slate-700 hover:text-brand-600 hover:bg-slate-50 rounded-lg">{ui("nav.blog")} - setIsOpen(false)} className="block px-3 py-3 text-base font-medium text-slate-700 hover:text-brand-600 hover:bg-slate-50 rounded-lg">{ui("nav.author")} - setIsOpen(false)} className="block px-3 py-3 text-base font-medium text-slate-700 hover:text-brand-600 hover:bg-slate-50 rounded-lg">{ui("nav.app")} - +
+ {navLinks.map((link) => ( + + {link.name} + + ))} + +
+ Language: + +
)} - + ); }; diff --git a/components/SEO.tsx b/components/SEO.tsx index d8ecc65..bf24cdc 100644 --- a/components/SEO.tsx +++ b/components/SEO.tsx @@ -1,65 +1,49 @@ - import React from 'react'; import { Helmet } from 'react-helmet-async'; -import { useLocation } from 'react-router-dom'; interface SEOProps { - title: string; - description: string; - type?: 'website' | 'article'; + title?: string; + description?: string; + name?: string; + type?: string; + image?: string; imageUrl?: string; + url?: string; + schema?: any; + lang?: string; author?: string; publishedTime?: string; - schema?: Record; } -const SEO: React.FC = ({ - title, - description, - type = 'website', - imageUrl = 'https://media.springernature.com/full/springer-static/cover-hires/book/978-3-658-51040-4?as=webp', - author = 'Serge Baumberger', - publishedTime, - schema -}) => { - const location = useLocation(); - const siteTitle = "Quality Tree Framework"; - const fullTitle = title === siteTitle ? title : `${title} | ${siteTitle}`; - - // Force the main domain - const domain = "https://www.quality-tree.com"; - - // Construct the URL. Since the app uses HashRouter, we construct the URL - // to include the hash so deep links work correctly when clicked from Search. - const currentPath = location.pathname === '/' ? '' : `/#${location.pathname}`; - const currentUrl = `${domain}${currentPath}`; +export default function SEO({ title, description, name, type, image, imageUrl, url, schema, lang = 'de', author, publishedTime }: SEOProps) { + const siteTitle = title ? `${title} | Quality Tree Framework` : 'Quality Tree Framework'; + const siteDesc = description || 'Quality Tree Framework'; + const siteImage = image || imageUrl || 'https://www.quality-tree.com/og-image.jpg'; + const siteUrl = url || (typeof window !== 'undefined' ? window.location.href : 'https://www.quality-tree.com'); return ( - - {/* Standard Metadata */} - {fullTitle} - - - - + + {siteTitle} + + {/* Open Graph / Facebook */} - - - - - - - {publishedTime && } - {publishedTime && } + + + + + + {type === 'article' && publishedTime && } + {type === 'article' && author && } {/* Twitter */} - - - - + + + + + - {/* Schema Markup */} + {/* Structured Data */} {schema && ( -
- + \ No newline at end of file diff --git a/metadata.json b/metadata.json index cc03c6b..93a7a3e 100644 --- a/metadata.json +++ b/metadata.json @@ -1,5 +1,5 @@ { - "name": "quality-tree.com", + "name": "Remix: quality-tree.com", "description": "The official digital representation of the Quality Tree Framework by Serge Baumberger. A strategic blueprint for software quality, growth, and automation.", "requestFramePermissions": [] } \ No newline at end of file diff --git a/netlify.toml b/netlify.toml index f71fcb0..12accea 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,18 +1,10 @@ - [build] command = "npm run build" publish = "dist" [build.environment] - # Disable secrets scanning to avoid false positives from Firebase SDK node_modules SECRETS_SCAN_SMART_DETECTION_ENABLED = "false" -[dev] - command = "npm run dev" - targetPort = 5173 - -# Catch-all redirect for React Router (Single Page Application Support) -# Das ist wichtig, damit z.B. /blog/mein-artikel beim Neuladen funktioniert. [[redirects]] from = "/*" to = "/index.html" diff --git a/package-lock.json b/package-lock.json index ad89393..71882cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "canvas-confetti": "^1.9.2", "firebase": "^10.9.0", + "framer-motion": "^12.35.1", "lucide-react": "^0.363.0", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -22,7 +23,8 @@ "@types/react-dom": "^18.2.22", "@vitejs/plugin-react": "^4.2.1", "typescript": "^5.2.2", - "vite": "^5.2.0" + "vite": "^5.2.0", + "vite-plugin-singlefile": "^2.3.0" } }, "node_modules/@babel/code-frame": { @@ -307,405 +309,210 @@ "node": ">=6.9.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { + "node_modules/@esbuild/linux-x64": { "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ - "arm" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "android" + "linux" ], "engines": { "node": ">=12" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/analytics": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.8.tgz", + "integrity": "sha512-CVnHcS4iRJPqtIDc411+UmFldk0ShSK3OB+D0bKD8Ck5Vro6dbK5+APZpkuWpbfdL359DIQUnAaMLE+zs/PVyA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/analytics-compat": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.14.tgz", + "integrity": "sha512-unRVY6SvRqfNFIAA/kwl4vK+lvQAL2HVcgu9zTrUtTyYDmtIt/lOuHJynBMYEgLnKm39YKBDhtqdapP2e++ASw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/analytics": "0.10.8", + "@firebase/analytics-types": "0.8.2", + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } + "node_modules/@firebase/analytics-types": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.2.tgz", + "integrity": "sha512-EnzNNLh+9/sJsimsA/FGqzakmrAUKLeJvjRHlg8df1f97NLUlFidk9600y0ZgWOp3CAxn6Hjtk+08tixlUOWyw==", + "license": "Apache-2.0" }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/app": { + "version": "0.10.13", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.10.13.tgz", + "integrity": "sha512-OZiDAEK/lDB6xy/XzYAyJJkaDqmQ+BCtOEPLqFvxWKUz5JbBmej7IiiRHdtiIOD/twW7O5AxVsfaaGA/V1bNsA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "idb": "7.1.1", + "tslib": "^2.1.0" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/app-check": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.8.tgz", + "integrity": "sha512-O49RGF1xj7k6BuhxGpHmqOW5hqBIAEbt2q6POW0lIywx7emYtzPDeQI+ryQpC4zbKX646SoVZ711TN1DBLNSOQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/app-check-compat": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.15.tgz", + "integrity": "sha512-zFIvIFFNqDXpOT2huorz9cwf56VT3oJYRFjSFYdSbGYEJYEaXjLJbfC79lx/zjx4Fh+yuN8pry3TtvwaevrGbg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check": "0.8.8", + "@firebase/app-check-types": "0.5.2", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.2.tgz", + "integrity": "sha512-LMs47Vinv2HBMZi49C09dJxp0QT5LwDzFaVGf/+ITHe3BlIhUiLNttkATSXplc89A2lAaeTqjgqVkiRfUGyQiQ==", + "license": "Apache-2.0" }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } + "node_modules/@firebase/app-check-types": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.2.tgz", + "integrity": "sha512-FSOEzTzL5bLUbD2co3Zut46iyPWML6xc4x+78TeaXMSuJap5QObfb+rVvZJtla3asN4RwU7elaQaduP+HFizDA==", + "license": "Apache-2.0" }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/app-compat": { + "version": "0.2.43", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.43.tgz", + "integrity": "sha512-HM96ZyIblXjAC7TzE8wIk2QhHlSvksYkQ4Ukh1GmEenzkucSNUmUX4QvoKrqeWsLEQ8hdcojABeCV8ybVyZmeg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app": "0.10.13", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } + "node_modules/@firebase/app-types": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.2.tgz", + "integrity": "sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ==", + "license": "Apache-2.0" }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/auth-compat": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.14.tgz", + "integrity": "sha512-2eczCSqBl1KUPJacZlFpQayvpilg3dxXLy9cSMTKtQMTQSmondUtPI47P3ikH3bQAXhzKLOE+qVxJ3/IRtu9pw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth": "1.7.9", + "@firebase/auth-types": "0.12.2", + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/auth-compat/node_modules/@firebase/auth": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.7.9.tgz", + "integrity": "sha512-yLD5095kVgDw965jepMyUrIgDklD6qH/BZNHeKOgvu7pchOKNjVM+zQoOVYJIKWMWOWBq8IRNVU6NXzBbozaJg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^1.18.1" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.3.tgz", + "integrity": "sha512-Fc9wuJGgxoxQeavybiuwgyi+0rssr76b+nHpj+eGhXFYAdudMWyfBHvFL/I5fEHniUM/UQdFzi9VXJK2iZF7FQ==", + "license": "Apache-2.0" }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/auth-types": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.2.tgz", + "integrity": "sha512-qsEBaRMoGvHO10unlDJhaKSuPn4pyoTtlQuP1ghZfzB6rNQPuhp/N/DcFZxm9i4v0SogjCbf9reWupwIvfmH6w==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "node_modules/@firebase/component": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.9.tgz", + "integrity": "sha512-gm8EUEJE/fEac86AvHn8Z/QW8BvR56TBw3hMW0O838J/1mThYQXAIQBgUv75EqlCZfdawpWLrKt1uXvp9ciK3Q==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" } }, - "node_modules/@firebase/analytics": { - "version": "0.10.8", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.8.tgz", - "integrity": "sha512-CVnHcS4iRJPqtIDc411+UmFldk0ShSK3OB+D0bKD8Ck5Vro6dbK5+APZpkuWpbfdL359DIQUnAaMLE+zs/PVyA==", + "node_modules/@firebase/data-connect": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.1.0.tgz", + "integrity": "sha512-vSe5s8dY13ilhLnfY0eYRmQsdTbH7PUFZtBbqU6JVX/j8Qp9A6G5gG6//ulbX9/1JFOF1IWNOne9c8S/DOCJaQ==", "license": "Apache-2.0", "dependencies": { + "@firebase/auth-interop-types": "0.2.3", "@firebase/component": "0.6.9", - "@firebase/installations": "0.6.9", "@firebase/logger": "0.4.2", "@firebase/util": "1.10.0", "tslib": "^2.1.0" @@ -714,255 +521,76 @@ "@firebase/app": "0.x" } }, - "node_modules/@firebase/analytics-compat": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.14.tgz", - "integrity": "sha512-unRVY6SvRqfNFIAA/kwl4vK+lvQAL2HVcgu9zTrUtTyYDmtIt/lOuHJynBMYEgLnKm39YKBDhtqdapP2e++ASw==", + "node_modules/@firebase/database": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.8.tgz", + "integrity": "sha512-dzXALZeBI1U5TXt6619cv0+tgEhJiwlUtQ55WNZY7vGAjv7Q1QioV969iYwt1AQQ0ovHnEW0YW9TiBfefLvErg==", "license": "Apache-2.0", "dependencies": { - "@firebase/analytics": "0.10.8", - "@firebase/analytics-types": "0.8.2", + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/auth-interop-types": "0.2.3", "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", "@firebase/util": "1.10.0", + "faye-websocket": "0.11.4", "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" } }, - "node_modules/@firebase/analytics-types": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.2.tgz", - "integrity": "sha512-EnzNNLh+9/sJsimsA/FGqzakmrAUKLeJvjRHlg8df1f97NLUlFidk9600y0ZgWOp3CAxn6Hjtk+08tixlUOWyw==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/app": { - "version": "0.10.13", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.10.13.tgz", - "integrity": "sha512-OZiDAEK/lDB6xy/XzYAyJJkaDqmQ+BCtOEPLqFvxWKUz5JbBmej7IiiRHdtiIOD/twW7O5AxVsfaaGA/V1bNsA==", + "node_modules/@firebase/database-compat": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.8.tgz", + "integrity": "sha512-OpeWZoPE3sGIRPBKYnW9wLad25RaWbGyk7fFQe4xnJQKRzlynWeFBSRRAoLE2Old01WXwskUiucNqUUVlFsceg==", "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.9", + "@firebase/database": "1.0.8", + "@firebase/database-types": "1.0.5", "@firebase/logger": "0.4.2", "@firebase/util": "1.10.0", - "idb": "7.1.1", "tslib": "^2.1.0" } }, - "node_modules/@firebase/app-check": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.8.tgz", - "integrity": "sha512-O49RGF1xj7k6BuhxGpHmqOW5hqBIAEbt2q6POW0lIywx7emYtzPDeQI+ryQpC4zbKX646SoVZ711TN1DBLNSOQ==", + "node_modules/@firebase/database-types": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.5.tgz", + "integrity": "sha512-fTlqCNwFYyq/C6W7AJ5OCuq5CeZuBEsEwptnVxlNPkWCo5cTTyukzAHRSO/jaQcItz33FfYrrFk1SJofcu2AaQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-types": "0.9.2", + "@firebase/util": "1.10.0" + } + }, + "node_modules/@firebase/firestore": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.3.tgz", + "integrity": "sha512-NwVU+JPZ/3bhvNSJMCSzfcBZZg8SUGyzZ2T0EW3/bkUeefCyzMISSt/TTIfEHc8cdyXGlMqfGe3/62u9s74UEg==", "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.9", "@firebase/logger": "0.4.2", "@firebase/util": "1.10.0", - "tslib": "^2.1.0" + "@firebase/webchannel-wrapper": "1.0.1", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "tslib": "^2.1.0", + "undici": "6.19.7" + }, + "engines": { + "node": ">=10.10.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, - "node_modules/@firebase/app-check-compat": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.15.tgz", - "integrity": "sha512-zFIvIFFNqDXpOT2huorz9cwf56VT3oJYRFjSFYdSbGYEJYEaXjLJbfC79lx/zjx4Fh+yuN8pry3TtvwaevrGbg==", + "node_modules/@firebase/firestore-compat": { + "version": "0.3.38", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.38.tgz", + "integrity": "sha512-GoS0bIMMkjpLni6StSwRJarpu2+S5m346Na7gr9YZ/BZ/W3/8iHGNr9PxC+f0rNZXqS4fGRn88pICjrZEgbkqQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/app-check": "0.8.8", - "@firebase/app-check-types": "0.5.2", "@firebase/component": "0.6.9", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.10.0", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/app-check-interop-types": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.2.tgz", - "integrity": "sha512-LMs47Vinv2HBMZi49C09dJxp0QT5LwDzFaVGf/+ITHe3BlIhUiLNttkATSXplc89A2lAaeTqjgqVkiRfUGyQiQ==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/app-check-types": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.2.tgz", - "integrity": "sha512-FSOEzTzL5bLUbD2co3Zut46iyPWML6xc4x+78TeaXMSuJap5QObfb+rVvZJtla3asN4RwU7elaQaduP+HFizDA==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/app-compat": { - "version": "0.2.43", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.43.tgz", - "integrity": "sha512-HM96ZyIblXjAC7TzE8wIk2QhHlSvksYkQ4Ukh1GmEenzkucSNUmUX4QvoKrqeWsLEQ8hdcojABeCV8ybVyZmeg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app": "0.10.13", - "@firebase/component": "0.6.9", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.10.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-types": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.2.tgz", - "integrity": "sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/auth-compat": { - "version": "0.5.14", - "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.14.tgz", - "integrity": "sha512-2eczCSqBl1KUPJacZlFpQayvpilg3dxXLy9cSMTKtQMTQSmondUtPI47P3ikH3bQAXhzKLOE+qVxJ3/IRtu9pw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/auth": "1.7.9", - "@firebase/auth-types": "0.12.2", - "@firebase/component": "0.6.9", - "@firebase/util": "1.10.0", - "tslib": "^2.1.0", - "undici": "6.19.7" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/auth-compat/node_modules/@firebase/auth": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.7.9.tgz", - "integrity": "sha512-yLD5095kVgDw965jepMyUrIgDklD6qH/BZNHeKOgvu7pchOKNjVM+zQoOVYJIKWMWOWBq8IRNVU6NXzBbozaJg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.9", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.10.0", - "tslib": "^2.1.0", - "undici": "6.19.7" - }, - "peerDependencies": { - "@firebase/app": "0.x", - "@react-native-async-storage/async-storage": "^1.18.1" - }, - "peerDependenciesMeta": { - "@react-native-async-storage/async-storage": { - "optional": true - } - } - }, - "node_modules/@firebase/auth-interop-types": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.3.tgz", - "integrity": "sha512-Fc9wuJGgxoxQeavybiuwgyi+0rssr76b+nHpj+eGhXFYAdudMWyfBHvFL/I5fEHniUM/UQdFzi9VXJK2iZF7FQ==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/auth-types": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.2.tgz", - "integrity": "sha512-qsEBaRMoGvHO10unlDJhaKSuPn4pyoTtlQuP1ghZfzB6rNQPuhp/N/DcFZxm9i4v0SogjCbf9reWupwIvfmH6w==", - "license": "Apache-2.0", - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } - }, - "node_modules/@firebase/component": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.9.tgz", - "integrity": "sha512-gm8EUEJE/fEac86AvHn8Z/QW8BvR56TBw3hMW0O838J/1mThYQXAIQBgUv75EqlCZfdawpWLrKt1uXvp9ciK3Q==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/util": "1.10.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/data-connect": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.1.0.tgz", - "integrity": "sha512-vSe5s8dY13ilhLnfY0eYRmQsdTbH7PUFZtBbqU6JVX/j8Qp9A6G5gG6//ulbX9/1JFOF1IWNOne9c8S/DOCJaQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/auth-interop-types": "0.2.3", - "@firebase/component": "0.6.9", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.10.0", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/database": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.8.tgz", - "integrity": "sha512-dzXALZeBI1U5TXt6619cv0+tgEhJiwlUtQ55WNZY7vGAjv7Q1QioV969iYwt1AQQ0ovHnEW0YW9TiBfefLvErg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-check-interop-types": "0.3.2", - "@firebase/auth-interop-types": "0.2.3", - "@firebase/component": "0.6.9", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.10.0", - "faye-websocket": "0.11.4", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database-compat": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.8.tgz", - "integrity": "sha512-OpeWZoPE3sGIRPBKYnW9wLad25RaWbGyk7fFQe4xnJQKRzlynWeFBSRRAoLE2Old01WXwskUiucNqUUVlFsceg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.9", - "@firebase/database": "1.0.8", - "@firebase/database-types": "1.0.5", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.10.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database-types": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.5.tgz", - "integrity": "sha512-fTlqCNwFYyq/C6W7AJ5OCuq5CeZuBEsEwptnVxlNPkWCo5cTTyukzAHRSO/jaQcItz33FfYrrFk1SJofcu2AaQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-types": "0.9.2", - "@firebase/util": "1.10.0" - } - }, - "node_modules/@firebase/firestore": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.3.tgz", - "integrity": "sha512-NwVU+JPZ/3bhvNSJMCSzfcBZZg8SUGyzZ2T0EW3/bkUeefCyzMISSt/TTIfEHc8cdyXGlMqfGe3/62u9s74UEg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.9", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.10.0", - "@firebase/webchannel-wrapper": "1.0.1", - "@grpc/grpc-js": "~1.9.0", - "@grpc/proto-loader": "^0.7.8", - "tslib": "^2.1.0", - "undici": "6.19.7" - }, - "engines": { - "node": ">=10.10.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/firestore-compat": { - "version": "0.3.38", - "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.38.tgz", - "integrity": "sha512-GoS0bIMMkjpLni6StSwRJarpu2+S5m346Na7gr9YZ/BZ/W3/8iHGNr9PxC+f0rNZXqS4fGRn88pICjrZEgbkqQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.9", - "@firebase/firestore": "4.7.3", - "@firebase/firestore-types": "3.0.2", + "@firebase/firestore": "4.7.3", + "@firebase/firestore-types": "3.0.2", "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, @@ -1289,465 +917,143 @@ "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" }, "engines": { - "node": ">=6" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause" - }, - "node_modules/@remix-run/router": { - "version": "1.23.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", - "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node": ">=6" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">=6.0.0" + } }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] + "license": "MIT" }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "license": "MIT" }, - "node_modules/@rollup/rollup-win32-x64-gnu": { + "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], @@ -1755,13 +1061,13 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ] }, - "node_modules/@rollup/rollup-win32-x64-msvc": { + "node_modules/@rollup/rollup-linux-x64-musl": { "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], @@ -1769,7 +1075,7 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ] }, "node_modules/@types/babel__core": { @@ -1926,6 +1232,19 @@ "node": ">=6.0.0" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -1961,9 +1280,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001777", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", - "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", + "version": "1.0.30001774", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", + "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", "dev": true, "funding": [ { @@ -2056,9 +1375,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.307", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", - "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", + "version": "1.5.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", "dev": true, "license": "ISC" }, @@ -2128,6 +1447,19 @@ "node": ">=0.8.0" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/firebase": { "version": "10.14.1", "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.14.1.tgz", @@ -2186,19 +1518,31 @@ } } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, + "node_modules/framer-motion": { + "version": "12.35.1", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.35.1.tgz", + "integrity": "sha512-rL8cLrjYZNShZqKV3U0Qj6Y5WDiZXYEM5giiTLfEqsIZxtspzMDCkKmrO5po76jWfvOg04+Vk+sfBvTD0iMmLw==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "dependencies": { + "motion-dom": "^12.35.1", + "motion-utils": "^12.29.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } } }, "node_modules/gensync": { @@ -2250,6 +1594,16 @@ "node": ">=8" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2325,6 +1679,35 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/motion-dom": { + "version": "12.35.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.35.1.tgz", + "integrity": "sha512-7n6r7TtNOsH2UFSAXzTkfzOeO5616v9B178qBIjmu/WgEyJK0uqwytCEhwKBTuM/HJA40ptAw7hLFpxtPAMRZQ==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.29.2" + } + }, + "node_modules/motion-utils": { + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz", + "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2352,9 +1735,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, @@ -2365,10 +1748,23 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -2640,6 +2036,19 @@ "node": ">=8" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -2766,6 +2175,23 @@ } } }, + "node_modules/vite-plugin-singlefile": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/vite-plugin-singlefile/-/vite-plugin-singlefile-2.3.0.tgz", + "integrity": "sha512-DAcHzYypM0CasNLSz/WG0VdKOCxGHErfrjOoyIPiNxTPTGmO6rRD/te93n1YL/s+miXq66ipF1brMBikf99c6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">18.0.0" + }, + "peerDependencies": { + "rollup": "^4.44.1", + "vite": "^5.4.11 || ^6.0.0 || ^7.0.0" + } + }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", diff --git a/package.json b/package.json index e621971..9397780 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "canvas-confetti": "^1.9.2", "firebase": "^10.9.0", + "framer-motion": "^12.35.1", "lucide-react": "^0.363.0", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -23,6 +24,7 @@ "@types/react-dom": "^18.2.22", "@vitejs/plugin-react": "^4.2.1", "typescript": "^5.2.2", - "vite": "^5.2.0" + "vite": "^5.2.0", + "vite-plugin-singlefile": "^2.3.0" } -} \ No newline at end of file +} diff --git a/pages/AdminCreatePost.tsx b/pages/AdminCreatePost.tsx index 087dd22..63e6d9a 100644 --- a/pages/AdminCreatePost.tsx +++ b/pages/AdminCreatePost.tsx @@ -1,4 +1,3 @@ // File removed by user request -import React from 'react'; const AdminCreatePost = () =>
Admin interface removed
; export default AdminCreatePost; diff --git a/pages/AppTool.tsx b/pages/AppTool.tsx index 0e6f584..1916abf 100644 --- a/pages/AppTool.tsx +++ b/pages/AppTool.tsx @@ -4,43 +4,55 @@ import confetti from 'canvas-confetti'; import { rawConfig } from '../data/appToolData'; import { useLanguage } from '../contexts/LanguageContext'; import SEO from '../components/SEO'; - -const ConnectingLines = ({ nodeRefs, hoveredNode, containerRef }: any) => { +import { AppToolCell, AppToolLane, LocalizedString } from '../src/types'; + +interface AppNode extends Omit { + title: string; + description: string; + level: number; + branchId: string; + row: number; + col: number; +} + +const ConnectingLines = ({ nodeRefs, hoveredNode, containerRef }: { nodeRefs: React.MutableRefObject>, hoveredNode: string | null, containerRef: React.RefObject }) => { const [lines, setLines] = useState([]); useLayoutEffect(() => { const updateGraph = () => { const newLines: React.ReactElement[] = []; - rawConfig.arrows.forEach((arrow: any) => { - const startEl = nodeRefs.current.get(arrow.from); - const endEl = nodeRefs.current.get(arrow.to); - - if (startEl && endEl) { - const startX = startEl.offsetLeft + startEl.offsetWidth / 2; - const startY = startEl.offsetTop + startEl.offsetHeight / 2; - const endX = endEl.offsetLeft + endEl.offsetWidth / 2; - const endY = endEl.offsetTop + endEl.offsetHeight / 2; - - const isHovered = hoveredNode === arrow.from || hoveredNode === arrow.to; - - const deltaX = endX - startX; - const midX = startX + (deltaX / 2); - - const d = `M ${startX} ${startY} C ${midX} ${startY}, ${midX} ${endY}, ${endX} ${endY}`; + if (rawConfig.arrows) { + rawConfig.arrows.forEach((arrow: { from: string; to: string }, index: number) => { + const startEl = nodeRefs.current.get(arrow.from); + const endEl = nodeRefs.current.get(arrow.to); - newLines.push( - - ); - } - }); + if (startEl && endEl) { + const startX = startEl.offsetLeft + startEl.offsetWidth / 2; + const startY = startEl.offsetTop + startEl.offsetHeight / 2; + const endX = endEl.offsetLeft + endEl.offsetWidth / 2; + const endY = endEl.offsetTop + endEl.offsetHeight / 2; + + const isHovered = hoveredNode === arrow.from || hoveredNode === arrow.to; + + const deltaX = endX - startX; + const midX = startX + (deltaX / 2); + + const d = `M ${startX} ${startY} C ${midX} ${startY}, ${midX} ${endY}, ${endX} ${endY}`; + + newLines.push( + + ); + } + }); + } setLines(newLines); }; updateGraph(); @@ -53,7 +65,7 @@ const ConnectingLines = ({ nodeRefs, hoveredNode, containerRef }: any) => { return {lines}; }; -const WarningModal = ({ data, onClose, nodes }: any) => { +const WarningModal = ({ data, onClose, nodes }: { data: { isOpen: boolean, missingNodes: string[] }, onClose: () => void, nodes: any[] }) => { if (!data.isOpen) return null; return (
@@ -85,7 +97,7 @@ const WarningModal = ({ data, onClose, nodes }: any) => { ); }; -const BranchInfoModal = ({ branch, onClose, t }: any) => { +const BranchInfoModal = ({ branch, onClose, t }: { branch: any, onClose: () => void, t: (s: string | LocalizedString) => string }) => { if (!branch) return null; return ( @@ -96,7 +108,7 @@ const BranchInfoModal = ({ branch, onClose, t }: any) => {
-

{branch.name}

+

{t(branch.name)}

-

{node.title}

+

{node.title}

{isNA &&
{ui("app.not_relevant")}
}
@@ -302,7 +315,7 @@ const DetailPopup = ({ node, onClose, onStatusChange, onOkrToggle, nodeState, is ); }; -const ListView = ({ branches, nodes, userState, onNodeClick, isGardener, onStatusChange, onBranchInfoClick }: any) => { +const ListView = ({ branches, nodes, userState, onNodeClick, isGardener, onStatusChange, onBranchInfoClick, t }: { branches: any[], nodes: AppNode[], userState: any, onNodeClick: (id: string) => void, isGardener: boolean, onStatusChange: (id: string, status: string) => void, onBranchInfoClick: (branch: any) => void, t: (s: any) => string }) => { const [collapsed, setCollapsed] = useState>(() => { const initial: Record = {}; branches.forEach((b: any) => initial[b.id] = true); @@ -346,8 +359,8 @@ const ListView = ({ branches, nodes, userState, onNodeClick, isGardener, onStatu
-

- {branch.name} +

+ {t(branch.name)}
@@ -414,12 +427,12 @@ const ListView = ({ branches, nodes, userState, onNodeClick, isGardener, onStatu }; const AppTool: React.FC = () => { - const { t, ui } = useLanguage(); + const { t, ui, language } = useLanguage(); const levels = Array.from({ length: 9 }, (_, i) => ({ id: i + 1, label: `${ui("app.level")} ${i + 1}` })); const icons = ["fa-cogs", "fa-vial", "fa-cube", "fa-rocket", "fa-robot", "fa-server", "fa-user-check", "fa-chart-line"]; - const appBranches = rawConfig.lanes.map((lane: any, index: number) => ({ + const appBranches = rawConfig.lanes.map((lane: AppToolLane, index: number) => ({ id: `branch-${index}`, name: lane.label, icon: icons[index] || "fa-circle", @@ -428,7 +441,7 @@ const AppTool: React.FC = () => { details: lane.details })); - const appNodes = rawConfig.cells.map((cell: any) => { + const appNodes: AppNode[] = rawConfig.cells.map((cell: AppToolCell) => { const match = cell.id.match(/R(\d+)C(\d+)/); let row = 0, col = 0; if (match) { @@ -436,18 +449,27 @@ const AppTool: React.FC = () => { col = parseInt(match[2]); } const branch = appBranches.find(b => row >= b.startRow && row <= b.endRow); + + // Helper to ensure LocalizedString structure + const ensureLocalized = (val: string | LocalizedString | undefined): LocalizedString => { + if (!val) return { de: '', en: '' }; + if (typeof val === 'string') return { de: val, en: val }; + return { de: val.de || '', en: val.en || val.de || '' }; + }; + return { - id: cell.id, title: t(cell.label), description: t(cell.tooltip), acceptanceCriteria: cell.acceptanceCriteria || [], - level: col, // Col is now 1-based, Level 1 is Col 1. - branchId: branch ? branch.id : null, + level: col - 1, + branchId: branch ? branch.id : '', row: row, col: col, - ...cell + ...cell, + label: ensureLocalized(cell.label), + tooltip: ensureLocalized(cell.tooltip) }; - }).filter(n => n.branchId !== null); + }).filter(n => n.branchId !== ''); const [activeNodeId, setActiveNodeId] = useState(null); const [selectedBranch, setSelectedBranch] = useState(null); @@ -461,11 +483,67 @@ const AppTool: React.FC = () => { const [projectData, setProjectData] = useState({ name: '', owner: '' }); const [isMobile, setIsMobile] = useState(false); + const [selectedMobileBranchId, setSelectedMobileBranchId] = useState(null); + + const renderNodeCard = (node: any) => { + const state = getNodeState(node.id); + const isCompleted = state.status === 'completed'; + const isInProgress = state.status === 'in-progress'; + const isLocked = state.status === 'locked'; + const isNotRelevant = state.status === 'not-relevant'; + + let cardClasses = "bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700 hover:border-slate-300 dark:hover:border-slate-600 shadow-sm"; + let textClasses = "text-slate-700 dark:text-slate-200"; + let statusIndicator = null; + + if (isNotRelevant) { + cardClasses = "bg-slate-50 dark:bg-slate-900/50 border-slate-200 dark:border-slate-800 border-dashed opacity-60"; + textClasses = "text-slate-400 dark:text-slate-600 line-through decoration-slate-300 dark:decoration-slate-700"; + } else if (isGardenerMode) { + if (isCompleted) { + cardClasses = "bg-emerald-50 dark:bg-emerald-900/20 border-emerald-500 dark:border-emerald-500 shadow-md ring-1 ring-emerald-500/20"; + textClasses = "text-emerald-900 dark:text-emerald-100 font-medium"; + statusIndicator = (
); + } else if (isInProgress) { + cardClasses = "bg-amber-50 dark:bg-amber-900/20 border-amber-400 dark:border-amber-500 shadow-md ring-1 ring-amber-400/20"; + textClasses = "text-amber-900 dark:text-amber-100 font-medium"; + statusIndicator = (
); + } else if (isLocked) { + cardClasses = "bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700 opacity-90"; + } + } else { + if (isCompleted) cardClasses = "bg-white dark:bg-slate-800 border-b-4 border-b-emerald-500 border-slate-200 dark:border-slate-700 shadow-sm"; + else if (isInProgress) cardClasses = "bg-white dark:bg-slate-800 border-b-4 border-b-amber-400 border-slate-200 dark:border-slate-700 shadow-sm"; + } + + return ( +
{ if(el) nodeRefs.current.set(node.id, el) }} + className={`relative group perspective w-full max-w-[160px] print:hidden z-10 transition-transform duration-200 hover:-translate-y-0.5`} + onMouseEnter={() => setHoveredNodeId(node.id)} + onMouseLeave={() => setHoveredNodeId(null)} + onClick={() => setActiveNodeId(node.id)}> +
+ {statusIndicator} +
+ L{node.level} + {isNotRelevant && } +
+

{node.title}

+
+
+ ) + }; const gridRef = useRef(null); const nodeRefs = useRef(new Map()); const fileInputRef = useRef(null); + const scrollGrid = (direction: number) => { + if (gridRef.current) { + gridRef.current.scrollBy({ left: direction * 300, behavior: 'smooth' }); + } + }; + useEffect(() => { const checkMobile = () => setIsMobile(window.innerWidth < 1024); checkMobile(); @@ -490,11 +568,6 @@ const AppTool: React.FC = () => { localStorage.setItem('qtree-project', JSON.stringify(projectData)); }, [userState, projectData]); - const scrollToTree = () => { - const target = isMobile ? document.getElementById('mobile-branch-selector') : gridRef.current; - target?.scrollIntoView({ behavior: 'smooth' }); - }; - const getPrerequisites = (nodeId: string) => { if (!rawConfig || !rawConfig.arrows) return []; return rawConfig.arrows @@ -631,6 +704,7 @@ const AppTool: React.FC = () => { @@ -640,8 +714,8 @@ const AppTool: React.FC = () => { setSelectedBranch(null)} t={t} /> )} - {activeNodeId && ( - setActiveNodeId(null)} onStatusChange={(id: string, status: string, data: any) => handleStatusChangeAttempt(id, status, data, () => setActiveNodeId(null))} onOkrToggle={(idx: number) => toggleNodeOkr(activeNodeId, idx)} nodeState={getNodeState(activeNodeId)} isGardener={isGardenerMode} isMobile={isMobile} allNodes={appNodes} userState={userState} onNavigate={setActiveNodeId} branches={appBranches} ui={ui} /> + {activeNode && ( + setActiveNodeId(null)} onStatusChange={(id: string, status: string, data: any) => handleStatusChangeAttempt(id, status, data, () => setActiveNodeId(null))} onOkrToggle={(idx: number) => toggleNodeOkr(activeNodeId!, idx)} nodeState={getNodeState(activeNodeId!)} isMobile={isMobile} allNodes={appNodes} userState={userState} onNavigate={setActiveNodeId} branches={appBranches} ui={ui} t={t} /> )}
@@ -699,7 +773,7 @@ const AppTool: React.FC = () => {
-
+
@@ -714,86 +788,85 @@ const AppTool: React.FC = () => {
{viewMode === 'map' ? ( -
-
-
- -
-
{ui("app.ast")}
- {levels.map(level => ( -
- {level.label} -
- ))} + <> +
+
+ {!selectedMobileBranchId ? ( +
+

{ui("app.discipline")}

{appBranches.map((branch: any) => ( - -
-
setSelectedBranch(branch)} - > -
-

{branch.name}

- -
-
- {levels.map(level => { - const cellNodes = appNodes.filter((n: any) => n.branchId === branch.id && n.level === level.id); - return ( -
- {cellNodes.map((node: any) => { - const state = getNodeState(node.id); - const isCompleted = state.status === 'completed'; - const isInProgress = state.status === 'in-progress'; - const isNotRelevant = state.status === 'not-relevant'; - let cardClasses = "bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700 hover:border-slate-300 dark:hover:border-slate-600 shadow-sm"; - let textClasses = "text-slate-700 dark:text-slate-200"; - let statusIndicator = null; - if (isNotRelevant) { - cardClasses = "bg-slate-50 dark:bg-slate-900/50 border-slate-200 dark:border-slate-800 border-dashed opacity-60"; - textClasses = "text-slate-400 dark:text-slate-600 line-through decoration-slate-300 dark:decoration-slate-700"; - } else if (isGardenerMode) { - if (isCompleted) { - cardClasses = "bg-emerald-50 dark:bg-emerald-900/20 border-emerald-500 dark:border-emerald-500 shadow-md ring-1 ring-emerald-500/20"; - textClasses = "text-emerald-900 dark:text-emerald-100 font-medium"; - statusIndicator = (
); - } else if (isInProgress) { - cardClasses = "bg-amber-50 dark:bg-amber-900/20 border-amber-400 dark:border-amber-500 shadow-md ring-1 ring-amber-400/20"; - textClasses = "text-amber-900 dark:text-amber-100 font-medium"; - statusIndicator = (
); - } - } else { - if (isCompleted) cardClasses = "bg-white dark:bg-slate-800 border-b-4 border-b-emerald-500 border-slate-200 dark:border-slate-700 shadow-sm"; - else if (isInProgress) cardClasses = "bg-white dark:bg-slate-800 border-b-4 border-b-amber-400 border-slate-200 dark:border-slate-700 shadow-sm"; - } - return ( -
{ if(el) nodeRefs.current.set(node.id, el) }} - className={`relative group perspective w-full max-w-[160px] print:hidden z-10 transition-transform duration-200 hover:-translate-y-0.5`} - onMouseEnter={() => setHoveredNodeId(node.id)} - onMouseLeave={() => setHoveredNodeId(null)} - onClick={() => setActiveNodeId(node.id)}> -
- {statusIndicator} -
- L{node.level} - {isNotRelevant && } -
-

{node.title}

-
-
- ) - })} -
- ); - })} -
+
setSelectedMobileBranchId(branch.id)} className="p-4 rounded bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 shadow-sm flex items-center space-x-4 cursor-pointer active:scale-95 transition-transform"> +
+

{t(branch.name)}

+
+
))} +
+ ) : ( +
+ +
+ {levels.map(level => { + const branchNodes = appNodes.filter((n: any) => n.branchId === selectedMobileBranchId && n.level === level.id); + if (branchNodes.length === 0) return null; + return ( +
+
+ {level.id} +
+
{branchNodes.map(node => renderNodeCard(node))}
+
+ ); + })}
+ )}
-
+
+
+ + +
+
+
+ +
+
{ui("app.ast")}
+ {levels.map(level => ( +
+ {level.label} +
+ ))} + {appBranches.map((branch: any) => ( + +
+
setSelectedBranch(branch)} + > +
+

{t(branch.name)}

+ +
+
+ {levels.map(level => { + const cellNodes = appNodes.filter((n: any) => n.branchId === branch.id && n.level === level.id); + return ( +
+ {cellNodes.map((node: any) => renderNodeCard(node))} +
+ ); + })} +
+ ))} +
+
+
+
+
+ ) : ( - handleStatusChangeAttempt(id, status)} onBranchInfoClick={(branch: any) => setSelectedBranch(branch)} /> + handleStatusChangeAttempt(id, status)} onBranchInfoClick={(branch: any) => setSelectedBranch(branch)} t={t} /> )}
diff --git a/pages/Author.tsx b/pages/Author.tsx index 9215e59..100e72c 100644 --- a/pages/Author.tsx +++ b/pages/Author.tsx @@ -1,151 +1,119 @@ - import React from 'react'; -import { Linkedin, Mail, Globe, Calendar, Video } from 'lucide-react'; -import SEO from '../components/SEO'; import { useLanguage } from '../contexts/LanguageContext'; +import SEO from '../components/SEO'; import ScrollReveal from '../components/ScrollReveal'; +import { Linkedin, Mail, Calendar, Video, ArrowRight } from 'lucide-react'; +import { Link } from 'react-router-dom'; const Author: React.FC = () => { - const { ui } = useLanguage(); - - const schema = { - "@context": "https://schema.org", - "@type": "Person", - "name": "Serge Baumberger", - "jobTitle": "Co-CEO Infometis AG", - "url": "https://www.quality-tree.com/author", - "image": "https://cdn.prod.website-files.com/659bd602c8644fb17135bbe7/660c27367443119851a48cd0_Swarmie%20Profile%20-%20Serge%20Wolf.png", - "sameAs": [ - "https://www.linkedin.com/in/sergewolf/", - "https://www.infometis.ch" - ] - }; + const { ui, language } = useLanguage(); return ( -
+
-
- - {/* Profile Card */} -
-
- - {/* Image Section */} -
- +
+ +
+

+ {ui("author.title")} +

+
+
+
+ +
+
+ +
+
Serge Baumberger -
-
-

Serge Baumberger

-

Author | Co-CEO Infometis AG

+
+

"Wir bauen keine Software, wir bauen Vertrauen."

+

Serge Baumberger

- -
+
+
+
+ +
+ +

+ {ui("author.subtitle")} +

+

+ {ui("author.role")} +

- {/* Biography Content */} -
- -
- {ui("author.badge")} -
+
+

+ {ui("author.bio.p1")} +

+

+ {ui("author.bio.p2")} +

+

+ {ui("author.bio.p3")} +

+
+ +
+
-
-

- {ui("author.quote_main")} -

-

- {ui("author.bio_p1")} -

-

- {ui("author.bio_p2")} -

-
+

+ {ui("author.contact.title")} +

+

+ {ui("author.contact.desc")} +

- {/* Booking & Social Integration */} - -
+
+ {ui("author.contact.email")} + - {/* Story Section */} -
-
- -

{ui("author.thanks_title")}

-
-

- {ui("author.thanks_p1")} -

-

- {ui("author.thanks_p2")} -

-
- -
-

{ui("author.quote_footer")}

-
-
- Serge Baumberger -
-
-
-
- -
- -
- Workshop Context -
-
-
-
-
-

{ui("author.next_talk")}

-

Swiss Testing Day '25

-
-
+ +
+ +
+ {ui("author.contact.linkedin")} + +
-
-
+
+
+
-
); diff --git a/pages/Blog.tsx b/pages/Blog.tsx index c53e23d..31b5f07 100644 --- a/pages/Blog.tsx +++ b/pages/Blog.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; -import { BlogPost } from '../types'; +import { BlogPost } from '../src/types'; import { fetchAllPosts } from '../services/blogService'; import { Calendar, User, ArrowRight, BookOpen, Search } from 'lucide-react'; import SEO from '../components/SEO'; @@ -31,7 +31,8 @@ const Blog: React.FC = () => { const [displayPosts, setDisplayPosts] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [loading, setLoading] = useState(true); - const { ui, t } = useLanguage(); + const [visibleCount, setVisibleCount] = useState(6); + const { ui, t, language } = useLanguage(); useEffect(() => { const loadPosts = async () => { @@ -45,11 +46,27 @@ const Blog: React.FC = () => { loadPosts(); }, []); - const filteredPosts = displayPosts.filter(post => - t(post.title).toLowerCase().includes(searchQuery.toLowerCase()) || - t(post.excerpt).toLowerCase().includes(searchQuery.toLowerCase()) || - t(post.content).toLowerCase().includes(searchQuery.toLowerCase()) - ); + const filteredPosts = React.useMemo(() => { + const query = searchQuery.toLowerCase(); + if (!query) return displayPosts; + + return displayPosts.filter(post => + t(post.title).toLowerCase().includes(query) || + t(post.excerpt).toLowerCase().includes(query) || + t(post.content).toLowerCase().includes(query) + ); + }, [displayPosts, searchQuery, t]); + + // Reset visible count when search query changes + useEffect(() => { + setVisibleCount(6); + }, [searchQuery]); + + const visiblePosts = filteredPosts.slice(0, visibleCount); + + const handleLoadMore = () => { + setVisibleCount(prev => prev + 6); + }; const schema = { "@context": "https://schema.org", @@ -65,13 +82,14 @@ const Blog: React.FC = () => { title={ui("seo.blog.title")} description={ui("blog.header_desc")} schema={schema} + lang={language} />
{/* Blog Header */}
- +
@@ -83,7 +101,7 @@ const Blog: React.FC = () => {
{/* Search Bar */} - +
@@ -106,16 +124,20 @@ const Blog: React.FC = () => { {loading ? : ( <> {filteredPosts.length > 0 ? ( -
- {filteredPosts.map((post, index) => ( - -
+ <> +
+ {visiblePosts.map((post, index) => ( + +
{post.imageUrl ? ( {t(post.title)} ) : (
@@ -155,6 +177,18 @@ const Blog: React.FC = () => { ))}
+ + {visibleCount < filteredPosts.length && ( +
+ +
+ )} + ) : (
diff --git a/pages/BlogPostDetail.tsx b/pages/BlogPostDetail.tsx index d4c18f5..dc32bd9 100644 --- a/pages/BlogPostDetail.tsx +++ b/pages/BlogPostDetail.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { useParams, Link } from 'react-router-dom'; import { fetchPostById } from '../services/blogService'; -import { BlogPost } from '../types'; +import { BlogPost } from '../src/types'; import { ArrowLeft, Calendar, User, Linkedin, Twitter, Mail, Share2, TreeDeciduous, ArrowRight } from 'lucide-react'; import SEO from '../components/SEO'; import { useLanguage } from '../contexts/LanguageContext'; @@ -36,7 +36,7 @@ const BlogPostDetail: React.FC = () => { const { id } = useParams<{ id: string }>(); const [post, setPost] = useState(undefined); const [loading, setLoading] = useState(true); - const { ui, t } = useLanguage(); + const { ui, t, language } = useLanguage(); useEffect(() => { const loadPost = async () => { @@ -155,6 +155,7 @@ const BlogPostDetail: React.FC = () => { author={post.author} publishedTime={post.date} schema={schema} + lang={language} /> {post.imageUrl && ( @@ -162,6 +163,9 @@ const BlogPostDetail: React.FC = () => { {postTitle}
diff --git a/pages/BranchDetail.tsx b/pages/BranchDetail.tsx index 223bbb7..7ee9677 100644 --- a/pages/BranchDetail.tsx +++ b/pages/BranchDetail.tsx @@ -6,10 +6,10 @@ import { ArrowLeft, CheckCircle2, ArrowRight, GitBranch, Microscope, Hammer, Rocket, Bot, Cloud, UserCheck, BarChart, Zap } from 'lucide-react'; -import { BranchDecoration, LeafIcon } from '../components/Illustrations'; +import { BranchDecoration } from '../components/Illustrations'; import SEO from '../components/SEO'; import { useLanguage } from '../contexts/LanguageContext'; -import { Level } from '../types'; +import { Level } from '../src/types'; interface LevelCardProps { level: Level; @@ -31,7 +31,7 @@ const LevelCard: React.FC = ({ level, visual, index }) => { useEffect(() => { const observer = new IntersectionObserver( - (entries) => { + (entries: IntersectionObserverEntry[]) => { entries.forEach((entry) => { if (entry.isIntersecting) { setIsVisible(true); @@ -99,13 +99,13 @@ const LevelCard: React.FC = ({ level, visual, index }) => { const BranchDetail: React.FC = () => { const { id } = useParams<{ id: string }>(); - const { t, ui } = useLanguage(); + const { t, ui, language } = useLanguage(); const branch = branches.find(b => b.id === id); if (!branch) { return (
- + Branch not found. Go back
); @@ -178,6 +178,7 @@ const BranchDetail: React.FC = () => { description={branchDesc} schema={branchSchema} type="article" + lang={language} />
diff --git a/pages/FAQ.tsx b/pages/FAQ.tsx index dbb57b1..f64d619 100644 --- a/pages/FAQ.tsx +++ b/pages/FAQ.tsx @@ -1,81 +1,72 @@ - import React from 'react'; -import SEO from '../components/SEO'; import { useLanguage } from '../contexts/LanguageContext'; -import { HelpCircle, Bot } from 'lucide-react'; +import SEO from '../components/SEO'; +import ScrollReveal from '../components/ScrollReveal'; const FAQ: React.FC = () => { const { ui } = useLanguage(); const faqs = [ - { q: ui("faq.q1"), a: ui("faq.a1") }, - { q: ui("faq.q2"), a: ui("faq.a2") }, - { q: ui("faq.q3"), a: ui("faq.a3") }, + { + question: ui("faq.q1"), + answer: ui("faq.a1") + }, + { + question: ui("faq.q2"), + answer: ui("faq.a2") + }, + { + question: ui("faq.q3"), + answer: ui("faq.a3") + }, + { + question: ui("faq.q4"), + answer: ui("faq.a4") + }, + { + question: ui("faq.q5"), + answer: ui("faq.a5") + }, + { + question: ui("faq.q6"), + answer: ui("faq.a6") + } ]; - // Generate FAQ Schema for Google and AI - const schema = { - "@context": "https://schema.org", - "@type": "FAQPage", - "mainEntity": faqs.map(item => ({ - "@type": "Question", - "name": item.q, - "acceptedAnswer": { - "@type": "Answer", - "text": item.a - } - })) - }; - return ( -
+
-
-
- + +
+

+ {ui("faq.title")} +

+

+ {ui("faq.subtitle")} +

+
-

{ui("faq.title")}

-

- {ui("faq.subtitle")} -

-
+
- {faqs.map((item, index) => ( -
-

- Q - {item.q} -

-
-

- {item.a} + {faqs.map((faq, index) => ( + +

+

+ {faq.question} +

+

+ {faq.answer}

-
+ ))}
- - {/* AI Context Hints - Visually identifiable block for AI optimization */} -
-
- AI Context & Metadata -
-

- Entity: Quality Tree Framework (QTF)
- Type: Software Quality Methodology
- Creator: Serge Baumberger
- Core Concept: Growth Metaphor (Roots, Trunk, Branches, Leaves)
- Publication: Springer Vieweg (2025) -

-
-
); diff --git a/pages/Framework.tsx b/pages/Framework.tsx index f11cdbb..0619440 100644 --- a/pages/Framework.tsx +++ b/pages/Framework.tsx @@ -21,12 +21,12 @@ const iconMap: Record { const [searchQuery, setSearchQuery] = useState(""); - const { t, ui } = useLanguage(); + const { t, ui, language } = useLanguage(); const filteredBranches = branches.filter(branch => t(branch.title).toLowerCase().includes(searchQuery.toLowerCase()) || t(branch.description).toLowerCase().includes(searchQuery.toLowerCase()) || - branch.levels.some(l => t(l.title).toLowerCase().includes(searchQuery.toLowerCase())) + branch.levels.some((l: any) => t(l.title).toLowerCase().includes(searchQuery.toLowerCase())) ); const frameworkSchema = { @@ -53,6 +53,7 @@ const Framework: React.FC = () => { title={ui("seo.framework.title")} description={ui("seo.framework.description")} schema={frameworkSchema} + lang={language} />
diff --git a/pages/Home.tsx b/pages/Home.tsx index fd52402..b7277a5 100644 --- a/pages/Home.tsx +++ b/pages/Home.tsx @@ -11,7 +11,7 @@ const Home: React.FC = () => { const [pulseScore, setPulseScore] = useState(null); const [treeProgress, setTreeProgress] = useState(0); const containerRef = useRef(null); - const { ui } = useLanguage(); + const { ui, language } = useLanguage(); useEffect(() => { const handleScroll = () => { @@ -31,6 +31,11 @@ const Home: React.FC = () => { let progress = scrolled / totalScrollable; + // Ensure progress is valid + if (isNaN(progress) || !isFinite(progress)) { + progress = 0; + } + // Clamp between 0 and 1 progress = Math.max(0, Math.min(1, progress)); @@ -66,6 +71,7 @@ const Home: React.FC = () => { title={ui("nav.title")} description={ui("hero.desc")} schema={schema} + lang={language} /> {/* Hero Section */} @@ -83,7 +89,7 @@ const Home: React.FC = () => { alt="" className="w-full h-full object-cover opacity-10 mix-blend-overlay animate-pulse-slow" loading="eager" - fetchPriority="high" + fetchpriority="high" />
@@ -164,7 +170,7 @@ const Home: React.FC = () => { width="380" height="548" loading="eager" - fetchPriority="high" + fetchpriority="high" className="w-[280px] md:w-[380px] h-auto mx-auto rounded-r-2xl shadow-[20px_20px_60px_-15px_rgba(0,0,0,0.7)] border-l-8 border-slate-800" />
@@ -439,6 +445,7 @@ const Home: React.FC = () => { Serge Baumberger
diff --git a/services/blogService.ts b/services/blogService.ts index 476b86b..5681c55 100644 --- a/services/blogService.ts +++ b/services/blogService.ts @@ -1,12 +1,12 @@ import { db } from '../firebaseConfig'; import { collection, getDocs } from 'firebase/firestore'; -import { BlogPost } from '../types'; +import { BlogPost } from '../src/types'; import { blogPosts as staticPosts } from '../data/framework'; const COLLECTION_NAME = 'posts'; -const STORAGE_KEY = 'qtf_blog_posts_v1'; +const STORAGE_KEY = 'qtf_blog_posts_v2'; -const isFirebaseReady = () => !!db; +const isFirebaseReady = () => !!db && import.meta.env.VITE_FIREBASE_API_KEY && import.meta.env.VITE_FIREBASE_API_KEY !== 'dummy'; export const fetchAllPosts = async (): Promise => { // 1. Firebase diff --git a/src/App.tsx b/src/App.tsx index 9843a31..3f92c21 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,42 +1,45 @@ - -import { HashRouter, Routes, Route } from 'react-router-dom'; +import React, { Suspense } from 'react'; +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import { HelmetProvider } from 'react-helmet-async'; -import { LanguageProvider } from './contexts/LanguageContext'; -import Navbar from './components/Navbar'; -import Footer from './components/Footer'; -import Home from './pages/Home'; -import Framework from './pages/Framework'; -import BranchDetail from './pages/BranchDetail'; -import Author from './pages/Author'; -import Blog from './pages/Blog'; -import BlogPostDetail from './pages/BlogPostDetail'; -import AppTool from './pages/AppTool'; -import FAQ from './pages/FAQ'; -import ScrollToTop from './components/ScrollToTop'; +import { LanguageProvider } from '../contexts/LanguageContext'; +import Navbar from '../components/Navbar'; +import Footer from '../components/Footer'; + +const Home = React.lazy(() => import('../pages/Home')); +const Blog = React.lazy(() => import('../pages/Blog')); +const BlogPostDetail = React.lazy(() => import('../pages/BlogPostDetail')); +const Framework = React.lazy(() => import('../pages/Framework')); +const BranchDetail = React.lazy(() => import('../pages/BranchDetail')); +const AppTool = React.lazy(() => import('../pages/AppTool')); +const AdminCreatePost = React.lazy(() => import('../pages/AdminCreatePost')); +const Author = React.lazy(() => import('../pages/Author')); +const FAQ = React.lazy(() => import('../pages/FAQ')); function App() { return ( - - +
-
- - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - +
+
}> + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + +
- + ); diff --git a/src/index.css b/src/index.css index c4722d9..3b572eb 100644 --- a/src/index.css +++ b/src/index.css @@ -1,21 +1,4 @@ -@import "tailwindcss"; - -@layer utilities { - .animate-pulse-slow { - animation: pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite; - } - - .animate-float { - animation: float 6s ease-in-out infinite; - } - - .animate-float-delayed { - animation: float 6s ease-in-out 3s infinite; - } - - @keyframes float { - 0% { transform: translateY(0px); } - 50% { transform: translateY(-20px); } - 100% { transform: translateY(0px); } - } +@theme { + --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif; + --font-serif: "Playfair Display", ui-serif, Georgia, serif; } diff --git a/src/index.tsx b/src/index.tsx index 2339d59..a2bf01b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom/client'; import App from './App'; import './index.css'; -ReactDOM.createRoot(document.getElementById('root')!).render( +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/src/types.ts b/src/types.ts index 1b9d83c..6c2e485 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,11 +1,6 @@ - -export type LocalizedString = string | { de: string; en?: string }; - -export interface Level { - id: number; - title: LocalizedString; - description: LocalizedString; - tools?: string[]; +export interface LocalizedString { + de: string; + en: string; } export interface Branch { @@ -13,52 +8,44 @@ export interface Branch { title: LocalizedString; description: LocalizedString; icon: string; - levels: Level[]; + levels: { + id: number; + title: LocalizedString; + description: LocalizedString; + tools: string[]; + }[]; } export interface BlogPost { id: string; title: LocalizedString; date: string; + author: string; + imageUrl: string; excerpt: LocalizedString; content: LocalizedString; - author: string; - imageUrl?: string; } export interface AppToolCell { id: string; - label: LocalizedString; - tooltip: LocalizedString; + label: any; + tooltip: any; + row?: number; + col?: number; + status?: string; + okrs?: boolean[]; acceptanceCriteria?: string[]; class?: string; isRoot?: boolean; } export interface AppToolLane { - label: string; + id?: string; + title?: LocalizedString; + label?: any; startRow: number; endRow: number; - icon?: string; - details: { - description: LocalizedString; - why: LocalizedString; - how: LocalizedString; - resources?: { label: string; url: string }[]; - }; -} - -export interface AppToolConfig { - lanes: AppToolLane[]; - cells: AppToolCell[]; - arrows: { from: string; to: string }[]; -} - -export interface AppNode extends Omit { - title: string; - description: string; - level: number; - branchId: string; - row: number; - col: number; + color?: string; + details?: any; + targetCount?: number; } diff --git a/tsconfig.json b/tsconfig.json index 2702703..c66ea84 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,6 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["**/*.ts", "**/*.tsx"], + "include": ["src/**/*.ts", "src/**/*.tsx"], "exclude": ["node_modules"] } \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 7186bf9..744d484 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,9 +1,23 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +import { viteSingleFile } from "vite-plugin-singlefile"; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], - // Keine komplexen Alias-Configs mehr, wir nutzen relative Pfade. + plugins: [react(), viteSingleFile()], + base: './', // Ensure relative paths for single file portability + build: { + target: "esnext", + assetsInlineLimit: 100000000, // Inline everything + chunkSizeWarningLimit: 100000000, + cssCodeSplit: false, // Don't split CSS + brotliSize: false, + rollupOptions: { + inlineDynamicImports: true, // Inline dynamic imports + output: { + manualChunks: undefined, // Disable manual chunks + }, + }, + }, });