Added favicon and logo to website

This commit is contained in:
danielvici123
2025-05-16 20:55:47 +02:00
parent cf7e3d3775
commit 32f339fbda
21 changed files with 1422 additions and 125 deletions

View File

@@ -1,6 +1,6 @@
export default function About() {
return (
<main className="min-h-screen p-8">
<main className="min-h-screen p-8 pt-24">
<div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">About</h1>
@@ -13,13 +13,9 @@ export default function About() {
The Source Code is available on GitHub (link below).
</p>
<h2 className="text-2xl font-bold text-white mt-6 mb-4">Features</h2>
<ul className="list-disc pl-6 text-gray-300 space-y-2">
<li>Password Generator - Create secure passwords with customizable options</li>
<li>Image Converter - Convert images between different formats</li>
<li>Website Bookmarks - Save and organize your favorite websites</li>
<li>More tools coming soon!</li>
</ul>
<div className="bg-zinc-900 rounded-lg p-4 mb-6">
<p className="text-sm text-blue-400 font-mono">Current Version: 0.2.0</p>
</div>
<h2 className="text-2xl font-bold text-white mt-6 mb-4">Privacy</h2>
<p className="text-gray-300 mb-6">

View File

@@ -34,6 +34,15 @@ export async function POST(req: NextRequest) {
case 'gif':
convertedBuffer = await sharpInstance.gif().toBuffer()
break
case 'ico':
convertedBuffer = await sharpInstance
.resize(256, 256, {
fit: 'contain',
background: { r: 0, g: 0, b: 0, alpha: 0 }
})
.png()
.toBuffer()
break
default:
throw new Error('Unsupported format')
}

View File

@@ -0,0 +1,186 @@
'use client'
import { useState } from 'react'
export default function ScientificCalculator() {
const [display, setDisplay] = useState('0')
const [memory, setMemory] = useState<number>(0)
const [lastOperation, setLastOperation] = useState<string | null>(null)
const [newNumber, setNewNumber] = useState(true)
const operations = {
'+': (a: number, b: number) => a + b,
'-': (a: number, b: number) => a - b,
'×': (a: number, b: number) => a * b,
'÷': (a: number, b: number) => a / b,
'xⁿ': (a: number, b: number) => Math.pow(a, b),
'ⁿ√x': (a: number, b: number) => Math.pow(a, 1/b),
}
const handleNumber = (num: string) => {
if (newNumber) {
setDisplay(num)
setNewNumber(false)
} else {
setDisplay(display === '0' ? num : display + num)
}
}
const handleDecimal = () => {
if (newNumber) {
setDisplay('0.')
setNewNumber(false)
} else if (!display.includes('.')) {
setDisplay(display + '.')
}
}
const handleOperation = (op: string) => {
setMemory(parseFloat(display))
setLastOperation(op)
setNewNumber(true)
}
const handleEquals = () => {
if (lastOperation && !newNumber) {
const current = parseFloat(display)
const operation = operations[lastOperation as keyof typeof operations]
const result = operation(memory, current)
setDisplay(result.toString())
setNewNumber(true)
setLastOperation(null)
}
}
const handleFunction = (func: string) => {
const num = parseFloat(display)
let result: number
switch (func) {
case 'sin':
result = Math.sin(num * Math.PI / 180)
break
case 'cos':
result = Math.cos(num * Math.PI / 180)
break
case 'tan':
result = Math.tan(num * Math.PI / 180)
break
case 'log':
result = Math.log10(num)
break
case 'ln':
result = Math.log(num)
break
case '√':
result = Math.sqrt(num)
break
case 'x²':
result = num * num
break
case '1/x':
result = 1 / num
break
default:
return
}
setDisplay(result.toString())
setNewNumber(true)
}
const handleClear = () => {
setDisplay('0')
setMemory(0)
setLastOperation(null)
setNewNumber(true)
}
const handleBackspace = () => {
if (display.length > 1) {
setDisplay(display.slice(0, -1))
} else {
setDisplay('0')
setNewNumber(true)
}
}
const handlePlusMinus = () => {
setDisplay(display.startsWith('-') ? display.slice(1) : '-' + display)
}
const Button = ({ children, onClick, className = '' }: { children: React.ReactNode, onClick: () => void, className?: string }) => (
<button
onClick={onClick}
className={`p-3 text-white rounded-md transition-colors ${className}`}
>
{children}
</button>
)
return (
<main className="min-h-screen p-8 pt-24">
<div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">Scientific Calculator</h1>
<div className="bg-zinc-800 rounded-lg shadow-md p-6">
<div className="prose prose-invert max-w-none mb-6">
<p className="text-gray-300">
A scientific calculator with advanced mathematical functions including trigonometry,
logarithms, and exponential operations.
</p>
</div>
<div className="space-y-4">
<div className="bg-zinc-900 p-4 rounded-lg">
<input
type="text"
readOnly
value={display}
className="w-full bg-transparent text-right text-2xl text-white font-mono outline-none"
/>
</div>
<div className="grid grid-cols-4 gap-2">
{/* Scientific Functions */}
<Button onClick={() => handleFunction('sin')} className="bg-zinc-700 hover:bg-zinc-600">sin</Button>
<Button onClick={() => handleFunction('cos')} className="bg-zinc-700 hover:bg-zinc-600">cos</Button>
<Button onClick={() => handleFunction('tan')} className="bg-zinc-700 hover:bg-zinc-600">tan</Button>
<Button onClick={handleClear} className="bg-red-600 hover:bg-red-700">C</Button>
<Button onClick={() => handleFunction('log')} className="bg-zinc-700 hover:bg-zinc-600">log</Button>
<Button onClick={() => handleFunction('ln')} className="bg-zinc-700 hover:bg-zinc-600">ln</Button>
<Button onClick={() => handleFunction('√')} className="bg-zinc-700 hover:bg-zinc-600"></Button>
<Button onClick={handleBackspace} className="bg-zinc-700 hover:bg-zinc-600"></Button>
<Button onClick={() => handleFunction('x²')} className="bg-zinc-700 hover:bg-zinc-600">x²</Button>
<Button onClick={() => handleOperation('xⁿ')} className="bg-zinc-700 hover:bg-zinc-600">xⁿ</Button>
<Button onClick={() => handleOperation('ⁿ√x')} className="bg-zinc-700 hover:bg-zinc-600">x</Button>
<Button onClick={() => handleOperation('÷')} className="bg-blue-600 hover:bg-blue-700">÷</Button>
{/* Numbers and Basic Operations */}
<Button onClick={() => handleNumber('7')} className="bg-zinc-600 hover:bg-zinc-500">7</Button>
<Button onClick={() => handleNumber('8')} className="bg-zinc-600 hover:bg-zinc-500">8</Button>
<Button onClick={() => handleNumber('9')} className="bg-zinc-600 hover:bg-zinc-500">9</Button>
<Button onClick={() => handleOperation('×')} className="bg-blue-600 hover:bg-blue-700">×</Button>
<Button onClick={() => handleNumber('4')} className="bg-zinc-600 hover:bg-zinc-500">4</Button>
<Button onClick={() => handleNumber('5')} className="bg-zinc-600 hover:bg-zinc-500">5</Button>
<Button onClick={() => handleNumber('6')} className="bg-zinc-600 hover:bg-zinc-500">6</Button>
<Button onClick={() => handleOperation('-')} className="bg-blue-600 hover:bg-blue-700">-</Button>
<Button onClick={() => handleNumber('1')} className="bg-zinc-600 hover:bg-zinc-500">1</Button>
<Button onClick={() => handleNumber('2')} className="bg-zinc-600 hover:bg-zinc-500">2</Button>
<Button onClick={() => handleNumber('3')} className="bg-zinc-600 hover:bg-zinc-500">3</Button>
<Button onClick={() => handleOperation('+')} className="bg-blue-600 hover:bg-blue-700">+</Button>
<Button onClick={() => handlePlusMinus()} className="bg-zinc-600 hover:bg-zinc-500">±</Button>
<Button onClick={() => handleNumber('0')} className="bg-zinc-600 hover:bg-zinc-500">0</Button>
<Button onClick={() => handleDecimal()} className="bg-zinc-600 hover:bg-zinc-500">.</Button>
<Button onClick={() => handleEquals()} className="bg-green-600 hover:bg-green-700">=</Button>
</div>
</div>
</div>
</div>
</main>
)
}

40
app/changelog/page.tsx Normal file
View File

@@ -0,0 +1,40 @@
export default function Changelog() {
return (
<main className="min-h-screen p-8 pt-24">
<div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">Changelog</h1>
<div className="bg-zinc-800 rounded-lg shadow-md p-6 space-y-8">
<div className="prose prose-invert max-w-none">
<div className="space-y-8">
<div>
<h2 className="text-2xl font-bold text-white mb-4">Version 0.2.0</h2>
<ul className="list-disc pl-5 space-y-2 text-gray-300">
<li>Added Settings and Changelog pages</li>
<li>Color Converter added</li>
<li>Enhanced QR code generator</li>
<li>Currency converter added</li>
<li>Number Converter added</li>
<li>Scientific Calculator added</li>
<li>Removed bookmarked websites feature</li>
<li>Updated navigation structure</li>
</ul>
</div>
<div>
<h2 className="text-2xl font-bold text-white mb-4">Version 0.1.0</h2>
<ul className="list-disc pl-5 space-y-2 text-gray-300">
<li>Initial release</li>
<li>Added basic tools: Password Generator, QR Code Generator</li>
<li>Added converters: Number, Color, Currency</li>
<li>Added Scientific Calculator</li>
<li>Basic website layout and navigation</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</main>
)
}

View File

@@ -1,13 +1,13 @@
export default function ComingSoon() {
return (
<main className="min-h-screen p-8">
<main className="min-h-screen p-8 pt-24">
<div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">Coming Soon</h1>
<div className="bg-zinc-800 rounded-lg shadow-md p-6 space-y-8">
<div className="prose prose-invert max-w-none">
<p className="text-gray-300 mb-6">
This project is maintained and developed by <a href="https://github.com/danielvici" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:text-blue-600">me</a> as an individual developer.
This project is maintained and developed by <a href="https://github.com/danielvici" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:text-blue-600">me</a> as an individual developer.
New functions and tools will be added as time and inspiration allow.
</p>
@@ -16,10 +16,42 @@ export default function ComingSoon() {
Instead, I add new tools and functions when I have a good idea and the time allows.
</p>
<p className="text-gray-300 mt-8">
If you have suggestions for new tools or features, you can submit them via GitHub
or contact <a href="https://x.com/danielvici123" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:text-blue-600">me</a> directly. I welcome any feedback and suggestions!
</p>
<div className="space-y-6">
<div>
<h2 className="text-2xl font-bold text-white mb-4">Planned Features</h2>
<ul className="list-disc pl-5 space-y-2 text-gray-300">
<li>Text Tools
<ul className="list-disc pl-5 mt-2 space-y-1">
<li>Text Case Converter</li>
<li>Lorem Ipsum Generator</li>
<li>String Hash Generator</li>
</ul>
</li>
<li>Development Tools
<ul className="list-disc pl-5 mt-2 space-y-1">
<li>JSON Formatter</li>
<li>Base64 Encoder/Decoder</li>
<li>URL Encoder/Decoder</li>
</ul>
</li>
<li>Image Tools
<ul className="list-disc pl-5 mt-2 space-y-1">
<li>Image Resizer</li>
<li>Image Format Converter</li>
<li>Image Compression</li>
</ul>
</li>
</ul>
</div>
<div className="bg-blue-900/20 rounded-lg p-6 border border-blue-800">
<h3 className="text-xl font-bold text-blue-400 mb-2">Want to suggest a feature?</h3>
<p className="text-gray-300">
Feel free to suggest new tools or features through the GitHub repository or contact <a href="https://x.com/danielvici123" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:text-blue-600">me</a> directly.
I welcome any feedback and suggestions!
</p>
</div>
</div>
</div>
</div>
</div>

View File

@@ -7,12 +7,26 @@ export default function AddWebsite() {
const [title, setTitle] = useState('')
const [isAdding, setIsAdding] = useState(false)
const formatUrl = (inputUrl: string) => {
// Remove leading/trailing whitespace
let formattedUrl = inputUrl.trim()
// If URL doesn't start with a protocol, add https://
if (!formattedUrl.match(/^https?:\/\//i)) {
formattedUrl = 'https://' + formattedUrl
}
return formattedUrl
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
// Validiere URL
const formattedUrl = formatUrl(url)
// Validate URL
try {
new URL(url)
new URL(formattedUrl)
} catch {
alert('Please enter a valid URL')
return
@@ -20,23 +34,23 @@ export default function AddWebsite() {
const newWebsite: PinnedWebsite = {
id: Date.now().toString(),
title: title || url,
url: url.startsWith('http') ? url : `https://${url}`,
title: title || formattedUrl,
url: formattedUrl,
addedAt: Date.now()
}
// Lade bestehende Websites
// Load existing websites
const existingWebsites = JSON.parse(localStorage.getItem('pinnedWebsites') || '[]')
// Füge neue Website hinzu
// Add new website
localStorage.setItem('pinnedWebsites', JSON.stringify([...existingWebsites, newWebsite]))
// Setze Formular zurück
// Reset form
setUrl('')
setTitle('')
setIsAdding(false)
// Lade die Seite neu um die neue Website anzuzeigen
// Reload page to show new website
window.location.reload()
}
@@ -59,10 +73,13 @@ export default function AddWebsite() {
type="text"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="https://example.com"
placeholder="example.com"
className="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2"
required
/>
<p className="text-sm text-gray-400 mt-1">
HTTPS will be added automatically if not provided
</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">

View File

@@ -4,15 +4,23 @@ import Link from 'next/link'
export default function Navbar() {
return (
<nav className="bg-blue-900 p-4 shadow-lg">
<nav className="bg-blue-900 p-4 shadow-lg fixed w-full top-0 z-50">
<div className="container mx-auto">
<div className="flex justify-between items-center">
<div className="flex-1" /> {/* Spacer */}
<Link href="/" className="text-white font-bold text-xl hover:text-blue-100">
<Link href="/" className="text-white font-bold text-xl hover:text-blue-200">
HOME
</Link>
<div className="flex-1 flex justify-end">
<Link href="/about" className="text-white hover:text-blue-100">
<div className="flex gap-6">
<Link href="/coming_soon" className="text-white hover:text-blue-200">
Coming Soon
</Link>
<Link href="/changelog" className="text-white hover:text-blue-200">
Changelog
</Link>
<Link href="/settings" className="text-white hover:text-blue-200">
Settings
</Link>
<Link href="/about" className="text-white hover:text-blue-200">
About
</Link>
</div>

View File

@@ -0,0 +1,155 @@
'use client'
import { useState, useEffect } from 'react'
type ColorFormats = {
hex: string
rgb: string
hsl: string
}
export default function ColorConverter() {
const [color, setColor] = useState('#000000')
const [formats, setFormats] = useState<ColorFormats>({
hex: '#000000',
rgb: 'rgb(0, 0, 0)',
hsl: 'hsl(0, 0%, 0%)'
})
const hexToRgb = (hex: string): number[] => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result
? [
parseInt(result[1], 16),
parseInt(result[2], 16),
parseInt(result[3], 16)
]
: [0, 0, 0]
}
const rgbToHsl = (r: number, g: number, b: number): number[] => {
r /= 255
g /= 255
b /= 255
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
let h = 0
let s = 0
const l = (max + min) / 2
if (max !== min) {
const d = max - min
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0)
break
case g:
h = (b - r) / d + 2
break
case b:
h = (r - g) / d + 4
break
}
h /= 6
}
return [
Math.round(h * 360),
Math.round(s * 100),
Math.round(l * 100)
]
}
const updateFormats = (hex: string) => {
const rgb = hexToRgb(hex)
const hsl = rgbToHsl(rgb[0], rgb[1], rgb[2])
setFormats({
hex: hex,
rgb: `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`,
hsl: `hsl(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%)`
})
}
useEffect(() => {
updateFormats(color)
}, [color])
return (
<main className="min-h-screen p-8 pt-24">
<div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">Color Converter</h1>
<div className="bg-zinc-800 rounded-lg shadow-md p-6">
<div className="prose prose-invert max-w-none mb-6">
<p className="text-gray-300">
Convert colors between different formats: HEX, RGB, and HSL.
Use the color picker or enter values manually to see the conversions.
</p>
</div>
<div className="space-y-6">
<div className="flex flex-col md:flex-row gap-6">
<div className="w-full">
<label className="block text-sm font-medium text-gray-300 mb-2">
Color Picker
</label>
<input
type="color"
value={color}
onChange={(e) => setColor(e.target.value)}
className="w-full h-12 rounded-md cursor-pointer bg-zinc-700 p-1"
/>
</div>
</div>
<div className="space-y-4">
<h2 className="text-xl font-semibold text-white">Color Values</h2>
<div className="grid gap-4">
<div className="bg-zinc-900 p-4 rounded-lg">
<label className="block text-sm font-medium text-gray-400 mb-1">
HEX
</label>
<input
type="text"
readOnly
value={formats.hex}
className="w-full bg-zinc-800 text-white rounded-md border border-gray-700 p-2"
/>
</div>
<div className="bg-zinc-900 p-4 rounded-lg">
<label className="block text-sm font-medium text-gray-400 mb-1">
RGB
</label>
<input
type="text"
readOnly
value={formats.rgb}
className="w-full bg-zinc-800 text-white rounded-md border border-gray-700 p-2"
/>
</div>
<div className="bg-zinc-900 p-4 rounded-lg">
<label className="block text-sm font-medium text-gray-400 mb-1">
HSL
</label>
<input
type="text"
readOnly
value={formats.hsl}
className="w-full bg-zinc-800 text-white rounded-md border border-gray-700 p-2"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
)
}

View File

@@ -0,0 +1,173 @@
'use client'
import { useState, useEffect } from 'react'
type ExchangeRates = {
[key: string]: number
}
const POPULAR_CURRENCIES = [
{ code: 'USD', name: 'US Dollar' },
{ code: 'EUR', name: 'Euro' },
{ code: 'GBP', name: 'British Pound' },
{ code: 'JPY', name: 'Japanese Yen' },
{ code: 'CHF', name: 'Swiss Franc' },
{ code: 'CAD', name: 'Canadian Dollar' },
{ code: 'AUD', name: 'Australian Dollar' },
{ code: 'CNY', name: 'Chinese Yuan' },
]
export default function CurrencyConverter() {
const [amount, setAmount] = useState('1')
const [fromCurrency, setFromCurrency] = useState('EUR')
const [toCurrency, setToCurrency] = useState('USD')
const [rates, setRates] = useState<ExchangeRates>({})
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
const [lastUpdated, setLastUpdated] = useState<Date | null>(null)
useEffect(() => {
const fetchRates = async () => {
try {
setLoading(true)
setError('')
// Using ExchangeRate-API (free tier)
const response = await fetch('https://open.er-api.com/v6/latest/EUR')
const data = await response.json()
if (data.rates) {
setRates(data.rates)
setLastUpdated(new Date())
} else {
throw new Error('Failed to fetch exchange rates')
}
} catch (err) {
setError('Failed to load exchange rates. Please try again later.')
} finally {
setLoading(false)
}
}
fetchRates()
}, [])
const convert = () => {
if (!rates[fromCurrency] || !rates[toCurrency]) return '0.00'
const amountNum = parseFloat(amount)
if (isNaN(amountNum)) return '0.00'
// Convert through EUR (base currency)
const inEUR = amountNum / rates[fromCurrency]
const result = inEUR * rates[toCurrency]
return result.toFixed(2)
}
const handleSwap = () => {
setFromCurrency(toCurrency)
setToCurrency(fromCurrency)
}
return (
<main className="min-h-screen p-8 pt-24">
<div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">Currency Converter</h1>
<div className="bg-zinc-800 rounded-lg shadow-md p-6">
<div className="prose prose-invert max-w-none mb-6">
<p className="text-gray-300">
Convert between different currencies using real-time exchange rates.
{lastUpdated && (
<span className="block text-sm text-gray-400 mt-1">
Last updated: {lastUpdated.toLocaleString()}
</span>
)}
</p>
</div>
{loading ? (
<div className="text-center py-8">
<div className="text-blue-400">Loading exchange rates...</div>
</div>
) : error ? (
<div className="text-red-500 text-sm p-3 bg-red-500/10 rounded-md border border-red-500/20">
{error}
</div>
) : (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Amount
</label>
<input
type="number"
min="0"
step="0.01"
value={amount}
onChange={(e) => setAmount(e.target.value)}
className="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2"
/>
</div>
<div className="flex items-end gap-2">
<div className="flex-1">
<label className="block text-sm font-medium text-gray-300 mb-2">
From
</label>
<select
value={fromCurrency}
onChange={(e) => setFromCurrency(e.target.value)}
className="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2 text-sm"
>
{POPULAR_CURRENCIES.map(currency => (
<option key={currency.code} value={currency.code} className="text-sm">
{currency.code} - {currency.name}
</option>
))}
</select>
</div>
<button
onClick={handleSwap}
className="bg-zinc-700 text-white p-2 rounded-md hover:bg-zinc-600 transition-colors mb-0.5"
>
</button>
<div className="flex-1">
<label className="block text-sm font-medium text-gray-300 mb-2">
To
</label>
<select
value={toCurrency}
onChange={(e) => setToCurrency(e.target.value)}
className="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2 text-sm"
>
{POPULAR_CURRENCIES.map(currency => (
<option key={currency.code} value={currency.code} className="text-sm">
{currency.code} - {currency.name}
</option>
))}
</select>
</div>
</div>
</div>
<div className="bg-zinc-900 p-6 rounded-lg text-center">
<div className="text-sm text-gray-400 mb-2">Result</div>
<div className="text-2xl font-bold text-white">
{amount} {fromCurrency} = {convert()} {toCurrency}
</div>
<div className="text-sm text-gray-400 mt-2">
1 {fromCurrency} = {(rates[toCurrency] / rates[fromCurrency]).toFixed(4)} {toCurrency}
</div>
</div>
</div>
)}
</div>
</div>
</main>
)
}

View File

@@ -60,7 +60,7 @@ export default function ImageConverter() {
}
return (
<main className="min-h-screen p-8">
<main className="min-h-screen p-8 pt-24">
<div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">Image Converter</h1>
@@ -100,6 +100,7 @@ export default function ImageConverter() {
<option value="jpg">JPG</option>
<option value="webp">WebP</option>
<option value="gif">GIF</option>
<option value="ico">ICO</option>
</select>
</div>

View File

@@ -0,0 +1,143 @@
'use client'
import { useState } from 'react'
export default function NumberConverter() {
const [value, setValue] = useState('')
const [fromBase, setFromBase] = useState('10')
const [error, setError] = useState('')
const isValidNumber = (num: string, base: string) => {
const validChars = {
'2': /^[01]+$/,
'10': /^[0-9]+$/,
'16': /^[0-9A-Fa-f]+$/
}
return validChars[base as keyof typeof validChars].test(num)
}
const convert = (input: string, from: string) => {
if (!input) return { binary: '', decimal: '', hex: '' }
try {
if (!isValidNumber(input, from)) {
throw new Error('Invalid number for selected base')
}
const decimal = parseInt(input, parseInt(from))
if (isNaN(decimal)) throw new Error('Invalid number')
return {
binary: decimal.toString(2),
decimal: decimal.toString(10),
hex: decimal.toString(16).toUpperCase()
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Invalid input')
return { binary: '', decimal: '', hex: '' }
}
}
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value)
setError('')
}
const results = convert(value, fromBase)
return (
<main className="min-h-screen p-8 pt-24">
<div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">Number Converter</h1>
<div className="bg-zinc-800 rounded-lg shadow-md p-6">
<div className="prose prose-invert max-w-none mb-6">
<p className="text-gray-300">
Convert numbers between binary (base 2), decimal (base 10), and hexadecimal (base 16) formats.
Enter a number in any base and see its equivalent representations.
</p>
</div>
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Input Number
</label>
<input
type="text"
value={value}
onChange={handleInputChange}
placeholder="Enter a number"
className="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Input Base
</label>
<select
value={fromBase}
onChange={(e) => setFromBase(e.target.value)}
className="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2"
>
<option value="2">Binary (Base 2)</option>
<option value="10">Decimal (Base 10)</option>
<option value="16">Hexadecimal (Base 16)</option>
</select>
</div>
</div>
{error && (
<div className="text-red-500 text-sm p-3 bg-red-500/10 rounded-md border border-red-500/20">
{error}
</div>
)}
<div className="space-y-4">
<h2 className="text-xl font-semibold text-white">Results</h2>
<div className="grid gap-4">
<div className="bg-zinc-900 p-4 rounded-lg">
<label className="block text-sm font-medium text-gray-400 mb-1">
Binary (Base 2)
</label>
<input
type="text"
readOnly
value={results.binary}
className="w-full bg-zinc-800 text-white rounded-md border border-gray-700 p-2"
/>
</div>
<div className="bg-zinc-900 p-4 rounded-lg">
<label className="block text-sm font-medium text-gray-400 mb-1">
Decimal (Base 10)
</label>
<input
type="text"
readOnly
value={results.decimal}
className="w-full bg-zinc-800 text-white rounded-md border border-gray-700 p-2"
/>
</div>
<div className="bg-zinc-900 p-4 rounded-lg">
<label className="block text-sm font-medium text-gray-400 mb-1">
Hexadecimal (Base 16)
</label>
<input
type="text"
readOnly
value={results.hex}
className="w-full bg-zinc-800 text-white rounded-md border border-gray-700 p-2"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
)
}

View File

@@ -3,16 +3,32 @@ import { useState } from 'react'
export default function PasswordGenerator() {
const [password, setPassword] = useState('')
const [length, setLength] = useState(12)
const [length, setLength] = useState(16)
const [includeNumbers, setIncludeNumbers] = useState(true)
const [includeSymbols, setIncludeSymbols] = useState(true)
const [includeLetters, setIncludeLetters] = useState(true)
const getStrengthDescription = (len: number) => {
if (len < 8) return { text: 'Very Weak - Not recommended for security', color: 'text-red-500' }
if (len < 12) return { text: 'Weak - Only for low-security needs', color: 'text-orange-500' }
if (len < 16) return { text: 'Moderate - Good for most purposes', color: 'text-yellow-500' }
if (len < 20) return { text: 'Strong - Recommended for sensitive data', color: 'text-green-500' }
return { text: 'Very Strong - Excellent for critical security', color: 'text-emerald-500' }
}
const generatePassword = () => {
const numbers = '0123456789'
const symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?'
const letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
let chars = letters
// Ensure at least one type is selected
if (!includeLetters && !includeNumbers && !includeSymbols) {
setIncludeLetters(true)
return
}
let chars = ''
if (includeLetters) chars += letters
if (includeNumbers) chars += numbers
if (includeSymbols) chars += symbols
@@ -24,12 +40,25 @@ export default function PasswordGenerator() {
setPassword(newPassword)
}
const strength = getStrengthDescription(length)
return (
<main className="min-h-screen p-8">
<main className="min-h-screen p-8 pt-24">
<div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">Password Generator</h1>
<div className="bg-zinc-800 rounded-lg shadow-md p-6">
<div className="prose prose-invert max-w-none mb-6">
<p className="text-gray-300">
Generate secure passwords with customizable options. For maximum security, we recommend:
</p>
<ul className="list-disc pl-6 text-gray-300 space-y-1">
<li>Use at least 16 characters for sensitive accounts</li>
<li>Include a mix of letters, numbers, and special characters</li>
<li>Use different passwords for each account</li>
</ul>
</div>
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
@@ -37,15 +66,28 @@ export default function PasswordGenerator() {
</label>
<input
type="range"
min="8"
max="32"
min="4"
max="64"
value={length}
onChange={(e) => setLength(Number(e.target.value))}
className="w-full mt-2"
/>
<p className={`text-sm mt-2 ${strength.color}`}>
{strength.text}
</p>
</div>
<div className="flex space-x-4">
<div className="flex flex-wrap gap-4">
<label className="flex items-center">
<input
type="checkbox"
checked={includeLetters}
onChange={(e) => setIncludeLetters(e.target.checked)}
className="rounded border-gray-600 bg-zinc-700 text-blue-500 mr-2 focus:ring-blue-500 focus:ring-offset-zinc-800"
/>
<span className="text-gray-300">Letters</span>
</label>
<label className="flex items-center">
<input
type="checkbox"

143
app/generator/qr/page.tsx Normal file
View File

@@ -0,0 +1,143 @@
'use client'
import { useState, useRef } from 'react'
import QRCode from 'qrcode'
export default function QRGenerator() {
const [text, setText] = useState('')
const [qrDataUrl, setQrDataUrl] = useState('')
const [size, setSize] = useState(300)
const [darkColor, setDarkColor] = useState('#000000')
const [lightColor, setLightColor] = useState('#FFFFFF')
const canvasRef = useRef<HTMLCanvasElement>(null)
const generateQR = async () => {
if (!text) return
try {
const canvas = canvasRef.current
if (!canvas) return
await QRCode.toCanvas(canvas, text, {
width: size,
margin: 2,
color: {
dark: darkColor,
light: lightColor
}
})
setQrDataUrl(canvas.toDataURL('image/png'))
} catch (err) {
console.error('Error generating QR code:', err)
}
}
const downloadQR = () => {
if (!qrDataUrl) return
const link = document.createElement('a')
link.download = 'qrcode.png'
link.href = qrDataUrl
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
return (
<main className="min-h-screen p-8 pt-24">
<div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">QR Code Generator</h1>
<div className="bg-zinc-800 rounded-lg shadow-md p-6">
<div className="prose prose-invert max-w-none mb-6">
<p className="text-gray-300">
Generate QR codes for text, URLs, or any other content. Customize the size and colors,
then download the QR code as a PNG image.
</p>
</div>
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Text or URL
</label>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Enter text or URL"
className="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Size: {size}x{size} pixels
</label>
<input
type="range"
min="100"
max="1000"
step="50"
value={size}
onChange={(e) => setSize(Number(e.target.value))}
className="w-full"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
QR Code Color
</label>
<input
type="color"
value={darkColor}
onChange={(e) => setDarkColor(e.target.value)}
className="w-full h-10 rounded-md cursor-pointer bg-zinc-700 p-1"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Background Color
</label>
<input
type="color"
value={lightColor}
onChange={(e) => setLightColor(e.target.value)}
className="w-full h-10 rounded-md cursor-pointer bg-zinc-700 p-1"
/>
</div>
</div>
<button
onClick={generateQR}
className="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 transition-colors"
>
Generate QR Code
</button>
<div className="flex justify-center">
<canvas ref={canvasRef} className="hidden" />
{qrDataUrl && (
<div className="space-y-4">
<img
src={qrDataUrl}
alt="Generated QR Code"
className="mx-auto border-4 border-white rounded-lg"
/>
<button
onClick={downloadQR}
className="w-full bg-green-500 text-white py-2 px-4 rounded-md hover:bg-green-600 transition-colors"
>
Download QR Code
</button>
</div>
)}
</div>
</div>
</div>
</div>
</main>
)
}

View File

@@ -6,7 +6,7 @@ const inter = Inter({ subsets: ['latin'] })
export const metadata = {
title: 'Tool Website',
description: 'Eine Sammlung nützlicher Tools',
description: 'A collection of useful web-based tools. Secure and Open Source.',
}
export default function RootLayout({
@@ -15,8 +15,11 @@ export default function RootLayout({
children: React.ReactNode
}) {
return (
<html lang="de">
<body className={inter.className}>
<html lang="en">
<head>
<link rel="icon" type="image/vnd.microsoft.icon" href="/logo.ico" />
</head>
<body className="bg-zinc-900">
<Navbar />
{children}
</body>

View File

@@ -1,95 +1,81 @@
'use client'
import { useEffect, useState } from 'react'
import Link from 'next/link'
import { PinnedWebsite } from './types/types'
import AddWebsite from './components/AddWebsite'
export default function Home() {
const [pinnedWebsites, setPinnedWebsites] = useState<PinnedWebsite[]>([])
useEffect(() => {
const saved = localStorage.getItem('pinnedWebsites')
if (saved) {
setPinnedWebsites(JSON.parse(saved))
}
}, [])
const handleDelete = (id: string) => {
const newWebsites = pinnedWebsites.filter(site => site.id !== id)
localStorage.setItem('pinnedWebsites', JSON.stringify(newWebsites))
setPinnedWebsites(newWebsites)
}
const tools = [
{
name: 'Password Generator',
file: '/password',
description: 'Create secure passwords with custom options',
category: 'Generators',
items: [
{
name: 'Password Generator',
file: '/generator/password',
description: 'Create secure passwords with custom options',
},
{
name: 'QR Code Generator',
file: '/generator/qr',
description: 'Generate and download QR codes for any text or URL',
},
]
},
{
name: 'Image Converter',
file: '/img_converter',
description: 'Convert images between different formats',
category: 'Converters',
items: [
{
name: 'Image Converter',
file: '/converter/image',
description: 'Convert images between different formats',
},
{
name: 'Number Converter',
file: '/converter/number',
description: 'Convert numbers between binary, decimal, and hexadecimal',
},
{
name: 'Color Converter',
file: '/converter/color',
description: 'Convert colors between RGB, HEX, HSL',
},
{
name: 'Currency Converter',
file: '/converter/currency',
description: 'Convert between different currencies with live rates',
},
]
},
{
name: 'More Coming Soon',
file: '/coming_soon',
description: 'Learn more about future features',
isComingSoon: true
}
category: 'Calculators',
items: [
{
name: 'Scientific Calculator',
file: '/calculator/scientific',
description: 'Advanced calculator with scientific functions',
},
]
},
]
return (
<main className="min-h-screen p-8 text-white">
<main className="min-h-screen p-8 pt-24 text-white">
<div className="max-w-7xl mx-auto space-y-12">
{/* Tools Section */}
<section>
<h2 className="text-2xl font-bold mb-6">Tools</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{tools.map((tool, index) => (
<Link
key={index}
href={tool.file}
className={`p-6 rounded-lg border border-gray-700 bg-zinc-800 transition-colors duration-200 cursor-pointer
${tool.isComingSoon
? 'opacity-50 hover:opacity-75'
: 'hover:border-blue-500'}`}
>
<h2 className="text-xl font-semibold mb-2">{tool.name}</h2>
<p className="text-gray-400">{tool.description}</p>
</Link>
))}
</div>
</section>
{/* Pinned Websites Section */}
<section>
<h2 className="text-2xl font-bold mb-6">Bookmarked Websites</h2>
<AddWebsite />
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{pinnedWebsites.map((website) => (
<div
key={website.id}
className="group relative p-6 rounded-lg border border-gray-700 bg-zinc-800 hover:border-blue-500 transition-colors"
>
<a
href={website.url}
target="_blank"
rel="noopener noreferrer"
className="block"
>
<h3 className="text-xl font-semibold mb-2">{website.title}</h3>
<p className="text-gray-400 truncate">{website.url}</p>
</a>
<button
onClick={(e) => {
e.preventDefault()
handleDelete(website.id)
}}
className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 text-gray-400 hover:text-red-500 transition-opacity"
>
</button>
<h2 className="text-2xl font-bold mb-6 text-white">Tools</h2>
<div className="space-y-8">
{tools.map((category, categoryIndex) => (
<div key={categoryIndex}>
<h3 className="text-xl font-semibold mb-4 text-blue-400">{category.category}</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{category.items.map((tool, toolIndex) => (
<Link
key={toolIndex}
href={tool.file}
className="p-6 rounded-lg border border-gray-700 bg-zinc-800 transition-colors duration-200 hover:border-blue-500"
>
<h2 className="text-xl font-semibold mb-2 text-white">{tool.name}</h2>
<p className="text-gray-400">{tool.description}</p>
</Link>
))}
</div>
</div>
))}
</div>

41
app/settings/page.tsx Normal file
View File

@@ -0,0 +1,41 @@
'use client'
import { useState, useEffect } from 'react'
export default function Settings() {
const [darkMode, setDarkMode] = useState(true)
useEffect(() => {
const savedMode = localStorage.getItem('darkMode')
if (savedMode !== null) {
setDarkMode(JSON.parse(savedMode))
}
}, [])
const toggleDarkMode = () => {
const newMode = !darkMode
setDarkMode(newMode)
localStorage.setItem('darkMode', JSON.stringify(newMode))
// Apply dark mode to document
if (newMode) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
}
return (
<main className="min-h-screen p-8 pt-24">
<div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">Settings</h1>
<div className="bg-zinc-800 rounded-lg shadow-md p-6 space-y-8">
<div className="space-y-6">
<p className="text-gray-300">
Currently, there are no settings available.
</p>
</div>
</div>
</div>
</main>
)
}

View File

@@ -1,7 +0,0 @@
export type PinnedWebsite = {
id: string;
title: string;
url: string;
icon?: string;
addedAt: number;
}