add some tools

This commit is contained in:
mei 2024-12-27 19:27:49 +08:00
parent 42cdf41f47
commit 6db68cf014
32 changed files with 1761 additions and 3 deletions

View File

@ -0,0 +1,47 @@
"use client";
import NicknameGenerator from '@/components/tools/CatgirlGenerator/NicknameGenerator'
import TextReversal from '@/components/tools/CatgirlGenerator/TextReversal'
import CustomTemplate from '@/components/tools/CatgirlGenerator/CustomTemplate'
import DarkModeToggle from '@/components/tools/CatgirlGenerator/DarkModeToggle'
import Footer from "@/components/tools/CatgirlGenerator/Footer";
export default function Home() {
return (
<div className="min-h-screen bg-gray-100 dark:bg-gray-900 transition-colors duration-300">
<div className="container mx-auto px-4 py-8">
<header className="mb-8 flex justify-between items-center">
<h1 className="text-4xl font-bold text-gray-800 dark:text-white"></h1>
<DarkModeToggle />
</header>
<main>
<section className="mb-12">
<h2 className="text-2xl font-semibold mb-4 text-gray-800 dark:text-white"></h2>
<p className="text-gray-600 dark:text-gray-300 mb-4">
@你时@的文字后面加上一另段文字~
</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
{['step1.jpg', 'step2.jpg', 'example.jpg'].map((img, index) => (
<img
key={index}
src={`/img/CatgirlGenerator/${img}`}
alt={`Example ${index + 1}`}
className="w-full h-auto rounded-lg shadow-md transition-transform duration-300 hover:scale-105"
/>
))}
</div>
<p className="text-gray-600 dark:text-gray-300">
unicode控制字符 <code className="bg-gray-200 dark:bg-gray-700 px-1 rounded">RLI</code>
<code className="bg-gray-200 dark:bg-gray-700 px-1 rounded">LRI</code>
@后@后面的文本会跑到被包裹部分的前面去
</p>
</section>
<NicknameGenerator />
<TextReversal />
<CustomTemplate />
</main>
</div>
<Footer />
</div>
)
}

View File

@ -0,0 +1,55 @@
'use client'
import { useState } from 'react'
import { ThemeProvider } from 'next-themes'
import FileUpload from '@/components/tools/compressor/FileUpload'
import CompressionOptions from '@/components/tools/compressor/CompressionOptions'
import CompressionResult from '@/components/tools/compressor/CompressionResult'
import ThemeToggle from '@/components/tools/compressor/ThemeToggle'
import Instructions from '@/components/tools/compressor/Instructions'
import useImageCompression from '@/hooks/useImageCompression'
export default function ImageCompressor() {
const [file, setFile] = useState<File | null>(null)
const { compressImage, compressedImage, isCompressing, progress, error } = useImageCompression()
const handleCompress = async (format: string) => {
if (file) {
await compressImage(file, format)
}
}
return (
<ThemeProvider attribute="class">
<div className="min-h-screen bg-gray-100 dark:bg-gray-900 py-12 px-4 sm:px-6 lg:px-8 transition-colors duration-200">
<div className="max-w-3xl mx-auto">
<div className="flex justify-between items-center mb-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white"></h1>
<ThemeToggle />
</div>
<div className="bg-white dark:bg-gray-800 shadow-md rounded-lg p-6 transition-colors duration-200">
<Instructions />
<FileUpload onFileSelect={setFile} selectedFile={file} />
{file && (
<CompressionOptions
onCompress={handleCompress}
isCompressing={isCompressing}
progress={progress}
/>
)}
{compressedImage && (
<CompressionResult
originalImage={file!}
compressedImage={compressedImage}
/>
)}
{error && (
<p className="mt-4 text-red-600 dark:text-red-400 text-center">{error}</p>
)}
</div>
</div>
</div>
</ThemeProvider>
)
}

View File

