✅ E2.S1: Tailwind config update - Teal-700 (#0F766E) als PRIMARY brand color (5.47:1 contrast) - Amber-600→700 gradient voor AI features - Updated ring color naar teal-700 voor WCAG AA ✅ E2.S2: Global CSS variables - --color-brand → teal-700 (was blue-600) - --color-info → teal-700 (was blue-500) - --color-input-focus → teal-700 (was blue-500) - --color-ai toegevoegd → amber-600 ✅ E2.S3: Component color updates - components/ui/sign-in.tsx: Alle blue → teal - components/ui/timeline.tsx: Gradient blue → teal - components/ui/reading-progress.tsx: Progress bar blue → teal - components/ui/modern-side-bar.tsx: Logo, active states blue → teal ✅ E2.S4: AIButton component - Nieuw component: components/ui/ai-button.tsx - Amber-600→700 gradient voor WCAG compliance - 3 variants: default, outline, ghost - Loading state + Sparkles icon - Fully accessible (WCAG AA focus states) ✅ E2.S5: Contrast testing - scripts/test-contrast.ts: Automated WCAG testing - docs/design/wcag-compliance.md: Compliance documentatie - Resultaten: 7/11 AA Normal (4.5:1), 11/11 AA Large (3:1) ✅ WCAG AA Compliance: ✅ PASS - Teal-700 op wit: 5.47:1 (AA Normal) - White op teal-700: 5.47:1 (AA Normal) - White op amber-600: 3.19:1 (AA Large - OK voor buttons) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
91 lines
3.3 KiB
TypeScript
91 lines
3.3 KiB
TypeScript
"use client";
|
|
import {
|
|
useMotionValueEvent,
|
|
useScroll,
|
|
useTransform,
|
|
motion,
|
|
} from "framer-motion";
|
|
import React, { useEffect, useRef, useState } from "react";
|
|
|
|
interface TimelineEntry {
|
|
title: string;
|
|
content: React.ReactNode;
|
|
}
|
|
|
|
export const Timeline = ({ data }: { data: TimelineEntry[] }) => {
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const [height, setHeight] = useState(0);
|
|
|
|
useEffect(() => {
|
|
if (ref.current) {
|
|
const rect = ref.current.getBoundingClientRect();
|
|
setHeight(rect.height);
|
|
}
|
|
}, [ref]);
|
|
|
|
const { scrollYProgress } = useScroll({
|
|
target: containerRef,
|
|
offset: ["start 10%", "end 50%"],
|
|
});
|
|
|
|
const heightTransform = useTransform(scrollYProgress, [0, 1], [0, height]);
|
|
const opacityTransform = useTransform(scrollYProgress, [0, 0.1], [0, 1]);
|
|
|
|
return (
|
|
<div
|
|
className="w-full bg-white dark:bg-neutral-950 font-sans md:px-10"
|
|
ref={containerRef}
|
|
>
|
|
<div className="max-w-7xl mx-auto py-20 px-4 md:px-8 lg:px-10">
|
|
<h2 className="text-lg md:text-4xl mb-4 text-black dark:text-white max-w-4xl">
|
|
Changelog from my journey
|
|
</h2>
|
|
<p className="text-neutral-700 dark:text-neutral-300 text-sm md:text-base max-w-sm">
|
|
I've been working on Aceternity for the past 2 years. Here's
|
|
a timeline of my journey.
|
|
</p>
|
|
</div>
|
|
|
|
<div ref={ref} className="relative max-w-7xl mx-auto pb-20">
|
|
{data.map((item, index) => (
|
|
<div
|
|
key={index}
|
|
className="flex justify-start pt-10 md:pt-40 md:gap-10"
|
|
>
|
|
<div className="sticky flex flex-col md:flex-row z-40 items-center top-40 self-start max-w-xs lg:max-w-sm md:w-full">
|
|
<div className="h-10 absolute left-3 md:left-3 w-10 rounded-full bg-white dark:bg-black flex items-center justify-center">
|
|
<div className="h-4 w-4 rounded-full bg-neutral-200 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 p-2" />
|
|
</div>
|
|
<h3 className="hidden md:block text-xl md:pl-20 md:text-5xl font-bold text-neutral-500 dark:text-neutral-500 ">
|
|
{item.title}
|
|
</h3>
|
|
</div>
|
|
|
|
<div className="relative pl-20 pr-4 md:pl-4 w-full">
|
|
<h3 className="md:hidden block text-2xl mb-4 text-left font-bold text-neutral-500 dark:text-neutral-500">
|
|
{item.title}
|
|
</h3>
|
|
{item.content}{" "}
|
|
</div>
|
|
</div>
|
|
))}
|
|
<div
|
|
style={{
|
|
height: height + "px",
|
|
}}
|
|
className="absolute md:left-8 left-8 top-0 overflow-hidden w-[2px] bg-[linear-gradient(to_bottom,var(--tw-gradient-stops))] from-transparent from-[0%] via-neutral-200 dark:via-neutral-700 to-transparent to-[99%] [mask-image:linear-gradient(to_bottom,transparent_0%,black_10%,black_90%,transparent_100%)] "
|
|
>
|
|
<motion.div
|
|
style={{
|
|
height: heightTransform,
|
|
opacity: opacityTransform,
|
|
}}
|
|
className="absolute inset-x-0 top-0 w-[2px] bg-gradient-to-t from-teal-500 via-teal-400 to-transparent from-[0%] via-[10%] rounded-full"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|