diff --git a/CLAUDE.md b/CLAUDE.md index 022b9c31..64be3737 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -71,7 +71,7 @@ We enforce this with a Claude hook (`scripts/hooks/reuse_surface_reminder.sh`) t ## Styling & Tokens -- **Semantic Tokens are Canonical:** Style with semantic Tailwind classes (e.g., `bg-surface`, `text-ink`, `border-border`, `text-brand-primary`). +- **Semantic Tokens are Canonical:** Style with semantic Tailwind classes (e.g., `bg-surface-card`, `text-content-primary`, `border-border-light`, `text-brand-amber`). - **No Hardcoded Colors:** Never hardcode hex values (`#ef4444`) or raw Tailwind palette colors (`bg-red-500`, `text-slate-400`) in feature code. - **Typography:** Rely on global typography settings. If custom typography components are introduced (e.g., a `` wrapper), they must be imported from the shared UI directory, never hand-styled inline. diff --git a/src/components/DropZone.tsx b/src/components/DropZone.tsx index a52ecd55..20cdaee4 100644 --- a/src/components/DropZone.tsx +++ b/src/components/DropZone.tsx @@ -55,8 +55,8 @@ export function DropZone({ onFile, disabled, status }: DropZoneProps) { "rounded-xl border-2 border-dashed px-6 py-12 text-center", "transition-colors", dragOver - ? "border-neutral-900 bg-neutral-100 dark:border-neutral-100 dark:bg-neutral-800" - : "border-neutral-300 hover:border-neutral-500 dark:border-neutral-700 dark:hover:border-neutral-500", + ? "border-content-primary bg-surface-hover" + : "border-border hover:border-border-strong", disabled && "cursor-not-allowed opacity-60", ] .filter(Boolean) @@ -74,16 +74,16 @@ export function DropZone({ onFile, disabled, status }: DropZoneProps) {

Drop a resume PDF here, or click to pick one

-

+

Your file stays in this browser tab.