@ -0,0 +1,119 @@
"use client";
import { useState, useEffect } from "react";
import RandomSeed from "@/components/tools/hash-random/RandomSeed";
import ParticipantList from "@/components/tools/hash-random/ParticipantList";
import ResultDisplay from "@/components/tools/hash-random/ResultDisplay";
import HashTable from "@/components/tools/hash-random/HashTable";
import CoreAlgorithm from "@/components/tools/hash-random/CoreAlgorithm";
import { useHashCalculation } from "@/hooks/useHashCalculation";
import DarkModeToggle from "@/components/tools/hash-random/DarkModeToggle";
import SaveLoadSelection from "@/components/tools/hash-random/SaveLoadSelection";
import Footer from "@/components/tools/hash-random/Footer";
import { AnimatePresence, motion } from "framer-motion";
export default function Home() {
const [salt, setSalt] = useState("");
const [count, setCount] = useState(1);
const [participants, setParticipants] = useState<string[]>([]);
const { sortedResults, saltHash, isCalculating } = useHashCalculation(
salt,
participants,
count
);
const [darkMode, setDarkMode] = useState(false);
useEffect(() => {
const isDarkMode = localStorage.getItem("darkMode") === "true";
setDarkMode(isDarkMode);
}, []);
useEffect(() => {
document.documentElement.classList.toggle("dark", darkMode);
localStorage.setItem("darkMode", darkMode.toString());
}, [darkMode]);
return (
<div
className={`min-h-screen transition-colors duration-300 ${
darkMode
? "dark bg-gray-900"
: "bg-gradient-to-br from-blue-50 to-indigo-100"
}`}
>
<div className="container mx-auto px-4 py-12">
<div className="flex justify-between items-center mb-8">
<h1 className="text-5xl font-extrabold text-indigo-800 dark:text-indigo-200">
</h1>
<DarkModeToggle darkMode={darkMode} setDarkMode={setDarkMode} />
</div>
<div className="flex justify-center space-x-4 mb-12">
<span className="bg-indigo-500 text-white px-4 py-2 rounded-full text-sm font-semibold shadow-md">
</span>
<span className="bg-green-500 text-white px-4 py-2 rounded-full text-sm font-semibold shadow-md">
</span>
<span className="bg-purple-500 text-white px-4 py-2 rounded-full text-sm font-semibold shadow-md">
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div className="space-y-8">
<RandomSeed
salt={salt}
setSalt={setSalt}
count={count}
setCount={setCount}
/>
<ParticipantList setParticipants={setParticipants} />
<SaveLoadSelection
salt={salt}
setSalt={setSalt}
count={count}
setCount={setCount}
participants={participants}
setParticipants={setParticipants}
/>
</div>
<div className="space-y-8">
<AnimatePresence mode="wait">
{isCalculating ? (
<motion.div
key="loading"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="flex justify-center items-center h-64"
>
<div className="loader ease-linear rounded-full border-8 border-t-8 border-gray-200 h-24 w-24"></div>
</motion.div>
) : (
<motion.div
key="results"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<ResultDisplay
results={sortedResults.slice(0, count)}
saltHash={saltHash}
/>
<HashTable
results={sortedResults.slice(0, 1000)}
saltHash={saltHash}
/>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
<CoreAlgorithm />
</div>
<Footer />
</div>
);
}

View File

@ -33,7 +33,7 @@ export default function Component() {
useEffect(() => {
async function loadData() {
try {
const response = await fetch('https://aps.icu/api/load-yaml-data');
const response = await fetch('/api/load-yaml-data');
const yamlData = await response.json();
setData(yamlData);
} catch (error) {

View File

@ -0,0 +1,69 @@
'use client'
import { useState } from 'react'
import { motion } from 'framer-motion'
import Toast from './Toast'
export default function CustomTemplate() {
const [input, setInput] = useState('')
const [output, setOutput] = useState('')
const [showToast, setShowToast] = useState(false)
const generateTemplate = () => {
setOutput(input.replace(/#r/g, '\u2067').replace(/#l/g, '\u2066'))
}
const copyToClipboard = () => {
navigator.clipboard.writeText(output)
setShowToast(true)
setTimeout(() => setShowToast(false), 3000)
}
return (
<motion.section
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.4 }}
className="mb-12 bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md"
>
<h2 className="text-2xl font-semibold mb-4 text-gray-800 dark:text-white"></h2>
<p className="text-gray-600 dark:text-gray-300 mb-4">
使 <code className="bg-gray-200 dark:bg-gray-700 px-1 rounded">#r</code> RLI
使 <code className="bg-gray-200 dark:bg-gray-700 px-1 rounded">#l</code> LRI
</p>
<div className="space-y-4">
<div>
<label htmlFor="template" className="block text-sm font-medium text-gray-700 dark:text-gray-300"></label>
<input
type="text"
id="template"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyUp={generateTemplate}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
</div>
<div>
<label htmlFor="result" className="block text-sm font-medium text-gray-700 dark:text-gray-300"></label>
<div className="mt-1 flex rounded-md shadow-sm">
<input
type="text"
id="result"
value={output}
readOnly
className="flex-1 rounded-none rounded-l-md border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
<button
onClick={copyToClipboard}
className="inline-flex items-center px-3 rounded-r-md border border-l-0 border-gray-300 bg-gray-50 text-gray-500 text-sm dark:bg-gray-600 dark:border-gray-500 dark:text-gray-300"
>
</button>
</div>
</div>
</div>
<Toast message="复制成功!" isVisible={showToast} />
</motion.section>
)
}

View File

@ -0,0 +1,36 @@
'use client'
import { useState, useEffect } from 'react'
import { motion } from 'framer-motion'
import { Moon, Sun } from 'lucide-react'
export default function DarkModeToggle() {
const [darkMode, setDarkMode] = useState(false)
useEffect(() => {
if (typeof window !== 'undefined') {
const isDarkMode = localStorage.getItem('darkMode') === 'true'
setDarkMode(isDarkMode)
document.documentElement.classList.toggle('dark', isDarkMode)
}
}, [])
const toggleDarkMode = () => {
const newDarkMode = !darkMode
setDarkMode(newDarkMode)
localStorage.setItem('darkMode', newDarkMode.toString())
document.documentElement.classList.toggle('dark', newDarkMode)
}
return (
<motion.button
onClick={toggleDarkMode}
className="p-2 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
{darkMode ? <Sun size={24} /> : <Moon size={24} />}
</motion.button>
)
}

View File

@ -0,0 +1,22 @@
export default function Footer() {
return (
<footer className="bg-white dark:bg-gray-800 shadow-md mt-12 py-6 transition-colors duration-200">
<div className="container mx-auto px-4 text-center">
<p className="text-gray-600 dark:text-gray-300">
© {new Date().getFullYear()} APS NAV.
</p>
<p className="text-gray-600 dark:text-gray-300 mt-2">
:{" "}
<a
href="https://github.com/TaylorAndTony/CatgirlGenerator"
target="_blank"
rel="noopener noreferrer"
className="text-indigo-600 dark:text-indigo-400 hover:underline"
>
TaylorAndTony
</a>
</p>
</div>
</footer>
);
}

View File

@ -0,0 +1,102 @@
'use client'
import { useState } from 'react'
import { motion } from 'framer-motion'
import Toast from './Toast'
export default function NicknameGenerator() {
const [prefix, setPrefix] = useState('爱蜜莉雅')
const [suffix, setSuffix] = useState('碳~')
const [result, setResult] = useState('')
const [result2, setResult2] = useState('')
const [showToast, setShowToast] = useState(false)
const generateNickname = () => {
const wrapped = '\u2067' + suffix.split('').reverse().join('') + '\u2066'
setResult(prefix + wrapped)
setResult2(prefix + '\u2067' + suffix + '\u2066')
}
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text)
setShowToast(true)
setTimeout(() => setShowToast(false), 3000)
}
return (
<motion.section
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="mb-12 bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md"
>
<h2 className="text-2xl font-semibold mb-4 text-gray-800 dark:text-white"></h2>
<div className="space-y-4">
<div>
<label htmlFor="prefix" className="block text-sm font-medium text-gray-700 dark:text-gray-300"></label>
<input
type="text"
id="prefix"
value={prefix}
onChange={(e) => setPrefix(e.target.value)}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
</div>
<div>
<label htmlFor="suffix" className="block text-sm font-medium text-gray-700 dark:text-gray-300"></label>
<input
type="text"
id="suffix"
value={suffix}
onChange={(e) => setSuffix(e.target.value)}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
</div>
<button
onClick={generateNickname}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 transition-colors duration-300"
>
</button>
<div>
<label htmlFor="result" className="block text-sm font-medium text-gray-700 dark:text-gray-300">1</label>
<div className="mt-1 flex rounded-md shadow-sm">
<input
type="text"
id="result"
value={result}
readOnly
className="flex-1 rounded-none rounded-l-md border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
<button
onClick={() => copyToClipboard(result)}
className="inline-flex items-center px-3 rounded-r-md border border-l-0 border-gray-300 bg-gray-50 text-gray-500 text-sm dark:bg-gray-600 dark:border-gray-500 dark:text-gray-300"
>
</button>
</div>
</div>
<div>
<label htmlFor="result2" className="block text-sm font-medium text-gray-700 dark:text-gray-300">2</label>
<div className="mt-1 flex rounded-md shadow-sm">
<input
type="text"
id="result2"
value={result2}
readOnly
className="flex-1 rounded-none rounded-l-md border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
<button
onClick={() => copyToClipboard(result2)}
className="inline-flex items-center px-3 rounded-r-md border border-l-0 border-gray-300 bg-gray-50 text-gray-500 text-sm dark:bg-gray-600 dark:border-gray-500 dark:text-gray-300"
>
</button>
</div>
</div>
</div>
<Toast message="复制成功!" isVisible={showToast} />
</motion.section>
)
}

View File

@ -0,0 +1,65 @@
'use client'
import { useState } from 'react'
import { motion } from 'framer-motion'
import Toast from './Toast'
export default function TextReversal() {
const [input, setInput] = useState('')
const [output, setOutput] = useState('')
const [showToast, setShowToast] = useState(false)
const reverseText = () => {
setOutput(input.split('').reverse().join(''))
}
const copyToClipboard = () => {
navigator.clipboard.writeText(output)
setShowToast(true)
setTimeout(() => setShowToast(false), 3000)
}
return (
<motion.section
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.2 }}
className="mb-12 bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md"
>
<h2 className="text-2xl font-semibold mb-4 text-gray-800 dark:text-white"></h2>
<div className="space-y-4">
<div>
<label htmlFor="input" className="block text-sm font-medium text-gray-700 dark:text-gray-300"></label>
<input
type="text"
id="input"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyUp={reverseText}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
</div>
<div>
<label htmlFor="output" className="block text-sm font-medium text-gray-700 dark:text-gray-300"></label>
<div className="mt-1 flex rounded-md shadow-sm">
<input
type="text"
id="output"
value={output}
readOnly
className="flex-1 rounded-none rounded-l-md border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
<button
onClick={copyToClipboard}
className="inline-flex items-center px-3 rounded-r-md border border-l-0 border-gray-300 bg-gray-50 text-gray-500 text-sm dark:bg-gray-600 dark:border-gray-500 dark:text-gray-300"
>
</button>
</div>
</div>
</div>
<Toast message="复制成功!" isVisible={showToast} />
</motion.section>
)
}

View File

@ -0,0 +1,26 @@
import { motion, AnimatePresence } from 'framer-motion'
import { CheckCircle } from 'lucide-react'
interface ToastProps {
message: string
isVisible: boolean
}
export default function Toast({ message, isVisible }: ToastProps) {
return (
<AnimatePresence>
{isVisible && (
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 50 }}
className="fixed bottom-4 right-4 bg-green-500 text-white px-4 py-2 rounded-md shadow-lg flex items-center space-x-2"
>
<CheckCircle size={20} />
<span>{message}</span>
</motion.div>
)}
</AnimatePresence>
)
}

View File

@ -0,0 +1,51 @@
import { useState } from 'react'
import { motion } from 'framer-motion'
interface CompressionOptionsProps {
onCompress: (format: string) => void
isCompressing: boolean
progress: number
}
export default function CompressionOptions({ onCompress, isCompressing, progress }: CompressionOptionsProps) {
const [format, setFormat] = useState('webp')
return (
<div className="mt-6">
<div className="flex flex-wrap -mx-2">
<div className="w-full px-2 mb-4">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
</label>
<select
value={format}
onChange={(e) => setFormat(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
>
<option value="webp">WebP</option>
<option value="jpeg">JPEG</option>
<option value="png">PNG</option>
</select>
</div>
</div>
<motion.button
onClick={() => onCompress(format)}
disabled={isCompressing}
className="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed dark:bg-blue-600 dark:hover:bg-blue-700"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
{isCompressing ? '压缩中...' : '开始压缩'}
</motion.button>
{isCompressing && (
<div className="mt-4">
<div className="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
<div className="bg-blue-600 h-2.5 rounded-full" style={{ width: `${progress}%` }}></div>
</div>
<p className="text-center mt-2 text-sm text-gray-600 dark:text-gray-400">{progress}%</p>
</div>
)}
</div>
)
}

View File

@ -0,0 +1,69 @@
import { useState } from 'react'
import Image from 'next/image'
interface CompressionResultProps {
originalImage: File
compressedImage: File
}
export default function CompressionResult({ originalImage, compressedImage }: CompressionResultProps) {
const [showOriginal, setShowOriginal] = useState(false)
const originalSizeKB = (originalImage.size / 1024).toFixed(2)
const compressedSizeKB = (compressedImage.size / 1024).toFixed(2)
const compressionRatio = ((1 - compressedImage.size / originalImage.size) * 100).toFixed(2)
return (
<div className="mt-6">
<h2 className="text-lg font-semibold mb-2 text-gray-900 dark:text-white"></h2>
<div className="bg-gray-100 dark:bg-gray-700 rounded-lg p-4">
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm font-medium text-gray-500 dark:text-gray-400"></p>
<p className="text-lg font-semibold text-gray-900 dark:text-white">{originalSizeKB} KB</p>
</div>
<div>
<p className="text-sm font-medium text-gray-500 dark:text-gray-400"></p>
<p className="text-lg font-semibold text-gray-900 dark:text-white">{compressedSizeKB} KB</p>
</div>
<div>
<p className="text-sm font-medium text-gray-500 dark:text-gray-400"></p>
<p className="text-lg font-semibold text-gray-900 dark:text-white">{compressionRatio}%</p>
</div>
<div>
<p className="text-sm font-medium text-gray-500 dark:text-gray-400"></p>
<p className="text-lg font-semibold text-gray-900 dark:text-white">{compressedImage.type.split('/')[1].toUpperCase()}</p>
</div>
</div>
<div className="mt-4">
<a
href={URL.createObjectURL(compressedImage)}
download={`compressed_${compressedImage.name}`}
className="inline-block bg-green-500 text-white py-2 px-4 rounded-md hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 dark:bg-green-600 dark:hover:bg-green-700"
>
</a>
</div>
</div>
<div className="mt-6">
<h3 className="text-lg font-semibold mb-2 text-gray-900 dark:text-white"></h3>
<div className="relative">
<Image
src={URL.createObjectURL(showOriginal ? originalImage : compressedImage)}
alt="Compressed Image"
width={600}
height={400}
className="w-full h-auto rounded-lg"
/>
<button
className="absolute top-2 right-2 bg-white dark:bg-gray-800 text-gray-900 dark:text-white px-3 py-1 rounded-md text-sm font-medium"
onClick={() => setShowOriginal(!showOriginal)}
>
{showOriginal ? '显示压缩后' : '显示原图'}
</button>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,88 @@
import { useCallback, useState } from "react";
import { useDropzone } from "react-dropzone";
import { motion, HTMLMotionProps } from "framer-motion";
interface FileUploadProps {
onFileSelect: (file: File) => void;
selectedFile: File | null;
}
export default function FileUpload({
onFileSelect,
selectedFile,
}: FileUploadProps) {
const [error, setError] = useState<string | null>(null);
const onDrop = useCallback(
(acceptedFiles: File[]) => {
if (acceptedFiles.length > 0) {
const file = acceptedFiles[0];
if (file.type.startsWith("image/")) {
onFileSelect(file);
setError(null);
} else {
setError("请上传有效的图片文件。");
}
}
},
[onFileSelect]
);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: {
"image/*": [".jpeg", ".jpg", ".png", ".gif", ".webp"],
},
multiple: false,
});
return (
<>
<motion.div
{...getRootProps() as Omit<HTMLMotionProps<"div">, "onDragStart">}
className={`border-2 border-dashed rounded-lg p-8 text-center cursor-pointer transition-colors ${
isDragActive
? "border-blue-500 bg-blue-50 dark:bg-blue-900"
: "border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500"
}`}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
<input {...getInputProps()} />
{isDragActive ? (
<p className="text-blue-500 dark:text-blue-400"> ...</p>
) : (
<p className="text-gray-600 dark:text-gray-300">
</p>
)}
</motion.div>
{error && <p className="text-red-500">{error}</p>}
{selectedFile && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="mt-4 p-4 bg-green-100 dark:bg-green-800 rounded-lg"
>
<p className="text-green-700 dark:text-green-200">
<span className="font-semibold">:</span>{" "}
{selectedFile.name}
</p>
<p className="text-sm text-green-600 dark:text-green-300 mt-1">
: {(selectedFile.size / 1024 / 1024).toFixed(2)} MB
</p>
{selectedFile.type.startsWith("image/") && (
<div className="mt-2">
<img
src={URL.createObjectURL(selectedFile)}
alt={selectedFile.name}
className="max-w-full h-auto rounded-lg"
/>
</div>
)}
</motion.div>
)}
</>
);
}

View File

@ -0,0 +1,15 @@
export default function Instructions() {
return (
<div className="mb-6 p-4 bg-blue-50 dark:bg-blue-900 rounded-lg">
<h2 className="text-lg font-semibold mb-2 text-blue-800 dark:text-blue-200">使</h2>
<ol className="list-decimal list-inside text-sm text-blue-700 dark:text-blue-200">
<li></li>
<li>WebPJPEG PNG</li>
<li>&quot;&quot;</li>
<li></li>
</ol>
</div>
)
}

View File

@ -0,0 +1,28 @@
'use client'
import { useTheme } from 'next-themes'
import { useState, useEffect } from 'react'
import { Sun, Moon } from 'lucide-react'
export default function ThemeToggle() {
const [mounted, setMounted] = useState(false)
const { theme, setTheme } = useTheme()
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return null
}
return (
<button
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
className="p-2 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200"
>
{theme === 'dark' ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
</button>
)
}

View File

@ -0,0 +1,57 @@
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { tomorrow } from 'react-syntax-highlighter/dist/esm/styles/prism'
export default function CoreAlgorithm() {
const distanceCode = `
function calcMd5Distance(s1, s2) {
let distance = 0;
for (let i = 0; i < Math.min(s1.length, s2.length); i++) {
if (s1[i] === s2[i]) {
continue;
}
distance += Math.abs(s1.charCodeAt(i) - s2.charCodeAt(i));
}
return distance;
}
`
const sortCode = `
function makeSortedNameHashDistanceTuple(targetHash, names) {
let nameHashDistanceTuple = names.map((name) => {
let nameMd5 = makeMd5(name);
let distance = calcMd5Distance(targetHash, nameMd5);
return [name, nameMd5, distance];
});
// sort by distance
nameHashDistanceTuple.sort((a, b) => {
return a[2] - b[2];
});
return nameHashDistanceTuple;
}
`
return (
<div className="mt-12 bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-200">
<h2 className="text-2xl font-bold mb-4 text-indigo-800 dark:text-indigo-200"></h2>
<p className="mb-4 text-gray-600 dark:text-gray-300">md5 js-md5 </p>
<div className="mb-6">
<SyntaxHighlighter language="html" style={tomorrow} className="rounded-md">
{'<script src="https://cdn.jsdelivr.net/npm/js-md5@0.7.3/src/md5.min.js"></script>'}
</SyntaxHighlighter>
</div>
<div className="mb-6">
<h3 className="text-lg font-semibold mb-2 text-indigo-700 dark:text-indigo-300"></h3>
<SyntaxHighlighter language="javascript" style={tomorrow} className="rounded-md">
{distanceCode}
</SyntaxHighlighter>
</div>
<div>
<h3 className="text-lg font-semibold mb-2 text-indigo-700 dark:text-indigo-300"></h3>
<SyntaxHighlighter language="javascript" style={tomorrow} className="rounded-md">
{sortCode}
</SyntaxHighlighter>
</div>
</div>
)
}

View File

@ -0,0 +1,19 @@
import { Moon, Sun } from 'lucide-react'
interface DarkModeToggleProps {
darkMode: boolean
setDarkMode: (darkMode: boolean) => void
}
export default function DarkModeToggle({ darkMode, setDarkMode }: DarkModeToggleProps) {
return (
<button
onClick={() => setDarkMode(!darkMode)}
className="p-2 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 transition-colors duration-200"
aria-label={darkMode ? "Switch to light mode" : "Switch to dark mode"}
>
{darkMode ? <Sun size={24} /> : <Moon size={24} />}
</button>
)
}

View File

@ -0,0 +1,22 @@
export default function Footer() {
return (
<footer className="bg-white dark:bg-gray-800 shadow-md mt-12 py-6 transition-colors duration-200">
<div className="container mx-auto px-4 text-center">
<p className="text-gray-600 dark:text-gray-300">
© {new Date().getFullYear()} APS NAV.
</p>
<p className="text-gray-600 dark:text-gray-300 mt-2">
:{" "}
<a
href="https://github.com/TaylorAndTony/hash-random"
target="_blank"
rel="noopener noreferrer"
className="text-indigo-600 dark:text-indigo-400 hover:underline"
>
TaylorAndTony
</a>
</p>
</div>
</footer>
);
}

View File

@ -0,0 +1,37 @@
interface HashTableProps {
results: [string, string, number][]
saltHash: string
}
export default function HashTable({ results }: HashTableProps) {
return (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-200">
<h2 className="text-2xl font-bold mb-4 text-indigo-800 dark:text-indigo-200"></h2>
<p className="mb-4 text-gray-600 dark:text-gray-300"> 1000 </p>
<div className="overflow-x-auto">
<table className="w-full border-collapse">
<thead>
<tr className="bg-indigo-100 dark:bg-indigo-900">
<th className="border border-indigo-200 dark:border-indigo-700 px-4 py-2 text-left text-indigo-800 dark:text-indigo-200"></th>
<th className="border border-indigo-200 dark:border-indigo-700 px-4 py-2 text-left text-indigo-800 dark:text-indigo-200"></th>
<th className="border border-indigo-200 dark:border-indigo-700 px-4 py-2 text-left text-indigo-800 dark:text-indigo-200"></th>
<th className="border border-indigo-200 dark:border-indigo-700 px-4 py-2 text-left text-indigo-800 dark:text-indigo-200"></th>
</tr>
</thead>
<tbody>
{results.map(([name, hash, distance], index) => (
<tr key={index} className={index % 2 === 0 ? 'bg-gray-50 dark:bg-gray-800' : 'bg-white dark:bg-gray-700'}>
<td className="border border-indigo-200 dark:border-indigo-700 px-4 py-2 text-gray-800 dark:text-gray-200">{index + 1}</td>
<td className="border border-indigo-200 dark:border-indigo-700 px-4 py-2 text-gray-800 dark:text-gray-200">{name}</td>
<td className="border border-indigo-200 dark:border-indigo-700 px-4 py-2 font-mono text-sm text-gray-800 dark:text-gray-200">{hash}</td>
<td className="border border-indigo-200 dark:border-indigo-700 px-4 py-2 text-gray-800 dark:text-gray-200">{distance}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}

View File

@ -0,0 +1,40 @@
import { useState } from 'react'
interface ParticipantListProps {
setParticipants: (participants: string[]) => void
}
export default function ParticipantList({ setParticipants }: ParticipantListProps) {
const [participantText, setParticipantText] = useState('')
const handleRoll = () => {
const participantArray = participantText.split(/[\n\s]+/).filter(Boolean)
setParticipants(participantArray)
}
return (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-200">
<h2 className="text-2xl font-bold mb-4 text-indigo-800 dark:text-indigo-200"></h2>
<div className="space-y-4">
<div>
<label htmlFor="participants" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
</label>
<textarea
id="participants"
value={participantText}
onChange={(e) => setParticipantText(e.target.value)}
className="w-full h-32 border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
</div>
<button
onClick={handleRoll}
className="w-full bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors"
>
</button>
</div>
</div>
)
}

View File

@ -0,0 +1,68 @@
import { ChangeEvent, useState } from 'react'
interface RandomSeedProps {
salt: string
setSalt: (salt: string) => void
count: number
setCount: (count: number) => void
}
export default function RandomSeed({ salt, setSalt, count, setCount }: RandomSeedProps) {
const [errors, setErrors] = useState({ salt: '', count: '' })
const validateSalt = (value: string) => {
if (value.trim() === '') {
setErrors(prev => ({ ...prev, salt: '请输入随机种子' }))
} else {
setErrors(prev => ({ ...prev, salt: '' }))
}
setSalt(value)
}
const validateCount = (value: number) => {
if (value < 1) {
setErrors(prev => ({ ...prev, count: '抽取数量必须大于0' }))
} else {
setErrors(prev => ({ ...prev, count: '' }))
}
setCount(value)
}
return (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-200">
<h2 className="text-2xl font-bold mb-4 text-indigo-800 dark:text-indigo-200"></h2>
<p className="mb-4 text-gray-600 dark:text-gray-300">
</p>
<div className="space-y-4">
<div>
<label htmlFor="salt" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
</label>
<input
id="salt"
type="text"
value={salt}
onChange={(e: ChangeEvent<HTMLInputElement>) => validateSalt(e.target.value)}
className="w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
{errors.salt && <p className="mt-1 text-sm text-red-600 dark:text-red-400">{errors.salt}</p>}
</div>
<div>
<label htmlFor="count" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
</label>
<input
id="count"
type="number"
value={count}
onChange={(e: ChangeEvent<HTMLInputElement>) => validateCount(Number(e.target.value))}
className="w-24 border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
{errors.count && <p className="mt-1 text-sm text-red-600 dark:text-red-400">{errors.count}</p>}
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,70 @@
import { useState } from "react";
interface ResultDisplayProps {
results: [string, string, number][];
saltHash: string;
}
export default function ResultDisplay({
results,
saltHash,
}: ResultDisplayProps) {
const [copied, setCopied] = useState(false);
const handleCopy = () => {
const resultText = results.map(([name]) => name).join("\n");
navigator.clipboard.writeText(resultText);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-200">
<h2 className="text-2xl font-bold mb-4 text-indigo-800 dark:text-indigo-200">
</h2>
<p className="mb-4 text-gray-600 dark:text-gray-300">
</p>
<div className="space-y-4">
<div>
<label
htmlFor="results"
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
>
</label>
<div className="flex">
<textarea
id="results"
value={results.map(([name]) => name).join("\n")}
readOnly
className="flex-grow h-32 border-gray-300 rounded-l-md shadow-sm bg-gray-50 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
<button
onClick={handleCopy}
className="bg-green-500 text-white px-4 py-2 rounded-r-md hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-colors"
>
{copied ? "已复制" : "复制"}
</button>
</div>
</div>
<div>
<label
htmlFor="saltHash"
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
>
</label>
<input
id="saltHash"
type="text"
value={saltHash}
readOnly
className="w-full border-gray-300 rounded-md shadow-sm bg-gray-50 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,98 @@
import { useState } from 'react'
interface SaveLoadSelectionProps {
salt: string
setSalt: (salt: string) => void
count: number
setCount: (count: number) => void
participants: string[]
setParticipants: (participants: string[]) => void
}
// 定义 SelectionData 类型
interface SelectionData {
salt: string
count: number
participants: string[]
}
export default function SaveLoadSelection({
salt,
setSalt,
count,
setCount,
participants,
setParticipants,
}: SaveLoadSelectionProps) {
// 使用 SelectionData 类型更新 savedSelections 的类型
const [savedSelections, setSavedSelections] = useState<{ name: string; data: SelectionData }[]>([])
const [selectionName, setSelectionName] = useState('')
const saveSelection = () => {
if (selectionName) {
const newSelection = {
name: selectionName,
data: { salt, count, participants },
}
setSavedSelections([...savedSelections, newSelection])
setSelectionName('')
}
}
// 使用 SelectionData 类型更新 loadSelection 的参数
const loadSelection = (selection: { name: string; data: SelectionData }) => {
setSalt(selection.data.salt)
setCount(selection.data.count)
setParticipants(selection.data.participants)
}
const deleteSelection = (index: number) => {
const newSelections = [...savedSelections];
newSelections.splice(index, 1);
setSavedSelections(newSelections);
};
return (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-200">
<h2 className="text-2xl font-bold mb-4 text-indigo-800 dark:text-indigo-200">/</h2>
<div className="space-y-4">
<div className="flex space-x-2">
<input
type="text"
value={selectionName}
onChange={(e) => setSelectionName(e.target.value)}
placeholder="输入保存名称"
className="flex-grow border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
<button
onClick={saveSelection}
className="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors"
>
</button>
</div>
<div className="space-y-2">
{savedSelections.map((selection, index) => (
<div key={index} className="flex justify-between items-center">
<span className="text-gray-700 dark:text-gray-300">{selection.name}</span>
<div>
<button
onClick={() => loadSelection(selection)}
className="bg-green-500 text-white px-3 py-1 rounded-md hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-colors mr-2"
>
</button>
<button
onClick={() => deleteSelection(index)}
className="bg-red-500 text-white px-3 py-1 rounded-md hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-colors"
>
</button>
</div>
</div>
))}
</div>
</div>
</div>
)
}

12
data/本站工具.yml Normal file
View File

@ -0,0 +1,12 @@
- name: 图片压缩
url: /tools/Compressor
description: 简洁的图片压缩工具
subcategory: 其它
- name: 哈希随机抽取
url: /tools/hash-random
description: 逐一比较每个参与者的哈希值与种子的哈希值,通过计算它们之间的相似度,找出与种子值最接近的参与者
subcategory: 其它
- name: 扩展昵称生成器
url: /tools/CatgirlGenerator
description: 当别人@你时,会在@的文字后面加上一另段文字
subcategory: 其它

View File

@ -0,0 +1,53 @@
import { useState, useEffect } from 'react'
import md5 from 'md5'
function calcMd5Distance(s1: string, s2: string) {
let distance = 0
for (let i = 0; i < Math.min(s1.length, s2.length); i++) {
if (s1[i] === s2[i]) {
continue
}
distance += Math.abs(s1.charCodeAt(i) - s2.charCodeAt(i))
}
return distance
}
function makeSortedNameHashDistanceTuple(targetHash: string, names: string[]) {
let nameHashDistanceTuple = names.map((name) => {
let nameMd5 = md5(name)
let distance = calcMd5Distance(targetHash, nameMd5)
return [name, nameMd5, distance] as [string, string, number]
})
// sort by distance
nameHashDistanceTuple.sort((a, b) => {
return a[2] - b[2]
})
return nameHashDistanceTuple
}
export function useHashCalculation(salt: string, participants: string[], count: number) {
const [sortedResults, setSortedResults] = useState<[string, string, number][]>([])
const [saltHash, setSaltHash] = useState('')
const [isCalculating, setIsCalculating] = useState(false)
useEffect(() => {
if (salt && participants.length > 0) {
setIsCalculating(true)
const timer = setTimeout(() => {
const newSaltHash = md5(salt)
setSaltHash(newSaltHash)
const newSortedResults = makeSortedNameHashDistanceTuple(newSaltHash, participants)
setSortedResults(newSortedResults)
setIsCalculating(false)
}, 1000) // Simulate a delay for the calculation
return () => clearTimeout(timer)
} else {
setSortedResults([])
setSaltHash('')
setIsCalculating(false)
}
}, [salt, participants])
return { sortedResults, saltHash, isCalculating }
}

View File

@ -0,0 +1,38 @@
import { useState } from 'react'
import imageCompression from 'browser-image-compression'
export default function useImageCompression() {
const [compressedImage, setCompressedImage] = useState<File | null>(null)
const [isCompressing, setIsCompressing] = useState(false)
const [progress, setProgress] = useState(0)
const [error, setError] = useState<string | null>(null)
const compressImage = async (file: File, format: string) => {
setIsCompressing(true)
setProgress(0)
setError(null)
try {
const options = {
maxSizeMB: 1,
maxWidthOrHeight: 1920,
useWebWorker: true,
fileType: `image/${format}`,
onProgress: (progress: number) => {
setProgress(Math.round(progress))
},
}
const compressedFile = await imageCompression(file, options)
setCompressedImage(compressedFile)
} catch (err) {
setError('压缩过程中出错,请重试。')
console.error(err)
} finally {
setIsCompressing(false)
}
}
return { compressImage, compressedImage, isCompressing, progress, error }
}

440
package-lock.json generated
View File

@ -11,23 +11,31 @@
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-scroll-area": "^1.2.1",
"@radix-ui/react-slot": "^1.1.0",
"browser-image-compression": "^2.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"framer-motion": "^11.15.0",
"js-yaml": "^4.1.0",
"lucide-react": "^0.460.0",
"md5": "^2.3.0",
"next": "14.2.16",
"next-auth": "^4.24.10",
"next-themes": "^0.4.4",
"react": "^18",
"react-dom": "^18",
"react-dropzone": "^14.3.5",
"react-syntax-highlighter": "^15.6.1",
"react-toastify": "^10.0.6",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/md5": "^2.3.5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-syntax-highlighter": "^15.5.13",
"eslint": "^8",
"eslint-config-next": "14.2.16",
"postcss": "^8",
@ -712,6 +720,15 @@
"tslib": "^2.4.0"
}
},
"node_modules/@types/hast": {
"version": "2.3.10",
"resolved": "https://registry.npmmirror.com/@types/hast/-/hast-2.3.10.tgz",
"integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==",
"license": "MIT",
"dependencies": {
"@types/unist": "^2"
}
},
"node_modules/@types/js-yaml": {
"version": "4.0.9",
"resolved": "https://registry.npmmirror.com/@types/js-yaml/-/js-yaml-4.0.9.tgz",
@ -726,6 +743,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/md5": {
"version": "2.3.5",
"resolved": "https://registry.npmmirror.com/@types/md5/-/md5-2.3.5.tgz",
"integrity": "sha512-/i42wjYNgE6wf0j2bcTX6kuowmdL/6PE4IVitMpm2eYKBUuYCprdcWVK+xEF0gcV6ufMCRhtxmReGfc6hIK7Jw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.17.6",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-20.17.6.tgz",
@ -764,6 +788,22 @@
"@types/react": "*"
}
},
"node_modules/@types/react-syntax-highlighter": {
"version": "15.5.13",
"resolved": "https://registry.npmmirror.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
"integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/unist": {
"version": "2.0.11",
"resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz",
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.18.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",
@ -1270,6 +1310,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/attr-accept": {
"version": "2.2.5",
"resolved": "https://registry.npmmirror.com/attr-accept/-/attr-accept-2.2.5.tgz",
"integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@ -1347,6 +1396,15 @@
"node": ">=8"
}
},
"node_modules/browser-image-compression": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/browser-image-compression/-/browser-image-compression-2.0.2.tgz",
"integrity": "sha512-pBLlQyUf6yB8SmmngrcOw3EoS4RpQ1BcylI3T9Yqn7+4nrQTXJD4sJDe5ODnJdrvNMaio5OicFo75rDyJD2Ucw==",
"license": "MIT",
"dependencies": {
"uzip": "0.20201231.0"
}
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmmirror.com/busboy/-/busboy-1.6.0.tgz",
@ -1434,6 +1492,45 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/character-entities": {
"version": "1.2.4",
"resolved": "https://registry.npmmirror.com/character-entities/-/character-entities-1.2.4.tgz",
"integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/character-entities-legacy": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
"integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/character-reference-invalid": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmmirror.com/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
"license": "BSD-3-Clause",
"engines": {
"node": "*"
}
},
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
@ -1524,6 +1621,16 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/comma-separated-tokens": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz",
"integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz",
@ -1563,6 +1670,15 @@
"node": ">= 8"
}
},
"node_modules/crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmmirror.com/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
"license": "BSD-3-Clause",
"engines": {
"node": "*"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz",
@ -2455,6 +2571,19 @@
"reusify": "^1.0.4"
}
},
"node_modules/fault": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/fault/-/fault-1.0.4.tgz",
"integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
"license": "MIT",
"dependencies": {
"format": "^0.2.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@ -2468,6 +2597,18 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/file-selector": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/file-selector/-/file-selector-2.1.2.tgz",
"integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==",
"license": "MIT",
"dependencies": {
"tslib": "^2.7.0"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
@ -2545,6 +2686,41 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/format": {
"version": "0.2.2",
"resolved": "https://registry.npmmirror.com/format/-/format-0.2.2.tgz",
"integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
"engines": {
"node": ">=0.4.x"
}
},
"node_modules/framer-motion": {
"version": "11.15.0",
"resolved": "https://registry.npmmirror.com/framer-motion/-/framer-motion-11.15.0.tgz",
"integrity": "sha512-MLk8IvZntxOMg7lDBLw2qgTHHv664bYoYmnFTmE0Gm/FW67aOJk0WM3ctMcG+Xhcv+vh5uyyXwxvxhSeJzSe+w==",
"license": "MIT",
"dependencies": {
"motion-dom": "^11.14.3",
"motion-utils": "^11.14.3",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -2880,6 +3056,48 @@
"node": ">= 0.4"
}
},
"node_modules/hast-util-parse-selector": {
"version": "2.2.5",
"resolved": "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
"integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hastscript": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/hastscript/-/hastscript-6.0.0.tgz",
"integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==",
"license": "MIT",
"dependencies": {
"@types/hast": "^2.0.0",
"comma-separated-tokens": "^1.0.0",
"hast-util-parse-selector": "^2.0.0",
"property-information": "^5.0.0",
"space-separated-tokens": "^1.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-10.7.3.tgz",
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
"license": "BSD-3-Clause",
"engines": {
"node": "*"
}
},
"node_modules/highlightjs-vue": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz",
"integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==",
"license": "CC0-1.0"
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz",
@ -2951,6 +3169,30 @@
"node": ">= 0.4"
}
},
"node_modules/is-alphabetical": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
"integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-alphanumerical": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
"integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
"license": "MIT",
"dependencies": {
"is-alphabetical": "^1.0.0",
"is-decimal": "^1.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-array-buffer": {
"version": "3.0.4",
"resolved": "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
@ -3026,6 +3268,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"license": "MIT"
},
"node_modules/is-bun-module": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/is-bun-module/-/is-bun-module-1.2.1.tgz",
@ -3096,6 +3344,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-decimal": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/is-decimal/-/is-decimal-1.0.4.tgz",
"integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
@ -3155,6 +3413,16 @@
"node": ">=0.10.0"
}
},
"node_modules/is-hexadecimal": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-map": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/is-map/-/is-map-2.0.3.tgz",
@ -3581,6 +3849,20 @@
"loose-envify": "cli.js"
}
},
"node_modules/lowlight": {
"version": "1.20.0",
"resolved": "https://registry.npmmirror.com/lowlight/-/lowlight-1.20.0.tgz",
"integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
"license": "MIT",
"dependencies": {
"fault": "^1.0.0",
"highlight.js": "~10.7.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz",
@ -3596,6 +3878,17 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
}
},
"node_modules/md5": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/md5/-/md5-2.3.0.tgz",
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
"license": "BSD-3-Clause",
"dependencies": {
"charenc": "0.0.2",
"crypt": "0.0.2",
"is-buffer": "~1.1.6"
}
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz",
@ -3650,6 +3943,18 @@
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/motion-dom": {
"version": "11.14.3",
"resolved": "https://registry.npmmirror.com/motion-dom/-/motion-dom-11.14.3.tgz",
"integrity": "sha512-lW+D2wBy5vxLJi6aCP0xyxTxlTfiu+b+zcpVbGVFUxotwThqhdpPRSmX8xztAgtZMPMeU0WGVn/k1w4I+TbPqA==",
"license": "MIT"
},
"node_modules/motion-utils": {
"version": "11.14.3",
"resolved": "https://registry.npmmirror.com/motion-utils/-/motion-utils-11.14.3.tgz",
"integrity": "sha512-Xg+8xnqIJTpr0L/cidfTTBFkvRw26ZtGGuIhA94J9PQ2p4mEa06Xx7QVYZH0BP+EpMSaDlu+q0I0mmvwADPsaQ==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
@ -3775,6 +4080,16 @@
}
}
},
"node_modules/next-themes": {
"version": "0.4.4",
"resolved": "https://registry.npmmirror.com/next-themes/-/next-themes-0.4.4.tgz",
"integrity": "sha512-LDQ2qIOJF0VnuVrrMSMLrWGjRMkq+0mpgl6e0juCLqdJ+oo8Q84JRWT6Wh11VDQKkMMe+dVzDKLWs5n87T+PkQ==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.31.tgz",
@ -4063,6 +4378,24 @@
"node": ">=6"
}
},
"node_modules/parse-entities": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/parse-entities/-/parse-entities-2.0.0.tgz",
"integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
"license": "MIT",
"dependencies": {
"character-entities": "^1.0.0",
"character-entities-legacy": "^1.0.0",
"character-reference-invalid": "^1.0.0",
"is-alphanumerical": "^1.0.0",
"is-decimal": "^1.0.0",
"is-hexadecimal": "^1.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
@ -4363,11 +4696,19 @@
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==",
"license": "MIT"
},
"node_modules/prismjs": {
"version": "1.29.0",
"resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.29.0.tgz",
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
@ -4375,6 +4716,19 @@
"react-is": "^16.13.1"
}
},
"node_modules/property-information": {
"version": "5.6.0",
"resolved": "https://registry.npmmirror.com/property-information/-/property-information-5.6.0.tgz",
"integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==",
"license": "MIT",
"dependencies": {
"xtend": "^4.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz",
@ -4430,13 +4784,46 @@
"react": "^18.3.1"
}
},
"node_modules/react-dropzone": {
"version": "14.3.5",
"resolved": "https://registry.npmmirror.com/react-dropzone/-/react-dropzone-14.3.5.tgz",
"integrity": "sha512-9nDUaEEpqZLOz5v5SUcFA0CjM4vq8YbqO0WRls+EYT7+DvxUdzDPKNCPLqGfj3YL9MsniCLCD4RFA6M95V6KMQ==",
"license": "MIT",
"dependencies": {
"attr-accept": "^2.2.4",
"file-selector": "^2.1.0",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">= 10.13"
},
"peerDependencies": {
"react": ">= 16.8 || 18.0.0"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true,
"license": "MIT"
},
"node_modules/react-syntax-highlighter": {
"version": "15.6.1",
"resolved": "https://registry.npmmirror.com/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz",
"integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.3.1",
"highlight.js": "^10.4.1",
"highlightjs-vue": "^1.0.0",
"lowlight": "^1.17.0",
"prismjs": "^1.27.0",
"refractor": "^3.6.0"
},
"peerDependencies": {
"react": ">= 0.14.0"
}
},
"node_modules/react-toastify": {
"version": "10.0.6",
"resolved": "https://registry.npmmirror.com/react-toastify/-/react-toastify-10.0.6.tgz",
@ -4493,6 +4880,30 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/refractor": {
"version": "3.6.0",
"resolved": "https://registry.npmmirror.com/refractor/-/refractor-3.6.0.tgz",
"integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==",
"license": "MIT",
"dependencies": {
"hastscript": "^6.0.0",
"parse-entities": "^2.0.0",
"prismjs": "~1.27.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/refractor/node_modules/prismjs": {
"version": "1.27.0",
"resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.27.0.tgz",
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
@ -4791,6 +5202,16 @@
"node": ">=0.10.0"
}
},
"node_modules/space-separated-tokens": {
"version": "1.1.5",
"resolved": "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz",
"integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/streamsearch/-/streamsearch-1.1.0.tgz",
@ -5396,6 +5817,12 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/uzip": {
"version": "0.20201231.0",
"resolved": "https://registry.npmmirror.com/uzip/-/uzip-0.20201231.0.tgz",
"integrity": "sha512-OZeJfZP+R0z9D6TmBgLq2LHzSSptGMGDGigGiEe0pr8UBe/7fdflgHlHBNDASTXB5jnFuxHpNaJywSg8YFeGng==",
"license": "MIT"
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
@ -5605,6 +6032,15 @@
"dev": true,
"license": "ISC"
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"license": "MIT",
"engines": {
"node": ">=0.4"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",

View File

@ -12,23 +12,31 @@
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-scroll-area": "^1.2.1",
"@radix-ui/react-slot": "^1.1.0",
"browser-image-compression": "^2.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"framer-motion": "^11.15.0",
"js-yaml": "^4.1.0",
"lucide-react": "^0.460.0",
"md5": "^2.3.0",
"next": "14.2.16",
"next-auth": "^4.24.10",
"next-themes": "^0.4.4",
"react": "^18",
"react-dom": "^18",
"react-dropzone": "^14.3.5",
"react-syntax-highlighter": "^15.6.1",
"react-toastify": "^10.0.6",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/md5": "^2.3.5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-syntax-highlighter": "^15.5.13",
"eslint": "^8",
"eslint-config-next": "14.2.16",
"postcss": "^8",

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -9,6 +9,14 @@ const config: Config = {
],
theme: {
extend: {
backgroundColor: {
'dark-primary': '#1a202c',
'dark-secondary': '#2d3748',
},
textColor: {
'dark-primary': '#e2e8f0',
'dark-secondary': '#a0aec0',
},
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',