Web-Tools

Nützliche Tools im Browser — kostenlos, quelloffen & ohne Anmeldung

Taschenrechner
components/projekte/Taschenrechner.tsx
0
Taschenrechner.tsx
1'use client';
2 
3import { useState } from 'react';
4 
5export default function Taschenrechner() {
6 const [display, setDisplay] = useState('0');
7 const [prev, setPrev] = useState<number | null>(null);
8 const [op, setOp] = useState<string | null>(null);
9 const [resetNext, setResetNext] = useState(false);
10 
11 const handleNumber = (num: string) => {
12 if (resetNext) {
13 setDisplay(num);
14 setResetNext(false);
15 } else {
16 setDisplay(display === '0' ? num : display + num);
17 }
18 };
19 
20 const handleOperator = (operator: string) => {
21 const current = parseFloat(display);
22 if (prev !== null && op) {
23 const result = calculate(prev, current, op);
24 setDisplay(String(result));
25 setPrev(result);
26 } else {
27 setPrev(current);
28 }
29 setOp(operator);
30 setResetNext(true);
31 };
32 
33 const calculate = (a: number, b: number, operator: string): number => {
34 switch (operator) {
35 case '+': return a + b;
36 case '-': return a - b;
37 case '*': return a * b;
38 case '/': return b !== 0 ? a / b : 0;
39 default: return b;
40 }
41 };
42 
43 const handleEquals = () => {
44 if (prev !== null && op) {
45 const current = parseFloat(display);
46 const result = calculate(prev, current, op);
47 setDisplay(String(result));
48 setPrev(null);
49 setOp(null);
50 setResetNext(true);
51 }
52 };
53 
54 const handleClear = () => {
55 setDisplay('0');
56 setPrev(null);
57 setOp(null);
58 setResetNext(false);
59 };
60 
61 const handleDecimal = () => {
62 if (!display.includes('.')) {
63 setDisplay(display + '.');
64 }
65 };
66 
67 const numBtn = "bg-white/10 text-white hover:bg-white/20 rounded-xl py-3 font-semibold transition-colors text-lg";
68 const opBtn = "bg-[#52e9a2]/20 text-[#52e9a2] border border-[#52e9a2]/30 hover:bg-[#52e9a2]/30 rounded-xl py-3 font-semibold transition-colors text-lg";
69 
70 return (
71 <div className="bg-white/5 rounded-2xl p-4 border border-white/10">
72 {/* Display */}
73 <div className="bg-[#0a0a0f] rounded-xl p-4 mb-4 text-right">
74 <div className="text-xs text-gray-500 h-4">
75 {prev !== null && op ? `${prev} ${op}` : ''}
76 </div>
77 <div className="text-3xl font-bold text-white truncate">{display}</div>
78 </div>
79 
80 {/* Buttons */}
81 <div className="grid grid-cols-4 gap-2">
82 <button onClick={handleClear} className="bg-red-500/20 text-red-400 border border-red-500/30 hover:bg-red-500/30 rounded-xl py-3 font-semibold transition-colors text-lg col-span-2">C</button>
83 <button onClick={() => handleOperator('/')} className={opBtn}>/</button>
84 <button onClick={() => handleOperator('*')} className={opBtn}>*</button>
85 
86 <button onClick={() => handleNumber('7')} className={numBtn}>7</button>
87 <button onClick={() => handleNumber('8')} className={numBtn}>8</button>
88 <button onClick={() => handleNumber('9')} className={numBtn}>9</button>
89 <button onClick={() => handleOperator('-')} className={opBtn}>-</button>
90 
91 <button onClick={() => handleNumber('4')} className={numBtn}>4</button>
92 <button onClick={() => handleNumber('5')} className={numBtn}>5</button>
93 <button onClick={() => handleNumber('6')} className={numBtn}>6</button>
94 <button onClick={() => handleOperator('+')} className={opBtn}>+</button>
95 
96 <button onClick={() => handleNumber('1')} className={numBtn}>1</button>
97 <button onClick={() => handleNumber('2')} className={numBtn}>2</button>
98 <button onClick={() => handleNumber('3')} className={numBtn}>3</button>
99 <button onClick={handleEquals} className="bg-[#52e9a2] text-[#121212] font-bold rounded-xl py-3 text-lg row-span-2 hover:brightness-110 transition-all">=</button>
100 
101 <button onClick={() => handleNumber('0')} className={`${numBtn} col-span-2`}>0</button>
102 <button onClick={handleDecimal} className={numBtn}>.</button>
103 </div>
104 </div>
105 );
106}
107 
108export const taschenrechnerCode = `import { useState } from 'react';
109 
110export default function Taschenrechner() {
111 const [display, setDisplay] = useState('0');
112 const [prev, setPrev] = useState(null);
113 const [op, setOp] = useState(null);
114 const [resetNext, setResetNext] = useState(false);
115 
116 const handleNumber = (num) => {
117 if (resetNext) {
118 setDisplay(num);
119 setResetNext(false);
120 } else {
121 setDisplay(display === '0' ? num : display + num);
122 }
123 };
124 
125 const handleOperator = (operator) => {
126 const current = parseFloat(display);
127 if (prev !== null && op) {
128 const result = calculate(prev, current, op);
129 setDisplay(String(result));
130 setPrev(result);
131 } else {
132 setPrev(current);
133 }
134 setOp(operator);
135 setResetNext(true);
136 };
137 
138 const calculate = (a, b, operator) => {
139 switch (operator) {
140 case '+': return a + b;
141 case '-': return a - b;
142 case '*': return a * b;
143 case '/': return b !== 0 ? a / b : 0;
144 default: return b;
145 }
146 };
147 
148 const handleEquals = () => {
149 if (prev !== null && op) {
150 const current = parseFloat(display);
151 const result = calculate(prev, current, op);
152 setDisplay(String(result));
153 setPrev(null);
154 setOp(null);
155 setResetNext(true);
156 }
157 };
158 
159 return (
160 <div>
161 <div className="display">{display}</div>
162 <div className="grid grid-cols-4 gap-2">
163 <button onClick={() => handleClear()}>C</button>
164 {[7,8,9,4,5,6,1,2,3,0].map(n => (
165 <button key={n} onClick={() => handleNumber(String(n))}>
166 {n}
167 </button>
168 ))}
169 {['+','-','*','/'].map(o => (
170 <button key={o} onClick={() => handleOperator(o)}>
171 {o}
172 </button>
173 ))}
174 <button onClick={handleEquals}>=</button>
175 </div>
176 </div>
177 );
178}`;
179 
Zufallsgenerator
components/tools/ZufallsGenerator.tsx
Klicke "Generieren"
ZufallsGenerator.tsx
1/**
2 * @file components/tools/ZufallsGenerator.tsx
3 * @description Interaktiver Zufallszahl-Generator mit Min/Max, Animation und History.
4 * @author Andreas Andorfer
5 */
6 
7'use client';
8 
9import { useState, useCallback } from 'react';
10import { motion, AnimatePresence } from 'framer-motion';
11import { FaDice, FaHistory } from 'react-icons/fa';
12 
13export default function ZufallsGenerator() {
14 const [min, setMin] = useState(1);
15 const [max, setMax] = useState(100);
16 const [result, setResult] = useState<number | null>(null);
17 const [isRolling, setIsRolling] = useState(false);
18 const [history, setHistory] = useState<number[]>([]);
19 
20 const generate = useCallback(() => {
21 if (isRolling) return;
22 setIsRolling(true);
23 
24 const minVal = Math.min(min, max);
25 const maxVal = Math.max(min, max);
26 
27 // Schnelle Zufallszahlen-Animation
28 let count = 0;
29 const interval = setInterval(() => {
30 setResult(Math.floor(Math.random() * (maxVal - minVal + 1)) + minVal);
31 count++;
32 if (count >= 12) {
33 clearInterval(interval);
34 const final = Math.floor(Math.random() * (maxVal - minVal + 1)) + minVal;
35 setResult(final);
36 setHistory((prev) => [final, ...prev].slice(0, 5));
37 setIsRolling(false);
38 }
39 }, 60);
40 }, [min, max, isRolling]);
41 
42 return (
43 <div className="bg-white/5 rounded-2xl p-4 border border-white/10">
44 {/* Ergebnis-Display */}
45 <div className="bg-[#0a0a0f] rounded-xl p-6 mb-4 text-center border border-white/10 min-h-[100px] flex items-center justify-center">
46 <AnimatePresence mode="wait">
47 {result !== null ? (
48 <motion.span
49 key={result + (isRolling ? '-rolling' : '')}
50 initial={{ scale: 0.5, opacity: 0 }}
51 animate={{ scale: 1, opacity: 1 }}
52 className={`text-5xl font-bold font-mono ${isRolling ? 'text-gray-400' : 'text-[#52e9a2]'}`}
53 >
54 {result}
55 </motion.span>
56 ) : (
57 <span className="text-gray-600 text-lg">
58 <FaDice className="inline mr-2" />
59 Klicke &quot;Generieren&quot;
60 </span>
61 )}
62 </AnimatePresence>
63 </div>
64 
65 {/* Min/Max Eingabe */}
66 <div className="grid grid-cols-2 gap-3 mb-4">
67 <div>
68 <label className="text-xs text-gray-500 mb-1 block">Min</label>
69 <input
70 type="number"
71 value={min}
72 onChange={(e) => setMin(Number(e.target.value))}
73 className="w-full bg-white/5 border border-white/10 rounded-xl px-3 py-2 text-white text-center font-mono focus:border-[#52e9a2] focus:outline-none transition-colors"
74 />
75 </div>
76 <div>
77 <label className="text-xs text-gray-500 mb-1 block">Max</label>
78 <input
79 type="number"
80 value={max}
81 onChange={(e) => setMax(Number(e.target.value))}
82 className="w-full bg-white/5 border border-white/10 rounded-xl px-3 py-2 text-white text-center font-mono focus:border-[#52e9a2] focus:outline-none transition-colors"
83 />
84 </div>
85 </div>
86 
87 {/* Generieren-Button */}
88 <button
89 onClick={generate}
90 disabled={isRolling}
91 className="w-full bg-[#52e9a2] text-[#121212] rounded-xl py-3 font-semibold hover:brightness-110 transition-all flex items-center justify-center gap-2 disabled:opacity-50"
92 >
93 <FaDice className={isRolling ? 'animate-spin' : ''} />
94 Generieren
95 </button>
96 
97 {/* History */}
98 {history.length > 0 && (
99 <div className="mt-4">
100 <div className="flex items-center gap-2 text-xs text-gray-500 mb-2">
101 <FaHistory />
102 <span>Letzte Ergebnisse</span>
103 </div>
104 <div className="flex gap-2 flex-wrap">
105 {history.map((num, i) => (
106 <span
107 key={`${num}-${i}`}
108 className="px-3 py-1 bg-white/5 border border-white/10 rounded-lg text-sm font-mono text-gray-300"
109 >
110 {num}
111 </span>
112 ))}
113 </div>
114 </div>
115 )}
116 </div>
117 );
118}
119 
Passwort-Generator
components/projekte/PasswortGenerator.tsx
Klicke "Generieren"...
Länge16
PasswortGenerator.tsx
1'use client';
2 
3import { useState } from 'react';
4import { FaCopy, FaCheck, FaSyncAlt } from 'react-icons/fa';
5 
6export default function PasswortGenerator() {
7 const [password, setPassword] = useState('');
8 const [length, setLength] = useState(16);
9 const [uppercase, setUppercase] = useState(true);
10 const [numbers, setNumbers] = useState(true);
11 const [symbols, setSymbols] = useState(true);
12 const [copied, setCopied] = useState(false);
13 
14 const generate = () => {
15 let chars = 'abcdefghijklmnopqrstuvwxyz';
16 if (uppercase) chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
17 if (numbers) chars += '0123456789';
18 if (symbols) chars += '!@#$%^&*()_+-=[]{}|;:,.<>?';
19 let result = '';
20 for (let i = 0; i < length; i++) {
21 result += chars.charAt(Math.floor(Math.random() * chars.length));
22 }
23 setPassword(result);
24 setCopied(false);
25 };
26 
27 const copyPassword = async () => {
28 if (!password) return;
29 await navigator.clipboard.writeText(password);
30 setCopied(true);
31 setTimeout(() => setCopied(false), 2000);
32 };
33 
34 const getStrength = () => {
35 let score = 0;
36 if (length >= 12) score++;
37 if (length >= 16) score++;
38 if (uppercase) score++;
39 if (numbers) score++;
40 if (symbols) score++;
41 if (score <= 2) return { label: 'Schwach', color: 'bg-red-500', width: 'w-1/3' };
42 if (score <= 3) return { label: 'Mittel', color: 'bg-yellow-500', width: 'w-2/3' };
43 return { label: 'Stark', color: 'bg-[#52e9a2]', width: 'w-full' };
44 };
45 
46 const strength = getStrength();
47 
48 return (
49 <div className="bg-white/5 rounded-2xl p-4 border border-white/10">
50 {/* Display */}
51 <div className="bg-[#0a0a0f] rounded-xl p-4 mb-4 flex items-center gap-2 border border-white/10">
52 <span className="flex-1 text-white font-mono text-sm truncate">
53 {password || 'Klicke "Generieren"...'}
54 </span>
55 <button onClick={copyPassword} className="text-gray-400 hover:text-[#52e9a2] transition-colors shrink-0" aria-label="Kopieren">
56 {copied ? <FaCheck className="text-[#52e9a2]" /> : <FaCopy />}
57 </button>
58 </div>
59 
60 {/* Strength bar */}
61 {password && (
62 <div className="mb-4">
63 <div className="flex justify-between text-xs mb-1">
64 <span className="text-gray-500">Stärke</span>
65 <span className="text-gray-400">{strength.label}</span>
66 </div>
67 <div className="h-1.5 bg-white/10 rounded-full overflow-hidden">
68 <div className={`h-full ${strength.color} ${strength.width} rounded-full transition-all duration-300`} />
69 </div>
70 </div>
71 )}
72 
73 {/* Length slider */}
74 <div className="mb-4">
75 <div className="flex justify-between text-sm mb-2">
76 <span className="text-gray-400">Länge</span>
77 <span className="text-white font-bold">{length}</span>
78 </div>
79 <input
80 type="range" min={6} max={32} value={length}
81 onChange={e => setLength(Number(e.target.value))}
82 className="w-full accent-[#52e9a2]"
83 />
84 </div>
85 
86 {/* Options */}
87 <div className="grid grid-cols-3 gap-2 mb-4">
88 {[
89 { label: 'ABC', state: uppercase, set: setUppercase },
90 { label: '123', state: numbers, set: setNumbers },
91 { label: '#$%', state: symbols, set: setSymbols },
92 ].map(opt => (
93 <button
94 key={opt.label}
95 onClick={() => opt.set(!opt.state)}
96 className={`py-2 rounded-xl text-sm font-semibold transition-all ${
97 opt.state
98 ? 'bg-[#52e9a2]/20 text-[#52e9a2] border border-[#52e9a2]/30'
99 : 'bg-white/5 text-gray-500 border border-white/10'
100 }`}
101 >
102 {opt.label}
103 </button>
104 ))}
105 </div>
106 
107 <button onClick={generate} className="w-full bg-[#52e9a2] text-[#121212] rounded-xl py-3 font-semibold hover:brightness-110 transition-all flex items-center justify-center gap-2">
108 <FaSyncAlt /> Generieren
109 </button>
110 </div>
111 );
112}
113 
114export const passwortGeneratorCode = `import { useState } from 'react';
115 
116export default function PasswortGenerator() {
117 const [password, setPassword] = useState('');
118 const [length, setLength] = useState(16);
119 const [uppercase, setUppercase] = useState(true);
120 const [numbers, setNumbers] = useState(true);
121 const [symbols, setSymbols] = useState(true);
122 
123 const generate = () => {
124 let chars = 'abcdefghijklmnopqrstuvwxyz';
125 if (uppercase) chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
126 if (numbers) chars += '0123456789';
127 if (symbols) chars += '!@#$%^&*()_+-=';
128 let result = '';
129 for (let i = 0; i < length; i++) {
130 result += chars.charAt(
131 Math.floor(Math.random() * chars.length)
132 );
133 }
134 setPassword(result);
135 };
136 
137 const getStrength = () => {
138 let score = 0;
139 if (length >= 12) score++;
140 if (length >= 16) score++;
141 if (uppercase) score++;
142 if (numbers) score++;
143 if (symbols) score++;
144 return score <= 2 ? 'Schwach' : score <= 3 ? 'Mittel' : 'Stark';
145 };
146 
147 return (
148 <div>
149 <div className="password-display">
150 {password || 'Klicke Generieren...'}
151 </div>
152 <p>Staerke: {getStrength()}</p>
153 <input
154 type="range" min={6} max={32}
155 value={length}
156 onChange={e => setLength(Number(e.target.value))}
157 />
158 <label>
159 <input type="checkbox" checked={uppercase}
160 onChange={() => setUppercase(!uppercase)} />
161 Grossbuchstaben
162 </label>
163 <label>
164 <input type="checkbox" checked={numbers}
165 onChange={() => setNumbers(!numbers)} />
166 Zahlen
167 </label>
168 <label>
169 <input type="checkbox" checked={symbols}
170 onChange={() => setSymbols(!symbols)} />
171 Sonderzeichen
172 </label>
173 <button onClick={generate}>Generieren</button>
174 </div>
175 );
176}`;
177 
Farbgenerator
components/projekte/Farbgenerator.tsx
#52E9A2
R: 82G: 233B: 162
Farbgenerator.tsx
1'use client';
2 
3import { useState } from 'react';
4import { FaCopy, FaCheck, FaSyncAlt } from 'react-icons/fa';
5 
6export default function Farbgenerator() {
7 const [color, setColor] = useState('#52e9a2');
8 const [history, setHistory] = useState<string[]>(['#52e9a2']);
9 const [copied, setCopied] = useState(false);
10 
11 const generateColor = () => {
12 const hex = '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
13 setColor(hex);
14 setHistory(prev => [hex, ...prev].slice(0, 5));
15 };
16 
17 const copyColor = async () => {
18 await navigator.clipboard.writeText(color);
19 setCopied(true);
20 setTimeout(() => setCopied(false), 2000);
21 };
22 
23 // Calculate if text should be light or dark
24 const r = parseInt(color.slice(1, 3), 16);
25 const g = parseInt(color.slice(3, 5), 16);
26 const b = parseInt(color.slice(5, 7), 16);
27 const brightness = (r * 299 + g * 587 + b * 114) / 1000;
28 const textColor = brightness > 128 ? '#121212' : '#ffffff';
29 
30 return (
31 <div className="bg-white/5 rounded-2xl p-4 border border-white/10">
32 {/* Color swatch */}
33 <div
34 className="rounded-xl h-48 flex items-center justify-center mb-4 transition-colors duration-300 border border-white/10"
35 style={{ backgroundColor: color }}
36 >
37 <span className="text-4xl font-bold font-mono" style={{ color: textColor }}>
38 {color.toUpperCase()}
39 </span>
40 </div>
41 
42 {/* RGB values */}
43 <div className="flex gap-3 mb-4 text-xs text-gray-400">
44 <span>R: {r}</span>
45 <span>G: {g}</span>
46 <span>B: {b}</span>
47 </div>
48 
49 {/* Buttons */}
50 <div className="flex gap-2 mb-4">
51 <button
52 onClick={generateColor}
53 className="flex-1 bg-[#52e9a2] text-[#121212] rounded-xl py-3 font-semibold hover:brightness-110 transition-all flex items-center justify-center gap-2"
54 >
55 <FaSyncAlt className="text-sm" />
56 Neue Farbe
57 </button>
58 <button
59 onClick={copyColor}
60 className="bg-white/10 text-gray-300 hover:bg-white/20 rounded-xl px-4 py-3 transition-colors flex items-center gap-2 text-sm"
61 >
62 {copied ? <FaCheck className="text-[#52e9a2]" /> : <FaCopy />}
63 {copied ? 'Kopiert!' : 'Kopieren'}
64 </button>
65 </div>
66 
67 {/* History */}
68 <div className="flex gap-2">
69 {history.map((c, i) => (
70 <button
71 key={`${c}-${i}`}
72 onClick={() => setColor(c)}
73 className="w-8 h-8 rounded-lg border border-white/10 transition-transform hover:scale-110"
74 style={{ backgroundColor: c }}
75 aria-label={`Farbe ${c} auswählen`}
76 />
77 ))}
78 </div>
79 </div>
80 );
81}
82 
83export const farbgeneratorCode = `import { useState } from 'react';
84 
85export default function Farbgenerator() {
86 const [color, setColor] = useState('#52e9a2');
87 const [history, setHistory] = useState(['#52e9a2']);
88 
89 const generateColor = () => {
90 const hex = '#' + Math.floor(Math.random() * 16777215)
91 .toString(16).padStart(6, '0');
92 setColor(hex);
93 setHistory(prev => [hex, ...prev].slice(0, 5));
94 };
95 
96 const copyColor = async () => {
97 await navigator.clipboard.writeText(color);
98 };
99 
100 // Textfarbe basierend auf Helligkeit
101 const r = parseInt(color.slice(1, 3), 16);
102 const g = parseInt(color.slice(3, 5), 16);
103 const b = parseInt(color.slice(5, 7), 16);
104 const brightness = (r * 299 + g * 587 + b * 114) / 1000;
105 const textColor = brightness > 128 ? '#000' : '#fff';
106 
107 return (
108 <div>
109 <div
110 className="color-swatch"
111 style={{ backgroundColor: color }}
112 >
113 <span style={{ color: textColor }}>
114 {color.toUpperCase()}
115 </span>
116 </div>
117 
118 <p>R: {r} G: {g} B: {b}</p>
119 
120 <button onClick={generateColor}>Neue Farbe</button>
121 <button onClick={copyColor}>Kopieren</button>
122 
123 <div className="history">
124 {history.map((c, i) => (
125 <button
126 key={i}
127 onClick={() => setColor(c)}
128 style={{ backgroundColor: c }}
129 />
130 ))}
131 </div>
132 </div>
133 );
134}`;
135 
Einheitenumrechner
components/projekte/Einheitenumrechner.tsx
0.0010
Einheitenumrechner.tsx
1'use client';
2 
3import { useState } from 'react';
4import { FaExchangeAlt } from 'react-icons/fa';
5 
6const categories: Record<string, { units: string[]; convert: (v: number, from: number, to: number) => number }> = {
7 Länge: {
8 units: ['m', 'km', 'cm', 'mm', 'Meile', 'Fuß', 'Zoll'],
9 convert: (v, from, to) => {
10 const toMeter = [1, 1000, 0.01, 0.001, 1609.344, 0.3048, 0.0254];
11 return v * toMeter[from] / toMeter[to];
12 },
13 },
14 Gewicht: {
15 units: ['kg', 'g', 'mg', 't', 'lb', 'oz'],
16 convert: (v, from, to) => {
17 const toKg = [1, 0.001, 0.000001, 1000, 0.453592, 0.0283495];
18 return v * toKg[from] / toKg[to];
19 },
20 },
21 Temperatur: {
22 units: ['°C', '°F', 'K'],
23 convert: (v, from, to) => {
24 // Convert to Celsius first
25 let c = from === 0 ? v : from === 1 ? (v - 32) * 5/9 : v - 273.15;
26 // Convert from Celsius to target
27 if (to === 0) return c;
28 if (to === 1) return c * 9/5 + 32;
29 return c + 273.15;
30 },
31 },
32};
33 
34export default function Einheitenumrechner() {
35 const [category, setCategory] = useState('Länge');
36 const [fromUnit, setFromUnit] = useState(0);
37 const [toUnit, setToUnit] = useState(1);
38 const [value, setValue] = useState('1');
39 
40 const cat = categories[category];
41 const numValue = parseFloat(value) || 0;
42 const result = cat.convert(numValue, fromUnit, toUnit);
43 
44 const swap = () => {
45 setFromUnit(toUnit);
46 setToUnit(fromUnit);
47 };
48 
49 return (
50 <div className="bg-white/5 rounded-2xl p-4 border border-white/10">
51 {/* Category tabs */}
52 <div className="flex gap-2 mb-4">
53 {Object.keys(categories).map(c => (
54 <button
55 key={c}
56 onClick={() => { setCategory(c); setFromUnit(0); setToUnit(1); }}
57 className={`flex-1 py-1.5 rounded-lg text-xs font-semibold transition-all ${
58 category === c ? 'bg-[#52e9a2]/20 text-[#52e9a2] border border-[#52e9a2]/30' : 'bg-white/5 text-gray-500 border border-white/10'
59 }`}
60 >
61 {c}
62 </button>
63 ))}
64 </div>
65 
66 {/* From */}
67 <div className="bg-[#0a0a0f] rounded-xl p-3 border border-white/10 mb-2">
68 <div className="flex gap-2">
69 <input
70 type="number" value={value} onChange={e => setValue(e.target.value)}
71 className="flex-1 bg-transparent text-white text-xl font-bold focus:outline-none min-w-0"
72 />
73 <select
74 value={fromUnit} onChange={e => setFromUnit(Number(e.target.value))}
75 className="bg-white/10 text-white rounded-lg px-2 py-1 text-sm focus:outline-none border border-white/10"
76 >
77 {cat.units.map((u, i) => <option key={u} value={i} className="bg-[#121212]">{u}</option>)}
78 </select>
79 </div>
80 </div>
81 
82 {/* Swap button */}
83 <div className="flex justify-center -my-1 relative z-10">
84 <button onClick={swap} className="bg-[#52e9a2] text-[#121212] rounded-full w-8 h-8 flex items-center justify-center hover:brightness-110 transition-all">
85 <FaExchangeAlt className="text-xs rotate-90" />
86 </button>
87 </div>
88 
89 {/* To */}
90 <div className="bg-[#0a0a0f] rounded-xl p-3 border border-white/10 mt-2">
91 <div className="flex gap-2">
92 <div className="flex-1 text-[#52e9a2] text-xl font-bold">
93 {result % 1 === 0 ? result : result.toFixed(4)}
94 </div>
95 <select
96 value={toUnit} onChange={e => setToUnit(Number(e.target.value))}
97 className="bg-white/10 text-white rounded-lg px-2 py-1 text-sm focus:outline-none border border-white/10"
98 >
99 {cat.units.map((u, i) => <option key={u} value={i} className="bg-[#121212]">{u}</option>)}
100 </select>
101 </div>
102 </div>
103 </div>
104 );
105}
106 
107export const einheitenumrechnerCode = `import { useState } from 'react';
108 
109const categories = {
110 Laenge: {
111 units: ['m', 'km', 'cm', 'mm', 'Meile'],
112 convert: (v, from, to) => {
113 const toMeter = [1, 1000, 0.01, 0.001, 1609.344];
114 return v * toMeter[from] / toMeter[to];
115 },
116 },
117 Gewicht: {
118 units: ['kg', 'g', 'mg', 't', 'lb'],
119 convert: (v, from, to) => {
120 const toKg = [1, 0.001, 0.000001, 1000, 0.4536];
121 return v * toKg[from] / toKg[to];
122 },
123 },
124 Temperatur: {
125 units: ['Celsius', 'Fahrenheit', 'Kelvin'],
126 convert: (v, from, to) => {
127 let c = from === 0 ? v
128 : from === 1 ? (v - 32) * 5/9
129 : v - 273.15;
130 if (to === 0) return c;
131 if (to === 1) return c * 9/5 + 32;
132 return c + 273.15;
133 },
134 },
135};
136 
137export default function Einheitenumrechner() {
138 const [category, setCategory] = useState('Laenge');
139 const [fromUnit, setFromUnit] = useState(0);
140 const [toUnit, setToUnit] = useState(1);
141 const [value, setValue] = useState('1');
142 
143 const cat = categories[category];
144 const result = cat.convert(
145 parseFloat(value) || 0, fromUnit, toUnit
146 );
147 
148 return (
149 <div>
150 {Object.keys(categories).map(c => (
151 <button key={c} onClick={() => setCategory(c)}>
152 {c}
153 </button>
154 ))}
155 
156 <input type="number" value={value}
157 onChange={e => setValue(e.target.value)} />
158 <select value={fromUnit}
159 onChange={e => setFromUnit(Number(e.target.value))}>
160 {cat.units.map((u, i) => (
161 <option key={u} value={i}>{u}</option>
162 ))}
163 </select>
164 
165 <p>= {result.toFixed(4)}</p>
166 
167 <select value={toUnit}
168 onChange={e => setToUnit(Number(e.target.value))}>
169 {cat.units.map((u, i) => (
170 <option key={u} value={i}>{u}</option>
171 ))}
172 </select>
173 </div>
174 );
175}`;
176 
Zeichenzähler
components/projekte/ZeichenZaehler.tsx
0
Zeichen
0
Ohne Leerzeichen
0
Wörter
0
Sätze
0
Absätze
~1 Min
Lesezeit
ZeichenZaehler.tsx
1'use client';
2 
3import { useState } from 'react';
4 
5export default function ZeichenZaehler() {
6 const [text, setText] = useState('');
7 
8 const chars = text.length;
9 const charsNoSpaces = text.replace(/\s/g, '').length;
10 const words = text.trim() ? text.trim().split(/\s+/).length : 0;
11 const sentences = text.trim() ? text.split(/[.!?]+/).filter(s => s.trim()).length : 0;
12 const paragraphs = text.trim() ? text.split(/\n\n+/).filter(p => p.trim()).length : 0;
13 const readTime = Math.max(1, Math.ceil(words / 200));
14 
15 const stats = [
16 { label: 'Zeichen', value: chars },
17 { label: 'Ohne Leerzeichen', value: charsNoSpaces },
18 { label: 'Wörter', value: words },
19 { label: 'Sätze', value: sentences },
20 { label: 'Absätze', value: paragraphs },
21 { label: 'Lesezeit', value: `~${readTime} Min` },
22 ];
23 
24 return (
25 <div className="bg-white/5 rounded-2xl p-4 border border-white/10">
26 <textarea
27 value={text}
28 onChange={e => setText(e.target.value)}
29 placeholder="Text hier eingeben oder einfügen..."
30 rows={5}
31 className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-gray-500 focus:border-[#52e9a2] focus:outline-none transition-colors text-sm resize-none mb-4"
32 />
33 
34 <div className="grid grid-cols-3 gap-2">
35 {stats.map(s => (
36 <div key={s.label} className="bg-[#0a0a0f] rounded-xl p-3 text-center border border-white/10">
37 <div className="text-lg font-bold text-[#52e9a2]">{s.value}</div>
38 <div className="text-[10px] text-gray-500">{s.label}</div>
39 </div>
40 ))}
41 </div>
42 </div>
43 );
44}
45 
46export const zeichenZaehlerCode = `import { useState } from 'react';
47 
48export default function ZeichenZaehler() {
49 const [text, setText] = useState('');
50 
51 const chars = text.length;
52 const charsNoSpaces = text.replace(/\\s/g, '').length;
53 const words = text.trim()
54 ? text.trim().split(/\\s+/).length
55 : 0;
56 const sentences = text.trim()
57 ? text.split(/[.!?]+/).filter(s => s.trim()).length
58 : 0;
59 const readTime = Math.max(1, Math.ceil(words / 200));
60 
61 return (
62 <div>
63 <textarea
64 value={text}
65 onChange={e => setText(e.target.value)}
66 placeholder="Text hier eingeben..."
67 rows={5}
68 />
69 
70 <div className="stats-grid">
71 <div>
72 <strong>{chars}</strong>
73 <span>Zeichen</span>
74 </div>
75 <div>
76 <strong>{charsNoSpaces}</strong>
77 <span>Ohne Leerzeichen</span>
78 </div>
79 <div>
80 <strong>{words}</strong>
81 <span>Woerter</span>
82 </div>
83 <div>
84 <strong>{sentences}</strong>
85 <span>Saetze</span>
86 </div>
87 <div>
88 <strong>~{readTime} Min</strong>
89 <span>Lesezeit</span>
90 </div>
91 </div>
92 </div>
93 );
94}`;
95 
Gradient-Generator
components/projekte/GradientGenerator.tsx
#52e9a2
#3b82f6
Winkel135°
background: linear-gradient(135deg, #52e9a2, #3b82f6);
GradientGenerator.tsx
1'use client';
2 
3import { useState } from 'react';
4import { FaCopy, FaCheck, FaSyncAlt } from 'react-icons/fa';
5 
6export default function GradientGenerator() {
7 const [color1, setColor1] = useState('#52e9a2');
8 const [color2, setColor2] = useState('#3b82f6');
9 const [angle, setAngle] = useState(135);
10 const [type, setType] = useState<'linear' | 'radial'>('linear');
11 const [copied, setCopied] = useState(false);
12 
13 const gradient = type === 'linear'
14 ? `linear-gradient(${angle}deg, ${color1}, ${color2})`
15 : `radial-gradient(circle, ${color1}, ${color2})`;
16 
17 const cssCode = `background: ${gradient};`;
18 
19 const copy = async () => {
20 await navigator.clipboard.writeText(cssCode);
21 setCopied(true);
22 setTimeout(() => setCopied(false), 2000);
23 };
24 
25 const randomize = () => {
26 const rHex = () => '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
27 setColor1(rHex());
28 setColor2(rHex());
29 setAngle(Math.floor(Math.random() * 360));
30 };
31 
32 return (
33 <div className="bg-white/5 rounded-2xl p-4 border border-white/10">
34 {/* Preview */}
35 <div className="rounded-xl h-40 mb-4 border border-white/10" style={{ background: gradient }} />
36 
37 {/* Type selector */}
38 <div className="flex gap-2 mb-3">
39 {(['linear', 'radial'] as const).map(t => (
40 <button
41 key={t}
42 onClick={() => setType(t)}
43 className={`flex-1 py-1.5 rounded-lg text-xs font-semibold transition-all capitalize ${
44 type === t ? 'bg-[#52e9a2]/20 text-[#52e9a2] border border-[#52e9a2]/30' : 'bg-white/5 text-gray-500 border border-white/10'
45 }`}
46 >
47 {t}
48 </button>
49 ))}
50 </div>
51 
52 {/* Controls */}
53 <div className="flex gap-3 mb-3">
54 <div className="flex-1">
55 <label className="text-xs text-gray-500 mb-1 block">Farbe 1</label>
56 <div className="flex gap-2 items-center">
57 <input type="color" value={color1} onChange={e => setColor1(e.target.value)} className="w-8 h-8 rounded-lg border-0 cursor-pointer bg-transparent" />
58 <span className="text-xs text-gray-400 font-mono">{color1}</span>
59 </div>
60 </div>
61 <div className="flex-1">
62 <label className="text-xs text-gray-500 mb-1 block">Farbe 2</label>
63 <div className="flex gap-2 items-center">
64 <input type="color" value={color2} onChange={e => setColor2(e.target.value)} className="w-8 h-8 rounded-lg border-0 cursor-pointer bg-transparent" />
65 <span className="text-xs text-gray-400 font-mono">{color2}</span>
66 </div>
67 </div>
68 </div>
69 
70 {type === 'linear' && (
71 <div className="flex items-center gap-3 mb-3">
72 <span className="text-xs text-gray-500">Winkel</span>
73 <input type="range" min={0} max={360} value={angle} onChange={e => setAngle(Number(e.target.value))} className="flex-1 accent-[#52e9a2]" />
74 <span className="text-xs text-white w-10 text-right">{angle}°</span>
75 </div>
76 )}
77 
78 {/* CSS output */}
79 <div className="bg-[#0a0a0f] rounded-xl px-3 py-2 border border-white/10 mb-3 flex items-center gap-2">
80 <code className="text-[#52e9a2] text-xs flex-1 truncate font-mono">{cssCode}</code>
81 <button onClick={copy} className="text-gray-400 hover:text-[#52e9a2] transition-colors shrink-0">
82 {copied ? <FaCheck className="text-[#52e9a2] text-sm" /> : <FaCopy className="text-sm" />}
83 </button>
84 </div>
85 
86 <button onClick={randomize} className="w-full bg-[#52e9a2] text-[#121212] rounded-xl py-2.5 font-semibold hover:brightness-110 transition-all flex items-center justify-center gap-2 text-sm">
87 <FaSyncAlt /> Zufällig
88 </button>
89 </div>
90 );
91}
92 
93export const gradientGeneratorCode = `import { useState } from 'react';
94 
95export default function GradientGenerator() {
96 const [color1, setColor1] = useState('#52e9a2');
97 const [color2, setColor2] = useState('#3b82f6');
98 const [angle, setAngle] = useState(135);
99 const [type, setType] = useState('linear');
100 
101 const gradient = type === 'linear'
102 ? 'linear-gradient(' + angle + 'deg, '
103 + color1 + ', ' + color2 + ')'
104 : 'radial-gradient(circle, '
105 + color1 + ', ' + color2 + ')';
106 
107 const cssCode = 'background: ' + gradient + ';';
108 
109 const copy = async () => {
110 await navigator.clipboard.writeText(cssCode);
111 };
112 
113 const randomize = () => {
114 const rHex = () => '#' + Math.floor(
115 Math.random() * 16777215
116 ).toString(16).padStart(6, '0');
117 setColor1(rHex());
118 setColor2(rHex());
119 setAngle(Math.floor(Math.random() * 360));
120 };
121 
122 return (
123 <div>
124 <div className="preview"
125 style={{ background: gradient }} />
126 
127 {['linear', 'radial'].map(t => (
128 <button key={t} onClick={() => setType(t)}>
129 {t}
130 </button>
131 ))}
132 
133 <input type="color" value={color1}
134 onChange={e => setColor1(e.target.value)} />
135 <input type="color" value={color2}
136 onChange={e => setColor2(e.target.value)} />
137 
138 {type === 'linear' && (
139 <input type="range" min={0} max={360}
140 value={angle}
141 onChange={e => setAngle(Number(e.target.value))}
142 />
143 )}
144 
145 <code>{cssCode}</code>
146 <button onClick={copy}>Kopieren</button>
147 <button onClick={randomize}>Zufaellig</button>
148 </div>
149 );
150}`;
151 
BMI-Rechner
components/projekte/BMIRechner.tsx
BMIRechner.tsx
1'use client';
2 
3import { useState } from 'react';
4 
5export default function BMIRechner() {
6 const [weight, setWeight] = useState('75');
7 const [height, setHeight] = useState('175');
8 const [result, setResult] = useState<{
9 bmi: number;
10 category: string;
11 color: string;
12 } | null>(null);
13 
14 const calculate = () => {
15 const w = parseFloat(weight);
16 const h = parseFloat(height) / 100;
17 if (!w || !h) return;
18 const bmi = w / (h * h);
19 let category = '';
20 let color = '';
21 if (bmi < 18.5) {
22 category = 'Untergewicht';
23 color = 'text-sky-400';
24 } else if (bmi < 25) {
25 category = 'Normalgewicht';
26 color = 'text-[#52e9a2]';
27 } else if (bmi < 30) {
28 category = 'Übergewicht';
29 color = 'text-yellow-400';
30 } else {
31 category = 'Adipositas';
32 color = 'text-red-400';
33 }
34 setResult({ bmi: Math.round(bmi * 10) / 10, category, color });
35 };
36 
37 return (
38 <div className="bg-white/5 rounded-2xl p-4 border border-white/10">
39 <div className="space-y-3 mb-4">
40 <div>
41 <label className="text-gray-400 text-xs mb-1 block">
42 Gewicht (kg)
43 </label>
44 <input
45 type="number"
46 value={weight}
47 onChange={(e) => setWeight(e.target.value)}
48 className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:border-[#52e9a2] focus:outline-none transition-colors"
49 />
50 </div>
51 <div>
52 <label className="text-gray-400 text-xs mb-1 block">Größe (cm)</label>
53 <input
54 type="number"
55 value={height}
56 onChange={(e) => setHeight(e.target.value)}
57 className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:border-[#52e9a2] focus:outline-none transition-colors"
58 />
59 </div>
60 </div>
61 
62 <button
63 onClick={calculate}
64 className="w-full bg-[#52e9a2] text-[#121212] rounded-xl py-3 font-semibold hover:brightness-110 transition-all mb-4"
65 >
66 Berechnen
67 </button>
68 
69 {result && (
70 <div className="bg-[#0a0a0f] rounded-xl p-4 text-center border border-white/10">
71 <div className="text-4xl font-bold text-white mb-1">{result.bmi}</div>
72 <div className={`text-lg font-semibold ${result.color}`}>
73 {result.category}
74 </div>
75 {/* Scale */}
76 <div className="mt-3 h-2 rounded-full bg-linear-to-r from-sky-400 via-[#52e9a2] via-yellow-400 to-red-400 relative">
77 <div
78 className="absolute w-3 h-3 bg-white rounded-full -top-0.5 border-2 border-[#0a0a0f] transition-all duration-500"
79 style={{
80 left: `${Math.min(Math.max(((result.bmi - 15) / 25) * 100, 0), 100)}%`,
81 }}
82 />
83 </div>
84 <div className="flex justify-between text-[10px] text-gray-500 mt-1">
85 <span>15</span>
86 <span>18.5</span>
87 <span>25</span>
88 <span>30</span>
89 <span>40</span>
90 </div>
91 </div>
92 )}
93 </div>
94 );
95}
96 
97export const bmiRechnerCode = `import { useState } from 'react';
98 
99export default function BMIRechner() {
100 const [weight, setWeight] = useState('75');
101 const [height, setHeight] = useState('175');
102 const [result, setResult] = useState(null);
103 
104 const calculate = () => {
105 const w = parseFloat(weight);
106 const h = parseFloat(height) / 100;
107 if (!w || !h) return;
108 
109 const bmi = w / (h * h);
110 let category = '';
111 
112 if (bmi < 18.5) category = 'Untergewicht';
113 else if (bmi < 25) category = 'Normalgewicht';
114 else if (bmi < 30) category = 'Uebergewicht';
115 else category = 'Adipositas';
116 
117 setResult({
118 bmi: Math.round(bmi * 10) / 10,
119 category,
120 });
121 };
122 
123 return (
124 <div>
125 <label>Gewicht (kg)</label>
126 <input
127 type="number" value={weight}
128 onChange={e => setWeight(e.target.value)}
129 />
130 
131 <label>Groesse (cm)</label>
132 <input
133 type="number" value={height}
134 onChange={e => setHeight(e.target.value)}
135 />
136 
137 <button onClick={calculate}>Berechnen</button>
138 
139 {result && (
140 <div className="result">
141 <p>BMI: {result.bmi}</p>
142 <p>{result.category}</p>
143 </div>
144 )}
145 </div>
146 );
147}`;
148