mirror of
https://github.com/danielvici/tool-website.git
synced 2026-01-16 19:41:26 +00:00
first code for switch to angular
This commit is contained in:
17
.editorconfig
Normal file
17
.editorconfig
Normal file
@@ -0,0 +1,17 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
ij_typescript_use_double_quotes = false
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
47
.github/copilot-instructions.md
vendored
Normal file
47
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
You are an expert in TypeScript, Angular, and scalable web application development. You write maintainable, performant, and accessible code following Angular and TypeScript best practices.
|
||||
|
||||
## TypeScript Best Practices
|
||||
|
||||
- Use strict type checking
|
||||
- Prefer type inference when the type is obvious
|
||||
- Avoid the `any` type; use `unknown` when type is uncertain
|
||||
|
||||
## Angular Best Practices
|
||||
|
||||
- Always use standalone components over NgModules
|
||||
- Must NOT set `standalone: true` inside Angular decorators. It's the default.
|
||||
- Use signals for state management
|
||||
- Implement lazy loading for feature routes
|
||||
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
|
||||
- Use `NgOptimizedImage` for all static images.
|
||||
- `NgOptimizedImage` does not work for inline base64 images.
|
||||
|
||||
## Components
|
||||
|
||||
- Keep components small and focused on a single responsibility
|
||||
- Use `input()` and `output()` functions instead of decorators
|
||||
- Use `computed()` for derived state
|
||||
- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
|
||||
- Prefer inline templates for small components
|
||||
- Prefer Reactive forms instead of Template-driven ones
|
||||
- Do NOT use `ngClass`, use `class` bindings instead
|
||||
- Do NOT use `ngStyle`, use `style` bindings instead
|
||||
|
||||
## State Management
|
||||
|
||||
- Use signals for local component state
|
||||
- Use `computed()` for derived state
|
||||
- Keep state transformations pure and predictable
|
||||
- Do NOT use `mutate` on signals, use `update` or `set` instead
|
||||
|
||||
## Templates
|
||||
|
||||
- Keep templates simple and avoid complex logic
|
||||
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
|
||||
- Use the async pipe to handle observables
|
||||
|
||||
## Services
|
||||
|
||||
- Design services around a single responsibility
|
||||
- Use the `providedIn: 'root'` option for singleton services
|
||||
- Use the `inject()` function instead of constructor injection
|
||||
65
.gitignore
vendored
65
.gitignore
vendored
@@ -1,33 +1,42 @@
|
||||
# dependencies
|
||||
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
||||
|
||||
# Compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
/bazel-out
|
||||
|
||||
# Node
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# testing
|
||||
# IDEs and editors
|
||||
.idea/
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# Miscellaneous
|
||||
/.angular/cache
|
||||
.sass-cache/
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
# System files
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
Thumbs.db
|
||||
|
||||
4
.vscode/extensions.json
vendored
Normal file
4
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
|
||||
"recommendations": ["angular.ng-template"]
|
||||
}
|
||||
20
.vscode/launch.json
vendored
Normal file
20
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "ng serve",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "npm: start",
|
||||
"url": "http://localhost:4200/"
|
||||
},
|
||||
{
|
||||
"name": "ng test",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "npm: test",
|
||||
"url": "http://localhost:9876/debug.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
42
.vscode/tasks.json
vendored
Normal file
42
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "start",
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"pattern": "$tsc",
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": {
|
||||
"regexp": "(.*?)"
|
||||
},
|
||||
"endsPattern": {
|
||||
"regexp": "bundle generation complete"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "test",
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"pattern": "$tsc",
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": {
|
||||
"regexp": "(.*?)"
|
||||
},
|
||||
"endsPattern": {
|
||||
"regexp": "bundle generation complete"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
74
README.md
74
README.md
@@ -1,49 +1,59 @@
|
||||
# Tool Website
|
||||
# ToolsWebsite
|
||||
|
||||
A collection of useful web-based tools built with Next.js, TypeScript, and Tailwind CSS.
|
||||
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.3.1.
|
||||
|
||||
## Features
|
||||
## Development server
|
||||
|
||||
- Password Generator
|
||||
- Image Format Converter
|
||||
- Website Bookmarks
|
||||
- More tools coming soon!
|
||||
To start a local development server, run:
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- Next.js 14
|
||||
- TypeScript
|
||||
- Tailwind CSS
|
||||
- React
|
||||
- Sharp (for image processing)
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/danielvici/tool-website.git
|
||||
ng serve
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
|
||||
|
||||
```bash
|
||||
cd tool-website
|
||||
npm install
|
||||
ng generate component component-name
|
||||
```
|
||||
|
||||
3. Run the development server:
|
||||
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
ng generate --help
|
||||
```
|
||||
|
||||
4. Open [http://localhost:3000](http://localhost:3000) in your browser.
|
||||
## Building
|
||||
|
||||
## Development
|
||||
To build the project run:
|
||||
|
||||
- `npm run dev` - Start development server
|
||||
- `npm run build` - Build for production
|
||||
- `npm start` - Start production server
|
||||
- `npm run lint` - Run ESLint
|
||||
```bash
|
||||
ng build
|
||||
```
|
||||
|
||||
## Privacy
|
||||
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
|
||||
|
||||
All tools operate entirely in the browser. No data is sent to external servers (except for explicitly shared URLs in bookmarks). Your data remains on your device and is stored only in your browser's local storage.
|
||||
## Running unit tests
|
||||
|
||||
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
|
||||
|
||||
```bash
|
||||
ng test
|
||||
```
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
For end-to-end (e2e) testing, run:
|
||||
|
||||
```bash
|
||||
ng e2e
|
||||
```
|
||||
|
||||
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|
||||
|
||||
92
angular.json
Normal file
92
angular.json
Normal file
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"tools-website": {
|
||||
"projectType": "application",
|
||||
"schematics": {},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular/build:application",
|
||||
"options": {
|
||||
"browser": "src/main.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kB",
|
||||
"maximumError": "1MB"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "4kB",
|
||||
"maximumError": "8kB"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular/build:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "tools-website:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "tools-website:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular/build:extract-i18n"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular/build:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
export default function About() {
|
||||
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">About</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-4">
|
||||
Welcome to my Tool Website! This Website provides a collection of useful web-based tools
|
||||
designed to help you with various tasks. All tools run directly in your browser, ensuring
|
||||
your data stays private and secure. This Website is hosted on Vercel and Open Source.
|
||||
The Source Code is available on GitHub (link below).
|
||||
</p>
|
||||
|
||||
<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">
|
||||
All tools operate entirely in your browser. No data is sent to external servers
|
||||
(except for explicitly shared URLs in bookmarks). Your data remains on your device
|
||||
and is stored only in your browser's local storage.
|
||||
</p>
|
||||
|
||||
<h2 className="text-2xl font-bold text-white mt-6 mb-4">Developer</h2>
|
||||
<div className="bg-zinc-900 rounded-lg p-6 border border-gray-700">
|
||||
<p className="text-gray-300 mb-4">
|
||||
This website is developed and maintained by <a href="https://github.com/danielvici" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:text-blue-600">danielvici123</a>. If you find these tools helpful,
|
||||
you can support the development or check out other projects through the links below:
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 mt-4">
|
||||
<a
|
||||
href="https://github.com/danielvici/tool-website"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center px-4 py-2 bg-gray-800 text-white rounded-md hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path fillRule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clipRule="evenodd" />
|
||||
</svg>
|
||||
GitHub
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://ko-fi.com/danielvici123"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center px-4 py-2 bg-[#13C3FF] text-white rounded-md hover:bg-[#00b8f5] transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M23.881 8.948c-.773-4.085-4.859-4.593-4.859-4.593H.723c-.604 0-.679.798-.679.798s-.082 7.324-.022 11.822c.164 2.424 2.586 2.672 2.586 2.672s8.267-.023 11.966-.049c2.438-.426 2.683-2.566 2.658-3.734 4.352.24 7.422-2.831 6.649-6.916zm-11.062 3.511c-1.246 1.453-4.011 3.976-4.011 3.976s-.121.119-.31.023c-.076-.057-.108-.09-.108-.09-.443-.441-3.368-3.049-4.034-3.954-.709-.965-1.041-2.7-.091-3.71.951-1.01 3.005-1.086 4.363.407 0 0 1.565-1.782 3.468-.963 1.904.82 1.832 3.011.723 4.311zm6.173.478c-.928.116-1.682.028-1.682.028V7.284h1.77s1.971.551 1.971 2.638c0 1.913-.985 2.667-2.059 3.015z"/>
|
||||
</svg>
|
||||
Buy me a coffee
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://x.com/danielvici123"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center px-4 py-2 bg-black text-white rounded-md hover:bg-gray-900 transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
|
||||
</svg>
|
||||
Contact
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import sharp from 'sharp'
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const formData = await req.formData()
|
||||
const file = formData.get('file') as File
|
||||
const format = formData.get('format') as string
|
||||
|
||||
if (!file || !format) {
|
||||
return NextResponse.json(
|
||||
{ error: 'File and format are required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(await file.arrayBuffer())
|
||||
let convertedBuffer: Buffer
|
||||
|
||||
try {
|
||||
const sharpInstance = sharp(buffer)
|
||||
|
||||
switch (format) {
|
||||
case 'png':
|
||||
convertedBuffer = await sharpInstance.png().toBuffer()
|
||||
break
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
convertedBuffer = await sharpInstance.jpeg().toBuffer()
|
||||
break
|
||||
case 'webp':
|
||||
convertedBuffer = await sharpInstance.webp().toBuffer()
|
||||
break
|
||||
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')
|
||||
}
|
||||
|
||||
return new NextResponse(convertedBuffer, {
|
||||
headers: {
|
||||
'Content-Type': `image/${format}`,
|
||||
'Content-Disposition': `attachment; filename="converted.${format}"`,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Conversion error:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Error converting image. Make sure the input format is supported.' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Server error:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Server error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
export default function ComingSoon() {
|
||||
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">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.
|
||||
New functions and tools will be added as time and inspiration allow.
|
||||
</p>
|
||||
|
||||
<p className="text-gray-300 mb-6">
|
||||
Since I develop this project in my free time, there is no fixed schedule for new features.
|
||||
Instead, I add new tools and functions when I have a good idea and the time allows.
|
||||
</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>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Navbar() {
|
||||
return (
|
||||
<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">
|
||||
<Link href="/" className="text-white font-bold text-xl hover:text-blue-200">
|
||||
HOME
|
||||
</Link>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
'use client'
|
||||
import { useState, useCallback } from 'react'
|
||||
import { useDropzone } from 'react-dropzone'
|
||||
|
||||
export default function ImageConverter() {
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
||||
const [convertTo, setConvertTo] = useState('png')
|
||||
const [isConverting, setIsConverting] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||
setError(null)
|
||||
if (acceptedFiles[0]) {
|
||||
setSelectedFile(acceptedFiles[0])
|
||||
}
|
||||
}, [])
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
onDrop,
|
||||
accept: {
|
||||
'image/*': ['.jpeg', '.jpg', '.png', '.gif', '.webp']
|
||||
},
|
||||
multiple: false
|
||||
})
|
||||
|
||||
const handleConvert = async () => {
|
||||
if (!selectedFile) return
|
||||
setError(null)
|
||||
setIsConverting(true)
|
||||
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('file', selectedFile)
|
||||
formData.append('format', convertTo)
|
||||
|
||||
const response = await fetch('/api/convert', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json()
|
||||
throw new Error(errorData.error || 'Conversion failed')
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `picture_converted_to.${convertTo}`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
document.body.removeChild(a)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'An error occurred')
|
||||
} finally {
|
||||
setIsConverting(false)
|
||||
}
|
||||
}
|
||||
|
||||
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">Image Converter</h1>
|
||||
|
||||
<div className="bg-zinc-800 rounded-lg shadow-md p-6">
|
||||
<div className="space-y-6">
|
||||
{/* Drag & Drop Zone */}
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={`border-2 border-dashed rounded-lg p-8 text-center cursor-pointer transition-colors
|
||||
${isDragActive ? 'border-blue-500 bg-blue-500/10' : 'border-gray-600 hover:border-blue-500'}
|
||||
`}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<p className="text-gray-300">
|
||||
{isDragActive
|
||||
? 'Drop the image here...'
|
||||
: 'Drag & drop an image here, or click to select'}
|
||||
</p>
|
||||
{selectedFile && (
|
||||
<p className="mt-2 text-blue-400">
|
||||
Selected: {selectedFile.name}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Format Selection */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Target Format
|
||||
</label>
|
||||
<select
|
||||
value={convertTo}
|
||||
onChange={(e) => setConvertTo(e.target.value)}
|
||||
className="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2"
|
||||
>
|
||||
<option value="png">PNG</option>
|
||||
<option value="jpg">JPG</option>
|
||||
<option value="webp">WebP</option>
|
||||
<option value="gif">GIF</option>
|
||||
<option value="ico">ICO</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Error Message */}
|
||||
{error && (
|
||||
<div className="text-red-500 text-sm p-3 bg-red-500/10 rounded-md border border-red-500/20">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Convert Button */}
|
||||
<button
|
||||
onClick={handleConvert}
|
||||
disabled={!selectedFile || isConverting}
|
||||
className={`w-full py-2 px-4 rounded-md text-white transition-colors
|
||||
${!selectedFile || isConverting
|
||||
? 'bg-blue-500/50 cursor-not-allowed'
|
||||
: 'bg-blue-500 hover:bg-blue-600'}
|
||||
`}
|
||||
>
|
||||
{isConverting ? 'Converting...' : 'Convert'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
'use client'
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function PasswordGenerator() {
|
||||
const [password, setPassword] = useState('')
|
||||
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'
|
||||
|
||||
// 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
|
||||
|
||||
let newPassword = ''
|
||||
for (let i = 0; i < length; i++) {
|
||||
newPassword += chars[Math.floor(Math.random() * chars.length)]
|
||||
}
|
||||
|
||||
setPassword(newPassword)
|
||||
}
|
||||
|
||||
const strength = getStrengthDescription(length)
|
||||
|
||||
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">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">
|
||||
Password Length: {length}
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
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 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"
|
||||
checked={includeNumbers}
|
||||
onChange={(e) => setIncludeNumbers(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">Numbers</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={includeSymbols}
|
||||
onChange={(e) => setIncludeSymbols(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">Special Characters</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={generatePassword}
|
||||
className="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 transition-colors"
|
||||
>
|
||||
Generate Password
|
||||
</button>
|
||||
|
||||
{password && (
|
||||
<div className="mt-4">
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Generated Password:
|
||||
</label>
|
||||
<div className="flex">
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
value={password}
|
||||
className="flex-1 bg-zinc-700 text-white rounded-l-md border border-gray-600 p-2"
|
||||
/>
|
||||
<button
|
||||
onClick={() => navigator.clipboard.writeText(password)}
|
||||
className="bg-zinc-700 px-4 rounded-r-md border-t border-r border-b border-gray-600 text-gray-300 hover:bg-zinc-600 transition-colors"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html, body {
|
||||
@apply bg-zinc-900;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
main {
|
||||
height: calc(100vh - 64px); /* 64px ist die Höhe der Navbar */
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import './globals.css'
|
||||
import { Inter } from 'next/font/google'
|
||||
import Navbar from './components/Navbar'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
export const metadata = {
|
||||
title: 'Tool Website',
|
||||
description: 'A collection of useful web-based tools. Secure and Open Source.',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="icon" type="image/vnd.microsoft.icon" href="/logo.ico" />
|
||||
</head>
|
||||
<body className="bg-zinc-900">
|
||||
<Navbar />
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
86
app/page.tsx
86
app/page.tsx
@@ -1,86 +0,0 @@
|
||||
'use client'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Home() {
|
||||
const tools = [
|
||||
{
|
||||
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',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
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',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'Calculators',
|
||||
items: [
|
||||
{
|
||||
name: 'Scientific Calculator',
|
||||
file: '/calculator/scientific',
|
||||
description: 'Advanced calculator with scientific functions',
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<main className="min-h-screen p-8 pt-24 text-white">
|
||||
<div className="max-w-7xl mx-auto space-y-12">
|
||||
<section>
|
||||
<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>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
9967
package-lock.json
generated
9967
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
64
package.json
64
package.json
@@ -1,30 +1,48 @@
|
||||
{
|
||||
"name": "tool-website",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"name": "tools-website",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test"
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 100,
|
||||
"singleQuote": true,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.html",
|
||||
"options": {
|
||||
"parser": "angular"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.7",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"next": "^14.0.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"sharp": "^0.34.1"
|
||||
"@angular/common": "^20.3.0",
|
||||
"@angular/compiler": "^20.3.0",
|
||||
"@angular/core": "^20.3.0",
|
||||
"@angular/forms": "^20.3.0",
|
||||
"@angular/platform-browser": "^20.3.0",
|
||||
"@angular/router": "^20.3.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"autoprefixer": "^10.4.0",
|
||||
"postcss": "^8.5.3",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.0.0"
|
||||
"@angular/build": "^20.3.1",
|
||||
"@angular/cli": "^20.3.1",
|
||||
"@angular/compiler-cli": "^20.3.0",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"jasmine-core": "~5.9.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"typescript": "~5.9.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
public/logo.ico
BIN
public/logo.ico
Binary file not shown.
|
Before Width: | Height: | Size: 68 KiB |
12
src/app/app.config.ts
Normal file
12
src/app/app.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(routes)
|
||||
]
|
||||
};
|
||||
0
src/app/app.css
Normal file
0
src/app/app.css
Normal file
342
src/app/app.html
Normal file
342
src/app/app.html
Normal file
@@ -0,0 +1,342 @@
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * The content below * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * Delete the template below * * * * * * * * * -->
|
||||
<!-- * * * * * * * to get started with your project! * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
|
||||
<style>
|
||||
:host {
|
||||
--bright-blue: oklch(51.01% 0.274 263.83);
|
||||
--electric-violet: oklch(53.18% 0.28 296.97);
|
||||
--french-violet: oklch(47.66% 0.246 305.88);
|
||||
--vivid-pink: oklch(69.02% 0.277 332.77);
|
||||
--hot-red: oklch(61.42% 0.238 15.34);
|
||||
--orange-red: oklch(63.32% 0.24 31.68);
|
||||
|
||||
--gray-900: oklch(19.37% 0.006 300.98);
|
||||
--gray-700: oklch(36.98% 0.014 302.71);
|
||||
--gray-400: oklch(70.9% 0.015 304.04);
|
||||
|
||||
--red-to-pink-to-purple-vertical-gradient: linear-gradient(
|
||||
180deg,
|
||||
var(--orange-red) 0%,
|
||||
var(--vivid-pink) 50%,
|
||||
var(--electric-violet) 100%
|
||||
);
|
||||
|
||||
--red-to-pink-to-purple-horizontal-gradient: linear-gradient(
|
||||
90deg,
|
||||
var(--orange-red) 0%,
|
||||
var(--vivid-pink) 50%,
|
||||
var(--electric-violet) 100%
|
||||
);
|
||||
|
||||
--pill-accent: var(--bright-blue);
|
||||
|
||||
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
|
||||
"Segoe UI Symbol";
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.125rem;
|
||||
color: var(--gray-900);
|
||||
font-weight: 500;
|
||||
line-height: 100%;
|
||||
letter-spacing: -0.125rem;
|
||||
margin: 0;
|
||||
font-family: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
|
||||
"Segoe UI Symbol";
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
main {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
box-sizing: inherit;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.angular-logo {
|
||||
max-width: 9.2rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.content h1 {
|
||||
margin-top: 1.75rem;
|
||||
}
|
||||
|
||||
.content p {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 1px;
|
||||
background: var(--red-to-pink-to-purple-vertical-gradient);
|
||||
margin-inline: 0.5rem;
|
||||
}
|
||||
|
||||
.pill-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.pill {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
--pill-accent: var(--bright-blue);
|
||||
background: color-mix(in srgb, var(--pill-accent) 5%, transparent);
|
||||
color: var(--pill-accent);
|
||||
padding-inline: 0.75rem;
|
||||
padding-block: 0.375rem;
|
||||
border-radius: 2.75rem;
|
||||
border: 0;
|
||||
transition: background 0.3s ease;
|
||||
font-family: var(--inter-font);
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.4rem;
|
||||
letter-spacing: -0.00875rem;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pill:hover {
|
||||
background: color-mix(in srgb, var(--pill-accent) 15%, transparent);
|
||||
}
|
||||
|
||||
.pill-group .pill:nth-child(6n + 1) {
|
||||
--pill-accent: var(--bright-blue);
|
||||
}
|
||||
.pill-group .pill:nth-child(6n + 2) {
|
||||
--pill-accent: var(--electric-violet);
|
||||
}
|
||||
.pill-group .pill:nth-child(6n + 3) {
|
||||
--pill-accent: var(--french-violet);
|
||||
}
|
||||
|
||||
.pill-group .pill:nth-child(6n + 4),
|
||||
.pill-group .pill:nth-child(6n + 5),
|
||||
.pill-group .pill:nth-child(6n + 6) {
|
||||
--pill-accent: var(--hot-red);
|
||||
}
|
||||
|
||||
.pill-group svg {
|
||||
margin-inline-start: 0.25rem;
|
||||
}
|
||||
|
||||
.social-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.73rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.social-links path {
|
||||
transition: fill 0.3s ease;
|
||||
fill: var(--gray-400);
|
||||
}
|
||||
|
||||
.social-links a:hover svg path {
|
||||
fill: var(--gray-900);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 650px) {
|
||||
.content {
|
||||
flex-direction: column;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background: var(--red-to-pink-to-purple-horizontal-gradient);
|
||||
margin-block: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<main class="main">
|
||||
<div class="content">
|
||||
<div class="left-side">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 982 239"
|
||||
fill="none"
|
||||
class="angular-logo"
|
||||
>
|
||||
<g clip-path="url(#a)">
|
||||
<path
|
||||
fill="url(#b)"
|
||||
d="M388.676 191.625h30.849L363.31 31.828h-35.758l-56.215 159.797h30.848l13.174-39.356h60.061l13.256 39.356Zm-65.461-62.675 21.602-64.311h1.227l21.602 64.311h-44.431Zm126.831-7.527v70.202h-28.23V71.839h27.002v20.374h1.392c2.782-6.71 7.2-12.028 13.255-15.956 6.056-3.927 13.584-5.89 22.503-5.89 8.264 0 15.465 1.8 21.684 5.318 6.137 3.518 10.964 8.673 14.319 15.382 3.437 6.71 5.074 14.81 4.992 24.383v76.175h-28.23v-71.92c0-8.019-2.046-14.237-6.219-18.819-4.173-4.5-9.819-6.791-17.102-6.791-4.91 0-9.328 1.063-13.174 3.272-3.846 2.128-6.792 5.237-9.001 9.328-2.046 4.009-3.191 8.918-3.191 14.728ZM589.233 239c-10.147 0-18.82-1.391-26.103-4.091-7.282-2.7-13.092-6.382-17.511-10.964-4.418-4.582-7.528-9.655-9.164-15.219l25.448-6.136c1.145 2.372 2.782 4.663 4.991 6.954 2.209 2.291 5.155 4.255 8.837 5.81 3.683 1.554 8.428 2.291 14.074 2.291 8.019 0 14.647-1.964 19.884-5.81 5.237-3.845 7.856-10.227 7.856-19.064v-22.665h-1.391c-1.473 2.946-3.601 5.892-6.383 9.001-2.782 3.109-6.464 5.645-10.965 7.691-4.582 2.046-10.228 3.109-17.101 3.109-9.165 0-17.511-2.209-25.039-6.545-7.446-4.337-13.42-10.883-17.757-19.474-4.418-8.673-6.628-19.473-6.628-32.565 0-13.091 2.21-24.301 6.628-33.383 4.419-9.082 10.311-15.955 17.839-20.7 7.528-4.746 15.874-7.037 25.039-7.037 7.037 0 12.846 1.145 17.347 3.518 4.582 2.373 8.182 5.236 10.883 8.51 2.7 3.272 4.746 6.382 6.137 9.327h1.554v-19.8h27.821v121.749c0 10.228-2.454 18.737-7.364 25.447-4.91 6.709-11.538 11.7-20.048 15.055-8.509 3.355-18.165 4.991-28.884 4.991Zm.245-71.266c5.974 0 11.047-1.473 15.302-4.337 4.173-2.945 7.446-7.118 9.573-12.519 2.21-5.482 3.274-12.027 3.274-19.637 0-7.609-1.064-14.155-3.274-19.8-2.127-5.646-5.318-10.064-9.491-13.255-4.174-3.11-9.329-4.746-15.384-4.746s-11.537 1.636-15.792 4.91c-4.173 3.272-7.365 7.772-9.492 13.418-2.128 5.727-3.191 12.191-3.191 19.392 0 7.2 1.063 13.745 3.273 19.228 2.127 5.482 5.318 9.736 9.573 12.764 4.174 3.027 9.41 4.582 15.629 4.582Zm141.56-26.51V71.839h28.23v119.786h-27.412v-21.273h-1.227c-2.7 6.709-7.119 12.191-13.338 16.446-6.137 4.255-13.747 6.382-22.748 6.382-7.855 0-14.81-1.718-20.783-5.237-5.974-3.518-10.72-8.591-14.075-15.382-3.355-6.709-5.073-14.891-5.073-24.464V71.839h28.312v71.921c0 7.609 2.046 13.664 6.219 18.083 4.173 4.5 9.655 6.709 16.365 6.709 4.173 0 8.183-.982 12.111-3.028 3.927-2.045 7.118-5.072 9.655-9.082 2.537-4.091 3.764-9.164 3.764-15.218Zm65.707-109.395v159.796h-28.23V31.828h28.23Zm44.841 162.169c-7.61 0-14.402-1.391-20.457-4.091-6.055-2.7-10.883-6.791-14.32-12.109-3.518-5.319-5.237-11.946-5.237-19.801 0-6.791 1.228-12.355 3.765-16.773 2.536-4.419 5.891-7.937 10.228-10.637 4.337-2.618 9.164-4.664 14.647-6.055 5.4-1.391 11.046-2.373 16.856-3.027 7.037-.737 12.683-1.391 17.102-1.964 4.337-.573 7.528-1.555 9.574-2.782 1.963-1.309 3.027-3.273 3.027-5.973v-.491c0-5.891-1.718-10.391-5.237-13.664-3.518-3.191-8.51-4.828-15.056-4.828-6.955 0-12.356 1.473-16.447 4.5-4.009 3.028-6.71 6.546-8.183 10.719l-26.348-3.764c2.046-7.282 5.483-13.336 10.31-18.328 4.746-4.909 10.638-8.59 17.511-11.045 6.955-2.455 14.565-3.682 22.912-3.682 5.809 0 11.537.654 17.265 2.045s10.965 3.6 15.711 6.71c4.746 3.109 8.51 7.282 11.455 12.6 2.864 5.318 4.337 11.946 4.337 19.883v80.184h-27.166v-16.446h-.9c-1.719 3.355-4.092 6.464-7.201 9.328-3.109 2.864-6.955 5.237-11.619 6.955-4.828 1.718-10.229 2.536-16.529 2.536Zm7.364-20.701c5.646 0 10.556-1.145 14.729-3.354 4.173-2.291 7.364-5.237 9.655-9.001 2.292-3.763 3.355-7.854 3.355-12.273v-14.155c-.9.737-2.373 1.391-4.5 2.046-2.128.654-4.419 1.145-7.037 1.636-2.619.491-5.155.9-7.692 1.227-2.537.328-4.746.655-6.628.901-4.173.572-8.019 1.472-11.292 2.781-3.355 1.31-5.973 3.11-7.855 5.401-1.964 2.291-2.864 5.318-2.864 8.918 0 5.237 1.882 9.164 5.728 11.782 3.682 2.782 8.51 4.091 14.401 4.091Zm64.643 18.328V71.839h27.412v19.965h1.227c2.21-6.955 5.974-12.274 11.292-16.038 5.319-3.763 11.456-5.645 18.329-5.645 1.555 0 3.355.082 5.237.163 1.964.164 3.601.328 4.91.573v25.938c-1.227-.41-3.109-.819-5.646-1.146a58.814 58.814 0 0 0-7.446-.49c-5.155 0-9.738 1.145-13.829 3.354-4.091 2.209-7.282 5.236-9.655 9.164-2.373 3.927-3.519 8.427-3.519 13.5v70.448h-28.312ZM222.077 39.192l-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
|
||||
/>
|
||||
<path
|
||||
fill="url(#c)"
|
||||
d="M388.676 191.625h30.849L363.31 31.828h-35.758l-56.215 159.797h30.848l13.174-39.356h60.061l13.256 39.356Zm-65.461-62.675 21.602-64.311h1.227l21.602 64.311h-44.431Zm126.831-7.527v70.202h-28.23V71.839h27.002v20.374h1.392c2.782-6.71 7.2-12.028 13.255-15.956 6.056-3.927 13.584-5.89 22.503-5.89 8.264 0 15.465 1.8 21.684 5.318 6.137 3.518 10.964 8.673 14.319 15.382 3.437 6.71 5.074 14.81 4.992 24.383v76.175h-28.23v-71.92c0-8.019-2.046-14.237-6.219-18.819-4.173-4.5-9.819-6.791-17.102-6.791-4.91 0-9.328 1.063-13.174 3.272-3.846 2.128-6.792 5.237-9.001 9.328-2.046 4.009-3.191 8.918-3.191 14.728ZM589.233 239c-10.147 0-18.82-1.391-26.103-4.091-7.282-2.7-13.092-6.382-17.511-10.964-4.418-4.582-7.528-9.655-9.164-15.219l25.448-6.136c1.145 2.372 2.782 4.663 4.991 6.954 2.209 2.291 5.155 4.255 8.837 5.81 3.683 1.554 8.428 2.291 14.074 2.291 8.019 0 14.647-1.964 19.884-5.81 5.237-3.845 7.856-10.227 7.856-19.064v-22.665h-1.391c-1.473 2.946-3.601 5.892-6.383 9.001-2.782 3.109-6.464 5.645-10.965 7.691-4.582 2.046-10.228 3.109-17.101 3.109-9.165 0-17.511-2.209-25.039-6.545-7.446-4.337-13.42-10.883-17.757-19.474-4.418-8.673-6.628-19.473-6.628-32.565 0-13.091 2.21-24.301 6.628-33.383 4.419-9.082 10.311-15.955 17.839-20.7 7.528-4.746 15.874-7.037 25.039-7.037 7.037 0 12.846 1.145 17.347 3.518 4.582 2.373 8.182 5.236 10.883 8.51 2.7 3.272 4.746 6.382 6.137 9.327h1.554v-19.8h27.821v121.749c0 10.228-2.454 18.737-7.364 25.447-4.91 6.709-11.538 11.7-20.048 15.055-8.509 3.355-18.165 4.991-28.884 4.991Zm.245-71.266c5.974 0 11.047-1.473 15.302-4.337 4.173-2.945 7.446-7.118 9.573-12.519 2.21-5.482 3.274-12.027 3.274-19.637 0-7.609-1.064-14.155-3.274-19.8-2.127-5.646-5.318-10.064-9.491-13.255-4.174-3.11-9.329-4.746-15.384-4.746s-11.537 1.636-15.792 4.91c-4.173 3.272-7.365 7.772-9.492 13.418-2.128 5.727-3.191 12.191-3.191 19.392 0 7.2 1.063 13.745 3.273 19.228 2.127 5.482 5.318 9.736 9.573 12.764 4.174 3.027 9.41 4.582 15.629 4.582Zm141.56-26.51V71.839h28.23v119.786h-27.412v-21.273h-1.227c-2.7 6.709-7.119 12.191-13.338 16.446-6.137 4.255-13.747 6.382-22.748 6.382-7.855 0-14.81-1.718-20.783-5.237-5.974-3.518-10.72-8.591-14.075-15.382-3.355-6.709-5.073-14.891-5.073-24.464V71.839h28.312v71.921c0 7.609 2.046 13.664 6.219 18.083 4.173 4.5 9.655 6.709 16.365 6.709 4.173 0 8.183-.982 12.111-3.028 3.927-2.045 7.118-5.072 9.655-9.082 2.537-4.091 3.764-9.164 3.764-15.218Zm65.707-109.395v159.796h-28.23V31.828h28.23Zm44.841 162.169c-7.61 0-14.402-1.391-20.457-4.091-6.055-2.7-10.883-6.791-14.32-12.109-3.518-5.319-5.237-11.946-5.237-19.801 0-6.791 1.228-12.355 3.765-16.773 2.536-4.419 5.891-7.937 10.228-10.637 4.337-2.618 9.164-4.664 14.647-6.055 5.4-1.391 11.046-2.373 16.856-3.027 7.037-.737 12.683-1.391 17.102-1.964 4.337-.573 7.528-1.555 9.574-2.782 1.963-1.309 3.027-3.273 3.027-5.973v-.491c0-5.891-1.718-10.391-5.237-13.664-3.518-3.191-8.51-4.828-15.056-4.828-6.955 0-12.356 1.473-16.447 4.5-4.009 3.028-6.71 6.546-8.183 10.719l-26.348-3.764c2.046-7.282 5.483-13.336 10.31-18.328 4.746-4.909 10.638-8.59 17.511-11.045 6.955-2.455 14.565-3.682 22.912-3.682 5.809 0 11.537.654 17.265 2.045s10.965 3.6 15.711 6.71c4.746 3.109 8.51 7.282 11.455 12.6 2.864 5.318 4.337 11.946 4.337 19.883v80.184h-27.166v-16.446h-.9c-1.719 3.355-4.092 6.464-7.201 9.328-3.109 2.864-6.955 5.237-11.619 6.955-4.828 1.718-10.229 2.536-16.529 2.536Zm7.364-20.701c5.646 0 10.556-1.145 14.729-3.354 4.173-2.291 7.364-5.237 9.655-9.001 2.292-3.763 3.355-7.854 3.355-12.273v-14.155c-.9.737-2.373 1.391-4.5 2.046-2.128.654-4.419 1.145-7.037 1.636-2.619.491-5.155.9-7.692 1.227-2.537.328-4.746.655-6.628.901-4.173.572-8.019 1.472-11.292 2.781-3.355 1.31-5.973 3.11-7.855 5.401-1.964 2.291-2.864 5.318-2.864 8.918 0 5.237 1.882 9.164 5.728 11.782 3.682 2.782 8.51 4.091 14.401 4.091Zm64.643 18.328V71.839h27.412v19.965h1.227c2.21-6.955 5.974-12.274 11.292-16.038 5.319-3.763 11.456-5.645 18.329-5.645 1.555 0 3.355.082 5.237.163 1.964.164 3.601.328 4.91.573v25.938c-1.227-.41-3.109-.819-5.646-1.146a58.814 58.814 0 0 0-7.446-.49c-5.155 0-9.738 1.145-13.829 3.354-4.091 2.209-7.282 5.236-9.655 9.164-2.373 3.927-3.519 8.427-3.519 13.5v70.448h-28.312ZM222.077 39.192l-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<radialGradient
|
||||
id="c"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientTransform="rotate(118.122 171.182 60.81) scale(205.794)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#FF41F8" />
|
||||
<stop offset=".707" stop-color="#FF41F8" stop-opacity=".5" />
|
||||
<stop offset="1" stop-color="#FF41F8" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="b"
|
||||
x1="0"
|
||||
x2="982"
|
||||
y1="192"
|
||||
y2="192"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#F0060B" />
|
||||
<stop offset="0" stop-color="#F0070C" />
|
||||
<stop offset=".526" stop-color="#CC26D5" />
|
||||
<stop offset="1" stop-color="#7702FF" />
|
||||
</linearGradient>
|
||||
<clipPath id="a"><path fill="#fff" d="M0 0h982v239H0z" /></clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<h1>Hello, {{ title() }}</h1>
|
||||
<p>Congratulations! Your app is running. 🎉</p>
|
||||
</div>
|
||||
<div class="divider" role="separator" aria-label="Divider"></div>
|
||||
<div class="right-side">
|
||||
<div class="pill-group">
|
||||
@for (item of [
|
||||
{ title: 'Explore the Docs', link: 'https://angular.dev' },
|
||||
{ title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' },
|
||||
{ title: 'Prompt and best practices for AI', link: 'https://angular.dev/ai/develop-with-ai'},
|
||||
{ title: 'CLI Docs', link: 'https://angular.dev/tools/cli' },
|
||||
{ title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' },
|
||||
{ title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' },
|
||||
]; track item.title) {
|
||||
<a
|
||||
class="pill"
|
||||
[href]="item.link"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<span>{{ item.title }}</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="14"
|
||||
viewBox="0 -960 960 960"
|
||||
width="14"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="social-links">
|
||||
<a
|
||||
href="https://github.com/angular/angular"
|
||||
aria-label="Github"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<svg
|
||||
width="25"
|
||||
height="24"
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
alt="Github"
|
||||
>
|
||||
<path
|
||||
d="M12.3047 0C5.50634 0 0 5.50942 0 12.3047C0 17.7423 3.52529 22.3535 8.41332 23.9787C9.02856 24.0946 9.25414 23.7142 9.25414 23.3871C9.25414 23.0949 9.24389 22.3207 9.23876 21.2953C5.81601 22.0377 5.09414 19.6444 5.09414 19.6444C4.53427 18.2243 3.72524 17.8449 3.72524 17.8449C2.61064 17.082 3.81137 17.0973 3.81137 17.0973C5.04697 17.1835 5.69604 18.3647 5.69604 18.3647C6.79321 20.2463 8.57636 19.7029 9.27978 19.3881C9.39052 18.5924 9.70736 18.0499 10.0591 17.7423C7.32641 17.4347 4.45429 16.3765 4.45429 11.6618C4.45429 10.3185 4.9311 9.22133 5.72065 8.36C5.58222 8.04931 5.16694 6.79833 5.82831 5.10337C5.82831 5.10337 6.85883 4.77319 9.2121 6.36459C10.1965 6.09082 11.2424 5.95546 12.2883 5.94931C13.3342 5.95546 14.3801 6.09082 15.3644 6.36459C17.7023 4.77319 18.7328 5.10337 18.7328 5.10337C19.3942 6.79833 18.9789 8.04931 18.8559 8.36C19.6403 9.22133 20.1171 10.3185 20.1171 11.6618C20.1171 16.3888 17.2409 17.4296 14.5031 17.7321C14.9338 18.1012 15.3337 18.8559 15.3337 20.0084C15.3337 21.6552 15.3183 22.978 15.3183 23.3779C15.3183 23.7009 15.5336 24.0854 16.1642 23.9623C21.0871 22.3484 24.6094 17.7341 24.6094 12.3047C24.6094 5.50942 19.0999 0 12.3047 0Z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/angular"
|
||||
aria-label="Twitter"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
alt="Twitter"
|
||||
>
|
||||
<path
|
||||
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.youtube.com/channel/UCbn1OgGei-DV7aSRo_HaAiw"
|
||||
aria-label="Youtube"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<svg
|
||||
width="29"
|
||||
height="20"
|
||||
viewBox="0 0 29 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
alt="Youtube"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M27.4896 1.52422C27.9301 1.96749 28.2463 2.51866 28.4068 3.12258C29.0004 5.35161 29.0004 10 29.0004 10C29.0004 10 29.0004 14.6484 28.4068 16.8774C28.2463 17.4813 27.9301 18.0325 27.4896 18.4758C27.0492 18.9191 26.5 19.2389 25.8972 19.4032C23.6778 20 14.8068 20 14.8068 20C14.8068 20 5.93586 20 3.71651 19.4032C3.11363 19.2389 2.56449 18.9191 2.12405 18.4758C1.68361 18.0325 1.36732 17.4813 1.20683 16.8774C0.613281 14.6484 0.613281 10 0.613281 10C0.613281 10 0.613281 5.35161 1.20683 3.12258C1.36732 2.51866 1.68361 1.96749 2.12405 1.52422C2.56449 1.08095 3.11363 0.76113 3.71651 0.596774C5.93586 0 14.8068 0 14.8068 0C14.8068 0 23.6778 0 25.8972 0.596774C26.5 0.76113 27.0492 1.08095 27.4896 1.52422ZM19.3229 10L11.9036 5.77905V14.221L19.3229 10Z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * The content above * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
|
||||
|
||||
<router-outlet />
|
||||
3
src/app/app.routes.ts
Normal file
3
src/app/app.routes.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
export const routes: Routes = [];
|
||||
23
src/app/app.spec.ts
Normal file
23
src/app/app.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { App } from './app';
|
||||
|
||||
describe('App', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [App],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(App);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, tools-website');
|
||||
});
|
||||
});
|
||||
12
src/app/app.ts
Normal file
12
src/app/app.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Component, signal } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [RouterOutlet],
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.css'
|
||||
})
|
||||
export class App {
|
||||
protected readonly title = signal('tools-website');
|
||||
}
|
||||
13
src/index.html
Normal file
13
src/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>ToolsWebsite</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
6
src/main.ts
Normal file
6
src/main.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { appConfig } from './app/app.config';
|
||||
import { App } from './app/app';
|
||||
|
||||
bootstrapApplication(App, appConfig)
|
||||
.catch((err) => console.error(err));
|
||||
1
src/styles.css
Normal file
1
src/styles.css
Normal file
@@ -0,0 +1 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
28
start.js
28
start.js
@@ -1,28 +0,0 @@
|
||||
const { exec } = require('child_process');
|
||||
const os = require('os');
|
||||
|
||||
// Starte den Entwicklungsserver
|
||||
exec('npm run dev', (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`Fehler beim Starten des Servers: ${error}`);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Warte kurz und öffne dann den Browser
|
||||
setTimeout(() => {
|
||||
const platform = os.platform();
|
||||
const url = 'http://localhost:3000';
|
||||
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
exec(`start ${url}`);
|
||||
break;
|
||||
case 'darwin':
|
||||
exec(`open ${url}`);
|
||||
break;
|
||||
default:
|
||||
exec(`xdg-open ${url}`);
|
||||
break;
|
||||
}
|
||||
}, 3000); // Warte 3 Sekunden, bis der Server gestartet ist
|
||||
@@ -1,12 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
15
tsconfig.app.json
Normal file
15
tsconfig.app.json
Normal file
@@ -0,0 +1,15 @@
|
||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,28 +1,34 @@
|
||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"skipLibCheck": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
"experimentalDecorators": true,
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "preserve"
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"typeCheckHostBindings": true,
|
||||
"strictTemplates": true
|
||||
},
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
14
tsconfig.spec.json
Normal file
14
tsconfig.spec.json
Normal file
@@ -0,0 +1,14 @@
|
||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user