{status && ( -

+

{status}

)} {error && ( -

{error}

+

{error}

)} ); diff --git a/src/components/PdfPreview.tsx b/src/components/PdfPreview.tsx index d82b8a8b..739be36f 100644 --- a/src/components/PdfPreview.tsx +++ b/src/components/PdfPreview.tsx @@ -39,7 +39,7 @@ export function PdfPreview({ bytes, maxPages = 2 }: PdfPreviewProps) { canvas.width = viewport.width; canvas.height = viewport.height; canvas.className = - "w-full rounded border border-neutral-200 dark:border-neutral-800"; + "w-full rounded border border-border-light"; const ctx = canvas.getContext("2d"); if (!ctx) continue; await page.render({ canvasContext: ctx, viewport }).promise; @@ -61,7 +61,7 @@ export function PdfPreview({ bytes, maxPages = 2 }: PdfPreviewProps) { return (
{error && ( -

+

Preview failed: {error}

)} diff --git a/src/components/Result.tsx b/src/components/Result.tsx index e7cdcb37..4163ed16 100644 --- a/src/components/Result.tsx +++ b/src/components/Result.tsx @@ -43,8 +43,8 @@ function StatusPill({ }) { const cls = tone === "ok" - ? "bg-emerald-100 text-emerald-900 dark:bg-emerald-900/40 dark:text-emerald-200" - : "bg-amber-100 text-amber-900 dark:bg-amber-900/40 dark:text-amber-200"; + ? "bg-feedback-success-bg text-feedback-success-text" + : "bg-feedback-warning-bg text-feedback-warning-text"; return ( void; }) { return ( -
+
Parsed - + {result.diagnostics.pages} page {result.diagnostics.pages === 1 ? "" : "s"} ·{" "} {result.diagnostics.elapsedMs} ms @@ -79,7 +79,7 @@ function ParsedCard({ @@ -87,16 +87,16 @@ function ParsedCard({
-

+

Source PDF

-

+

Extracted plain text

-
+          
             {result.rawText || "(no text extracted)"}
           
@@ -112,21 +112,21 @@ function ParsedCard({ function LayoutFlagsList({ triggers }: { triggers: readonly LayoutTrigger[] }) { return (
-

+

Layout flags

{triggers.length === 0 ? ( -

+

None — single-column, text-PDF layout.

) : (
    {triggers.map((t) => (
  • - + {t} {" "} - + — {LAYOUT_TRIGGER_BLURBS[t]}
  • @@ -141,23 +141,23 @@ function AtsScoreReadout({ score }: { score: AnonymousAtsScore }) { return (
    -

    +

    Reference ATS score

    - + alpha {score.algoVersion && ( - + algo v{score.algoVersion} )}
    {score.overall} - / 100 + / 100
    -

    +

    Our reference number for iterating on the parser. Not a universal score — different ATSes weigh things differently. See the dimensions below. @@ -190,7 +190,7 @@ function AtsScoreReadout({ score }: { score: AnonymousAtsScore }) { /> {score.layout.multiplier < 1 && ( -

    +

    Layout penalty applied (multiplier {score.layout.multiplier.toFixed(2)} ): pre-layout score was {score.preLayoutOverall}.

    @@ -213,21 +213,21 @@ function Dimension({ hint: string; }) { return ( -
    -
    +
    +
    {label}
    {gradable ? ( <> {value} - / {max} + / {max} ) : ( - + )}
    -

    +

    {hint}

    @@ -242,10 +242,10 @@ function PerBulletFeedback({ if (!bullets || bullets.length === 0) { return (
    -

    +

    Per-bullet feedback

    -

    +

    No bullet-shaped lines detected.

    @@ -262,10 +262,10 @@ function PerBulletFeedback({ return (
    -

    +

    Per-bullet feedback

    -

    +

    Each bullet checked against three rules: an action verb, the 8–30-word length window, and a metric. {summary}

    @@ -288,13 +288,13 @@ function BulletRow({ bullet }: { bullet: BulletObservation }) { bullet.hasMetric && bullet.startsWithActionVerb && bullet.wellFormedLength; const containerCls = attention - ? "border-amber-300 bg-amber-50/40 dark:border-amber-700/60 dark:bg-amber-950/20" + ? "border-feedback-warning-border bg-feedback-warning-bg" : allPass - ? "border-neutral-200 dark:border-neutral-800" - : "border-neutral-300 dark:border-neutral-700"; + ? "border-border-light" + : "border-border"; const textCls = allPass - ? "text-neutral-500 dark:text-neutral-400" - : "text-neutral-800 dark:text-neutral-100"; + ? "text-content-muted" + : "text-content-primary"; const lengthLabel = bullet.wellFormedLength ? `${bullet.wordCount} words` @@ -307,7 +307,7 @@ function BulletRow({ bullet }: { bullet: BulletObservation }) { className={`flex flex-col gap-1.5 rounded border p-2 ${containerCls}`} >

    - + #{bullet.index + 1} {bullet.text} @@ -343,8 +343,8 @@ function CheckPill({ failLabel: string; }) { const cls = ok - ? "bg-emerald-50 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-200" - : "bg-amber-100 text-amber-900 dark:bg-amber-900/40 dark:text-amber-200"; + ? "bg-feedback-success-bg text-feedback-success-text" + : "bg-feedback-warning-bg text-feedback-warning-text"; return ( l.url))); return ( -

    +
    Limited parsing @@ -382,7 +382,7 @@ function LimitedParsingCard({

    Some text wasn't readable in this PDF

    -

    +

    {result.diagnostics.pages} page {result.diagnostics.pages === 1 ? "" : "s"} scanned. Below is what we could recover. @@ -390,11 +390,11 @@ function LimitedParsingCard({

-

+

Recovered links

{uniqueUrls.length === 0 ? ( -

+

No link annotations were embedded in this PDF.

) : ( @@ -405,7 +405,7 @@ function LimitedParsingCard({ href={url} target="_blank" rel="noreferrer noopener" - className="font-mono text-xs text-neutral-700 underline decoration-dotted hover:decoration-solid dark:text-neutral-200" + className="font-mono text-xs text-content-secondary underline decoration-dotted hover:decoration-solid" > {url} @@ -415,13 +415,13 @@ function LimitedParsingCard({ )}
-
+
-

+

What happened

-

+

{LAYOUT_TRIGGER_BLURBS.fonts_unmappable}

diff --git a/src/styles.css b/src/styles.css index 038be9d2..4ffcd981 100644 --- a/src/styles.css +++ b/src/styles.css @@ -4,14 +4,90 @@ :root { color-scheme: light dark; -} -body { - @apply bg-white text-neutral-900 antialiased; + --color-brand-navy: #1a365d; + --color-brand-navy-dark: #132a4a; + --color-brand-amber: #b8860b; + --color-brand-amber-light: #d4a017; + --color-brand-cream: #FAFAF8; + --color-bg-base: #FAFAF8; + --color-bg-card: #ffffff; + --color-bg-card-warm: #f7f5f0; + --color-bg-subtle: #f3f4f6; + --color-bg-hover: #f0ede8; + --color-text-primary: #111827; + --color-text-secondary: #374151; + --color-text-tertiary: #4b5563; + --color-text-muted: #9ca3af; + --color-text-inverse: #ffffff; + --color-border-light: #e5e7eb; + --color-border-default: #d1d5db; + --color-border-strong: #9ca3af; + --color-accent-primary: #1a365d; + --color-accent-primary-hover: #132a4a; + --color-accent-forward: #4f46e5; + --color-accent-forward-bg: #EEF2FF; + --color-feedback-error-bg: #fef2f2; + --color-feedback-error-border: #fecaca; + --color-feedback-error-text: #b91c1c; + --color-feedback-error-icon: #dc2626; + --color-feedback-warning-bg: #fffbeb; + --color-feedback-warning-border: #fde68a; + --color-feedback-warning-text: #92400e; + --color-feedback-warning-icon: #f59e0b; + --color-feedback-info-bg: #eff6ff; + --color-feedback-info-border: #bfdbfe; + --color-feedback-info-text: #1e40af; + --color-feedback-info-icon: #2563eb; + --color-feedback-success-bg: #ecfdf5; + --color-feedback-success-border: #a7f3d0; + --color-feedback-success-text: #065f46; + --color-feedback-success-icon: #059669; } @media (prefers-color-scheme: dark) { - body { - @apply bg-neutral-950 text-neutral-100; + :root { + --color-brand-navy: #4a90c4; + --color-brand-navy-dark: #3a7aad; + --color-brand-amber: #f0c760; + --color-brand-amber-light: #f5d47a; + --color-brand-cream: #0f1419; + --color-bg-base: #0f1419; + --color-bg-card: #1a2030; + --color-bg-card-warm: #1e2538; + --color-bg-subtle: #252d3f; + --color-bg-hover: #2c3549; + --color-text-primary: #f0f2f5; + --color-text-secondary: #b8bfcc; + --color-text-tertiary: #8892a4; + --color-text-muted: #4d5668; + --color-text-inverse: #0f1419; + --color-border-light: #2c3549; + --color-border-default: #3a4459; + --color-border-strong: #4d5668; + --color-accent-primary: #6ba3d9; + --color-accent-primary-hover: #84b6e5; + --color-accent-forward: #818cf8; + --color-accent-forward-bg: #1e1b4b; + --color-feedback-error-bg: #2d1215; + --color-feedback-error-border: #5c1d24; + --color-feedback-error-text: #f87171; + --color-feedback-error-icon: #ef4444; + --color-feedback-warning-bg: #2d2310; + --color-feedback-warning-border: #5c4a1d; + --color-feedback-warning-text: #fbbf24; + --color-feedback-warning-icon: #f59e0b; + --color-feedback-info-bg: #1a2540; + --color-feedback-info-border: #1d3a6e; + --color-feedback-info-text: #60a5fa; + --color-feedback-info-icon: #3b82f6; + --color-feedback-success-bg: #0d2818; + --color-feedback-success-border: #145c33; + --color-feedback-success-text: #34d399; + --color-feedback-success-icon: #10b981; } } + +body { + @apply bg-surface-base text-content-primary antialiased; +} diff --git a/tailwind.config.js b/tailwind.config.js index 1899223a..34ea7cf3 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -22,6 +22,66 @@ export default { "monospace", ], }, + colors: { + brand: { + navy: "var(--color-brand-navy)", + "navy-dark": "var(--color-brand-navy-dark)", + amber: "var(--color-brand-amber)", + "amber-light": "var(--color-brand-amber-light)", + cream: "var(--color-brand-cream)", + }, + surface: { + base: "var(--color-bg-base)", + card: "var(--color-bg-card)", + "card-warm": "var(--color-bg-card-warm)", + subtle: "var(--color-bg-subtle)", + hover: "var(--color-bg-hover)", + }, + content: { + primary: "var(--color-text-primary)", + secondary: "var(--color-text-secondary)", + tertiary: "var(--color-text-tertiary)", + muted: "var(--color-text-muted)", + inverse: "var(--color-text-inverse)", + }, + border: { + light: "var(--color-border-light)", + DEFAULT: "var(--color-border-default)", + strong: "var(--color-border-strong)", + }, + accent: { + primary: "var(--color-accent-primary)", + "primary-hover": "var(--color-accent-primary-hover)", + forward: "var(--color-accent-forward)", + "forward-bg": "var(--color-accent-forward-bg)", + }, + feedback: { + error: { + bg: "var(--color-feedback-error-bg)", + border: "var(--color-feedback-error-border)", + text: "var(--color-feedback-error-text)", + icon: "var(--color-feedback-error-icon)", + }, + warning: { + bg: "var(--color-feedback-warning-bg)", + border: "var(--color-feedback-warning-border)", + text: "var(--color-feedback-warning-text)", + icon: "var(--color-feedback-warning-icon)", + }, + info: { + bg: "var(--color-feedback-info-bg)", + border: "var(--color-feedback-info-border)", + text: "var(--color-feedback-info-text)", + icon: "var(--color-feedback-info-icon)", + }, + success: { + bg: "var(--color-feedback-success-bg)", + border: "var(--color-feedback-success-border)", + text: "var(--color-feedback-success-text)", + icon: "var(--color-feedback-success-icon)", + }, + }, + }, }, }, plugins: [],