From 05ef5f2aef898687cb5b70cc8c7aa674799723c2 Mon Sep 17 00:00:00 2001 From: Aryan Singh Date: Wed, 2 Sep 2026 00:02:23 -0500 Subject: [PATCH] Redesign the landing page around a print-forensics palette The old page was the default dark-SaaS template: near-black ground, violet gradient headline, blur-glow orbs, glassmorphic rounded cards, emoji icons. It looked like every other generated landing page and said nothing about what the product is for. This one is built out of document materials, because the product is about documents: - Warm newsprint stock (#f2efe6) and warm near-black ink instead of near-black and violet. Two accents only, each with a job: vermilion is reserved exclusively for content that was hiding, olive for content that has been cleared. Nothing else gets to be colored. - Instrument Serif carries the headlines, Geist Mono carries labels and metadata, so the UI sans never has to pretend to be expressive. - Hairline rules do the structural work that bordered cards were doing. The six hiding places are now a numbered editorial list rather than a grid of identical rounded boxes, and the pipeline steps are a ruled table with serif numerals. - Left-aligned asymmetric hero with a colophon block, rather than everything stacked down the center axis. - The self-host section inverts to ink for tonal contrast, which also gives the terminal block somewhere natural to live. The page also now practices what it preaches: there is a real white-on-white sentence planted in the hero, and an X-ray switch in the nav reveals it. Selecting it works too, the same way it would in a PDF viewer. The four-stage demo keeps its behavior and moves to the new palette. Co-Authored-By: Claude Opus 5 --- client/src/app/globals.css | 46 +- client/src/app/layout.tsx | 13 +- client/src/app/page.tsx | 563 ++++++++++++----------- client/src/components/HiddenTextDemo.tsx | 261 +++++------ 4 files changed, 450 insertions(+), 433 deletions(-) diff --git a/client/src/app/globals.css b/client/src/app/globals.css index e3b4421..15be8ba 100644 --- a/client/src/app/globals.css +++ b/client/src/app/globals.css @@ -1,5 +1,35 @@ @import "tailwindcss"; +/* + * Two palettes live here. The signed-in app is dark (see dashboard/layout); + * the marketing page is paper. The landing page is about documents, so it is + * built out of document materials: warm newsprint stock, near-black ink, a + * single vermilion reserved for content that was hiding, and an olive for + * content that has been cleared. Nothing else gets to be colored. + */ +@theme { + /* Paper stock — warm, never pure white. */ + --color-stock: #f2efe6; + --color-sheet: #fbfaf6; + + /* Ink — warm near-black, never pure black. */ + --color-ink: #17150f; + --color-ink-soft: #57534a; + --color-ink-faint: #8b857a; + + /* Hairlines do the structural work that borders-on-cards would. */ + --color-rule: #dcd6c7; + --color-rule-soft: #e8e3d8; + + /* Vermilion: reserved exclusively for revealed hidden content. */ + --color-signal: #bf3517; + --color-signal-wash: #f7e5df; + + /* Olive: the cleared / verified state. */ + --color-verified: #3d5a37; + --color-verified-wash: #e5ebe1; +} + :root { --background: #0a0a0a; --foreground: #ededed; @@ -10,6 +40,8 @@ --color-foreground: var(--foreground); --font-sans: var(--font-geist-sans); --font-mono: var(--font-geist-mono); + --font-display: var(--font-instrument-serif), "Iowan Old Style", Georgia, + serif; } body { @@ -18,7 +50,7 @@ body { font-family: var(--font-sans), system-ui, -apple-system, sans-serif; } -/* Smooth scrollbar for dark theme */ +/* Neutral enough to read against both the dark app and the paper landing. */ ::-webkit-scrollbar { width: 6px; height: 6px; @@ -27,9 +59,17 @@ body { background: transparent; } ::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.1); + background: rgba(128, 128, 128, 0.35); border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.2); + background: rgba(128, 128, 128, 0.55); +} + +/* Mono label used throughout the landing page for metadata and section marks. */ +.label { + font-family: var(--font-mono), ui-monospace, monospace; + font-size: 0.6875rem; + letter-spacing: 0.16em; + text-transform: uppercase; } diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx index 33164ad..8a9be49 100644 --- a/client/src/app/layout.tsx +++ b/client/src/app/layout.tsx @@ -1,5 +1,5 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; +import { Geist, Geist_Mono, Instrument_Serif } from "next/font/google"; import "./globals.css"; import Providers from "@/lib/SessionProvider"; @@ -13,6 +13,15 @@ const geistMono = Geist_Mono({ subsets: ["latin"], }); +// Display face for the landing page. High-contrast and editorial — it carries +// the headlines so the UI sans never has to pretend to be expressive. +const instrumentSerif = Instrument_Serif({ + variable: "--font-instrument-serif", + subsets: ["latin"], + weight: "400", + style: ["normal", "italic"], +}); + export const metadata: Metadata = { title: "XRayPDF — Strip hidden text out of PDFs", description: @@ -34,7 +43,7 @@ export default function RootLayout({ {children} diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx index fe899b8..b91b882 100644 --- a/client/src/app/page.tsx +++ b/client/src/app/page.tsx @@ -2,7 +2,7 @@ import { signIn, useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import HiddenTextDemo from "@/components/HiddenTextDemo"; const REPO_URL = "https://github.com/geneticglitch1/XRayPDF"; @@ -11,359 +11,368 @@ const REPO_URL = "https://github.com/geneticglitch1/XRayPDF"; const HIDING_SPOTS = [ { title: "White text on white paper", - desc: "The classic. Rendered in #FFFFFF, invisible on screen and in print, still returned verbatim by every text extractor.", - caught: "Contrast normalization drags it into visible gray.", + desc: "Rendered in #FFFFFF. Invisible on screen and in print, still returned verbatim by every text extractor.", + caught: "Contrast normalization drags it into visible gray", }, { title: "Near-zero font sizes", - desc: "Text set at 0.5pt reads as a smudge or as nothing at all, but copies and pastes back at full length.", - caught: "Renders at 2× scale, so the smudge becomes glyphs.", + desc: "Text set at half a point reads as a smudge, or as nothing at all, but copies and pastes back at full length.", + caught: "Renders at 2× scale, so the smudge becomes glyphs", }, { - title: "Text hidden behind images", - desc: "A full-page graphic drawn on top of a text run. Looks like a scan; it is not a scan.", - caught: "Flattening rebuilds the page from what's actually visible.", + title: "Text behind images", + desc: "A full-page graphic drawn on top of a live text run. Looks like a scan. It is not a scan.", + caught: "Flattening rebuilds the page from what is visible", }, { title: "Invisible render modes", - desc: "PDF text render mode 3 draws nothing at all. The characters are in the file and in the clipboard, and on no pixel of the page.", - caught: "The export drops the text layer entirely.", + desc: "PDF text render mode 3 draws nothing at all. The characters are in the file and in your clipboard, and on no pixel of the page.", + caught: "The export drops the text layer entirely", }, { title: "Content parked off-page", desc: "Positioned outside the crop box. Never displayed, never printed, always extracted.", - caught: "Cropped away by the render, gone from the export.", + caught: "Cropped away by the render, gone from the export", }, { title: "Leftover revision data", desc: "Incremental-save history and orphaned objects that keep earlier drafts alive inside the same file.", - caught: "Not carried over — the export is a fresh document.", + caught: "Not carried over — the export is a fresh document", }, ]; const STEPS = [ { n: "01", - title: "Rasterize every page", - desc: "pdfjs renders each page to a PNG at 2× scale on a forced white background. This is the honest picture of the document — whatever is actually drawn, and nothing else.", + title: "Rasterize", + tool: "pdfjs-dist + @napi-rs/canvas", + desc: "Every page renders to a PNG at 2× scale on a forced white background. This is the honest picture of the document: whatever is actually drawn, and nothing else.", }, { n: "02", title: "Push the contrast", - desc: "sharp flattens alpha onto white, converts to grayscale, stretches the histogram across the full 0–255 range, and sharpens. Ink that was one shade off white lands in plain view.", + tool: "sharp", + desc: "Flatten alpha onto white, convert to grayscale, stretch the histogram across the full 0–255 range, sharpen. Ink that was one shade off white lands in plain view.", }, { n: "03", - title: "Read it back with OCR", - desc: "Tesseract runs over the sanitized image and returns word boxes with per-word confidence. Overlay them on the page to see exactly where the surprises are.", + title: "Read it back", + tool: "tesseract.js", + desc: "OCR runs over the sanitized image and returns word boxes with per-word confidence. Overlay them on the page to see exactly where the surprises are.", }, { n: "04", - title: "Export a flat PDF", - desc: "pdf-lib rebuilds the document from the page images. No text layer, no annotations, no object history — nothing left for anything to hide inside.", + title: "Flatten the export", + tool: "pdf-lib", + desc: "Rebuild the document from the page images. No text layer, no annotations, no object history — nothing left for anything to hide inside.", }, ]; export default function Home() { const { data: session, status } = useSession(); const router = useRouter(); + const [xray, setXray] = useState(false); useEffect(() => { if (session) router.replace("/dashboard"); }, [session, router]); return ( -
- {/* Ambient glow */} -
- - {/* Nav */} -