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: { images: {
unoptimized: true, unoptimized: true,
}, },
devIndicators: false,
}; };
export default nextConfig; export default nextConfig;

View file

@ -1,8 +1,8 @@
@import "tailwindcss"; @import "tailwindcss";
:root { :root {
--background: #f9fafb; --background: #000000;
--foreground: #111827; --foreground: #e5e5e5;
} }
@theme inline { @theme inline {
@ -12,31 +12,17 @@
--font-mono: var(--font-geist-mono); --font-mono: var(--font-geist-mono);
} }
@media (prefers-color-scheme: dark) {
:root {
--background: #030712;
--foreground: #f3f4f6;
}
}
body { body {
background: var(--background); background: #000000;
color: var(--foreground); color: var(--foreground);
font-family: var(--font-sans), Arial, Helvetica, sans-serif; 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 { .ad-container ins.adsbygoogle {
min-height: 90px; min-height: 90px;
background: repeating-linear-gradient( background: #050505;
-45deg, border: 1px solid rgba(255, 255, 255, 0.04);
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;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -44,9 +30,15 @@ body {
} }
.ad-container ins.adsbygoogle::after { .ad-container ins.adsbygoogle::after {
content: "Ad Space"; content: "Ad";
font-size: 0.75rem; font-size: 0.6rem;
color: rgba(128, 128, 128, 0.4); color: rgba(255, 255, 255, 0.1);
font-weight: 500; font-weight: 400;
letter-spacing: 0.05em; 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" crossOrigin="anonymous"
/> />
</head> </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 /> <Header />
<main className="flex-1">{children}</main> <main className="flex-1">{children}</main>
<Footer /> <Footer />

View file

@ -6,26 +6,40 @@ export default function Home() {
const toolsByCategory = getToolsByCategory(); const toolsByCategory = getToolsByCategory();
return ( return (
<div className="mx-auto max-w-4xl px-4 py-8"> <div className="flex w-full">
<h1 className="text-xl font-bold text-gray-900 dark:text-white">Developer Tools</h1> {/* Left side ad */}
<p className="mb-6 mt-1 text-sm text-gray-400">Free. Private. Browser-based.</p> <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]) => ( {Object.entries(toolsByCategory).map(([category, categoryTools]) => (
<section key={category} className="mb-6"> <section key={category} className="mb-6">
<h2 className="mb-2 text-xs font-semibold uppercase tracking-wider text-gray-400"> <h2 className="mb-2 text-xs font-semibold uppercase tracking-wider text-gray-500">
{category} {category}
</h2> </h2>
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3"> <div className="grid grid-cols-1 gap-px sm:grid-cols-2 lg:grid-cols-3 border border-gray-900">
{categoryTools.map((tool) => ( {categoryTools.map((tool) => (
<ToolCard key={tool.slug} tool={tool} /> <ToolCard key={tool.slug} tool={tool} />
))} ))}
</div> </div>
</section> </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> </div>
); );
} }

View file

@ -1,7 +1,7 @@
export default function Footer() { export default function Footer() {
return ( 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"> <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 — Free, private, browser-based developer tools. © {new Date().getFullYear()} DevToolbox
</footer> </footer>
); );
} }

View file

@ -1,17 +1,55 @@
"use client"; "use client";
import { useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { tools } from "@/lib/tools";
export default function Header() { 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 ( return (
<header className="border-b border-gray-200 dark:border-gray-800"> <header className="border-b border-gray-900 bg-black">
<div className="mx-auto flex max-w-5xl items-center justify-between px-4 py-3"> <div className="mx-auto max-w-5xl px-4 py-2.5 relative">
<Link href="/" className="text-sm font-semibold text-gray-900 dark:text-white"> <input
DevToolbox type="text"
</Link> placeholder="Search tools..."
<Link href="/" className="text-xs text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"> value={query}
All Tools onChange={(e) => {
</Link> 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> </div>
</header> </header>
); );

View file

@ -5,12 +5,12 @@ export default function ToolCard({ tool }: { tool: Tool }) {
return ( return (
<Link <Link
href={`/tools/${tool.slug}`} 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} {tool.icon} {tool.name}
</div> </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> </Link>
); );
} }

View file

@ -8,15 +8,32 @@ interface ToolLayoutProps {
export default function ToolLayout({ title, description, children }: ToolLayoutProps) { export default function ToolLayout({ title, description, children }: ToolLayoutProps) {
return ( return (
<div className="mx-auto w-full max-w-4xl px-4 py-6"> <div className="flex w-full">
<AdSlot slot="1234567890" format="horizontal" /> {/* 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> {/* Main content */}
<p className="mb-4 text-sm text-gray-400">{description}</p> <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> </div>
); );
} }