Web-Tools
Nützliche Tools im Browser — kostenlos, quelloffen & ohne Anmeldung
TaschenrechnerGrundrechenarten im Browser
components/projekte/Taschenrechner.tsx0
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 ZufallsgeneratorZufallszahlen mit einstellbarem Bereich
components/tools/ZufallsGenerator.tsxKlicke "Generieren"
ZufallsGenerator.tsx
1/**2 * @file components/tools/ZufallsGenerator.tsx3 * @description Interaktiver Zufallszahl-Generator mit Min/Max, Animation und History.4 * @author Andreas Andorfer5 */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-Animation28 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.span49 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 "Generieren"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 <input70 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 <input79 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 <button89 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 Generieren95 </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 <span107 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-GeneratorSichere Passwörter auf Knopfdruck
components/projekte/PasswortGenerator.tsxKlicke "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 <input80 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 <button94 key={opt.label}95 onClick={() => opt.set(!opt.state)}96 className={`py-2 rounded-xl text-sm font-semibold transition-all ${97 opt.state98 ? '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 /> Generieren109 </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 <input154 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 Grossbuchstaben162 </label>163 <label>164 <input type="checkbox" checked={numbers}165 onChange={() => setNumbers(!numbers)} />166 Zahlen167 </label>168 <label>169 <input type="checkbox" checked={symbols}170 onChange={() => setSymbols(!symbols)} />171 Sonderzeichen172 </label>173 <button onClick={generate}>Generieren</button>174 </div>175 );176}`;177 FarbgeneratorZufällige Farben mit HEX & RGB
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 dark24 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 <div34 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 <button52 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 Farbe57 </button>58 <button59 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 <button71 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 Helligkeit101 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 <div110 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 <button126 key={i}127 onClick={() => setColor(c)}128 style={{ backgroundColor: c }}129 />130 ))}131 </div>132 </div>133 );134}`;135 EinheitenumrechnerLänge, Gewicht, Temperatur & mehr
components/projekte/Einheitenumrechner.tsx0.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 first25 let c = from === 0 ? v : from === 1 ? (v - 32) * 5/9 : v - 273.15;26 // Convert from Celsius to target27 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 <button55 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 <input70 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 <select74 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 <select96 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 ? v128 : from === 1 ? (v - 32) * 5/9129 : 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, toUnit146 );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ählerZeichen, Wörter & Lesezeit
components/projekte/ZeichenZaehler.tsx0
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 <textarea27 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+/).length55 : 0;56 const sentences = text.trim()57 ? text.split(/[.!?]+/).filter(s => s.trim()).length58 : 0;59 const readTime = Math.max(1, Math.ceil(words / 200));60 61 return (62 <div>63 <textarea64 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-GeneratorCSS-Verläufe per Klick erstellen
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 <button41 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ällig88 </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() * 16777215116 ).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-RechnerBody-Mass-Index berechnen
components/projekte/BMIRechner.tsxBMIRechner.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 <input45 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 <input54 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 <button63 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 Berechnen67 </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 <div78 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 <input127 type="number" value={weight}128 onChange={e => setWeight(e.target.value)}129 />130 131 <label>Groesse (cm)</label>132 <input133 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