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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
13 changes: 7 additions & 6 deletions components/Illustrations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => ({
Expand All @@ -196,12 +197,12 @@ export const TheGrowingTree: React.FC<{ progress: number; className?: string }>
</g>

{/* 2. TRUNK (Spanning Level 1 to 4) */}
<g className={progress > 0.5 ? "text-brand-600 transition-colors duration-1000" : "text-slate-300 transition-colors duration-1000"}>
<g className={safeProgress > 0.5 ? "text-brand-600 transition-colors duration-1000" : "text-slate-300 transition-colors duration-1000"}>
{/* Main Central Trunk Line */}
<path
d="M100 60 C 100 150, 95 250, 100 350 C 105 450, 95 550, 100 650"
stroke="currentColor"
strokeWidth={progress > 0.5 ? 4 : 2}
strokeWidth={safeProgress > 0.5 ? 4 : 2}
strokeLinecap="round"
fill="none"
style={{
Expand Down
221 changes: 129 additions & 92 deletions components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<nav className="bg-white/90 backdrop-blur-md border-b border-slate-200 sticky top-0 z-50 shadow-sm">
<header
className={`fixed top-0 w-full z-50 transition-all duration-300 ${
scrolled
? 'bg-white/90 backdrop-blur-md shadow-sm py-3'
: 'bg-transparent py-5'
}`}
>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex items-center">
<Link to="/" className="flex-shrink-0 flex items-center gap-2 group">
<div className="bg-brand-600 p-1.5 rounded-lg group-hover:bg-brand-700 transition-colors shadow-sm">
<TreeDeciduous className="h-6 w-6 text-white" />
</div>
<span className="font-bold text-xl text-slate-900 tracking-tight">{ui("nav.title")}</span>
</Link>
</div>

<div className="hidden md:flex items-center space-x-1">
<Link to="/" className={isActive('/')}>{ui("nav.home")}</Link>
<Link to="/framework" className={isActive('/framework')}>{ui("nav.framework")}</Link>
<a
href="https://www.infometis.ch/services/test-assessment"
target="_blank"
rel="noreferrer"
className="text-slate-600 hover:text-brand-700 hover:bg-slate-50 rounded-lg px-3 py-2 transition-all"
>
{ui("nav.assessment")}
</a>
<Link to="/blog" className={isActive('/blog')}>{ui("nav.blog")}</Link>
<Link to="/author" className={isActive('/author')}>{ui("nav.author")}</Link>
<Link to="/app" className={isActive('/app')}>{ui("nav.app")}</Link>

<div className="ml-4 pl-4 border-l border-slate-200 flex items-center gap-3">
<button
onClick={toggleLanguage}
className="flex items-center gap-1.5 text-sm font-medium text-slate-600 hover:text-brand-600 transition-colors px-2 py-1 rounded hover:bg-slate-50"
>
<Globe className="h-4 w-4" />
{language.toUpperCase()}
</button>
<a
href="https://www.amazon.de/Das-Quality-Tree-Framework-Automatisierung/dp/3658510404"
target="_blank"
rel="noreferrer"
className="text-slate-700 hover:text-brand-600 font-medium text-sm flex items-center"
<div className="flex justify-between items-center">
{/* Logo */}
<Link to="/" className="flex items-center gap-2 group">
<div className="bg-brand-600 text-white p-2 rounded-lg group-hover:bg-brand-700 transition-colors">
<TreePine size={24} />
</div>
<span className="font-sans font-bold text-xl tracking-tight text-slate-900">
{ui('nav.title')}
</span>
</Link>

{/* Desktop Navigation */}
<nav className="hidden md:flex items-center gap-8">
<div className="flex items-center gap-6">
{navLinks.map((link) => (
<Link
key={link.path}
to={link.path}
className={`text-sm font-medium transition-colors hover:text-brand-600 ${
isActive(link.path) ? 'text-brand-600' : 'text-slate-600'
}`}
>
{link.name}
</Link>
))}
</div>

{/* Language Toggle */}
<div className="flex items-center gap-2 border-l border-slate-200 pl-6">
<button
onClick={() => setLanguage('de')}
className={`text-xs font-bold px-2 py-1 rounded transition-colors ${
language === 'de'
? 'bg-slate-800 text-white'
: 'text-slate-500 hover:bg-slate-100'
}`}
>
<BookOpen className="h-4 w-4 mr-1.5" /> {ui("nav.book")}
</a>
<a
href="https://sn.pub/97r1i7"
target="_blank"
rel="noreferrer"
className="bg-slate-900 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-brand-600 transition-colors shadow-md hover:shadow-lg transform hover:-translate-y-0.5"
DE
</button>
<button
onClick={() => setLanguage('en')}
className={`text-xs font-bold px-2 py-1 rounded transition-colors ${
language === 'en'
? 'bg-slate-800 text-white'
: 'text-slate-500 hover:bg-slate-100'
}`}
>
{ui("nav.course")}
</a>
EN
</button>
</div>
</div>
</nav>

<div className="flex items-center md:hidden gap-3">
<button
onClick={toggleLanguage}
className="flex items-center gap-1 text-sm font-bold text-slate-600"
>
{language.toUpperCase()}
</button>
<button
onClick={() => setIsOpen(!isOpen)}
className="p-2 rounded-md text-slate-600 hover:text-slate-900 hover:bg-slate-100 focus:outline-none"
>
{isOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
</button>
</div>
{/* Mobile Menu Button */}
<button
className="md:hidden p-2 text-slate-600 hover:text-brand-600 transition-colors"
onClick={() => setIsOpen(!isOpen)}
aria-label="Toggle menu"
>
{isOpen ? <X size={24} /> : <Menu size={24} />}
</button>
</div>
</div>

{/* Mobile menu */}
{/* Mobile Navigation */}
{isOpen && (
<div className="md:hidden bg-white border-t border-slate-100 absolute w-full shadow-lg animate-fade-in z-50">
<div className="px-4 pt-2 pb-6 space-y-1">
<Link to="/" onClick={() => 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")}</Link>
<Link to="/framework" onClick={() => 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")}</Link>
<a href="https://www.infometis.ch/services/test-assessment" target="_blank" rel="noreferrer" onClick={() => 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")}</a>
<Link to="/blog" onClick={() => 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")}</Link>
<Link to="/author" onClick={() => 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")}</Link>
<Link to="/app" onClick={() => 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")}</Link>
<div className="pt-4 mt-4 border-t border-slate-100 grid grid-cols-2 gap-3">
<a href="https://www.amazon.de/Das-Quality-Tree-Framework-Automatisierung/dp/3658510404" target="_blank" rel="noreferrer" className="flex justify-center items-center px-3 py-3 text-base font-medium text-slate-700 bg-slate-50 rounded-lg border border-slate-200">
{ui("nav.book")}
</a>
<a href="https://sn.pub/97r1i7" target="_blank" rel="noreferrer" className="flex justify-center items-center px-3 py-3 text-base font-medium text-white bg-brand-600 rounded-lg shadow-sm">
{ui("nav.course")}
</a>
</div>
<div className="md:hidden absolute top-full left-0 w-full bg-white shadow-lg border-t border-slate-100 py-4 px-4 flex flex-col gap-4">
{navLinks.map((link) => (
<Link
key={link.path}
to={link.path}
className={`block px-4 py-2 rounded-lg text-base font-medium ${
isActive(link.path)
? 'bg-brand-50 text-brand-700'
: 'text-slate-600 hover:bg-slate-50 hover:text-brand-600'
}`}
>
{link.name}
</Link>
))}

<div className="flex items-center gap-2 px-4 pt-4 border-t border-slate-100 mt-2">
<span className="text-sm text-slate-500 mr-2">Language:</span>
<button
onClick={() => { setLanguage('de'); setIsOpen(false); }}
className={`text-xs font-bold px-3 py-1.5 rounded transition-colors ${
language === 'de'
? 'bg-slate-800 text-white'
: 'bg-slate-100 text-slate-600'
}`}
>
DE
</button>
<button
onClick={() => { setLanguage('en'); setIsOpen(false); }}
className={`text-xs font-bold px-3 py-1.5 rounded transition-colors ${
language === 'en'
? 'bg-slate-800 text-white'
: 'bg-slate-100 text-slate-600'
}`}
>
EN
</button>
</div>
</div>
)}
</nav>
</header>
);
};

Expand Down
Loading