ui: sharp AMOLED theme, search bar, side ads

This commit is contained in:
katR 2026-08-31 23:05:48 +05:30
parent f9f17164cd
commit f66a105ea1
10 changed files with 134 additions and 62 deletions

9
AGENTS.md Normal file
View file

@ -0,0 +1,9 @@
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
<!-- END:nextjs-agent-rules -->

1
CLAUDE.md Normal file
View file

@ -0,0 +1 @@
@AGENTS.md

View file

@ -5,6 +5,7 @@ const nextConfig: NextConfig = {
images: {
unoptimized: true,
},
devIndicators: false,
};
export default nextConfig;

View file

@ -1,8 +1,8 @@
@import "tailwindcss";
:root {
--background: #f9fafb;
--foreground: #111827;
--background: #000000;
--foreground: #e5e5e5;
}
@theme inline {
@ -12,31 +12,17 @@
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #030712;
--foreground: #f3f4f6;
}
}
body {
background: var(--background);
background: #000000;
color: var(--foreground);
font-family: var(--font-sans), Arial, Helvetica, sans-serif;
}
/* AdSense placeholder styling — remove once real ads are connected */
/* AdSense placeholder styling — sharp theme */
.ad-container ins.adsbygoogle {
min-height: 90px;
background: repeating-linear-gradient(
-45deg,
transparent,
transparent 10px,
rgba(128, 128, 128, 0.03) 10px,
rgba(128, 128, 128, 0.03) 20px
);
border: 1px dashed rgba(128, 128, 128, 0.2);
border-radius: 8px;
background: #050505;
border: 1px solid rgba(255, 255, 255, 0.04);
display: flex;
align-items: center;
justify-content: center;
@ -44,9 +30,15 @@ body {
}
.ad-container ins.adsbygoogle::after {
content: "Ad Space";
font-size: 0.75rem;
color: rgba(128, 128, 128, 0.4);
font-weight: 500;
letter-spacing: 0.05em;
content: "Ad";
font-size: 0.6rem;
color: rgba(255, 255, 255, 0.1);
font-weight: 400;
letter-spacing: 0.1em;
font-family: monospace;
}
.ad-side ins.adsbygoogle {
min-height: 600px;
min-width: 160px;
}

View file

@ -64,7 +64,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
crossOrigin="anonymous"
/>
</head>
<body className="flex min-h-full flex-col bg-background text-foreground">
<body className="flex min-h-full flex-col bg-black text-foreground">
<Header />
<main className="flex-1">{children}</main>
<Footer />

View file

@ -6,26 +6,40 @@ export default function Home() {
const toolsByCategory = getToolsByCategory();
return (
<div className="mx-auto max-w-4xl px-4 py-8">
<h1 className="text-xl font-bold text-gray-900 dark:text-white">Developer Tools</h1>
<p className="mb-6 mt-1 text-sm text-gray-400">Free. Private. Browser-based.</p>
<div className="flex w-full">
{/* Left side ad */}
<div className="ad-side hidden xl:flex w-[200px] shrink-0 justify-center pt-6 pl-2">
<div className="sticky top-16">
<AdSlot slot="home-left" format="vertical" />
</div>
</div>
<AdSlot slot="homepage-top" format="horizontal" />
{/* Main content */}
<div className="flex-1 min-w-0 max-w-4xl mx-auto px-4 py-6">
<AdSlot slot="homepage-top" format="horizontal" />
{Object.entries(toolsByCategory).map(([category, categoryTools]) => (
<section key={category} className="mb-6">
<h2 className="mb-2 text-xs font-semibold uppercase tracking-wider text-gray-400">
{category}
</h2>
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3">
{categoryTools.map((tool) => (
<ToolCard key={tool.slug} tool={tool} />
))}
</div>
</section>
))}
{Object.entries(toolsByCategory).map(([category, categoryTools]) => (
<section key={category} className="mb-6">
<h2 className="mb-2 text-xs font-semibold uppercase tracking-wider text-gray-500">
{category}
</h2>
<div className="grid grid-cols-1 gap-px sm:grid-cols-2 lg:grid-cols-3 border border-gray-900">
{categoryTools.map((tool) => (
<ToolCard key={tool.slug} tool={tool} />
))}
</div>
</section>
))}
<AdSlot slot="homepage-bottom" format="horizontal" />
<AdSlot slot="homepage-bottom" format="horizontal" />
</div>
{/* Right side ad */}
<div className="ad-side hidden xl:flex w-[200px] shrink-0 justify-center pt-6 pr-2">
<div className="sticky top-16">
<AdSlot slot="home-right" format="vertical" />
</div>
</div>
</div>
);
}

View file

@ -1,7 +1,7 @@
export default function Footer() {
return (
<footer className="mt-auto border-t border-gray-200 px-4 py-4 text-center text-xs text-gray-400 dark:border-gray-800">
© {new Date().getFullYear()} DevToolbox — Free, private, browser-based developer tools.
<footer className="mt-auto border-t border-gray-900 px-4 py-4 text-center text-xs text-gray-600 bg-black">
© {new Date().getFullYear()} DevToolbox
</footer>
);
}

View file

@ -1,17 +1,55 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import { tools } from "@/lib/tools";
export default function Header() {
const [query, setQuery] = useState("");
const [showResults, setShowResults] = useState(false);
const filtered = query.trim()
? tools.filter(
(t) =>
t.name.toLowerCase().includes(query.toLowerCase()) ||
t.description.toLowerCase().includes(query.toLowerCase())
)
: [];
return (
<header className="border-b border-gray-200 dark:border-gray-800">
<div className="mx-auto flex max-w-5xl items-center justify-between px-4 py-3">
<Link href="/" className="text-sm font-semibold text-gray-900 dark:text-white">
DevToolbox
</Link>
<Link href="/" className="text-xs text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
All Tools
</Link>
<header className="border-b border-gray-900 bg-black">
<div className="mx-auto max-w-5xl px-4 py-2.5 relative">
<input
type="text"
placeholder="Search tools..."
value={query}
onChange={(e) => {
setQuery(e.target.value);
setShowResults(true);
}}
onFocus={() => setShowResults(true)}
onBlur={() => setTimeout(() => setShowResults(false), 150)}
className="w-full border border-gray-800 bg-black px-3 py-2 text-sm text-gray-200 placeholder-gray-600 outline-none focus:border-gray-600 font-mono"
/>
{showResults && filtered.length > 0 && (
<div className="absolute left-4 right-4 top-full z-50 mt-0 max-h-64 overflow-y-auto border border-gray-800 border-t-0 bg-black shadow-2xl">
{filtered.map((tool) => (
<Link
key={tool.slug}
href={`/tools/${tool.slug}`}
className="block border-b border-gray-900 px-3 py-2 text-sm text-gray-300 hover:bg-gray-950"
onClick={() => {
setQuery("");
setShowResults(false);
}}
>
{tool.icon} {tool.name}
<span className="ml-2 text-xs text-gray-600">{tool.description}</span>
</Link>
))}
</div>
)}
</div>
</header>
);

View file

@ -5,12 +5,12 @@ export default function ToolCard({ tool }: { tool: Tool }) {
return (
<Link
href={`/tools/${tool.slug}`}
className="block rounded-lg border border-gray-200 px-4 py-3 transition-colors hover:border-gray-400 dark:border-gray-800 dark:hover:border-gray-600"
className="block border border-gray-900 px-4 py-3 transition-colors hover:border-gray-600 hover:bg-gray-950 bg-black"
>
<div className="text-sm font-medium text-gray-900 dark:text-white">
<div className="text-sm font-medium text-gray-200">
{tool.icon} {tool.name}
</div>
<div className="mt-0.5 text-xs text-gray-400">{tool.description}</div>
<div className="mt-0.5 text-xs text-gray-600">{tool.description}</div>
</Link>
);
}

View file

@ -8,15 +8,32 @@ interface ToolLayoutProps {
export default function ToolLayout({ title, description, children }: ToolLayoutProps) {
return (
<div className="mx-auto w-full max-w-4xl px-4 py-6">
<AdSlot slot="1234567890" format="horizontal" />
<div className="flex w-full">
{/* Left side ad — flush to edge */}
<div className="ad-side hidden xl:flex w-[200px] shrink-0 justify-center pt-6 pl-2">
<div className="sticky top-16">
<AdSlot slot="left-sidebar" format="vertical" />
</div>
</div>
<h1 className="mb-1 text-lg font-semibold text-gray-900 dark:text-white">{title}</h1>
<p className="mb-4 text-sm text-gray-400">{description}</p>
{/* Main content */}
<div className="flex-1 min-w-0 max-w-4xl mx-auto px-4 py-6">
<AdSlot slot="1234567890" format="horizontal" />
{children}
<h1 className="mb-1 text-lg font-semibold text-white">{title}</h1>
<p className="mb-4 text-sm text-gray-500">{description}</p>
<AdSlot slot="0987654321" format="horizontal" className="mt-6" />
{children}
<AdSlot slot="0987654321" format="horizontal" className="mt-6" />
</div>
{/* Right side ad — flush to edge */}
<div className="ad-side hidden xl:flex w-[200px] shrink-0 justify-center pt-6 pr-2">
<div className="sticky top-16">
<AdSlot slot="right-sidebar" format="vertical" />
</div>
</div>
</div>
);
}