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() { export default function About() {
return ( return (
<main className="min-h-screen p-8"> <main className="min-h-screen p-8 pt-24">
<div className="max-w-2xl mx-auto"> <div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">About</h1> <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). The Source Code is available on GitHub (link below).
</p> </p>
<h2 className="text-2xl font-bold text-white mt-6 mb-4">Features</h2> <div className="bg-zinc-900 rounded-lg p-4 mb-6">
<ul className="list-disc pl-6 text-gray-300 space-y-2"> <p className="text-sm text-blue-400 font-mono">Current Version: 0.2.0</p>
<li>Password Generator - Create secure passwords with customizable options</li> </div>
<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>
<h2 className="text-2xl font-bold text-white mt-6 mb-4">Privacy</h2> <h2 className="text-2xl font-bold text-white mt-6 mb-4">Privacy</h2>
<p className="text-gray-300 mb-6"> <p className="text-gray-300 mb-6">

View File

@@ -34,6 +34,15 @@ export async function POST(req: NextRequest) {
case 'gif': case 'gif':
convertedBuffer = await sharpInstance.gif().toBuffer() convertedBuffer = await sharpInstance.gif().toBuffer()
break break
case 'ico':
convertedBuffer = await sharpInstance
.resize(256, 256, {
fit: 'contain',
background: { r: 0, g: 0, b: 0, alpha: 0 }
})
.png()
.toBuffer()
break
default: default:
throw new Error('Unsupported format') 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,6 +1,6 @@
export default function ComingSoon() { export default function ComingSoon() {
return ( return (
<main className="min-h-screen p-8"> <main className="min-h-screen p-8 pt-24">
<div className="max-w-2xl mx-auto"> <div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">Coming Soon</h1> <h1 className="text-4xl font-bold mb-8 text-white">Coming Soon</h1>
@@ -16,13 +16,45 @@ export default function ComingSoon() {
Instead, I add new tools and functions when I have a good idea and the time allows. Instead, I add new tools and functions when I have a good idea and the time allows.
</p> </p>
<p className="text-gray-300 mt-8"> <div className="space-y-6">
If you have suggestions for new tools or features, you can submit them via GitHub <div>
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! <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> </p>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
</main> </main>
) )
} }

View File

@@ -7,12 +7,26 @@ export default function AddWebsite() {
const [title, setTitle] = useState('') const [title, setTitle] = useState('')
const [isAdding, setIsAdding] = useState(false) 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) => { const handleSubmit = (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
// Validiere URL const formattedUrl = formatUrl(url)
// Validate URL
try { try {
new URL(url) new URL(formattedUrl)
} catch { } catch {
alert('Please enter a valid URL') alert('Please enter a valid URL')
return return
@@ -20,23 +34,23 @@ export default function AddWebsite() {
const newWebsite: PinnedWebsite = { const newWebsite: PinnedWebsite = {
id: Date.now().toString(), id: Date.now().toString(),
title: title || url, title: title || formattedUrl,
url: url.startsWith('http') ? url : `https://${url}`, url: formattedUrl,
addedAt: Date.now() addedAt: Date.now()
} }
// Lade bestehende Websites // Load existing websites
const existingWebsites = JSON.parse(localStorage.getItem('pinnedWebsites') || '[]') const existingWebsites = JSON.parse(localStorage.getItem('pinnedWebsites') || '[]')
// Füge neue Website hinzu // Add new website
localStorage.setItem('pinnedWebsites', JSON.stringify([...existingWebsites, newWebsite])) localStorage.setItem('pinnedWebsites', JSON.stringify([...existingWebsites, newWebsite]))
// Setze Formular zurück // Reset form
setUrl('') setUrl('')
setTitle('') setTitle('')
setIsAdding(false) setIsAdding(false)
// Lade die Seite neu um die neue Website anzuzeigen // Reload page to show new website
window.location.reload() window.location.reload()
} }
@@ -59,10 +73,13 @@ export default function AddWebsite() {
type="text" type="text"
value={url} value={url}
onChange={(e) => setUrl(e.target.value)} 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" className="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2"
required required
/> />
<p className="text-sm text-gray-400 mt-1">
HTTPS will be added automatically if not provided
</p>
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-300 mb-1"> <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() { export default function Navbar() {
return ( 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="container mx-auto">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="flex-1" /> {/* Spacer */} <Link href="/" className="text-white font-bold text-xl hover:text-blue-200">
<Link href="/" className="text-white font-bold text-xl hover:text-blue-100">
HOME HOME
</Link> </Link>
<div className="flex-1 flex justify-end"> <div className="flex gap-6">
<Link href="/about" className="text-white hover:text-blue-100"> <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 About
</Link> </Link>
</div> </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 ( return (
<main className="min-h-screen p-8"> <main className="min-h-screen p-8 pt-24">
<div className="max-w-2xl mx-auto"> <div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">Image Converter</h1> <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="jpg">JPG</option>
<option value="webp">WebP</option> <option value="webp">WebP</option>
<option value="gif">GIF</option> <option value="gif">GIF</option>
<option value="ico">ICO</option>
</select> </select>
</div> </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() { export default function PasswordGenerator() {
const [password, setPassword] = useState('') const [password, setPassword] = useState('')
const [length, setLength] = useState(12) const [length, setLength] = useState(16)
const [includeNumbers, setIncludeNumbers] = useState(true) const [includeNumbers, setIncludeNumbers] = useState(true)
const [includeSymbols, setIncludeSymbols] = 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 generatePassword = () => {
const numbers = '0123456789' const numbers = '0123456789'
const symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?' const symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?'
const letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' 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 (includeNumbers) chars += numbers
if (includeSymbols) chars += symbols if (includeSymbols) chars += symbols
@@ -24,12 +40,25 @@ export default function PasswordGenerator() {
setPassword(newPassword) setPassword(newPassword)
} }
const strength = getStrengthDescription(length)
return ( return (
<main className="min-h-screen p-8"> <main className="min-h-screen p-8 pt-24">
<div className="max-w-2xl mx-auto"> <div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8 text-white">Password Generator</h1> <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="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 className="space-y-6">
<div> <div>
<label className="block text-sm font-medium text-gray-300 mb-2"> <label className="block text-sm font-medium text-gray-300 mb-2">
@@ -37,15 +66,28 @@ export default function PasswordGenerator() {
</label> </label>
<input <input
type="range" type="range"
min="8" min="4"
max="32" max="64"
value={length} value={length}
onChange={(e) => setLength(Number(e.target.value))} onChange={(e) => setLength(Number(e.target.value))}
className="w-full mt-2" className="w-full mt-2"
/> />
<p className={`text-sm mt-2 ${strength.color}`}>
{strength.text}
</p>
</div> </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"> <label className="flex items-center">
<input <input
type="checkbox" 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 = { export const metadata = {
title: 'Tool Website', 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({ export default function RootLayout({
@@ -15,8 +15,11 @@ export default function RootLayout({
children: React.ReactNode children: React.ReactNode
}) { }) {
return ( return (
<html lang="de"> <html lang="en">
<body className={inter.className}> <head>
<link rel="icon" type="image/vnd.microsoft.icon" href="/logo.ico" />
</head>
<body className="bg-zinc-900">
<Navbar /> <Navbar />
{children} {children}
</body> </body>

View File

@@ -1,95 +1,81 @@
'use client' 'use client'
import { useEffect, useState } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { PinnedWebsite } from './types/types'
import AddWebsite from './components/AddWebsite'
export default function Home() { 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 = [ const tools = [
{
category: 'Generators',
items: [
{ {
name: 'Password Generator', name: 'Password Generator',
file: '/password', file: '/generator/password',
description: 'Create secure passwords with custom options', 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',
},
]
},
{
category: 'Converters',
items: [
{ {
name: 'Image Converter', name: 'Image Converter',
file: '/img_converter', file: '/converter/image',
description: 'Convert images between different formats', description: 'Convert images between different formats',
}, },
{ {
name: 'More Coming Soon', name: 'Number Converter',
file: '/coming_soon', file: '/converter/number',
description: 'Learn more about future features', description: 'Convert numbers between binary, decimal, and hexadecimal',
isComingSoon: true },
} {
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',
},
]
},
{
category: 'Calculators',
items: [
{
name: 'Scientific Calculator',
file: '/calculator/scientific',
description: 'Advanced calculator with scientific functions',
},
]
},
] ]
return ( 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"> <div className="max-w-7xl mx-auto space-y-12">
{/* Tools Section */}
<section> <section>
<h2 className="text-2xl font-bold mb-6">Tools</h2> <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"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{tools.map((tool, index) => ( {category.items.map((tool, toolIndex) => (
<Link <Link
key={index} key={toolIndex}
href={tool.file} href={tool.file}
className={`p-6 rounded-lg border border-gray-700 bg-zinc-800 transition-colors duration-200 cursor-pointer className="p-6 rounded-lg border border-gray-700 bg-zinc-800 transition-colors duration-200 hover:border-blue-500"
${tool.isComingSoon
? 'opacity-50 hover:opacity-75'
: 'hover:border-blue-500'}`}
> >
<h2 className="text-xl font-semibold mb-2">{tool.name}</h2> <h2 className="text-xl font-semibold mb-2 text-white">{tool.name}</h2>
<p className="text-gray-400">{tool.description}</p> <p className="text-gray-400">{tool.description}</p>
</Link> </Link>
))} ))}
</div> </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>
</div> </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;
}

BIN
logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

333
package-lock.json generated
View File

@@ -9,7 +9,9 @@
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@tailwindcss/postcss": "^4.1.7", "@tailwindcss/postcss": "^4.1.7",
"@types/qrcode": "^1.5.5",
"next": "^14.0.0", "next": "^14.0.0",
"qrcode": "^1.5.4",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.3.8", "react-dropzone": "^14.3.8",
@@ -1016,7 +1018,6 @@
"version": "20.17.47", "version": "20.17.47",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.47.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.47.tgz",
"integrity": "sha512-3dLX0Upo1v7RvUimvxLeXqwrfyKxUINk0EAM83swP2mlSUcwV73sZy8XhNz8bcZ3VbsfQyC/y6jRdL5tgCNpDQ==", "integrity": "sha512-3dLX0Upo1v7RvUimvxLeXqwrfyKxUINk0EAM83swP2mlSUcwV73sZy8XhNz8bcZ3VbsfQyC/y6jRdL5tgCNpDQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~6.19.2" "undici-types": "~6.19.2"
@@ -1029,6 +1030,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/qrcode": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.5.tgz",
"integrity": "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.3.21", "version": "18.3.21",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.21.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.21.tgz",
@@ -1238,6 +1248,15 @@
"node": ">=10.16.0" "node": ">=10.16.0"
} }
}, },
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/camelcase-css": { "node_modules/camelcase-css": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
@@ -1321,6 +1340,87 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"node_modules/cliui/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/cliui/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/cliui/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/cliui/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/cliui/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/cliui/node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/color": { "node_modules/color": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@@ -1407,6 +1507,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/detect-libc": { "node_modules/detect-libc": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
@@ -1423,6 +1532,12 @@
"dev": true, "dev": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/dijkstrajs": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
"license": "MIT"
},
"node_modules/dlv": { "node_modules/dlv": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@@ -1539,6 +1654,19 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"license": "MIT",
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/foreground-child": { "node_modules/foreground-child": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
@@ -1595,6 +1723,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/glob": { "node_modules/glob": {
"version": "10.4.5", "version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
@@ -1697,7 +1834,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -2013,6 +2149,18 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"license": "MIT",
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/loose-envify": { "node_modules/loose-envify": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -2271,6 +2419,42 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"license": "MIT",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"license": "MIT",
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/package-json-from-dist": { "node_modules/package-json-from-dist": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@@ -2278,6 +2462,15 @@
"dev": true, "dev": true,
"license": "BlueOak-1.0.0" "license": "BlueOak-1.0.0"
}, },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-key": { "node_modules/path-key": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -2351,6 +2544,15 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
"license": "MIT",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.3", "version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
@@ -2475,6 +2677,23 @@
"react-is": "^16.13.1" "react-is": "^16.13.1"
} }
}, },
"node_modules/qrcode": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
"license": "MIT",
"dependencies": {
"dijkstrajs": "^1.0.1",
"pngjs": "^5.0.0",
"yargs": "^15.3.1"
},
"bin": {
"qrcode": "bin/qrcode"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -2567,6 +2786,21 @@
"node": ">=8.10.0" "node": ">=8.10.0"
} }
}, },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"license": "ISC"
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.10", "version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@@ -2644,6 +2878,12 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC"
},
"node_modules/sharp": { "node_modules/sharp": {
"version": "0.34.1", "version": "0.34.1",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz",
@@ -3076,7 +3316,6 @@
"version": "6.19.8", "version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
@@ -3133,6 +3372,12 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
"license": "ISC"
},
"node_modules/wrap-ansi": { "node_modules/wrap-ansi": {
"version": "8.1.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
@@ -3231,6 +3476,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"license": "ISC"
},
"node_modules/yallist": { "node_modules/yallist": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
@@ -3252,6 +3503,82 @@
"engines": { "engines": {
"node": ">= 14.6" "node": ">= 14.6"
} }
},
"node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"license": "MIT",
"dependencies": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"license": "ISC",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/yargs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/yargs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
} }
} }
} }

View File

@@ -10,7 +10,9 @@
}, },
"dependencies": { "dependencies": {
"@tailwindcss/postcss": "^4.1.7", "@tailwindcss/postcss": "^4.1.7",
"@types/qrcode": "^1.5.5",
"next": "^14.0.0", "next": "^14.0.0",
"qrcode": "^1.5.4",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.3.8", "react-dropzone": "^14.3.8",

BIN
public/logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB