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 */} -