ui: AMOLED theme across all 30 tools, blue accent, squared edges

This commit is contained in:
katR 2026-08-31 23:25:25 +05:30
parent 634a2cddcd
commit 35d84857e3
35 changed files with 1645 additions and 1649 deletions

View file

@ -3,7 +3,9 @@
:root {
--background: #000000;
--foreground: #e5e5e5;
--accent: #10b981;
--accent: #60a5fa;
--accent-hover: #3b82f6;
--accent-dim: #1e3a5f;
--surface: #0a0a0a;
--surface-hover: #111111;
--border: #1a1a1a;
@ -26,12 +28,6 @@ body {
}
/* Dot pattern background */
.dot-bg {
background-image: radial-gradient(circle, rgba(255, 255, 255, 0.035) 1px, transparent 1px);
background-size: 20px 20px;
}
/* Fade the dots at top and bottom */
.dot-bg-fade {
position: relative;
}

View file

@ -9,7 +9,7 @@ export default function Home() {
{Object.entries(toolsByCategory).map(([category, categoryTools]) => (
<section key={category} className="mb-8">
<div className="flex items-center gap-2 mb-3">
<div className="h-px w-4 bg-emerald-900"></div>
<div className="h-px w-4 bg-blue-900"></div>
<h2 className="text-xs font-medium uppercase tracking-widest text-[#525252]">
{category}
</h2>

View file

@ -161,12 +161,12 @@ export default function Base64Page() {
<div className="space-y-6">
{/* Controls Bar */}
<div className="flex flex-wrap items-center justify-between gap-3 pb-2 border-b border-gray-200 dark:border-gray-700">
<div className="flex flex-wrap items-center justify-between gap-3 pb-2 border-b border-[#1a1a1a]">
<div className="flex flex-wrap items-center gap-2">
<button
type="button"
onClick={handleEncode}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 active:bg-blue-800 text-white rounded-lg font-medium text-sm transition-colors shadow-sm cursor-pointer flex items-center gap-1.5"
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 active:bg-blue-800 text-white font-medium text-sm transition-colors cursor-pointer flex items-center gap-1.5"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" />
@ -176,7 +176,7 @@ export default function Base64Page() {
<button
type="button"
onClick={handleDecode}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800 text-white rounded-lg font-medium text-sm transition-colors shadow-sm cursor-pointer flex items-center gap-1.5"
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800 text-white font-medium text-sm transition-colors cursor-pointer flex items-center gap-1.5"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
@ -187,7 +187,7 @@ export default function Base64Page() {
type="button"
onClick={handleSwap}
disabled={!output && !input}
className="px-3 py-2 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed text-gray-700 dark:text-gray-200 rounded-lg text-sm font-medium transition-colors cursor-pointer flex items-center gap-1"
className="px-3 py-2 bg-[#111] hover:bg-[#1a1a1a] disabled:opacity-40 disabled:cursor-not-allowed text-gray-400 text-sm font-medium transition-colors cursor-pointer flex items-center gap-1"
title="Swap input and output"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -199,7 +199,7 @@ export default function Base64Page() {
type="button"
onClick={handleClear}
disabled={!input && !output}
className="px-3 py-2 bg-gray-100 dark:bg-gray-800 hover:bg-red-50 dark:hover:bg-red-950/30 text-gray-700 dark:text-gray-300 hover:text-red-600 dark:hover:text-red-400 disabled:opacity-40 disabled:cursor-not-allowed rounded-lg text-sm font-medium transition-colors cursor-pointer"
className="px-3 py-2 bg-[#111] hover:bg-[#1a0a0a] text-gray-400 hover:text-red-400 disabled:opacity-40 disabled:cursor-not-allowed text-sm font-medium transition-colors cursor-pointer"
>
Clear
</button>
@ -207,25 +207,25 @@ export default function Base64Page() {
{/* Sample buttons */}
<div className="flex items-center gap-2 text-xs">
<span className="text-gray-500 dark:text-gray-400 font-medium">Samples:</span>
<span className="text-gray-500 font-medium">Samples:</span>
<button
type="button"
onClick={() => loadSample("text")}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 rounded transition-colors cursor-pointer"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] text-gray-400 transition-colors cursor-pointer"
>
Plain Text
</button>
<button
type="button"
onClick={() => loadSample("unicode")}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 rounded transition-colors cursor-pointer"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] text-gray-400 transition-colors cursor-pointer"
>
Unicode / Emojis
</button>
<button
type="button"
onClick={() => loadSample("base64")}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 rounded transition-colors cursor-pointer"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] text-gray-400 transition-colors cursor-pointer"
>
Base64
</button>
@ -233,7 +233,7 @@ export default function Base64Page() {
</div>
{/* Options */}
<div className="flex flex-wrap items-center gap-4 text-sm text-gray-700 dark:text-gray-300">
<div className="flex flex-wrap items-center gap-4 text-sm text-gray-400">
<label className="inline-flex items-center gap-2 cursor-pointer select-none">
<input
type="checkbox"
@ -245,9 +245,9 @@ export default function Base64Page() {
encodeBase64(input, val, splitLines);
}
}}
className="w-4 h-4 text-blue-600 rounded border-gray-300 dark:border-gray-600 focus:ring-blue-500 dark:bg-gray-700"
className="w-4 h-4 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>URL-safe Base64 <code className="text-xs bg-gray-100 dark:bg-gray-800 px-1 py-0.5 rounded">(-_ instead of +/)</code></span>
<span>URL-safe Base64 <code className="text-xs bg-[#111] px-1 py-0.5 rounded">(-_ instead of +/)</code></span>
</label>
<label className="inline-flex items-center gap-2 cursor-pointer select-none">
<input
@ -260,7 +260,7 @@ export default function Base64Page() {
encodeBase64(input, urlSafe, val);
}
}}
className="w-4 h-4 text-blue-600 rounded border-gray-300 dark:border-gray-600 focus:ring-blue-500 dark:bg-gray-700"
className="w-4 h-4 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>Wrap lines at 76 characters (MIME)</span>
</label>
@ -268,7 +268,7 @@ export default function Base64Page() {
{/* Error Notification */}
{error && (
<div className="flex items-start gap-3 p-4 bg-red-50 dark:bg-red-950/40 border border-red-200 dark:border-red-800 rounded-lg text-red-800 dark:text-red-300 text-sm">
<div className="flex items-start gap-3 p-4 bg-[#1a0a0a] border border-red-900 text-red-400 text-sm">
<svg className="w-5 h-5 text-red-500 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
@ -284,10 +284,10 @@ export default function Base64Page() {
{/* Input Area */}
<div className="flex flex-col space-y-2">
<div className="flex items-center justify-between">
<label htmlFor={inputId} className="font-medium text-sm text-gray-700 dark:text-gray-300">
<label htmlFor={inputId} className="font-medium text-sm text-gray-400">
Input String
</label>
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
<span className="text-xs text-gray-500 font-mono">
{input.length} chars | {inputBytes} bytes
</span>
</div>
@ -300,7 +300,7 @@ export default function Base64Page() {
}}
placeholder="Enter or paste text to encode, or Base64 string to decode..."
rows={12}
className="w-full p-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none resize-y placeholder:text-gray-400 dark:placeholder:text-gray-500 leading-relaxed shadow-inner"
className="w-full p-3 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-mono text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none resize-y placeholder:text-gray-400 leading-relaxed shadow-inner"
/>
</div>
@ -308,31 +308,31 @@ export default function Base64Page() {
<div className="flex flex-col space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<label htmlFor={outputId} className="font-medium text-sm text-gray-700 dark:text-gray-300">
<label htmlFor={outputId} className="font-medium text-sm text-gray-400">
Output Result
</label>
{lastAction && (
<span className="px-2 py-0.5 text-xs rounded-full font-medium bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300">
<span className="px-2 py-0.5 text-xs rounded-none font-medium bg-[#0a0a1a] text-blue-400">
{lastAction === "encode" ? "Base64 Encoded" : "Decoded Text"}
</span>
)}
</div>
<div className="flex items-center gap-3">
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
<span className="text-xs text-gray-500 font-mono">
{output.length} chars | {outputBytes} bytes
</span>
<button
type="button"
onClick={handleCopy}
disabled={!output}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300 rounded text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] disabled:opacity-40 disabled:cursor-not-allowed text-gray-400 text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
>
{copied ? (
<>
<svg className="w-3.5 h-3.5 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="w-3.5 h-3.5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
<span className="text-green-600 dark:text-green-400 font-semibold">Copied!</span>
<span className="text-green-400 font-semibold">Copied!</span>
</>
) : (
<>
@ -351,23 +351,23 @@ export default function Base64Page() {
readOnly
placeholder="Encoded or decoded output will appear here..."
rows={12}
className="w-full p-3 rounded-lg border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-900/60 text-gray-900 dark:text-gray-100 font-mono text-sm focus:outline-none resize-y placeholder:text-gray-400 dark:placeholder:text-gray-500 leading-relaxed select-all"
className="w-full p-3 border border-[#1a1a1a] bg-black text-gray-200 font-mono text-sm focus:outline-none resize-y placeholder:text-gray-400 leading-relaxed select-all"
/>
</div>
</div>
{/* Information & Feature Highlights */}
<div className="mt-8 pt-6 border-t border-gray-200 dark:border-gray-800 grid grid-cols-1 md:grid-cols-3 gap-4 text-xs text-gray-600 dark:text-gray-400">
<div className="p-3 bg-gray-50 dark:bg-gray-800/40 rounded-lg border border-gray-200 dark:border-gray-700/60">
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-1">Full UTF-8 Unicode Support</h4>
<div className="mt-8 pt-6 border-t border-[#1a1a1a] grid grid-cols-1 md:grid-cols-3 gap-4 text-xs text-gray-600">
<div className="p-3 bg-black border border-[#1a1a1a]">
<h4 className="font-semibold text-gray-300 mb-1">Full UTF-8 Unicode Support</h4>
<p>Handles international character sets, special symbols, and emojis accurately without Latin1 byte truncation errors.</p>
</div>
<div className="p-3 bg-gray-50 dark:bg-gray-800/40 rounded-lg border border-gray-200 dark:border-gray-700/60">
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-1">100% Client-Side &amp; Private</h4>
<div className="p-3 bg-black border border-[#1a1a1a]">
<h4 className="font-semibold text-gray-300 mb-1">100% Client-Side &amp; Private</h4>
<p>Your data never leaves your browser. All encoding and decoding operations execute locally in JavaScript.</p>
</div>
<div className="p-3 bg-gray-50 dark:bg-gray-800/40 rounded-lg border border-gray-200 dark:border-gray-700/60">
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-1">URL-Safe &amp; MIME Formatting</h4>
<div className="p-3 bg-black border border-[#1a1a1a]">
<h4 className="font-semibold text-gray-300 mb-1">URL-Safe &amp; MIME Formatting</h4>
<p>Convert standard Base64 characters into URL-friendly variants or wrap output lines for MIME/email standards.</p>
</div>
</div>

View file

@ -218,21 +218,21 @@ export default function BoxShadowGeneratorPage() {
{/* Live Preview Area */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500">
Live Preview
</h2>
<div className="flex items-center gap-2">
<span className="text-xs font-medium text-gray-500 dark:text-gray-400 hidden sm:inline">
<span className="text-xs font-medium text-gray-500 hidden sm:inline">
Background:
</span>
<div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-800 p-0.5 rounded-lg text-xs">
<div className="flex items-center gap-1 bg-[#111] p-0.5 text-xs">
<button
type="button"
onClick={() => setPreviewBg("light")}
className={`px-2.5 py-1 rounded-md transition-colors ${
className={`px-2.5 py-1 transition-colors ${
previewBg === "light"
? "bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-medium shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900"
? "bg-[#0a0a0a] text-gray-200 font-medium shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Light
@ -240,10 +240,10 @@ export default function BoxShadowGeneratorPage() {
<button
type="button"
onClick={() => setPreviewBg("dark")}
className={`px-2.5 py-1 rounded-md transition-colors ${
className={`px-2.5 py-1 transition-colors ${
previewBg === "dark"
? "bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-medium shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900"
? "bg-[#0a0a0a] text-gray-200 font-medium shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Dark
@ -251,10 +251,10 @@ export default function BoxShadowGeneratorPage() {
<button
type="button"
onClick={() => setPreviewBg("checker")}
className={`px-2.5 py-1 rounded-md transition-colors ${
className={`px-2.5 py-1 transition-colors ${
previewBg === "checker"
? "bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-medium shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900"
? "bg-[#0a0a0a] text-gray-200 font-medium shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Pattern
@ -264,12 +264,12 @@ export default function BoxShadowGeneratorPage() {
</div>
<div
className={`relative w-full h-72 sm:h-80 rounded-2xl border border-gray-200 dark:border-gray-700 flex items-center justify-center p-6 overflow-hidden transition-all ${
className={`relative w-full h-72 sm:h-80 border border-[#1a1a1a] flex items-center justify-center p-6 overflow-hidden transition-all ${
previewBg === "light"
? "bg-gray-100"
? "bg-[#111]"
: previewBg === "dark"
? "bg-gray-950"
: "bg-[radial-gradient(#cbd5e1_1px,transparent_1px)] dark:bg-[radial-gradient(#334155_1px,transparent_1px)] [background-size:16px_16px] bg-gray-100 dark:bg-gray-900"
: "bg-[radial-gradient(#cbd5e1_1px,transparent_1px)](#334155_1px,transparent_1px)] [background-size:16px_16px] bg-[#111]"
}`}
>
{/* The Shadow Card */}
@ -281,7 +281,7 @@ export default function BoxShadowGeneratorPage() {
boxShadow: shadowValue,
}}
>
<div className="w-10 h-10 rounded-full bg-blue-50 dark:bg-blue-900/30 flex items-center justify-center text-blue-600 dark:text-blue-400 mb-2">
<div className="w-10 h-10 rounded-none bg-[#0a0a1a] flex items-center justify-center text-blue-600 mb-2">
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
@ -291,7 +291,7 @@ export default function BoxShadowGeneratorPage() {
/>
</svg>
</div>
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-800">
<h3 className="text-sm font-semibold text-gray-200">
Shadow Preview
</h3>
<p className="text-[11px] font-mono text-gray-500 mt-1">
@ -304,13 +304,13 @@ export default function BoxShadowGeneratorPage() {
{/* Presets Gallery */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500">
Quick Presets
</h2>
<button
type="button"
onClick={resetDefaults}
className="text-xs text-blue-600 dark:text-blue-400 hover:underline font-medium cursor-pointer"
className="text-xs text-blue-600 hover:underline font-medium cursor-pointer"
>
Reset to Default
</button>
@ -326,13 +326,13 @@ export default function BoxShadowGeneratorPage() {
key={preset.name}
type="button"
onClick={() => applyPreset(preset)}
className="group flex flex-col items-center justify-center p-3 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:border-blue-500 dark:hover:border-blue-500 transition-all hover:scale-[1.03]"
className="group flex flex-col items-center justify-center p-3 border border-[#1a1a1a] bg-[#0a0a0a] hover:border-blue-500 transition-all hover:scale-[1.03]"
>
<div
className="w-10 h-8 rounded-md bg-white border border-gray-100 transition-all mb-2"
className="w-10 h-8 bg-[#0a0a0a] border border-gray-100 transition-all mb-2"
style={{ boxShadow: pShadow }}
/>
<span className="text-[11px] font-medium text-gray-700 dark:text-gray-300 text-center w-full truncate">
<span className="text-[11px] font-medium text-gray-400 text-center w-full truncate">
{preset.name}
</span>
</button>
@ -344,8 +344,8 @@ export default function BoxShadowGeneratorPage() {
{/* Shadow Control Sliders Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Controls Column 1: Dimensions & Geometry */}
<div className="space-y-5 p-5 rounded-xl border border-gray-200 dark:border-gray-700/80 bg-gray-50/50 dark:bg-gray-800/50">
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2">
<div className="space-y-5 p-5 border border-[#1a1a1a] bg-black/50">
<h3 className="text-sm font-semibold text-gray-200 flex items-center gap-2">
<svg className="w-4 h-4 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16m-7 6h7" />
</svg>
@ -355,7 +355,7 @@ export default function BoxShadowGeneratorPage() {
{/* Horizontal Offset */}
<div className="space-y-1.5">
<div className="flex justify-between items-center">
<label className="text-xs font-medium text-gray-700 dark:text-gray-300">
<label className="text-xs font-medium text-gray-400">
Horizontal Offset (X)
</label>
<div className="flex items-center gap-1">
@ -365,9 +365,9 @@ export default function BoxShadowGeneratorPage() {
max="50"
value={horizontal}
onChange={(e) => setHorizontal(Number(e.target.value))}
className="w-16 px-1.5 py-0.5 text-xs font-mono text-right rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-16 px-1.5 py-0.5 text-xs font-mono text-right border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">px</span>
<span className="text-xs text-gray-500 font-mono">px</span>
</div>
</div>
<input
@ -376,7 +376,7 @@ export default function BoxShadowGeneratorPage() {
max="50"
value={horizontal}
onChange={(e) => setHorizontal(Number(e.target.value))}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 accent-blue-600"
className="w-full h-2 bg-[#1a1a1a] appearance-none cursor-pointer accent-blue-600"
/>
<div className="flex justify-between text-[10px] text-gray-400 font-mono">
<span>-50px</span>
@ -388,7 +388,7 @@ export default function BoxShadowGeneratorPage() {
{/* Vertical Offset */}
<div className="space-y-1.5">
<div className="flex justify-between items-center">
<label className="text-xs font-medium text-gray-700 dark:text-gray-300">
<label className="text-xs font-medium text-gray-400">
Vertical Offset (Y)
</label>
<div className="flex items-center gap-1">
@ -398,9 +398,9 @@ export default function BoxShadowGeneratorPage() {
max="50"
value={vertical}
onChange={(e) => setVertical(Number(e.target.value))}
className="w-16 px-1.5 py-0.5 text-xs font-mono text-right rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-16 px-1.5 py-0.5 text-xs font-mono text-right border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">px</span>
<span className="text-xs text-gray-500 font-mono">px</span>
</div>
</div>
<input
@ -409,7 +409,7 @@ export default function BoxShadowGeneratorPage() {
max="50"
value={vertical}
onChange={(e) => setVertical(Number(e.target.value))}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 accent-blue-600"
className="w-full h-2 bg-[#1a1a1a] appearance-none cursor-pointer accent-blue-600"
/>
<div className="flex justify-between text-[10px] text-gray-400 font-mono">
<span>-50px</span>
@ -421,7 +421,7 @@ export default function BoxShadowGeneratorPage() {
{/* Blur Radius */}
<div className="space-y-1.5">
<div className="flex justify-between items-center">
<label className="text-xs font-medium text-gray-700 dark:text-gray-300">
<label className="text-xs font-medium text-gray-400">
Blur Radius
</label>
<div className="flex items-center gap-1">
@ -431,9 +431,9 @@ export default function BoxShadowGeneratorPage() {
max="100"
value={blur}
onChange={(e) => setBlur(Math.max(0, Number(e.target.value)))}
className="w-16 px-1.5 py-0.5 text-xs font-mono text-right rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-16 px-1.5 py-0.5 text-xs font-mono text-right border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">px</span>
<span className="text-xs text-gray-500 font-mono">px</span>
</div>
</div>
<input
@ -442,7 +442,7 @@ export default function BoxShadowGeneratorPage() {
max="100"
value={blur}
onChange={(e) => setBlur(Number(e.target.value))}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 accent-blue-600"
className="w-full h-2 bg-[#1a1a1a] appearance-none cursor-pointer accent-blue-600"
/>
<div className="flex justify-between text-[10px] text-gray-400 font-mono">
<span>0px</span>
@ -454,7 +454,7 @@ export default function BoxShadowGeneratorPage() {
{/* Spread Radius */}
<div className="space-y-1.5">
<div className="flex justify-between items-center">
<label className="text-xs font-medium text-gray-700 dark:text-gray-300">
<label className="text-xs font-medium text-gray-400">
Spread Radius
</label>
<div className="flex items-center gap-1">
@ -464,9 +464,9 @@ export default function BoxShadowGeneratorPage() {
max="50"
value={spread}
onChange={(e) => setSpread(Number(e.target.value))}
className="w-16 px-1.5 py-0.5 text-xs font-mono text-right rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-16 px-1.5 py-0.5 text-xs font-mono text-right border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">px</span>
<span className="text-xs text-gray-500 font-mono">px</span>
</div>
</div>
<input
@ -475,7 +475,7 @@ export default function BoxShadowGeneratorPage() {
max="50"
value={spread}
onChange={(e) => setSpread(Number(e.target.value))}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 accent-blue-600"
className="w-full h-2 bg-[#1a1a1a] appearance-none cursor-pointer accent-blue-600"
/>
<div className="flex justify-between text-[10px] text-gray-400 font-mono">
<span>-50px</span>
@ -486,8 +486,8 @@ export default function BoxShadowGeneratorPage() {
</div>
{/* Controls Column 2: Color, Opacity & Box Appearance */}
<div className="space-y-5 p-5 rounded-xl border border-gray-200 dark:border-gray-700/80 bg-gray-50/50 dark:bg-gray-800/50">
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2">
<div className="space-y-5 p-5 border border-[#1a1a1a] bg-black/50">
<h3 className="text-sm font-semibold text-gray-200 flex items-center gap-2">
<svg className="w-4 h-4 text-purple-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
@ -502,11 +502,11 @@ export default function BoxShadowGeneratorPage() {
{/* Shadow Color & Inset Toggle */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-1.5">
<label className="text-xs font-medium text-gray-700 dark:text-gray-300">
<label className="text-xs font-medium text-gray-400">
Shadow Color
</label>
<div className="flex items-center gap-2">
<div className="relative flex-shrink-0 w-9 h-9 rounded-lg border border-gray-300 dark:border-gray-600 overflow-hidden shadow-inner cursor-pointer">
<div className="relative flex-shrink-0 w-9 h-9 border border-[#1a1a1a] overflow-hidden shadow-inner cursor-pointer">
<input
type="color"
value={color}
@ -518,24 +518,24 @@ export default function BoxShadowGeneratorPage() {
type="text"
value={color}
onChange={(e) => setColor(e.target.value)}
className="w-full px-2.5 py-1.5 text-xs font-mono rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none uppercase"
className="w-full px-2.5 py-1.5 text-xs font-mono border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none uppercase"
/>
</div>
</div>
{/* Inset Toggle Checkbox */}
<div className="flex flex-col justify-center space-y-1.5 pt-1">
<label className="text-xs font-medium text-gray-700 dark:text-gray-300">
<label className="text-xs font-medium text-gray-400">
Shadow Type
</label>
<label className="flex items-center gap-2.5 p-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 cursor-pointer hover:border-blue-500 transition-colors">
<label className="flex items-center gap-2.5 p-2 border border-[#1a1a1a] bg-[#0a0a0a] cursor-pointer hover:border-blue-500 transition-colors">
<input
type="checkbox"
checked={inset}
onChange={(e) => setInset(e.target.checked)}
className="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
className="w-4 h-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span className="text-xs font-medium text-gray-800 dark:text-gray-200">
<span className="text-xs font-medium text-gray-300">
Inset Shadow (Inner)
</span>
</label>
@ -545,7 +545,7 @@ export default function BoxShadowGeneratorPage() {
{/* Opacity Slider */}
<div className="space-y-1.5">
<div className="flex justify-between items-center">
<label className="text-xs font-medium text-gray-700 dark:text-gray-300">
<label className="text-xs font-medium text-gray-400">
Shadow Opacity
</label>
<div className="flex items-center gap-1">
@ -559,9 +559,9 @@ export default function BoxShadowGeneratorPage() {
const val = Math.max(0, Math.min(1, Number(e.target.value)));
setOpacity(isNaN(val) ? 0 : val);
}}
className="w-16 px-1.5 py-0.5 text-xs font-mono text-right rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-16 px-1.5 py-0.5 text-xs font-mono text-right border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
<span className="text-xs text-gray-500 font-mono">
({Math.round(opacity * 100)}%)
</span>
</div>
@ -573,7 +573,7 @@ export default function BoxShadowGeneratorPage() {
step="0.01"
value={opacity}
onChange={(e) => setOpacity(Number(e.target.value))}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 accent-blue-600"
className="w-full h-2 bg-[#1a1a1a] appearance-none cursor-pointer accent-blue-600"
/>
<div className="flex justify-between text-[10px] text-gray-400 font-mono">
<span>0 (Transparent)</span>
@ -583,14 +583,14 @@ export default function BoxShadowGeneratorPage() {
</div>
{/* Box Appearance: Box Background & Border Radius */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 pt-2 border-t border-gray-200 dark:border-gray-700">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 pt-2 border-t border-[#1a1a1a]">
{/* Box Color */}
<div className="space-y-1.5">
<label className="text-xs font-medium text-gray-700 dark:text-gray-300">
<label className="text-xs font-medium text-gray-400">
Box Background Color
</label>
<div className="flex items-center gap-2">
<div className="relative flex-shrink-0 w-8 h-8 rounded-lg border border-gray-300 dark:border-gray-600 overflow-hidden shadow-inner cursor-pointer">
<div className="relative flex-shrink-0 w-8 h-8 border border-[#1a1a1a] overflow-hidden shadow-inner cursor-pointer">
<input
type="color"
value={boxColor}
@ -602,7 +602,7 @@ export default function BoxShadowGeneratorPage() {
type="text"
value={boxColor}
onChange={(e) => setBoxColor(e.target.value)}
className="w-full px-2 py-1 text-xs font-mono rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none uppercase"
className="w-full px-2 py-1 text-xs font-mono border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none uppercase"
/>
</div>
</div>
@ -610,10 +610,10 @@ export default function BoxShadowGeneratorPage() {
{/* Border Radius */}
<div className="space-y-1.5">
<div className="flex justify-between items-center">
<label className="text-xs font-medium text-gray-700 dark:text-gray-300">
<label className="text-xs font-medium text-gray-400">
Border Radius
</label>
<span className="text-xs font-mono text-gray-500 dark:text-gray-400">
<span className="text-xs font-mono text-gray-500">
{borderRadius}px
</span>
</div>
@ -623,7 +623,7 @@ export default function BoxShadowGeneratorPage() {
max="50"
value={borderRadius}
onChange={(e) => setBorderRadius(Number(e.target.value))}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 accent-blue-600"
className="w-full h-2 bg-[#1a1a1a] appearance-none cursor-pointer accent-blue-600"
/>
</div>
</div>
@ -631,20 +631,20 @@ export default function BoxShadowGeneratorPage() {
</div>
{/* Generated CSS Code Block */}
<div className="space-y-3 pt-4 border-t border-gray-200 dark:border-gray-800">
<div className="space-y-3 pt-4 border-t border-[#1a1a1a]">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div className="flex flex-wrap items-center gap-3">
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500">
Generated CSS
</h2>
<div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-800 p-0.5 rounded-lg text-xs">
<div className="flex items-center gap-1 bg-[#111] p-0.5 text-xs">
<button
type="button"
onClick={() => setCodeFormat("standard")}
className={`px-2.5 py-1 rounded-md transition-colors ${
className={`px-2.5 py-1 transition-colors ${
codeFormat === "standard"
? "bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-medium shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900"
? "bg-[#0a0a0a] text-gray-200 font-medium shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Standard
@ -652,10 +652,10 @@ export default function BoxShadowGeneratorPage() {
<button
type="button"
onClick={() => setCodeFormat("full")}
className={`px-2.5 py-1 rounded-md transition-colors ${
className={`px-2.5 py-1 transition-colors ${
codeFormat === "full"
? "bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-medium shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900"
? "bg-[#0a0a0a] text-gray-200 font-medium shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Full CSS Class
@ -663,10 +663,10 @@ export default function BoxShadowGeneratorPage() {
<button
type="button"
onClick={() => setCodeFormat("tailwind")}
className={`px-2.5 py-1 rounded-md transition-colors ${
className={`px-2.5 py-1 transition-colors ${
codeFormat === "tailwind"
? "bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-medium shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900"
? "bg-[#0a0a0a] text-gray-200 font-medium shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Tailwind
@ -674,12 +674,12 @@ export default function BoxShadowGeneratorPage() {
</div>
{codeFormat !== "tailwind" && (
<label className="flex items-center gap-1.5 text-xs text-gray-600 dark:text-gray-400 cursor-pointer ml-2">
<label className="flex items-center gap-1.5 text-xs text-gray-600 cursor-pointer ml-2">
<input
type="checkbox"
checked={includeVendorPrefixes}
onChange={(e) => setIncludeVendorPrefixes(e.target.checked)}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
className="rounded border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
Vendor prefixes (-webkit, -moz)
</label>
@ -689,7 +689,7 @@ export default function BoxShadowGeneratorPage() {
<button
type="button"
onClick={handleCopy}
className="inline-flex items-center justify-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium text-sm transition-colors shadow-sm cursor-pointer"
className="inline-flex items-center justify-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium text-sm transition-colors cursor-pointer"
>
{copied ? (
<>
@ -715,7 +715,7 @@ export default function BoxShadowGeneratorPage() {
</div>
<div className="relative group">
<pre className="w-full p-4 rounded-xl border border-gray-200 dark:border-gray-700 bg-gray-900 text-gray-100 font-mono text-sm overflow-x-auto leading-relaxed shadow-sm">
<pre className="w-full p-4 border border-[#1a1a1a] bg-gray-900 text-gray-100 font-mono text-sm overflow-x-auto leading-relaxed ">
<code>{generatedCode}</code>
</pre>
</div>

View file

@ -293,12 +293,12 @@ export default function CaseConverterPage() {
<div className="space-y-6">
{/* Top Actions Bar */}
<div className="flex flex-wrap items-center justify-between gap-3 pb-2 border-b border-gray-200 dark:border-gray-700">
<div className="flex flex-wrap items-center justify-between gap-3 pb-2 border-b border-[#1a1a1a]">
<div className="flex flex-wrap items-center gap-2">
<button
type="button"
onClick={handleLoadSample}
className="px-3 py-1.5 bg-blue-50 dark:bg-blue-950/40 hover:bg-blue-100 dark:hover:bg-blue-900/50 text-blue-700 dark:text-blue-300 rounded-lg text-xs font-semibold transition-colors cursor-pointer"
className="px-3 py-1.5 bg-[#0a0a1a] hover:bg-[#0a0a1a] text-blue-400 text-xs font-semibold transition-colors cursor-pointer"
>
Load Sample
</button>
@ -306,7 +306,7 @@ export default function CaseConverterPage() {
type="button"
onClick={handleSwap}
disabled={!output && !input}
className="px-3 py-1.5 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-40 text-gray-700 dark:text-gray-200 rounded-lg text-xs font-semibold transition-colors cursor-pointer flex items-center gap-1"
className="px-3 py-1.5 bg-[#111] hover:bg-[#1a1a1a] disabled:opacity-40 text-gray-400 text-xs font-semibold transition-colors cursor-pointer flex items-center gap-1"
>
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
@ -317,20 +317,20 @@ export default function CaseConverterPage() {
type="button"
onClick={handleClear}
disabled={!input && !output}
className="px-3 py-1.5 bg-gray-100 dark:bg-gray-800 hover:bg-rose-50 dark:hover:bg-rose-950/30 disabled:opacity-40 text-gray-700 dark:text-gray-300 hover:text-rose-600 dark:hover:text-rose-400 rounded-lg text-xs font-semibold transition-colors cursor-pointer"
className="px-3 py-1.5 bg-[#111] hover:bg-rose-50 disabled:opacity-40 text-gray-400 hover:text-rose-600 text-xs font-semibold transition-colors cursor-pointer"
>
Clear
</button>
</div>
<div className="text-xs text-gray-500 dark:text-gray-400">
<div className="text-xs text-gray-500">
Click any case button below to transform immediately
</div>
</div>
{/* Case Transformation Buttons Grid */}
<div className="space-y-2">
<label className="text-xs font-bold uppercase tracking-wider text-gray-600 dark:text-gray-400">
<label className="text-xs font-bold uppercase tracking-wider text-gray-600">
Choose Transformation Case
</label>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-2">
@ -341,10 +341,10 @@ export default function CaseConverterPage() {
key={key}
type="button"
onClick={() => handleCaseButtonClick(key)}
className={`p-2.5 rounded-lg border text-left transition-all cursor-pointer shadow-xs ${
className={`p-2.5 border text-left transition-all cursor-pointer shadow-xs ${
isActive
? "bg-blue-600 border-blue-600 text-white ring-2 ring-blue-500/40"
: "bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 text-gray-900 dark:text-gray-100 hover:bg-blue-50/70 dark:hover:bg-blue-950/30 hover:border-blue-300 dark:hover:border-blue-700"
: "bg-[#0a0a0a] border-[#1a1a1a] text-gray-200 hover:bg-[#0a0a1a]/70 hover:border-blue-300"
}`}
>
<div className="font-semibold text-xs truncate">{conv.name}</div>
@ -352,7 +352,7 @@ export default function CaseConverterPage() {
className={`text-[10px] font-mono mt-0.5 truncate ${
isActive
? "text-blue-100"
: "text-gray-500 dark:text-gray-400"
: "text-gray-500"
}`}
>
{conv.example}
@ -370,11 +370,11 @@ export default function CaseConverterPage() {
<div className="flex items-center justify-between">
<label
htmlFor={inputId}
className="text-sm font-semibold text-gray-700 dark:text-gray-300"
className="text-sm font-semibold text-gray-400"
>
Input Text
</label>
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
<span className="text-xs text-gray-500 font-mono">
{input.length} chars | {input.trim() ? input.trim().split(/\s+/).length : 0} words
</span>
</div>
@ -384,7 +384,7 @@ export default function CaseConverterPage() {
onChange={(e) => setInput(e.target.value)}
placeholder="Enter or paste text to convert..."
rows={10}
className="w-full p-3.5 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none resize-y leading-relaxed shadow-inner"
className="w-full p-3.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-mono text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none resize-y leading-relaxed shadow-inner"
/>
</div>
@ -394,23 +394,23 @@ export default function CaseConverterPage() {
<div className="flex items-center gap-2">
<label
htmlFor={outputId}
className="text-sm font-semibold text-gray-700 dark:text-gray-300"
className="text-sm font-semibold text-gray-400"
>
Converted Output
</label>
<span className="px-2 py-0.5 rounded-full text-xs font-semibold bg-blue-100 text-blue-800 dark:bg-blue-950 dark:text-blue-300">
<span className="px-2 py-0.5 rounded-none text-xs font-semibold bg-[#0a0a1a] text-blue-400">
{CONVERTERS[activeCaseKey]?.name || "Result"}
</span>
</div>
<div className="flex items-center gap-3">
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
<span className="text-xs text-gray-500 font-mono">
{output.length} chars
</span>
<button
type="button"
onClick={handleCopy}
disabled={!output}
className="px-2.5 py-1 bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-40 disabled:cursor-not-allowed rounded text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
className="px-2.5 py-1 bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-40 disabled:cursor-not-allowed text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
>
{copied ? (
<>
@ -436,7 +436,7 @@ export default function CaseConverterPage() {
readOnly
placeholder="Converted result will appear here..."
rows={10}
className="w-full p-3.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-900/70 text-gray-900 dark:text-gray-100 font-mono text-sm focus:outline-none resize-y leading-relaxed select-all"
className="w-full p-3.5 border border-[#1a1a1a] bg-black text-gray-200 font-mono text-sm focus:outline-none resize-y leading-relaxed select-all"
/>
</div>
</div>
@ -444,10 +444,10 @@ export default function CaseConverterPage() {
{/* All Cases Live Preview Cards */}
<div className="space-y-3 pt-2">
<div className="flex items-center justify-between">
<h3 className="text-sm font-bold text-gray-900 dark:text-gray-100">
<h3 className="text-sm font-bold text-gray-200">
Live Preview of All Cases
</h3>
<span className="text-xs text-gray-500 dark:text-gray-400">
<span className="text-xs text-gray-500">
Click &quot;Apply&quot; or &quot;Copy&quot; on any case
</span>
</div>
@ -459,19 +459,19 @@ export default function CaseConverterPage() {
return (
<div
key={key}
className={`p-3 rounded-lg border transition-all ${
className={`p-3 border transition-all ${
isSelected
? "border-blue-500 bg-blue-50/40 dark:border-blue-600 dark:bg-blue-950/20"
: "border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-850"
? "border-blue-500 bg-[#0a0a1a]/40"
: "border-[#1a1a1a] bg-[#0a0a0a]"
}`}
>
<div className="flex items-center justify-between mb-1.5">
<div className="flex items-center gap-2">
<span className="font-semibold text-xs text-gray-900 dark:text-gray-100">
<span className="font-semibold text-xs text-gray-200">
{conv.name}
</span>
{isSelected && (
<span className="text-[10px] px-1.5 py-0.2 rounded font-bold bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-200">
<span className="text-[10px] px-1.5 py-0.2 font-bold bg-[#0a0a1a] text-blue-400">
Active
</span>
)}
@ -480,21 +480,21 @@ export default function CaseConverterPage() {
<button
type="button"
onClick={() => handleCaseButtonClick(key)}
className="px-2 py-0.5 text-xs text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-950/40 rounded transition-colors cursor-pointer font-medium"
className="px-2 py-0.5 text-xs text-blue-600 hover:bg-[#0a0a1a] transition-colors cursor-pointer font-medium"
>
Apply
</button>
<button
type="button"
onClick={() => handleCopyPreview(convertedVal, key)}
className="px-2 py-0.5 text-xs text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors cursor-pointer"
className="px-2 py-0.5 text-xs text-gray-600 hover:bg-[#111] transition-colors cursor-pointer"
>
{previewCopiedKey === key ? "Copied!" : "Copy"}
</button>
</div>
</div>
<div className="font-mono text-xs text-gray-800 dark:text-gray-200 p-2 rounded bg-gray-50 dark:bg-gray-900/60 truncate">
<div className="font-mono text-xs text-gray-300 p-2 bg-black truncate">
{convertedVal || <span className="text-gray-400 italic">Empty</span>}
</div>
</div>

View file

@ -448,7 +448,7 @@ export default function ColorConverterPage() {
<div className="grid grid-cols-1 gap-6 lg:grid-cols-12">
{/* Large Color Swatch Box */}
<div className="lg:col-span-5 flex flex-col">
<div className="relative h-56 sm:h-64 w-full rounded-2xl border border-gray-200 dark:border-gray-700 overflow-hidden shadow-inner">
<div className="relative h-56 sm:h-64 w-full border border-[#1a1a1a] overflow-hidden shadow-inner">
{/* Checkerboard backdrop for transparency */}
<div
className="absolute inset-0"
@ -472,20 +472,20 @@ export default function ColorConverterPage() {
>
<div className="flex justify-between items-center">
<span
className="inline-flex items-center px-2.5 py-1 rounded-md text-xs font-bold shadow-sm backdrop-blur-md bg-white/70 dark:bg-black/70 text-gray-900 dark:text-white"
className="inline-flex items-center px-2.5 py-1 text-xs font-bold backdrop-blur-md bg-[#0a0a0a]/70 text-gray-200"
>
Alpha: {Math.round(color.a * 100)}%
</span>
<button
type="button"
onClick={() => copyToClipboard(currentHex, "swatch-hex")}
className="inline-flex items-center gap-1 px-2.5 py-1 rounded-md text-xs font-bold shadow-sm backdrop-blur-md bg-white/70 dark:bg-black/70 text-gray-900 dark:text-white hover:bg-white dark:hover:bg-black transition-colors"
className="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-bold backdrop-blur-md bg-[#0a0a0a]/70 text-gray-200 hover:bg-[#0a0a0a] transition-colors"
>
{copiedKey === "swatch-hex" ? "Copied!" : currentHex}
</button>
</div>
<div className="text-center font-mono font-bold text-lg sm:text-xl drop-shadow-md" style={{ color: contrastWhite >= contrastBlack ? "#FFFFFF" : "#000000" }}>
<div className="text-center font-mono font-bold text-lg sm:text-xl drop-" style={{ color: contrastWhite >= contrastBlack ? "#FFFFFF" : "#000000" }}>
{cssRgbString}
</div>
</div>
@ -500,9 +500,9 @@ export default function ColorConverterPage() {
onChange={(e) => handleColorPickerChange(e.target.value)}
className="sr-only"
/>
<div className="flex items-center justify-center gap-2 rounded-lg border border-gray-300 bg-white py-2.5 px-4 text-sm font-semibold text-gray-700 shadow-sm hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700 transition-colors">
<div className="flex items-center justify-center gap-2 border border-[#1a1a1a] bg-[#0a0a0a] py-2.5 px-4 text-sm font-semibold text-gray-400 hover:bg-black transition-colors">
<span
className="h-4 w-4 rounded-full border border-gray-400"
className="h-4 w-4 rounded-none border border-[#2a2a2a]"
style={{ backgroundColor: currentHex }}
/>
<span>Pick Color (Visual)</span>
@ -512,7 +512,7 @@ export default function ColorConverterPage() {
<button
type="button"
onClick={generateRandomColor}
className="flex items-center justify-center gap-1.5 rounded-lg border border-gray-300 bg-white py-2.5 px-4 text-sm font-semibold text-gray-700 shadow-sm hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700 transition-colors"
className="flex items-center justify-center gap-1.5 border border-[#1a1a1a] bg-[#0a0a0a] py-2.5 px-4 text-sm font-semibold text-gray-400 hover:bg-black transition-colors"
>
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
@ -530,53 +530,53 @@ export default function ColorConverterPage() {
{/* Right Controls: Sliders & Quick Palettes */}
<div className="lg:col-span-7 space-y-4">
{/* Channel Sliders */}
<div className="rounded-xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-800 dark:bg-gray-900/50 space-y-3">
<h3 className="text-xs font-bold uppercase tracking-wider text-gray-600 dark:text-gray-400">
<div className=" border border-[#1a1a1a] bg-black p-4 space-y-3">
<h3 className="text-xs font-bold uppercase tracking-wider text-gray-600">
Channel Controls
</h3>
{/* Red slider */}
<div className="flex items-center gap-3">
<span className="w-12 font-mono text-xs font-bold text-red-600 dark:text-red-400">R: {color.r}</span>
<span className="w-12 font-mono text-xs font-bold text-red-400">R: {color.r}</span>
<input
type="range"
min="0"
max="255"
value={color.r}
onChange={(e) => updateAllFromRgba(Number(e.target.value), color.g, color.b, color.a)}
className="h-2 w-full cursor-pointer appearance-none rounded-lg bg-red-200 dark:bg-red-950 accent-red-600"
className="h-2 w-full cursor-pointer appearance-none bg-red-200 accent-red-600"
/>
</div>
{/* Green slider */}
<div className="flex items-center gap-3">
<span className="w-12 font-mono text-xs font-bold text-emerald-600 dark:text-emerald-400">G: {color.g}</span>
<span className="w-12 font-mono text-xs font-bold text-emerald-600">G: {color.g}</span>
<input
type="range"
min="0"
max="255"
value={color.g}
onChange={(e) => updateAllFromRgba(color.r, Number(e.target.value), color.b, color.a)}
className="h-2 w-full cursor-pointer appearance-none rounded-lg bg-emerald-200 dark:bg-emerald-950 accent-emerald-600"
className="h-2 w-full cursor-pointer appearance-none bg-emerald-200 accent-emerald-600"
/>
</div>
{/* Blue slider */}
<div className="flex items-center gap-3">
<span className="w-12 font-mono text-xs font-bold text-blue-600 dark:text-blue-400">B: {color.b}</span>
<span className="w-12 font-mono text-xs font-bold text-blue-600">B: {color.b}</span>
<input
type="range"
min="0"
max="255"
value={color.b}
onChange={(e) => updateAllFromRgba(color.r, color.g, Number(e.target.value), color.a)}
className="h-2 w-full cursor-pointer appearance-none rounded-lg bg-blue-200 dark:bg-blue-950 accent-blue-600"
className="h-2 w-full cursor-pointer appearance-none bg-blue-200 accent-blue-600"
/>
</div>
{/* Alpha slider */}
<div className="flex items-center gap-3 pt-1 border-t border-gray-200 dark:border-gray-800">
<span className="w-12 font-mono text-xs font-bold text-gray-700 dark:text-gray-300">
<div className="flex items-center gap-3 pt-1 border-t border-[#1a1a1a]">
<span className="w-12 font-mono text-xs font-bold text-gray-400">
A: {Math.round(color.a * 100)}%
</span>
<input
@ -586,14 +586,14 @@ export default function ColorConverterPage() {
step="0.01"
value={color.a}
onChange={(e) => handleAlphaChange(parseFloat(e.target.value))}
className="h-2 w-full cursor-pointer appearance-none rounded-lg bg-gray-300 dark:bg-gray-700 accent-gray-800 dark:accent-gray-200"
className="h-2 w-full cursor-pointer appearance-none bg-[#222] accent-gray-800"
/>
</div>
</div>
{/* Quick Preset Colors */}
<div className="rounded-xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-800 dark:bg-gray-900/50">
<span className="text-xs font-bold uppercase tracking-wider text-gray-600 dark:text-gray-400 block mb-2">
<div className=" border border-[#1a1a1a] bg-black p-4">
<span className="text-xs font-bold uppercase tracking-wider text-gray-600 block mb-2">
Preset Palettes
</span>
<div className="flex flex-wrap gap-2">
@ -605,11 +605,11 @@ export default function ColorConverterPage() {
const p = parseHex(preset.hex);
if (p) updateAllFromRgba(p.r, p.g, p.b, 1);
}}
className="flex items-center gap-1.5 rounded-lg border border-gray-200 bg-white px-2.5 py-1.5 text-xs font-medium text-gray-700 shadow-sm hover:scale-105 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200 transition-all"
className="flex items-center gap-1.5 border border-[#1a1a1a] bg-[#0a0a0a] px-2.5 py-1.5 text-xs font-medium text-gray-400 hover:scale-105 transition-all"
title={preset.name}
>
<span
className="h-3.5 w-3.5 rounded-full border border-black/10"
className="h-3.5 w-3.5 rounded-none border border-black/10"
style={{ backgroundColor: preset.hex }}
/>
<span>{preset.name}</span>
@ -622,25 +622,25 @@ export default function ColorConverterPage() {
{/* Live Two-Way Formats Conversion Section */}
<div className="space-y-4">
<h2 className="text-lg font-bold text-gray-900 dark:text-white">
<h2 className="text-lg font-bold text-gray-200">
Color Formats & Two-Way Editors
</h2>
<p className="text-xs text-gray-500 dark:text-gray-400">
<p className="text-xs text-gray-500">
Edit any input below to convert instantly into all other formats.
</p>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
{/* HEX Input Card */}
<div className="flex flex-col justify-between rounded-xl border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-gray-900">
<div className="flex flex-col justify-between border border-[#1a1a1a] bg-[#0a0a0a] p-4">
<div>
<div className="flex items-center justify-between mb-2">
<label htmlFor="hex-field" className="text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<label htmlFor="hex-field" className="text-xs font-bold uppercase tracking-wider text-gray-400">
HEX Value
</label>
<button
type="button"
onClick={() => copyToClipboard(currentHex, "fmt-hex")}
className="text-xs font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 flex items-center gap-1"
className="text-xs font-medium text-blue-600 hover:text-blue-400 flex items-center gap-1"
>
{copiedKey === "fmt-hex" ? "Copied!" : "Copy"}
</button>
@ -651,16 +651,16 @@ export default function ColorConverterPage() {
value={hexInput}
onChange={(e) => handleHexChange(e.target.value)}
placeholder="#FF5733"
className="w-full p-3 font-mono text-base rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-3 font-mono text-base border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
{inputErrors.hex && <p className="mt-1 text-xs text-red-500">{inputErrors.hex}</p>}
</div>
<div className="mt-3 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 pt-2 border-t border-gray-100 dark:border-gray-800">
<div className="mt-3 flex items-center justify-between text-xs text-gray-500 pt-2 border-t border-gray-100">
<span>8-digit HEX (with Alpha):</span>
<button
type="button"
onClick={() => copyToClipboard(currentHex8, "fmt-hex8")}
className="font-mono text-gray-800 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400 font-semibold"
className="font-mono text-gray-300 hover:text-blue-600 font-semibold"
>
{copiedKey === "fmt-hex8" ? "Copied!" : currentHex8}
</button>
@ -668,16 +668,16 @@ export default function ColorConverterPage() {
</div>
{/* RGB Input Card */}
<div className="flex flex-col justify-between rounded-xl border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-gray-900">
<div className="flex flex-col justify-between border border-[#1a1a1a] bg-[#0a0a0a] p-4">
<div>
<div className="flex items-center justify-between mb-2">
<label htmlFor="rgb-field" className="text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<label htmlFor="rgb-field" className="text-xs font-bold uppercase tracking-wider text-gray-400">
RGB Value
</label>
<button
type="button"
onClick={() => copyToClipboard(cssRgbString, "fmt-rgb")}
className="text-xs font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 flex items-center gap-1"
className="text-xs font-medium text-blue-600 hover:text-blue-400 flex items-center gap-1"
>
{copiedKey === "fmt-rgb" ? "Copied!" : "Copy"}
</button>
@ -688,29 +688,29 @@ export default function ColorConverterPage() {
value={rgbInput}
onChange={(e) => handleRgbChange(e.target.value)}
placeholder="rgb(255, 87, 51)"
className="w-full p-3 font-mono text-base rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-3 font-mono text-base border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
{inputErrors.rgb && <p className="mt-1 text-xs text-red-500">{inputErrors.rgb}</p>}
</div>
<div className="mt-3 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 pt-2 border-t border-gray-100 dark:border-gray-800">
<div className="mt-3 flex items-center justify-between text-xs text-gray-500 pt-2 border-t border-gray-100">
<span>Array values:</span>
<span className="font-mono text-gray-800 dark:text-gray-200">
<span className="font-mono text-gray-300">
[{color.r}, {color.g}, {color.b}]
</span>
</div>
</div>
{/* RGBA Input Card */}
<div className="flex flex-col justify-between rounded-xl border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-gray-900">
<div className="flex flex-col justify-between border border-[#1a1a1a] bg-[#0a0a0a] p-4">
<div>
<div className="flex items-center justify-between mb-2">
<label htmlFor="rgba-field" className="text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<label htmlFor="rgba-field" className="text-xs font-bold uppercase tracking-wider text-gray-400">
RGBA Value (with Alpha)
</label>
<button
type="button"
onClick={() => copyToClipboard(cssRgbaString, "fmt-rgba")}
className="text-xs font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 flex items-center gap-1"
className="text-xs font-medium text-blue-600 hover:text-blue-400 flex items-center gap-1"
>
{copiedKey === "fmt-rgba" ? "Copied!" : "Copy"}
</button>
@ -721,27 +721,27 @@ export default function ColorConverterPage() {
value={rgbaInput}
onChange={(e) => handleRgbaChange(e.target.value)}
placeholder="rgba(255, 87, 51, 1)"
className="w-full p-3 font-mono text-base rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-3 font-mono text-base border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
{inputErrors.rgba && <p className="mt-1 text-xs text-red-500">{inputErrors.rgba}</p>}
</div>
<div className="mt-3 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 pt-2 border-t border-gray-100 dark:border-gray-800">
<div className="mt-3 flex items-center justify-between text-xs text-gray-500 pt-2 border-t border-gray-100">
<span>CSS Output:</span>
<span className="font-mono text-gray-800 dark:text-gray-200 truncate ml-2">{cssRgbaString}</span>
<span className="font-mono text-gray-300 truncate ml-2">{cssRgbaString}</span>
</div>
</div>
{/* HSL Input Card */}
<div className="flex flex-col justify-between rounded-xl border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-gray-900">
<div className="flex flex-col justify-between border border-[#1a1a1a] bg-[#0a0a0a] p-4">
<div>
<div className="flex items-center justify-between mb-2">
<label htmlFor="hsl-field" className="text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<label htmlFor="hsl-field" className="text-xs font-bold uppercase tracking-wider text-gray-400">
HSL Value
</label>
<button
type="button"
onClick={() => copyToClipboard(cssHslString, "fmt-hsl")}
className="text-xs font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 flex items-center gap-1"
className="text-xs font-medium text-blue-600 hover:text-blue-400 flex items-center gap-1"
>
{copiedKey === "fmt-hsl" ? "Copied!" : "Copy"}
</button>
@ -752,16 +752,16 @@ export default function ColorConverterPage() {
value={hslInput}
onChange={(e) => handleHslChange(e.target.value)}
placeholder="hsl(11, 100%, 60%)"
className="w-full p-3 font-mono text-base rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-3 font-mono text-base border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
{inputErrors.hsl && <p className="mt-1 text-xs text-red-500">{inputErrors.hsl}</p>}
</div>
<div className="mt-3 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 pt-2 border-t border-gray-100 dark:border-gray-800">
<div className="mt-3 flex items-center justify-between text-xs text-gray-500 pt-2 border-t border-gray-100">
<span>HSLA (with Alpha):</span>
<button
type="button"
onClick={() => copyToClipboard(cssHslaString, "fmt-hsla")}
className="font-mono text-gray-800 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400 font-semibold"
className="font-mono text-gray-300 hover:text-blue-600 font-semibold"
>
{copiedKey === "fmt-hsla" ? "Copied!" : cssHslaString}
</button>
@ -773,38 +773,38 @@ export default function ColorConverterPage() {
{/* Additional Formats (HSV & CMYK) */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
{/* HSV Card */}
<div className="flex items-center justify-between rounded-xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-800 dark:bg-gray-900/50">
<div className="flex items-center justify-between border border-[#1a1a1a] bg-black p-4">
<div>
<span className="text-xs font-semibold text-gray-500 dark:text-gray-400 block mb-1">
<span className="text-xs font-semibold text-gray-500 block mb-1">
HSV / HSB Format
</span>
<span className="font-mono text-sm font-bold text-gray-900 dark:text-white">
<span className="font-mono text-sm font-bold text-gray-200">
{cssHsvString}
</span>
</div>
<button
type="button"
onClick={() => copyToClipboard(cssHsvString, "fmt-hsv")}
className="rounded bg-white px-2.5 py-1.5 text-xs font-medium text-gray-700 border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded bg-[#0a0a0a] px-2.5 py-1.5 text-xs font-medium text-gray-400 border border-[#1a1a1a] hover:bg-[#111]"
>
{copiedKey === "fmt-hsv" ? "Copied!" : "Copy"}
</button>
</div>
{/* CMYK Card */}
<div className="flex items-center justify-between rounded-xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-800 dark:bg-gray-900/50">
<div className="flex items-center justify-between border border-[#1a1a1a] bg-black p-4">
<div>
<span className="text-xs font-semibold text-gray-500 dark:text-gray-400 block mb-1">
<span className="text-xs font-semibold text-gray-500 block mb-1">
CMYK Format (Print)
</span>
<span className="font-mono text-sm font-bold text-gray-900 dark:text-white">
<span className="font-mono text-sm font-bold text-gray-200">
{cssCmykString}
</span>
</div>
<button
type="button"
onClick={() => copyToClipboard(cssCmykString, "fmt-cmyk")}
className="rounded bg-white px-2.5 py-1.5 text-xs font-medium text-gray-700 border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded bg-[#0a0a0a] px-2.5 py-1.5 text-xs font-medium text-gray-400 border border-[#1a1a1a] hover:bg-[#111]"
>
{copiedKey === "fmt-cmyk" ? "Copied!" : "Copy"}
</button>
@ -812,41 +812,41 @@ export default function ColorConverterPage() {
</div>
{/* Accessibility & Contrast Info */}
<div className="rounded-xl border border-gray-200 bg-gray-50 p-5 dark:border-gray-800 dark:bg-gray-900/50 space-y-4">
<h3 className="text-sm font-bold uppercase tracking-wider text-gray-800 dark:text-gray-200">
<div className=" border border-[#1a1a1a] bg-black p-5 space-y-4">
<h3 className="text-sm font-bold uppercase tracking-wider text-gray-300">
WCAG Accessibility Contrast
</h3>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
{/* Contrast with White */}
<div className="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-4">
<div className="flex items-center justify-between mb-2">
<span className="text-xs font-semibold text-gray-600 dark:text-gray-300">White Text (#FFFFFF)</span>
<span className="font-mono text-sm font-bold text-gray-900 dark:text-white">
<span className="text-xs font-semibold text-gray-600">White Text (#FFFFFF)</span>
<span className="font-mono text-sm font-bold text-gray-200">
{contrastWhite.toFixed(2)}:1
</span>
</div>
<div
className="h-10 rounded-md flex items-center justify-center font-semibold text-white text-sm shadow-inner"
className="h-10 flex items-center justify-center font-semibold text-white text-sm shadow-inner"
style={{ backgroundColor: currentHex }}
>
Sample White Text
</div>
<div className="mt-2 flex gap-2 text-xs">
<span
className={`px-2 py-0.5 rounded font-medium ${
className={`px-2 py-0.5 font-medium ${
contrastWhite >= 4.5
? "bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300"
: "bg-red-100 text-red-800 dark:bg-red-950 dark:text-red-300"
? "bg-emerald-100 text-emerald-800"
: "bg-[#1a0a0a] text-red-400"
}`}
>
AA Normal {contrastWhite >= 4.5 ? "✓ Pass" : "✕ Fail"}
</span>
<span
className={`px-2 py-0.5 rounded font-medium ${
className={`px-2 py-0.5 font-medium ${
contrastWhite >= 7
? "bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300"
: "bg-red-100 text-red-800 dark:bg-red-950 dark:text-red-300"
? "bg-emerald-100 text-emerald-800"
: "bg-[#1a0a0a] text-red-400"
}`}
>
AAA {contrastWhite >= 7 ? "✓ Pass" : "✕ Fail"}
@ -855,34 +855,34 @@ export default function ColorConverterPage() {
</div>
{/* Contrast with Black */}
<div className="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-4">
<div className="flex items-center justify-between mb-2">
<span className="text-xs font-semibold text-gray-600 dark:text-gray-300">Black Text (#000000)</span>
<span className="font-mono text-sm font-bold text-gray-900 dark:text-white">
<span className="text-xs font-semibold text-gray-600">Black Text (#000000)</span>
<span className="font-mono text-sm font-bold text-gray-200">
{contrastBlack.toFixed(2)}:1
</span>
</div>
<div
className="h-10 rounded-md flex items-center justify-center font-semibold text-black text-sm shadow-inner"
className="h-10 flex items-center justify-center font-semibold text-black text-sm shadow-inner"
style={{ backgroundColor: currentHex }}
>
Sample Black Text
</div>
<div className="mt-2 flex gap-2 text-xs">
<span
className={`px-2 py-0.5 rounded font-medium ${
className={`px-2 py-0.5 font-medium ${
contrastBlack >= 4.5
? "bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300"
: "bg-red-100 text-red-800 dark:bg-red-950 dark:text-red-300"
? "bg-emerald-100 text-emerald-800"
: "bg-[#1a0a0a] text-red-400"
}`}
>
AA Normal {contrastBlack >= 4.5 ? "✓ Pass" : "✕ Fail"}
</span>
<span
className={`px-2 py-0.5 rounded font-medium ${
className={`px-2 py-0.5 font-medium ${
contrastBlack >= 7
? "bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300"
: "bg-red-100 text-red-800 dark:bg-red-950 dark:text-red-300"
? "bg-emerald-100 text-emerald-800"
: "bg-[#1a0a0a] text-red-400"
}`}
>
AAA {contrastBlack >= 7 ? "✓ Pass" : "✕ Fail"}
@ -893,27 +893,27 @@ export default function ColorConverterPage() {
</div>
{/* Color Harmonies */}
<div className="rounded-xl border border-gray-200 bg-gray-50 p-5 dark:border-gray-800 dark:bg-gray-900/50 space-y-4">
<h3 className="text-sm font-bold uppercase tracking-wider text-gray-800 dark:text-gray-200">
<div className=" border border-[#1a1a1a] bg-black p-5 space-y-4">
<h3 className="text-sm font-bold uppercase tracking-wider text-gray-300">
Harmonies & Shades (Click to load)
</h3>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
{/* Complementary */}
<div>
<span className="text-xs font-medium text-gray-500 dark:text-gray-400 block mb-1.5">
<span className="text-xs font-medium text-gray-500 block mb-1.5">
Complementary
</span>
<button
type="button"
onClick={() => updateAllFromRgba(harmonies.complementary.rgb.r, harmonies.complementary.rgb.g, harmonies.complementary.rgb.b, 1)}
className="w-full flex items-center gap-2 p-2 rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:scale-[1.02] transition-transform text-left"
className="w-full flex items-center gap-2 p-2 bg-[#0a0a0a] border border-[#1a1a1a] hover:scale-[1.02] transition-transform text-left"
>
<span
className="h-8 w-8 rounded-md border border-black/10 shrink-0"
className="h-8 w-8 border border-black/10 shrink-0"
style={{ backgroundColor: harmonies.complementary.hex }}
/>
<span className="font-mono text-xs font-medium text-gray-800 dark:text-gray-200 truncate">
<span className="font-mono text-xs font-medium text-gray-300 truncate">
{harmonies.complementary.hex}
</span>
</button>
@ -921,33 +921,33 @@ export default function ColorConverterPage() {
{/* Analogous */}
<div>
<span className="text-xs font-medium text-gray-500 dark:text-gray-400 block mb-1.5">
<span className="text-xs font-medium text-gray-500 block mb-1.5">
Analogous
</span>
<div className="flex gap-2">
<button
type="button"
onClick={() => updateAllFromRgba(harmonies.analogous1.rgb.r, harmonies.analogous1.rgb.g, harmonies.analogous1.rgb.b, 1)}
className="flex-1 flex items-center gap-1.5 p-2 rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:scale-[1.02] transition-transform text-left"
className="flex-1 flex items-center gap-1.5 p-2 bg-[#0a0a0a] border border-[#1a1a1a] hover:scale-[1.02] transition-transform text-left"
>
<span
className="h-6 w-6 rounded-md border border-black/10 shrink-0"
className="h-6 w-6 border border-black/10 shrink-0"
style={{ backgroundColor: harmonies.analogous1.hex }}
/>
<span className="font-mono text-xs font-medium text-gray-800 dark:text-gray-200 truncate">
<span className="font-mono text-xs font-medium text-gray-300 truncate">
{harmonies.analogous1.hex}
</span>
</button>
<button
type="button"
onClick={() => updateAllFromRgba(harmonies.analogous2.rgb.r, harmonies.analogous2.rgb.g, harmonies.analogous2.rgb.b, 1)}
className="flex-1 flex items-center gap-1.5 p-2 rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:scale-[1.02] transition-transform text-left"
className="flex-1 flex items-center gap-1.5 p-2 bg-[#0a0a0a] border border-[#1a1a1a] hover:scale-[1.02] transition-transform text-left"
>
<span
className="h-6 w-6 rounded-md border border-black/10 shrink-0"
className="h-6 w-6 border border-black/10 shrink-0"
style={{ backgroundColor: harmonies.analogous2.hex }}
/>
<span className="font-mono text-xs font-medium text-gray-800 dark:text-gray-200 truncate">
<span className="font-mono text-xs font-medium text-gray-300 truncate">
{harmonies.analogous2.hex}
</span>
</button>
@ -956,33 +956,33 @@ export default function ColorConverterPage() {
{/* Triadic */}
<div>
<span className="text-xs font-medium text-gray-500 dark:text-gray-400 block mb-1.5">
<span className="text-xs font-medium text-gray-500 block mb-1.5">
Triadic
</span>
<div className="flex gap-2">
<button
type="button"
onClick={() => updateAllFromRgba(harmonies.triadic1.rgb.r, harmonies.triadic1.rgb.g, harmonies.triadic1.rgb.b, 1)}
className="flex-1 flex items-center gap-1.5 p-2 rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:scale-[1.02] transition-transform text-left"
className="flex-1 flex items-center gap-1.5 p-2 bg-[#0a0a0a] border border-[#1a1a1a] hover:scale-[1.02] transition-transform text-left"
>
<span
className="h-6 w-6 rounded-md border border-black/10 shrink-0"
className="h-6 w-6 border border-black/10 shrink-0"
style={{ backgroundColor: harmonies.triadic1.hex }}
/>
<span className="font-mono text-xs font-medium text-gray-800 dark:text-gray-200 truncate">
<span className="font-mono text-xs font-medium text-gray-300 truncate">
{harmonies.triadic1.hex}
</span>
</button>
<button
type="button"
onClick={() => updateAllFromRgba(harmonies.triadic2.rgb.r, harmonies.triadic2.rgb.g, harmonies.triadic2.rgb.b, 1)}
className="flex-1 flex items-center gap-1.5 p-2 rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:scale-[1.02] transition-transform text-left"
className="flex-1 flex items-center gap-1.5 p-2 bg-[#0a0a0a] border border-[#1a1a1a] hover:scale-[1.02] transition-transform text-left"
>
<span
className="h-6 w-6 rounded-md border border-black/10 shrink-0"
className="h-6 w-6 border border-black/10 shrink-0"
style={{ backgroundColor: harmonies.triadic2.hex }}
/>
<span className="font-mono text-xs font-medium text-gray-800 dark:text-gray-200 truncate">
<span className="font-mono text-xs font-medium text-gray-300 truncate">
{harmonies.triadic2.hex}
</span>
</button>
@ -992,7 +992,7 @@ export default function ColorConverterPage() {
{/* Tints & Shades scale */}
<div>
<span className="text-xs font-medium text-gray-500 dark:text-gray-400 block mb-1.5">
<span className="text-xs font-medium text-gray-500 block mb-1.5">
Lightness Spectrum (15% to 90%)
</span>
<div className="grid grid-cols-6 gap-1.5">
@ -1001,7 +1001,7 @@ export default function ColorConverterPage() {
key={tint.lightness}
type="button"
onClick={() => updateAllFromRgba(tint.rgb.r, tint.rgb.g, tint.rgb.b, 1)}
className="h-10 rounded-md border border-black/10 hover:scale-105 transition-transform flex items-center justify-center"
className="h-10 border border-black/10 hover:scale-105 transition-transform flex items-center justify-center"
style={{ backgroundColor: tint.hex }}
title={`${tint.hex} (${tint.lightness}%)`}
/>

View file

@ -680,9 +680,9 @@ export default function CronParserPage() {
<div className="space-y-6">
{/* Top Preset Gallery */}
<div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-gray-50/70 dark:bg-gray-900/50 p-4 space-y-3 shadow-xs">
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-gray-200/80 dark:border-gray-800 pb-2">
<span className="text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<div className=" border border-[#1a1a1a] bg-black/70 p-4 space-y-3 shadow-xs">
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-[#1a1a1a]/80 pb-2">
<span className="text-xs font-bold uppercase tracking-wider text-gray-400">
Common Cron Presets
</span>
<span className="text-xs text-gray-400">Click any preset to load</span>
@ -696,18 +696,18 @@ export default function CronParserPage() {
key={p.cron + p.name}
type="button"
onClick={() => setCronExpression(p.cron)}
className={`px-3 py-1.5 rounded-lg text-xs transition-all cursor-pointer flex items-center gap-2 border ${
className={`px-3 py-1.5 text-xs transition-all cursor-pointer flex items-center gap-2 border ${
isSelected
? "bg-blue-600 border-blue-600 text-white font-semibold shadow-xs"
: "bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 text-gray-700 dark:text-gray-300 hover:bg-blue-50 dark:hover:bg-blue-950/40 hover:border-blue-300 dark:hover:border-blue-700"
: "bg-[#0a0a0a] border-[#1a1a1a] text-gray-400 hover:bg-[#0a0a1a] hover:border-blue-300"
}`}
>
<span>{p.name}</span>
<code
className={`font-mono text-[11px] px-1 py-0.5 rounded ${
className={`font-mono text-[11px] px-1 py-0.5 ${
isSelected
? "bg-blue-700 text-blue-100"
: "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400"
: "bg-[#111] text-gray-600"
}`}
>
{p.cron}
@ -719,9 +719,9 @@ export default function CronParserPage() {
</div>
{/* Main Cron Input Bar */}
<div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-5 shadow-xs space-y-4">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-5 shadow-xs space-y-4">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
<label htmlFor="cron-main-input" className="text-sm font-bold text-gray-900 dark:text-white">
<label htmlFor="cron-main-input" className="text-sm font-bold text-gray-200">
Enter Cron Expression (5 fields)
</label>
<div className="flex items-center gap-3">
@ -729,7 +729,7 @@ export default function CronParserPage() {
<button
type="button"
onClick={handleCopyCron}
className="px-2.5 py-1 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 rounded text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] text-gray-400 text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
>
{copiedCron ? "Copied!" : "Copy Expression"}
</button>
@ -743,18 +743,18 @@ export default function CronParserPage() {
value={cronExpression}
onChange={(e) => setCronExpression(e.target.value)}
placeholder="* * * * *"
className="w-full p-4 font-mono text-xl sm:text-2xl tracking-wider rounded-xl border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none shadow-inner"
className="w-full p-4 font-mono text-xl sm:text-2xl tracking-wider border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none shadow-inner"
/>
</div>
{/* Human Readable Explanation Banner */}
{parsedCron.valid ? (
<div className="flex items-start gap-3 p-4 rounded-xl border border-emerald-200 bg-emerald-50/70 dark:border-emerald-900/50 dark:bg-emerald-950/20 text-emerald-900 dark:text-emerald-200">
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-emerald-500 text-white text-xs font-bold mt-0.5">
<div className="flex items-start gap-3 p-4 border border-emerald-200 bg-emerald-50/70 text-emerald-900">
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-none bg-emerald-500 text-white text-xs font-bold mt-0.5">
✓
</span>
<div>
<span className="text-xs font-bold uppercase tracking-wider text-emerald-700 dark:text-emerald-400 block">
<span className="text-xs font-bold uppercase tracking-wider text-emerald-700 block">
Human-Readable Schedule
</span>
<p className="mt-0.5 text-base sm:text-lg font-bold">
@ -763,12 +763,12 @@ export default function CronParserPage() {
</div>
</div>
) : (
<div className="flex items-start gap-3 p-4 rounded-xl border border-rose-200 bg-rose-50/80 dark:border-rose-900/50 dark:bg-rose-950/30 text-rose-900 dark:text-rose-200">
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-rose-500 text-white text-xs font-bold mt-0.5">
<div className="flex items-start gap-3 p-4 border border-rose-200 bg-rose-50/80 text-rose-900">
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-none bg-rose-500 text-white text-xs font-bold mt-0.5">
!
</span>
<div>
<span className="text-xs font-bold uppercase tracking-wider text-rose-700 dark:text-rose-400 block">
<span className="text-xs font-bold uppercase tracking-wider text-rose-700 block">
Syntax Error
</span>
<p className="mt-0.5 text-sm font-medium">{parsedCron.error}</p>
@ -780,7 +780,7 @@ export default function CronParserPage() {
{/* 5 Field Breakdown Interactive Cards */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<h3 className="text-sm font-bold text-gray-900 dark:text-gray-100">
<h3 className="text-sm font-bold text-gray-200">
Cron Field Breakdown &amp; Structure
</h3>
<span className="text-xs text-gray-400">Edit individual fields directly below</span>
@ -795,15 +795,15 @@ export default function CronParserPage() {
return (
<div
key={fDef.key}
className={`flex flex-col justify-between p-3.5 rounded-xl border transition-all ${
className={`flex flex-col justify-between p-3.5 border transition-all ${
isValid
? "border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-850"
: "border-rose-300 dark:border-rose-800 bg-rose-50/40 dark:bg-rose-950/20"
? "border-[#1a1a1a] bg-[#0a0a0a]"
: "border-rose-300 bg-rose-50/40"
}`}
>
<div className="space-y-1.5">
<div className="flex items-center justify-between">
<span className="text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wide">
<span className="text-xs font-bold text-gray-300 uppercase tracking-wide">
{fDef.name}
</span>
<span className="text-[10px] font-mono text-gray-400">
@ -816,17 +816,17 @@ export default function CronParserPage() {
value={rawToken}
onChange={(e) => handleFieldChange(idx, e.target.value)}
placeholder="*"
className="w-full p-2 font-mono text-base font-bold text-center rounded-lg border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2 font-mono text-base font-bold text-center border border-[#1a1a1a] bg-black text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
<p className="text-[11px] text-gray-500 dark:text-gray-400 leading-tight">
<p className="text-[11px] text-gray-500 leading-tight">
{fDef.description}
</p>
</div>
{parsedF && parsedF.valid && (
<div className="mt-3 pt-2 border-t border-gray-100 dark:border-gray-800">
<span className="text-[11px] font-medium text-blue-600 dark:text-blue-400 block truncate">
<div className="mt-3 pt-2 border-t border-gray-100">
<span className="text-[11px] font-medium text-blue-600 block truncate">
{parsedF.explanation}
</span>
<div className="text-[10px] text-gray-400 font-mono mt-0.5 truncate" title={parsedF.values.join(", ")}>
@ -841,13 +841,13 @@ export default function CronParserPage() {
</div>
{/* Scheduled Runs Section */}
<div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-5 shadow-xs space-y-4">
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-gray-100 dark:border-gray-800 pb-3">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-5 shadow-xs space-y-4">
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-gray-100 pb-3">
<div className="flex items-center gap-2">
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300 text-xs font-bold">
<span className="flex h-6 w-6 items-center justify-center rounded-none bg-[#0a0a1a] text-blue-400 text-xs font-bold">
⏰
</span>
<h3 className="text-sm font-bold text-gray-900 dark:text-white">
<h3 className="text-sm font-bold text-gray-200">
Upcoming Scheduled Runs (Next {runCount})
</h3>
</div>
@ -855,15 +855,15 @@ export default function CronParserPage() {
<div className="flex flex-wrap items-center gap-3">
{/* Timezone Toggle */}
<div className="flex items-center gap-1.5 text-xs">
<span className="text-gray-500 dark:text-gray-400">Timezone:</span>
<div className="inline-flex rounded-lg border border-gray-300 dark:border-gray-700 p-0.5 bg-gray-100 dark:bg-gray-800">
<span className="text-gray-500">Timezone:</span>
<div className="inline-flex border border-[#1a1a1a] p-0.5 bg-[#111]">
<button
type="button"
onClick={() => setTimezone("local")}
className={`px-2 py-0.5 rounded text-xs font-medium transition-colors cursor-pointer ${
className={`px-2 py-0.5 text-xs font-medium transition-colors cursor-pointer ${
timezone === "local"
? "bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
? "bg-[#0a0a0a] text-gray-200 shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Local Time
@ -871,10 +871,10 @@ export default function CronParserPage() {
<button
type="button"
onClick={() => setTimezone("utc")}
className={`px-2 py-0.5 rounded text-xs font-medium transition-colors cursor-pointer ${
className={`px-2 py-0.5 text-xs font-medium transition-colors cursor-pointer ${
timezone === "utc"
? "bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
? "bg-[#0a0a0a] text-gray-200 shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
UTC
@ -884,11 +884,11 @@ export default function CronParserPage() {
{/* Count selector */}
<div className="flex items-center gap-1.5 text-xs">
<span className="text-gray-500 dark:text-gray-400">Count:</span>
<span className="text-gray-500">Count:</span>
<select
value={runCount}
onChange={(e) => setRunCount(Number(e.target.value))}
className="p-1 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-xs focus:outline-none"
className="p-1 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-xs focus:outline-none"
>
<option value={5}>5 runs</option>
<option value={8}>8 runs</option>
@ -901,7 +901,7 @@ export default function CronParserPage() {
type="button"
onClick={handleCopySchedule}
disabled={nextRuns.length === 0}
className="px-3 py-1 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 disabled:opacity-40 rounded text-xs font-medium transition-colors cursor-pointer"
className="px-3 py-1 bg-[#111] hover:bg-[#1a1a1a] text-gray-400 disabled:opacity-40 text-xs font-medium transition-colors cursor-pointer"
>
{copiedSchedule ? "Copied List!" : "Copy Schedule"}
</button>
@ -909,7 +909,7 @@ export default function CronParserPage() {
</div>
{nextRuns.length > 0 ? (
<div className="divide-y divide-gray-100 dark:divide-gray-800 max-h-[420px] overflow-y-auto">
<div className="divide-y divide-gray-100 max-h-[420px] overflow-y-auto">
{nextRuns.map((date, idx) => {
const relative = getRelativeTime(date, now);
const isLocal = timezone === "local";
@ -934,24 +934,24 @@ export default function CronParserPage() {
return (
<div
key={idx}
className="flex flex-col sm:flex-row sm:items-center justify-between py-2.5 px-2 hover:bg-gray-50 dark:hover:bg-gray-800/40 rounded-lg transition-colors text-xs"
className="flex flex-col sm:flex-row sm:items-center justify-between py-2.5 px-2 hover:bg-black transition-colors text-xs"
>
<div className="flex items-center gap-3">
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-gray-100 dark:bg-gray-800 text-[11px] font-mono font-bold text-gray-500 dark:text-gray-400">
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-none bg-[#111] text-[11px] font-mono font-bold text-gray-500">
{idx + 1}
</span>
<div>
<span className="font-semibold text-gray-900 dark:text-white mr-2">
<span className="font-semibold text-gray-200 mr-2">
{dateStr}
</span>
<span className="font-mono text-gray-600 dark:text-gray-300">
<span className="font-mono text-gray-600">
{timeStr}
</span>
</div>
</div>
<div className="mt-1 sm:mt-0 flex items-center gap-3">
<span className="px-2 py-0.5 rounded-full bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-300 font-medium">
<span className="px-2 py-0.5 rounded-none bg-[#0a0a1a] text-blue-400 font-medium">
{relative}
</span>
<span className="font-mono text-[11px] text-gray-400 hidden md:inline">
@ -970,41 +970,41 @@ export default function CronParserPage() {
</div>
{/* Cron Syntax Cheat Sheet */}
<div className="mt-8 pt-6 border-t border-gray-200 dark:border-gray-800 space-y-4">
<h3 className="text-sm font-bold text-gray-900 dark:text-white">
<div className="mt-8 pt-6 border-t border-[#1a1a1a] space-y-4">
<h3 className="text-sm font-bold text-gray-200">
Cron Syntax Reference &amp; Special Characters
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 text-xs">
<div className="p-3 bg-gray-50 dark:bg-gray-850/60 rounded-xl border border-gray-200 dark:border-gray-800 space-y-1">
<div className="font-mono font-bold text-blue-600 dark:text-blue-400 text-sm">* (Asterisk)</div>
<p className="font-semibold text-gray-800 dark:text-gray-200">Wildcard (Any Value)</p>
<p className="text-gray-500 dark:text-gray-400">
Matches every possible value in the field. E.g. <code className="bg-gray-200 dark:bg-gray-700 px-1 rounded">*</code> in hour means every hour.
<div className="p-3 bg-black border border-[#1a1a1a] space-y-1">
<div className="font-mono font-bold text-blue-600 text-sm">* (Asterisk)</div>
<p className="font-semibold text-gray-300">Wildcard (Any Value)</p>
<p className="text-gray-500">
Matches every possible value in the field. E.g. <code className="bg-[#1a1a1a] px-1 rounded">*</code> in hour means every hour.
</p>
</div>
<div className="p-3 bg-gray-50 dark:bg-gray-850/60 rounded-xl border border-gray-200 dark:border-gray-800 space-y-1">
<div className="font-mono font-bold text-blue-600 dark:text-blue-400 text-sm">, (Comma)</div>
<p className="font-semibold text-gray-800 dark:text-gray-200">Value List</p>
<p className="text-gray-500 dark:text-gray-400">
Specifies discrete multiple values. E.g. <code className="bg-gray-200 dark:bg-gray-700 px-1 rounded">1,15,30</code> in minute runs at minutes 1, 15, 30.
<div className="p-3 bg-black border border-[#1a1a1a] space-y-1">
<div className="font-mono font-bold text-blue-600 text-sm">, (Comma)</div>
<p className="font-semibold text-gray-300">Value List</p>
<p className="text-gray-500">
Specifies discrete multiple values. E.g. <code className="bg-[#1a1a1a] px-1 rounded">1,15,30</code> in minute runs at minutes 1, 15, 30.
</p>
</div>
<div className="p-3 bg-gray-50 dark:bg-gray-850/60 rounded-xl border border-gray-200 dark:border-gray-800 space-y-1">
<div className="font-mono font-bold text-blue-600 dark:text-blue-400 text-sm">- (Hyphen)</div>
<p className="font-semibold text-gray-800 dark:text-gray-200">Range of Values</p>
<p className="text-gray-500 dark:text-gray-400">
Specifies an inclusive range. E.g. <code className="bg-gray-200 dark:bg-gray-700 px-1 rounded">1-5</code> in weekday means Monday through Friday.
<div className="p-3 bg-black border border-[#1a1a1a] space-y-1">
<div className="font-mono font-bold text-blue-600 text-sm">- (Hyphen)</div>
<p className="font-semibold text-gray-300">Range of Values</p>
<p className="text-gray-500">
Specifies an inclusive range. E.g. <code className="bg-[#1a1a1a] px-1 rounded">1-5</code> in weekday means Monday through Friday.
</p>
</div>
<div className="p-3 bg-gray-50 dark:bg-gray-850/60 rounded-xl border border-gray-200 dark:border-gray-800 space-y-1">
<div className="font-mono font-bold text-blue-600 dark:text-blue-400 text-sm">/ (Slash)</div>
<p className="font-semibold text-gray-800 dark:text-gray-200">Step Values</p>
<p className="text-gray-500 dark:text-gray-400">
Specifies step increments. E.g. <code className="bg-gray-200 dark:bg-gray-700 px-1 rounded">*/15</code> in minute runs every 15 minutes.
<div className="p-3 bg-black border border-[#1a1a1a] space-y-1">
<div className="font-mono font-bold text-blue-600 text-sm">/ (Slash)</div>
<p className="font-semibold text-gray-300">Step Values</p>
<p className="text-gray-500">
Specifies step increments. E.g. <code className="bg-[#1a1a1a] px-1 rounded">*/15</code> in minute runs every 15 minutes.
</p>
</div>
</div>

View file

@ -281,23 +281,23 @@ export default function CssGradientGeneratorPage() {
{/* Live Preview Area */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500">
Live Preview
</h2>
<div className="flex items-center gap-3">
<label className="flex items-center gap-2 text-xs font-medium text-gray-600 dark:text-gray-300 cursor-pointer">
<label className="flex items-center gap-2 text-xs font-medium text-gray-600 cursor-pointer">
<input
type="checkbox"
checked={previewText}
onChange={(e) => setPreviewText(e.target.checked)}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
className="rounded border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
Show Text Overlay
</label>
<button
type="button"
onClick={generateRandomGradient}
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700 rounded-lg transition-colors"
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-gray-400 bg-[#111] hover:bg-[#1a1a1a] transition-colors"
title="Generate Random Gradient"
>
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -314,12 +314,12 @@ export default function CssGradientGeneratorPage() {
</div>
<div
className="relative w-full h-64 sm:h-72 rounded-2xl border border-gray-200 dark:border-gray-700 shadow-inner flex items-center justify-center p-6 transition-all duration-300 overflow-hidden"
className="relative w-full h-64 sm:h-72 border border-[#1a1a1a] shadow-inner flex items-center justify-center p-6 transition-all duration-300 overflow-hidden"
style={{ background: gradientValue }}
>
{previewText && (
<div className="bg-black/30 backdrop-blur-md text-white rounded-xl p-5 max-w-md text-center shadow-lg border border-white/10 transition-all">
<h3 className="text-xl sm:text-2xl font-bold tracking-tight mb-1 drop-shadow-sm">
<div className="bg-black/30 backdrop-blur-md text-white p-5 max-w-md text-center border border-white/10 transition-all">
<h3 className="text-xl sm:text-2xl font-bold tracking-tight mb-1 drop-">
CSS Gradient Preview
</h3>
<p className="text-xs sm:text-sm text-gray-100/90 font-mono">
@ -332,7 +332,7 @@ export default function CssGradientGeneratorPage() {
{/* Presets Gallery */}
<div className="space-y-3">
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500">
Quick Presets
</h2>
<div className="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-8 gap-2.5">
@ -346,13 +346,13 @@ export default function CssGradientGeneratorPage() {
key={preset.name}
type="button"
onClick={() => applyPreset(preset)}
className="group relative flex flex-col items-center gap-1.5 p-1.5 rounded-xl border border-gray-200 dark:border-gray-700 hover:border-blue-500 dark:hover:border-blue-500 bg-white dark:bg-gray-800 transition-all hover:scale-[1.03] text-left"
className="group relative flex flex-col items-center gap-1.5 p-1.5 border border-[#1a1a1a] hover:border-blue-500 bg-[#0a0a0a] transition-all hover:scale-[1.03] text-left"
>
<div
className="w-full h-12 rounded-lg shadow-sm"
className="w-full h-12 "
style={{ background: bgString }}
/>
<span className="text-[11px] font-medium text-gray-700 dark:text-gray-300 truncate w-full text-center">
<span className="text-[11px] font-medium text-gray-400 truncate w-full text-center">
{preset.name}
</span>
</button>
@ -367,17 +367,17 @@ export default function CssGradientGeneratorPage() {
<div className="lg:col-span-5 space-y-6">
{/* Gradient Type */}
<div className="space-y-2">
<label className="text-sm font-medium text-gray-900 dark:text-gray-100">
<label className="text-sm font-medium text-gray-200">
Gradient Type
</label>
<div className="grid grid-cols-2 gap-2">
<button
type="button"
onClick={() => setGradientType("linear")}
className={`py-2.5 px-4 text-sm font-medium rounded-lg border transition-all ${
className={`py-2.5 px-4 text-sm font-medium border transition-all ${
gradientType === "linear"
? "bg-blue-600 border-blue-600 text-white shadow-sm"
: "bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700/50"
? "bg-blue-600 border-blue-600 text-white "
: "bg-[#0a0a0a] border-[#1a1a1a] text-gray-400 hover:bg-black"
}`}
>
Linear
@ -385,10 +385,10 @@ export default function CssGradientGeneratorPage() {
<button
type="button"
onClick={() => setGradientType("radial")}
className={`py-2.5 px-4 text-sm font-medium rounded-lg border transition-all ${
className={`py-2.5 px-4 text-sm font-medium border transition-all ${
gradientType === "radial"
? "bg-blue-600 border-blue-600 text-white shadow-sm"
: "bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700/50"
? "bg-blue-600 border-blue-600 text-white "
: "bg-[#0a0a0a] border-[#1a1a1a] text-gray-400 hover:bg-black"
}`}
>
Radial
@ -401,8 +401,8 @@ export default function CssGradientGeneratorPage() {
<div className="space-y-4">
<div className="space-y-2">
<div className="flex justify-between items-center">
<label className="text-sm font-medium text-gray-900 dark:text-gray-100">
Angle: <span className="font-mono text-blue-600 dark:text-blue-400">{angle}°</span>
<label className="text-sm font-medium text-gray-200">
Angle: <span className="font-mono text-blue-600">{angle}°</span>
</label>
<input
type="number"
@ -410,7 +410,7 @@ export default function CssGradientGeneratorPage() {
max="360"
value={angle}
onChange={(e) => setAngle(Number(e.target.value) % 361)}
className="w-20 px-2 py-1 text-sm font-mono text-right rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-20 px-2 py-1 text-sm font-mono text-right border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
<input
@ -419,13 +419,13 @@ export default function CssGradientGeneratorPage() {
max="360"
value={angle}
onChange={(e) => setAngle(Number(e.target.value))}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 accent-blue-600"
className="w-full h-2 bg-[#1a1a1a] appearance-none cursor-pointer accent-blue-600"
/>
</div>
{/* Quick Direction Selector */}
<div className="space-y-2">
<label className="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
<label className="text-xs font-semibold uppercase tracking-wider text-gray-500">
Quick Directions
</label>
<div className="grid grid-cols-4 gap-2">
@ -434,10 +434,10 @@ export default function CssGradientGeneratorPage() {
key={dir.angle}
type="button"
onClick={() => setAngle(dir.angle)}
className={`py-2 px-1.5 text-xs font-medium rounded-lg border text-center transition-all ${
className={`py-2 px-1.5 text-xs font-medium border text-center transition-all ${
angle === dir.angle
? "bg-blue-50 dark:bg-blue-900/30 border-blue-500 text-blue-700 dark:text-blue-300 font-semibold"
: "bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700"
? "bg-[#0a0a1a] border-blue-500 text-blue-400 font-semibold"
: "bg-[#0a0a0a] border-[#1a1a1a] text-gray-400 hover:bg-black"
}`}
>
{dir.label}
@ -452,17 +452,17 @@ export default function CssGradientGeneratorPage() {
{gradientType === "radial" && (
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium text-gray-900 dark:text-gray-100">
<label className="text-sm font-medium text-gray-200">
Radial Shape
</label>
<div className="grid grid-cols-2 gap-2">
<button
type="button"
onClick={() => setRadialShape("circle")}
className={`py-2 px-3 text-sm font-medium rounded-lg border transition-all ${
className={`py-2 px-3 text-sm font-medium border transition-all ${
radialShape === "circle"
? "bg-blue-600 border-blue-600 text-white"
: "bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700"
: "bg-[#0a0a0a] border-[#1a1a1a] text-gray-400 hover:bg-black"
}`}
>
Circle
@ -470,10 +470,10 @@ export default function CssGradientGeneratorPage() {
<button
type="button"
onClick={() => setRadialShape("ellipse")}
className={`py-2 px-3 text-sm font-medium rounded-lg border transition-all ${
className={`py-2 px-3 text-sm font-medium border transition-all ${
radialShape === "ellipse"
? "bg-blue-600 border-blue-600 text-white"
: "bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700"
: "bg-[#0a0a0a] border-[#1a1a1a] text-gray-400 hover:bg-black"
}`}
>
Ellipse
@ -482,13 +482,13 @@ export default function CssGradientGeneratorPage() {
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-gray-900 dark:text-gray-100">
<label className="text-sm font-medium text-gray-200">
Radial Position
</label>
<select
value={radialPosition}
onChange={(e) => setRadialPosition(e.target.value)}
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none"
>
{RADIAL_POSITIONS.map((pos) => (
<option key={pos.value} value={pos.value}>
@ -505,10 +505,10 @@ export default function CssGradientGeneratorPage() {
<div className="lg:col-span-7 space-y-4">
<div className="flex items-center justify-between">
<div>
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
<h3 className="text-sm font-semibold text-gray-200">
Color Stops ({colorStops.length})
</h3>
<p className="text-xs text-gray-500 dark:text-gray-400">
<p className="text-xs text-gray-500">
Customize start, middle, and end colors with position stops.
</p>
</div>
@ -516,7 +516,7 @@ export default function CssGradientGeneratorPage() {
<button
type="button"
onClick={reverseColors}
className="px-2.5 py-1.5 text-xs font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700 rounded-lg transition-colors flex items-center gap-1"
className="px-2.5 py-1.5 text-xs font-medium text-gray-400 bg-[#111] hover:bg-[#1a1a1a] transition-colors flex items-center gap-1"
title="Reverse Color Order"
>
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -533,10 +533,10 @@ export default function CssGradientGeneratorPage() {
type="button"
onClick={addColorStop}
disabled={colorStops.length >= 6}
className={`px-3 py-1.5 text-xs font-medium rounded-lg transition-colors flex items-center gap-1 ${
className={`px-3 py-1.5 text-xs font-medium transition-colors flex items-center gap-1 ${
colorStops.length >= 6
? "bg-gray-200 text-gray-400 cursor-not-allowed dark:bg-gray-800 dark:text-gray-600"
: "bg-blue-600 hover:bg-blue-700 text-white shadow-sm"
? "bg-[#1a1a1a] text-gray-400 cursor-not-allowed"
: "bg-blue-600 hover:bg-blue-700 text-white "
}`}
>
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -560,12 +560,12 @@ export default function CssGradientGeneratorPage() {
return (
<div
key={stop.id}
className="p-3.5 rounded-xl border border-gray-200 dark:border-gray-700/80 bg-gray-50/50 dark:bg-gray-800/50 space-y-2.5 transition-all"
className="p-3.5 border border-[#1a1a1a] bg-black/50 space-y-2.5 transition-all"
>
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2">
<span className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: stop.color }} />
<span className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<span className="w-2.5 h-2.5 rounded-none" style={{ backgroundColor: stop.color }} />
<span className="text-xs font-semibold text-gray-400">
{label}
</span>
</div>
@ -574,7 +574,7 @@ export default function CssGradientGeneratorPage() {
<button
type="button"
onClick={() => removeColorStop(stop.id)}
className="text-xs text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 transition-colors p-1 rounded"
className="text-xs text-red-500 hover:text-red-400 transition-colors p-1 rounded"
title="Remove Stop"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -592,7 +592,7 @@ export default function CssGradientGeneratorPage() {
<div className="grid grid-cols-1 sm:grid-cols-12 gap-3 items-center">
{/* Color Picker and Hex Input */}
<div className="sm:col-span-5 flex items-center gap-2">
<div className="relative flex-shrink-0 w-9 h-9 rounded-lg border border-gray-300 dark:border-gray-600 overflow-hidden shadow-inner cursor-pointer">
<div className="relative flex-shrink-0 w-9 h-9 border border-[#1a1a1a] overflow-hidden shadow-inner cursor-pointer">
<input
type="color"
value={stop.color}
@ -604,7 +604,7 @@ export default function CssGradientGeneratorPage() {
type="text"
value={stop.color}
onChange={(e) => updateStopColor(stop.id, e.target.value)}
className="w-full px-2.5 py-1.5 text-xs font-mono rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none uppercase"
className="w-full px-2.5 py-1.5 text-xs font-mono border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none uppercase"
placeholder="#000000"
/>
</div>
@ -617,7 +617,7 @@ export default function CssGradientGeneratorPage() {
max="100"
value={stop.stop}
onChange={(e) => updateStopPosition(stop.id, Number(e.target.value))}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 accent-blue-600"
className="w-full h-2 bg-[#1a1a1a] appearance-none cursor-pointer accent-blue-600"
/>
<div className="flex items-center gap-1">
<input
@ -626,9 +626,9 @@ export default function CssGradientGeneratorPage() {
max="100"
value={stop.stop}
onChange={(e) => updateStopPosition(stop.id, Number(e.target.value))}
className="w-14 px-1.5 py-1 text-xs font-mono text-right rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-14 px-1.5 py-1 text-xs font-mono text-right border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">%</span>
<span className="text-xs text-gray-500 font-mono">%</span>
</div>
</div>
</div>
@ -640,20 +640,20 @@ export default function CssGradientGeneratorPage() {
</div>
{/* Generated CSS Output Block */}
<div className="space-y-3 pt-4 border-t border-gray-200 dark:border-gray-800">
<div className="space-y-3 pt-4 border-t border-[#1a1a1a]">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div className="flex items-center gap-3">
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-500">
Generated CSS
</h2>
<div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-800 p-0.5 rounded-lg text-xs">
<div className="flex items-center gap-1 bg-[#111] p-0.5 text-xs">
<button
type="button"
onClick={() => setCodeFormat("standard")}
className={`px-2.5 py-1 rounded-md transition-colors ${
className={`px-2.5 py-1 transition-colors ${
codeFormat === "standard"
? "bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-medium shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900"
? "bg-[#0a0a0a] text-gray-200 font-medium shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Standard
@ -661,10 +661,10 @@ export default function CssGradientGeneratorPage() {
<button
type="button"
onClick={() => setCodeFormat("full")}
className={`px-2.5 py-1 rounded-md transition-colors ${
className={`px-2.5 py-1 transition-colors ${
codeFormat === "full"
? "bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-medium shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900"
? "bg-[#0a0a0a] text-gray-200 font-medium shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Full CSS
@ -672,10 +672,10 @@ export default function CssGradientGeneratorPage() {
<button
type="button"
onClick={() => setCodeFormat("tailwind")}
className={`px-2.5 py-1 rounded-md transition-colors ${
className={`px-2.5 py-1 transition-colors ${
codeFormat === "tailwind"
? "bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-medium shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900"
? "bg-[#0a0a0a] text-gray-200 font-medium shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Tailwind
@ -686,7 +686,7 @@ export default function CssGradientGeneratorPage() {
<button
type="button"
onClick={handleCopy}
className="inline-flex items-center justify-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium text-sm transition-colors shadow-sm cursor-pointer"
className="inline-flex items-center justify-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium text-sm transition-colors cursor-pointer"
>
{copied ? (
<>
@ -712,7 +712,7 @@ export default function CssGradientGeneratorPage() {
</div>
<div className="relative group">
<pre className="w-full p-4 rounded-xl border border-gray-200 dark:border-gray-700 bg-gray-900 text-gray-100 font-mono text-sm overflow-x-auto leading-relaxed shadow-sm">
<pre className="w-full p-4 border border-[#1a1a1a] bg-gray-900 text-gray-100 font-mono text-sm overflow-x-auto leading-relaxed ">
<code>{generatedCode}</code>
</pre>
</div>

View file

@ -330,11 +330,11 @@ export default function CsvToJsonPage() {
>
<div className="space-y-5">
{/* Top Controls Toolbar */}
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-gray-200 pb-4 dark:border-gray-800">
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-[#1a1a1a] pb-4">
<div className="flex flex-wrap items-center gap-2">
<button
onClick={handleConvert}
className="inline-flex items-center gap-1.5 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 active:scale-95 cursor-pointer"
className="inline-flex items-center gap-1.5 bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 active:scale-95 cursor-pointer"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
@ -344,7 +344,7 @@ export default function CsvToJsonPage() {
<button
onClick={handleLoadSample}
className="inline-flex items-center gap-1.5 rounded-lg bg-blue-50 px-3 py-2 text-sm font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:bg-blue-950/40 dark:text-blue-300 dark:hover:bg-blue-900/50 cursor-pointer"
className="inline-flex items-center gap-1.5 bg-[#0a0a1a] px-3 py-2 text-sm font-medium text-blue-400 transition-colors hover:bg-[#0a0a1a] cursor-pointer"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
@ -355,24 +355,24 @@ export default function CsvToJsonPage() {
<button
onClick={handleClear}
disabled={!input && !output}
className="inline-flex items-center gap-1.5 rounded-lg bg-gray-100 px-3 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-rose-50 hover:text-rose-600 disabled:opacity-40 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-rose-950/30 dark:hover:text-rose-400 cursor-pointer"
className="inline-flex items-center gap-1.5 bg-[#111] px-3 py-2 text-sm font-medium text-gray-400 transition-colors hover:bg-rose-50 hover:text-rose-600 disabled:opacity-40 cursor-pointer"
>
Clear
</button>
</div>
{/* Options */}
<div className="flex flex-wrap items-center gap-3 text-sm text-gray-700 dark:text-gray-300">
<div className="flex flex-wrap items-center gap-3 text-sm text-gray-400">
{/* Delimiter */}
<div className="flex items-center gap-1.5">
<label htmlFor={delimiterSelectId} className="text-xs font-semibold text-gray-500 uppercase dark:text-gray-400">
<label htmlFor={delimiterSelectId} className="text-xs font-semibold text-gray-500 uppercase">
Delimiter:
</label>
<select
id={delimiterSelectId}
value={delimiterSetting}
onChange={(e) => setDelimiterSetting(e.target.value)}
className="rounded-md border border-gray-300 bg-white px-2 py-1 text-xs text-gray-800 shadow-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
className=" border border-[#1a1a1a] bg-[#0a0a0a] px-2 py-1 text-xs text-gray-300 shadow-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900"
>
<option value="auto">Auto-detect</option>
<option value=",">Comma (,)</option>
@ -384,14 +384,14 @@ export default function CsvToJsonPage() {
{/* Indent Selector */}
<div className="flex items-center gap-1.5">
<label htmlFor={indentSelectId} className="text-xs font-semibold text-gray-500 uppercase dark:text-gray-400">
<label htmlFor={indentSelectId} className="text-xs font-semibold text-gray-500 uppercase">
Indent:
</label>
<select
id={indentSelectId}
value={indent}
onChange={(e) => setIndent(e.target.value)}
className="rounded-md border border-gray-300 bg-white px-2 py-1 text-xs text-gray-800 shadow-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
className=" border border-[#1a1a1a] bg-[#0a0a0a] px-2 py-1 text-xs text-gray-300 shadow-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900"
>
<option value="2">2 Spaces</option>
<option value="4">4 Spaces</option>
@ -403,34 +403,34 @@ export default function CsvToJsonPage() {
</div>
{/* Toggles Strip */}
<div className="flex flex-wrap items-center gap-5 text-xs text-gray-600 dark:text-gray-400">
<label className="flex cursor-pointer items-center gap-1.5 select-none hover:text-gray-900 dark:hover:text-gray-100">
<div className="flex flex-wrap items-center gap-5 text-xs text-gray-600">
<label className="flex cursor-pointer items-center gap-1.5 select-none hover:text-gray-200">
<input
type="checkbox"
checked={hasHeaders}
onChange={(e) => setHasHeaders(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span className="font-medium">First row as headers (keys)</span>
</label>
<label className="flex cursor-pointer items-center gap-1.5 select-none hover:text-gray-900 dark:hover:text-gray-100">
<label className="flex cursor-pointer items-center gap-1.5 select-none hover:text-gray-200">
<input
type="checkbox"
checked={autoParseTypes}
onChange={(e) => setAutoParseTypes(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>Auto-parse numbers, booleans &amp; null</span>
</label>
<label className="flex cursor-pointer items-center gap-1.5 select-none hover:text-gray-900 dark:hover:text-gray-100">
<label className="flex cursor-pointer items-center gap-1.5 select-none hover:text-gray-200">
<input
type="checkbox"
checked={unflatten}
disabled={!hasHeaders}
onChange={(e) => setUnflatten(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 disabled:opacity-40 dark:border-gray-700 dark:bg-gray-800"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900 disabled:opacity-40"
/>
<span>Unflatten dot keys (e.g. <code>address.city</code> &rarr; object)</span>
</label>
@ -438,9 +438,9 @@ export default function CsvToJsonPage() {
{/* Status Notification Banner */}
{status.type === "success" && (
<div className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-emerald-300 bg-emerald-50 px-4 py-3 text-sm text-emerald-900 dark:border-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-300">
<div className="flex flex-wrap items-center justify-between gap-2 border border-emerald-300 bg-emerald-50 px-4 py-3 text-sm text-emerald-900">
<div className="flex items-center gap-2">
<svg className="h-5 w-5 shrink-0 text-emerald-600 dark:text-emerald-400" fill="currentColor" viewBox="0 0 20 20">
<svg className="h-5 w-5 shrink-0 text-emerald-600" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
@ -449,7 +449,7 @@ export default function CsvToJsonPage() {
</svg>
<span className="font-medium">{status.message}</span>
</div>
<div className="flex flex-wrap items-center gap-3 text-xs text-emerald-800 dark:text-emerald-300">
<div className="flex flex-wrap items-center gap-3 text-xs text-emerald-800">
{status.rowCount !== undefined && <span>Rows: <strong>{status.rowCount}</strong></span>}
{status.colCount !== undefined && <span>Columns: <strong>{status.colCount}</strong></span>}
{status.byteSize !== undefined && <span>Size: <strong>{formatBytes(status.byteSize)}</strong></span>}
@ -458,9 +458,9 @@ export default function CsvToJsonPage() {
)}
{status.type === "error" && (
<div className="rounded-lg border border-red-300 bg-red-50 p-4 text-sm text-red-900 dark:border-red-800 dark:bg-red-950/40 dark:text-red-300">
<div className=" border border-red-900 bg-[#1a0a0a] p-4 text-sm text-red-900">
<div className="flex items-start gap-2">
<svg className="mt-0.5 h-5 w-5 shrink-0 text-red-600 dark:text-red-400" fill="currentColor" viewBox="0 0 20 20">
<svg className="mt-0.5 h-5 w-5 shrink-0 text-red-400" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
@ -468,8 +468,8 @@ export default function CsvToJsonPage() {
/>
</svg>
<div>
<p className="font-semibold text-red-800 dark:text-red-200">Error Parsing CSV</p>
<p className="mt-1 font-mono text-xs text-red-700 dark:text-red-300">{status.message}</p>
<p className="font-semibold text-red-400">Error Parsing CSV</p>
<p className="mt-1 font-mono text-xs text-red-400">{status.message}</p>
</div>
</div>
</div>
@ -481,8 +481,8 @@ export default function CsvToJsonPage() {
<div className="flex flex-col">
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-800 dark:text-gray-200">Input CSV</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
<span className="text-sm font-semibold text-gray-300">Input CSV</span>
<span className="text-xs text-gray-500">
({inputLineCount} {inputLineCount === 1 ? "line" : "lines"}, {input.length} chars)
</span>
</div>
@ -496,14 +496,14 @@ export default function CsvToJsonPage() {
// Permission denied
}
}}
className="rounded px-2 py-1 text-xs font-medium text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800 cursor-pointer"
className="rounded px-2 py-1 text-xs font-medium text-gray-600 hover:bg-[#111] cursor-pointer"
title="Paste from clipboard"
>
Paste
</button>
<button
onClick={() => setInput("")}
className="rounded px-2 py-1 text-xs font-medium text-gray-500 hover:bg-gray-100 hover:text-red-600 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-red-400 cursor-pointer"
className="rounded px-2 py-1 text-xs font-medium text-gray-500 hover:bg-[#111] hover:text-red-400 cursor-pointer"
title="Clear input"
>
Clear
@ -516,7 +516,7 @@ export default function CsvToJsonPage() {
placeholder="Paste raw CSV text here (comma, semicolon, or tab separated)..."
rows={18}
spellCheck={false}
className="w-full flex-1 resize-y rounded-lg border border-gray-300 bg-white p-3 font-mono text-sm leading-relaxed text-gray-900 shadow-xs focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-500"
className="w-full flex-1 resize-y border border-[#1a1a1a] bg-[#0a0a0a] p-3 font-mono text-sm leading-relaxed text-gray-200 shadow-xs focus:border-blue-500 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
@ -524,8 +524,8 @@ export default function CsvToJsonPage() {
<div className="flex flex-col">
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-800 dark:text-gray-200">JSON Output</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
<span className="text-sm font-semibold text-gray-300">JSON Output</span>
<span className="text-xs text-gray-500">
({outputLineCount} {outputLineCount === 1 ? "line" : "lines"}, {output.length} chars)
</span>
</div>
@ -533,7 +533,7 @@ export default function CsvToJsonPage() {
<button
onClick={handleDownload}
disabled={!output}
className="inline-flex items-center gap-1 rounded px-2.5 py-1 text-xs font-medium text-gray-700 hover:bg-gray-100 disabled:opacity-40 dark:text-gray-300 dark:hover:bg-gray-800 cursor-pointer"
className="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium text-gray-400 hover:bg-[#111] disabled:opacity-40 cursor-pointer"
title="Download as JSON file"
>
<svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -544,7 +544,7 @@ export default function CsvToJsonPage() {
<button
onClick={handleCopy}
disabled={!output}
className="inline-flex items-center gap-1 rounded-md bg-blue-600 px-3 py-1 text-xs font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 disabled:opacity-40 cursor-pointer"
className="inline-flex items-center gap-1 bg-blue-600 px-3 py-1 text-xs font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 disabled:opacity-40 cursor-pointer"
title="Copy JSON to clipboard"
>
{copied ? (
@ -576,29 +576,29 @@ export default function CsvToJsonPage() {
placeholder="Formatted JSON array will appear here..."
rows={18}
spellCheck={false}
className="w-full flex-1 resize-y rounded-lg border border-gray-300 bg-gray-50 p-3 font-mono text-sm leading-relaxed text-gray-900 shadow-xs focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none dark:border-gray-700 dark:bg-gray-900/80 dark:text-gray-100 dark:placeholder-gray-500"
className="w-full flex-1 resize-y border border-[#1a1a1a] bg-black p-3 font-mono text-sm leading-relaxed text-gray-200 shadow-xs focus:border-blue-500 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
</div>
{/* Feature Highlights & Guide */}
<div className="mt-8 rounded-xl border border-gray-200 bg-gray-50/70 p-5 text-sm text-gray-600 dark:border-gray-800 dark:bg-gray-800/30 dark:text-gray-400">
<h3 className="font-semibold text-gray-900 dark:text-gray-100">About CSV to JSON Converter</h3>
<div className="mt-8 border border-[#1a1a1a] bg-black/70 p-5 text-sm text-gray-600">
<h3 className="font-semibold text-gray-200">About CSV to JSON Converter</h3>
<div className="mt-3 grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">🔍 Smart Delimiter Detection</p>
<p className="font-medium text-gray-300">🔍 Smart Delimiter Detection</p>
<p className="mt-1 text-xs leading-normal">
Automatically detects whether your data uses commas, tabs, semicolons, or pipes, or choose manually.
</p>
</div>
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">🌳 Nested Object Reconstruction</p>
<p className="font-medium text-gray-300">🌳 Nested Object Reconstruction</p>
<p className="mt-1 text-xs leading-normal">
Reconstitutes dot-notation headers (such as <code>address.city</code>) back into rich nested JSON objects.
</p>
</div>
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">🛡️ Full RFC 4180 Support</p>
<p className="font-medium text-gray-300">🛡️ Full RFC 4180 Support</p>
<p className="mt-1 text-xs leading-normal">
Properly processes escaped quotes, multi-line values, special characters, and numeric types.
</p>

View file

@ -255,12 +255,12 @@ export default function DiffCheckerPage() {
<div className="space-y-6">
{/* Controls and Stats Bar */}
<div className="flex flex-wrap items-center justify-between gap-3 pb-3 border-b border-gray-200 dark:border-gray-700">
<div className="flex flex-wrap items-center justify-between gap-3 pb-3 border-b border-[#1a1a1a]">
<div className="flex flex-wrap items-center gap-2">
<button
type="button"
onClick={handleSwap}
className="px-3.5 py-1.5 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200 rounded-lg text-xs font-semibold transition-colors cursor-pointer flex items-center gap-1.5"
className="px-3.5 py-1.5 bg-[#111] hover:bg-[#1a1a1a] text-gray-400 text-xs font-semibold transition-colors cursor-pointer flex items-center gap-1.5"
>
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
@ -270,7 +270,7 @@ export default function DiffCheckerPage() {
<button
type="button"
onClick={handleLoadSample}
className="px-3.5 py-1.5 bg-blue-50 dark:bg-blue-950/40 hover:bg-blue-100 dark:hover:bg-blue-900/50 text-blue-700 dark:text-blue-300 rounded-lg text-xs font-semibold transition-colors cursor-pointer"
className="px-3.5 py-1.5 bg-[#0a0a1a] hover:bg-[#0a0a1a] text-blue-400 text-xs font-semibold transition-colors cursor-pointer"
>
Load Sample
</button>
@ -278,7 +278,7 @@ export default function DiffCheckerPage() {
type="button"
onClick={handleClear}
disabled={!original && !modified}
className="px-3.5 py-1.5 bg-gray-100 dark:bg-gray-800 hover:bg-rose-50 dark:hover:bg-rose-950/30 text-gray-700 dark:text-gray-300 hover:text-rose-600 dark:hover:text-rose-400 disabled:opacity-40 rounded-lg text-xs font-semibold transition-colors cursor-pointer"
className="px-3.5 py-1.5 bg-[#111] hover:bg-rose-50 text-gray-400 hover:text-rose-600 disabled:opacity-40 text-xs font-semibold transition-colors cursor-pointer"
>
Clear
</button>
@ -286,34 +286,34 @@ export default function DiffCheckerPage() {
{/* Options & View mode */}
<div className="flex flex-wrap items-center gap-4 text-xs">
<label className="inline-flex items-center gap-1.5 cursor-pointer text-gray-700 dark:text-gray-300 select-none">
<label className="inline-flex items-center gap-1.5 cursor-pointer text-gray-400 select-none">
<input
type="checkbox"
checked={ignoreWhitespace}
onChange={(e) => setIgnoreWhitespace(e.target.checked)}
className="w-3.5 h-3.5 text-blue-600 rounded border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-3.5 h-3.5 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>Ignore whitespace</span>
</label>
<label className="inline-flex items-center gap-1.5 cursor-pointer text-gray-700 dark:text-gray-300 select-none">
<label className="inline-flex items-center gap-1.5 cursor-pointer text-gray-400 select-none">
<input
type="checkbox"
checked={ignoreCase}
onChange={(e) => setIgnoreCase(e.target.checked)}
className="w-3.5 h-3.5 text-blue-600 rounded border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-3.5 h-3.5 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>Ignore case</span>
</label>
{/* View Mode Toggle */}
<div className="flex items-center rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-100 dark:bg-gray-800 p-0.5">
<div className="flex items-center border border-[#1a1a1a] bg-[#111] p-0.5">
<button
type="button"
onClick={() => setViewMode("unified")}
className={`px-2.5 py-1 rounded-md text-xs font-medium transition-colors cursor-pointer ${
className={`px-2.5 py-1 text-xs font-medium transition-colors cursor-pointer ${
viewMode === "unified"
? "bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
? "bg-[#0a0a0a] text-gray-200 shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Unified
@ -321,10 +321,10 @@ export default function DiffCheckerPage() {
<button
type="button"
onClick={() => setViewMode("split")}
className={`px-2.5 py-1 rounded-md text-xs font-medium transition-colors cursor-pointer ${
className={`px-2.5 py-1 text-xs font-medium transition-colors cursor-pointer ${
viewMode === "split"
? "bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
? "bg-[#0a0a0a] text-gray-200 shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Split
@ -339,9 +339,9 @@ export default function DiffCheckerPage() {
<div className="flex items-center justify-between">
<label
htmlFor="diff-original-text"
className="text-xs font-bold uppercase tracking-wider text-gray-600 dark:text-gray-300 flex items-center gap-1.5"
className="text-xs font-bold uppercase tracking-wider text-gray-600 flex items-center gap-1.5"
>
<span className="w-2.5 h-2.5 rounded-full bg-rose-500 inline-block" />
<span className="w-2.5 h-2.5 rounded-none bg-rose-500 inline-block" />
Original Text
</label>
<span className="text-xs text-gray-400 font-mono">
@ -354,7 +354,7 @@ export default function DiffCheckerPage() {
value={original}
onChange={(e) => setOriginal(e.target.value)}
placeholder="Paste original / before text..."
className="w-full p-3 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono text-xs focus:ring-2 focus:ring-blue-500 focus:outline-none resize-y leading-relaxed shadow-inner"
className="w-full p-3 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-mono text-xs focus:ring-1 focus:ring-blue-900 focus:outline-none resize-y leading-relaxed shadow-inner"
/>
</div>
@ -362,9 +362,9 @@ export default function DiffCheckerPage() {
<div className="flex items-center justify-between">
<label
htmlFor="diff-modified-text"
className="text-xs font-bold uppercase tracking-wider text-gray-600 dark:text-gray-300 flex items-center gap-1.5"
className="text-xs font-bold uppercase tracking-wider text-gray-600 flex items-center gap-1.5"
>
<span className="w-2.5 h-2.5 rounded-full bg-emerald-500 inline-block" />
<span className="w-2.5 h-2.5 rounded-none bg-emerald-500 inline-block" />
Modified Text
</label>
<span className="text-xs text-gray-400 font-mono">
@ -377,45 +377,45 @@ export default function DiffCheckerPage() {
value={modified}
onChange={(e) => setModified(e.target.value)}
placeholder="Paste modified / after text..."
className="w-full p-3 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono text-xs focus:ring-2 focus:ring-blue-500 focus:outline-none resize-y leading-relaxed shadow-inner"
className="w-full p-3 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-mono text-xs focus:ring-1 focus:ring-blue-900 focus:outline-none resize-y leading-relaxed shadow-inner"
/>
</div>
</div>
{/* Diff Stats Banner */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<div className="rounded-xl border border-emerald-200 bg-emerald-50/70 dark:border-emerald-900/50 dark:bg-emerald-950/20 p-3 text-center">
<span className="text-xs font-semibold uppercase tracking-wider text-emerald-700 dark:text-emerald-400">
<div className=" border border-emerald-200 bg-emerald-50/70 p-3 text-center">
<span className="text-xs font-semibold uppercase tracking-wider text-emerald-700">
+ Lines Added
</span>
<p className="mt-1 text-2xl font-extrabold text-emerald-900 dark:text-emerald-200">
<p className="mt-1 text-2xl font-extrabold text-emerald-900">
+{stats.added}
</p>
</div>
<div className="rounded-xl border border-rose-200 bg-rose-50/70 dark:border-rose-900/50 dark:bg-rose-950/20 p-3 text-center">
<span className="text-xs font-semibold uppercase tracking-wider text-rose-700 dark:text-rose-400">
<div className=" border border-rose-200 bg-rose-50/70 p-3 text-center">
<span className="text-xs font-semibold uppercase tracking-wider text-rose-700">
- Lines Removed
</span>
<p className="mt-1 text-2xl font-extrabold text-rose-900 dark:text-rose-200">
<p className="mt-1 text-2xl font-extrabold text-rose-900">
-{stats.removed}
</p>
</div>
<div className="rounded-xl border border-gray-200 bg-gray-50/70 dark:border-gray-800 dark:bg-gray-850 p-3 text-center">
<span className="text-xs font-semibold uppercase tracking-wider text-gray-600 dark:text-gray-400">
<div className=" border border-[#1a1a1a] bg-black/70 p-3 text-center">
<span className="text-xs font-semibold uppercase tracking-wider text-gray-600">
Unchanged Lines
</span>
<p className="mt-1 text-2xl font-extrabold text-gray-800 dark:text-gray-200">
<p className="mt-1 text-2xl font-extrabold text-gray-300">
{stats.unchanged}
</p>
</div>
<div className="rounded-xl border border-blue-200 bg-blue-50/70 dark:border-blue-900/50 dark:bg-blue-950/20 p-3 text-center">
<span className="text-xs font-semibold uppercase tracking-wider text-blue-700 dark:text-blue-400">
<div className=" border border-blue-900 bg-[#0a0a1a]/70 p-3 text-center">
<span className="text-xs font-semibold uppercase tracking-wider text-blue-400">
Similarity
</span>
<p className="mt-1 text-2xl font-extrabold text-blue-900 dark:text-blue-200">
<p className="mt-1 text-2xl font-extrabold text-blue-900">
{stats.similarity}%
</p>
</div>
@ -424,7 +424,7 @@ export default function DiffCheckerPage() {
{/* Diff Output Viewer */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<h3 className="text-sm font-bold text-gray-900 dark:text-gray-100 flex items-center gap-2">
<h3 className="text-sm font-bold text-gray-200 flex items-center gap-2">
<span>Diff Comparison Output</span>
<span className="text-xs font-normal text-gray-500">
({viewMode === "unified" ? "Unified View" : "Split View"})
@ -435,45 +435,45 @@ export default function DiffCheckerPage() {
type="button"
onClick={handleCopyUnifiedDiff}
disabled={diffResult.length === 0}
className="px-3 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-40 text-gray-700 dark:text-gray-300 rounded text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
className="px-3 py-1 bg-[#111] hover:bg-[#1a1a1a] disabled:opacity-40 text-gray-400 text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
>
{copiedPatch ? "Copied Patch!" : "Copy Diff Patch"}
</button>
</div>
{diffResult.length === 0 ? (
<div className="p-8 text-center rounded-xl border border-dashed border-gray-300 dark:border-gray-700 text-gray-400 text-sm">
<div className="p-8 text-center border border-dashed border-[#1a1a1a] text-gray-400 text-sm">
Enter original and modified texts above to calculate diff.
</div>
) : viewMode === "unified" ? (
/* Unified Diff View */
<div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 overflow-hidden font-mono text-xs shadow-xs">
<div className="max-h-[500px] overflow-y-auto overflow-x-auto divide-y divide-gray-100 dark:divide-gray-800">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] overflow-hidden font-mono text-xs shadow-xs">
<div className="max-h-[500px] overflow-y-auto overflow-x-auto divide-y divide-gray-100">
{diffResult.map((item, idx) => {
let bgClass = "bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200";
let badge = <span className="text-gray-300 dark:text-gray-600 select-none">&nbsp;</span>;
let bgClass = "bg-[#0a0a0a] text-gray-300";
let badge = <span className="text-gray-300 select-none">&nbsp;</span>;
let borderClass = "border-l-4 border-transparent";
if (item.type === "added") {
bgClass = "bg-emerald-50/80 dark:bg-emerald-950/30 text-emerald-950 dark:text-emerald-200";
bgClass = "bg-emerald-50/80 text-emerald-950";
borderClass = "border-l-4 border-emerald-500";
badge = <span className="text-emerald-600 dark:text-emerald-400 font-bold select-none">+</span>;
badge = <span className="text-emerald-600 font-bold select-none">+</span>;
} else if (item.type === "removed") {
bgClass = "bg-rose-50/80 dark:bg-rose-950/30 text-rose-950 dark:text-rose-200";
bgClass = "bg-rose-50/80 text-rose-950";
borderClass = "border-l-4 border-rose-500";
badge = <span className="text-rose-600 dark:text-rose-400 font-bold select-none">-</span>;
badge = <span className="text-rose-600 font-bold select-none">-</span>;
}
return (
<div
key={idx}
className={`flex items-stretch hover:bg-black/5 dark:hover:bg-white/5 transition-colors ${bgClass} ${borderClass}`}
className={`flex items-stretch hover:bg-black/5 transition-colors ${bgClass} ${borderClass}`}
>
{/* Line numbers */}
<div className="w-10 py-1 pr-2 text-right text-gray-400 dark:text-gray-500 select-none border-r border-gray-200 dark:border-gray-800 shrink-0">
<div className="w-10 py-1 pr-2 text-right text-gray-400 select-none border-r border-[#1a1a1a] shrink-0">
{item.origLine || ""}
</div>
<div className="w-10 py-1 pr-2 text-right text-gray-400 dark:text-gray-500 select-none border-r border-gray-200 dark:border-gray-800 shrink-0">
<div className="w-10 py-1 pr-2 text-right text-gray-400 select-none border-r border-[#1a1a1a] shrink-0">
{item.modLine || ""}
</div>
{/* Symbol */}
@ -491,25 +491,25 @@ export default function DiffCheckerPage() {
</div>
) : (
/* Split Diff View */
<div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 overflow-hidden font-mono text-xs shadow-xs">
<div className="grid grid-cols-2 bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 py-1.5 px-3 text-xs font-semibold text-gray-600 dark:text-gray-300">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] overflow-hidden font-mono text-xs shadow-xs">
<div className="grid grid-cols-2 bg-[#111] border-b border-[#1a1a1a] py-1.5 px-3 text-xs font-semibold text-gray-600">
<div>Original (Before)</div>
<div>Modified (After)</div>
</div>
<div className="max-h-[500px] overflow-y-auto overflow-x-auto divide-y divide-gray-100 dark:divide-gray-800">
<div className="max-h-[500px] overflow-y-auto overflow-x-auto divide-y divide-gray-100">
{splitRows.map((row, rIdx) => (
<div key={rIdx} className="grid grid-cols-2 divide-x divide-gray-200 dark:divide-gray-800">
<div key={rIdx} className="grid grid-cols-2 divide-x divide-[#1a1a1a]">
{/* Left Column */}
<div
className={`flex items-stretch ${
row.left
? row.left.type === "removed"
? "bg-rose-50/80 dark:bg-rose-950/30 text-rose-950 dark:text-rose-200 border-l-4 border-rose-500"
: "bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200 border-l-4 border-transparent"
: "bg-gray-50 dark:bg-gray-950/50"
? "bg-rose-50/80 text-rose-950 border-l-4 border-rose-500"
: "bg-[#0a0a0a] text-gray-300 border-l-4 border-transparent"
: "bg-black"
}`}
>
<div className="w-9 py-1 pr-1.5 text-right text-gray-400 dark:text-gray-500 select-none border-r border-gray-200 dark:border-gray-800 shrink-0">
<div className="w-9 py-1 pr-1.5 text-right text-gray-400 select-none border-r border-[#1a1a1a] shrink-0">
{row.left?.line || ""}
</div>
<div className="w-5 py-1 text-center shrink-0 font-bold select-none">
@ -525,12 +525,12 @@ export default function DiffCheckerPage() {
className={`flex items-stretch ${
row.right
? row.right.type === "added"
? "bg-emerald-50/80 dark:bg-emerald-950/30 text-emerald-950 dark:text-emerald-200 border-l-4 border-emerald-500"
: "bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200 border-l-4 border-transparent"
: "bg-gray-50 dark:bg-gray-950/50"
? "bg-emerald-50/80 text-emerald-950 border-l-4 border-emerald-500"
: "bg-[#0a0a0a] text-gray-300 border-l-4 border-transparent"
: "bg-black"
}`}
>
<div className="w-9 py-1 pr-1.5 text-right text-gray-400 dark:text-gray-500 select-none border-r border-gray-200 dark:border-gray-800 shrink-0">
<div className="w-9 py-1 pr-1.5 text-right text-gray-400 select-none border-r border-[#1a1a1a] shrink-0">
{row.right?.line || ""}
</div>
<div className="w-5 py-1 text-center shrink-0 font-bold select-none">

View file

@ -286,11 +286,11 @@ export default function HashGeneratorPage() {
<div className="mb-2 flex flex-wrap items-center justify-between gap-2">
<label
htmlFor="hash-input"
className="text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300"
className="text-xs font-semibold uppercase tracking-wider text-gray-400"
>
Input Text
</label>
<div className="flex items-center gap-3 text-xs text-gray-500 dark:text-gray-400">
<div className="flex items-center gap-3 text-xs text-gray-500">
<span>{inputText.length} characters</span>
<span>•</span>
<span>{byteLength} UTF-8 bytes</span>
@ -303,19 +303,19 @@ export default function HashGeneratorPage() {
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="Type or paste text to generate cryptographic hashes..."
className="w-full rounded-lg border border-gray-300 bg-white p-3 font-mono text-sm text-gray-900 shadow-inner focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] p-3 font-mono text-sm text-gray-200 shadow-inner focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900/20"
/>
{/* Quick Presets and Clear */}
<div className="mt-2.5 flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-1.5">
<span className="text-xs text-gray-500 dark:text-gray-400">Samples:</span>
<span className="text-xs text-gray-500">Samples:</span>
{sampleTexts.map((sample) => (
<button
key={sample.label}
type="button"
onClick={() => setInputText(sample.text)}
className="rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
className=" bg-[#111] px-2 py-1 text-xs font-medium text-gray-400 hover:bg-[#1a1a1a]"
>
{sample.label}
</button>
@ -326,7 +326,7 @@ export default function HashGeneratorPage() {
<button
type="button"
onClick={() => setInputText("")}
className="text-xs font-medium text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
className="text-xs font-medium text-red-400 hover:text-red-400"
>
Clear input
</button>
@ -335,18 +335,18 @@ export default function HashGeneratorPage() {
</div>
{/* Hashes Section Toolbar */}
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-gray-200 pb-3 dark:border-gray-700">
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-[#1a1a1a] pb-3">
<h2 className="text-sm font-semibold uppercase tracking-wider text-gray-400">
Computed Hashes
</h2>
<div className="flex flex-wrap items-center gap-3">
<label className="inline-flex cursor-pointer items-center gap-2 text-xs font-medium text-gray-700 dark:text-gray-300">
<label className="inline-flex cursor-pointer items-center gap-2 text-xs font-medium text-gray-400">
<input
type="checkbox"
checked={uppercase}
onChange={(e) => setUppercase(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>UPPERCASE HEX</span>
</label>
@ -354,10 +354,10 @@ export default function HashGeneratorPage() {
<button
type="button"
onClick={copyAllHashes}
className={`inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors ${
className={`inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium transition-colors ${
copiedAll
? "bg-green-600 text-white"
: "bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700"
: "bg-[#111] text-gray-400 hover:bg-[#1a1a1a]"
}`}
>
{copiedAll ? (
@ -393,25 +393,25 @@ export default function HashGeneratorPage() {
return (
<div
key={item.algorithm}
className="rounded-lg border border-gray-200 bg-gray-50/50 p-4 transition-all hover:border-gray-300 dark:border-gray-700/80 dark:bg-gray-800/40 dark:hover:border-gray-600"
className=" border border-[#1a1a1a] bg-black/50 p-4 transition-all hover:border-[#1a1a1a]"
>
<div className="mb-2 flex flex-wrap items-center justify-between gap-2">
<div className="flex items-center gap-2">
<span className="font-bold text-gray-900 dark:text-white">
<span className="font-bold text-gray-200">
{item.algorithm}
</span>
<span className="rounded bg-gray-200 px-1.5 py-0.5 text-[10px] font-semibold text-gray-700 dark:bg-gray-700 dark:text-gray-300">
<span className="rounded bg-[#1a1a1a] px-1.5 py-0.5 text-[10px] font-semibold text-gray-400">
{item.bitLength}-bit ({item.hash.length} chars)
</span>
</div>
<span className="text-xs text-gray-500 dark:text-gray-400">
<span className="text-xs text-gray-500">
{item.securityNote}
</span>
</div>
<div className="flex items-center gap-2">
<div className="relative flex-1 overflow-hidden rounded-md border border-gray-300 bg-white p-2.5 dark:border-gray-700 dark:bg-gray-900">
<p className="select-all break-all font-mono text-xs font-medium text-gray-800 dark:text-gray-200 sm:text-sm">
<div className="relative flex-1 overflow-hidden border border-[#1a1a1a] bg-[#0a0a0a] p-2.5">
<p className="select-all break-all font-mono text-xs font-medium text-gray-300 sm:text-sm">
{displayHash || "—"}
</p>
</div>
@ -420,7 +420,7 @@ export default function HashGeneratorPage() {
type="button"
onClick={() => copyHash(item.hash, item.algorithm)}
aria-label={`Copy ${item.algorithm} hash`}
className={`flex shrink-0 items-center gap-1.5 rounded-md px-3 py-2 text-xs font-medium transition-all ${
className={`flex shrink-0 items-center gap-1.5 px-3 py-2 text-xs font-medium transition-all ${
isCopied
? "bg-green-600 text-white"
: "bg-blue-600 text-white hover:bg-blue-700 active:scale-95"
@ -454,8 +454,8 @@ export default function HashGeneratorPage() {
</div>
{/* Educational / Comparison Guide */}
<div className="rounded-lg border border-gray-100 bg-gray-50/80 p-4 text-xs text-gray-500 dark:border-gray-800 dark:bg-gray-800/40 dark:text-gray-400">
<h4 className="mb-2 font-semibold text-gray-800 dark:text-gray-200">
<div className=" border border-gray-100 bg-black/80 p-4 text-xs text-gray-500">
<h4 className="mb-2 font-semibold text-gray-300">
About Cryptographic Hashes
</h4>
<p className="leading-relaxed">

View file

@ -188,12 +188,12 @@ export default function HtmlEntitiesPage() {
<div className="space-y-6">
{/* Controls Bar */}
<div className="flex flex-wrap items-center justify-between gap-3 pb-2 border-b border-gray-200 dark:border-gray-700">
<div className="flex flex-wrap items-center justify-between gap-3 pb-2 border-b border-[#1a1a1a]">
<div className="flex flex-wrap items-center gap-2">
<button
type="button"
onClick={handleEncode}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 active:bg-blue-800 text-white rounded-lg font-medium text-sm transition-colors shadow-sm cursor-pointer flex items-center gap-1.5"
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 active:bg-blue-800 text-white font-medium text-sm transition-colors cursor-pointer flex items-center gap-1.5"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
@ -203,7 +203,7 @@ export default function HtmlEntitiesPage() {
<button
type="button"
onClick={handleDecode}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800 text-white rounded-lg font-medium text-sm transition-colors shadow-sm cursor-pointer flex items-center gap-1.5"
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800 text-white font-medium text-sm transition-colors cursor-pointer flex items-center gap-1.5"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
@ -214,7 +214,7 @@ export default function HtmlEntitiesPage() {
type="button"
onClick={handleSwap}
disabled={!output && !input}
className="px-3 py-2 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed text-gray-700 dark:text-gray-200 rounded-lg text-sm font-medium transition-colors cursor-pointer flex items-center gap-1"
className="px-3 py-2 bg-[#111] hover:bg-[#1a1a1a] disabled:opacity-40 disabled:cursor-not-allowed text-gray-400 text-sm font-medium transition-colors cursor-pointer flex items-center gap-1"
title="Swap input and output"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -226,7 +226,7 @@ export default function HtmlEntitiesPage() {
type="button"
onClick={handleClear}
disabled={!input && !output}
className="px-3 py-2 bg-gray-100 dark:bg-gray-800 hover:bg-red-50 dark:hover:bg-red-950/30 text-gray-700 dark:text-gray-300 hover:text-red-600 dark:hover:text-red-400 disabled:opacity-40 disabled:cursor-not-allowed rounded-lg text-sm font-medium transition-colors cursor-pointer"
className="px-3 py-2 bg-[#111] hover:bg-[#1a0a0a] text-gray-400 hover:text-red-400 disabled:opacity-40 disabled:cursor-not-allowed text-sm font-medium transition-colors cursor-pointer"
>
Clear
</button>
@ -234,25 +234,25 @@ export default function HtmlEntitiesPage() {
{/* Sample buttons */}
<div className="flex items-center gap-2 text-xs">
<span className="text-gray-500 dark:text-gray-400 font-medium">Samples:</span>
<span className="text-gray-500 font-medium">Samples:</span>
<button
type="button"
onClick={() => loadSample("html")}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 rounded transition-colors cursor-pointer"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] text-gray-400 transition-colors cursor-pointer"
>
HTML Code
</button>
<button
type="button"
onClick={() => loadSample("symbols")}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 rounded transition-colors cursor-pointer"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] text-gray-400 transition-colors cursor-pointer"
>
Symbols &amp; Quotes
</button>
<button
type="button"
onClick={() => loadSample("entities")}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 rounded transition-colors cursor-pointer"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] text-gray-400 transition-colors cursor-pointer"
>
Encoded Entities
</button>
@ -260,8 +260,8 @@ export default function HtmlEntitiesPage() {
</div>
{/* Encoding Mode Selector */}
<div className="flex flex-wrap items-center gap-4 text-sm text-gray-700 dark:text-gray-300 bg-gray-50 dark:bg-gray-800/50 p-3 rounded-lg border border-gray-200 dark:border-gray-700/60">
<span className="font-medium text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider">
<div className="flex flex-wrap items-center gap-4 text-sm text-gray-400 bg-black p-3 border border-[#1a1a1a]">
<span className="font-medium text-xs text-gray-500 uppercase tracking-wider">
Encoding Format:
</span>
<div className="flex flex-wrap items-center gap-4">
@ -277,9 +277,9 @@ export default function HtmlEntitiesPage() {
encodeText(input, "basic");
}
}}
className="w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-4 h-4 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>Basic Special Chars <code className="text-xs bg-gray-200 dark:bg-gray-700 px-1 py-0.5 rounded font-mono">(&lt; &gt; &amp; &quot; &#39;)</code></span>
<span>Basic Special Chars <code className="text-xs bg-[#1a1a1a] px-1 py-0.5 font-mono">(&lt; &gt; &amp; &quot; &#39;)</code></span>
</label>
<label className="inline-flex items-center gap-1.5 cursor-pointer">
<input
@ -293,9 +293,9 @@ export default function HtmlEntitiesPage() {
encodeText(input, "extended");
}
}}
className="w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-4 h-4 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>Extended Named <code className="text-xs bg-gray-200 dark:bg-gray-700 px-1 py-0.5 rounded font-mono">(&amp;copy;, &amp;euro;, etc.)</code></span>
<span>Extended Named <code className="text-xs bg-[#1a1a1a] px-1 py-0.5 font-mono">(&amp;copy;, &amp;euro;, etc.)</code></span>
</label>
<label className="inline-flex items-center gap-1.5 cursor-pointer">
<input
@ -309,9 +309,9 @@ export default function HtmlEntitiesPage() {
encodeText(input, "decimal");
}
}}
className="w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-4 h-4 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>Decimal Numeric <code className="text-xs bg-gray-200 dark:bg-gray-700 px-1 py-0.5 rounded font-mono">(&amp;#60;)</code></span>
<span>Decimal Numeric <code className="text-xs bg-[#1a1a1a] px-1 py-0.5 font-mono">(&amp;#60;)</code></span>
</label>
<label className="inline-flex items-center gap-1.5 cursor-pointer">
<input
@ -325,16 +325,16 @@ export default function HtmlEntitiesPage() {
encodeText(input, "hex");
}
}}
className="w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-4 h-4 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>Hexadecimal <code className="text-xs bg-gray-200 dark:bg-gray-700 px-1 py-0.5 rounded font-mono">(&amp;#x3C;)</code></span>
<span>Hexadecimal <code className="text-xs bg-[#1a1a1a] px-1 py-0.5 font-mono">(&amp;#x3C;)</code></span>
</label>
</div>
</div>
{/* Error Notification */}
{error && (
<div className="flex items-start gap-3 p-4 bg-red-50 dark:bg-red-950/40 border border-red-200 dark:border-red-800 rounded-lg text-red-800 dark:text-red-300 text-sm">
<div className="flex items-start gap-3 p-4 bg-[#1a0a0a] border border-red-900 text-red-400 text-sm">
<svg className="w-5 h-5 text-red-500 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
@ -350,10 +350,10 @@ export default function HtmlEntitiesPage() {
{/* Input Area */}
<div className="flex flex-col space-y-2">
<div className="flex items-center justify-between">
<label htmlFor={inputId} className="font-medium text-sm text-gray-700 dark:text-gray-300">
<label htmlFor={inputId} className="font-medium text-sm text-gray-400">
Input Text / HTML
</label>
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
<span className="text-xs text-gray-500 font-mono">
{input.length} chars
</span>
</div>
@ -366,7 +366,7 @@ export default function HtmlEntitiesPage() {
}}
placeholder="Type or paste HTML code or text to encode/decode..."
rows={12}
className="w-full p-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none resize-y placeholder:text-gray-400 dark:placeholder:text-gray-500 leading-relaxed shadow-inner"
className="w-full p-3 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-mono text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none resize-y placeholder:text-gray-400 leading-relaxed shadow-inner"
/>
</div>
@ -374,31 +374,31 @@ export default function HtmlEntitiesPage() {
<div className="flex flex-col space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<label htmlFor={outputId} className="font-medium text-sm text-gray-700 dark:text-gray-300">
<label htmlFor={outputId} className="font-medium text-sm text-gray-400">
Output Result
</label>
{lastAction && (
<span className="px-2 py-0.5 text-xs rounded-full font-medium bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300">
<span className="px-2 py-0.5 text-xs rounded-none font-medium bg-[#0a0a1a] text-blue-400">
{lastAction === "encode" ? "HTML Entities Encoded" : "Decoded Plain Text"}
</span>
)}
</div>
<div className="flex items-center gap-3">
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
<span className="text-xs text-gray-500 font-mono">
{output.length} chars
</span>
<button
type="button"
onClick={handleCopy}
disabled={!output}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300 rounded text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] disabled:opacity-40 disabled:cursor-not-allowed text-gray-400 text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
>
{copied ? (
<>
<svg className="w-3.5 h-3.5 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="w-3.5 h-3.5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
<span className="text-green-600 dark:text-green-400 font-semibold">Copied!</span>
<span className="text-green-400 font-semibold">Copied!</span>
</>
) : (
<>
@ -417,38 +417,38 @@ export default function HtmlEntitiesPage() {
readOnly
placeholder="Output will appear here..."
rows={12}
className="w-full p-3 rounded-lg border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-900/60 text-gray-900 dark:text-gray-100 font-mono text-sm focus:outline-none resize-y placeholder:text-gray-400 dark:placeholder:text-gray-500 leading-relaxed select-all"
className="w-full p-3 border border-[#1a1a1a] bg-black text-gray-200 font-mono text-sm focus:outline-none resize-y placeholder:text-gray-400 leading-relaxed select-all"
/>
</div>
</div>
{/* HTML Entities Reference Table */}
<div className="mt-8 pt-6 border-t border-gray-200 dark:border-gray-800">
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-3">Key HTML Entities Reference</h3>
<div className="mt-8 pt-6 border-t border-[#1a1a1a]">
<h3 className="text-sm font-semibold text-gray-200 mb-3">Key HTML Entities Reference</h3>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-2 text-xs font-mono">
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 flex justify-between items-center">
<span className="text-gray-600 dark:text-gray-400 font-sans">&lt; (Less than)</span>
<span className="font-semibold text-blue-600 dark:text-blue-400">&amp;lt;</span>
<div className="p-2 bg-black border border-[#1a1a1a] flex justify-between items-center">
<span className="text-gray-600 font-sans">&lt; (Less than)</span>
<span className="font-semibold text-blue-600">&amp;lt;</span>
</div>
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 flex justify-between items-center">
<span className="text-gray-600 dark:text-gray-400 font-sans">&gt; (Greater)</span>
<span className="font-semibold text-blue-600 dark:text-blue-400">&amp;gt;</span>
<div className="p-2 bg-black border border-[#1a1a1a] flex justify-between items-center">
<span className="text-gray-600 font-sans">&gt; (Greater)</span>
<span className="font-semibold text-blue-600">&amp;gt;</span>
</div>
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 flex justify-between items-center">
<span className="text-gray-600 dark:text-gray-400 font-sans">&amp; (Ampersand)</span>
<span className="font-semibold text-blue-600 dark:text-blue-400">&amp;amp;</span>
<div className="p-2 bg-black border border-[#1a1a1a] flex justify-between items-center">
<span className="text-gray-600 font-sans">&amp; (Ampersand)</span>
<span className="font-semibold text-blue-600">&amp;amp;</span>
</div>
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 flex justify-between items-center">
<span className="text-gray-600 dark:text-gray-400 font-sans">&quot; (Double quote)</span>
<span className="font-semibold text-blue-600 dark:text-blue-400">&amp;quot;</span>
<div className="p-2 bg-black border border-[#1a1a1a] flex justify-between items-center">
<span className="text-gray-600 font-sans">&quot; (Double quote)</span>
<span className="font-semibold text-blue-600">&amp;quot;</span>
</div>
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 flex justify-between items-center">
<span className="text-gray-600 dark:text-gray-400 font-sans">&#39; (Single quote)</span>
<span className="font-semibold text-blue-600 dark:text-blue-400">&amp;#39;</span>
<div className="p-2 bg-black border border-[#1a1a1a] flex justify-between items-center">
<span className="text-gray-600 font-sans">&#39; (Single quote)</span>
<span className="font-semibold text-blue-600">&amp;#39;</span>
</div>
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 flex justify-between items-center">
<span className="text-gray-600 dark:text-gray-400 font-sans">&copy; (Copyright)</span>
<span className="font-semibold text-blue-600 dark:text-blue-400">&amp;copy;</span>
<div className="p-2 bg-black border border-[#1a1a1a] flex justify-between items-center">
<span className="text-gray-600 font-sans">&copy; (Copyright)</span>
<span className="font-semibold text-blue-600">&amp;copy;</span>
</div>
</div>
</div>

View file

@ -258,12 +258,12 @@ export default function ImageToBase64Page() {
<div className="space-y-6">
{/* Top Action / Samples Bar */}
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-gray-200 pb-3 dark:border-gray-700">
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-[#1a1a1a] pb-3">
<div className="flex flex-wrap items-center gap-2">
<button
type="button"
onClick={() => fileInputRef.current?.click()}
className="flex cursor-pointer items-center gap-1.5 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm transition-colors hover:bg-blue-700 active:bg-blue-800"
className="flex cursor-pointer items-center gap-1.5 bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 active:bg-blue-800"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l4-4m0 0l4 4m-4-4v12" />
@ -275,7 +275,7 @@ export default function ImageToBase64Page() {
<button
type="button"
onClick={handleClear}
className="flex cursor-pointer items-center gap-1 rounded-lg bg-gray-100 px-3 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-red-50 hover:text-red-600 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-red-950/40 dark:hover:text-red-400"
className="flex cursor-pointer items-center gap-1 bg-[#111] px-3 py-2 text-sm font-medium text-gray-400 transition-colors hover:bg-[#1a0a0a] hover:text-red-400"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
@ -286,18 +286,18 @@ export default function ImageToBase64Page() {
</div>
<div className="flex items-center gap-2 text-xs">
<span className="font-medium text-gray-500 dark:text-gray-400">Try Sample:</span>
<span className="font-medium text-gray-500">Try Sample:</span>
<button
type="button"
onClick={() => loadSampleImage("badge")}
className="cursor-pointer rounded bg-gray-100 px-2.5 py-1 text-gray-700 transition-colors hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
className="cursor-pointer bg-[#111] px-2.5 py-1 text-gray-400 transition-colors hover:bg-[#1a1a1a]"
>
Badge (PNG)
</button>
<button
type="button"
onClick={() => loadSampleImage("icon")}
className="cursor-pointer rounded bg-gray-100 px-2.5 py-1 text-gray-700 transition-colors hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
className="cursor-pointer bg-[#111] px-2.5 py-1 text-gray-400 transition-colors hover:bg-[#1a1a1a]"
>
Vector Icon (SVG)
</button>
@ -315,7 +315,7 @@ export default function ImageToBase64Page() {
{/* Error notification */}
{error && (
<div className="flex items-start gap-3 rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-800 dark:border-red-800 dark:bg-red-950/40 dark:text-red-300">
<div className="flex items-start gap-3 border border-red-900 bg-[#1a0a0a] p-4 text-sm text-red-400">
<svg className="mt-0.5 h-5 w-5 shrink-0 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
@ -333,47 +333,47 @@ export default function ImageToBase64Page() {
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={() => fileInputRef.current?.click()}
className={`flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-10 text-center transition-all ${
className={`flex cursor-pointer flex-col items-center justify-center border-2 border-dashed p-10 text-center transition-all ${
isDragging
? "border-blue-500 bg-blue-50/60 dark:border-blue-400 dark:bg-blue-950/30 scale-[1.01]"
: "border-gray-300 bg-white hover:border-blue-400 hover:bg-gray-50/50 dark:border-gray-700 dark:bg-gray-800/40 dark:hover:border-gray-600 dark:hover:bg-gray-800/80"
? "border-blue-500 bg-[#0a0a1a]/60 scale-[1.01]"
: "border-[#1a1a1a] bg-[#0a0a0a] hover:border-blue-400 hover:bg-black/50"
}`}
>
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-blue-50 dark:bg-blue-950/60 text-blue-600 dark:text-blue-400 mb-4">
<div className="flex h-16 w-16 items-center justify-center rounded-none bg-[#0a0a1a] text-blue-600 mb-4">
<svg className="h-8 w-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
<p className="text-base font-semibold text-gray-800 dark:text-gray-200">
Drag &amp; drop your image here, or <span className="text-blue-600 dark:text-blue-400 underline">browse files</span>
<p className="text-base font-semibold text-gray-300">
Drag &amp; drop your image here, or <span className="text-blue-600 underline">browse files</span>
</p>
<p className="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
<p className="mt-1.5 text-xs text-gray-500">
Supports PNG, JPG, JPEG, SVG, WebP, GIF, ICO, AVIF, BMP • Paste directly with Ctrl+V / ⌘V
</p>
<div className="mt-4 flex items-center gap-2 text-xs text-gray-400">
<span className="inline-block h-2 w-2 rounded-full bg-green-500"></span>
<span className="inline-block h-2 w-2 rounded-none bg-[#0a1a0a]0"></span>
<span>100% Client-side processing — No files uploaded to servers</span>
</div>
</div>
) : (
<div className="space-y-6">
{/* Image Preview & Details Card */}
<div className="grid grid-cols-1 gap-6 rounded-xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-700 dark:bg-gray-800 lg:grid-cols-12">
<div className="grid grid-cols-1 gap-6 border border-[#1a1a1a] bg-[#0a0a0a] p-5 lg:grid-cols-12">
{/* Preview Box */}
<div className="flex flex-col items-center justify-center lg:col-span-4">
<div className="relative flex max-h-56 min-h-[160px] w-full items-center justify-center overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700 bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] [background-size:12px_12px] dark:bg-[radial-gradient(#374151_1px,transparent_1px)] p-2">
<div className="relative flex max-h-56 min-h-[160px] w-full items-center justify-center overflow-hidden border border-[#1a1a1a] bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] [background-size:12px_12px](#374151_1px,transparent_1px)] p-2">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={image.dataUri}
alt={image.name}
className="max-h-48 max-w-full rounded object-contain shadow-sm"
className="max-h-48 max-w-full object-contain "
/>
</div>
<button
type="button"
onClick={() => fileInputRef.current?.click()}
className="mt-3 text-xs text-blue-600 hover:underline dark:text-blue-400 font-medium cursor-pointer"
className="mt-3 text-xs text-blue-600 hover:underline font-medium cursor-pointer"
>
Change Image
</button>
@ -382,41 +382,41 @@ export default function ImageToBase64Page() {
{/* Metadata & Quick Actions */}
<div className="flex flex-col justify-between space-y-4 lg:col-span-8">
<div>
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-gray-100 pb-3 dark:border-gray-700">
<h3 className="font-semibold text-gray-900 dark:text-white truncate max-w-xs sm:max-w-md" title={image.name}>
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-gray-100 pb-3">
<h3 className="font-semibold text-gray-200 truncate max-w-xs sm:max-w-md" title={image.name}>
{image.name}
</h3>
<span className="rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-semibold text-blue-800 dark:bg-blue-900/60 dark:text-blue-300">
<span className="rounded-none bg-[#0a0a1a] px-2.5 py-0.5 text-xs font-semibold text-blue-400">
{image.type}
</span>
</div>
{/* Details Grid */}
<div className="mt-3 grid grid-cols-2 gap-3 sm:grid-cols-4">
<div className="rounded-lg bg-gray-50 p-2.5 dark:bg-gray-900/60 border border-gray-200 dark:border-gray-700/60">
<span className="block text-[11px] font-medium text-gray-500 dark:text-gray-400 uppercase">Dimensions</span>
<span className="font-mono text-sm font-semibold text-gray-800 dark:text-gray-200">
<div className=" bg-black p-2.5 border border-[#1a1a1a]">
<span className="block text-[11px] font-medium text-gray-500 uppercase">Dimensions</span>
<span className="font-mono text-sm font-semibold text-gray-300">
{image.width > 0 ? `${image.width} × ${image.height} px` : "Vector (SVG)"}
</span>
</div>
<div className="rounded-lg bg-gray-50 p-2.5 dark:bg-gray-900/60 border border-gray-200 dark:border-gray-700/60">
<span className="block text-[11px] font-medium text-gray-500 dark:text-gray-400 uppercase">Original Size</span>
<span className="font-mono text-sm font-semibold text-gray-800 dark:text-gray-200">
<div className=" bg-black p-2.5 border border-[#1a1a1a]">
<span className="block text-[11px] font-medium text-gray-500 uppercase">Original Size</span>
<span className="font-mono text-sm font-semibold text-gray-300">
{formatBytes(image.size)}
</span>
</div>
<div className="rounded-lg bg-gray-50 p-2.5 dark:bg-gray-900/60 border border-gray-200 dark:border-gray-700/60">
<span className="block text-[11px] font-medium text-gray-500 dark:text-gray-400 uppercase">Base64 Length</span>
<span className="font-mono text-sm font-semibold text-gray-800 dark:text-gray-200">
<div className=" bg-black p-2.5 border border-[#1a1a1a]">
<span className="block text-[11px] font-medium text-gray-500 uppercase">Base64 Length</span>
<span className="font-mono text-sm font-semibold text-gray-300">
{image.dataUri.length.toLocaleString()} chars
</span>
</div>
<div className="rounded-lg bg-gray-50 p-2.5 dark:bg-gray-900/60 border border-gray-200 dark:border-gray-700/60">
<span className="block text-[11px] font-medium text-gray-500 dark:text-gray-400 uppercase">Base64 Size</span>
<span className="font-mono text-sm font-semibold text-gray-800 dark:text-gray-200">
<div className=" bg-black p-2.5 border border-[#1a1a1a]">
<span className="block text-[11px] font-medium text-gray-500 uppercase">Base64 Size</span>
<span className="font-mono text-sm font-semibold text-gray-300">
{formatBytes(base64Bytes)}
</span>
</div>
@ -424,14 +424,14 @@ export default function ImageToBase64Page() {
</div>
{/* Size Comparison Bar */}
<div className="rounded-lg bg-gray-50 p-3 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between text-xs mb-1.5 font-medium text-gray-700 dark:text-gray-300">
<div className=" bg-black p-3 border border-[#1a1a1a]">
<div className="flex items-center justify-between text-xs mb-1.5 font-medium text-gray-400">
<span>Size Overhead Comparison</span>
<span className="font-mono text-amber-600 dark:text-amber-400">
<span className="font-mono text-amber-600">
+{sizeIncreasePercent}% (+{formatBytes(sizeDiffBytes)})
</span>
</div>
<div className="h-2.5 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700 flex">
<div className="h-2.5 w-full overflow-hidden rounded-none bg-[#1a1a1a] flex">
<div
className="bg-blue-600 h-full"
style={{ width: `${Math.min(100, (originalBytes / base64Bytes) * 100)}%` }}
@ -443,13 +443,13 @@ export default function ImageToBase64Page() {
title={`Base64 Encoding Overhead: +${formatBytes(sizeDiffBytes)}`}
/>
</div>
<div className="flex items-center justify-between text-[11px] text-gray-500 dark:text-gray-400 mt-1.5">
<div className="flex items-center justify-between text-[11px] text-gray-500 mt-1.5">
<span className="flex items-center gap-1">
<span className="h-2 w-2 rounded-full bg-blue-600 inline-block"></span>
<span className="h-2 w-2 rounded-none bg-blue-600 inline-block"></span>
Original ({formatBytes(originalBytes)})
</span>
<span className="flex items-center gap-1">
<span className="h-2 w-2 rounded-full bg-amber-500 inline-block"></span>
<span className="h-2 w-2 rounded-none bg-amber-500 inline-block"></span>
Base64 Overhead (~33% ASCII expansion)
</span>
</div>
@ -461,14 +461,14 @@ export default function ImageToBase64Page() {
<div className="space-y-3">
<div className="flex flex-wrap items-center justify-between gap-3">
{/* Tabs */}
<div className="inline-flex rounded-lg border border-gray-200 bg-gray-100 p-1 dark:border-gray-700 dark:bg-gray-800">
<div className="inline-flex border border-[#1a1a1a] bg-[#111] p-1">
<button
type="button"
onClick={() => setActiveTab("data-uri")}
className={`cursor-pointer rounded-md px-3 py-1.5 text-xs font-semibold transition-colors ${
className={`cursor-pointer px-3 py-1.5 text-xs font-semibold transition-colors ${
activeTab === "data-uri"
? "bg-white text-blue-600 shadow-sm dark:bg-gray-700 dark:text-white"
: "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
? "bg-[#0a0a0a] text-blue-600 "
: "text-gray-600 hover:text-gray-200"
}`}
>
Data URI
@ -476,10 +476,10 @@ export default function ImageToBase64Page() {
<button
type="button"
onClick={() => setActiveTab("raw")}
className={`cursor-pointer rounded-md px-3 py-1.5 text-xs font-semibold transition-colors ${
className={`cursor-pointer px-3 py-1.5 text-xs font-semibold transition-colors ${
activeTab === "raw"
? "bg-white text-blue-600 shadow-sm dark:bg-gray-700 dark:text-white"
: "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
? "bg-[#0a0a0a] text-blue-600 "
: "text-gray-600 hover:text-gray-200"
}`}
>
Raw Base64
@ -487,10 +487,10 @@ export default function ImageToBase64Page() {
<button
type="button"
onClick={() => setActiveTab("html")}
className={`cursor-pointer rounded-md px-3 py-1.5 text-xs font-semibold transition-colors ${
className={`cursor-pointer px-3 py-1.5 text-xs font-semibold transition-colors ${
activeTab === "html"
? "bg-white text-blue-600 shadow-sm dark:bg-gray-700 dark:text-white"
: "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
? "bg-[#0a0a0a] text-blue-600 "
: "text-gray-600 hover:text-gray-200"
}`}
>
HTML &lt;img&gt;
@ -498,10 +498,10 @@ export default function ImageToBase64Page() {
<button
type="button"
onClick={() => setActiveTab("css")}
className={`cursor-pointer rounded-md px-3 py-1.5 text-xs font-semibold transition-colors ${
className={`cursor-pointer px-3 py-1.5 text-xs font-semibold transition-colors ${
activeTab === "css"
? "bg-white text-blue-600 shadow-sm dark:bg-gray-700 dark:text-white"
: "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
? "bg-[#0a0a0a] text-blue-600 "
: "text-gray-600 hover:text-gray-200"
}`}
>
CSS Background
@ -512,7 +512,7 @@ export default function ImageToBase64Page() {
<button
type="button"
onClick={() => copyToClipboard(getOutputText(activeTab), `active-${activeTab}`)}
className="flex cursor-pointer items-center gap-1.5 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm transition-colors hover:bg-blue-700 active:bg-blue-800"
className="flex cursor-pointer items-center gap-1.5 bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 active:bg-blue-800"
>
{copiedKey === `active-${activeTab}` ? (
<>
@ -539,60 +539,60 @@ export default function ImageToBase64Page() {
rows={8}
value={getOutputText(activeTab)}
placeholder="Base64 output will appear here..."
className="w-full rounded-lg border border-gray-300 bg-white p-3.5 font-mono text-xs text-gray-900 shadow-inner focus:outline-none dark:border-gray-700 dark:bg-gray-900/80 dark:text-gray-100 leading-relaxed select-all"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] p-3.5 font-mono text-xs text-gray-200 shadow-inner focus:outline-none leading-relaxed select-all"
/>
</div>
{/* Quick Copy Cards for All 4 Formats */}
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4 pt-2">
{/* 1. Data URI */}
<div className="flex flex-col justify-between rounded-lg border border-gray-200 bg-white p-3 dark:border-gray-700 dark:bg-gray-800">
<div className="flex flex-col justify-between border border-[#1a1a1a] bg-[#0a0a0a] p-3">
<div>
<div className="flex items-center justify-between">
<span className="text-xs font-semibold text-gray-800 dark:text-gray-200">Full Data URI</span>
<span className="text-xs font-semibold text-gray-300">Full Data URI</span>
<span className="text-[10px] text-gray-400">data:image/...</span>
</div>
<p className="mt-1 text-[11px] text-gray-500 dark:text-gray-400 line-clamp-2 font-mono">
<p className="mt-1 text-[11px] text-gray-500 line-clamp-2 font-mono">
{image.dataUri}
</p>
</div>
<button
type="button"
onClick={() => copyToClipboard(image.dataUri, "quick-data-uri")}
className="mt-2.5 w-full cursor-pointer rounded bg-gray-100 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 transition-colors flex items-center justify-center gap-1"
className="mt-2.5 w-full cursor-pointer bg-[#111] py-1.5 text-xs font-medium text-gray-400 hover:bg-[#1a1a1a] transition-colors flex items-center justify-center gap-1"
>
{copiedKey === "quick-data-uri" ? "✓ Copied!" : "Copy Data URI"}
</button>
</div>
{/* 2. Raw Base64 */}
<div className="flex flex-col justify-between rounded-lg border border-gray-200 bg-white p-3 dark:border-gray-700 dark:bg-gray-800">
<div className="flex flex-col justify-between border border-[#1a1a1a] bg-[#0a0a0a] p-3">
<div>
<div className="flex items-center justify-between">
<span className="text-xs font-semibold text-gray-800 dark:text-gray-200">Raw Base64</span>
<span className="text-xs font-semibold text-gray-300">Raw Base64</span>
<span className="text-[10px] text-gray-400">No header</span>
</div>
<p className="mt-1 text-[11px] text-gray-500 dark:text-gray-400 line-clamp-2 font-mono">
<p className="mt-1 text-[11px] text-gray-500 line-clamp-2 font-mono">
{image.rawBase64}
</p>
</div>
<button
type="button"
onClick={() => copyToClipboard(image.rawBase64, "quick-raw")}
className="mt-2.5 w-full cursor-pointer rounded bg-gray-100 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 transition-colors flex items-center justify-center gap-1"
className="mt-2.5 w-full cursor-pointer bg-[#111] py-1.5 text-xs font-medium text-gray-400 hover:bg-[#1a1a1a] transition-colors flex items-center justify-center gap-1"
>
{copiedKey === "quick-raw" ? "✓ Copied!" : "Copy Raw Base64"}
</button>
</div>
{/* 3. HTML <img> */}
<div className="flex flex-col justify-between rounded-lg border border-gray-200 bg-white p-3 dark:border-gray-700 dark:bg-gray-800">
<div className="flex flex-col justify-between border border-[#1a1a1a] bg-[#0a0a0a] p-3">
<div>
<div className="flex items-center justify-between">
<span className="text-xs font-semibold text-gray-800 dark:text-gray-200">HTML &lt;img&gt;</span>
<span className="text-xs font-semibold text-gray-300">HTML &lt;img&gt;</span>
<span className="text-[10px] text-gray-400">&lt;img src=...&gt;</span>
</div>
<p className="mt-1 text-[11px] text-gray-500 dark:text-gray-400 line-clamp-2 font-mono">
<p className="mt-1 text-[11px] text-gray-500 line-clamp-2 font-mono">
{`<img src="${image.dataUri}" alt="${image.name}" />`}
</p>
</div>
@ -604,20 +604,20 @@ export default function ImageToBase64Page() {
"quick-html"
)
}
className="mt-2.5 w-full cursor-pointer rounded bg-gray-100 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 transition-colors flex items-center justify-center gap-1"
className="mt-2.5 w-full cursor-pointer bg-[#111] py-1.5 text-xs font-medium text-gray-400 hover:bg-[#1a1a1a] transition-colors flex items-center justify-center gap-1"
>
{copiedKey === "quick-html" ? "✓ Copied!" : "Copy HTML Tag"}
</button>
</div>
{/* 4. CSS background-image */}
<div className="flex flex-col justify-between rounded-lg border border-gray-200 bg-white p-3 dark:border-gray-700 dark:bg-gray-800">
<div className="flex flex-col justify-between border border-[#1a1a1a] bg-[#0a0a0a] p-3">
<div>
<div className="flex items-center justify-between">
<span className="text-xs font-semibold text-gray-800 dark:text-gray-200">CSS Background</span>
<span className="text-xs font-semibold text-gray-300">CSS Background</span>
<span className="text-[10px] text-gray-400">background-image</span>
</div>
<p className="mt-1 text-[11px] text-gray-500 dark:text-gray-400 line-clamp-2 font-mono">
<p className="mt-1 text-[11px] text-gray-500 line-clamp-2 font-mono">
{`background-image: url("${image.dataUri}");`}
</p>
</div>
@ -626,7 +626,7 @@ export default function ImageToBase64Page() {
onClick={() =>
copyToClipboard(`background-image: url("${image.dataUri}");`, "quick-css")
}
className="mt-2.5 w-full cursor-pointer rounded bg-gray-100 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 transition-colors flex items-center justify-center gap-1"
className="mt-2.5 w-full cursor-pointer bg-[#111] py-1.5 text-xs font-medium text-gray-400 hover:bg-[#1a1a1a] transition-colors flex items-center justify-center gap-1"
>
{copiedKey === "quick-css" ? "✓ Copied!" : "Copy CSS"}
</button>
@ -637,21 +637,21 @@ export default function ImageToBase64Page() {
)}
{/* Informational Cards */}
<div className="mt-8 grid grid-cols-1 gap-4 border-t border-gray-200 pt-6 text-xs text-gray-600 dark:border-gray-800 dark:text-gray-400 md:grid-cols-3">
<div className="rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-700/60 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-1">When to Use Base64 Images</h4>
<div className="mt-8 grid grid-cols-1 gap-4 border-t border-[#1a1a1a] pt-6 text-xs text-gray-600 md:grid-cols-3">
<div className=" border border-[#1a1a1a] bg-black/50 p-4">
<h4 className="font-semibold text-gray-300 mb-1">When to Use Base64 Images</h4>
<p>
Ideal for embedding small icons (&lt;10KB), logos in standalone HTML/emails, offline web apps, and preventing render-blocking HTTP requests.
</p>
</div>
<div className="rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-700/60 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-1">Understanding the ~33% Overhead</h4>
<div className=" border border-[#1a1a1a] bg-black/50 p-4">
<h4 className="font-semibold text-gray-300 mb-1">Understanding the ~33% Overhead</h4>
<p>
Base64 encodes every 3 binary bytes into 4 ASCII characters (a 33.3% size expansion). For large photographs, standard image hosting with CDN caching is recommended.
</p>
</div>
<div className="rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-700/60 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-1">Private &amp; In-Browser Execution</h4>
<div className=" border border-[#1a1a1a] bg-black/50 p-4">
<h4 className="font-semibold text-gray-300 mb-1">Private &amp; In-Browser Execution</h4>
<p>
Your graphics never touch an external server or API. All binary-to-string transformation executes natively via HTML5 FileReader in your browser.
</p>

View file

@ -302,11 +302,11 @@ export default function JsonFormatterPage() {
>
<div className="space-y-5">
{/* Controls Toolbar */}
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-gray-200 pb-4 dark:border-gray-800">
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-[#1a1a1a] pb-4">
<div className="flex flex-wrap items-center gap-2">
<button
onClick={handleFormat}
className="inline-flex items-center gap-1.5 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 active:scale-95"
className="inline-flex items-center gap-1.5 bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 active:scale-95"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h7" />
@ -316,7 +316,7 @@ export default function JsonFormatterPage() {
<button
onClick={handleMinify}
className="inline-flex items-center gap-1.5 rounded-lg bg-gray-200 px-4 py-2 text-sm font-medium text-gray-800 transition-colors hover:bg-gray-300 active:scale-95 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700"
className="inline-flex items-center gap-1.5 bg-[#1a1a1a] px-4 py-2 text-sm font-medium text-gray-300 transition-colors hover:bg-[#222] active:scale-95"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 14l-7 7m0 0l-7-7m7 7V3" />
@ -326,7 +326,7 @@ export default function JsonFormatterPage() {
<button
onClick={handleValidate}
className="inline-flex items-center gap-1.5 rounded-lg bg-emerald-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-emerald-700 active:scale-95 dark:bg-emerald-700 dark:hover:bg-emerald-600"
className="inline-flex items-center gap-1.5 bg-emerald-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-emerald-700 active:scale-95"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
@ -337,7 +337,7 @@ export default function JsonFormatterPage() {
<div className="flex flex-wrap items-center gap-3">
{/* Indent Selector */}
<div className="flex items-center gap-1.5 text-sm text-gray-600 dark:text-gray-400">
<div className="flex items-center gap-1.5 text-sm text-gray-600">
<label htmlFor={indentSelectId} className="text-xs font-medium uppercase tracking-wide">
Indent:
</label>
@ -345,7 +345,7 @@ export default function JsonFormatterPage() {
id={indentSelectId}
value={indent}
onChange={(e) => setIndent(e.target.value)}
className="rounded-md border border-gray-300 bg-white px-2.5 py-1 text-sm text-gray-800 shadow-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
className=" border border-[#1a1a1a] bg-[#0a0a0a] px-2.5 py-1 text-sm text-gray-300 shadow-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900"
>
<option value="2">2 Spaces</option>
<option value="4">4 Spaces</option>
@ -355,26 +355,26 @@ export default function JsonFormatterPage() {
</div>
{/* Sort Keys Toggle */}
<label className="flex cursor-pointer items-center gap-1.5 text-sm text-gray-700 select-none dark:text-gray-300">
<label className="flex cursor-pointer items-center gap-1.5 text-sm text-gray-400 select-none">
<input
type="checkbox"
checked={sortKeys}
onChange={(e) => setSortKeys(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>Sort Keys</span>
</label>
<button
onClick={handleLoadSample}
className="rounded-md px-2.5 py-1 text-xs font-medium text-gray-600 underline-offset-2 hover:text-blue-600 hover:underline dark:text-gray-400 dark:hover:text-blue-400"
className=" px-2.5 py-1 text-xs font-medium text-gray-600 underline-offset-2 hover:text-blue-600 hover:underline"
>
Load Sample
</button>
<button
onClick={handleClear}
className="rounded-md px-2.5 py-1 text-xs font-medium text-red-600 underline-offset-2 hover:text-red-700 hover:underline dark:text-red-400 dark:hover:text-red-300"
className=" px-2.5 py-1 text-xs font-medium text-red-400 underline-offset-2 hover:text-red-400 hover:underline"
>
Clear
</button>
@ -383,9 +383,9 @@ export default function JsonFormatterPage() {
{/* Status Notification Banner */}
{status.type === "success" && (
<div className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-emerald-300 bg-emerald-50 px-4 py-3 text-sm text-emerald-900 dark:border-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-300">
<div className="flex flex-wrap items-center justify-between gap-2 border border-emerald-300 bg-emerald-50 px-4 py-3 text-sm text-emerald-900">
<div className="flex items-center gap-2">
<svg className="h-5 w-5 shrink-0 text-emerald-600 dark:text-emerald-400" fill="currentColor" viewBox="0 0 20 20">
<svg className="h-5 w-5 shrink-0 text-emerald-600" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
@ -394,7 +394,7 @@ export default function JsonFormatterPage() {
</svg>
<span className="font-medium">{status.message}</span>
</div>
<div className="flex flex-wrap items-center gap-3 text-xs text-emerald-800 dark:text-emerald-300">
<div className="flex flex-wrap items-center gap-3 text-xs text-emerald-800">
{status.byteSize !== undefined && <span>Size: <strong>{formatBytes(status.byteSize)}</strong></span>}
{status.itemCount !== undefined && <span>Keys: <strong>{status.itemCount}</strong></span>}
{status.depth !== undefined && <span>Depth: <strong>{status.depth}</strong></span>}
@ -403,9 +403,9 @@ export default function JsonFormatterPage() {
)}
{status.type === "error" && (
<div className="rounded-lg border border-red-300 bg-red-50 p-4 text-sm text-red-900 dark:border-red-800 dark:bg-red-950/40 dark:text-red-300">
<div className=" border border-red-900 bg-[#1a0a0a] p-4 text-sm text-red-900">
<div className="flex items-start gap-2">
<svg className="mt-0.5 h-5 w-5 shrink-0 text-red-600 dark:text-red-400" fill="currentColor" viewBox="0 0 20 20">
<svg className="mt-0.5 h-5 w-5 shrink-0 text-red-400" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
@ -413,10 +413,10 @@ export default function JsonFormatterPage() {
/>
</svg>
<div className="flex-1">
<p className="font-semibold text-red-800 dark:text-red-200">Invalid JSON Syntax</p>
<p className="mt-1 font-mono text-xs text-red-700 dark:text-red-300">{status.message}</p>
<p className="font-semibold text-red-400">Invalid JSON Syntax</p>
<p className="mt-1 font-mono text-xs text-red-400">{status.message}</p>
{(status.line !== undefined || status.column !== undefined) && (
<div className="mt-2 inline-flex items-center gap-2 rounded bg-red-100 px-2.5 py-1 text-xs font-medium text-red-800 dark:bg-red-900/60 dark:text-red-200">
<div className="mt-2 inline-flex items-center gap-2 bg-[#1a0a0a] px-2.5 py-1 text-xs font-medium text-red-400">
<span>📍 Error location:</span>
{status.line !== undefined && <span>Line <strong>{status.line}</strong></span>}
{status.column !== undefined && <span>Column <strong>{status.column}</strong></span>}
@ -433,8 +433,8 @@ export default function JsonFormatterPage() {
<div className="flex flex-col">
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-800 dark:text-gray-200">Input JSON</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
<span className="text-sm font-semibold text-gray-300">Input JSON</span>
<span className="text-xs text-gray-500">
({inputLineCount} {inputLineCount === 1 ? "line" : "lines"}, {input.length} chars)
</span>
</div>
@ -448,14 +448,14 @@ export default function JsonFormatterPage() {
// Clipboard permission denied or unavailable
}
}}
className="rounded px-2 py-1 text-xs font-medium text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800"
className="rounded px-2 py-1 text-xs font-medium text-gray-600 hover:bg-[#111]"
title="Paste from clipboard"
>
Paste
</button>
<button
onClick={() => setInput("")}
className="rounded px-2 py-1 text-xs font-medium text-gray-500 hover:bg-gray-100 hover:text-red-600 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-red-400"
className="rounded px-2 py-1 text-xs font-medium text-gray-500 hover:bg-[#111] hover:text-red-400"
title="Clear input"
>
Clear
@ -468,7 +468,7 @@ export default function JsonFormatterPage() {
placeholder="Paste or type raw JSON here..."
rows={18}
spellCheck={false}
className="w-full flex-1 resize-y rounded-lg border border-gray-300 bg-white p-3 font-mono text-sm leading-relaxed text-gray-900 shadow-xs focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-500"
className="w-full flex-1 resize-y border border-[#1a1a1a] bg-[#0a0a0a] p-3 font-mono text-sm leading-relaxed text-gray-200 shadow-xs focus:border-blue-500 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
@ -476,8 +476,8 @@ export default function JsonFormatterPage() {
<div className="flex flex-col">
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-800 dark:text-gray-200">Output Result</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
<span className="text-sm font-semibold text-gray-300">Output Result</span>
<span className="text-xs text-gray-500">
({outputLineCount} {outputLineCount === 1 ? "line" : "lines"}, {output.length} chars)
</span>
</div>
@ -485,7 +485,7 @@ export default function JsonFormatterPage() {
<button
onClick={handleDownload}
disabled={!output && !input}
className="inline-flex items-center gap-1 rounded px-2.5 py-1 text-xs font-medium text-gray-700 hover:bg-gray-100 disabled:opacity-40 dark:text-gray-300 dark:hover:bg-gray-800"
className="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium text-gray-400 hover:bg-[#111] disabled:opacity-40"
title="Download as JSON file"
>
<svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -496,7 +496,7 @@ export default function JsonFormatterPage() {
<button
onClick={handleCopy}
disabled={!output && !input}
className="inline-flex items-center gap-1 rounded-md bg-blue-600 px-3 py-1 text-xs font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 disabled:opacity-40"
className="inline-flex items-center gap-1 bg-blue-600 px-3 py-1 text-xs font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 disabled:opacity-40"
title="Copy to clipboard"
>
{copied ? (
@ -528,29 +528,29 @@ export default function JsonFormatterPage() {
placeholder="Formatted or minified output will appear here..."
rows={18}
spellCheck={false}
className="w-full flex-1 resize-y rounded-lg border border-gray-300 bg-gray-50 p-3 font-mono text-sm leading-relaxed text-gray-900 shadow-xs focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none dark:border-gray-700 dark:bg-gray-900/80 dark:text-gray-100 dark:placeholder-gray-500"
className="w-full flex-1 resize-y border border-[#1a1a1a] bg-black p-3 font-mono text-sm leading-relaxed text-gray-200 shadow-xs focus:border-blue-500 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
</div>
{/* Feature Highlights & Guide */}
<div className="mt-8 rounded-xl border border-gray-200 bg-gray-50/70 p-5 text-sm text-gray-600 dark:border-gray-800 dark:bg-gray-800/30 dark:text-gray-400">
<h3 className="font-semibold text-gray-900 dark:text-gray-100">About JSON Formatter & Validator</h3>
<div className="mt-8 border border-[#1a1a1a] bg-black/70 p-5 text-sm text-gray-600">
<h3 className="font-semibold text-gray-200">About JSON Formatter & Validator</h3>
<div className="mt-3 grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">✨ Beautify & Format</p>
<p className="font-medium text-gray-300">✨ Beautify & Format</p>
<p className="mt-1 text-xs leading-normal">
Format unorganized or compact JSON with customizable 2-space, 4-space, or tab indentation for maximum readability.
</p>
</div>
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">🔍 Strict Validation</p>
<p className="font-medium text-gray-300">🔍 Strict Validation</p>
<p className="mt-1 text-xs leading-normal">
Identify syntax mistakes, unquoted keys, trailing commas, and locate exact line numbers and column coordinates instantly.
</p>
</div>
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">🔒 100% Client-Side</p>
<p className="font-medium text-gray-300">🔒 100% Client-Side</p>
<p className="mt-1 text-xs leading-normal">
Your data never leaves your browser. Parsing and formatting run entirely on your local machine for complete privacy.
</p>

View file

@ -344,11 +344,11 @@ export default function JsonToCsvPage() {
>
<div className="space-y-5">
{/* Top Controls Toolbar */}
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-gray-200 pb-4 dark:border-gray-800">
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-[#1a1a1a] pb-4">
<div className="flex flex-wrap items-center gap-2">
<button
onClick={handleConvert}
className="inline-flex items-center gap-1.5 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 active:scale-95 cursor-pointer"
className="inline-flex items-center gap-1.5 bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 active:scale-95 cursor-pointer"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
@ -358,7 +358,7 @@ export default function JsonToCsvPage() {
<button
onClick={handleLoadSample}
className="inline-flex items-center gap-1.5 rounded-lg bg-blue-50 px-3 py-2 text-sm font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:bg-blue-950/40 dark:text-blue-300 dark:hover:bg-blue-900/50 cursor-pointer"
className="inline-flex items-center gap-1.5 bg-[#0a0a1a] px-3 py-2 text-sm font-medium text-blue-400 transition-colors hover:bg-[#0a0a1a] cursor-pointer"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
@ -369,24 +369,24 @@ export default function JsonToCsvPage() {
<button
onClick={handleClear}
disabled={!input && !output}
className="inline-flex items-center gap-1.5 rounded-lg bg-gray-100 px-3 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-rose-50 hover:text-rose-600 disabled:opacity-40 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-rose-950/30 dark:hover:text-rose-400 cursor-pointer"
className="inline-flex items-center gap-1.5 bg-[#111] px-3 py-2 text-sm font-medium text-gray-400 transition-colors hover:bg-rose-50 hover:text-rose-600 disabled:opacity-40 cursor-pointer"
>
Clear
</button>
</div>
{/* Options */}
<div className="flex flex-wrap items-center gap-3 text-sm text-gray-700 dark:text-gray-300">
<div className="flex flex-wrap items-center gap-3 text-sm text-gray-400">
{/* Delimiter */}
<div className="flex items-center gap-1.5">
<label htmlFor={delimiterId} className="text-xs font-semibold text-gray-500 uppercase dark:text-gray-400">
<label htmlFor={delimiterId} className="text-xs font-semibold text-gray-500 uppercase">
Delimiter:
</label>
<select
id={delimiterId}
value={delimiter}
onChange={(e) => setDelimiter(e.target.value)}
className="rounded-md border border-gray-300 bg-white px-2 py-1 text-xs text-gray-800 shadow-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
className=" border border-[#1a1a1a] bg-[#0a0a0a] px-2 py-1 text-xs text-gray-300 shadow-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900"
>
<option value=",">Comma (,)</option>
<option value=";">Semicolon (;)</option>
@ -397,14 +397,14 @@ export default function JsonToCsvPage() {
{/* Array Mode */}
<div className="flex items-center gap-1.5">
<label htmlFor={arrayModeId} className="text-xs font-semibold text-gray-500 uppercase dark:text-gray-400">
<label htmlFor={arrayModeId} className="text-xs font-semibold text-gray-500 uppercase">
Arrays:
</label>
<select
id={arrayModeId}
value={arrayMode}
onChange={(e) => setArrayMode(e.target.value as "join" | "json")}
className="rounded-md border border-gray-300 bg-white px-2 py-1 text-xs text-gray-800 shadow-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
className=" border border-[#1a1a1a] bg-[#0a0a0a] px-2 py-1 text-xs text-gray-300 shadow-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900"
>
<option value="join">Join with &quot;;&quot;</option>
<option value="json">JSON String</option>
@ -414,33 +414,33 @@ export default function JsonToCsvPage() {
</div>
{/* Toggles Strip */}
<div className="flex flex-wrap items-center gap-5 text-xs text-gray-600 dark:text-gray-400">
<label className="flex cursor-pointer items-center gap-1.5 select-none hover:text-gray-900 dark:hover:text-gray-100">
<div className="flex flex-wrap items-center gap-5 text-xs text-gray-600">
<label className="flex cursor-pointer items-center gap-1.5 select-none hover:text-gray-200">
<input
type="checkbox"
checked={flatten}
onChange={(e) => setFlatten(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>Flatten nested objects (e.g. <code>address.city</code>)</span>
</label>
<label className="flex cursor-pointer items-center gap-1.5 select-none hover:text-gray-900 dark:hover:text-gray-100">
<label className="flex cursor-pointer items-center gap-1.5 select-none hover:text-gray-200">
<input
type="checkbox"
checked={includeHeaders}
onChange={(e) => setIncludeHeaders(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>Include header row</span>
</label>
<label className="flex cursor-pointer items-center gap-1.5 select-none hover:text-gray-900 dark:hover:text-gray-100">
<label className="flex cursor-pointer items-center gap-1.5 select-none hover:text-gray-200">
<input
type="checkbox"
checked={quoteAll}
onChange={(e) => setQuoteAll(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>Quote all fields</span>
</label>
@ -448,9 +448,9 @@ export default function JsonToCsvPage() {
{/* Status Notification Banner */}
{status.type === "success" && (
<div className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-emerald-300 bg-emerald-50 px-4 py-3 text-sm text-emerald-900 dark:border-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-300">
<div className="flex flex-wrap items-center justify-between gap-2 border border-emerald-300 bg-emerald-50 px-4 py-3 text-sm text-emerald-900">
<div className="flex items-center gap-2">
<svg className="h-5 w-5 shrink-0 text-emerald-600 dark:text-emerald-400" fill="currentColor" viewBox="0 0 20 20">
<svg className="h-5 w-5 shrink-0 text-emerald-600" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
@ -459,7 +459,7 @@ export default function JsonToCsvPage() {
</svg>
<span className="font-medium">{status.message}</span>
</div>
<div className="flex flex-wrap items-center gap-3 text-xs text-emerald-800 dark:text-emerald-300">
<div className="flex flex-wrap items-center gap-3 text-xs text-emerald-800">
{status.rowCount !== undefined && <span>Rows: <strong>{status.rowCount}</strong></span>}
{status.colCount !== undefined && <span>Columns: <strong>{status.colCount}</strong></span>}
{status.byteSize !== undefined && <span>Size: <strong>{formatBytes(status.byteSize)}</strong></span>}
@ -468,9 +468,9 @@ export default function JsonToCsvPage() {
)}
{status.type === "error" && (
<div className="rounded-lg border border-red-300 bg-red-50 p-4 text-sm text-red-900 dark:border-red-800 dark:bg-red-950/40 dark:text-red-300">
<div className=" border border-red-900 bg-[#1a0a0a] p-4 text-sm text-red-900">
<div className="flex items-start gap-2">
<svg className="mt-0.5 h-5 w-5 shrink-0 text-red-600 dark:text-red-400" fill="currentColor" viewBox="0 0 20 20">
<svg className="mt-0.5 h-5 w-5 shrink-0 text-red-400" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
@ -478,10 +478,10 @@ export default function JsonToCsvPage() {
/>
</svg>
<div className="flex-1">
<p className="font-semibold text-red-800 dark:text-red-200">Error Parsing JSON</p>
<p className="mt-1 font-mono text-xs text-red-700 dark:text-red-300">{status.message}</p>
<p className="font-semibold text-red-400">Error Parsing JSON</p>
<p className="mt-1 font-mono text-xs text-red-400">{status.message}</p>
{(status.line !== undefined || status.column !== undefined) && (
<div className="mt-2 inline-flex items-center gap-2 rounded bg-red-100 px-2.5 py-1 text-xs font-medium text-red-800 dark:bg-red-900/60 dark:text-red-200">
<div className="mt-2 inline-flex items-center gap-2 bg-[#1a0a0a] px-2.5 py-1 text-xs font-medium text-red-400">
<span>📍 Location:</span>
{status.line !== undefined && <span>Line <strong>{status.line}</strong></span>}
{status.column !== undefined && <span>Column <strong>{status.column}</strong></span>}
@ -498,8 +498,8 @@ export default function JsonToCsvPage() {
<div className="flex flex-col">
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-800 dark:text-gray-200">Input JSON</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
<span className="text-sm font-semibold text-gray-300">Input JSON</span>
<span className="text-xs text-gray-500">
({inputLineCount} {inputLineCount === 1 ? "line" : "lines"}, {input.length} chars)
</span>
</div>
@ -513,14 +513,14 @@ export default function JsonToCsvPage() {
// Clipboard permission denied
}
}}
className="rounded px-2 py-1 text-xs font-medium text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800 cursor-pointer"
className="rounded px-2 py-1 text-xs font-medium text-gray-600 hover:bg-[#111] cursor-pointer"
title="Paste from clipboard"
>
Paste
</button>
<button
onClick={() => setInput("")}
className="rounded px-2 py-1 text-xs font-medium text-gray-500 hover:bg-gray-100 hover:text-red-600 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-red-400 cursor-pointer"
className="rounded px-2 py-1 text-xs font-medium text-gray-500 hover:bg-[#111] hover:text-red-400 cursor-pointer"
title="Clear input"
>
Clear
@ -533,7 +533,7 @@ export default function JsonToCsvPage() {
placeholder="Paste JSON array of objects here, e.g. [ { &quot;id&quot;: 1, &quot;name&quot;: &quot;Alice&quot; }, ... ]"
rows={18}
spellCheck={false}
className="w-full flex-1 resize-y rounded-lg border border-gray-300 bg-white p-3 font-mono text-sm leading-relaxed text-gray-900 shadow-xs focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-500"
className="w-full flex-1 resize-y border border-[#1a1a1a] bg-[#0a0a0a] p-3 font-mono text-sm leading-relaxed text-gray-200 shadow-xs focus:border-blue-500 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
@ -541,8 +541,8 @@ export default function JsonToCsvPage() {
<div className="flex flex-col">
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-800 dark:text-gray-200">CSV Output</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
<span className="text-sm font-semibold text-gray-300">CSV Output</span>
<span className="text-xs text-gray-500">
({outputLineCount} {outputLineCount === 1 ? "line" : "lines"}, {output.length} chars)
</span>
</div>
@ -550,7 +550,7 @@ export default function JsonToCsvPage() {
<button
onClick={handleDownload}
disabled={!output}
className="inline-flex items-center gap-1 rounded px-2.5 py-1 text-xs font-medium text-gray-700 hover:bg-gray-100 disabled:opacity-40 dark:text-gray-300 dark:hover:bg-gray-800 cursor-pointer"
className="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium text-gray-400 hover:bg-[#111] disabled:opacity-40 cursor-pointer"
title="Download as CSV file"
>
<svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -561,7 +561,7 @@ export default function JsonToCsvPage() {
<button
onClick={handleCopy}
disabled={!output}
className="inline-flex items-center gap-1 rounded-md bg-blue-600 px-3 py-1 text-xs font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 disabled:opacity-40 cursor-pointer"
className="inline-flex items-center gap-1 bg-blue-600 px-3 py-1 text-xs font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 disabled:opacity-40 cursor-pointer"
title="Copy CSV to clipboard"
>
{copied ? (
@ -593,29 +593,29 @@ export default function JsonToCsvPage() {
placeholder="Converted CSV tabular data will appear here..."
rows={18}
spellCheck={false}
className="w-full flex-1 resize-y rounded-lg border border-gray-300 bg-gray-50 p-3 font-mono text-sm leading-relaxed text-gray-900 shadow-xs focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none dark:border-gray-700 dark:bg-gray-900/80 dark:text-gray-100 dark:placeholder-gray-500"
className="w-full flex-1 resize-y border border-[#1a1a1a] bg-black p-3 font-mono text-sm leading-relaxed text-gray-200 shadow-xs focus:border-blue-500 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
</div>
{/* Feature Highlights & Guide */}
<div className="mt-8 rounded-xl border border-gray-200 bg-gray-50/70 p-5 text-sm text-gray-600 dark:border-gray-800 dark:bg-gray-800/30 dark:text-gray-400">
<h3 className="font-semibold text-gray-900 dark:text-gray-100">How to Convert JSON to CSV</h3>
<div className="mt-8 border border-[#1a1a1a] bg-black/70 p-5 text-sm text-gray-600">
<h3 className="font-semibold text-gray-200">How to Convert JSON to CSV</h3>
<div className="mt-3 grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">🌿 Nested Object Flattening</p>
<p className="font-medium text-gray-300">🌿 Nested Object Flattening</p>
<p className="mt-1 text-xs leading-normal">
Automatically unpacks deep nested objects into flat dot-notation headers such as <code>user.address.geo.lat</code>.
</p>
</div>
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">🛡️ RFC 4180 Escaping</p>
<p className="font-medium text-gray-300">🛡️ RFC 4180 Escaping</p>
<p className="mt-1 text-xs leading-normal">
Safely handles quotes, multi-line values, commas, and special delimiters with compliant CSV double-quote escaping.
</p>
</div>
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">🔒 100% Client-Side Privacy</p>
<p className="font-medium text-gray-300">🔒 100% Client-Side Privacy</p>
<p className="mt-1 text-xs leading-normal">
Your data is processed directly inside your browser. No files or records are sent to any remote server.
</p>

View file

@ -173,27 +173,27 @@ export default function JwtDecoderPage() {
<div className="space-y-6">
{/* Samples & Actions Bar */}
<div className="flex flex-wrap items-center justify-between gap-3 pb-2 border-b border-gray-200 dark:border-gray-700">
<div className="flex flex-wrap items-center justify-between gap-3 pb-2 border-b border-[#1a1a1a]">
<div className="flex items-center gap-2">
<span className="text-xs font-medium text-gray-500 dark:text-gray-400">Sample Tokens:</span>
<span className="text-xs font-medium text-gray-500">Sample Tokens:</span>
<button
type="button"
onClick={() => setTokenInput(SAMPLE_VALID)}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 rounded text-xs font-medium transition-colors cursor-pointer"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] text-gray-400 text-xs font-medium transition-colors cursor-pointer"
>
Active Token
</button>
<button
type="button"
onClick={() => setTokenInput(SAMPLE_EXPIRED)}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 rounded text-xs font-medium transition-colors cursor-pointer"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] text-gray-400 text-xs font-medium transition-colors cursor-pointer"
>
Expired Token
</button>
<button
type="button"
onClick={() => setTokenInput(SAMPLE_FIREBASE)}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 rounded text-xs font-medium transition-colors cursor-pointer"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] text-gray-400 text-xs font-medium transition-colors cursor-pointer"
>
RS256 Token
</button>
@ -204,14 +204,14 @@ export default function JwtDecoderPage() {
type="button"
onClick={() => copyToClipboard(tokenInput, setCopiedToken)}
disabled={!tokenInput}
className="px-3 py-1.5 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300 rounded text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
className="px-3 py-1.5 bg-[#111] hover:bg-[#1a1a1a] disabled:opacity-40 disabled:cursor-not-allowed text-gray-400 text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
>
{copiedToken ? (
<>
<svg className="w-3.5 h-3.5 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="w-3.5 h-3.5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
<span className="text-green-600 dark:text-green-400 font-semibold">Copied!</span>
<span className="text-green-400 font-semibold">Copied!</span>
</>
) : (
<>
@ -226,7 +226,7 @@ export default function JwtDecoderPage() {
type="button"
onClick={() => setTokenInput("")}
disabled={!tokenInput}
className="px-3 py-1.5 bg-gray-100 dark:bg-gray-800 hover:bg-red-50 dark:hover:bg-red-950/30 text-gray-700 dark:text-gray-300 hover:text-red-600 dark:hover:text-red-400 disabled:opacity-40 disabled:cursor-not-allowed rounded text-xs font-medium transition-colors cursor-pointer"
className="px-3 py-1.5 bg-[#111] hover:bg-[#1a0a0a] text-gray-400 hover:text-red-400 disabled:opacity-40 disabled:cursor-not-allowed text-xs font-medium transition-colors cursor-pointer"
>
Clear
</button>
@ -236,23 +236,23 @@ export default function JwtDecoderPage() {
{/* Input Area */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<label htmlFor={inputId} className="font-semibold text-sm text-gray-800 dark:text-gray-200 flex items-center gap-2">
<label htmlFor={inputId} className="font-semibold text-sm text-gray-300 flex items-center gap-2">
<span>Encoded JWT Token</span>
<span className="text-xs font-normal text-gray-500 dark:text-gray-400">
<span className="text-xs font-normal text-gray-500">
(Paste token below)
</span>
</label>
<div className="flex items-center gap-3 text-xs font-medium">
<span className="inline-flex items-center gap-1 text-rose-600 dark:text-rose-400">
<span className="w-2.5 h-2.5 rounded-full bg-rose-500 inline-block"></span>
<span className="inline-flex items-center gap-1 text-rose-600">
<span className="w-2.5 h-2.5 rounded-none bg-rose-500 inline-block"></span>
Header
</span>
<span className="inline-flex items-center gap-1 text-purple-600 dark:text-purple-400">
<span className="w-2.5 h-2.5 rounded-full bg-purple-500 inline-block"></span>
<span className="inline-flex items-center gap-1 text-purple-600">
<span className="w-2.5 h-2.5 rounded-none bg-purple-500 inline-block"></span>
Payload
</span>
<span className="inline-flex items-center gap-1 text-sky-600 dark:text-sky-400">
<span className="w-2.5 h-2.5 rounded-full bg-sky-500 inline-block"></span>
<span className="inline-flex items-center gap-1 text-sky-600">
<span className="w-2.5 h-2.5 rounded-none bg-sky-500 inline-block"></span>
Signature
</span>
</div>
@ -264,24 +264,24 @@ export default function JwtDecoderPage() {
onChange={(e) => setTokenInput(e.target.value)}
placeholder="Paste a JSON Web Token here (e.g. eyJhbGciOi...)"
rows={4}
className="w-full p-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none resize-y leading-relaxed shadow-inner"
className="w-full p-3 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-mono text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none resize-y leading-relaxed shadow-inner"
/>
{/* Color-Coded Token Preview Bar */}
{tokenInput.trim() && parts.length === 3 && (
<div className="p-3 bg-gray-50 dark:bg-gray-900/70 border border-gray-200 dark:border-gray-700/70 rounded-lg font-mono text-xs break-all leading-relaxed">
<span className="text-rose-600 dark:text-rose-400 font-semibold">{parts[0]}</span>
<span className="text-gray-400 dark:text-gray-600 font-bold">.</span>
<span className="text-purple-600 dark:text-purple-400 font-semibold">{parts[1]}</span>
<span className="text-gray-400 dark:text-gray-600 font-bold">.</span>
<span className="text-sky-600 dark:text-sky-400 font-semibold">{parts[2]}</span>
<div className="p-3 bg-black border border-[#1a1a1a] font-mono text-xs break-all leading-relaxed">
<span className="text-rose-600 font-semibold">{parts[0]}</span>
<span className="text-gray-400 font-bold">.</span>
<span className="text-purple-600 font-semibold">{parts[1]}</span>
<span className="text-gray-400 font-bold">.</span>
<span className="text-sky-600 font-semibold">{parts[2]}</span>
</div>
)}
</div>
{/* Error Notification */}
{error && (
<div className="flex items-start gap-3 p-4 bg-red-50 dark:bg-red-950/40 border border-red-200 dark:border-red-800 rounded-lg text-red-800 dark:text-red-300 text-sm">
<div className="flex items-start gap-3 p-4 bg-[#1a0a0a] border border-red-900 text-red-400 text-sm">
<svg className="w-5 h-5 text-red-500 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
@ -297,18 +297,18 @@ export default function JwtDecoderPage() {
<div>
{expInfo ? (
<div
className={`p-4 rounded-xl border flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 ${
className={`p-4 border flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 ${
expInfo.isExpired
? "bg-red-50 dark:bg-red-950/40 border-red-200 dark:border-red-800 text-red-900 dark:text-red-300"
: "bg-emerald-50 dark:bg-emerald-950/40 border-emerald-200 dark:border-emerald-800 text-emerald-900 dark:text-emerald-300"
? "bg-[#1a0a0a] border-red-900 text-red-900"
: "bg-emerald-50 border-emerald-200 text-emerald-900"
}`}
>
<div className="flex items-center gap-3">
<div
className={`w-9 h-9 rounded-lg flex items-center justify-center shrink-0 ${
className={`w-9 h-9 flex items-center justify-center shrink-0 ${
expInfo.isExpired
? "bg-red-100 dark:bg-red-900/60 text-red-600 dark:text-red-300"
: "bg-emerald-100 dark:bg-emerald-900/60 text-emerald-600 dark:text-emerald-300"
? "bg-[#1a0a0a] text-red-400"
: "bg-emerald-100 text-emerald-600"
}`}
>
{expInfo.isExpired ? (
@ -324,7 +324,7 @@ export default function JwtDecoderPage() {
<div>
<h3 className="font-bold text-base flex items-center gap-2">
<span>{expInfo.isExpired ? "Token is Expired" : "Token is Active"}</span>
<span className="text-xs px-2 py-0.5 rounded-full font-semibold uppercase tracking-wide bg-white/60 dark:bg-black/30">
<span className="text-xs px-2 py-0.5 rounded-none font-semibold uppercase tracking-wide bg-[#0a0a0a]/60">
{expInfo.relative}
</span>
</h3>
@ -341,11 +341,11 @@ export default function JwtDecoderPage() {
)}
</div>
) : (
<div className="p-3.5 bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800 rounded-xl text-amber-900 dark:text-amber-300 text-xs flex items-center gap-2">
<svg className="w-4 h-4 shrink-0 text-amber-600 dark:text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="p-3.5 bg-amber-50 border border-amber-200 text-amber-900 text-xs flex items-center gap-2">
<svg className="w-4 h-4 shrink-0 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>No expiration (<code className="font-mono bg-amber-100 dark:bg-amber-900/60 px-1 py-0.5 rounded">exp</code>) claim present in this token payload.</span>
<span>No expiration (<code className="font-mono bg-amber-100 px-1 py-0.5 rounded">exp</code>) claim present in this token payload.</span>
</div>
)}
</div>
@ -357,11 +357,11 @@ export default function JwtDecoderPage() {
<div className="flex flex-col space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="w-3 h-3 rounded-full bg-rose-500"></span>
<h3 className="font-bold text-sm text-gray-900 dark:text-gray-100">
<span className="w-3 h-3 rounded-none bg-rose-500"></span>
<h3 className="font-bold text-sm text-gray-200">
Decoded Header
</h3>
<span className="text-xs text-rose-600 dark:text-rose-400 font-mono">
<span className="text-xs text-rose-600 font-mono">
(Algorithm &amp; Token Type)
</span>
</div>
@ -369,14 +369,14 @@ export default function JwtDecoderPage() {
type="button"
onClick={() => copyToClipboard(headerString, setCopiedHeader)}
disabled={!headerString}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300 rounded text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] disabled:opacity-40 disabled:cursor-not-allowed text-gray-400 text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
>
{copiedHeader ? (
<>
<svg className="w-3.5 h-3.5 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="w-3.5 h-3.5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
<span className="text-green-600 dark:text-green-400 font-semibold">Copied!</span>
<span className="text-green-400 font-semibold">Copied!</span>
</>
) : (
<>
@ -389,19 +389,19 @@ export default function JwtDecoderPage() {
</button>
</div>
<pre className="p-4 rounded-lg border border-rose-200 dark:border-rose-900/50 bg-rose-50/50 dark:bg-rose-950/20 text-rose-950 dark:text-rose-200 font-mono text-xs leading-relaxed min-h-[140px] overflow-auto select-all shadow-inner">
<pre className="p-4 border border-rose-200 bg-rose-50/50 text-rose-950 font-mono text-xs leading-relaxed min-h-[140px] overflow-auto select-all shadow-inner">
{headerString || "// Decoded header JSON will appear here"}
</pre>
{token?.header && (
<div className="grid grid-cols-2 gap-2 text-xs font-mono">
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700">
<span className="text-gray-500 dark:text-gray-400">Algorithm (alg):</span>{" "}
<span className="font-semibold text-rose-600 dark:text-rose-400">{String(token.header.alg || "none")}</span>
<div className="p-2 bg-black border border-[#1a1a1a]">
<span className="text-gray-500">Algorithm (alg):</span>{" "}
<span className="font-semibold text-rose-600">{String(token.header.alg || "none")}</span>
</div>
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700">
<span className="text-gray-500 dark:text-gray-400">Type (typ):</span>{" "}
<span className="font-semibold text-rose-600 dark:text-rose-400">{String(token.header.typ || "JWT")}</span>
<div className="p-2 bg-black border border-[#1a1a1a]">
<span className="text-gray-500">Type (typ):</span>{" "}
<span className="font-semibold text-rose-600">{String(token.header.typ || "JWT")}</span>
</div>
</div>
)}
@ -411,11 +411,11 @@ export default function JwtDecoderPage() {
<div className="flex flex-col space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="w-3 h-3 rounded-full bg-purple-500"></span>
<h3 className="font-bold text-sm text-gray-900 dark:text-gray-100">
<span className="w-3 h-3 rounded-none bg-purple-500"></span>
<h3 className="font-bold text-sm text-gray-200">
Decoded Payload
</h3>
<span className="text-xs text-purple-600 dark:text-purple-400 font-mono">
<span className="text-xs text-purple-600 font-mono">
(Data Claims)
</span>
</div>
@ -423,14 +423,14 @@ export default function JwtDecoderPage() {
type="button"
onClick={() => copyToClipboard(payloadString, setCopiedPayload)}
disabled={!payloadString}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300 rounded text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] disabled:opacity-40 disabled:cursor-not-allowed text-gray-400 text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
>
{copiedPayload ? (
<>
<svg className="w-3.5 h-3.5 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="w-3.5 h-3.5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
<span className="text-green-600 dark:text-green-400 font-semibold">Copied!</span>
<span className="text-green-400 font-semibold">Copied!</span>
</>
) : (
<>
@ -443,34 +443,34 @@ export default function JwtDecoderPage() {
</button>
</div>
<pre className="p-4 rounded-lg border border-purple-200 dark:border-purple-900/50 bg-purple-50/50 dark:bg-purple-950/20 text-purple-950 dark:text-purple-200 font-mono text-xs leading-relaxed min-h-[140px] overflow-auto select-all shadow-inner">
<pre className="p-4 border border-purple-200 bg-purple-50/50 text-purple-950 font-mono text-xs leading-relaxed min-h-[140px] overflow-auto select-all shadow-inner">
{payloadString || "// Decoded payload JSON will appear here"}
</pre>
{token?.payload && (
<div className="space-y-1.5 text-xs font-mono">
{token.payload.sub !== undefined && (
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 flex justify-between">
<span className="text-gray-500 dark:text-gray-400">Subject (sub):</span>
<span className="font-semibold text-purple-600 dark:text-purple-400">{String(token.payload.sub)}</span>
<div className="p-2 bg-black border border-[#1a1a1a] flex justify-between">
<span className="text-gray-500">Subject (sub):</span>
<span className="font-semibold text-purple-600">{String(token.payload.sub)}</span>
</div>
)}
{token.payload.iss !== undefined && (
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 flex justify-between">
<span className="text-gray-500 dark:text-gray-400">Issuer (iss):</span>
<span className="font-semibold text-purple-600 dark:text-purple-400">{String(token.payload.iss)}</span>
<div className="p-2 bg-black border border-[#1a1a1a] flex justify-between">
<span className="text-gray-500">Issuer (iss):</span>
<span className="font-semibold text-purple-600">{String(token.payload.iss)}</span>
</div>
)}
{token.payload.aud !== undefined && (
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 flex justify-between">
<span className="text-gray-500 dark:text-gray-400">Audience (aud):</span>
<span className="font-semibold text-purple-600 dark:text-purple-400">{String(token.payload.aud)}</span>
<div className="p-2 bg-black border border-[#1a1a1a] flex justify-between">
<span className="text-gray-500">Audience (aud):</span>
<span className="font-semibold text-purple-600">{String(token.payload.aud)}</span>
</div>
)}
{nbfInfo && (
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 flex justify-between">
<span className="text-gray-500 dark:text-gray-400">Not Before (nbf):</span>
<span className="font-semibold text-purple-600 dark:text-purple-400">{nbfInfo.formatted}</span>
<div className="p-2 bg-black border border-[#1a1a1a] flex justify-between">
<span className="text-gray-500">Not Before (nbf):</span>
<span className="font-semibold text-purple-600">{nbfInfo.formatted}</span>
</div>
)}
</div>
@ -480,34 +480,34 @@ export default function JwtDecoderPage() {
{/* Signature Inspection Card */}
{token && (
<div className="p-4 rounded-xl border border-sky-200 dark:border-sky-900/50 bg-sky-50/40 dark:bg-sky-950/20 space-y-2">
<div className="p-4 border border-sky-200 bg-sky-50/40 space-y-2">
<div className="flex items-center gap-2">
<span className="w-3 h-3 rounded-full bg-sky-500"></span>
<h4 className="font-bold text-sm text-gray-900 dark:text-gray-100">
<span className="w-3 h-3 rounded-none bg-sky-500"></span>
<h4 className="font-bold text-sm text-gray-200">
Signature Component
</h4>
</div>
<p className="text-xs text-gray-600 dark:text-gray-400">
<p className="text-xs text-gray-600">
The signature is calculated by taking the encoded header, the encoded payload, a secret key, and signing them using the specified algorithm ({String(token.header?.alg || "HS256")}).
</p>
<div className="p-2.5 bg-white dark:bg-gray-900 rounded border border-sky-200 dark:border-sky-800 font-mono text-xs text-sky-700 dark:text-sky-300 break-all select-all">
<div className="p-2.5 bg-[#0a0a0a] border border-sky-200 font-mono text-xs text-sky-700 break-all select-all">
{token.signature || "// No signature"}
</div>
</div>
)}
{/* Informative Guide */}
<div className="mt-8 pt-6 border-t border-gray-200 dark:border-gray-800 grid grid-cols-1 md:grid-cols-3 gap-4 text-xs text-gray-600 dark:text-gray-400">
<div className="p-3 bg-gray-50 dark:bg-gray-800/40 rounded-lg border border-gray-200 dark:border-gray-700/60">
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-1">🔴 Header (Red)</h4>
<div className="mt-8 pt-6 border-t border-[#1a1a1a] grid grid-cols-1 md:grid-cols-3 gap-4 text-xs text-gray-600">
<div className="p-3 bg-black border border-[#1a1a1a]">
<h4 className="font-semibold text-gray-300 mb-1">🔴 Header (Red)</h4>
<p>Contains metadata about the token, such as the signing algorithm (e.g. HS256, RS256) and token type (JWT).</p>
</div>
<div className="p-3 bg-gray-50 dark:bg-gray-800/40 rounded-lg border border-gray-200 dark:border-gray-700/60">
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-1">🟣 Payload (Purple)</h4>
<div className="p-3 bg-black border border-[#1a1a1a]">
<h4 className="font-semibold text-gray-300 mb-1">🟣 Payload (Purple)</h4>
<p>Contains the claims and entity statements (user ID, roles, permissions, expiration timestamps).</p>
</div>
<div className="p-3 bg-gray-50 dark:bg-gray-800/40 rounded-lg border border-gray-200 dark:border-gray-700/60">
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-1">🔵 Signature (Blue)</h4>
<div className="p-3 bg-black border border-[#1a1a1a]">
<h4 className="font-semibold text-gray-300 mb-1">🔵 Signature (Blue)</h4>
<p>Cryptographic hash used by the server to verify that the message wasn&apos;t changed along the way.</p>
</div>
</div>

View file

@ -159,11 +159,11 @@ export default function LoremIpsumPage() {
>
<div className="space-y-6">
{/* Controls Section */}
<div className="rounded-lg border border-gray-200 bg-gray-50/60 p-4 dark:border-gray-700/60 dark:bg-gray-800/40">
<div className=" border border-[#1a1a1a] bg-black/60 p-4">
<div className="grid grid-cols-1 gap-5 md:grid-cols-2 lg:grid-cols-4">
{/* Number of Paragraphs */}
<div>
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-gray-400">
Paragraphs (1 - 20)
</label>
<div className="flex items-center gap-2">
@ -176,7 +176,7 @@ export default function LoremIpsumPage() {
const val = Math.min(Math.max(1, parseInt(e.target.value) || 1), 20);
setNumParagraphs(val);
}}
className="w-20 rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
className="w-20 border border-[#1a1a1a] bg-[#0a0a0a] px-3 py-2 text-sm font-medium text-gray-200 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900/20"
/>
<div className="flex flex-wrap gap-1">
{[1, 3, 5, 10].map((count) => (
@ -187,7 +187,7 @@ export default function LoremIpsumPage() {
className={`rounded px-2 py-1 text-xs font-medium transition-colors ${
numParagraphs === count
? "bg-blue-600 text-white"
: "bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
: "bg-[#1a1a1a] text-gray-400 hover:bg-[#222]"
}`}
>
{count}
@ -199,13 +199,13 @@ export default function LoremIpsumPage() {
{/* Paragraph Length */}
<div>
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-gray-400">
Paragraph Length
</label>
<select
value={lengthPreset}
onChange={(e) => setLengthPreset(e.target.value as ParagraphLength)}
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] px-3 py-2 text-sm font-medium text-gray-200 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900/20"
>
<option value="short">Short (~35 words)</option>
<option value="medium">Medium (~75 words)</option>
@ -215,7 +215,7 @@ export default function LoremIpsumPage() {
{lengthPreset === "custom" && (
<div className="mt-2 flex items-center gap-2">
<span className="text-xs text-gray-500 dark:text-gray-400">Words:</span>
<span className="text-xs text-gray-500">Words:</span>
<input
type="number"
min="10"
@ -224,7 +224,7 @@ export default function LoremIpsumPage() {
onChange={(e) =>
setCustomWords(Math.min(Math.max(10, parseInt(e.target.value) || 10), 300))
}
className="w-24 rounded border border-gray-300 bg-white px-2 py-1 text-xs font-medium text-gray-900 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
className="w-24 border border-[#1a1a1a] bg-[#0a0a0a] px-2 py-1 text-xs font-medium text-gray-200 focus:outline-none focus:ring-1 focus:ring-blue-900"
/>
</div>
)}
@ -232,17 +232,17 @@ export default function LoremIpsumPage() {
{/* Output Format */}
<div>
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-gray-400">
Format
</label>
<div className="flex rounded-lg border border-gray-300 bg-gray-100 p-0.5 dark:border-gray-700 dark:bg-gray-800">
<div className="flex border border-[#1a1a1a] bg-[#111] p-0.5">
<button
type="button"
onClick={() => setFormat("plain")}
className={`flex-1 rounded-md py-1.5 text-xs font-medium transition-colors ${
className={`flex-1 py-1.5 text-xs font-medium transition-colors ${
format === "plain"
? "bg-white text-gray-900 shadow-xs dark:bg-gray-700 dark:text-white"
: "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
? "bg-[#0a0a0a] text-gray-200 shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Plain Text
@ -250,10 +250,10 @@ export default function LoremIpsumPage() {
<button
type="button"
onClick={() => setFormat("html")}
className={`flex-1 rounded-md py-1.5 text-xs font-medium transition-colors ${
className={`flex-1 py-1.5 text-xs font-medium transition-colors ${
format === "html"
? "bg-white text-gray-900 shadow-xs dark:bg-gray-700 dark:text-white"
: "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
? "bg-[#0a0a0a] text-gray-200 shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
HTML &lt;p&gt;
@ -263,15 +263,15 @@ export default function LoremIpsumPage() {
{/* Action Options */}
<div className="flex flex-col justify-between">
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-gray-400">
Options
</label>
<label className="inline-flex cursor-pointer items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
<label className="inline-flex cursor-pointer items-center gap-2 text-sm text-gray-400">
<input
type="checkbox"
checked={startWithLorem}
onChange={(e) => setStartWithLorem(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>Start with &ldquo;Lorem ipsum...&rdquo;</span>
</label>
@ -279,7 +279,7 @@ export default function LoremIpsumPage() {
<button
type="button"
onClick={generate}
className="mt-3 flex w-full items-center justify-center gap-1.5 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm transition-colors hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
className="mt-3 flex w-full items-center justify-center gap-1.5 bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 focus:outline-none focus:ring-1 focus:ring-blue-900 focus:ring-offset-2"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
@ -296,18 +296,18 @@ export default function LoremIpsumPage() {
</div>
{/* Output Section Header with Stats & Actions */}
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-gray-200 pb-3 dark:border-gray-700">
<div className="flex flex-wrap items-center gap-3 text-xs text-gray-500 dark:text-gray-400">
<span className="inline-flex items-center gap-1 font-medium text-gray-700 dark:text-gray-300">
<span className="font-semibold text-blue-600 dark:text-blue-400">{paragraphCount}</span> paragraphs
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-[#1a1a1a] pb-3">
<div className="flex flex-wrap items-center gap-3 text-xs text-gray-500">
<span className="inline-flex items-center gap-1 font-medium text-gray-400">
<span className="font-semibold text-blue-600">{paragraphCount}</span> paragraphs
</span>
<span>•</span>
<span className="inline-flex items-center gap-1 font-medium text-gray-700 dark:text-gray-300">
<span className="font-semibold text-blue-600 dark:text-blue-400">{wordCount}</span> words
<span className="inline-flex items-center gap-1 font-medium text-gray-400">
<span className="font-semibold text-blue-600">{wordCount}</span> words
</span>
<span>•</span>
<span className="inline-flex items-center gap-1 font-medium text-gray-700 dark:text-gray-300">
<span className="font-semibold text-blue-600 dark:text-blue-400">{charCount}</span> characters
<span className="inline-flex items-center gap-1 font-medium text-gray-400">
<span className="font-semibold text-blue-600">{charCount}</span> characters
</span>
</div>
@ -315,7 +315,7 @@ export default function LoremIpsumPage() {
<button
type="button"
onClick={handleCopy}
className={`inline-flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm font-medium transition-all ${
className={`inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium transition-all ${
copied
? "bg-green-600 text-white"
: "bg-blue-600 text-white hover:bg-blue-700 active:scale-95"
@ -347,7 +347,7 @@ export default function LoremIpsumPage() {
type="button"
onClick={handleDownload}
title="Download text file"
className="inline-flex items-center gap-1.5 rounded-lg bg-gray-100 px-3 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700"
className="inline-flex items-center gap-1.5 bg-[#111] px-3 py-2 text-sm font-medium text-gray-400 transition-colors hover:bg-[#1a1a1a]"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
@ -368,21 +368,21 @@ export default function LoremIpsumPage() {
readOnly
value={generatedText}
rows={12}
className="w-full rounded-lg border border-gray-300 bg-white p-4 font-sans text-sm leading-relaxed text-gray-900 shadow-inner focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-700 dark:bg-gray-800/80 dark:text-gray-100"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] p-4 font-sans text-sm leading-relaxed text-gray-200 shadow-inner focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900/20"
aria-label="Generated Lorem Ipsum Text"
/>
</div>
{/* Informational Details */}
<div className="grid grid-cols-1 gap-4 pt-2 text-xs text-gray-500 dark:text-gray-400 sm:grid-cols-2">
<div className="rounded-lg border border-gray-100 bg-gray-50 p-3.5 dark:border-gray-800 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200">Origin of Lorem Ipsum</h4>
<div className="grid grid-cols-1 gap-4 pt-2 text-xs text-gray-500 sm:grid-cols-2">
<div className=" border border-gray-100 bg-black p-3.5">
<h4 className="font-semibold text-gray-300">Origin of Lorem Ipsum</h4>
<p className="mt-1 leading-normal">
Lorem Ipsum has been standard placeholder text since the 1500s, adapted from Cicero&apos;s philosophical work <em>&ldquo;de Finibus Bonorum et Malorum&rdquo;</em> written in 45 BC.
</p>
</div>
<div className="rounded-lg border border-gray-100 bg-gray-50 p-3.5 dark:border-gray-800 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200">Design &amp; Layout Utility</h4>
<div className=" border border-gray-100 bg-black p-3.5">
<h4 className="font-semibold text-gray-300">Design &amp; Layout Utility</h4>
<p className="mt-1 leading-normal">
Using realistic filler text with natural word and sentence distribution avoids distracting reviewers with readable content while focusing purely on layout and typography.
</p>

View file

@ -382,45 +382,45 @@ ${renderedHtml}
` }} />
{/* Top Control Bar */}
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-gray-200 pb-3 dark:border-gray-800">
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-[#1a1a1a] pb-3">
{/* View Mode Switcher */}
<div className="flex items-center rounded-lg bg-gray-100 p-1 dark:bg-gray-800">
<div className="flex items-center bg-[#111] p-1">
<button
onClick={() => setViewMode("split")}
className={`rounded-md px-3 py-1 text-xs font-medium transition-all ${
className={` px-3 py-1 text-xs font-medium transition-all ${
viewMode === "split"
? "bg-white text-blue-600 shadow-xs dark:bg-gray-700 dark:text-white"
: "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
? "bg-[#0a0a0a] text-blue-600 shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Split View
</button>
<button
onClick={() => setViewMode("preview")}
className={`rounded-md px-3 py-1 text-xs font-medium transition-all ${
className={` px-3 py-1 text-xs font-medium transition-all ${
viewMode === "preview"
? "bg-white text-blue-600 shadow-xs dark:bg-gray-700 dark:text-white"
: "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
? "bg-[#0a0a0a] text-blue-600 shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Preview
</button>
<button
onClick={() => setViewMode("rawHtml")}
className={`rounded-md px-3 py-1 text-xs font-medium transition-all ${
className={` px-3 py-1 text-xs font-medium transition-all ${
viewMode === "rawHtml"
? "bg-white text-blue-600 shadow-xs dark:bg-gray-700 dark:text-white"
: "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
? "bg-[#0a0a0a] text-blue-600 shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
HTML Code
</button>
<button
onClick={() => setViewMode("edit")}
className={`rounded-md px-3 py-1 text-xs font-medium transition-all ${
className={` px-3 py-1 text-xs font-medium transition-all ${
viewMode === "edit"
? "bg-white text-blue-600 shadow-xs dark:bg-gray-700 dark:text-white"
: "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
? "bg-[#0a0a0a] text-blue-600 shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Editor Only
@ -431,7 +431,7 @@ ${renderedHtml}
<div className="flex flex-wrap items-center gap-2">
<button
onClick={handleCopyHtml}
className="inline-flex items-center gap-1.5 rounded-lg bg-blue-600 px-3.5 py-1.5 text-xs font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 active:scale-95"
className="inline-flex items-center gap-1.5 bg-blue-600 px-3.5 py-1.5 text-xs font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 active:scale-95"
>
{copiedHtml ? (
<>
@ -457,14 +457,14 @@ ${renderedHtml}
<button
onClick={handleCopyMarkdown}
className="inline-flex items-center gap-1 rounded-lg bg-gray-200 px-3 py-1.5 text-xs font-medium text-gray-800 transition-colors hover:bg-gray-300 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700"
className="inline-flex items-center gap-1 bg-[#1a1a1a] px-3 py-1.5 text-xs font-medium text-gray-300 transition-colors hover:bg-[#222]"
>
{copiedMd ? "Markdown Copied!" : "Copy Markdown"}
</button>
<button
onClick={handleDownloadHtml}
className="inline-flex items-center gap-1 rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
className="inline-flex items-center gap-1 border border-[#1a1a1a] bg-[#0a0a0a] px-2.5 py-1.5 text-xs font-medium text-gray-400 hover:bg-black"
title="Download standalone HTML document"
>
<svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -475,7 +475,7 @@ ${renderedHtml}
<button
onClick={handleDownloadMd}
className="inline-flex items-center gap-1 rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
className="inline-flex items-center gap-1 border border-[#1a1a1a] bg-[#0a0a0a] px-2.5 py-1.5 text-xs font-medium text-gray-400 hover:bg-black"
title="Download markdown file"
>
<svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -486,14 +486,14 @@ ${renderedHtml}
<button
onClick={() => setMarkdown(SAMPLE_MARKDOWN)}
className="rounded-md px-2.5 py-1 text-xs font-medium text-gray-600 underline-offset-2 hover:text-blue-600 hover:underline dark:text-gray-400 dark:hover:text-blue-400"
className=" px-2.5 py-1 text-xs font-medium text-gray-600 underline-offset-2 hover:text-blue-600 hover:underline"
>
Reset Sample
</button>
<button
onClick={() => setMarkdown("")}
className="rounded-md px-2.5 py-1 text-xs font-medium text-red-600 underline-offset-2 hover:text-red-700 hover:underline dark:text-red-400 dark:hover:text-red-300"
className=" px-2.5 py-1 text-xs font-medium text-red-400 underline-offset-2 hover:text-red-400 hover:underline"
>
Clear
</button>
@ -501,12 +501,12 @@ ${renderedHtml}
</div>
{/* Quick Markdown Insertion Toolbar */}
<div className="flex flex-wrap items-center gap-1 rounded-lg border border-gray-200 bg-gray-50/70 p-1.5 text-xs dark:border-gray-800 dark:bg-gray-800/40">
<span className="px-2 font-medium text-gray-500 dark:text-gray-400">Insert:</span>
<div className="flex flex-wrap items-center gap-1 border border-[#1a1a1a] bg-black/70 p-1.5 text-xs">
<span className="px-2 font-medium text-gray-500">Insert:</span>
<button
type="button"
onClick={() => insertSnippet("**", "**", "bold text")}
className="rounded px-2 py-1 font-bold text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded px-2 py-1 font-bold text-gray-400 hover:bg-[#1a1a1a]"
title="Bold"
>
B
@ -514,7 +514,7 @@ ${renderedHtml}
<button
type="button"
onClick={() => insertSnippet("*", "*", "italic text")}
className="rounded px-2 py-1 italic text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded px-2 py-1 italic text-gray-400 hover:bg-[#1a1a1a]"
title="Italic"
>
I
@ -522,7 +522,7 @@ ${renderedHtml}
<button
type="button"
onClick={() => insertSnippet("## ", "\n", "Heading 2")}
className="rounded px-2 py-1 font-semibold text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded px-2 py-1 font-semibold text-gray-400 hover:bg-[#1a1a1a]"
title="Heading 2"
>
H2
@ -530,7 +530,7 @@ ${renderedHtml}
<button
type="button"
onClick={() => insertSnippet("### ", "\n", "Heading 3")}
className="rounded px-2 py-1 font-semibold text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded px-2 py-1 font-semibold text-gray-400 hover:bg-[#1a1a1a]"
title="Heading 3"
>
H3
@ -538,7 +538,7 @@ ${renderedHtml}
<button
type="button"
onClick={() => insertSnippet("[", "](https://example.com)", "link title")}
className="rounded px-2 py-1 text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded px-2 py-1 text-gray-400 hover:bg-[#1a1a1a]"
title="Link"
>
🔗 Link
@ -546,7 +546,7 @@ ${renderedHtml}
<button
type="button"
onClick={() => insertSnippet("\n```javascript\n", "\n```\n", "// code here")}
className="rounded px-2 py-1 font-mono text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded px-2 py-1 font-mono text-gray-400 hover:bg-[#1a1a1a]"
title="Code Block"
>
{"</>"} Code
@ -554,7 +554,7 @@ ${renderedHtml}
<button
type="button"
onClick={() => insertSnippet("\n> ", "\n", "Quote text")}
className="rounded px-2 py-1 text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded px-2 py-1 text-gray-400 hover:bg-[#1a1a1a]"
title="Blockquote"
>
❝ Quote
@ -562,7 +562,7 @@ ${renderedHtml}
<button
type="button"
onClick={() => insertSnippet("- ", "\n", "List item")}
className="rounded px-2 py-1 text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded px-2 py-1 text-gray-400 hover:bg-[#1a1a1a]"
title="Bullet list"
>
• List
@ -570,7 +570,7 @@ ${renderedHtml}
<button
type="button"
onClick={() => insertSnippet("- [ ] ", "\n", "Todo task")}
className="rounded px-2 py-1 text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded px-2 py-1 text-gray-400 hover:bg-[#1a1a1a]"
title="Checklist task"
>
☑ Task
@ -578,7 +578,7 @@ ${renderedHtml}
<button
type="button"
onClick={() => insertSnippet("\n| Column 1 | Column 2 |\n| :--- | :--- |\n| Value 1 | Value 2 |\n", "")}
className="rounded px-2 py-1 text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded px-2 py-1 text-gray-400 hover:bg-[#1a1a1a]"
title="Table"
>
▦ Table
@ -597,10 +597,10 @@ ${renderedHtml}
{(viewMode === "split" || viewMode === "edit") && (
<div className="flex flex-col">
<div className="mb-2 flex items-center justify-between">
<span className="text-sm font-semibold text-gray-800 dark:text-gray-200">
<span className="text-sm font-semibold text-gray-300">
Markdown Source
</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
<span className="text-xs text-gray-500">
{stats.lines} lines · {stats.words} words · {stats.chars} chars
</span>
</div>
@ -611,7 +611,7 @@ ${renderedHtml}
placeholder="Type your markdown here..."
rows={22}
spellCheck={false}
className="w-full flex-1 resize-y rounded-lg border border-gray-300 bg-white p-4 font-mono text-sm leading-relaxed text-gray-900 shadow-xs focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-500"
className="w-full flex-1 resize-y border border-[#1a1a1a] bg-[#0a0a0a] p-4 font-mono text-sm leading-relaxed text-gray-200 shadow-xs focus:border-blue-500 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
)}
@ -620,15 +620,15 @@ ${renderedHtml}
{(viewMode === "split" || viewMode === "preview") && (
<div className="flex flex-col">
<div className="mb-2 flex items-center justify-between">
<span className="text-sm font-semibold text-gray-800 dark:text-gray-200">
<span className="text-sm font-semibold text-gray-300">
Rendered Preview
</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
<span className="text-xs text-gray-500">
Estimated read time: ~{stats.readingTimeMinutes} min
</span>
</div>
<div
className="markdown-preview-output min-h-[500px] flex-1 overflow-y-auto rounded-lg border border-gray-300 bg-white p-5 text-gray-900 shadow-xs dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100"
className="markdown-preview-output min-h-[500px] flex-1 overflow-y-auto border border-[#1a1a1a] bg-[#0a0a0a] p-5 text-gray-200 shadow-xs"
dangerouslySetInnerHTML={{ __html: renderedHtml }}
/>
</div>
@ -637,10 +637,10 @@ ${renderedHtml}
{viewMode === "rawHtml" && (
<div className="flex flex-col">
<div className="mb-2 flex items-center justify-between">
<span className="text-sm font-semibold text-gray-800 dark:text-gray-200">
<span className="text-sm font-semibold text-gray-300">
Generated HTML Code
</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
<span className="text-xs text-gray-500">
{renderedHtml.length} characters
</span>
</div>
@ -648,30 +648,30 @@ ${renderedHtml}
value={renderedHtml}
readOnly
rows={22}
className="w-full flex-1 resize-y rounded-lg border border-gray-300 bg-gray-50 p-4 font-mono text-xs leading-relaxed text-gray-900 shadow-xs focus:outline-none dark:border-gray-700 dark:bg-gray-900/90 dark:text-gray-200"
className="w-full flex-1 resize-y border border-[#1a1a1a] bg-black p-4 font-mono text-xs leading-relaxed text-gray-200 shadow-xs focus:outline-none"
/>
</div>
)}
</div>
{/* Informational Footer */}
<div className="mt-8 rounded-xl border border-gray-200 bg-gray-50/70 p-5 text-sm text-gray-600 dark:border-gray-800 dark:bg-gray-800/30 dark:text-gray-400">
<h3 className="font-semibold text-gray-900 dark:text-gray-100">About Live Markdown Preview</h3>
<div className="mt-8 border border-[#1a1a1a] bg-black/70 p-5 text-sm text-gray-600">
<h3 className="font-semibold text-gray-200">About Live Markdown Preview</h3>
<div className="mt-3 grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">⚡ Live Rendering</p>
<p className="font-medium text-gray-300">⚡ Live Rendering</p>
<p className="mt-1 text-xs leading-normal">
Real-time parsing as you type using the fast and robust marked compiler with Github Flavored Markdown (GFM) support.
</p>
</div>
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">📋 Export & Share</p>
<p className="font-medium text-gray-300">📋 Export & Share</p>
<p className="mt-1 text-xs leading-normal">
Instantly copy the generated HTML code or download standalone .html and .md files ready for publishing.
</p>
</div>
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">🔒 100% Private</p>
<p className="font-medium text-gray-300">🔒 100% Private</p>
<p className="mt-1 text-xs leading-normal">
Zero server requests. All markdown processing and parsing occurs strictly within your browser.
</p>

View file

@ -162,17 +162,17 @@ export default function MetaTagGeneratorPage() {
const descLen = state.description.length;
const getTitleStatus = (len: number) => {
if (len === 0) return { label: "Empty", color: "text-gray-400", bg: "bg-gray-200 dark:bg-gray-700", border: "border-gray-300" };
if (len <= 60) return { label: "Optimal (<60)", color: "text-emerald-600 dark:text-emerald-400", bg: "bg-emerald-500", border: "border-emerald-500" };
if (len <= 70) return { label: "Slightly Long (61-70)", color: "text-amber-600 dark:text-amber-400", bg: "bg-amber-500", border: "border-amber-500" };
return { label: "Too Long (>70)", color: "text-rose-600 dark:text-rose-400", bg: "bg-rose-500", border: "border-rose-500" };
if (len === 0) return { label: "Empty", color: "text-gray-400", bg: "bg-[#1a1a1a]", border: "border-[#1a1a1a]" };
if (len <= 60) return { label: "Optimal (<60)", color: "text-emerald-600", bg: "bg-emerald-500", border: "border-emerald-500" };
if (len <= 70) return { label: "Slightly Long (61-70)", color: "text-amber-600", bg: "bg-amber-500", border: "border-amber-500" };
return { label: "Too Long (>70)", color: "text-rose-600", bg: "bg-rose-500", border: "border-rose-500" };
};
const getDescStatus = (len: number) => {
if (len === 0) return { label: "Empty", color: "text-gray-400", bg: "bg-gray-200 dark:bg-gray-700", border: "border-gray-300" };
if (len <= 160) return { label: "Optimal (<160)", color: "text-emerald-600 dark:text-emerald-400", bg: "bg-emerald-500", border: "border-emerald-500" };
if (len <= 180) return { label: "Slightly Long (161-180)", color: "text-amber-600 dark:text-amber-400", bg: "bg-amber-500", border: "border-amber-500" };
return { label: "Too Long (>180)", color: "text-rose-600 dark:text-rose-400", bg: "bg-rose-500", border: "border-rose-500" };
if (len === 0) return { label: "Empty", color: "text-gray-400", bg: "bg-[#1a1a1a]", border: "border-[#1a1a1a]" };
if (len <= 160) return { label: "Optimal (<160)", color: "text-emerald-600", bg: "bg-emerald-500", border: "border-emerald-500" };
if (len <= 180) return { label: "Slightly Long (161-180)", color: "text-amber-600", bg: "bg-amber-500", border: "border-amber-500" };
return { label: "Too Long (>180)", color: "text-rose-600", bg: "bg-rose-500", border: "border-rose-500" };
};
const titleStatus = getTitleStatus(titleLen);
@ -309,15 +309,15 @@ export default function MetaTagGeneratorPage() {
<div className="space-y-8">
{/* Top Controls and Presets */}
<div className="flex flex-wrap items-center justify-between gap-3 pb-3 border-b border-gray-200 dark:border-gray-800">
<div className="flex flex-wrap items-center justify-between gap-3 pb-3 border-b border-[#1a1a1a]">
<div className="flex flex-wrap items-center gap-2">
<span className="text-xs font-semibold text-gray-500 dark:text-gray-400">Presets:</span>
<span className="text-xs font-semibold text-gray-500">Presets:</span>
{SAMPLE_PRESETS.map((p) => (
<button
key={p.name}
type="button"
onClick={() => handleApplyPreset(p.state)}
className="px-2.5 py-1 text-xs font-medium bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200 rounded-md transition-colors cursor-pointer"
className="px-2.5 py-1 text-xs font-medium bg-[#111] hover:bg-[#1a1a1a] text-gray-400 transition-colors cursor-pointer"
>
{p.name}
</button>
@ -328,7 +328,7 @@ export default function MetaTagGeneratorPage() {
<button
type="button"
onClick={handleSyncToSocial}
className="px-3 py-1 text-xs font-medium bg-blue-50 hover:bg-blue-100 dark:bg-blue-950/50 dark:hover:bg-blue-900/60 text-blue-700 dark:text-blue-300 rounded-md transition-colors cursor-pointer flex items-center gap-1"
className="px-3 py-1 text-xs font-medium bg-[#0a0a1a] hover:bg-[#0a0a1a] text-blue-400 transition-colors cursor-pointer flex items-center gap-1"
title="Copy Title & Description into Open Graph and Twitter Card fields"
>
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -339,7 +339,7 @@ export default function MetaTagGeneratorPage() {
<button
type="button"
onClick={handleClear}
className="px-3 py-1 text-xs font-medium bg-gray-100 hover:bg-rose-50 dark:bg-gray-800 dark:hover:bg-rose-950/30 text-gray-700 dark:text-gray-300 hover:text-rose-600 dark:hover:text-rose-400 rounded-md transition-colors cursor-pointer"
className="px-3 py-1 text-xs font-medium bg-[#111] hover:bg-rose-50 text-gray-400 hover:text-rose-600 transition-colors cursor-pointer"
>
Clear
</button>
@ -351,12 +351,12 @@ export default function MetaTagGeneratorPage() {
{/* Left Column: Form Fields (7 cols on large screens) */}
<div className="lg:col-span-7 space-y-6">
{/* Section 1: Basic SEO Meta Tags */}
<div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900/60 p-5 shadow-xs space-y-4">
<div className="flex items-center gap-2 border-b border-gray-100 dark:border-gray-800 pb-2">
<span className="flex h-6 w-6 items-center justify-center rounded-md bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300 text-xs font-bold">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-5 shadow-xs space-y-4">
<div className="flex items-center gap-2 border-b border-gray-100 pb-2">
<span className="flex h-6 w-6 items-center justify-center bg-[#0a0a1a] text-blue-400 text-xs font-bold">
1
</span>
<h2 className="text-sm font-bold text-gray-900 dark:text-white">
<h2 className="text-sm font-bold text-gray-200">
Basic SEO &amp; Search Engine Tags
</h2>
</div>
@ -364,7 +364,7 @@ export default function MetaTagGeneratorPage() {
{/* Title Field with Character Warning */}
<div className="space-y-1.5">
<div className="flex items-center justify-between">
<label htmlFor={titleId} className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label htmlFor={titleId} className="text-xs font-semibold text-gray-400">
Page Title <span className="text-rose-500">*</span>
</label>
<div className="flex items-center gap-2">
@ -379,10 +379,10 @@ export default function MetaTagGeneratorPage() {
value={state.title}
onChange={(e) => setState({ ...state, title: e.target.value })}
placeholder="e.g. My Website — High Performance Developer Utilities"
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none font-sans"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none font-sans"
/>
{/* Visual Progress Bar */}
<div className="h-1.5 w-full overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div className="h-1.5 w-full overflow-hidden rounded-none bg-[#111]">
<div
className={`h-full transition-all duration-300 ${titleStatus.bg}`}
style={{ width: `${Math.min((titleLen / 60) * 100, 100)}%` }}
@ -393,7 +393,7 @@ export default function MetaTagGeneratorPage() {
{/* Description Field with Character Warning */}
<div className="space-y-1.5">
<div className="flex items-center justify-between">
<label htmlFor={descId} className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label htmlFor={descId} className="text-xs font-semibold text-gray-400">
Meta Description <span className="text-rose-500">*</span>
</label>
<div className="flex items-center gap-2">
@ -408,10 +408,10 @@ export default function MetaTagGeneratorPage() {
value={state.description}
onChange={(e) => setState({ ...state, description: e.target.value })}
placeholder="A concise summary of your webpage for search engine results..."
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none resize-y font-sans leading-relaxed"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none resize-y font-sans leading-relaxed"
/>
{/* Visual Progress Bar */}
<div className="h-1.5 w-full overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div className="h-1.5 w-full overflow-hidden rounded-none bg-[#111]">
<div
className={`h-full transition-all duration-300 ${descStatus.bg}`}
style={{ width: `${Math.min((descLen / 160) * 100, 100)}%` }}
@ -422,7 +422,7 @@ export default function MetaTagGeneratorPage() {
{/* Keywords and Author Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-1">
<label htmlFor={keywordsId} className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label htmlFor={keywordsId} className="text-xs font-semibold text-gray-400">
Keywords (comma-separated)
</label>
<input
@ -431,12 +431,12 @@ export default function MetaTagGeneratorPage() {
value={state.keywords}
onChange={(e) => setState({ ...state, keywords: e.target.value })}
placeholder="tools, developer, seo, open graph"
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
<div className="space-y-1">
<label htmlFor={authorId} className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label htmlFor={authorId} className="text-xs font-semibold text-gray-400">
Author
</label>
<input
@ -445,7 +445,7 @@ export default function MetaTagGeneratorPage() {
value={state.author}
onChange={(e) => setState({ ...state, author: e.target.value })}
placeholder="Jane Doe or Organization"
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
</div>
@ -453,7 +453,7 @@ export default function MetaTagGeneratorPage() {
{/* Canonical URL & Theme Color */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div className="sm:col-span-2 space-y-1">
<label htmlFor={canonicalId} className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label htmlFor={canonicalId} className="text-xs font-semibold text-gray-400">
Canonical URL
</label>
<input
@ -462,12 +462,12 @@ export default function MetaTagGeneratorPage() {
value={state.canonicalUrl}
onChange={(e) => setState({ ...state, canonicalUrl: e.target.value })}
placeholder="https://example.com/page-url"
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm font-mono focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-sm font-mono focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label className="text-xs font-semibold text-gray-400">
Theme Color
</label>
<div className="flex items-center gap-2">
@ -475,80 +475,80 @@ export default function MetaTagGeneratorPage() {
type="color"
value={state.themeColor || "#2563eb"}
onChange={(e) => setState({ ...state, themeColor: e.target.value })}
className="h-9 w-9 p-0.5 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 cursor-pointer"
className="h-9 w-9 p-0.5 border border-[#1a1a1a] bg-[#0a0a0a] cursor-pointer"
/>
<input
type="text"
value={state.themeColor}
onChange={(e) => setState({ ...state, themeColor: e.target.value })}
placeholder="#2563eb"
className="flex-1 p-2 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono text-xs focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="flex-1 p-2 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-mono text-xs focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
</div>
</div>
{/* Robots Directives Checkboxes */}
<div className="pt-2 border-t border-gray-100 dark:border-gray-800">
<span className="text-xs font-semibold text-gray-700 dark:text-gray-300 block mb-2">
<div className="pt-2 border-t border-gray-100">
<span className="text-xs font-semibold text-gray-400 block mb-2">
Robots &amp; Crawler Indexing Directives
</span>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2 text-xs">
<label className="flex items-center gap-2 p-2 rounded-lg border border-gray-200 dark:border-gray-700/60 bg-gray-50/50 dark:bg-gray-800/40 cursor-pointer select-none">
<label className="flex items-center gap-2 p-2 border border-[#1a1a1a] bg-black/50 cursor-pointer select-none">
<input
type="checkbox"
checked={state.robotsIndex}
onChange={(e) => setState({ ...state, robotsIndex: e.target.checked })}
className="w-4 h-4 text-blue-600 rounded border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-4 h-4 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span className="font-medium text-gray-800 dark:text-gray-200">
<span className="font-medium text-gray-300">
{state.robotsIndex ? "Index (allow)" : "NoIndex (block)"}
</span>
</label>
<label className="flex items-center gap-2 p-2 rounded-lg border border-gray-200 dark:border-gray-700/60 bg-gray-50/50 dark:bg-gray-800/40 cursor-pointer select-none">
<label className="flex items-center gap-2 p-2 border border-[#1a1a1a] bg-black/50 cursor-pointer select-none">
<input
type="checkbox"
checked={state.robotsFollow}
onChange={(e) => setState({ ...state, robotsFollow: e.target.checked })}
className="w-4 h-4 text-blue-600 rounded border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-4 h-4 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span className="font-medium text-gray-800 dark:text-gray-200">
<span className="font-medium text-gray-300">
{state.robotsFollow ? "Follow links" : "NoFollow links"}
</span>
</label>
<label className="flex items-center gap-2 p-2 rounded-lg border border-gray-200 dark:border-gray-700/60 bg-gray-50/50 dark:bg-gray-800/40 cursor-pointer select-none">
<label className="flex items-center gap-2 p-2 border border-[#1a1a1a] bg-black/50 cursor-pointer select-none">
<input
type="checkbox"
checked={state.robotsNoArchive}
onChange={(e) => setState({ ...state, robotsNoArchive: e.target.checked })}
className="w-4 h-4 text-blue-600 rounded border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-4 h-4 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span className="text-gray-700 dark:text-gray-300">NoArchive</span>
<span className="text-gray-400">NoArchive</span>
</label>
<label className="flex items-center gap-2 p-2 rounded-lg border border-gray-200 dark:border-gray-700/60 bg-gray-50/50 dark:bg-gray-800/40 cursor-pointer select-none">
<label className="flex items-center gap-2 p-2 border border-[#1a1a1a] bg-black/50 cursor-pointer select-none">
<input
type="checkbox"
checked={state.robotsNoSnippet}
onChange={(e) => setState({ ...state, robotsNoSnippet: e.target.checked })}
className="w-4 h-4 text-blue-600 rounded border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-4 h-4 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span className="text-gray-700 dark:text-gray-300">NoSnippet</span>
<span className="text-gray-400">NoSnippet</span>
</label>
</div>
</div>
</div>
{/* Section 2: Open Graph (Facebook / LinkedIn / Discord) */}
<div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900/60 p-5 shadow-xs space-y-4">
<div className="flex items-center justify-between border-b border-gray-100 dark:border-gray-800 pb-2">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-5 shadow-xs space-y-4">
<div className="flex items-center justify-between border-b border-gray-100 pb-2">
<div className="flex items-center gap-2">
<span className="flex h-6 w-6 items-center justify-center rounded-md bg-indigo-100 text-indigo-700 dark:bg-indigo-950 dark:text-indigo-300 text-xs font-bold">
<span className="flex h-6 w-6 items-center justify-center bg-indigo-100 text-indigo-700 text-xs font-bold">
2
</span>
<h2 className="text-sm font-bold text-gray-900 dark:text-white">
<h2 className="text-sm font-bold text-gray-200">
Open Graph Tags (Facebook, LinkedIn, Discord)
</h2>
</div>
@ -557,13 +557,13 @@ export default function MetaTagGeneratorPage() {
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-1">
<label className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label className="text-xs font-semibold text-gray-400">
OG Type (og:type)
</label>
<select
value={state.ogType}
onChange={(e) => setState({ ...state, ogType: e.target.value })}
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none"
>
<option value="website">website</option>
<option value="article">article</option>
@ -575,7 +575,7 @@ export default function MetaTagGeneratorPage() {
</div>
<div className="space-y-1">
<label className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label className="text-xs font-semibold text-gray-400">
Site Name (og:site_name)
</label>
<input
@ -583,13 +583,13 @@ export default function MetaTagGeneratorPage() {
value={state.ogSiteName}
onChange={(e) => setState({ ...state, ogSiteName: e.target.value })}
placeholder="e.g. My Awesome Site"
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
</div>
<div className="space-y-1">
<label className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label className="text-xs font-semibold text-gray-400">
OG Title (og:title)
</label>
<input
@ -597,12 +597,12 @@ export default function MetaTagGeneratorPage() {
value={state.ogTitle}
onChange={(e) => setState({ ...state, ogTitle: e.target.value })}
placeholder="Custom title for social shares..."
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label className="text-xs font-semibold text-gray-400">
OG Description (og:description)
</label>
<textarea
@ -610,13 +610,13 @@ export default function MetaTagGeneratorPage() {
value={state.ogDescription}
onChange={(e) => setState({ ...state, ogDescription: e.target.value })}
placeholder="Custom description for social card shares..."
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none resize-y leading-relaxed"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none resize-y leading-relaxed"
/>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-1">
<label className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label className="text-xs font-semibold text-gray-400">
OG Image URL (og:image)
</label>
<input
@ -624,12 +624,12 @@ export default function MetaTagGeneratorPage() {
value={state.ogImage}
onChange={(e) => setState({ ...state, ogImage: e.target.value })}
placeholder="https://example.com/og-image.jpg"
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono text-xs focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-mono text-xs focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label className="text-xs font-semibold text-gray-400">
OG URL (og:url)
</label>
<input
@ -637,20 +637,20 @@ export default function MetaTagGeneratorPage() {
value={state.ogUrl}
onChange={(e) => setState({ ...state, ogUrl: e.target.value })}
placeholder="https://example.com/page"
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono text-xs focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-mono text-xs focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
</div>
</div>
{/* Section 3: Twitter / X Cards */}
<div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900/60 p-5 shadow-xs space-y-4">
<div className="flex items-center justify-between border-b border-gray-100 dark:border-gray-800 pb-2">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-5 shadow-xs space-y-4">
<div className="flex items-center justify-between border-b border-gray-100 pb-2">
<div className="flex items-center gap-2">
<span className="flex h-6 w-6 items-center justify-center rounded-md bg-sky-100 text-sky-700 dark:bg-sky-950 dark:text-sky-300 text-xs font-bold">
<span className="flex h-6 w-6 items-center justify-center bg-sky-100 text-sky-700 text-xs font-bold">
3
</span>
<h2 className="text-sm font-bold text-gray-900 dark:text-white">
<h2 className="text-sm font-bold text-gray-200">
Twitter / X Card Tags
</h2>
</div>
@ -659,7 +659,7 @@ export default function MetaTagGeneratorPage() {
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div className="space-y-1">
<label className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label className="text-xs font-semibold text-gray-400">
Card Type
</label>
<select
@ -670,7 +670,7 @@ export default function MetaTagGeneratorPage() {
twitterCard: e.target.value as MetaTagState["twitterCard"],
})
}
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none"
>
<option value="summary_large_image">summary_large_image (Large Hero)</option>
<option value="summary">summary (Small Thumbnail)</option>
@ -680,7 +680,7 @@ export default function MetaTagGeneratorPage() {
</div>
<div className="space-y-1">
<label className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label className="text-xs font-semibold text-gray-400">
Site Handle (@username)
</label>
<input
@ -688,12 +688,12 @@ export default function MetaTagGeneratorPage() {
value={state.twitterSite}
onChange={(e) => setState({ ...state, twitterSite: e.target.value })}
placeholder="@company"
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm font-mono focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-sm font-mono focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label className="text-xs font-semibold text-gray-400">
Creator Handle (@author)
</label>
<input
@ -701,13 +701,13 @@ export default function MetaTagGeneratorPage() {
value={state.twitterCreator}
onChange={(e) => setState({ ...state, twitterCreator: e.target.value })}
placeholder="@creator"
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm font-mono focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-sm font-mono focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
</div>
<div className="space-y-1">
<label className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label className="text-xs font-semibold text-gray-400">
Twitter Card Title (optional override)
</label>
<input
@ -715,12 +715,12 @@ export default function MetaTagGeneratorPage() {
value={state.twitterTitle}
onChange={(e) => setState({ ...state, twitterTitle: e.target.value })}
placeholder="Leave empty or fill to override standard title..."
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label className="text-xs font-semibold text-gray-400">
Twitter Image URL
</label>
<input
@ -728,7 +728,7 @@ export default function MetaTagGeneratorPage() {
value={state.twitterImage}
onChange={(e) => setState({ ...state, twitterImage: e.target.value })}
placeholder="https://example.com/twitter-image.jpg"
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono text-xs focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-mono text-xs focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
</div>
@ -738,14 +738,14 @@ export default function MetaTagGeneratorPage() {
<div className="lg:col-span-5 space-y-4">
<div className="sticky top-6 space-y-4">
{/* Tab Navigation */}
<div className="flex rounded-xl border border-gray-200 dark:border-gray-800 bg-gray-100 dark:bg-gray-900 p-1">
<div className="flex border border-[#1a1a1a] bg-[#111] p-1">
<button
type="button"
onClick={() => setPreviewTab("html")}
className={`flex-1 py-1.5 rounded-lg text-xs font-semibold transition-all cursor-pointer ${
className={`flex-1 py-1.5 text-xs font-semibold transition-all cursor-pointer ${
previewTab === "html"
? "bg-white dark:bg-gray-800 text-blue-600 dark:text-blue-400 shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
? "bg-[#0a0a0a] text-blue-600 shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
HTML Tags
@ -753,10 +753,10 @@ export default function MetaTagGeneratorPage() {
<button
type="button"
onClick={() => setPreviewTab("google")}
className={`flex-1 py-1.5 rounded-lg text-xs font-semibold transition-all cursor-pointer ${
className={`flex-1 py-1.5 text-xs font-semibold transition-all cursor-pointer ${
previewTab === "google"
? "bg-white dark:bg-gray-800 text-blue-600 dark:text-blue-400 shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
? "bg-[#0a0a0a] text-blue-600 shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Google
@ -764,10 +764,10 @@ export default function MetaTagGeneratorPage() {
<button
type="button"
onClick={() => setPreviewTab("facebook")}
className={`flex-1 py-1.5 rounded-lg text-xs font-semibold transition-all cursor-pointer ${
className={`flex-1 py-1.5 text-xs font-semibold transition-all cursor-pointer ${
previewTab === "facebook"
? "bg-white dark:bg-gray-800 text-blue-600 dark:text-blue-400 shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
? "bg-[#0a0a0a] text-blue-600 shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Facebook / OG
@ -775,10 +775,10 @@ export default function MetaTagGeneratorPage() {
<button
type="button"
onClick={() => setPreviewTab("twitter")}
className={`flex-1 py-1.5 rounded-lg text-xs font-semibold transition-all cursor-pointer ${
className={`flex-1 py-1.5 text-xs font-semibold transition-all cursor-pointer ${
previewTab === "twitter"
? "bg-white dark:bg-gray-800 text-blue-600 dark:text-blue-400 shadow-xs"
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
? "bg-[#0a0a0a] text-blue-600 shadow-xs"
: "text-gray-600 hover:text-gray-200"
}`}
>
Twitter / X
@ -787,19 +787,19 @@ export default function MetaTagGeneratorPage() {
{/* Tab 1: HTML Output */}
{previewTab === "html" && (
<div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-gray-900 text-gray-100 overflow-hidden shadow-lg">
<div className=" border border-[#1a1a1a] bg-gray-900 text-gray-100 overflow-hidden ">
<div className="flex items-center justify-between px-4 py-2.5 bg-gray-950/80 border-b border-gray-800">
<div className="flex items-center gap-2">
<span className="h-3 w-3 rounded-full bg-red-500/80 inline-block" />
<span className="h-3 w-3 rounded-full bg-amber-500/80 inline-block" />
<span className="h-3 w-3 rounded-full bg-emerald-500/80 inline-block" />
<span className="h-3 w-3 rounded-none bg-[#1a0a0a]0/80 inline-block" />
<span className="h-3 w-3 rounded-none bg-amber-500/80 inline-block" />
<span className="h-3 w-3 rounded-none bg-emerald-500/80 inline-block" />
<span className="ml-2 text-xs font-mono text-gray-400">&lt;head&gt; tags</span>
</div>
<button
type="button"
onClick={handleCopy}
className="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white rounded text-xs font-medium transition-colors cursor-pointer flex items-center gap-1.5"
className="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-xs font-medium transition-colors cursor-pointer flex items-center gap-1.5"
>
{copied ? (
<>
@ -829,21 +829,21 @@ export default function MetaTagGeneratorPage() {
{/* Tab 2: Google Search Snippet Preview */}
{previewTab === "google" && (
<div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-5 shadow-xs space-y-3">
<div className="flex items-center justify-between border-b border-gray-100 dark:border-gray-800 pb-2">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-5 shadow-xs space-y-3">
<div className="flex items-center justify-between border-b border-gray-100 pb-2">
<span className="text-xs font-bold uppercase tracking-wider text-gray-500">
Google Search Result Snippet
</span>
<span className="text-[11px] text-gray-400 font-mono">Desktop &amp; Mobile</span>
</div>
<div className="p-4 rounded-xl border border-gray-100 dark:border-gray-800 bg-gray-50/50 dark:bg-gray-800/30 space-y-1.5 font-sans">
<div className="p-4 border border-gray-100 bg-black/50 space-y-1.5 font-sans">
{/* Breadcrumbs & URL */}
<div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400 truncate">
<div className="w-4 h-4 rounded-full bg-blue-600 text-white text-[10px] flex items-center justify-center font-bold">
<div className="flex items-center gap-2 text-xs text-gray-600 truncate">
<div className="w-4 h-4 rounded-none bg-blue-600 text-white text-[10px] flex items-center justify-center font-bold">
G
</div>
<span className="text-gray-900 dark:text-gray-200 font-medium">
<span className="text-gray-200 font-medium">
{state.ogSiteName || "Website"}
</span>
<span className="text-gray-400">•</span>
@ -853,17 +853,17 @@ export default function MetaTagGeneratorPage() {
</div>
{/* Google Blue Link Title */}
<h3 className="text-base sm:text-lg font-medium text-blue-700 hover:underline dark:text-blue-400 cursor-pointer line-clamp-1 leading-snug">
<h3 className="text-base sm:text-lg font-medium text-blue-400 hover:underline cursor-pointer line-clamp-1 leading-snug">
{state.title || "Untitled Page — Please enter a title"}
</h3>
{/* Meta Description snippet */}
<p className="text-xs sm:text-sm text-gray-600 dark:text-gray-300 line-clamp-2 leading-relaxed">
<p className="text-xs sm:text-sm text-gray-600 line-clamp-2 leading-relaxed">
{state.description || "No description provided yet. Enter a meta description to see how your site will appear in Google search results."}
</p>
</div>
<div className="text-[11px] text-gray-500 dark:text-gray-400 space-y-1 pt-2">
<div className="text-[11px] text-gray-500 space-y-1 pt-2">
<div>
<strong>Google Title:</strong> {titleLen}/60 characters ({titleStatus.label})
</div>
@ -876,17 +876,17 @@ export default function MetaTagGeneratorPage() {
{/* Tab 3: Facebook / Open Graph Card Preview */}
{previewTab === "facebook" && (
<div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-5 shadow-xs space-y-3">
<div className="flex items-center justify-between border-b border-gray-100 dark:border-gray-800 pb-2">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-5 shadow-xs space-y-3">
<div className="flex items-center justify-between border-b border-gray-100 pb-2">
<span className="text-xs font-bold uppercase tracking-wider text-gray-500">
Facebook / LinkedIn / Discord Card
</span>
<span className="text-[11px] text-gray-400 font-mono">1200 × 630 px</span>
</div>
<div className="rounded-xl border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-850 overflow-hidden shadow-sm">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] overflow-hidden ">
{/* Card Image Area */}
<div className="aspect-[1.91/1] w-full bg-gray-200 dark:bg-gray-800 relative flex items-center justify-center overflow-hidden border-b border-gray-200 dark:border-gray-700">
<div className="aspect-[1.91/1] w-full bg-[#1a1a1a] relative flex items-center justify-center overflow-hidden border-b border-[#1a1a1a]">
{state.ogImage ? (
// eslint-disable-next-line @next/next/no-img-element
<img
@ -908,14 +908,14 @@ export default function MetaTagGeneratorPage() {
</div>
{/* Card Meta Content */}
<div className="p-3.5 space-y-1 bg-gray-50 dark:bg-gray-800/80">
<span className="text-[11px] uppercase tracking-wider text-gray-500 dark:text-gray-400 font-semibold block truncate">
<div className="p-3.5 space-y-1 bg-black">
<span className="text-[11px] uppercase tracking-wider text-gray-500 font-semibold block truncate">
{state.ogUrl ? new URL(state.ogUrl, "https://example.com").hostname : "EXAMPLE.COM"}
</span>
<h4 className="text-sm font-bold text-gray-900 dark:text-gray-100 line-clamp-1 leading-snug">
<h4 className="text-sm font-bold text-gray-200 line-clamp-1 leading-snug">
{state.ogTitle || state.title || "Your Open Graph Title"}
</h4>
<p className="text-xs text-gray-600 dark:text-gray-300 line-clamp-2 leading-relaxed">
<p className="text-xs text-gray-600 line-clamp-2 leading-relaxed">
{state.ogDescription || state.description || "Your Open Graph description will appear here on social networks."}
</p>
</div>
@ -925,8 +925,8 @@ export default function MetaTagGeneratorPage() {
{/* Tab 4: Twitter / X Card Preview */}
{previewTab === "twitter" && (
<div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-5 shadow-xs space-y-3">
<div className="flex items-center justify-between border-b border-gray-100 dark:border-gray-800 pb-2">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-5 shadow-xs space-y-3">
<div className="flex items-center justify-between border-b border-gray-100 pb-2">
<span className="text-xs font-bold uppercase tracking-wider text-gray-500">
Twitter / X Card Preview
</span>
@ -935,8 +935,8 @@ export default function MetaTagGeneratorPage() {
{state.twitterCard === "summary_large_image" ? (
/* Large Image Card */
<div className="rounded-2xl border border-gray-300 dark:border-gray-700 bg-white dark:bg-black overflow-hidden shadow-sm">
<div className="aspect-[2/1] w-full bg-gray-100 dark:bg-gray-900 relative flex items-center justify-center overflow-hidden border-b border-gray-200 dark:border-gray-800">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] overflow-hidden ">
<div className="aspect-[2/1] w-full bg-[#111] relative flex items-center justify-center overflow-hidden border-b border-[#1a1a1a]">
{state.twitterImage || state.ogImage ? (
// eslint-disable-next-line @next/next/no-img-element
<img
@ -953,22 +953,22 @@ export default function MetaTagGeneratorPage() {
</div>
)}
</div>
<div className="p-3 space-y-0.5 bg-white dark:bg-black">
<span className="text-[11px] text-gray-500 dark:text-gray-400 block truncate">
<div className="p-3 space-y-0.5 bg-[#0a0a0a]">
<span className="text-[11px] text-gray-500 block truncate">
{state.canonicalUrl ? new URL(state.canonicalUrl, "https://example.com").hostname : "example.com"}
</span>
<h4 className="text-sm font-semibold text-gray-900 dark:text-gray-100 line-clamp-1">
<h4 className="text-sm font-semibold text-gray-200 line-clamp-1">
{state.twitterTitle || state.title || "Twitter Card Title"}
</h4>
<p className="text-xs text-gray-600 dark:text-gray-400 line-clamp-2">
<p className="text-xs text-gray-600 line-clamp-2">
{state.twitterDescription || state.description || "Twitter card description summary..."}
</p>
</div>
</div>
) : (
/* Summary Small Thumbnail Card */
<div className="flex rounded-2xl border border-gray-300 dark:border-gray-700 bg-white dark:bg-black overflow-hidden shadow-sm">
<div className="w-28 h-28 bg-gray-100 dark:bg-gray-900 shrink-0 flex items-center justify-center border-r border-gray-200 dark:border-gray-800 overflow-hidden">
<div className="flex border border-[#1a1a1a] bg-[#0a0a0a] overflow-hidden ">
<div className="w-28 h-28 bg-[#111] shrink-0 flex items-center justify-center border-r border-[#1a1a1a] overflow-hidden">
{state.twitterImage || state.ogImage ? (
// eslint-disable-next-line @next/next/no-img-element
<img
@ -984,13 +984,13 @@ export default function MetaTagGeneratorPage() {
)}
</div>
<div className="p-2.5 flex-1 flex flex-col justify-center space-y-0.5 overflow-hidden">
<span className="text-[10px] text-gray-500 dark:text-gray-400 truncate">
<span className="text-[10px] text-gray-500 truncate">
{state.canonicalUrl ? new URL(state.canonicalUrl, "https://example.com").hostname : "example.com"}
</span>
<h4 className="text-xs font-semibold text-gray-900 dark:text-gray-100 line-clamp-1">
<h4 className="text-xs font-semibold text-gray-200 line-clamp-1">
{state.twitterTitle || state.title || "Twitter Card Title"}
</h4>
<p className="text-[11px] text-gray-600 dark:text-gray-400 line-clamp-2">
<p className="text-[11px] text-gray-600 line-clamp-2">
{state.twitterDescription || state.description || "Twitter card description..."}
</p>
</div>

View file

@ -252,10 +252,10 @@ export default function NumberBaseConverterPage() {
>
<div className="space-y-8">
{/* Input Configuration Box */}
<div className="rounded-xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-800 dark:bg-gray-900/50 sm:p-6 space-y-4">
<div className=" border border-[#1a1a1a] bg-black p-4 sm:p-6 space-y-4">
{/* Base Selector */}
<div>
<label className="text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300 block mb-2">
<label className="text-xs font-bold uppercase tracking-wider text-gray-400 block mb-2">
Input Base:
</label>
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
@ -264,10 +264,10 @@ export default function NumberBaseConverterPage() {
key={baseOpt.value}
type="button"
onClick={() => setInputBase(baseOpt.value)}
className={`flex items-center justify-center gap-2 rounded-lg py-2.5 px-3 text-sm font-semibold border transition-all ${
className={`flex items-center justify-center gap-2 py-2.5 px-3 text-sm font-semibold border transition-all ${
inputBase === baseOpt.value
? "border-blue-600 bg-blue-600 text-white shadow-sm"
: "border-gray-300 bg-white text-gray-700 hover:bg-gray-100 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
? "border-blue-600 bg-blue-600 text-white "
: "border-[#1a1a1a] bg-[#0a0a0a] text-gray-400 hover:bg-[#111]"
}`}
>
<span>{baseOpt.label}</span>
@ -279,14 +279,14 @@ export default function NumberBaseConverterPage() {
{/* Number Input Field */}
<div>
<div className="flex items-center justify-between mb-1.5">
<label htmlFor="num-input" className="text-xs font-semibold text-gray-600 dark:text-gray-400">
<label htmlFor="num-input" className="text-xs font-semibold text-gray-600">
Number value (supports BigInt & arbitrary precision):
</label>
{inputVal && (
<button
type="button"
onClick={() => setInputVal("")}
className="text-xs text-gray-500 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-400"
className="text-xs text-gray-500 hover:text-red-400"
>
Clear input
</button>
@ -300,17 +300,17 @@ export default function NumberBaseConverterPage() {
value={inputVal}
onChange={(e) => setInputVal(e.target.value)}
placeholder={`Enter a ${BASE_OPTIONS.find((b) => b.value === inputBase)?.radixName} number...`}
className={`w-full p-3.5 font-mono text-base sm:text-lg rounded-lg border bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:outline-none ${
className={`w-full p-3.5 font-mono text-base sm:text-lg border bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:outline-none ${
parseResult.error
? "border-red-400 focus:ring-red-400 dark:border-red-600"
: "border-gray-300 dark:border-gray-600 focus:ring-blue-500"
? "border-red-400 focus:ring-red-400"
: "border-[#1a1a1a] focus:ring-blue-900"
}`}
/>
</div>
{/* Error Message banner */}
{parseResult.error && (
<p className="mt-2 text-xs font-medium text-red-600 dark:text-red-400 flex items-center gap-1.5">
<p className="mt-2 text-xs font-medium text-red-400 flex items-center gap-1.5">
<svg className="h-4 w-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
@ -321,7 +321,7 @@ export default function NumberBaseConverterPage() {
{/* Preset Buttons */}
<div className="flex flex-wrap items-center gap-1.5 pt-1">
<span className="text-xs font-medium text-gray-500 dark:text-gray-400 mr-1">Presets:</span>
<span className="text-xs font-medium text-gray-500 mr-1">Presets:</span>
{PRESETS.map((p) => (
<button
key={p.label}
@ -330,7 +330,7 @@ export default function NumberBaseConverterPage() {
setInputBase(p.base as BaseType);
setInputVal(p.value);
}}
className="rounded bg-white px-2.5 py-1 text-xs font-medium text-gray-700 border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700 transition-colors"
className="rounded bg-[#0a0a0a] px-2.5 py-1 text-xs font-medium text-gray-400 border border-[#1a1a1a] hover:bg-[#111] transition-colors"
>
{p.label}
</button>
@ -338,13 +338,13 @@ export default function NumberBaseConverterPage() {
</div>
{/* Display Options / Toggles */}
<div className="flex flex-wrap items-center gap-4 pt-3 border-t border-gray-200 dark:border-gray-800 text-xs text-gray-700 dark:text-gray-300">
<div className="flex flex-wrap items-center gap-4 pt-3 border-t border-[#1a1a1a] text-xs text-gray-400">
<label className="flex items-center gap-2 cursor-pointer select-none">
<input
type="checkbox"
checked={includePrefixes}
onChange={(e) => setIncludePrefixes(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>Include prefixes (0b, 0o, 0x)</span>
</label>
@ -354,7 +354,7 @@ export default function NumberBaseConverterPage() {
type="checkbox"
checked={hexUppercase}
onChange={(e) => setHexUppercase(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>Uppercase HEX</span>
</label>
@ -364,7 +364,7 @@ export default function NumberBaseConverterPage() {
type="checkbox"
checked={enableGrouping}
onChange={(e) => setEnableGrouping(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>Format with digit separators</span>
</label>
@ -376,111 +376,111 @@ export default function NumberBaseConverterPage() {
<div className="space-y-6">
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
{/* Decimal Card */}
<div className="flex flex-col justify-between rounded-xl border border-blue-200 bg-blue-50/40 p-4 dark:border-blue-900/40 dark:bg-blue-950/20">
<div className="flex flex-col justify-between border border-blue-900 bg-[#0a0a1a]/40 p-4">
<div>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<span className="inline-flex h-2 w-2 rounded-full bg-blue-500" />
<span className="text-xs font-bold uppercase tracking-wider text-blue-900 dark:text-blue-300">
<span className="inline-flex h-2 w-2 rounded-none bg-[#0a0a1a]0" />
<span className="text-xs font-bold uppercase tracking-wider text-blue-900">
Decimal (Base 10)
</span>
</div>
<button
type="button"
onClick={() => copyToClipboard(conversions.dec.raw, "copy-dec")}
className="flex items-center gap-1 text-xs font-medium px-2 py-1 rounded bg-white dark:bg-gray-800 border border-blue-200 dark:border-blue-900 text-blue-700 dark:text-blue-300 hover:bg-blue-100 dark:hover:bg-blue-900/50 transition-colors"
className="flex items-center gap-1 text-xs font-medium px-2 py-1 bg-[#0a0a0a] border border-blue-900 text-blue-400 hover:bg-[#0a0a1a] transition-colors"
>
{copiedKey === "copy-dec" ? "Copied!" : "Copy"}
</button>
</div>
<div className="font-mono text-lg font-bold text-gray-900 dark:text-white break-all">
<div className="font-mono text-lg font-bold text-gray-200 break-all">
{conversions.dec.formatted}
</div>
</div>
<div className="mt-3 pt-2 border-t border-blue-100 dark:border-blue-900/40 text-xs text-gray-500 dark:text-gray-400">
<div className="mt-3 pt-2 border-t border-blue-100 text-xs text-gray-500">
Standard base-10 numerical system
</div>
</div>
{/* Hexadecimal Card */}
<div className="flex flex-col justify-between rounded-xl border border-purple-200 bg-purple-50/40 p-4 dark:border-purple-900/40 dark:bg-purple-950/20">
<div className="flex flex-col justify-between border border-purple-200 bg-purple-50/40 p-4">
<div>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<span className="inline-flex h-2 w-2 rounded-full bg-purple-500" />
<span className="text-xs font-bold uppercase tracking-wider text-purple-900 dark:text-purple-300">
<span className="inline-flex h-2 w-2 rounded-none bg-purple-500" />
<span className="text-xs font-bold uppercase tracking-wider text-purple-900">
Hexadecimal (Base 16)
</span>
</div>
<button
type="button"
onClick={() => copyToClipboard(conversions.hex.withPrefix, "copy-hex")}
className="flex items-center gap-1 text-xs font-medium px-2 py-1 rounded bg-white dark:bg-gray-800 border border-purple-200 dark:border-purple-900 text-purple-700 dark:text-purple-300 hover:bg-purple-100 dark:hover:bg-purple-900/50 transition-colors"
className="flex items-center gap-1 text-xs font-medium px-2 py-1 bg-[#0a0a0a] border border-purple-200 text-purple-700 hover:bg-purple-100 transition-colors"
>
{copiedKey === "copy-hex" ? "Copied!" : "Copy"}
</button>
</div>
<div className="font-mono text-lg font-bold text-gray-900 dark:text-white break-all">
<div className="font-mono text-lg font-bold text-gray-200 break-all">
{conversions.hex.formatted}
</div>
</div>
<div className="mt-3 pt-2 border-t border-purple-100 dark:border-purple-900/40 text-xs text-gray-500 dark:text-gray-400 flex items-center justify-between">
<div className="mt-3 pt-2 border-t border-purple-100 text-xs text-gray-500 flex items-center justify-between">
<span>Prefix: 0x</span>
<span>{conversions.byteCount} Byte{conversions.byteCount > 1 ? "s" : ""}</span>
</div>
</div>
{/* Binary Card */}
<div className="flex flex-col justify-between rounded-xl border border-emerald-200 bg-emerald-50/40 p-4 dark:border-emerald-900/40 dark:bg-emerald-950/20">
<div className="flex flex-col justify-between border border-emerald-200 bg-emerald-50/40 p-4">
<div>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<span className="inline-flex h-2 w-2 rounded-full bg-emerald-500" />
<span className="text-xs font-bold uppercase tracking-wider text-emerald-900 dark:text-emerald-300">
<span className="inline-flex h-2 w-2 rounded-none bg-emerald-500" />
<span className="text-xs font-bold uppercase tracking-wider text-emerald-900">
Binary (Base 2)
</span>
</div>
<button
type="button"
onClick={() => copyToClipboard(conversions.bin.withPrefix, "copy-bin")}
className="flex items-center gap-1 text-xs font-medium px-2 py-1 rounded bg-white dark:bg-gray-800 border border-emerald-200 dark:border-emerald-900 text-emerald-700 dark:text-emerald-300 hover:bg-emerald-100 dark:hover:bg-emerald-900/50 transition-colors"
className="flex items-center gap-1 text-xs font-medium px-2 py-1 bg-[#0a0a0a] border border-emerald-200 text-emerald-700 hover:bg-emerald-100 transition-colors"
>
{copiedKey === "copy-bin" ? "Copied!" : "Copy"}
</button>
</div>
<div className="font-mono text-base sm:text-lg font-bold text-gray-900 dark:text-white break-all leading-relaxed">
<div className="font-mono text-base sm:text-lg font-bold text-gray-200 break-all leading-relaxed">
{conversions.bin.formatted}
</div>
</div>
<div className="mt-3 pt-2 border-t border-emerald-100 dark:border-emerald-900/40 text-xs text-gray-500 dark:text-gray-400 flex items-center justify-between">
<div className="mt-3 pt-2 border-t border-emerald-100 text-xs text-gray-500 flex items-center justify-between">
<span>Prefix: 0b</span>
<span>{conversions.bitLength} Bit{conversions.bitLength > 1 ? "s" : ""}</span>
</div>
</div>
{/* Octal Card */}
<div className="flex flex-col justify-between rounded-xl border border-amber-200 bg-amber-50/40 p-4 dark:border-amber-900/40 dark:bg-amber-950/20">
<div className="flex flex-col justify-between border border-amber-200 bg-amber-50/40 p-4">
<div>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<span className="inline-flex h-2 w-2 rounded-full bg-amber-500" />
<span className="text-xs font-bold uppercase tracking-wider text-amber-900 dark:text-amber-300">
<span className="inline-flex h-2 w-2 rounded-none bg-amber-500" />
<span className="text-xs font-bold uppercase tracking-wider text-amber-900">
Octal (Base 8)
</span>
</div>
<button
type="button"
onClick={() => copyToClipboard(conversions.oct.withPrefix, "copy-oct")}
className="flex items-center gap-1 text-xs font-medium px-2 py-1 rounded bg-white dark:bg-gray-800 border border-amber-200 dark:border-amber-900 text-amber-700 dark:text-amber-300 hover:bg-amber-100 dark:hover:bg-amber-900/50 transition-colors"
className="flex items-center gap-1 text-xs font-medium px-2 py-1 bg-[#0a0a0a] border border-amber-200 text-amber-700 hover:bg-amber-100 transition-colors"
>
{copiedKey === "copy-oct" ? "Copied!" : "Copy"}
</button>
</div>
<div className="font-mono text-lg font-bold text-gray-900 dark:text-white break-all">
<div className="font-mono text-lg font-bold text-gray-200 break-all">
{conversions.oct.formatted}
</div>
</div>
<div className="mt-3 pt-2 border-t border-amber-100 dark:border-amber-900/40 text-xs text-gray-500 dark:text-gray-400 flex items-center justify-between">
<div className="mt-3 pt-2 border-t border-amber-100 text-xs text-gray-500 flex items-center justify-between">
<span>Prefix: 0o</span>
<span>Digits: 0-7</span>
</div>
@ -490,29 +490,29 @@ export default function NumberBaseConverterPage() {
{/* Custom Base Converter & Bit Level Details */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-12">
{/* Custom Base Section */}
<div className="lg:col-span-6 rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-gray-900 space-y-4">
<div className="lg:col-span-6 border border-[#1a1a1a] bg-[#0a0a0a] p-5 space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-sm font-bold uppercase tracking-wider text-gray-800 dark:text-gray-200">
<h3 className="text-sm font-bold uppercase tracking-wider text-gray-300">
Custom Base (Base {customBase})
</h3>
<button
type="button"
onClick={() => copyToClipboard(conversions.custom, "copy-custom")}
className="text-xs text-blue-600 hover:text-blue-700 dark:text-blue-400 font-medium"
className="text-xs text-blue-600 hover:text-blue-400 font-medium"
>
{copiedKey === "copy-custom" ? "Copied!" : "Copy"}
</button>
</div>
<div className="flex items-center gap-3">
<span className="text-xs text-gray-500 dark:text-gray-400">Radix:</span>
<span className="text-xs text-gray-500">Radix:</span>
<input
type="range"
min="2"
max="36"
value={customBase}
onChange={(e) => setCustomBase(Number(e.target.value))}
className="flex-1 h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-600"
className="flex-1 h-2 bg-[#1a1a1a] appearance-none cursor-pointer accent-blue-600"
/>
<input
type="number"
@ -520,33 +520,33 @@ export default function NumberBaseConverterPage() {
max="36"
value={customBase}
onChange={(e) => setCustomBase(Math.min(Math.max(Number(e.target.value), 2), 36))}
className="w-14 p-1.5 text-center font-mono text-xs rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800"
className="w-14 p-1.5 text-center font-mono text-xs border border-[#1a1a1a] bg-[#0a0a0a]"
/>
</div>
<div className="font-mono text-base font-bold text-gray-900 dark:text-white break-all p-3 rounded-lg bg-gray-50 dark:bg-gray-800/60 border border-gray-200 dark:border-gray-700">
<div className="font-mono text-base font-bold text-gray-200 break-all p-3 bg-black border border-[#1a1a1a]">
{conversions.custom || "N/A"}
</div>
</div>
{/* Bit Information & ASCII */}
<div className="lg:col-span-6 rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-gray-900 space-y-3">
<h3 className="text-sm font-bold uppercase tracking-wider text-gray-800 dark:text-gray-200">
<div className="lg:col-span-6 border border-[#1a1a1a] bg-[#0a0a0a] p-5 space-y-3">
<h3 className="text-sm font-bold uppercase tracking-wider text-gray-300">
Bit-Level Representation
</h3>
<div className="space-y-2 text-xs font-mono">
<div className="flex items-center justify-between p-2 rounded bg-gray-50 dark:bg-gray-800/60 border border-gray-200 dark:border-gray-700">
<span className="text-gray-500 dark:text-gray-400">8-bit (Byte):</span>
<span className="text-gray-900 dark:text-white font-bold">{conversions.pad8.slice(-8)}</span>
<div className="flex items-center justify-between p-2 bg-black border border-[#1a1a1a]">
<span className="text-gray-500">8-bit (Byte):</span>
<span className="text-gray-200 font-bold">{conversions.pad8.slice(-8)}</span>
</div>
<div className="flex items-center justify-between p-2 rounded bg-gray-50 dark:bg-gray-800/60 border border-gray-200 dark:border-gray-700">
<span className="text-gray-500 dark:text-gray-400">16-bit (Word):</span>
<span className="text-gray-900 dark:text-white font-bold">{formatBinary(conversions.pad16.slice(-16), 4)}</span>
<div className="flex items-center justify-between p-2 bg-black border border-[#1a1a1a]">
<span className="text-gray-500">16-bit (Word):</span>
<span className="text-gray-200 font-bold">{formatBinary(conversions.pad16.slice(-16), 4)}</span>
</div>
<div className="flex items-center justify-between p-2 rounded bg-gray-50 dark:bg-gray-800/60 border border-gray-200 dark:border-gray-700">
<span className="text-gray-500 dark:text-gray-400">ASCII / Character:</span>
<span className="text-blue-600 dark:text-blue-400 font-bold">
<div className="flex items-center justify-between p-2 bg-black border border-[#1a1a1a]">
<span className="text-gray-500">ASCII / Character:</span>
<span className="text-blue-600 font-bold">
{conversions.asciiChar !== null ? `'${conversions.asciiChar}'` : "Non-printable / Out of ASCII range"}
</span>
</div>
@ -557,27 +557,27 @@ export default function NumberBaseConverterPage() {
) : null}
{/* Quick Reference Table (0 to 15) */}
<div className="rounded-xl border border-gray-200 bg-gray-50 p-5 dark:border-gray-800 dark:bg-gray-900/50">
<h3 className="text-sm font-bold text-gray-900 dark:text-white mb-3">
<div className=" border border-[#1a1a1a] bg-black p-5">
<h3 className="text-sm font-bold text-gray-200 mb-3">
Quick Reference Table (Values 0 - 15)
</h3>
<div className="overflow-x-auto">
<table className="w-full text-left text-xs font-mono">
<thead>
<tr className="border-b border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 uppercase">
<tr className="border-b border-[#1a1a1a] text-gray-500 uppercase">
<th className="pb-2 pr-4">Decimal (10)</th>
<th className="pb-2 pr-4">Binary (2)</th>
<th className="pb-2 pr-4">Octal (8)</th>
<th className="pb-2">Hexadecimal (16)</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100 dark:divide-gray-800 text-gray-800 dark:text-gray-200">
<tbody className="divide-y divide-gray-100 text-gray-300">
{[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].map((val) => (
<tr key={val} className="hover:bg-white dark:hover:bg-gray-800/50">
<tr key={val} className="hover:bg-[#0a0a0a]">
<td className="py-1.5 pr-4 font-semibold">{val}</td>
<td className="py-1.5 pr-4">{val.toString(2).padStart(4, "0")}</td>
<td className="py-1.5 pr-4">{val.toString(8)}</td>
<td className="py-1.5 font-semibold text-purple-600 dark:text-purple-400">
<td className="py-1.5 font-semibold text-purple-600">
0x{val.toString(16).toUpperCase()}
</td>
</tr>

View file

@ -41,7 +41,7 @@ function calculateStrength(password: string, poolSize: number): StrengthInfo {
return {
score: 0,
label: "Very Weak",
color: "bg-red-500",
color: "bg-[#1a0a0a]0",
barWidth: "10%",
entropy: 0,
crackTime: "Instant",
@ -55,7 +55,7 @@ function calculateStrength(password: string, poolSize: number): StrengthInfo {
return {
score: 1,
label: "Weak",
color: "bg-red-500",
color: "bg-[#1a0a0a]0",
barWidth: "25%",
entropy,
crackTime: "< 1 second",
@ -64,7 +64,7 @@ function calculateStrength(password: string, poolSize: number): StrengthInfo {
return {
score: 2,
label: "Fair",
color: "bg-yellow-500",
color: "bg-[#1a1a0a]0",
barWidth: "50%",
entropy,
crackTime: "Several hours to days",
@ -73,7 +73,7 @@ function calculateStrength(password: string, poolSize: number): StrengthInfo {
return {
score: 3,
label: "Strong",
color: "bg-green-500",
color: "bg-[#0a1a0a]0",
barWidth: "75%",
entropy,
crackTime: "Thousands of years",
@ -215,13 +215,13 @@ export default function PasswordGeneratorPage() {
>
<div className="space-y-6">
{/* Generated Password Display Card */}
<div className="relative rounded-xl border border-gray-200 bg-gray-50/70 p-5 dark:border-gray-700/80 dark:bg-gray-800/50">
<div className="relative border border-[#1a1a1a] bg-black/70 p-5">
<div className="flex flex-col items-center justify-between gap-4 sm:flex-row">
<div className="w-full min-w-0 flex-1 overflow-x-auto py-1">
<span className="mb-1 block text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
<span className="mb-1 block text-xs font-semibold uppercase tracking-wider text-gray-500">
Generated Password
</span>
<p className="select-all break-all font-mono text-xl font-bold tracking-wider text-gray-900 dark:text-white sm:text-2xl">
<p className="select-all break-all font-mono text-xl font-bold tracking-wider text-gray-200 sm:text-2xl">
{password || (
<span className="text-sm font-normal text-red-500">
Select at least one character set below
@ -236,7 +236,7 @@ export default function PasswordGeneratorPage() {
onClick={generatePassword}
disabled={!atLeastOneSelected}
title="Regenerate password"
className="flex items-center gap-1.5 rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-sm font-medium text-gray-700 shadow-sm transition-colors hover:bg-gray-50 disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600"
className="flex items-center gap-1.5 border border-[#1a1a1a] bg-[#0a0a0a] px-3 py-2.5 text-sm font-medium text-gray-400 transition-colors hover:bg-black disabled:opacity-50"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
@ -253,7 +253,7 @@ export default function PasswordGeneratorPage() {
type="button"
onClick={handleCopy}
disabled={!password}
className={`flex items-center gap-2 rounded-lg px-5 py-2.5 text-sm font-medium shadow-sm transition-all disabled:opacity-50 ${
className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium transition-all disabled:opacity-50 ${
copied
? "bg-green-600 text-white"
: "bg-blue-600 text-white hover:bg-blue-700 active:scale-95"
@ -284,19 +284,19 @@ export default function PasswordGeneratorPage() {
</div>
{/* Strength Bar */}
<div className="mt-4 pt-3 border-t border-gray-200 dark:border-gray-700">
<div className="mt-4 pt-3 border-t border-[#1a1a1a]">
<div className="mb-1.5 flex items-center justify-between text-xs">
<span className="font-medium text-gray-600 dark:text-gray-400">
<span className="font-medium text-gray-600">
Security Strength:{" "}
<strong className="text-gray-900 dark:text-white font-semibold">
<strong className="text-gray-200 font-semibold">
{strength.label}
</strong>
</span>
<span className="text-gray-500 dark:text-gray-400">
<span className="text-gray-500">
~{strength.entropy} bits of entropy ({strength.crackTime})
</span>
</div>
<div className="h-2 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
<div className="h-2 w-full overflow-hidden rounded-none bg-[#1a1a1a]">
<div
className={`h-full transition-all duration-300 ${strength.color}`}
style={{ width: strength.barWidth }}
@ -306,8 +306,8 @@ export default function PasswordGeneratorPage() {
</div>
{/* Configuration Controls */}
<div className="rounded-lg border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800/40">
<h3 className="mb-4 text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-5">
<h3 className="mb-4 text-xs font-semibold uppercase tracking-wider text-gray-400">
Password Settings
</h3>
@ -315,8 +315,8 @@ export default function PasswordGeneratorPage() {
{/* Length Slider & Presets */}
<div>
<div className="mb-2 flex items-center justify-between">
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">
Password Length: <span className="font-bold text-blue-600 dark:text-blue-400">{length}</span> characters
<label className="text-sm font-medium text-gray-400">
Password Length: <span className="font-bold text-blue-600">{length}</span> characters
</label>
<div className="flex gap-1">
{lengthPresets.map((preset) => (
@ -327,7 +327,7 @@ export default function PasswordGeneratorPage() {
className={`rounded px-2 py-0.5 text-xs font-medium transition-colors ${
length === preset
? "bg-blue-600 text-white"
: "bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
: "bg-[#111] text-gray-400 hover:bg-[#1a1a1a]"
}`}
>
{preset}
@ -343,7 +343,7 @@ export default function PasswordGeneratorPage() {
max="128"
value={length}
onChange={(e) => setLength(parseInt(e.target.value))}
className="h-2.5 w-full cursor-pointer appearance-none rounded-lg bg-gray-200 accent-blue-600 dark:bg-gray-700"
className="h-2.5 w-full cursor-pointer appearance-none bg-[#1a1a1a] accent-blue-600"
/>
<input
type="number"
@ -354,76 +354,76 @@ export default function PasswordGeneratorPage() {
const val = Math.min(Math.max(8, parseInt(e.target.value) || 8), 128);
setLength(val);
}}
className="w-18 rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 text-center text-sm font-medium text-gray-900 focus:border-blue-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white"
className="w-18 border border-[#1a1a1a] bg-[#0a0a0a] px-2.5 py-1.5 text-center text-sm font-medium text-gray-200 focus:border-blue-500 focus:outline-none"
/>
</div>
</div>
{/* Character Set Checkboxes */}
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4 pt-2">
<label className="flex cursor-pointer items-center gap-2.5 rounded-lg border border-gray-200 p-3 transition-colors hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800">
<label className="flex cursor-pointer items-center gap-2.5 border border-[#1a1a1a] p-3 transition-colors hover:bg-black">
<input
type="checkbox"
checked={useUppercase}
onChange={(e) => setUseUppercase(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<div>
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
<div className="text-sm font-medium text-gray-200">
Uppercase
</div>
<div className="font-mono text-xs text-gray-500 dark:text-gray-400">
<div className="font-mono text-xs text-gray-500">
A-Z
</div>
</div>
</label>
<label className="flex cursor-pointer items-center gap-2.5 rounded-lg border border-gray-200 p-3 transition-colors hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800">
<label className="flex cursor-pointer items-center gap-2.5 border border-[#1a1a1a] p-3 transition-colors hover:bg-black">
<input
type="checkbox"
checked={useLowercase}
onChange={(e) => setUseLowercase(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<div>
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
<div className="text-sm font-medium text-gray-200">
Lowercase
</div>
<div className="font-mono text-xs text-gray-500 dark:text-gray-400">
<div className="font-mono text-xs text-gray-500">
a-z
</div>
</div>
</label>
<label className="flex cursor-pointer items-center gap-2.5 rounded-lg border border-gray-200 p-3 transition-colors hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800">
<label className="flex cursor-pointer items-center gap-2.5 border border-[#1a1a1a] p-3 transition-colors hover:bg-black">
<input
type="checkbox"
checked={useDigits}
onChange={(e) => setUseDigits(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<div>
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
<div className="text-sm font-medium text-gray-200">
Digits
</div>
<div className="font-mono text-xs text-gray-500 dark:text-gray-400">
<div className="font-mono text-xs text-gray-500">
0-9
</div>
</div>
</label>
<label className="flex cursor-pointer items-center gap-2.5 rounded-lg border border-gray-200 p-3 transition-colors hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800">
<label className="flex cursor-pointer items-center gap-2.5 border border-[#1a1a1a] p-3 transition-colors hover:bg-black">
<input
type="checkbox"
checked={useSymbols}
onChange={(e) => setUseSymbols(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<div>
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
<div className="text-sm font-medium text-gray-200">
Symbols
</div>
<div className="font-mono text-xs text-gray-500 dark:text-gray-400">
<div className="font-mono text-xs text-gray-500">
!@#$%^&amp;*...
</div>
</div>
@ -432,12 +432,12 @@ export default function PasswordGeneratorPage() {
{/* Ambiguous filter */}
<div className="pt-1">
<label className="inline-flex cursor-pointer items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
<label className="inline-flex cursor-pointer items-center gap-2 text-sm text-gray-400">
<input
type="checkbox"
checked={excludeAmbiguous}
onChange={(e) => setExcludeAmbiguous(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>Exclude ambiguous characters (e.g. <code>i, l, 1, I, o, 0, O</code>)</span>
</label>
@ -447,23 +447,23 @@ export default function PasswordGeneratorPage() {
{/* History of Generated Passwords */}
{history.length > 1 && (
<div className="rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-700/80 dark:bg-gray-800/40">
<h3 className="mb-2 text-xs font-semibold uppercase tracking-wider text-gray-600 dark:text-gray-400">
<div className=" border border-[#1a1a1a] bg-black/50 p-4">
<h3 className="mb-2 text-xs font-semibold uppercase tracking-wider text-gray-600">
Recent Passwords (Session History)
</h3>
<div className="divide-y divide-gray-200 dark:divide-gray-700">
<div className="divide-y divide-[#1a1a1a]">
{history.map((pwd, idx) => (
<div
key={`${pwd}-${idx}`}
className="flex items-center justify-between py-2 text-xs"
>
<span className="truncate font-mono text-gray-800 dark:text-gray-200">
<span className="truncate font-mono text-gray-300">
{pwd}
</span>
<button
type="button"
onClick={() => handleCopyHistory(pwd, idx)}
className="ml-2 shrink-0 rounded bg-white px-2 py-1 font-medium text-gray-700 shadow-xs hover:bg-gray-100 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
className="ml-2 shrink-0 bg-[#0a0a0a] px-2 py-1 font-medium text-gray-400 shadow-xs hover:bg-[#111]"
>
{historyCopiedIndex === idx ? "Copied!" : "Copy"}
</button>
@ -474,21 +474,21 @@ export default function PasswordGeneratorPage() {
)}
{/* Information Grid */}
<div className="grid grid-cols-1 gap-4 pt-2 text-xs text-gray-500 dark:text-gray-400 sm:grid-cols-3">
<div className="rounded-lg border border-gray-100 bg-gray-50 p-3.5 dark:border-gray-800 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200">CSPRNG Powered</h4>
<div className="grid grid-cols-1 gap-4 pt-2 text-xs text-gray-500 sm:grid-cols-3">
<div className=" border border-gray-100 bg-black p-3.5">
<h4 className="font-semibold text-gray-300">CSPRNG Powered</h4>
<p className="mt-1">
Uses <code>crypto.getRandomValues()</code> for cryptographically secure pseudo-random number generation directly in browser.
</p>
</div>
<div className="rounded-lg border border-gray-100 bg-gray-50 p-3.5 dark:border-gray-800 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200">Zero Transmission</h4>
<div className=" border border-gray-100 bg-black p-3.5">
<h4 className="font-semibold text-gray-300">Zero Transmission</h4>
<p className="mt-1">
Passwords are never transmitted across a network, cached on a server, or stored persistently.
</p>
</div>
<div className="rounded-lg border border-gray-100 bg-gray-50 p-3.5 dark:border-gray-800 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200">Guaranteed Distribution</h4>
<div className=" border border-gray-100 bg-black p-3.5">
<h4 className="font-semibold text-gray-300">Guaranteed Distribution</h4>
<p className="mt-1">
Ensures every active character category is represented and evenly distributed via Fisher-Yates shuffle.
</p>

View file

@ -228,10 +228,10 @@ export default function PlaceholderImagePage() {
{/* Preset Sizes Bar */}
<div>
<div className="mb-2 flex items-center justify-between">
<span className="text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<span className="text-xs font-semibold uppercase tracking-wider text-gray-400">
Popular Presets
</span>
<span className="text-xs text-gray-500 dark:text-gray-400">Click to apply size</span>
<span className="text-xs text-gray-500">Click to apply size</span>
</div>
<div className="flex flex-wrap gap-2">
{PRESET_SIZES.map((preset) => {
@ -241,10 +241,10 @@ export default function PlaceholderImagePage() {
key={preset.name}
type="button"
onClick={() => applyPreset(preset)}
className={`cursor-pointer rounded-lg px-3 py-1.5 text-xs font-medium transition-all ${
className={`cursor-pointer px-3 py-1.5 text-xs font-medium transition-all ${
isSelected
? "bg-blue-600 text-white shadow-sm ring-2 ring-blue-500/30"
: "bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
? "bg-blue-600 text-white ring-2 ring-blue-500/30"
: "bg-[#111] text-gray-400 hover:bg-[#1a1a1a]"
}`}
>
<span className="font-semibold">{preset.name}</span>
@ -262,14 +262,14 @@ export default function PlaceholderImagePage() {
{/* Controls Column (7 Cols) */}
<div className="space-y-5 lg:col-span-6">
{/* Dimensions */}
<div className="rounded-xl border border-gray-200 bg-white p-4.5 shadow-sm dark:border-gray-700 dark:bg-gray-800 space-y-4">
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-4.5 space-y-4">
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-400">
Dimensions (Pixels)
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<label htmlFor={widthId} className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
<label htmlFor={widthId} className="mb-1 block text-xs font-medium text-gray-600">
Width (px)
</label>
<input
@ -280,12 +280,12 @@ export default function PlaceholderImagePage() {
step="1"
value={width}
onChange={(e) => setWidth(Math.max(10, Math.min(4000, parseInt(e.target.value) || 10)))}
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 font-mono text-sm text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-900/60 dark:text-gray-100"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] px-3 py-2 font-mono text-sm text-gray-200 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900/20"
/>
</div>
<div>
<label htmlFor={heightId} className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
<label htmlFor={heightId} className="mb-1 block text-xs font-medium text-gray-600">
Height (px)
</label>
<input
@ -296,22 +296,22 @@ export default function PlaceholderImagePage() {
step="1"
value={height}
onChange={(e) => setHeight(Math.max(10, Math.min(4000, parseInt(e.target.value) || 10)))}
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 font-mono text-sm text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-900/60 dark:text-gray-100"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] px-3 py-2 font-mono text-sm text-gray-200 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900/20"
/>
</div>
</div>
</div>
{/* Colors & Palette Swatches */}
<div className="rounded-xl border border-gray-200 bg-white p-4.5 shadow-sm dark:border-gray-700 dark:bg-gray-800 space-y-4">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-4.5 space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-400">
Colors &amp; Theme
</h3>
<button
type="button"
onClick={handleSwapColors}
className="flex items-center gap-1 text-xs text-blue-600 hover:underline dark:text-blue-400 font-medium cursor-pointer"
className="flex items-center gap-1 text-xs text-blue-600 hover:underline font-medium cursor-pointer"
>
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" />
@ -323,7 +323,7 @@ export default function PlaceholderImagePage() {
<div className="grid grid-cols-2 gap-4">
{/* Background Color */}
<div>
<label className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
<label className="mb-1 block text-xs font-medium text-gray-600">
Background Color
</label>
<div className="flex items-center gap-2">
@ -331,21 +331,21 @@ export default function PlaceholderImagePage() {
type="color"
value={bgColor}
onChange={(e) => setBgColor(e.target.value)}
className="h-9 w-10 cursor-pointer rounded border border-gray-300 bg-transparent p-0.5 dark:border-gray-600"
className="h-9 w-10 cursor-pointer border border-[#1a1a1a] bg-transparent p-0.5"
/>
<input
type="text"
value={bgColor}
onChange={(e) => setBgColor(e.target.value)}
maxLength={7}
className="w-full rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 font-mono text-xs text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-900/60 dark:text-gray-100 uppercase"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] px-2.5 py-1.5 font-mono text-xs text-gray-200 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900/20 uppercase"
/>
</div>
</div>
{/* Text Color */}
<div>
<label className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
<label className="mb-1 block text-xs font-medium text-gray-600">
Text Color
</label>
<div className="flex items-center gap-2">
@ -353,14 +353,14 @@ export default function PlaceholderImagePage() {
type="color"
value={textColor}
onChange={(e) => setTextColor(e.target.value)}
className="h-9 w-10 cursor-pointer rounded border border-gray-300 bg-transparent p-0.5 dark:border-gray-600"
className="h-9 w-10 cursor-pointer border border-[#1a1a1a] bg-transparent p-0.5"
/>
<input
type="text"
value={textColor}
onChange={(e) => setTextColor(e.target.value)}
maxLength={7}
className="w-full rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 font-mono text-xs text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-900/60 dark:text-gray-100 uppercase"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] px-2.5 py-1.5 font-mono text-xs text-gray-200 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900/20 uppercase"
/>
</div>
</div>
@ -368,7 +368,7 @@ export default function PlaceholderImagePage() {
{/* Palette swatches */}
<div>
<span className="mb-1.5 block text-[11px] font-medium text-gray-500 dark:text-gray-400">
<span className="mb-1.5 block text-[11px] font-medium text-gray-500">
Quick Palettes
</span>
<div className="grid grid-cols-4 gap-2">
@ -380,20 +380,20 @@ export default function PlaceholderImagePage() {
setBgColor(palette.bg);
setTextColor(palette.text);
}}
className="flex cursor-pointer items-center justify-between rounded-lg border border-gray-200 p-1.5 text-[11px] transition-all hover:border-blue-400 hover:shadow-xs dark:border-gray-700"
className="flex cursor-pointer items-center justify-between border border-[#1a1a1a] p-1.5 text-[11px] transition-all hover:border-blue-400 hover:shadow-xs"
title={palette.name}
>
<div className="flex items-center gap-1.5">
<span
className="h-4 w-4 rounded-full border border-gray-300 dark:border-gray-600"
className="h-4 w-4 rounded-none border border-[#1a1a1a]"
style={{ backgroundColor: palette.bg }}
/>
<span
className="h-3 w-3 rounded-full border border-gray-300 dark:border-gray-600 -ml-2"
className="h-3 w-3 rounded-none border border-[#1a1a1a] -ml-2"
style={{ backgroundColor: palette.text }}
/>
</div>
<span className="truncate text-[10px] text-gray-600 dark:text-gray-300">
<span className="truncate text-[10px] text-gray-600">
{palette.name.split(" ")[0]}
</span>
</button>
@ -403,13 +403,13 @@ export default function PlaceholderImagePage() {
</div>
{/* Text & Typography */}
<div className="rounded-xl border border-gray-200 bg-white p-4.5 shadow-sm dark:border-gray-700 dark:bg-gray-800 space-y-4">
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-4.5 space-y-4">
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-400">
Custom Text &amp; Typography
</h3>
<div>
<label htmlFor={textId} className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
<label htmlFor={textId} className="mb-1 block text-xs font-medium text-gray-600">
Label Text <span className="text-[11px] text-gray-400">(leave blank for W×H, use {"{w}"} and {"{h}"} for dynamic dimensions)</span>
</label>
<input
@ -418,20 +418,20 @@ export default function PlaceholderImagePage() {
value={customText}
onChange={(e) => setCustomText(e.target.value)}
placeholder={`${width} × ${height}`}
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 font-mono text-sm text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-900/60 dark:text-gray-100"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] px-3 py-2 font-mono text-sm text-gray-200 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900/20"
/>
</div>
{/* Font settings */}
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
<div>
<label className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
<label className="mb-1 block text-xs font-medium text-gray-600">
Font Family
</label>
<select
value={fontFamily}
onChange={(e) => setFontFamily(e.target.value)}
className="w-full rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 text-xs text-gray-900 focus:border-blue-500 focus:outline-none dark:border-gray-600 dark:bg-gray-900/60 dark:text-gray-100"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] px-2.5 py-1.5 text-xs text-gray-200 focus:border-blue-500 focus:outline-none"
>
<option value="sans-serif">Sans-Serif</option>
<option value="monospace">Monospace</option>
@ -440,13 +440,13 @@ export default function PlaceholderImagePage() {
</div>
<div>
<label className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
<label className="mb-1 block text-xs font-medium text-gray-600">
Font Weight
</label>
<select
value={fontWeight}
onChange={(e) => setFontWeight(e.target.value)}
className="w-full rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 text-xs text-gray-900 focus:border-blue-500 focus:outline-none dark:border-gray-600 dark:bg-gray-900/60 dark:text-gray-100"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] px-2.5 py-1.5 text-xs text-gray-200 focus:border-blue-500 focus:outline-none"
>
<option value="400">Normal (400)</option>
<option value="600">Semibold (600)</option>
@ -456,12 +456,12 @@ export default function PlaceholderImagePage() {
</div>
<div className="col-span-2 sm:col-span-1 flex items-end pb-1">
<label className="inline-flex items-center gap-2 text-xs font-medium text-gray-700 dark:text-gray-300 cursor-pointer select-none">
<label className="inline-flex items-center gap-2 text-xs font-medium text-gray-400 cursor-pointer select-none">
<input
type="checkbox"
checked={showBorder}
onChange={(e) => setShowBorder(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>Dashed Border</span>
</label>
@ -471,15 +471,15 @@ export default function PlaceholderImagePage() {
{/* Font Size Selector */}
<div>
<div className="flex items-center justify-between mb-1.5">
<label htmlFor={fontSizeId} className="text-xs font-medium text-gray-600 dark:text-gray-400">
Font Size: <span className="font-semibold text-gray-900 dark:text-gray-100">{calculatedFontSize}px</span>
<label htmlFor={fontSizeId} className="text-xs font-medium text-gray-600">
Font Size: <span className="font-semibold text-gray-200">{calculatedFontSize}px</span>
</label>
<label className="inline-flex items-center gap-1.5 text-xs text-blue-600 dark:text-blue-400 cursor-pointer select-none">
<label className="inline-flex items-center gap-1.5 text-xs text-blue-600 cursor-pointer select-none">
<input
type="checkbox"
checked={isAutoFontSize}
onChange={(e) => setIsAutoFontSize(e.target.checked)}
className="h-3.5 w-3.5 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
className="h-3.5 w-3.5 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>Auto calculate</span>
</label>
@ -494,9 +494,9 @@ export default function PlaceholderImagePage() {
max="140"
value={manualFontSize}
onChange={(e) => setManualFontSize(parseInt(e.target.value) || 10)}
className="h-2 w-full cursor-pointer appearance-none rounded-lg bg-gray-200 accent-blue-600 dark:bg-gray-700"
className="h-2 w-full cursor-pointer appearance-none bg-[#1a1a1a] accent-blue-600"
/>
<span className="w-8 text-right font-mono text-xs text-gray-700 dark:text-gray-300">
<span className="w-8 text-right font-mono text-xs text-gray-400">
{manualFontSize}px
</span>
</div>
@ -506,22 +506,22 @@ export default function PlaceholderImagePage() {
</div>
{/* Canvas Preview & Export Actions Column (6 Cols) */}
<div className="flex flex-col justify-between rounded-xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-700 dark:bg-gray-800 lg:col-span-6 space-y-5">
<div className="flex flex-col justify-between border border-[#1a1a1a] bg-[#0a0a0a] p-5 lg:col-span-6 space-y-5">
<div>
<div className="flex items-center justify-between mb-3">
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-400">
Live Canvas Preview
</h3>
<span className="font-mono text-xs text-gray-500 dark:text-gray-400">
<span className="font-mono text-xs text-gray-500">
{width} × {height} px
</span>
</div>
{/* Responsive Canvas Container with Checkerboard Background */}
<div className="relative flex min-h-[260px] max-h-[360px] w-full items-center justify-center overflow-hidden rounded-xl border border-gray-200 bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] [background-size:12px_12px] p-3 dark:border-gray-700 dark:bg-[radial-gradient(#374151_1px,transparent_1px)] shadow-inner">
<div className="relative flex min-h-[260px] max-h-[360px] w-full items-center justify-center overflow-hidden border border-[#1a1a1a] bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] [background-size:12px_12px] p-3(#374151_1px,transparent_1px)] shadow-inner">
<canvas
ref={canvasRef}
className="max-h-full max-w-full rounded-md shadow-md object-contain transition-transform"
className="max-h-full max-w-full object-contain transition-transform"
style={{
aspectRatio: `${width} / ${height}`,
}}
@ -535,7 +535,7 @@ export default function PlaceholderImagePage() {
<button
type="button"
onClick={handleDownloadPng}
className="flex cursor-pointer items-center justify-center gap-1.5 rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-medium text-white shadow-sm transition-colors hover:bg-blue-700 active:bg-blue-800"
className="flex cursor-pointer items-center justify-center gap-1.5 bg-blue-600 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-blue-700 active:bg-blue-800"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
@ -546,7 +546,7 @@ export default function PlaceholderImagePage() {
<button
type="button"
onClick={handleDownloadSvg}
className="flex cursor-pointer items-center justify-center gap-1.5 rounded-lg bg-indigo-600 px-4 py-2.5 text-sm font-medium text-white shadow-sm transition-colors hover:bg-indigo-700 active:bg-indigo-800"
className="flex cursor-pointer items-center justify-center gap-1.5 bg-indigo-600 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-indigo-700 active:bg-indigo-800"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
@ -560,7 +560,7 @@ export default function PlaceholderImagePage() {
<button
type="button"
onClick={handleCopyDataUri}
className="cursor-pointer rounded-lg bg-gray-100 px-3 py-2 text-xs font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 transition-colors flex items-center justify-center gap-1"
className="cursor-pointer bg-[#111] px-3 py-2 text-xs font-medium text-gray-400 hover:bg-[#1a1a1a] transition-colors flex items-center justify-center gap-1"
>
{copiedKey === "dataUri" ? "✓ Copied Data URI!" : "Copy PNG Data URI"}
</button>
@ -568,7 +568,7 @@ export default function PlaceholderImagePage() {
<button
type="button"
onClick={handleCopySvgDataUri}
className="cursor-pointer rounded-lg bg-gray-100 px-3 py-2 text-xs font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 transition-colors flex items-center justify-center gap-1"
className="cursor-pointer bg-[#111] px-3 py-2 text-xs font-medium text-gray-400 hover:bg-[#1a1a1a] transition-colors flex items-center justify-center gap-1"
>
{copiedKey === "svgDataUri" ? "✓ Copied SVG URI!" : "Copy SVG Data URI"}
</button>
@ -576,7 +576,7 @@ export default function PlaceholderImagePage() {
<button
type="button"
onClick={handleCopySvgCode}
className="cursor-pointer rounded-lg bg-gray-100 px-3 py-2 text-xs font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 transition-colors flex items-center justify-center gap-1"
className="cursor-pointer bg-[#111] px-3 py-2 text-xs font-medium text-gray-400 hover:bg-[#1a1a1a] transition-colors flex items-center justify-center gap-1"
>
{copiedKey === "svgCode" ? "✓ Copied SVG Code!" : "Copy SVG Markup"}
</button>
@ -584,7 +584,7 @@ export default function PlaceholderImagePage() {
<button
type="button"
onClick={handleCopyImgTag}
className="cursor-pointer rounded-lg bg-gray-100 px-3 py-2 text-xs font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 transition-colors flex items-center justify-center gap-1"
className="cursor-pointer bg-[#111] px-3 py-2 text-xs font-medium text-gray-400 hover:bg-[#1a1a1a] transition-colors flex items-center justify-center gap-1"
>
{copiedKey === "imgTag" ? "✓ Copied HTML Tag!" : "Copy <img> Tag"}
</button>
@ -594,21 +594,21 @@ export default function PlaceholderImagePage() {
</div>
{/* Informative Grid */}
<div className="mt-8 grid grid-cols-1 gap-4 border-t border-gray-200 pt-6 text-xs text-gray-600 dark:border-gray-800 dark:text-gray-400 md:grid-cols-3">
<div className="rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-700/60 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-1">Standard Aspect Ratios</h4>
<div className="mt-8 grid grid-cols-1 gap-4 border-t border-[#1a1a1a] pt-6 text-xs text-gray-600 md:grid-cols-3">
<div className=" border border-[#1a1a1a] bg-black/50 p-4">
<h4 className="font-semibold text-gray-300 mb-1">Standard Aspect Ratios</h4>
<p>
Includes pre-configured dimensions for Open Graph social cards (1200×630), leaderboard ad banners (728×90), and avatars (64×64).
</p>
</div>
<div className="rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-700/60 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-1">Crisp Vector SVG &amp; PNG</h4>
<div className=" border border-[#1a1a1a] bg-black/50 p-4">
<h4 className="font-semibold text-gray-300 mb-1">Crisp Vector SVG &amp; PNG</h4>
<p>
Export as scalable XML vector graphics for lightweight mockups or rasterized PNG files for maximum software compatibility.
</p>
</div>
<div className="rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-700/60 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-1">Client-Side Canvas Rendering</h4>
<div className=" border border-[#1a1a1a] bg-black/50 p-4">
<h4 className="font-semibold text-gray-300 mb-1">Client-Side Canvas Rendering</h4>
<p>
Zero network requests required. Placeholders render instantly inside your browser canvas with zero server overhead.
</p>

View file

@ -168,21 +168,21 @@ export default function PxToRemPage() {
<div className="space-y-6">
{/* Base Font Size Configuration Bar */}
<div className="rounded-xl border border-gray-200 bg-white p-4.5 shadow-sm dark:border-gray-700 dark:bg-gray-800">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-4.5 ">
<div className="flex flex-wrap items-center justify-between gap-4">
<div>
<div className="flex items-center gap-2">
<label
htmlFor={baseId}
className="text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300"
className="text-xs font-semibold uppercase tracking-wider text-gray-400"
>
Root Base Font Size
</label>
<span className="rounded-full bg-blue-100 px-2 py-0.5 text-[11px] font-semibold text-blue-800 dark:bg-blue-900/60 dark:text-blue-300">
<span className="rounded-none bg-[#0a0a1a] px-2 py-0.5 text-[11px] font-semibold text-blue-400">
1rem = {baseSize}px
</span>
</div>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
<p className="mt-1 text-xs text-gray-500">
Standard browser default is 16px. Change to 10px if using the 62.5% font-size technique.
</p>
</div>
@ -196,9 +196,9 @@ export default function PxToRemPage() {
max="100"
value={baseSize}
onChange={(e) => handleBaseChange(parseInt(e.target.value) || 16)}
className="w-20 rounded-lg border border-gray-300 bg-white px-3 py-1.5 font-mono text-sm font-semibold text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-900 dark:text-gray-100"
className="w-20 border border-[#1a1a1a] bg-[#0a0a0a] px-3 py-1.5 font-mono text-sm font-semibold text-gray-200 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900/20"
/>
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">px</span>
<span className="text-sm font-medium text-gray-600">px</span>
</div>
{/* Preset buttons */}
@ -208,10 +208,10 @@ export default function PxToRemPage() {
key={preset.value}
type="button"
onClick={() => handleBaseChange(preset.value)}
className={`cursor-pointer rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors ${
className={`cursor-pointer px-2.5 py-1.5 text-xs font-medium transition-colors ${
baseSize === preset.value
? "bg-blue-600 text-white shadow-xs"
: "bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
: "bg-[#111] text-gray-400 hover:bg-[#1a1a1a]"
}`}
>
{preset.value}px
@ -225,8 +225,8 @@ export default function PxToRemPage() {
{/* Two-Way Interactive Live Converter */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-12">
{/* Main Converter Box (7 Cols) */}
<div className="rounded-xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-700 dark:bg-gray-800 lg:col-span-7 space-y-5">
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-5 lg:col-span-7 space-y-5">
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-400">
Live Two-Way Converter
</h3>
@ -235,7 +235,7 @@ export default function PxToRemPage() {
{/* PX Input */}
<div className="sm:col-span-5 space-y-1.5">
<div className="flex items-center justify-between">
<label htmlFor={pxInputId} className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label htmlFor={pxInputId} className="text-xs font-semibold text-gray-400">
Pixels (PX)
</label>
<span className="text-[11px] text-gray-400">Fixed</span>
@ -248,7 +248,7 @@ export default function PxToRemPage() {
value={pxValue}
onChange={(e) => handlePxChange(e.target.value)}
placeholder="24"
className="w-full rounded-lg border border-gray-300 bg-white p-3 pr-10 font-mono text-lg font-bold text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-900/70 dark:text-gray-100 shadow-inner"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] p-3 pr-10 font-mono text-lg font-bold text-gray-200 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900/20 shadow-inner"
/>
<span className="pointer-events-none absolute right-3 top-3.5 font-mono text-sm font-semibold text-gray-400">
px
@ -258,7 +258,7 @@ export default function PxToRemPage() {
{/* Arrow Indicator */}
<div className="sm:col-span-1 flex justify-center text-gray-400 pt-1">
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300">
<div className="flex h-9 w-9 items-center justify-center rounded-none bg-[#111] text-gray-600">
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
</svg>
@ -268,10 +268,10 @@ export default function PxToRemPage() {
{/* REM Input */}
<div className="sm:col-span-5 space-y-1.5">
<div className="flex items-center justify-between">
<label htmlFor={remInputId} className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label htmlFor={remInputId} className="text-xs font-semibold text-gray-400">
Root Em (REM)
</label>
<span className="text-[11px] text-blue-600 dark:text-blue-400 font-medium">Relative</span>
<span className="text-[11px] text-blue-600 font-medium">Relative</span>
</div>
<div className="relative">
<input
@ -281,7 +281,7 @@ export default function PxToRemPage() {
value={remValue}
onChange={(e) => handleRemChange(e.target.value)}
placeholder="1.5"
className="w-full rounded-lg border border-gray-300 bg-white p-3 pr-12 font-mono text-lg font-bold text-blue-600 dark:text-blue-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-900/70 shadow-inner"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] p-3 pr-12 font-mono text-lg font-bold text-blue-600 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900/20 shadow-inner"
/>
<span className="pointer-events-none absolute right-3 top-3.5 font-mono text-sm font-semibold text-gray-400">
rem
@ -292,31 +292,31 @@ export default function PxToRemPage() {
{/* Quick Multi-Unit Equivalents Cards */}
<div>
<span className="block text-[11px] font-medium text-gray-500 dark:text-gray-400 mb-2">
<span className="block text-[11px] font-medium text-gray-500 mb-2">
Equivalent Dimensions for {pxValue || 0}px:
</span>
<div className="grid grid-cols-2 gap-2.5 sm:grid-cols-4">
<div className="rounded-lg bg-gray-50 p-2 dark:bg-gray-900/60 border border-gray-200 dark:border-gray-700/60">
<div className=" bg-black p-2 border border-[#1a1a1a]">
<span className="block text-[10px] text-gray-500 uppercase font-medium">REM</span>
<span className="font-mono text-xs font-semibold text-blue-600 dark:text-blue-400">
<span className="font-mono text-xs font-semibold text-blue-600">
{remValue || "0"}rem
</span>
</div>
<div className="rounded-lg bg-gray-50 p-2 dark:bg-gray-900/60 border border-gray-200 dark:border-gray-700/60">
<div className=" bg-black p-2 border border-[#1a1a1a]">
<span className="block text-[10px] text-gray-500 uppercase font-medium">EM</span>
<span className="font-mono text-xs font-semibold text-gray-800 dark:text-gray-200">
<span className="font-mono text-xs font-semibold text-gray-300">
{remValue || "0"}em
</span>
</div>
<div className="rounded-lg bg-gray-50 p-2 dark:bg-gray-900/60 border border-gray-200 dark:border-gray-700/60">
<div className=" bg-black p-2 border border-[#1a1a1a]">
<span className="block text-[10px] text-gray-500 uppercase font-medium">Percent (%)</span>
<span className="font-mono text-xs font-semibold text-gray-800 dark:text-gray-200">
<span className="font-mono text-xs font-semibold text-gray-300">
{percentageValue}%
</span>
</div>
<div className="rounded-lg bg-gray-50 p-2 dark:bg-gray-900/60 border border-gray-200 dark:border-gray-700/60">
<div className=" bg-black p-2 border border-[#1a1a1a]">
<span className="block text-[10px] text-gray-500 uppercase font-medium">Points (pt)</span>
<span className="font-mono text-xs font-semibold text-gray-800 dark:text-gray-200">
<span className="font-mono text-xs font-semibold text-gray-300">
{ptValue}pt
</span>
</div>
@ -324,15 +324,15 @@ export default function PxToRemPage() {
</div>
{/* CSS Declaration Generator */}
<div className="rounded-lg border border-gray-200 bg-gray-50/70 p-3.5 dark:border-gray-700/60 dark:bg-gray-900/40 space-y-2">
<div className=" border border-[#1a1a1a] bg-black/70 p-3.5 space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<span className="text-xs font-semibold text-gray-400">
CSS Declaration Snippet
</span>
<select
value={selectedProperty}
onChange={(e) => setSelectedProperty(e.target.value)}
className="rounded-md border border-gray-300 bg-white px-2 py-1 text-xs text-gray-800 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
className=" border border-[#1a1a1a] bg-[#0a0a0a] px-2 py-1 text-xs text-gray-300"
>
<option value="font-size">font-size</option>
<option value="padding">padding</option>
@ -344,12 +344,12 @@ export default function PxToRemPage() {
</select>
</div>
<div className="flex items-center justify-between gap-2 rounded-md border border-gray-300 bg-white px-3 py-2 font-mono text-xs text-gray-900 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100">
<div className="flex items-center justify-between gap-2 border border-[#1a1a1a] bg-[#0a0a0a] px-3 py-2 font-mono text-xs text-gray-200">
<span className="truncate">{cssDeclaration}</span>
<button
type="button"
onClick={() => copyToClipboard(cssDeclaration, "css-decl")}
className="cursor-pointer shrink-0 rounded bg-blue-50 px-2.5 py-1 text-xs font-semibold text-blue-600 hover:bg-blue-100 dark:bg-blue-900/40 dark:text-blue-300 dark:hover:bg-blue-900/70 transition-colors"
className="cursor-pointer shrink-0 bg-[#0a0a1a] px-2.5 py-1 text-xs font-semibold text-blue-600 hover:bg-[#0a0a1a] transition-colors"
>
{copiedKey === "css-decl" ? "✓ Copied!" : "Copy CSS"}
</button>
@ -358,39 +358,39 @@ export default function PxToRemPage() {
</div>
{/* Formula & How It Works Card (5 Cols) */}
<div className="flex flex-col justify-between rounded-xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-700 dark:bg-gray-800 lg:col-span-5 space-y-4">
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<div className="flex flex-col justify-between border border-[#1a1a1a] bg-[#0a0a0a] p-5 lg:col-span-5 space-y-4">
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-400">
Calculation Formula
</h3>
<div className="space-y-3 font-mono text-xs">
<div className="rounded-lg bg-blue-50/70 p-3 dark:bg-blue-950/40 border border-blue-200 dark:border-blue-900/50">
<span className="block text-[11px] font-sans font-semibold text-blue-800 dark:text-blue-300 mb-1">
<div className=" bg-[#0a0a1a]/70 p-3 border border-blue-900">
<span className="block text-[11px] font-sans font-semibold text-blue-400 mb-1">
Pixels to Rem:
</span>
<p className="text-gray-900 dark:text-gray-100">
<p className="text-gray-200">
rem = px ÷ baseFont ({baseSize})
</p>
<p className="mt-1 text-[11px] text-gray-500 dark:text-gray-400">
<p className="mt-1 text-[11px] text-gray-500">
Example: {pxValue || 24}px ÷ {baseSize} = {remValue || 1.5}rem
</p>
</div>
<div className="rounded-lg bg-indigo-50/70 p-3 dark:bg-indigo-950/40 border border-indigo-200 dark:border-indigo-900/50">
<span className="block text-[11px] font-sans font-semibold text-indigo-800 dark:text-indigo-300 mb-1">
<div className=" bg-indigo-50/70 p-3 border border-indigo-200">
<span className="block text-[11px] font-sans font-semibold text-indigo-800 mb-1">
Rem to Pixels:
</span>
<p className="text-gray-900 dark:text-gray-100">
<p className="text-gray-200">
px = rem × baseFont ({baseSize})
</p>
<p className="mt-1 text-[11px] text-gray-500 dark:text-gray-400">
<p className="mt-1 text-[11px] text-gray-500">
Example: {remValue || 1.5}rem × {baseSize} = {pxValue || 24}px
</p>
</div>
</div>
<div className="rounded-lg border border-gray-200 bg-gray-50 p-3 text-xs text-gray-600 dark:border-gray-700 dark:bg-gray-900/50 dark:text-gray-400">
<p className="font-semibold text-gray-800 dark:text-gray-200 mb-1">💡 Why use REM over PX?</p>
<div className=" border border-[#1a1a1a] bg-black p-3 text-xs text-gray-600">
<p className="font-semibold text-gray-300 mb-1">💡 Why use REM over PX?</p>
<p>
REM units scale proportionally when users adjust their operating system or browser font size settings, ensuring accessibility (WCAG) compliance.
</p>
@ -399,13 +399,13 @@ export default function PxToRemPage() {
</div>
{/* Quick Reference Table */}
<div className="rounded-xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-700 dark:bg-gray-800 space-y-4">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-5 space-y-4">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-400">
Quick Reference Table (Base: {baseSize}px)
</h3>
<p className="text-xs text-gray-500 dark:text-gray-400">
<p className="text-xs text-gray-500">
Click any row to load into converter or copy individual values.
</p>
</div>
@ -417,14 +417,14 @@ export default function PxToRemPage() {
placeholder="Search px or rem..."
value={tableSearch}
onChange={(e) => setTableSearch(e.target.value)}
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-xs text-gray-900 placeholder:text-gray-400 focus:border-blue-500 focus:outline-none dark:border-gray-600 dark:bg-gray-900 dark:text-gray-100"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] px-3 py-1.5 text-xs text-gray-200 placeholder:text-gray-400 focus:border-blue-500 focus:outline-none"
/>
</div>
</div>
<div className="overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700">
<table className="w-full text-left text-xs text-gray-700 dark:text-gray-300">
<thead className="bg-gray-50 text-[11px] uppercase tracking-wider text-gray-500 dark:bg-gray-900 dark:text-gray-400">
<div className="overflow-x-auto border border-[#1a1a1a]">
<table className="w-full text-left text-xs text-gray-400">
<thead className="bg-black text-[11px] uppercase tracking-wider text-gray-500">
<tr>
<th className="px-4 py-2.5 font-semibold">Pixels (px)</th>
<th className="px-4 py-2.5 font-semibold">Root Em (rem)</th>
@ -433,7 +433,7 @@ export default function PxToRemPage() {
<th className="px-4 py-2.5 text-right font-semibold">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
<tbody className="divide-y divide-[#1a1a1a]">
{filteredTableValues.map((px) => {
const rem = formatNumber(px / baseSize);
const isSelected = px.toString() === pxValue;
@ -443,22 +443,22 @@ export default function PxToRemPage() {
<tr
key={px}
onClick={() => handlePxChange(px.toString())}
className={`cursor-pointer transition-colors hover:bg-blue-50/50 dark:hover:bg-blue-950/20 ${
isSelected ? "bg-blue-50/80 dark:bg-blue-950/40 font-semibold" : ""
className={`cursor-pointer transition-colors hover:bg-[#0a0a1a]/50 ${
isSelected ? "bg-[#0a0a1a]/80 font-semibold" : ""
}`}
>
<td className="whitespace-nowrap px-4 py-2.5 font-mono">
<span className="rounded bg-gray-100 px-2 py-0.5 font-bold text-gray-900 dark:bg-gray-700 dark:text-gray-100">
<span className="rounded bg-[#111] px-2 py-0.5 font-bold text-gray-200">
{px}px
</span>
</td>
<td className="whitespace-nowrap px-4 py-2.5 font-mono text-blue-600 dark:text-blue-400 font-bold">
<td className="whitespace-nowrap px-4 py-2.5 font-mono text-blue-600 font-bold">
{rem}rem
</td>
<td className="whitespace-nowrap px-4 py-2.5 font-mono text-gray-600 dark:text-gray-400">
<td className="whitespace-nowrap px-4 py-2.5 font-mono text-gray-600">
{rem}em
</td>
<td className="whitespace-nowrap px-4 py-2.5 font-mono text-emerald-600 dark:text-emerald-400">
<td className="whitespace-nowrap px-4 py-2.5 font-mono text-emerald-600">
{tailwindClass}
</td>
<td className="whitespace-nowrap px-4 py-2.5 text-right">
@ -468,7 +468,7 @@ export default function PxToRemPage() {
e.stopPropagation();
copyToClipboard(`${rem}rem`, `row-${px}`);
}}
className="cursor-pointer rounded bg-gray-100 px-2.5 py-1 text-[11px] font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 transition-colors"
className="cursor-pointer bg-[#111] px-2.5 py-1 text-[11px] font-medium text-gray-400 hover:bg-[#1a1a1a] transition-colors"
>
{copiedKey === `row-${px}` ? "✓ Copied" : "Copy REM"}
</button>
@ -482,24 +482,24 @@ export default function PxToRemPage() {
</div>
{/* Bulk Converter Section */}
<div className="rounded-xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-700 dark:bg-gray-800 space-y-4">
<div className=" border border-[#1a1a1a] bg-[#0a0a0a] p-5 space-y-4">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-400">
Bulk Pixel Converter
</h3>
<p className="text-xs text-gray-500 dark:text-gray-400">
<p className="text-xs text-gray-500">
Paste multiple pixel values (separated by newlines or commas) to convert all at once.
</p>
</div>
{/* Bulk Format Selector */}
<div className="flex items-center gap-2">
<span className="text-xs text-gray-500 dark:text-gray-400">Format:</span>
<span className="text-xs text-gray-500">Format:</span>
<select
value={bulkFormat}
onChange={(e) => setBulkFormat(e.target.value as "rem-list" | "mapping" | "css-vars" | "tailwind")}
className="rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 text-xs text-gray-800 dark:border-gray-600 dark:bg-gray-900 dark:text-gray-200"
className=" border border-[#1a1a1a] bg-[#0a0a0a] px-2.5 py-1.5 text-xs text-gray-300"
>
<option value="rem-list">Rem Values (0.5rem)</option>
<option value="mapping">Mapping (8px = 0.5rem)</option>
@ -513,13 +513,13 @@ export default function PxToRemPage() {
{/* Input Box */}
<div>
<div className="flex items-center justify-between mb-1.5">
<label htmlFor={bulkInputId} className="text-xs font-medium text-gray-700 dark:text-gray-300">
<label htmlFor={bulkInputId} className="text-xs font-medium text-gray-400">
Input Pixel Values (one per line)
</label>
<button
type="button"
onClick={() => setBulkInput("8\n12\n16\n20\n24\n32\n40\n48\n64\n80\n96\n128")}
className="text-xs text-blue-600 hover:underline dark:text-blue-400 font-medium cursor-pointer"
className="text-xs text-blue-600 hover:underline font-medium cursor-pointer"
>
Load Sample Scale
</button>
@ -530,21 +530,21 @@ export default function PxToRemPage() {
value={bulkInput}
onChange={(e) => setBulkInput(e.target.value)}
placeholder="Paste numbers like:&#10;12px&#10;16px&#10;24px&#10;32px"
className="w-full rounded-lg border border-gray-300 bg-white p-3 font-mono text-xs text-gray-900 shadow-inner focus:border-blue-500 focus:outline-none dark:border-gray-600 dark:bg-gray-900/60 dark:text-gray-100 leading-relaxed"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] p-3 font-mono text-xs text-gray-200 shadow-inner focus:border-blue-500 focus:outline-none leading-relaxed"
/>
</div>
{/* Output Box */}
<div>
<div className="flex items-center justify-between mb-1.5">
<span className="text-xs font-medium text-gray-700 dark:text-gray-300">
<span className="text-xs font-medium text-gray-400">
Converted Output ({parsedBulkRows.length} items)
</span>
<button
type="button"
onClick={() => copyToClipboard(getBulkOutputString(), "bulk-output")}
disabled={!getBulkOutputString()}
className="cursor-pointer rounded bg-blue-600 px-3 py-1 text-xs font-medium text-white hover:bg-blue-700 disabled:opacity-40 transition-colors"
className="cursor-pointer bg-blue-600 px-3 py-1 text-xs font-medium text-white hover:bg-blue-700 disabled:opacity-40 transition-colors"
>
{copiedKey === "bulk-output" ? "✓ Copied All!" : "Copy All"}
</button>
@ -554,28 +554,28 @@ export default function PxToRemPage() {
rows={7}
value={getBulkOutputString()}
placeholder="Bulk conversion output will appear here..."
className="w-full rounded-lg border border-gray-300 bg-gray-50 p-3 font-mono text-xs text-gray-900 shadow-inner focus:outline-none dark:border-gray-700 dark:bg-gray-900/80 dark:text-gray-100 leading-relaxed select-all"
className="w-full border border-[#1a1a1a] bg-black p-3 font-mono text-xs text-gray-200 shadow-inner focus:outline-none leading-relaxed select-all"
/>
</div>
</div>
</div>
{/* Informational Guidelines */}
<div className="mt-8 grid grid-cols-1 gap-4 border-t border-gray-200 pt-6 text-xs text-gray-600 dark:border-gray-800 dark:text-gray-400 md:grid-cols-3">
<div className="rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-700/60 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-1">REM vs EM Units</h4>
<div className="mt-8 grid grid-cols-1 gap-4 border-t border-[#1a1a1a] pt-6 text-xs text-gray-600 md:grid-cols-3">
<div className=" border border-[#1a1a1a] bg-black/50 p-4">
<h4 className="font-semibold text-gray-300 mb-1">REM vs EM Units</h4>
<p>
<code>rem</code> units are calculated relative to the root <code>&lt;html&gt;</code> element font size, avoiding compound scaling issues encountered with nested <code>em</code> elements.
</p>
</div>
<div className="rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-700/60 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-1">The 62.5% Trick (10px Base)</h4>
<div className=" border border-[#1a1a1a] bg-black/50 p-4">
<h4 className="font-semibold text-gray-300 mb-1">The 62.5% Trick (10px Base)</h4>
<p>
Setting <code>html &#123; font-size: 62.5%; &#125;</code> makes 1rem = 10px (1.6rem = 16px, 2.4rem = 24px) while preserving user browser zoom scaling.
</p>
</div>
<div className="rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-700/60 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-1">Instant Client Calculation</h4>
<div className=" border border-[#1a1a1a] bg-black/50 p-4">
<h4 className="font-semibold text-gray-300 mb-1">Instant Client Calculation</h4>
<p>
Computations happen instantly in your browser on each keystroke without sending queries over the network.
</p>

View file

@ -124,7 +124,7 @@ export default function QrCodePage() {
<div className="mb-2 flex items-center justify-between">
<label
htmlFor="qr-input"
className="text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300"
className="text-xs font-semibold uppercase tracking-wider text-gray-400"
>
Content / URL
</label>
@ -132,7 +132,7 @@ export default function QrCodePage() {
<button
type="button"
onClick={() => setInputText("")}
className="text-xs text-red-600 hover:text-red-700 dark:text-red-400"
className="text-xs text-red-400 hover:text-red-400"
>
Clear
</button>
@ -145,18 +145,18 @@ export default function QrCodePage() {
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="Enter a URL, text, or select a template below..."
className="w-full rounded-lg border border-gray-300 bg-white p-3 font-mono text-sm text-gray-900 shadow-inner focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] p-3 font-mono text-sm text-gray-200 shadow-inner focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900/20"
/>
{/* Presets */}
<div className="mt-2.5 flex flex-wrap items-center gap-1.5">
<span className="text-xs text-gray-500 dark:text-gray-400">Templates:</span>
<span className="text-xs text-gray-500">Templates:</span>
{presetTemplates.map((tpl) => (
<button
key={tpl.label}
type="button"
onClick={() => setInputText(tpl.value)}
className="rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
className=" bg-[#111] px-2 py-1 text-xs font-medium text-gray-400 hover:bg-[#1a1a1a]"
>
{tpl.label}
</button>
@ -165,21 +165,21 @@ export default function QrCodePage() {
</div>
{/* Configuration Options */}
<div className="rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-700/60 dark:bg-gray-800/40">
<h3 className="mb-3 text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<div className=" border border-[#1a1a1a] bg-black/50 p-4">
<h3 className="mb-3 text-xs font-semibold uppercase tracking-wider text-gray-400">
Customization Options
</h3>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
{/* Size Selector */}
<div>
<label className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
<label className="mb-1 block text-xs font-medium text-gray-600">
Export Resolution
</label>
<select
value={size}
onChange={(e) => setSize(e.target.value as SizeOption)}
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] px-3 py-2 text-sm text-gray-200 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900/20"
>
<option value="small">{SIZE_MAP.small.label}</option>
<option value="medium">{SIZE_MAP.medium.label}</option>
@ -189,13 +189,13 @@ export default function QrCodePage() {
{/* Error Correction */}
<div>
<label className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
<label className="mb-1 block text-xs font-medium text-gray-600">
Error Correction Level
</label>
<select
value={errorCorrection}
onChange={(e) => setErrorCorrection(e.target.value as ErrorCorrectionLevel)}
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] px-3 py-2 text-sm text-gray-200 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900/20"
>
<option value="L">L - {ERROR_CORRECTION_DESCRIPTIONS.L}</option>
<option value="M">M - {ERROR_CORRECTION_DESCRIPTIONS.M}</option>
@ -206,7 +206,7 @@ export default function QrCodePage() {
{/* Margin / Quiet Zone */}
<div>
<label className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
<label className="mb-1 block text-xs font-medium text-gray-600">
Margin / Border: {margin} module{margin > 1 ? "s" : ""}
</label>
<div className="flex items-center gap-2">
@ -216,9 +216,9 @@ export default function QrCodePage() {
max="6"
value={margin}
onChange={(e) => setMargin(parseInt(e.target.value))}
className="h-2 w-full cursor-pointer appearance-none rounded-lg bg-gray-200 accent-blue-600 dark:bg-gray-700"
className="h-2 w-full cursor-pointer appearance-none bg-[#1a1a1a] accent-blue-600"
/>
<span className="w-6 text-center text-xs font-medium text-gray-700 dark:text-gray-300">
<span className="w-6 text-center text-xs font-medium text-gray-400">
{margin}
</span>
</div>
@ -226,7 +226,7 @@ export default function QrCodePage() {
{/* Colors */}
<div>
<label className="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
<label className="mb-1 block text-xs font-medium text-gray-600">
Colors (Foreground / Background)
</label>
<div className="flex items-center gap-3 pt-1">
@ -235,10 +235,10 @@ export default function QrCodePage() {
type="color"
value={fgColor}
onChange={(e) => setFgColor(e.target.value)}
className="h-8 w-8 cursor-pointer rounded border border-gray-300 bg-transparent p-0.5 dark:border-gray-600"
className="h-8 w-8 cursor-pointer border border-[#1a1a1a] bg-transparent p-0.5"
title="Foreground color"
/>
<span className="font-mono text-xs text-gray-700 dark:text-gray-300">
<span className="font-mono text-xs text-gray-400">
{fgColor}
</span>
</div>
@ -248,10 +248,10 @@ export default function QrCodePage() {
type="color"
value={bgColor}
onChange={(e) => setBgColor(e.target.value)}
className="h-8 w-8 cursor-pointer rounded border border-gray-300 bg-transparent p-0.5 dark:border-gray-600"
className="h-8 w-8 cursor-pointer border border-[#1a1a1a] bg-transparent p-0.5"
title="Background color"
/>
<span className="font-mono text-xs text-gray-700 dark:text-gray-300">
<span className="font-mono text-xs text-gray-400">
{bgColor}
</span>
</div>
@ -263,7 +263,7 @@ export default function QrCodePage() {
setFgColor("#000000");
setBgColor("#ffffff");
}}
className="text-xs text-blue-600 hover:underline dark:text-blue-400"
className="text-xs text-blue-600 hover:underline"
>
Reset
</button>
@ -275,13 +275,13 @@ export default function QrCodePage() {
</div>
{/* QR Code Preview & Download Column */}
<div className="flex flex-col items-center justify-center rounded-xl border border-gray-200 bg-gray-50/50 p-6 dark:border-gray-700/60 dark:bg-gray-800/40 lg:col-span-5">
<h3 className="mb-4 text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<div className="flex flex-col items-center justify-center border border-[#1a1a1a] bg-black/50 p-6 lg:col-span-5">
<h3 className="mb-4 text-xs font-semibold uppercase tracking-wider text-gray-400">
Live QR Preview
</h3>
{error ? (
<div className="flex h-64 w-full flex-col items-center justify-center rounded-lg border border-red-200 bg-red-50 p-4 text-center dark:border-red-900/50 dark:bg-red-950/20">
<div className="flex h-64 w-full flex-col items-center justify-center border border-red-900 bg-[#1a0a0a] p-4 text-center">
<svg className="mb-2 h-8 w-8 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
@ -290,14 +290,14 @@ export default function QrCodePage() {
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<p className="text-xs font-medium text-red-700 dark:text-red-400">{error}</p>
<p className="mt-1 text-[11px] text-red-600 dark:text-red-500">
<p className="text-xs font-medium text-red-400">{error}</p>
<p className="mt-1 text-[11px] text-red-400">
Try shortening the text or lowering the error correction level.
</p>
</div>
) : dataUrl ? (
<div className="flex flex-col items-center">
<div className="overflow-hidden rounded-xl border border-gray-200 bg-white p-3 shadow-md dark:border-gray-700 dark:bg-gray-900">
<div className="overflow-hidden border border-[#1a1a1a] bg-[#0a0a0a] p-3 ">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={dataUrl}
@ -308,7 +308,7 @@ export default function QrCodePage() {
/>
</div>
<p className="mt-2 text-center text-xs text-gray-400 dark:text-gray-500">
<p className="mt-2 text-center text-xs text-gray-400">
{SIZE_MAP[size].px} × {SIZE_MAP[size].px} px • Level {errorCorrection}
</p>
@ -317,7 +317,7 @@ export default function QrCodePage() {
<button
type="button"
onClick={handleDownloadPng}
className="flex w-full items-center justify-center gap-2 rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-medium text-white shadow-sm transition-colors hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
className="flex w-full items-center justify-center gap-2 bg-blue-600 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-blue-700 focus:outline-none focus:ring-1 focus:ring-blue-900 focus:ring-offset-2"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
@ -334,14 +334,14 @@ export default function QrCodePage() {
<button
type="button"
onClick={handleDownloadSvg}
className="flex-1 rounded-lg bg-gray-200 px-3 py-2 text-xs font-medium text-gray-800 transition-colors hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600"
className="flex-1 bg-[#1a1a1a] px-3 py-2 text-xs font-medium text-gray-300 transition-colors hover:bg-[#222]"
>
Download SVG
</button>
<button
type="button"
onClick={handleCopyDataUrl}
className="flex-1 rounded-lg bg-gray-200 px-3 py-2 text-xs font-medium text-gray-800 transition-colors hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600"
className="flex-1 bg-[#1a1a1a] px-3 py-2 text-xs font-medium text-gray-300 transition-colors hover:bg-[#222]"
>
{copiedDataUrl ? "Copied Data URI!" : "Copy Data URI"}
</button>
@ -349,9 +349,9 @@ export default function QrCodePage() {
</div>
</div>
) : (
<div className="flex h-64 w-full flex-col items-center justify-center rounded-lg border border-dashed border-gray-300 p-4 text-center dark:border-gray-700">
<div className="flex h-64 w-full flex-col items-center justify-center border border-dashed border-[#1a1a1a] p-4 text-center">
<span className="text-3xl">📱</span>
<p className="mt-2 text-xs text-gray-500 dark:text-gray-400">
<p className="mt-2 text-xs text-gray-500">
Enter text or a URL above to render a live QR Code.
</p>
</div>
@ -360,21 +360,21 @@ export default function QrCodePage() {
</div>
{/* Informational Details */}
<div className="grid grid-cols-1 gap-4 pt-4 text-xs text-gray-500 dark:text-gray-400 sm:grid-cols-3">
<div className="rounded-lg border border-gray-100 bg-gray-50 p-3 dark:border-gray-800 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200">Error Correction (Reed-Solomon)</h4>
<div className="grid grid-cols-1 gap-4 pt-4 text-xs text-gray-500 sm:grid-cols-3">
<div className=" border border-gray-100 bg-black p-3">
<h4 className="font-semibold text-gray-300">Error Correction (Reed-Solomon)</h4>
<p className="mt-1">
Higher error correction allows the QR code to be scanned even if damaged, partially obstructed, or printed on textured materials.
</p>
</div>
<div className="rounded-lg border border-gray-100 bg-gray-50 p-3 dark:border-gray-800 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200">Universal Smartphone Support</h4>
<div className=" border border-gray-100 bg-black p-3">
<h4 className="font-semibold text-gray-300">Universal Smartphone Support</h4>
<p className="mt-1">
Scannable natively with standard iOS and Android camera apps without requiring any third-party scanner software.
</p>
</div>
<div className="rounded-lg border border-gray-100 bg-gray-50 p-3 dark:border-gray-800 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200">Zero Server Transfer</h4>
<div className=" border border-gray-100 bg-black p-3">
<h4 className="font-semibold text-gray-300">Zero Server Transfer</h4>
<p className="mt-1">
Your sensitive Wi-Fi passwords, contact cards, and URLs are encoded entirely in client JavaScript.
</p>

View file

@ -173,12 +173,12 @@ export default function RegexTesterPage() {
if (item.match.length === 0) {
// Zero-length match indicator
html += `<span class="bg-amber-400 text-black font-bold px-0.5 rounded text-xs border border-amber-500" title="Zero-width match at index ${item.start}">|</span>`;
html += `<span class="bg-amber-400 text-black font-bold px-0.5 text-xs border border-amber-500" title="Zero-width match at index ${item.start}">|</span>`;
} else {
const colorClass =
i % 2 === 0
? "bg-yellow-200 dark:bg-yellow-800/70 text-yellow-950 dark:text-yellow-100 border-b-2 border-yellow-500"
: "bg-teal-200 dark:bg-teal-800/70 text-teal-950 dark:text-teal-100 border-b-2 border-teal-500";
? "bg-yellow-200 text-yellow-950 border-b-2 border-yellow-500"
: "bg-teal-200 text-teal-950 border-b-2 border-teal-500";
html += `<mark class="${colorClass} px-0.5 rounded-xs font-mono font-medium">${escapeHtml(
item.match
)}</mark>`;
@ -258,9 +258,9 @@ export default function RegexTesterPage() {
<div className="space-y-6">
{/* Preset Selector */}
<div className="flex flex-wrap items-center justify-between gap-3 p-3 bg-gray-50 dark:bg-gray-800/60 rounded-xl border border-gray-200 dark:border-gray-700/80">
<div className="flex flex-wrap items-center justify-between gap-3 p-3 bg-black border border-[#1a1a1a]">
<div className="flex items-center gap-2">
<span className="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
<span className="text-xs font-semibold uppercase tracking-wider text-gray-500">
Presets:
</span>
<div className="flex flex-wrap gap-1.5">
@ -269,7 +269,7 @@ export default function RegexTesterPage() {
key={i}
type="button"
onClick={() => loadPreset(p)}
className="px-2.5 py-1 text-xs font-medium bg-white dark:bg-gray-850 hover:bg-blue-50 dark:hover:bg-blue-950/40 text-gray-700 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400 border border-gray-200 dark:border-gray-700 rounded-md transition-colors cursor-pointer shadow-xs"
className="px-2.5 py-1 text-xs font-medium bg-[#0a0a0a] hover:bg-[#0a0a1a] text-gray-400 hover:text-blue-600 border border-[#1a1a1a] transition-colors cursor-pointer shadow-xs"
>
{p.name}
</button>
@ -283,7 +283,7 @@ export default function RegexTesterPage() {
setTestString("");
setReplacement("");
}}
className="text-xs text-rose-600 dark:text-rose-400 hover:underline cursor-pointer"
className="text-xs text-rose-600 hover:underline cursor-pointer"
>
Clear All
</button>
@ -293,17 +293,17 @@ export default function RegexTesterPage() {
<div className="space-y-3">
<label
htmlFor="regex-pattern-input"
className="text-sm font-semibold text-gray-800 dark:text-gray-200 flex items-center justify-between"
className="text-sm font-semibold text-gray-300 flex items-center justify-between"
>
<span>Regular Expression Pattern</span>
<span className="text-xs font-normal text-gray-500 dark:text-gray-400 font-mono">
<span className="text-xs font-normal text-gray-500 font-mono">
/{pattern}/{flagString}
</span>
</label>
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2">
<div className="relative flex-1 flex items-center">
<span className="absolute left-3.5 text-gray-400 dark:text-gray-500 font-mono text-base select-none pointer-events-none">
<span className="absolute left-3.5 text-gray-400 font-mono text-base select-none pointer-events-none">
/
</span>
<input
@ -312,16 +312,16 @@ export default function RegexTesterPage() {
value={pattern}
onChange={(e) => setPattern(e.target.value)}
placeholder="Enter regex pattern, e.g. [a-z0-9]+"
className="w-full pl-7 pr-7 py-2.5 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none transition-shadow"
className="w-full pl-7 pr-7 py-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-mono text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none transition-shadow"
/>
<span className="absolute right-3.5 text-gray-400 dark:text-gray-500 font-mono text-base select-none pointer-events-none">
<span className="absolute right-3.5 text-gray-400 font-mono text-base select-none pointer-events-none">
/
</span>
</div>
{/* Flags Checkboxes */}
<div className="flex items-center gap-2 bg-gray-50 dark:bg-gray-800/80 p-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-xs">
<span className="text-gray-500 dark:text-gray-400 font-semibold px-1">Flags:</span>
<div className="flex items-center gap-2 bg-black p-1.5 border border-[#1a1a1a] text-xs">
<span className="text-gray-500 font-semibold px-1">Flags:</span>
{[
{ key: "g", label: "g", title: "Global (match all)" },
{ key: "i", label: "i", title: "Case insensitive" },
@ -337,10 +337,10 @@ export default function RegexTesterPage() {
type="button"
title={title}
onClick={() => toggleFlag(k)}
className={`px-2 py-1 rounded font-mono font-semibold transition-colors cursor-pointer ${
className={`px-2 py-1 font-mono font-semibold transition-colors cursor-pointer ${
active
? "bg-blue-600 text-white shadow-xs"
: "bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600"
: "bg-[#1a1a1a] text-gray-400 hover:bg-[#222]"
}`}
>
{label}
@ -352,7 +352,7 @@ export default function RegexTesterPage() {
{/* Error Message */}
{error && (
<div className="flex items-center gap-2.5 p-3 rounded-lg bg-rose-50 dark:bg-rose-950/40 border border-rose-200 dark:border-rose-800 text-rose-800 dark:text-rose-300 text-xs font-mono">
<div className="flex items-center gap-2.5 p-3 bg-rose-50 border border-rose-200 text-rose-800 text-xs font-mono">
<svg className="w-4 h-4 shrink-0 text-rose-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
@ -366,10 +366,10 @@ export default function RegexTesterPage() {
{/* Test String Textarea */}
<div className="flex flex-col space-y-2">
<div className="flex items-center justify-between">
<label htmlFor="regex-test-string" className="text-sm font-semibold text-gray-700 dark:text-gray-300">
<label htmlFor="regex-test-string" className="text-sm font-semibold text-gray-400">
Test String
</label>
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
<span className="text-xs text-gray-500 font-mono">
{testString.length} chars
</span>
</div>
@ -379,7 +379,7 @@ export default function RegexTesterPage() {
value={testString}
onChange={(e) => setTestString(e.target.value)}
placeholder="Paste or type text to match against the pattern..."
className="w-full p-3.5 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none resize-y leading-relaxed shadow-inner"
className="w-full p-3.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-mono text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none resize-y leading-relaxed shadow-inner"
/>
</div>
@ -387,25 +387,25 @@ export default function RegexTesterPage() {
<div className="flex flex-col space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<label className="text-sm font-semibold text-gray-700 dark:text-gray-300">
<label className="text-sm font-semibold text-gray-400">
Live Match Highlight
</label>
<span
className={`px-2 py-0.5 rounded-full text-xs font-semibold ${
className={`px-2 py-0.5 rounded-none text-xs font-semibold ${
matches.length > 0
? "bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300"
: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
? "bg-emerald-100 text-emerald-800"
: "bg-[#111] text-gray-600"
}`}
>
{matches.length} {matches.length === 1 ? "match" : "matches"}
</span>
</div>
<span className="text-xs text-gray-400 dark:text-gray-500">
<span className="text-xs text-gray-400">
Alternating highlights
</span>
</div>
<div
className="w-full p-3.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-gray-50/70 dark:bg-gray-900/60 text-gray-900 dark:text-gray-100 font-mono text-sm overflow-auto min-h-[190px] max-h-[300px] whitespace-pre-wrap break-words leading-relaxed select-text"
className="w-full p-3.5 border border-[#1a1a1a] bg-black/70 text-gray-200 font-mono text-sm overflow-auto min-h-[190px] max-h-[300px] whitespace-pre-wrap break-words leading-relaxed select-text"
dangerouslySetInnerHTML={{ __html: highlightedHtml }}
/>
</div>
@ -414,7 +414,7 @@ export default function RegexTesterPage() {
{/* Detailed Matches & Capture Groups List */}
<div className="space-y-3 pt-2">
<div className="flex items-center justify-between">
<h3 className="text-sm font-bold text-gray-900 dark:text-gray-100 flex items-center gap-2">
<h3 className="text-sm font-bold text-gray-200 flex items-center gap-2">
<span>Match Details &amp; Captured Groups</span>
<span className="text-xs font-normal text-gray-500">
({matches.length} {matches.length === 1 ? "item" : "items"})
@ -423,7 +423,7 @@ export default function RegexTesterPage() {
</div>
{matches.length === 0 ? (
<div className="p-6 text-center rounded-xl border border-dashed border-gray-300 dark:border-gray-700 text-gray-500 dark:text-gray-400 text-sm">
<div className="p-6 text-center border border-dashed border-[#1a1a1a] text-gray-500 text-sm">
{pattern ? "No matches found in the test string." : "Enter a regular expression pattern above to see matches."}
</div>
) : (
@ -431,27 +431,27 @@ export default function RegexTesterPage() {
{matches.map((item) => (
<div
key={item.index}
className="p-3.5 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800/80 shadow-xs space-y-2"
className="p-3.5 border border-[#1a1a1a] bg-[#0a0a0a] shadow-xs space-y-2"
>
<div className="flex items-center justify-between border-b border-gray-100 dark:border-gray-700 pb-2">
<div className="flex items-center justify-between border-b border-gray-100 pb-2">
<div className="flex items-center gap-2">
<span className="px-2 py-0.5 rounded text-xs font-bold bg-blue-100 text-blue-800 dark:bg-blue-950 dark:text-blue-300">
<span className="px-2 py-0.5 text-xs font-bold bg-[#0a0a1a] text-blue-400">
Match #{item.index}
</span>
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
<span className="text-xs text-gray-500 font-mono">
pos: [{item.start}, {item.end}] ({item.match.length} chars)
</span>
</div>
<button
type="button"
onClick={() => handleCopyMatch(item.match, item.index)}
className="text-xs text-gray-500 hover:text-blue-600 dark:hover:text-blue-400 font-medium cursor-pointer"
className="text-xs text-gray-500 hover:text-blue-600 font-medium cursor-pointer"
>
{copiedIndex === item.index ? "Copied!" : "Copy"}
</button>
</div>
<div className="font-mono text-xs p-2 rounded bg-gray-50 dark:bg-gray-900/80 text-gray-900 dark:text-gray-100 break-all">
<div className="font-mono text-xs p-2 bg-black text-gray-200 break-all">
{item.match.length === 0 ? (
<span className="italic text-gray-400">&lt;empty match&gt;</span>
) : (
@ -461,8 +461,8 @@ export default function RegexTesterPage() {
{/* Groups */}
{item.groups.length > 0 && (
<div className="space-y-1 pt-1 border-t border-gray-100 dark:border-gray-800 text-xs">
<span className="text-gray-500 dark:text-gray-400 font-medium">
<div className="space-y-1 pt-1 border-t border-gray-100 text-xs">
<span className="text-gray-500 font-medium">
Captured Groups:
</span>
<div className="space-y-1">
@ -471,10 +471,10 @@ export default function RegexTesterPage() {
key={gIdx}
className="flex items-start gap-2 font-mono text-xs pl-2 border-l-2 border-indigo-400"
>
<span className="text-indigo-600 dark:text-indigo-400 font-semibold shrink-0">
<span className="text-indigo-600 font-semibold shrink-0">
Group {gIdx + 1}:
</span>
<span className="text-gray-800 dark:text-gray-200 break-all">
<span className="text-gray-300 break-all">
{grp !== "" ? `"${grp}"` : <span className="italic text-gray-400">undefined</span>}
</span>
</div>
@ -485,8 +485,8 @@ export default function RegexTesterPage() {
{/* Named Groups */}
{item.namedGroups && Object.keys(item.namedGroups).length > 0 && (
<div className="space-y-1 pt-1 border-t border-gray-100 dark:border-gray-800 text-xs">
<span className="text-gray-500 dark:text-gray-400 font-medium">
<div className="space-y-1 pt-1 border-t border-gray-100 text-xs">
<span className="text-gray-500 font-medium">
Named Groups:
</span>
<div className="space-y-1">
@ -495,10 +495,10 @@ export default function RegexTesterPage() {
key={name}
className="flex items-start gap-2 font-mono text-xs pl-2 border-l-2 border-teal-400"
>
<span className="text-teal-600 dark:text-teal-400 font-semibold shrink-0">
<span className="text-teal-600 font-semibold shrink-0">
?&lt;{name}&gt;:
</span>
<span className="text-gray-800 dark:text-gray-200 break-all">
<span className="text-gray-300 break-all">
&quot;{val}&quot;
</span>
</div>
@ -513,12 +513,12 @@ export default function RegexTesterPage() {
</div>
{/* Substitution / Replace Section */}
<div className="rounded-xl border border-gray-200 dark:border-gray-700 bg-gray-50/50 dark:bg-gray-850 p-4 space-y-3">
<div className=" border border-[#1a1a1a] bg-black/50 p-4 space-y-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-bold text-gray-900 dark:text-gray-100">
<h3 className="text-sm font-bold text-gray-200">
Regex Substitution / Replace
</h3>
<span className="text-xs text-gray-500 dark:text-gray-400">
<span className="text-xs text-gray-500">
Supports $1, $2 group references
</span>
</div>
@ -527,7 +527,7 @@ export default function RegexTesterPage() {
<div className="space-y-1.5">
<label
htmlFor="regex-replace-input"
className="text-xs font-semibold text-gray-700 dark:text-gray-300"
className="text-xs font-semibold text-gray-400"
>
Replacement String
</label>
@ -537,25 +537,25 @@ export default function RegexTesterPage() {
value={replacement}
onChange={(e) => setReplacement(e.target.value)}
placeholder="e.g. [$1] or REDACTED"
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono text-xs focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-mono text-xs focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
<div className="space-y-1.5">
<div className="flex items-center justify-between">
<label className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label className="text-xs font-semibold text-gray-400">
Result After Replacement
</label>
<button
type="button"
onClick={handleCopyReplaced}
disabled={!replacedText}
className="text-xs text-blue-600 dark:text-blue-400 hover:underline disabled:opacity-40 cursor-pointer font-medium"
className="text-xs text-blue-600 hover:underline disabled:opacity-40 cursor-pointer font-medium"
>
{copiedReplace ? "Copied!" : "Copy Result"}
</button>
</div>
<div className="p-2.5 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 font-mono text-xs min-h-[42px] max-h-32 overflow-y-auto whitespace-pre-wrap break-words select-text">
<div className="p-2.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-mono text-xs min-h-[42px] max-h-32 overflow-y-auto whitespace-pre-wrap break-words select-text">
{replacedText}
</div>
</div>

View file

@ -335,16 +335,16 @@ export default function SlugGeneratorPage() {
<div className="space-y-6">
{/* Presets and Actions Bar */}
<div className="flex flex-wrap items-center justify-between gap-3 pb-3 border-b border-gray-200 dark:border-gray-800">
<div className="flex flex-wrap items-center justify-between gap-3 pb-3 border-b border-[#1a1a1a]">
<div className="flex flex-wrap items-center gap-2">
<span className="text-xs font-semibold text-gray-500 dark:text-gray-400">Presets:</span>
<span className="text-xs font-semibold text-gray-500">Presets:</span>
<button
type="button"
onClick={() => applyPreset("url")}
className={`px-3 py-1.5 rounded-lg text-xs font-semibold transition-all cursor-pointer ${
className={`px-3 py-1.5 text-xs font-semibold transition-all cursor-pointer ${
activePreset === "url"
? "bg-blue-600 text-white shadow-xs"
: "bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200"
: "bg-[#111] hover:bg-[#1a1a1a] text-gray-400"
}`}
>
URL Slug (my-post-title)
@ -352,10 +352,10 @@ export default function SlugGeneratorPage() {
<button
type="button"
onClick={() => applyPreset("filename")}
className={`px-3 py-1.5 rounded-lg text-xs font-semibold transition-all cursor-pointer ${
className={`px-3 py-1.5 text-xs font-semibold transition-all cursor-pointer ${
activePreset === "filename"
? "bg-blue-600 text-white shadow-xs"
: "bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200"
: "bg-[#111] hover:bg-[#1a1a1a] text-gray-400"
}`}
>
Filename-Safe (my_file_name)
@ -363,10 +363,10 @@ export default function SlugGeneratorPage() {
<button
type="button"
onClick={() => applyPreset("wordpress")}
className={`px-3 py-1.5 rounded-lg text-xs font-semibold transition-all cursor-pointer ${
className={`px-3 py-1.5 text-xs font-semibold transition-all cursor-pointer ${
activePreset === "wordpress"
? "bg-blue-600 text-white shadow-xs"
: "bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200"
: "bg-[#111] hover:bg-[#1a1a1a] text-gray-400"
}`}
>
WordPress Style
@ -374,10 +374,10 @@ export default function SlugGeneratorPage() {
<button
type="button"
onClick={() => applyPreset("seo-clean")}
className={`px-3 py-1.5 rounded-lg text-xs font-semibold transition-all cursor-pointer ${
className={`px-3 py-1.5 text-xs font-semibold transition-all cursor-pointer ${
activePreset === "seo-clean"
? "bg-blue-600 text-white shadow-xs"
: "bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200"
: "bg-[#111] hover:bg-[#1a1a1a] text-gray-400"
}`}
>
SEO Clean (&lt;60 + No Stop Words)
@ -388,7 +388,7 @@ export default function SlugGeneratorPage() {
<button
type="button"
onClick={() => setInput(SAMPLE_TEXT)}
className="px-3 py-1.5 text-xs font-medium bg-blue-50 hover:bg-blue-100 dark:bg-blue-950/50 dark:hover:bg-blue-900/50 text-blue-700 dark:text-blue-300 rounded-lg transition-colors cursor-pointer"
className="px-3 py-1.5 text-xs font-medium bg-[#0a0a1a] hover:bg-[#0a0a1a] text-blue-400 transition-colors cursor-pointer"
>
Load Sample
</button>
@ -396,7 +396,7 @@ export default function SlugGeneratorPage() {
type="button"
onClick={() => setInput("")}
disabled={!input}
className="px-3 py-1.5 text-xs font-medium bg-gray-100 hover:bg-rose-50 dark:bg-gray-800 dark:hover:bg-rose-950/30 text-gray-700 dark:text-gray-300 hover:text-rose-600 dark:hover:text-rose-400 disabled:opacity-40 rounded-lg transition-colors cursor-pointer"
className="px-3 py-1.5 text-xs font-medium bg-[#111] hover:bg-rose-50 text-gray-400 hover:text-rose-600 disabled:opacity-40 transition-colors cursor-pointer"
>
Clear
</button>
@ -404,9 +404,9 @@ export default function SlugGeneratorPage() {
</div>
{/* Configuration Options Card */}
<div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-gray-50/70 dark:bg-gray-900/50 p-4 space-y-4 shadow-xs">
<div className="flex items-center justify-between border-b border-gray-200/80 dark:border-gray-800 pb-2">
<span className="text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<div className=" border border-[#1a1a1a] bg-black/70 p-4 space-y-4 shadow-xs">
<div className="flex items-center justify-between border-b border-[#1a1a1a]/80 pb-2">
<span className="text-xs font-bold uppercase tracking-wider text-gray-400">
Slug Configuration &amp; Formatting Options
</span>
<span className="text-xs text-gray-400 font-mono">Real-time update</span>
@ -415,7 +415,7 @@ export default function SlugGeneratorPage() {
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Separator Selection */}
<div className="space-y-1.5">
<label className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label className="text-xs font-semibold text-gray-400">
Word Separator
</label>
<div className="grid grid-cols-4 gap-1">
@ -432,10 +432,10 @@ export default function SlugGeneratorPage() {
setOptions({ ...options, separator: s.val });
setActivePreset("custom");
}}
className={`py-1.5 text-center text-xs font-mono font-bold rounded-md border transition-colors cursor-pointer ${
className={`py-1.5 text-center text-xs font-mono font-bold border transition-colors cursor-pointer ${
options.separator === s.val
? "bg-blue-600 border-blue-600 text-white"
: "bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700"
: "bg-[#0a0a0a] border-[#1a1a1a] text-gray-300 hover:bg-[#111]"
}`}
title={s.label}
>
@ -447,7 +447,7 @@ export default function SlugGeneratorPage() {
{/* Letter Casing */}
<div className="space-y-1.5">
<label className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label className="text-xs font-semibold text-gray-400">
Letter Casing
</label>
<select
@ -459,7 +459,7 @@ export default function SlugGeneratorPage() {
});
setActivePreset("custom");
}}
className="w-full p-2 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-xs focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-xs focus:ring-1 focus:ring-blue-900 focus:outline-none"
>
<option value="lowercase">lowercase (recommended)</option>
<option value="uppercase">UPPERCASE</option>
@ -469,7 +469,7 @@ export default function SlugGeneratorPage() {
{/* Max Length Limit */}
<div className="space-y-1.5">
<label className="text-xs font-semibold text-gray-700 dark:text-gray-300">
<label className="text-xs font-semibold text-gray-400">
Max Length (characters)
</label>
<div className="flex items-center gap-2">
@ -484,13 +484,13 @@ export default function SlugGeneratorPage() {
setActivePreset("custom");
}}
placeholder="Unlimited (default)"
className="w-full p-2 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-xs focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-2 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 text-xs focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
{options.maxLength !== null && (
<button
type="button"
onClick={() => setOptions({ ...options, maxLength: null })}
className="p-2 text-xs text-gray-400 hover:text-gray-600 dark:hover:text-gray-200"
className="p-2 text-xs text-gray-400 hover:text-gray-600"
title="Remove max length"
>
✕
@ -501,7 +501,7 @@ export default function SlugGeneratorPage() {
{/* Quick Toggle Checkboxes */}
<div className="space-y-2 pt-1">
<label className="flex items-center gap-2 text-xs text-gray-700 dark:text-gray-300 cursor-pointer select-none">
<label className="flex items-center gap-2 text-xs text-gray-400 cursor-pointer select-none">
<input
type="checkbox"
checked={options.transliterateUnicode}
@ -509,12 +509,12 @@ export default function SlugGeneratorPage() {
setOptions({ ...options, transliterateUnicode: e.target.checked });
setActivePreset("custom");
}}
className="w-3.5 h-3.5 text-blue-600 rounded border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-3.5 h-3.5 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>Convert Accents (é→e, ñ→n)</span>
</label>
<label className="flex items-center gap-2 text-xs text-gray-700 dark:text-gray-300 cursor-pointer select-none">
<label className="flex items-center gap-2 text-xs text-gray-400 cursor-pointer select-none">
<input
type="checkbox"
checked={options.convertSymbols}
@ -522,7 +522,7 @@ export default function SlugGeneratorPage() {
setOptions({ ...options, convertSymbols: e.target.checked });
setActivePreset("custom");
}}
className="w-3.5 h-3.5 text-blue-600 rounded border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-3.5 h-3.5 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>Replace &amp; with &quot;and&quot;, @ with &quot;at&quot;</span>
</label>
@ -530,7 +530,7 @@ export default function SlugGeneratorPage() {
</div>
{/* Secondary Toggles Row */}
<div className="flex flex-wrap items-center gap-6 pt-2 border-t border-gray-200 dark:border-gray-800 text-xs text-gray-700 dark:text-gray-300">
<div className="flex flex-wrap items-center gap-6 pt-2 border-t border-[#1a1a1a] text-xs text-gray-400">
<label className="flex items-center gap-2 cursor-pointer select-none">
<input
type="checkbox"
@ -539,7 +539,7 @@ export default function SlugGeneratorPage() {
setOptions({ ...options, removeSpecialChars: e.target.checked });
setActivePreset("custom");
}}
className="w-3.5 h-3.5 text-blue-600 rounded border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-3.5 h-3.5 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>Remove Special Characters &amp; Emojis</span>
</label>
@ -552,7 +552,7 @@ export default function SlugGeneratorPage() {
setOptions({ ...options, removeStopWords: e.target.checked });
setActivePreset("custom");
}}
className="w-3.5 h-3.5 text-blue-600 rounded border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-3.5 h-3.5 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>Remove Stop Words (&quot;a&quot;, &quot;the&quot;, &quot;in&quot;, &quot;with&quot;)</span>
</label>
@ -566,7 +566,7 @@ export default function SlugGeneratorPage() {
setOptions({ ...options, smartTruncate: e.target.checked });
setActivePreset("custom");
}}
className="w-3.5 h-3.5 text-blue-600 rounded border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-3.5 h-3.5 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>Smart Truncate (don&apos;t split words in half)</span>
</label>
@ -579,7 +579,7 @@ export default function SlugGeneratorPage() {
{/* Input Area */}
<div className="flex flex-col space-y-2">
<div className="flex items-center justify-between">
<label htmlFor={inputId} className="text-sm font-semibold text-gray-700 dark:text-gray-300">
<label htmlFor={inputId} className="text-sm font-semibold text-gray-400">
Input Text or Multi-line Titles
</label>
<span className="text-xs text-gray-400 font-mono">
@ -593,7 +593,7 @@ export default function SlugGeneratorPage() {
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type or paste titles, post headlines, or phrases here (one per line for bulk conversion)..."
className="w-full p-3.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-sans text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none resize-y shadow-inner leading-relaxed"
className="w-full p-3.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-sans text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none resize-y shadow-inner leading-relaxed"
/>
</div>
@ -601,10 +601,10 @@ export default function SlugGeneratorPage() {
<div className="flex flex-col space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<label htmlFor={outputId} className="text-sm font-semibold text-gray-700 dark:text-gray-300">
<label htmlFor={outputId} className="text-sm font-semibold text-gray-400">
Generated Slugs
</label>
<span className="px-2 py-0.5 rounded-full text-xs font-semibold bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300">
<span className="px-2 py-0.5 rounded-none text-xs font-semibold bg-emerald-100 text-emerald-800">
{validSlugCount} {validSlugCount === 1 ? "slug" : "slugs"}
</span>
</div>
@ -614,7 +614,7 @@ export default function SlugGeneratorPage() {
type="button"
onClick={handleDownload}
disabled={!bulkOutput}
className="px-2.5 py-1 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 disabled:opacity-40 rounded text-xs font-medium transition-colors cursor-pointer"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] text-gray-400 disabled:opacity-40 text-xs font-medium transition-colors cursor-pointer"
title="Download all slugs as .txt"
>
Download .txt
@ -623,7 +623,7 @@ export default function SlugGeneratorPage() {
type="button"
onClick={handleCopyAll}
disabled={!bulkOutput}
className="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-40 rounded text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
className="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-40 text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
>
{copiedAll ? (
<>
@ -650,16 +650,16 @@ export default function SlugGeneratorPage() {
value={bulkOutput}
readOnly
placeholder="Generated slugs will appear here in real time..."
className="w-full p-3.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-900/70 text-gray-900 dark:text-gray-100 font-mono text-sm focus:outline-none resize-y select-all shadow-inner leading-relaxed"
className="w-full p-3.5 border border-[#1a1a1a] bg-black text-gray-200 font-mono text-sm focus:outline-none resize-y select-all shadow-inner leading-relaxed"
/>
</div>
</div>
{/* Live URL Preview Card for the First/Primary Slug */}
{slugs.length > 0 && slugs[0] && (
<div className="rounded-xl border border-blue-200 bg-blue-50/50 dark:border-blue-900/40 dark:bg-blue-950/20 p-4 space-y-2">
<div className=" border border-blue-900 bg-[#0a0a1a]/50 p-4 space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs font-bold uppercase tracking-wider text-blue-700 dark:text-blue-400 flex items-center gap-1.5">
<span className="text-xs font-bold uppercase tracking-wider text-blue-400 flex items-center gap-1.5">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
</svg>
@ -668,14 +668,14 @@ export default function SlugGeneratorPage() {
<button
type="button"
onClick={() => handleCopySingle(`https://example.com/blog/${slugs[0]}`, 999)}
className="text-xs text-blue-600 hover:text-blue-700 dark:text-blue-400 font-medium cursor-pointer"
className="text-xs text-blue-600 hover:text-blue-400 font-medium cursor-pointer"
>
{copiedIndex === 999 ? "Copied URL!" : "Copy Full URL"}
</button>
</div>
<div className="p-2.5 rounded-lg bg-white dark:bg-gray-900 border border-blue-100 dark:border-blue-900/60 font-mono text-xs text-gray-800 dark:text-gray-200 overflow-x-auto whitespace-nowrap">
<div className="p-2.5 bg-[#0a0a0a] border border-blue-100 font-mono text-xs text-gray-300 overflow-x-auto whitespace-nowrap">
<span className="text-gray-400 select-none">https://example.com/blog/</span>
<span className="font-bold text-blue-600 dark:text-blue-400">{slugs[0]}</span>
<span className="font-bold text-blue-600">{slugs[0]}</span>
</div>
</div>
)}
@ -684,7 +684,7 @@ export default function SlugGeneratorPage() {
{lines.length > 1 && (
<div className="space-y-3 pt-2">
<div className="flex items-center justify-between">
<h3 className="text-sm font-bold text-gray-900 dark:text-gray-100">
<h3 className="text-sm font-bold text-gray-200">
Individual Slugs Breakdown ({lines.length} items)
</h3>
<span className="text-xs text-gray-400">Click &quot;Copy&quot; on any individual slug</span>
@ -700,14 +700,14 @@ export default function SlugGeneratorPage() {
return (
<div
key={idx}
className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 p-3 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-850 hover:border-blue-300 dark:hover:border-blue-700 transition-colors"
className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 p-3 border border-[#1a1a1a] bg-[#0a0a0a] hover:border-blue-300 transition-colors"
>
<div className="min-w-0 flex-1 space-y-1">
<div className="text-xs text-gray-500 dark:text-gray-400 truncate" title={rawLine}>
<div className="text-xs text-gray-500 truncate" title={rawLine}>
<span className="font-semibold text-gray-400 mr-1.5">#{idx + 1}</span>
{rawLine}
</div>
<div className="font-mono text-xs font-semibold text-gray-900 dark:text-gray-100 truncate">
<div className="font-mono text-xs font-semibold text-gray-200 truncate">
{slugVal || <span className="text-gray-400 italic">No output</span>}
</div>
</div>
@ -720,10 +720,10 @@ export default function SlugGeneratorPage() {
type="button"
onClick={() => handleCopySingle(slugVal, idx)}
disabled={!slugVal}
className={`px-2.5 py-1 rounded text-xs font-medium transition-colors cursor-pointer ${
className={`px-2.5 py-1 text-xs font-medium transition-colors cursor-pointer ${
isCopied
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300"
: "bg-gray-100 hover:bg-gray-200 text-gray-700 dark:bg-gray-750 dark:hover:bg-gray-700 dark:text-gray-200"
? "bg-emerald-100 text-emerald-700"
: "bg-[#111] hover:bg-[#1a1a1a] text-gray-400"
}`}
>
{isCopied ? "Copied!" : "Copy"}
@ -737,17 +737,17 @@ export default function SlugGeneratorPage() {
)}
{/* SEO & Developer Tips Section */}
<div className="mt-8 pt-6 border-t border-gray-200 dark:border-gray-800 grid grid-cols-1 md:grid-cols-3 gap-4 text-xs text-gray-600 dark:text-gray-400">
<div className="p-3.5 bg-gray-50 dark:bg-gray-850/60 rounded-xl border border-gray-200 dark:border-gray-800">
<h4 className="font-bold text-gray-900 dark:text-gray-200 mb-1">SEO Best Practices</h4>
<div className="mt-8 pt-6 border-t border-[#1a1a1a] grid grid-cols-1 md:grid-cols-3 gap-4 text-xs text-gray-600">
<div className="p-3.5 bg-black border border-[#1a1a1a]">
<h4 className="font-bold text-gray-200 mb-1">SEO Best Practices</h4>
<p>Keep slugs between 3 to 5 words (&lt;60 chars) and separate words with hyphens for optimal search engine crawling.</p>
</div>
<div className="p-3.5 bg-gray-50 dark:bg-gray-850/60 rounded-xl border border-gray-200 dark:border-gray-800">
<h4 className="font-bold text-gray-900 dark:text-gray-200 mb-1">Accent Transliteration</h4>
<div className="p-3.5 bg-black border border-[#1a1a1a]">
<h4 className="font-bold text-gray-200 mb-1">Accent Transliteration</h4>
<p>Accented characters from European languages (like é, ü, ç, ñ, ø, å, ß) are mapped to clean ASCII equivalents automatically.</p>
</div>
<div className="p-3.5 bg-gray-50 dark:bg-gray-850/60 rounded-xl border border-gray-200 dark:border-gray-800">
<h4 className="font-bold text-gray-900 dark:text-gray-200 mb-1">Bulk Line Processing</h4>
<div className="p-3.5 bg-black border border-[#1a1a1a]">
<h4 className="font-bold text-gray-200 mb-1">Bulk Line Processing</h4>
<p>Paste multiple titles or list of article headlines at once to generate clean slugs for all items simultaneously.</p>
</div>
</div>

View file

@ -601,11 +601,11 @@ export default function SqlFormatterPage() {
>
<div className="space-y-5">
{/* Top Controls Toolbar */}
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-gray-200 pb-4 dark:border-gray-800">
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-[#1a1a1a] pb-4">
<div className="flex flex-wrap items-center gap-2">
<button
onClick={handleFormat}
className="inline-flex items-center gap-1.5 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 active:scale-95 cursor-pointer"
className="inline-flex items-center gap-1.5 bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 active:scale-95 cursor-pointer"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h7" />
@ -615,7 +615,7 @@ export default function SqlFormatterPage() {
<button
onClick={handleMinify}
className="inline-flex items-center gap-1.5 rounded-lg bg-gray-200 px-4 py-2 text-sm font-medium text-gray-800 transition-colors hover:bg-gray-300 active:scale-95 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700 cursor-pointer"
className="inline-flex items-center gap-1.5 bg-[#1a1a1a] px-4 py-2 text-sm font-medium text-gray-300 transition-colors hover:bg-[#222] active:scale-95 cursor-pointer"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 14l-7 7m0 0l-7-7m7 7V3" />
@ -625,7 +625,7 @@ export default function SqlFormatterPage() {
<button
onClick={handleLoadSample}
className="inline-flex items-center gap-1.5 rounded-lg bg-blue-50 px-3 py-2 text-sm font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:bg-blue-950/40 dark:text-blue-300 dark:hover:bg-blue-900/50 cursor-pointer"
className="inline-flex items-center gap-1.5 bg-[#0a0a1a] px-3 py-2 text-sm font-medium text-blue-400 transition-colors hover:bg-[#0a0a1a] cursor-pointer"
>
Load Sample SQL
</button>
@ -633,24 +633,24 @@ export default function SqlFormatterPage() {
<button
onClick={handleClear}
disabled={!input && !output}
className="inline-flex items-center gap-1.5 rounded-lg bg-gray-100 px-3 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-rose-50 hover:text-rose-600 disabled:opacity-40 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-rose-950/30 dark:hover:text-rose-400 cursor-pointer"
className="inline-flex items-center gap-1.5 bg-[#111] px-3 py-2 text-sm font-medium text-gray-400 transition-colors hover:bg-rose-50 hover:text-rose-600 disabled:opacity-40 cursor-pointer"
>
Clear
</button>
</div>
{/* Options */}
<div className="flex flex-wrap items-center gap-3 text-sm text-gray-700 dark:text-gray-300">
<div className="flex flex-wrap items-center gap-3 text-sm text-gray-400">
{/* Indent Selector */}
<div className="flex items-center gap-1.5">
<label htmlFor={indentSelectId} className="text-xs font-semibold text-gray-500 uppercase dark:text-gray-400">
<label htmlFor={indentSelectId} className="text-xs font-semibold text-gray-500 uppercase">
Indent:
</label>
<select
id={indentSelectId}
value={indentStr}
onChange={(e) => setIndentStr(e.target.value)}
className="rounded-md border border-gray-300 bg-white px-2 py-1 text-xs text-gray-800 shadow-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
className=" border border-[#1a1a1a] bg-[#0a0a0a] px-2 py-1 text-xs text-gray-300 shadow-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900"
>
<option value=" ">2 Spaces</option>
<option value=" ">4 Spaces</option>
@ -660,14 +660,14 @@ export default function SqlFormatterPage() {
{/* Keyword Casing */}
<div className="flex items-center gap-1.5">
<label htmlFor={casingSelectId} className="text-xs font-semibold text-gray-500 uppercase dark:text-gray-400">
<label htmlFor={casingSelectId} className="text-xs font-semibold text-gray-500 uppercase">
Keywords:
</label>
<select
id={casingSelectId}
value={keywordCase}
onChange={(e) => setKeywordCase(e.target.value as "upper" | "lower" | "preserve")}
className="rounded-md border border-gray-300 bg-white px-2 py-1 text-xs text-gray-800 shadow-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
className=" border border-[#1a1a1a] bg-[#0a0a0a] px-2 py-1 text-xs text-gray-300 shadow-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900"
>
<option value="upper">UPPERCASE</option>
<option value="lower">lowercase</option>
@ -676,12 +676,12 @@ export default function SqlFormatterPage() {
</div>
{/* Column Line Break */}
<label className="flex cursor-pointer items-center gap-1.5 text-xs text-gray-600 select-none hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100">
<label className="flex cursor-pointer items-center gap-1.5 text-xs text-gray-600 select-none hover:text-gray-200">
<input
type="checkbox"
checked={breakCommas}
onChange={(e) => setBreakCommas(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>Column per line</span>
</label>
@ -690,9 +690,9 @@ export default function SqlFormatterPage() {
{/* Status Notification Banner */}
{status.type === "success" && (
<div className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-emerald-300 bg-emerald-50 px-4 py-3 text-sm text-emerald-900 dark:border-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-300">
<div className="flex flex-wrap items-center justify-between gap-2 border border-emerald-300 bg-emerald-50 px-4 py-3 text-sm text-emerald-900">
<div className="flex items-center gap-2">
<svg className="h-5 w-5 shrink-0 text-emerald-600 dark:text-emerald-400" fill="currentColor" viewBox="0 0 20 20">
<svg className="h-5 w-5 shrink-0 text-emerald-600" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
@ -701,7 +701,7 @@ export default function SqlFormatterPage() {
</svg>
<span className="font-medium">{status.message}</span>
</div>
<div className="flex flex-wrap items-center gap-3 text-xs text-emerald-800 dark:text-emerald-300">
<div className="flex flex-wrap items-center gap-3 text-xs text-emerald-800">
{status.lineCount !== undefined && <span>Lines: <strong>{status.lineCount}</strong></span>}
{status.charCount !== undefined && <span>Characters: <strong>{status.charCount}</strong></span>}
</div>
@ -709,9 +709,9 @@ export default function SqlFormatterPage() {
)}
{status.type === "error" && (
<div className="rounded-lg border border-red-300 bg-red-50 p-4 text-sm text-red-900 dark:border-red-800 dark:bg-red-950/40 dark:text-red-300">
<div className=" border border-red-900 bg-[#1a0a0a] p-4 text-sm text-red-900">
<div className="flex items-start gap-2">
<svg className="mt-0.5 h-5 w-5 shrink-0 text-red-600 dark:text-red-400" fill="currentColor" viewBox="0 0 20 20">
<svg className="mt-0.5 h-5 w-5 shrink-0 text-red-400" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
@ -719,8 +719,8 @@ export default function SqlFormatterPage() {
/>
</svg>
<div>
<p className="font-semibold text-red-800 dark:text-red-200">Formatting Error</p>
<p className="mt-1 font-mono text-xs text-red-700 dark:text-red-300">{status.message}</p>
<p className="font-semibold text-red-400">Formatting Error</p>
<p className="mt-1 font-mono text-xs text-red-400">{status.message}</p>
</div>
</div>
</div>
@ -732,8 +732,8 @@ export default function SqlFormatterPage() {
<div className="flex flex-col">
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-800 dark:text-gray-200">Raw SQL Input</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
<span className="text-sm font-semibold text-gray-300">Raw SQL Input</span>
<span className="text-xs text-gray-500">
({inputLineCount} {inputLineCount === 1 ? "line" : "lines"}, {input.length} chars)
</span>
</div>
@ -747,14 +747,14 @@ export default function SqlFormatterPage() {
// Permission denied
}
}}
className="rounded px-2 py-1 text-xs font-medium text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800 cursor-pointer"
className="rounded px-2 py-1 text-xs font-medium text-gray-600 hover:bg-[#111] cursor-pointer"
title="Paste from clipboard"
>
Paste
</button>
<button
onClick={() => setInput("")}
className="rounded px-2 py-1 text-xs font-medium text-gray-500 hover:bg-gray-100 hover:text-red-600 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-red-400 cursor-pointer"
className="rounded px-2 py-1 text-xs font-medium text-gray-500 hover:bg-[#111] hover:text-red-400 cursor-pointer"
title="Clear input"
>
Clear
@ -767,7 +767,7 @@ export default function SqlFormatterPage() {
placeholder="Enter or paste unformatted SQL queries here..."
rows={18}
spellCheck={false}
className="w-full flex-1 resize-y rounded-lg border border-gray-300 bg-white p-3 font-mono text-sm leading-relaxed text-gray-900 shadow-xs focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-500"
className="w-full flex-1 resize-y border border-[#1a1a1a] bg-[#0a0a0a] p-3 font-mono text-sm leading-relaxed text-gray-200 shadow-xs focus:border-blue-500 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
@ -775,8 +775,8 @@ export default function SqlFormatterPage() {
<div className="flex flex-col">
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-800 dark:text-gray-200">Formatted SQL Result</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
<span className="text-sm font-semibold text-gray-300">Formatted SQL Result</span>
<span className="text-xs text-gray-500">
({outputLineCount} {outputLineCount === 1 ? "line" : "lines"}, {output.length} chars)
</span>
</div>
@ -784,7 +784,7 @@ export default function SqlFormatterPage() {
<button
onClick={handleCopy}
disabled={!output && !input}
className="inline-flex items-center gap-1 rounded-md bg-blue-600 px-3 py-1 text-xs font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 disabled:opacity-40 cursor-pointer"
className="inline-flex items-center gap-1 bg-blue-600 px-3 py-1 text-xs font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 disabled:opacity-40 cursor-pointer"
title="Copy SQL output"
>
{copied ? (
@ -816,29 +816,29 @@ export default function SqlFormatterPage() {
placeholder="Formatted SQL output will appear here..."
rows={18}
spellCheck={false}
className="w-full flex-1 resize-y rounded-lg border border-gray-300 bg-gray-50 p-3 font-mono text-sm leading-relaxed text-gray-900 shadow-xs focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none dark:border-gray-700 dark:bg-gray-900/80 dark:text-gray-100 dark:placeholder-gray-500"
className="w-full flex-1 resize-y border border-[#1a1a1a] bg-black p-3 font-mono text-sm leading-relaxed text-gray-200 shadow-xs focus:border-blue-500 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
</div>
{/* Feature Highlights & Guide */}
<div className="mt-8 rounded-xl border border-gray-200 bg-gray-50/70 p-5 text-sm text-gray-600 dark:border-gray-800 dark:bg-gray-800/30 dark:text-gray-400">
<h3 className="font-semibold text-gray-900 dark:text-gray-100">About SQL Formatter</h3>
<div className="mt-8 border border-[#1a1a1a] bg-black/70 p-5 text-sm text-gray-600">
<h3 className="font-semibold text-gray-200">About SQL Formatter</h3>
<div className="mt-3 grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">📐 Major Clause Alignment</p>
<p className="font-medium text-gray-300">📐 Major Clause Alignment</p>
<p className="mt-1 text-xs leading-normal">
Aligns SELECT, FROM, JOIN, WHERE, GROUP BY, HAVING, ORDER BY, and sub-clauses for optimal clarity.
</p>
</div>
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">🔤 Keyword Casing</p>
<p className="font-medium text-gray-300">🔤 Keyword Casing</p>
<p className="mt-1 text-xs leading-normal">
Standardize keywords to UPPERCASE or lowercase across ANSI SQL, MySQL, PostgreSQL, SQLite, and SQL Server.
</p>
</div>
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">⚡ Single-Line Minify</p>
<p className="font-medium text-gray-300">⚡ Single-Line Minify</p>
<p className="mt-1 text-xs leading-normal">
Remove unnecessary linebreaks and comments to compact SQL queries for embedding into code or scripts.
</p>

View file

@ -241,31 +241,31 @@ export default function TimestampConverterPage() {
>
<div className="space-y-8">
{/* Live Current Timestamp Banner */}
<div className="rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-4 dark:border-blue-900/50 dark:from-blue-950/30 dark:to-indigo-950/30 sm:p-6">
<div className=" border border-blue-900 bg-gradient-to-r from-blue-50 to-indigo-50 p-4 sm:p-6">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<div className="flex items-center gap-2">
<span className="relative flex h-3 w-3">
<span
className={`absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75 ${
className={`absolute inline-flex h-full w-full rounded-none bg-emerald-400 opacity-75 ${
!isLivePaused ? "animate-ping" : ""
}`}
/>
<span
className={`relative inline-flex h-3 w-3 rounded-full ${
className={`relative inline-flex h-3 w-3 rounded-none ${
!isLivePaused ? "bg-emerald-500" : "bg-amber-500"
}`}
/>
</span>
<span className="text-xs font-semibold uppercase tracking-wider text-gray-600 dark:text-gray-300">
<span className="text-xs font-semibold uppercase tracking-wider text-gray-600">
Current Unix Epoch Time {isLivePaused && "(Paused)"}
</span>
</div>
<div className="mt-2 font-mono text-3xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-4xl">
<div className="mt-2 font-mono text-3xl font-bold tracking-tight text-gray-200 sm:text-4xl">
{currentTimestamp}
</div>
<div className="mt-1 text-xs text-gray-600 dark:text-gray-400 sm:text-sm">
<span className="font-medium text-gray-800 dark:text-gray-200">UTC:</span> {nowDate.toUTCString()}
<div className="mt-1 text-xs text-gray-600 sm:text-sm">
<span className="font-medium text-gray-300">UTC:</span> {nowDate.toUTCString()}
</div>
</div>
@ -273,7 +273,7 @@ export default function TimestampConverterPage() {
<button
type="button"
onClick={() => copyToClipboard(currentTimestamp.toString(), "live-ts")}
className="flex items-center gap-1.5 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 active:scale-95 transition-all"
className="flex items-center gap-1.5 bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 active:scale-95 transition-all"
>
{copiedKey === "live-ts" ? (
<>
@ -300,7 +300,7 @@ export default function TimestampConverterPage() {
<button
type="button"
onClick={() => setIsLivePaused(!isLivePaused)}
className="rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700 transition-colors"
className=" border border-[#1a1a1a] bg-[#0a0a0a] px-3 py-2 text-sm font-medium text-gray-400 hover:bg-[#111] transition-colors"
>
{isLivePaused ? "Resume" : "Pause"}
</button>
@ -312,7 +312,7 @@ export default function TimestampConverterPage() {
setTsInput(Math.floor(Date.now() / 1000).toString());
setDateInput(toDatetimeLocal(new Date()));
}}
className="rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700 transition-colors"
className=" border border-[#1a1a1a] bg-[#0a0a0a] px-3 py-2 text-sm font-medium text-gray-400 hover:bg-[#111] transition-colors"
title="Reset to current time"
>
Reset to Now
@ -322,14 +322,14 @@ export default function TimestampConverterPage() {
</div>
{/* Tab Navigation */}
<div className="flex border-b border-gray-200 dark:border-gray-800">
<div className="flex border-b border-[#1a1a1a]">
<button
type="button"
onClick={() => setActiveTab("tsToDate")}
className={`flex items-center gap-2 border-b-2 px-4 py-3 text-sm font-semibold transition-colors ${
activeTab === "tsToDate"
? "border-blue-600 text-blue-600 dark:border-blue-400 dark:text-blue-400"
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
? "border-blue-600 text-blue-600"
: "border-transparent text-gray-500 hover:border-[#1a1a1a] hover:text-gray-400"
}`}
>
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -347,8 +347,8 @@ export default function TimestampConverterPage() {
onClick={() => setActiveTab("dateToTs")}
className={`flex items-center gap-2 border-b-2 px-4 py-3 text-sm font-semibold transition-colors ${
activeTab === "dateToTs"
? "border-blue-600 text-blue-600 dark:border-blue-400 dark:text-blue-400"
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
? "border-blue-600 text-blue-600"
: "border-transparent text-gray-500 hover:border-[#1a1a1a] hover:text-gray-400"
}`}
>
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -366,22 +366,22 @@ export default function TimestampConverterPage() {
{/* Tab 1: Timestamp to Date */}
{activeTab === "tsToDate" && (
<div className="space-y-6">
<div className="rounded-xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-800 dark:bg-gray-900/50 sm:p-5">
<div className=" border border-[#1a1a1a] bg-black p-4 sm:p-5">
<div className="flex flex-col gap-3">
<div className="flex flex-wrap items-center justify-between gap-2">
<label htmlFor="ts-input" className="text-sm font-semibold text-gray-900 dark:text-gray-100">
<label htmlFor="ts-input" className="text-sm font-semibold text-gray-200">
Enter Unix Timestamp:
</label>
<div className="flex items-center gap-2">
<span className="text-xs text-gray-500 dark:text-gray-400">Unit:</span>
<div className="inline-flex rounded-lg border border-gray-300 p-0.5 dark:border-gray-700 bg-white dark:bg-gray-800">
<span className="text-xs text-gray-500">Unit:</span>
<div className="inline-flex border border-[#1a1a1a] p-0.5 bg-[#0a0a0a]">
<button
type="button"
onClick={() => setUnitMode("auto")}
className={`px-2.5 py-1 text-xs font-medium rounded-md transition-colors ${
className={`px-2.5 py-1 text-xs font-medium transition-colors ${
unitMode === "auto"
? "bg-blue-600 text-white"
: "text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white"
: "text-gray-600 hover:text-gray-200"
}`}
>
Auto
@ -389,10 +389,10 @@ export default function TimestampConverterPage() {
<button
type="button"
onClick={() => setUnitMode("seconds")}
className={`px-2.5 py-1 text-xs font-medium rounded-md transition-colors ${
className={`px-2.5 py-1 text-xs font-medium transition-colors ${
unitMode === "seconds"
? "bg-blue-600 text-white"
: "text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white"
: "text-gray-600 hover:text-gray-200"
}`}
>
Seconds (s)
@ -400,10 +400,10 @@ export default function TimestampConverterPage() {
<button
type="button"
onClick={() => setUnitMode("milliseconds")}
className={`px-2.5 py-1 text-xs font-medium rounded-md transition-colors ${
className={`px-2.5 py-1 text-xs font-medium transition-colors ${
unitMode === "milliseconds"
? "bg-blue-600 text-white"
: "text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white"
: "text-gray-600 hover:text-gray-200"
}`}
>
Milliseconds (ms)
@ -419,13 +419,13 @@ export default function TimestampConverterPage() {
value={tsInput}
onChange={(e) => setTsInput(e.target.value)}
placeholder="e.g. 1788114108"
className="w-full p-3 font-mono text-base sm:text-lg rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-3 font-mono text-base sm:text-lg border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
{tsInput && (
<button
type="button"
onClick={() => setTsInput("")}
className="absolute right-3 top-1/2 -translate-y-1/2 rounded p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-700 dark:hover:text-gray-200"
className="absolute right-3 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:bg-[#111] hover:text-gray-600"
>
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
@ -436,53 +436,53 @@ export default function TimestampConverterPage() {
{/* Quick Buttons */}
<div className="flex flex-wrap items-center gap-1.5 pt-1">
<span className="text-xs font-medium text-gray-500 dark:text-gray-400 mr-1">Quick presets:</span>
<span className="text-xs font-medium text-gray-500 mr-1">Quick presets:</span>
<button
type="button"
onClick={() => handleQuickTs("now")}
className="rounded bg-white px-2.5 py-1 text-xs font-medium text-gray-700 border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded bg-[#0a0a0a] px-2.5 py-1 text-xs font-medium text-gray-400 border border-[#1a1a1a] hover:bg-[#111]"
>
Now
</button>
<button
type="button"
onClick={() => handleQuickTs("todayStart")}
className="rounded bg-white px-2.5 py-1 text-xs font-medium text-gray-700 border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded bg-[#0a0a0a] px-2.5 py-1 text-xs font-medium text-gray-400 border border-[#1a1a1a] hover:bg-[#111]"
>
Start of Today
</button>
<button
type="button"
onClick={() => handleQuickTs("todayEnd")}
className="rounded bg-white px-2.5 py-1 text-xs font-medium text-gray-700 border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded bg-[#0a0a0a] px-2.5 py-1 text-xs font-medium text-gray-400 border border-[#1a1a1a] hover:bg-[#111]"
>
End of Today
</button>
<button
type="button"
onClick={() => handleQuickTs("minusHour")}
className="rounded bg-white px-2.5 py-1 text-xs font-medium text-gray-700 border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded bg-[#0a0a0a] px-2.5 py-1 text-xs font-medium text-gray-400 border border-[#1a1a1a] hover:bg-[#111]"
>
-1 Hour
</button>
<button
type="button"
onClick={() => handleQuickTs("plusHour")}
className="rounded bg-white px-2.5 py-1 text-xs font-medium text-gray-700 border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded bg-[#0a0a0a] px-2.5 py-1 text-xs font-medium text-gray-400 border border-[#1a1a1a] hover:bg-[#111]"
>
+1 Hour
</button>
<button
type="button"
onClick={() => handleQuickTs("minusDay")}
className="rounded bg-white px-2.5 py-1 text-xs font-medium text-gray-700 border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded bg-[#0a0a0a] px-2.5 py-1 text-xs font-medium text-gray-400 border border-[#1a1a1a] hover:bg-[#111]"
>
-1 Day
</button>
<button
type="button"
onClick={() => handleQuickTs("plusDay")}
className="rounded bg-white px-2.5 py-1 text-xs font-medium text-gray-700 border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded bg-[#0a0a0a] px-2.5 py-1 text-xs font-medium text-gray-400 border border-[#1a1a1a] hover:bg-[#111]"
>
+1 Day
</button>
@ -500,7 +500,7 @@ export default function TimestampConverterPage() {
prefix="ts"
/>
) : (
<div className="rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-700 dark:border-red-900/50 dark:bg-red-950/30 dark:text-red-300">
<div className=" border border-red-900 bg-[#1a0a0a] p-4 text-sm text-red-400">
⚠️ {parsedFromTs.error}
</div>
)}
@ -510,22 +510,22 @@ export default function TimestampConverterPage() {
{/* Tab 2: Date to Timestamp */}
{activeTab === "dateToTs" && (
<div className="space-y-6">
<div className="rounded-xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-800 dark:bg-gray-900/50 sm:p-5">
<div className=" border border-[#1a1a1a] bg-black p-4 sm:p-5">
<div className="flex flex-col gap-4">
<div className="flex flex-wrap items-center justify-between gap-2">
<label htmlFor="datetime-picker" className="text-sm font-semibold text-gray-900 dark:text-gray-100">
<label htmlFor="datetime-picker" className="text-sm font-semibold text-gray-200">
Select Date & Time:
</label>
<div className="flex items-center gap-2">
<span className="text-xs text-gray-500 dark:text-gray-400">Interpret as:</span>
<div className="inline-flex rounded-lg border border-gray-300 p-0.5 dark:border-gray-700 bg-white dark:bg-gray-800">
<span className="text-xs text-gray-500">Interpret as:</span>
<div className="inline-flex border border-[#1a1a1a] p-0.5 bg-[#0a0a0a]">
<button
type="button"
onClick={() => setDateInputTz("local")}
className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
className={`px-3 py-1 text-xs font-medium transition-colors ${
dateInputTz === "local"
? "bg-blue-600 text-white"
: "text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white"
: "text-gray-600 hover:text-gray-200"
}`}
>
Local Timezone
@ -533,10 +533,10 @@ export default function TimestampConverterPage() {
<button
type="button"
onClick={() => setDateInputTz("utc")}
className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
className={`px-3 py-1 text-xs font-medium transition-colors ${
dateInputTz === "utc"
? "bg-blue-600 text-white"
: "text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white"
: "text-gray-600 hover:text-gray-200"
}`}
>
UTC Time
@ -553,14 +553,14 @@ export default function TimestampConverterPage() {
step="1"
value={dateInput}
onChange={(e) => setDateInput(e.target.value)}
className="w-full p-3 font-mono text-base rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="w-full p-3 font-mono text-base border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => handleQuickDate("now")}
className="flex-1 py-3 px-3 rounded-lg bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200 text-sm font-medium transition-colors"
className="flex-1 py-3 px-3 bg-[#1a1a1a] hover:bg-[#222] text-gray-300 text-sm font-medium transition-colors"
>
Set to Now
</button>
@ -569,32 +569,32 @@ export default function TimestampConverterPage() {
{/* Quick presets for date */}
<div className="flex flex-wrap items-center gap-1.5 pt-1">
<span className="text-xs font-medium text-gray-500 dark:text-gray-400 mr-1">Quick presets:</span>
<span className="text-xs font-medium text-gray-500 mr-1">Quick presets:</span>
<button
type="button"
onClick={() => handleQuickDate("todayStart")}
className="rounded bg-white px-2.5 py-1 text-xs font-medium text-gray-700 border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded bg-[#0a0a0a] px-2.5 py-1 text-xs font-medium text-gray-400 border border-[#1a1a1a] hover:bg-[#111]"
>
Start of Today
</button>
<button
type="button"
onClick={() => handleQuickDate("todayEnd")}
className="rounded bg-white px-2.5 py-1 text-xs font-medium text-gray-700 border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded bg-[#0a0a0a] px-2.5 py-1 text-xs font-medium text-gray-400 border border-[#1a1a1a] hover:bg-[#111]"
>
End of Today
</button>
<button
type="button"
onClick={() => handleQuickDate("minusDay")}
className="rounded bg-white px-2.5 py-1 text-xs font-medium text-gray-700 border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded bg-[#0a0a0a] px-2.5 py-1 text-xs font-medium text-gray-400 border border-[#1a1a1a] hover:bg-[#111]"
>
Yesterday
</button>
<button
type="button"
onClick={() => handleQuickDate("plusDay")}
className="rounded bg-white px-2.5 py-1 text-xs font-medium text-gray-700 border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
className="rounded bg-[#0a0a0a] px-2.5 py-1 text-xs font-medium text-gray-400 border border-[#1a1a1a] hover:bg-[#111]"
>
Tomorrow
</button>
@ -612,7 +612,7 @@ export default function TimestampConverterPage() {
prefix="date"
/>
) : (
<div className="rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-700 dark:border-red-900/50 dark:bg-red-950/30 dark:text-red-300">
<div className=" border border-red-900 bg-[#1a0a0a] p-4 text-sm text-red-400">
⚠️ {parsedFromDate.error}
</div>
)}
@ -620,8 +620,8 @@ export default function TimestampConverterPage() {
)}
{/* Cheat sheet / Developer Reference */}
<div className="mt-8 rounded-xl border border-gray-200 bg-gray-50 p-5 dark:border-gray-800 dark:bg-gray-900/50">
<h3 className="text-base font-bold text-gray-900 dark:text-white mb-3">
<div className="mt-8 border border-[#1a1a1a] bg-black p-5">
<h3 className="text-base font-bold text-gray-200 mb-3">
Developer Reference: Getting Current Timestamp
</h3>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
@ -638,31 +638,31 @@ export default function TimestampConverterPage() {
].map((item) => (
<div
key={item.lang}
className="rounded-lg border border-gray-200 bg-white p-3 dark:border-gray-700 dark:bg-gray-800"
className=" border border-[#1a1a1a] bg-[#0a0a0a] p-3"
>
<div className="flex items-center justify-between mb-1">
<span className="text-xs font-semibold text-gray-500 dark:text-gray-400">{item.lang}</span>
<span className="text-xs font-semibold text-gray-500">{item.lang}</span>
<button
type="button"
onClick={() => copyToClipboard(item.code, `code-${item.lang}`)}
className="text-xs text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
className="text-xs text-blue-600 hover:text-blue-400"
>
{copiedKey === `code-${item.lang}` ? "Copied!" : "Copy"}
</button>
</div>
<code className="block font-mono text-xs text-gray-800 dark:text-gray-200 overflow-x-auto whitespace-nowrap">
<code className="block font-mono text-xs text-gray-300 overflow-x-auto whitespace-nowrap">
{item.code}
</code>
</div>
))}
</div>
<div className="mt-4 pt-4 border-t border-gray-200 dark:border-gray-700 flex flex-wrap gap-x-6 gap-y-2 text-xs text-gray-600 dark:text-gray-400">
<div><strong className="text-gray-800 dark:text-gray-200">1 hour:</strong> 3,600 seconds</div>
<div><strong className="text-gray-800 dark:text-gray-200">1 day:</strong> 86,400 seconds</div>
<div><strong className="text-gray-800 dark:text-gray-200">1 week:</strong> 604,800 seconds</div>
<div><strong className="text-gray-800 dark:text-gray-200">30 days:</strong> 2,592,000 seconds</div>
<div><strong className="text-gray-800 dark:text-gray-200">365 days:</strong> 31,536,000 seconds</div>
<div className="mt-4 pt-4 border-t border-[#1a1a1a] flex flex-wrap gap-x-6 gap-y-2 text-xs text-gray-600">
<div><strong className="text-gray-300">1 hour:</strong> 3,600 seconds</div>
<div><strong className="text-gray-300">1 day:</strong> 86,400 seconds</div>
<div><strong className="text-gray-300">1 week:</strong> 604,800 seconds</div>
<div><strong className="text-gray-300">30 days:</strong> 2,592,000 seconds</div>
<div><strong className="text-gray-300">365 days:</strong> 31,536,000 seconds</div>
</div>
</div>
</div>
@ -727,7 +727,7 @@ function FormatResultsGrid({ date, nowDate, copiedKey, onCopy, prefix }: FormatR
return (
<div className="space-y-4">
<h3 className="text-sm font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<h3 className="text-sm font-bold uppercase tracking-wider text-gray-400">
Converted Formats
</h3>
@ -739,23 +739,23 @@ function FormatResultsGrid({ date, nowDate, copiedKey, onCopy, prefix }: FormatR
return (
<div
key={item.id}
className={`group flex flex-col justify-between rounded-xl border p-4 transition-all ${
className={`group flex flex-col justify-between border p-4 transition-all ${
item.highlight
? "border-blue-200 bg-blue-50/50 dark:border-blue-900/40 dark:bg-blue-950/20"
: "border-gray-200 bg-white dark:border-gray-800 dark:bg-gray-800/80"
? "border-blue-900 bg-[#0a0a1a]/50"
: "border-[#1a1a1a] bg-[#0a0a0a]"
}`}
>
<div className="flex items-center justify-between mb-2">
<span className="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
<span className="text-xs font-semibold uppercase tracking-wider text-gray-500">
{item.label}
</span>
<button
type="button"
onClick={() => onCopy(item.value, key)}
className={`flex items-center gap-1 text-xs font-medium px-2 py-1 rounded transition-colors ${
className={`flex items-center gap-1 text-xs font-medium px-2 py-1 transition-colors ${
isCopied
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300"
: "bg-gray-100 hover:bg-gray-200 text-gray-700 dark:bg-gray-700 dark:hover:bg-gray-600 dark:text-gray-300"
? "bg-emerald-100 text-emerald-700"
: "bg-[#111] hover:bg-[#1a1a1a] text-gray-400"
}`}
title="Copy to clipboard"
>
@ -782,7 +782,7 @@ function FormatResultsGrid({ date, nowDate, copiedKey, onCopy, prefix }: FormatR
</button>
</div>
<div className="font-mono text-sm font-medium text-gray-900 dark:text-white break-all">
<div className="font-mono text-sm font-medium text-gray-200 break-all">
{item.value}
</div>
</div>
@ -792,14 +792,14 @@ function FormatResultsGrid({ date, nowDate, copiedKey, onCopy, prefix }: FormatR
{/* Date metadata badges */}
<div className="flex flex-wrap gap-2 pt-2 text-xs">
<span className="inline-flex items-center rounded-md bg-gray-100 px-2.5 py-1 font-medium text-gray-800 dark:bg-gray-800 dark:text-gray-300">
Day of Year: <strong className="ml-1 text-gray-900 dark:text-white">{dayOfYear}</strong>
<span className="inline-flex items-center bg-[#111] px-2.5 py-1 font-medium text-gray-300">
Day of Year: <strong className="ml-1 text-gray-200">{dayOfYear}</strong>
</span>
<span className="inline-flex items-center rounded-md bg-gray-100 px-2.5 py-1 font-medium text-gray-800 dark:bg-gray-800 dark:text-gray-300">
ISO Week: <strong className="ml-1 text-gray-900 dark:text-white">#{weekNum}</strong>
<span className="inline-flex items-center bg-[#111] px-2.5 py-1 font-medium text-gray-300">
ISO Week: <strong className="ml-1 text-gray-200">#{weekNum}</strong>
</span>
<span className="inline-flex items-center rounded-md bg-gray-100 px-2.5 py-1 font-medium text-gray-800 dark:bg-gray-800 dark:text-gray-300">
Leap Year: <strong className="ml-1 text-gray-900 dark:text-white">{leap ? "Yes" : "No"}</strong>
<span className="inline-flex items-center bg-[#111] px-2.5 py-1 font-medium text-gray-300">
Leap Year: <strong className="ml-1 text-gray-200">{leap ? "Yes" : "No"}</strong>
</span>
</div>
</div>

View file

@ -144,12 +144,12 @@ export default function UrlEncoderPage() {
<div className="space-y-6">
{/* Controls Bar */}
<div className="flex flex-wrap items-center justify-between gap-3 pb-2 border-b border-gray-200 dark:border-gray-700">
<div className="flex flex-wrap items-center justify-between gap-3 pb-2 border-b border-[#1a1a1a]">
<div className="flex flex-wrap items-center gap-2">
<button
type="button"
onClick={handleEncode}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 active:bg-blue-800 text-white rounded-lg font-medium text-sm transition-colors shadow-sm cursor-pointer flex items-center gap-1.5"
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 active:bg-blue-800 text-white font-medium text-sm transition-colors cursor-pointer flex items-center gap-1.5"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
@ -159,7 +159,7 @@ export default function UrlEncoderPage() {
<button
type="button"
onClick={handleDecode}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800 text-white rounded-lg font-medium text-sm transition-colors shadow-sm cursor-pointer flex items-center gap-1.5"
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800 text-white font-medium text-sm transition-colors cursor-pointer flex items-center gap-1.5"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
@ -170,7 +170,7 @@ export default function UrlEncoderPage() {
type="button"
onClick={handleSwap}
disabled={!output && !input}
className="px-3 py-2 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed text-gray-700 dark:text-gray-200 rounded-lg text-sm font-medium transition-colors cursor-pointer flex items-center gap-1"
className="px-3 py-2 bg-[#111] hover:bg-[#1a1a1a] disabled:opacity-40 disabled:cursor-not-allowed text-gray-400 text-sm font-medium transition-colors cursor-pointer flex items-center gap-1"
title="Swap input and output"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -182,7 +182,7 @@ export default function UrlEncoderPage() {
type="button"
onClick={handleClear}
disabled={!input && !output}
className="px-3 py-2 bg-gray-100 dark:bg-gray-800 hover:bg-red-50 dark:hover:bg-red-950/30 text-gray-700 dark:text-gray-300 hover:text-red-600 dark:hover:text-red-400 disabled:opacity-40 disabled:cursor-not-allowed rounded-lg text-sm font-medium transition-colors cursor-pointer"
className="px-3 py-2 bg-[#111] hover:bg-[#1a0a0a] text-gray-400 hover:text-red-400 disabled:opacity-40 disabled:cursor-not-allowed text-sm font-medium transition-colors cursor-pointer"
>
Clear
</button>
@ -190,25 +190,25 @@ export default function UrlEncoderPage() {
{/* Sample buttons */}
<div className="flex items-center gap-2 text-xs">
<span className="text-gray-500 dark:text-gray-400 font-medium">Samples:</span>
<span className="text-gray-500 font-medium">Samples:</span>
<button
type="button"
onClick={() => loadSample("param")}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 rounded transition-colors cursor-pointer"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] text-gray-400 transition-colors cursor-pointer"
>
Query Param
</button>
<button
type="button"
onClick={() => loadSample("fullUrl")}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 rounded transition-colors cursor-pointer"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] text-gray-400 transition-colors cursor-pointer"
>
Full URL
</button>
<button
type="button"
onClick={() => loadSample("encoded")}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 rounded transition-colors cursor-pointer"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] text-gray-400 transition-colors cursor-pointer"
>
Encoded URI
</button>
@ -216,9 +216,9 @@ export default function UrlEncoderPage() {
</div>
{/* Encoding Options */}
<div className="flex flex-wrap items-center gap-6 text-sm text-gray-700 dark:text-gray-300 bg-gray-50 dark:bg-gray-800/50 p-3 rounded-lg border border-gray-200 dark:border-gray-700/60">
<div className="flex flex-wrap items-center gap-6 text-sm text-gray-400 bg-black p-3 border border-[#1a1a1a]">
<div className="flex items-center gap-3">
<span className="font-medium text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider">Encode Mode:</span>
<span className="font-medium text-xs text-gray-500 uppercase tracking-wider">Encode Mode:</span>
<label className="inline-flex items-center gap-1.5 cursor-pointer">
<input
type="radio"
@ -231,7 +231,7 @@ export default function UrlEncoderPage() {
encodeUrl(input, "component", spaceAsPlus);
}
}}
className="w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-4 h-4 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>encodeURIComponent <span className="text-xs text-gray-500">(for query params &amp; values)</span></span>
</label>
@ -247,7 +247,7 @@ export default function UrlEncoderPage() {
encodeUrl(input, "fullUrl", spaceAsPlus);
}
}}
className="w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-4 h-4 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>encodeURI <span className="text-xs text-gray-500">(preserves full URL structure)</span></span>
</label>
@ -265,9 +265,9 @@ export default function UrlEncoderPage() {
encodeUrl(input, encodeMode, val);
}
}}
className="w-4 h-4 text-blue-600 rounded border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-4 h-4 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>Encode spaces as <code className="text-xs bg-gray-200 dark:bg-gray-700 px-1 py-0.5 rounded font-mono">+</code></span>
<span>Encode spaces as <code className="text-xs bg-[#1a1a1a] px-1 py-0.5 font-mono">+</code></span>
</label>
<label className="inline-flex items-center gap-2 cursor-pointer select-none">
@ -281,16 +281,16 @@ export default function UrlEncoderPage() {
decodeUrl(input, val);
}
}}
className="w-4 h-4 text-blue-600 rounded border-gray-300 dark:border-gray-600 focus:ring-blue-500"
className="w-4 h-4 text-blue-600 border-[#1a1a1a] focus:ring-blue-900"
/>
<span>Decode <code className="text-xs bg-gray-200 dark:bg-gray-700 px-1 py-0.5 rounded font-mono">+</code> as spaces</span>
<span>Decode <code className="text-xs bg-[#1a1a1a] px-1 py-0.5 font-mono">+</code> as spaces</span>
</label>
</div>
</div>
{/* Error Notification */}
{error && (
<div className="flex items-start gap-3 p-4 bg-red-50 dark:bg-red-950/40 border border-red-200 dark:border-red-800 rounded-lg text-red-800 dark:text-red-300 text-sm">
<div className="flex items-start gap-3 p-4 bg-[#1a0a0a] border border-red-900 text-red-400 text-sm">
<svg className="w-5 h-5 text-red-500 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
@ -306,10 +306,10 @@ export default function UrlEncoderPage() {
{/* Input Area */}
<div className="flex flex-col space-y-2">
<div className="flex items-center justify-between">
<label htmlFor={inputId} className="font-medium text-sm text-gray-700 dark:text-gray-300">
<label htmlFor={inputId} className="font-medium text-sm text-gray-400">
Input Text or URL
</label>
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
<span className="text-xs text-gray-500 font-mono">
{input.length} chars
</span>
</div>
@ -322,7 +322,7 @@ export default function UrlEncoderPage() {
}}
placeholder="Enter text or URL to encode or decode..."
rows={12}
className="w-full p-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none resize-y placeholder:text-gray-400 dark:placeholder:text-gray-500 leading-relaxed shadow-inner"
className="w-full p-3 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-mono text-sm focus:ring-1 focus:ring-blue-900 focus:outline-none resize-y placeholder:text-gray-400 leading-relaxed shadow-inner"
/>
</div>
@ -330,31 +330,31 @@ export default function UrlEncoderPage() {
<div className="flex flex-col space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<label htmlFor={outputId} className="font-medium text-sm text-gray-700 dark:text-gray-300">
<label htmlFor={outputId} className="font-medium text-sm text-gray-400">
Output Result
</label>
{lastAction && (
<span className="px-2 py-0.5 text-xs rounded-full font-medium bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300">
<span className="px-2 py-0.5 text-xs rounded-none font-medium bg-[#0a0a1a] text-blue-400">
{lastAction === "encode" ? "URL Encoded" : "URL Decoded"}
</span>
)}
</div>
<div className="flex items-center gap-3">
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
<span className="text-xs text-gray-500 font-mono">
{output.length} chars
</span>
<button
type="button"
onClick={handleCopy}
disabled={!output}
className="px-2.5 py-1 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300 rounded text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
className="px-2.5 py-1 bg-[#111] hover:bg-[#1a1a1a] disabled:opacity-40 disabled:cursor-not-allowed text-gray-400 text-xs font-medium transition-colors cursor-pointer flex items-center gap-1"
>
{copied ? (
<>
<svg className="w-3.5 h-3.5 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="w-3.5 h-3.5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
<span className="text-green-600 dark:text-green-400 font-semibold">Copied!</span>
<span className="text-green-400 font-semibold">Copied!</span>
</>
) : (
<>
@ -373,38 +373,38 @@ export default function UrlEncoderPage() {
readOnly
placeholder="Encoded or decoded output will appear here..."
rows={12}
className="w-full p-3 rounded-lg border border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-900/60 text-gray-900 dark:text-gray-100 font-mono text-sm focus:outline-none resize-y placeholder:text-gray-400 dark:placeholder:text-gray-500 leading-relaxed select-all"
className="w-full p-3 border border-[#1a1a1a] bg-black text-gray-200 font-mono text-sm focus:outline-none resize-y placeholder:text-gray-400 leading-relaxed select-all"
/>
</div>
</div>
{/* Common Reference Table */}
<div className="mt-8 pt-6 border-t border-gray-200 dark:border-gray-800">
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-3">Common URL Percent-Encoding Reference</h3>
<div className="mt-8 pt-6 border-t border-[#1a1a1a]">
<h3 className="text-sm font-semibold text-gray-200 mb-3">Common URL Percent-Encoding Reference</h3>
<div className="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-6 gap-2 text-xs font-mono">
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Space:</span>
<span className="font-semibold text-blue-600 dark:text-blue-400">%20 or +</span>
<div className="p-2 bg-black border border-[#1a1a1a] flex justify-between">
<span className="text-gray-600">Space:</span>
<span className="font-semibold text-blue-600">%20 or +</span>
</div>
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 flex justify-between">
<span className="text-gray-600 dark:text-gray-400">/ (slash):</span>
<span className="font-semibold text-blue-600 dark:text-blue-400">%2F</span>
<div className="p-2 bg-black border border-[#1a1a1a] flex justify-between">
<span className="text-gray-600">/ (slash):</span>
<span className="font-semibold text-blue-600">%2F</span>
</div>
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 flex justify-between">
<span className="text-gray-600 dark:text-gray-400">: (colon):</span>
<span className="font-semibold text-blue-600 dark:text-blue-400">%3A</span>
<div className="p-2 bg-black border border-[#1a1a1a] flex justify-between">
<span className="text-gray-600">: (colon):</span>
<span className="font-semibold text-blue-600">%3A</span>
</div>
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 flex justify-between">
<span className="text-gray-600 dark:text-gray-400">&amp; (ampersand):</span>
<span className="font-semibold text-blue-600 dark:text-blue-400">%26</span>
<div className="p-2 bg-black border border-[#1a1a1a] flex justify-between">
<span className="text-gray-600">&amp; (ampersand):</span>
<span className="font-semibold text-blue-600">%26</span>
</div>
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 flex justify-between">
<span className="text-gray-600 dark:text-gray-400">? (question):</span>
<span className="font-semibold text-blue-600 dark:text-blue-400">%3F</span>
<div className="p-2 bg-black border border-[#1a1a1a] flex justify-between">
<span className="text-gray-600">? (question):</span>
<span className="font-semibold text-blue-600">%3F</span>
</div>
<div className="p-2 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 flex justify-between">
<span className="text-gray-600 dark:text-gray-400">= (equal):</span>
<span className="font-semibold text-blue-600 dark:text-blue-400">%3D</span>
<div className="p-2 bg-black border border-[#1a1a1a] flex justify-between">
<span className="text-gray-600">= (equal):</span>
<span className="font-semibold text-blue-600">%3D</span>
</div>
</div>
</div>

View file

@ -131,11 +131,11 @@ export default function UuidGeneratorPage() {
>
<div className="space-y-6">
{/* Controls Bar */}
<div className="rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-700/60 dark:bg-gray-800/40">
<div className=" border border-[#1a1a1a] bg-black/50 p-4">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{/* Quantity Selector */}
<div>
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-gray-400">
Quantity (1 - 100)
</label>
<div className="flex items-center gap-2">
@ -145,7 +145,7 @@ export default function UuidGeneratorPage() {
max="100"
value={count}
onChange={(e) => handleCountChange(parseInt(e.target.value) || 1)}
className="w-24 rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
className="w-24 border border-[#1a1a1a] bg-[#0a0a0a] px-3 py-2 text-sm font-medium text-gray-200 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900/20"
/>
<div className="flex flex-wrap gap-1">
{countPresets.map((preset) => (
@ -156,7 +156,7 @@ export default function UuidGeneratorPage() {
className={`rounded px-2 py-1 text-xs font-medium transition-colors ${
count === preset
? "bg-blue-600 text-white"
: "bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
: "bg-[#1a1a1a] text-gray-400 hover:bg-[#222]"
}`}
>
{preset}
@ -168,25 +168,25 @@ export default function UuidGeneratorPage() {
{/* Options Toggles */}
<div>
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-300">
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-gray-400">
Formatting Options
</label>
<div className="flex flex-wrap gap-4 pt-1">
<label className="inline-flex cursor-pointer items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
<label className="inline-flex cursor-pointer items-center gap-2 text-sm text-gray-400">
<input
type="checkbox"
checked={uppercase}
onChange={handleUppercaseToggle}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>Uppercase (A-F)</span>
</label>
<label className="inline-flex cursor-pointer items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
<label className="inline-flex cursor-pointer items-center gap-2 text-sm text-gray-400">
<input
type="checkbox"
checked={hyphens}
onChange={handleHyphensToggle}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>Include Hyphens</span>
</label>
@ -198,7 +198,7 @@ export default function UuidGeneratorPage() {
<button
type="button"
onClick={handleGenerate}
className="flex w-full items-center justify-center gap-2 rounded-lg bg-blue-600 px-5 py-2.5 font-medium text-white shadow-sm transition-all hover:bg-blue-700 active:scale-[0.99] focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
className="flex w-full items-center justify-center gap-2 bg-blue-600 px-5 py-2.5 font-medium text-white transition-all hover:bg-blue-700 active:scale-[0.99] focus:outline-none focus:ring-1 focus:ring-blue-900 focus:ring-offset-2"
>
<svg
className="h-4 w-4"
@ -220,12 +220,12 @@ export default function UuidGeneratorPage() {
</div>
{/* Results Header / Bulk Actions */}
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-gray-200 pb-3 dark:border-gray-700">
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-[#1a1a1a] pb-3">
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
<span className="text-sm font-medium text-gray-400">
Generated UUIDs
</span>
<span className="inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-semibold text-blue-800 dark:bg-blue-900/40 dark:text-blue-300">
<span className="inline-flex items-center rounded-none bg-[#0a0a1a] px-2.5 py-0.5 text-xs font-semibold text-blue-400">
{uuids.length}
</span>
</div>
@ -234,10 +234,10 @@ export default function UuidGeneratorPage() {
<button
type="button"
onClick={copyAllToClipboard}
className={`inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors ${
className={`inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium transition-colors ${
copiedAll
? "bg-green-600 text-white"
: "bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700"
: "bg-[#111] text-gray-400 hover:bg-[#1a1a1a]"
}`}
>
{copiedAll ? (
@ -266,7 +266,7 @@ export default function UuidGeneratorPage() {
type="button"
onClick={downloadAsText}
title="Download as .txt"
className="inline-flex items-center gap-1.5 rounded-lg bg-gray-100 px-3 py-1.5 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700"
className="inline-flex items-center gap-1.5 bg-[#111] px-3 py-1.5 text-xs font-medium text-gray-400 transition-colors hover:bg-[#1a1a1a]"
>
<svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
@ -283,7 +283,7 @@ export default function UuidGeneratorPage() {
type="button"
onClick={downloadAsJson}
title="Download as .json"
className="inline-flex items-center gap-1.5 rounded-lg bg-gray-100 px-3 py-1.5 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700"
className="inline-flex items-center gap-1.5 bg-[#111] px-3 py-1.5 text-xs font-medium text-gray-400 transition-colors hover:bg-[#1a1a1a]"
>
<svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
@ -300,19 +300,19 @@ export default function UuidGeneratorPage() {
{/* Featured Single UUID Card when count is 1 */}
{uuids.length === 1 && (
<div className="flex flex-col items-center justify-between gap-4 rounded-xl border border-blue-200 bg-blue-50/50 p-6 dark:border-blue-900/50 dark:bg-blue-950/20 sm:flex-row">
<div className="flex flex-col items-center justify-between gap-4 border border-blue-900 bg-[#0a0a1a]/50 p-6 sm:flex-row">
<div className="flex-1 overflow-hidden">
<span className="mb-1 block text-xs font-semibold uppercase tracking-wider text-blue-700 dark:text-blue-400">
<span className="mb-1 block text-xs font-semibold uppercase tracking-wider text-blue-400">
Generated UUID v4
</span>
<p className="select-all break-all font-mono text-lg font-bold text-gray-900 dark:text-white sm:text-2xl">
<p className="select-all break-all font-mono text-lg font-bold text-gray-200 sm:text-2xl">
{uuids[0]}
</p>
</div>
<button
type="button"
onClick={() => copyToClipboard(uuids[0], 0)}
className={`flex items-center gap-2 rounded-lg px-5 py-2.5 text-sm font-medium transition-all ${
className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium transition-all ${
copiedIndex === 0
? "bg-green-600 text-white"
: "bg-blue-600 text-white hover:bg-blue-700 active:scale-95"
@ -344,18 +344,18 @@ export default function UuidGeneratorPage() {
{/* UUID List for multiple */}
{uuids.length > 1 && (
<div className="max-h-[500px] overflow-y-auto rounded-lg border border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-900/60">
<div className="divide-y divide-gray-200 dark:divide-gray-800">
<div className="max-h-[500px] overflow-y-auto border border-[#1a1a1a] bg-black">
<div className="divide-y divide-[#1a1a1a]">
{uuids.map((uuid, idx) => (
<div
key={`${uuid}-${idx}`}
className="group flex items-center justify-between gap-4 p-3 transition-colors hover:bg-white dark:hover:bg-gray-800/80"
className="group flex items-center justify-between gap-4 p-3 transition-colors hover:bg-[#0a0a0a]"
>
<div className="flex min-w-0 items-center gap-3">
<span className="w-8 select-none text-right font-mono text-xs text-gray-400 dark:text-gray-500">
<span className="w-8 select-none text-right font-mono text-xs text-gray-400">
{idx + 1}.
</span>
<span className="select-all truncate font-mono text-sm font-medium text-gray-900 dark:text-gray-100">
<span className="select-all truncate font-mono text-sm font-medium text-gray-200">
{uuid}
</span>
</div>
@ -363,10 +363,10 @@ export default function UuidGeneratorPage() {
<button
type="button"
onClick={() => copyToClipboard(uuid, idx)}
className={`flex shrink-0 items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${
className={`flex shrink-0 items-center gap-1 px-2.5 py-1 text-xs font-medium transition-colors ${
copiedIndex === idx
? "bg-green-600 text-white"
: "bg-white text-gray-700 opacity-90 shadow-sm hover:bg-gray-100 group-hover:opacity-100 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
: "bg-[#0a0a0a] text-gray-400 opacity-90 hover:bg-[#111] group-hover:opacity-100"
}`}
>
{copiedIndex === idx ? (
@ -402,21 +402,21 @@ export default function UuidGeneratorPage() {
)}
{/* Informational Details */}
<div className="grid grid-cols-1 gap-4 pt-4 text-xs text-gray-500 dark:text-gray-400 sm:grid-cols-3">
<div className="rounded-lg border border-gray-100 bg-gray-50 p-3 dark:border-gray-800 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200">128-bit Randomness</h4>
<div className="grid grid-cols-1 gap-4 pt-4 text-xs text-gray-500 sm:grid-cols-3">
<div className=" border border-gray-100 bg-black p-3">
<h4 className="font-semibold text-gray-300">128-bit Randomness</h4>
<p className="mt-1">
UUID v4 utilizes 122 cryptographically secure random bits, making collisions statistically virtually impossible.
</p>
</div>
<div className="rounded-lg border border-gray-100 bg-gray-50 p-3 dark:border-gray-800 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200">100% Client-Side</h4>
<div className=" border border-gray-100 bg-black p-3">
<h4 className="font-semibold text-gray-300">100% Client-Side</h4>
<p className="mt-1">
All identifiers are created locally in your browser using the Web Crypto API. No server logs or tracking.
</p>
</div>
<div className="rounded-lg border border-gray-100 bg-gray-50 p-3 dark:border-gray-800 dark:bg-gray-800/40">
<h4 className="font-semibold text-gray-800 dark:text-gray-200">RFC 4122 Compliant</h4>
<div className=" border border-gray-100 bg-black p-3">
<h4 className="font-semibold text-gray-300">RFC 4122 Compliant</h4>
<p className="mt-1">
Generated UUIDs adhere strictly to RFC 4122 specifications with standard version 4 and variant bits.
</p>

View file

@ -159,56 +159,56 @@ export default function WordCounterPage() {
<div className="space-y-6">
{/* Primary Stats Grid */}
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-6">
<div className="rounded-xl border border-blue-100 bg-blue-50/60 p-4 text-center dark:border-blue-900/40 dark:bg-blue-950/30">
<span className="text-xs font-semibold uppercase tracking-wider text-blue-600 dark:text-blue-400">
<div className=" border border-blue-100 bg-[#0a0a1a]/60 p-4 text-center">
<span className="text-xs font-semibold uppercase tracking-wider text-blue-600">
Words
</span>
<p className="mt-1 text-2xl font-extrabold text-blue-950 dark:text-blue-100">
<p className="mt-1 text-2xl font-extrabold text-blue-950">
{stats.wordCount.toLocaleString()}
</p>
</div>
<div className="rounded-xl border border-indigo-100 bg-indigo-50/60 p-4 text-center dark:border-indigo-900/40 dark:bg-indigo-950/30">
<span className="text-xs font-semibold uppercase tracking-wider text-indigo-600 dark:text-indigo-400">
<div className=" border border-indigo-100 bg-indigo-50/60 p-4 text-center">
<span className="text-xs font-semibold uppercase tracking-wider text-indigo-600">
Characters
</span>
<p className="mt-1 text-2xl font-extrabold text-indigo-950 dark:text-indigo-100">
<p className="mt-1 text-2xl font-extrabold text-indigo-950">
{stats.charCountWithSpaces.toLocaleString()}
</p>
</div>
<div className="rounded-xl border border-purple-100 bg-purple-50/60 p-4 text-center dark:border-purple-900/40 dark:bg-purple-950/30">
<span className="text-xs font-semibold uppercase tracking-wider text-purple-600 dark:text-purple-400">
<div className=" border border-purple-100 bg-purple-50/60 p-4 text-center">
<span className="text-xs font-semibold uppercase tracking-wider text-purple-600">
No Spaces
</span>
<p className="mt-1 text-2xl font-extrabold text-purple-950 dark:text-purple-100">
<p className="mt-1 text-2xl font-extrabold text-purple-950">
{stats.charCountWithoutSpaces.toLocaleString()}
</p>
</div>
<div className="rounded-xl border border-teal-100 bg-teal-50/60 p-4 text-center dark:border-teal-900/40 dark:bg-teal-950/30">
<span className="text-xs font-semibold uppercase tracking-wider text-teal-600 dark:text-teal-400">
<div className=" border border-teal-100 bg-teal-50/60 p-4 text-center">
<span className="text-xs font-semibold uppercase tracking-wider text-teal-600">
Sentences
</span>
<p className="mt-1 text-2xl font-extrabold text-teal-950 dark:text-teal-100">
<p className="mt-1 text-2xl font-extrabold text-teal-950">
{stats.sentenceCount.toLocaleString()}
</p>
</div>
<div className="rounded-xl border border-amber-100 bg-amber-50/60 p-4 text-center dark:border-amber-900/40 dark:bg-amber-950/30">
<span className="text-xs font-semibold uppercase tracking-wider text-amber-600 dark:text-amber-400">
<div className=" border border-amber-100 bg-amber-50/60 p-4 text-center">
<span className="text-xs font-semibold uppercase tracking-wider text-amber-600">
Paragraphs
</span>
<p className="mt-1 text-2xl font-extrabold text-amber-950 dark:text-amber-100">
<p className="mt-1 text-2xl font-extrabold text-amber-950">
{stats.paragraphCount.toLocaleString()}
</p>
</div>
<div className="rounded-xl border border-emerald-100 bg-emerald-50/60 p-4 text-center dark:border-emerald-900/40 dark:bg-emerald-950/30">
<span className="text-xs font-semibold uppercase tracking-wider text-emerald-600 dark:text-emerald-400">
<div className=" border border-emerald-100 bg-emerald-50/60 p-4 text-center">
<span className="text-xs font-semibold uppercase tracking-wider text-emerald-600">
Reading Time
</span>
<p className="mt-1 text-2xl font-extrabold text-emerald-950 dark:text-emerald-100">
<p className="mt-1 text-2xl font-extrabold text-emerald-950">
{stats.readingTimeStr}
</p>
</div>
@ -219,7 +219,7 @@ export default function WordCounterPage() {
<div className="flex flex-wrap items-center justify-between gap-2">
<label
htmlFor="word-counter-input"
className="text-sm font-semibold text-gray-700 dark:text-gray-300"
className="text-sm font-semibold text-gray-400"
>
Enter or Paste Your Text
</label>
@ -227,7 +227,7 @@ export default function WordCounterPage() {
<button
type="button"
onClick={handleLoadSample}
className="px-3 py-1.5 text-xs font-medium text-blue-600 hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-blue-950/50 rounded-md transition-colors cursor-pointer"
className="px-3 py-1.5 text-xs font-medium text-blue-600 hover:bg-[#0a0a1a] transition-colors cursor-pointer"
>
Load Sample
</button>
@ -235,7 +235,7 @@ export default function WordCounterPage() {
type="button"
onClick={handleCleanWhitespace}
disabled={!text}
className="px-3 py-1.5 text-xs font-medium text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800 rounded-md disabled:opacity-40 transition-colors cursor-pointer"
className="px-3 py-1.5 text-xs font-medium text-gray-600 hover:bg-[#111] disabled:opacity-40 transition-colors cursor-pointer"
>
Clean Spaces
</button>
@ -243,7 +243,7 @@ export default function WordCounterPage() {
type="button"
onClick={handleClear}
disabled={!text}
className="px-3 py-1.5 text-xs font-medium text-rose-600 hover:bg-rose-50 dark:text-rose-400 dark:hover:bg-rose-950/50 rounded-md disabled:opacity-40 transition-colors cursor-pointer"
className="px-3 py-1.5 text-xs font-medium text-rose-600 hover:bg-rose-50 disabled:opacity-40 transition-colors cursor-pointer"
>
Clear
</button>
@ -251,7 +251,7 @@ export default function WordCounterPage() {
type="button"
onClick={handleCopy}
disabled={!text}
className="px-3 py-1.5 text-xs font-medium bg-blue-600 hover:bg-blue-700 text-white rounded-md disabled:opacity-40 transition-colors cursor-pointer flex items-center gap-1"
className="px-3 py-1.5 text-xs font-medium bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-40 transition-colors cursor-pointer flex items-center gap-1"
>
{copied ? "Copied!" : "Copy Text"}
</button>
@ -264,46 +264,46 @@ export default function WordCounterPage() {
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Type or paste your text here to get instant word count, character statistics, sentence counts, and reading time..."
className="w-full p-3.5 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-sans text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 focus:outline-none transition-shadow resize-y shadow-inner leading-relaxed"
className="w-full p-3.5 border border-[#1a1a1a] bg-[#0a0a0a] text-gray-200 font-sans text-sm focus:ring-1 focus:ring-blue-900 focus:border-blue-500 focus:outline-none transition-shadow resize-y shadow-inner leading-relaxed"
/>
</div>
{/* Secondary Detailed Stats */}
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
<div className="rounded-lg border border-gray-200 bg-gray-50/70 p-3.5 dark:border-gray-800 dark:bg-gray-800/50">
<span className="text-xs text-gray-500 dark:text-gray-400">
<div className=" border border-[#1a1a1a] bg-black/70 p-3.5">
<span className="text-xs text-gray-500">
Average Word Length
</span>
<p className="mt-1 text-lg font-bold text-gray-800 dark:text-gray-200">
<p className="mt-1 text-lg font-bold text-gray-300">
{stats.avgWordLength}{" "}
<span className="text-xs font-normal text-gray-500">chars</span>
</p>
</div>
<div className="rounded-lg border border-gray-200 bg-gray-50/70 p-3.5 dark:border-gray-800 dark:bg-gray-800/50">
<span className="text-xs text-gray-500 dark:text-gray-400">
<div className=" border border-[#1a1a1a] bg-black/70 p-3.5">
<span className="text-xs text-gray-500">
Speaking Time (~130 wpm)
</span>
<p className="mt-1 text-lg font-bold text-gray-800 dark:text-gray-200">
<p className="mt-1 text-lg font-bold text-gray-300">
{stats.speakingTimeStr}
</p>
</div>
<div className="rounded-lg border border-gray-200 bg-gray-50/70 p-3.5 dark:border-gray-800 dark:bg-gray-800/50">
<span className="text-xs text-gray-500 dark:text-gray-400">
<div className=" border border-[#1a1a1a] bg-black/70 p-3.5">
<span className="text-xs text-gray-500">
Total Lines
</span>
<p className="mt-1 text-lg font-bold text-gray-800 dark:text-gray-200">
<p className="mt-1 text-lg font-bold text-gray-300">
{stats.lineCount.toLocaleString()}
</p>
</div>
<div className="rounded-lg border border-gray-200 bg-gray-50/70 p-3.5 dark:border-gray-800 dark:bg-gray-800/50">
<span className="text-xs text-gray-500 dark:text-gray-400">
<div className=" border border-[#1a1a1a] bg-black/70 p-3.5">
<span className="text-xs text-gray-500">
Longest Word
</span>
<p
className="mt-1 text-lg font-bold text-gray-800 dark:text-gray-200 truncate"
className="mt-1 text-lg font-bold text-gray-300 truncate"
title={stats.longestWord || "None"}
>
{stats.longestWord || "—"}{" "}
@ -318,27 +318,27 @@ export default function WordCounterPage() {
{/* Top Keywords / Frequency Section */}
{stats.topKeywords.length > 0 && (
<div className="rounded-xl border border-gray-200 bg-gray-50/40 p-4 dark:border-gray-800 dark:bg-gray-900/40">
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
<div className=" border border-[#1a1a1a] bg-black/40 p-4">
<h3 className="text-sm font-semibold text-gray-200">
Top Keywords & Frequency
</h3>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-3">
<p className="text-xs text-gray-500 mb-3">
Most frequently occurring terms in your text.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2.5">
{stats.topKeywords.map((kw, i) => (
<div
key={i}
className="flex items-center justify-between p-2.5 rounded-lg border border-gray-200/80 bg-white dark:border-gray-800 dark:bg-gray-800 text-xs shadow-xs"
className="flex items-center justify-between p-2.5 border border-[#1a1a1a]/80 bg-[#0a0a0a] text-xs shadow-xs"
>
<span className="font-mono font-medium text-gray-800 dark:text-gray-200 truncate mr-2">
<span className="font-mono font-medium text-gray-300 truncate mr-2">
{kw.word}
</span>
<div className="flex items-center gap-2 flex-shrink-0">
<span className="px-2 py-0.5 rounded-full bg-blue-100 text-blue-800 dark:bg-blue-950 dark:text-blue-300 font-semibold">
<span className="px-2 py-0.5 rounded-none bg-[#0a0a1a] text-blue-400 font-semibold">
{kw.count}×
</span>
<span className="text-gray-400 dark:text-gray-500">
<span className="text-gray-400">
{kw.percentage}%
</span>
</div>

View file

@ -396,11 +396,11 @@ export default function XmlFormatterPage() {
>
<div className="space-y-5">
{/* Top Controls Toolbar */}
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-gray-200 pb-4 dark:border-gray-800">
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-[#1a1a1a] pb-4">
<div className="flex flex-wrap items-center gap-2">
<button
onClick={handleFormat}
className="inline-flex items-center gap-1.5 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 active:scale-95 cursor-pointer"
className="inline-flex items-center gap-1.5 bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 active:scale-95 cursor-pointer"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h7" />
@ -410,7 +410,7 @@ export default function XmlFormatterPage() {
<button
onClick={handleMinify}
className="inline-flex items-center gap-1.5 rounded-lg bg-gray-200 px-4 py-2 text-sm font-medium text-gray-800 transition-colors hover:bg-gray-300 active:scale-95 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700 cursor-pointer"
className="inline-flex items-center gap-1.5 bg-[#1a1a1a] px-4 py-2 text-sm font-medium text-gray-300 transition-colors hover:bg-[#222] active:scale-95 cursor-pointer"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 14l-7 7m0 0l-7-7m7 7V3" />
@ -420,7 +420,7 @@ export default function XmlFormatterPage() {
<button
onClick={handleValidate}
className="inline-flex items-center gap-1.5 rounded-lg bg-emerald-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-emerald-700 active:scale-95 dark:bg-emerald-700 dark:hover:bg-emerald-600 cursor-pointer"
className="inline-flex items-center gap-1.5 bg-emerald-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-emerald-700 active:scale-95 cursor-pointer"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
@ -430,7 +430,7 @@ export default function XmlFormatterPage() {
<button
onClick={handleLoadSample}
className="inline-flex items-center gap-1.5 rounded-lg bg-blue-50 px-3 py-2 text-sm font-medium text-blue-700 transition-colors hover:bg-blue-100 dark:bg-blue-950/40 dark:text-blue-300 dark:hover:bg-blue-900/50 cursor-pointer"
className="inline-flex items-center gap-1.5 bg-[#0a0a1a] px-3 py-2 text-sm font-medium text-blue-400 transition-colors hover:bg-[#0a0a1a] cursor-pointer"
>
Load Sample
</button>
@ -438,24 +438,24 @@ export default function XmlFormatterPage() {
<button
onClick={handleClear}
disabled={!input && !output}
className="inline-flex items-center gap-1.5 rounded-lg bg-gray-100 px-3 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-rose-50 hover:text-rose-600 disabled:opacity-40 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-rose-950/30 dark:hover:text-rose-400 cursor-pointer"
className="inline-flex items-center gap-1.5 bg-[#111] px-3 py-2 text-sm font-medium text-gray-400 transition-colors hover:bg-rose-50 hover:text-rose-600 disabled:opacity-40 cursor-pointer"
>
Clear
</button>
</div>
{/* Options */}
<div className="flex flex-wrap items-center gap-3 text-sm text-gray-700 dark:text-gray-300">
<div className="flex flex-wrap items-center gap-3 text-sm text-gray-400">
{/* Indent Selector */}
<div className="flex items-center gap-1.5">
<label htmlFor={indentSelectId} className="text-xs font-semibold text-gray-500 uppercase dark:text-gray-400">
<label htmlFor={indentSelectId} className="text-xs font-semibold text-gray-500 uppercase">
Indent:
</label>
<select
id={indentSelectId}
value={indentStr}
onChange={(e) => setIndentStr(e.target.value)}
className="rounded-md border border-gray-300 bg-white px-2 py-1 text-xs text-gray-800 shadow-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
className=" border border-[#1a1a1a] bg-[#0a0a0a] px-2 py-1 text-xs text-gray-300 shadow-xs focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-900"
>
<option value=" ">2 Spaces</option>
<option value=" ">4 Spaces</option>
@ -464,12 +464,12 @@ export default function XmlFormatterPage() {
</div>
{/* Collapse Empty Tags */}
<label className="flex cursor-pointer items-center gap-1.5 text-xs text-gray-600 select-none hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100">
<label className="flex cursor-pointer items-center gap-1.5 text-xs text-gray-600 select-none hover:text-gray-200">
<input
type="checkbox"
checked={collapseEmpty}
onChange={(e) => setCollapseEmpty(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800"
className="h-4 w-4 border-[#1a1a1a] text-blue-600 focus:ring-blue-900"
/>
<span>Self-close empty tags (<code>&lt;tag /&gt;</code>)</span>
</label>
@ -478,9 +478,9 @@ export default function XmlFormatterPage() {
{/* Status Notification Banner */}
{status.type === "success" && (
<div className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-emerald-300 bg-emerald-50 px-4 py-3 text-sm text-emerald-900 dark:border-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-300">
<div className="flex flex-wrap items-center justify-between gap-2 border border-emerald-300 bg-emerald-50 px-4 py-3 text-sm text-emerald-900">
<div className="flex items-center gap-2">
<svg className="h-5 w-5 shrink-0 text-emerald-600 dark:text-emerald-400" fill="currentColor" viewBox="0 0 20 20">
<svg className="h-5 w-5 shrink-0 text-emerald-600" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
@ -489,7 +489,7 @@ export default function XmlFormatterPage() {
</svg>
<span className="font-medium">{status.message}</span>
</div>
<div className="flex flex-wrap items-center gap-3 text-xs text-emerald-800 dark:text-emerald-300">
<div className="flex flex-wrap items-center gap-3 text-xs text-emerald-800">
{status.lineCount !== undefined && <span>Lines: <strong>{status.lineCount}</strong></span>}
{status.byteSize !== undefined && <span>Size: <strong>{formatBytes(status.byteSize)}</strong></span>}
</div>
@ -497,9 +497,9 @@ export default function XmlFormatterPage() {
)}
{status.type === "error" && (
<div className="rounded-lg border border-red-300 bg-red-50 p-4 text-sm text-red-900 dark:border-red-800 dark:bg-red-950/40 dark:text-red-300">
<div className=" border border-red-900 bg-[#1a0a0a] p-4 text-sm text-red-900">
<div className="flex items-start gap-2">
<svg className="mt-0.5 h-5 w-5 shrink-0 text-red-600 dark:text-red-400" fill="currentColor" viewBox="0 0 20 20">
<svg className="mt-0.5 h-5 w-5 shrink-0 text-red-400" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
@ -507,10 +507,10 @@ export default function XmlFormatterPage() {
/>
</svg>
<div className="flex-1">
<p className="font-semibold text-red-800 dark:text-red-200">XML Parsing Error</p>
<p className="mt-1 font-mono text-xs text-red-700 dark:text-red-300">{status.message}</p>
<p className="font-semibold text-red-400">XML Parsing Error</p>
<p className="mt-1 font-mono text-xs text-red-400">{status.message}</p>
{(status.line !== undefined || status.column !== undefined) && (
<div className="mt-2 inline-flex items-center gap-2 rounded bg-red-100 px-2.5 py-1 text-xs font-medium text-red-800 dark:bg-red-900/60 dark:text-red-200">
<div className="mt-2 inline-flex items-center gap-2 bg-[#1a0a0a] px-2.5 py-1 text-xs font-medium text-red-400">
<span>📍 Error location:</span>
{status.line !== undefined && <span>Line <strong>{status.line}</strong></span>}
{status.column !== undefined && <span>Column <strong>{status.column}</strong></span>}
@ -527,8 +527,8 @@ export default function XmlFormatterPage() {
<div className="flex flex-col">
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-800 dark:text-gray-200">Input XML</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
<span className="text-sm font-semibold text-gray-300">Input XML</span>
<span className="text-xs text-gray-500">
({inputLineCount} {inputLineCount === 1 ? "line" : "lines"}, {input.length} chars)
</span>
</div>
@ -542,14 +542,14 @@ export default function XmlFormatterPage() {
// Permission denied
}
}}
className="rounded px-2 py-1 text-xs font-medium text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800 cursor-pointer"
className="rounded px-2 py-1 text-xs font-medium text-gray-600 hover:bg-[#111] cursor-pointer"
title="Paste from clipboard"
>
Paste
</button>
<button
onClick={() => setInput("")}
className="rounded px-2 py-1 text-xs font-medium text-gray-500 hover:bg-gray-100 hover:text-red-600 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-red-400 cursor-pointer"
className="rounded px-2 py-1 text-xs font-medium text-gray-500 hover:bg-[#111] hover:text-red-400 cursor-pointer"
title="Clear input"
>
Clear
@ -562,7 +562,7 @@ export default function XmlFormatterPage() {
placeholder="Paste raw or unformatted XML here..."
rows={18}
spellCheck={false}
className="w-full flex-1 resize-y rounded-lg border border-gray-300 bg-white p-3 font-mono text-sm leading-relaxed text-gray-900 shadow-xs focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-500"
className="w-full flex-1 resize-y border border-[#1a1a1a] bg-[#0a0a0a] p-3 font-mono text-sm leading-relaxed text-gray-200 shadow-xs focus:border-blue-500 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
@ -570,8 +570,8 @@ export default function XmlFormatterPage() {
<div className="flex flex-col">
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-800 dark:text-gray-200">Formatted XML Output</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
<span className="text-sm font-semibold text-gray-300">Formatted XML Output</span>
<span className="text-xs text-gray-500">
({outputLineCount} {outputLineCount === 1 ? "line" : "lines"}, {output.length} chars)
</span>
</div>
@ -579,7 +579,7 @@ export default function XmlFormatterPage() {
<button
onClick={handleDownload}
disabled={!output && !input}
className="inline-flex items-center gap-1 rounded px-2.5 py-1 text-xs font-medium text-gray-700 hover:bg-gray-100 disabled:opacity-40 dark:text-gray-300 dark:hover:bg-gray-800 cursor-pointer"
className="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium text-gray-400 hover:bg-[#111] disabled:opacity-40 cursor-pointer"
title="Download as XML file"
>
<svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -590,7 +590,7 @@ export default function XmlFormatterPage() {
<button
onClick={handleCopy}
disabled={!output && !input}
className="inline-flex items-center gap-1 rounded-md bg-blue-600 px-3 py-1 text-xs font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 disabled:opacity-40 cursor-pointer"
className="inline-flex items-center gap-1 bg-blue-600 px-3 py-1 text-xs font-semibold text-white shadow-xs transition-colors hover:bg-blue-700 disabled:opacity-40 cursor-pointer"
title="Copy XML output"
>
{copied ? (
@ -622,29 +622,29 @@ export default function XmlFormatterPage() {
placeholder="Formatted XML output will appear here..."
rows={18}
spellCheck={false}
className="w-full flex-1 resize-y rounded-lg border border-gray-300 bg-gray-50 p-3 font-mono text-sm leading-relaxed text-gray-900 shadow-xs focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none dark:border-gray-700 dark:bg-gray-900/80 dark:text-gray-100 dark:placeholder-gray-500"
className="w-full flex-1 resize-y border border-[#1a1a1a] bg-black p-3 font-mono text-sm leading-relaxed text-gray-200 shadow-xs focus:border-blue-500 focus:ring-1 focus:ring-blue-900 focus:outline-none"
/>
</div>
</div>
{/* Feature Highlights & Guide */}
<div className="mt-8 rounded-xl border border-gray-200 bg-gray-50/70 p-5 text-sm text-gray-600 dark:border-gray-800 dark:bg-gray-800/30 dark:text-gray-400">
<h3 className="font-semibold text-gray-900 dark:text-gray-100">About XML Formatter & Validator</h3>
<div className="mt-8 border border-[#1a1a1a] bg-black/70 p-5 text-sm text-gray-600">
<h3 className="font-semibold text-gray-200">About XML Formatter & Validator</h3>
<div className="mt-3 grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">✨ Proper Tree Serialization</p>
<p className="font-medium text-gray-300">✨ Proper Tree Serialization</p>
<p className="mt-1 text-xs leading-normal">
Indents elements, handles namespaces, attributes, comments, processing instructions, and CDATA blocks.
</p>
</div>
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">🔍 Real-time DOM Validation</p>
<p className="font-medium text-gray-300">🔍 Real-time DOM Validation</p>
<p className="mt-1 text-xs leading-normal">
Validates XML well-formedness and points directly to unclosed tags, mismatching brackets, or invalid characters.
</p>
</div>
<div>
<p className="font-medium text-gray-800 dark:text-gray-200">🔒 100% Client-Side</p>
<p className="font-medium text-gray-300">🔒 100% Client-Side</p>
<p className="mt-1 text-xs leading-normal">
Uses the browser&apos;s native DOMParser engine. No XML payload ever leaves your computer.
</p>

View file

@ -33,7 +33,7 @@ export default function Header() {
}}
onFocus={() => setShowResults(true)}
onBlur={() => setTimeout(() => setShowResults(false), 150)}
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] pl-10 pr-3 py-2 text-sm text-gray-300 placeholder-[#525252] outline-none focus:border-emerald-900 transition-colors"
className="w-full border border-[#1a1a1a] bg-[#0a0a0a] pl-10 pr-3 py-2 text-sm text-gray-300 placeholder-[#525252] outline-none focus:border-blue-900 transition-colors"
/>
</div>
@ -43,7 +43,7 @@ export default function Header() {
<Link
key={tool.slug}
href={`/tools/${tool.slug}`}
className="flex items-center gap-3 border-b border-[#111] px-4 py-2.5 text-sm text-gray-400 hover:bg-[#111] hover:text-emerald-400 transition-colors"
className="flex items-center gap-3 border-b border-[#111] px-4 py-2.5 text-sm text-gray-400 hover:bg-[#111] hover:text-blue-400 transition-colors"
onClick={() => {
setQuery("");
setShowResults(false);

View file

@ -9,7 +9,7 @@ export default function ToolCard({ tool }: { tool: Tool }) {
>
<span className="text-lg mt-0.5 grayscale group-hover:grayscale-0 transition-all">{tool.icon}</span>
<div>
<div className="text-sm font-medium text-gray-300 group-hover:text-emerald-400 transition-colors">
<div className="text-sm font-medium text-gray-300 group-hover:text-blue-400 transition-colors">
{tool.name}
</div>
<div className="mt-0.5 text-xs text-[#525252] leading-relaxed">{tool.description}</div>

View file

@ -8,7 +8,7 @@ export default function ToolLayout({ title, description, children }: ToolLayoutP
return (
<div className="relative z-10 mx-auto max-w-5xl px-6 py-8">
<div className="flex items-center gap-2 mb-1">
<div className="h-px w-4 bg-emerald-900"></div>
<div className="h-px w-4 bg-blue-900"></div>
<h1 className="text-lg font-semibold text-white">{title}</h1>
</div>
<p className="mb-5 text-sm text-[#525252] ml-6">{description}</p>