We’re upgrading our email infrastructure — for immediate response, email andrewjgaber@gmail.com meanwhile.
Skip to main content

Digital Empire — design system

Component inventory and UI patterns as they actually ship across PixelProof, EntryProof, and TariffWatch — plain CSS (no component-library dependency), one shared ink-ramp token set, per-product scoped class prefixes (pp-/tw-/ss-), and self-contained, mostly prop-less React components.

Architecture

Plain CSS, not Tailwind or a component library — a deliberate build-risk decision documented in every product's scoped stylesheet header: "zero new build-dependency risk on a deploy Andy can't babysit." Each product owns one scoped stylesheet (app/meta-monitor/meta-monitor.css, app/tariffwatch/tariffwatch.css, app/cpsc-efile/cpsc-efile.css) with a two-to-three-letter class prefix (pp- / tw- / ss-) so class names never collide across products sharing one Next.js app. All three import the same global ink-ramp tokens from app/globals.css. Cross-portfolio pages (/products, /about, /brand, /design-system) use their own dep-/brd- prefixes rather than borrowing a single product's namespace.

Most components take zero props — configuration (copy, pricing, dates, legal clauses) lives centrally in lib/pixelproof/constants.ts, lib/pick3/constants.ts, and lib/cpsc/constants.ts — "single source of truth so landing page, scan report, and emails never drift" (constants.ts header comment). The handful of components that do take props (below) are the genuinely reusable, cross-context ones.

Component inventory

55 component files across 5 directories, this commit.

GroupCountExamples
Shared (app/components)3PortfolioFooterStrip (props: excludeKey, hideThesis), CookieConsentBanner, CookieConsentBannerServer
PixelProof (app/meta-monitor/components)22ScannerWidget, PricingTable, FaqSection, ExitIntentPopup, Countdown, IncumbentTable, TrustStrip, VideoHero, StructuredData
EntryProof (app/cpsc-efile/components)10ReadinessChecker, CaseStudyAccordion, ExitIntentPopup, RealtimeCohortCounter, FoundingCohortForm, StructuredData
TariffWatch (app/tariffwatch/components)10ExposureChecker, CountdownClock, WatchListSignup, AttorneyMatchOptIn, ExposureGhostPreview, StructuredData
Tools (app/tools/**/components)4CpscPenaltyClient, HtsLookupClient, MetaPixelDebuggerClient, ToolStructuredData

Props API — the reusable minority: PortfolioFooterStrip({ excludeKey?: string; hideThesis?: boolean }) — renders the 3-product cross-sell strip, omitting the current product via excludeKey and hiding the intro line via hideThesis when the host page already has its own heading. Mounted on /, /cpsc-efile, and /tariffwatch — deliberately not /meta-monitor, since / and /meta-monitor render the identical PixelProof landing.

Common patterns

Buttons

Primary: accent-filled (.pp-btn / .tw-btn / .ss-btn), white text, 8px radius, 12–20px padding, 15px/600 weight. Secondary: .pp-btn-secondary — transparent fill, 1px border, body text color. Every button uses the AA-safe #d6202f button shade, not the raw #ee2a3c accent.

Forms & inputs

.pp-input/.tw-input/.ss-input — dark-surface background, 1px border, 8px radius, 16px font-size (iOS zoom fix, non-negotiable). Focus state: 2px solid accent outline, 1px offset. Checkboxes use a flex row with the label wrapping at 13px, muted color — see .tw-checkbox-row (business-authorization consent, a regulatory requirement on TariffWatch's checker, not a style choice).

Cards

--panel background, 1px --border, 10–14px radius. Pricing cards (.pp-price-card) add a featured state — accent-colored border plus an absolutely-positioned .pp-tier-badge pill. Founder-guarantee and problem cards follow the same panel/border/radius recipe for visual consistency.

Modals

One modal pattern, reused verbatim across products: .pp-overlay/.ss-overlay (fixed, full-viewport, rgba(0,0,0,0.6) scrim) plus .pp-popup/.ss-popup (panel surface, 14px radius, 360px max-width, close button top-right). Used exclusively for the exit-intent capture flow today — see ExitIntentPopup.tsx in both PixelProof and EntryProof.

Tables

.pp-table/.tw-table — horizontally scrollable wrapper (overflow-x: auto) with a 560px min-width so dense comparison data never gets crushed on mobile. Header row: uppercase, 11–12px, muted color. PixelProof's incumbent-comparison table highlights its own row with a tinted background (.pp-table-us).

Empty, error & loading states

Loading: disabled primary button + inline status text (e.g. "Scanning…") — no skeleton screens or spinners ship today; every scan/check response returns fast enough that a disabled-button state suffices. Error: .pp-error/.tw-error/.ss-error — small red-tinted text directly under the input, never a full-page error state, so the user can retry without losing context. Network/config failures in optional widgets (e.g. the Ad Library urgency tile) fail silent — render nothing rather than a broken or infinitely-loading tile. Empty: no dedicated empty-state component exists yet; checkers and scanners always render either a result or an inline error, never a blank success state.

Interaction states

StateTreatment
Hover (links)Underline appears (footer nav links, FAQ items are non-hover-dependent since they're tap-based accordions)
Focus2px solid accent outline, 1–2px offset, on inputs; skip-nav link becomes visible with a 3px blue outline on keyboard focus
Disabledopacity: 0.6; cursor: not-allowed on buttons mid-submit
Active/expandedNative <details>/<summary> for FAQ and case-study accordions — no JS state, a "+" flips to "−" via CSS [open] selector

Responsive breakpoints

Mobile-first throughout — base rules target small screens, min-width media queries layer on desktop enhancements. No fixed named breakpoint tokens (no CSS variables for them); the values used consistently across all three products' stylesheets are:

min-widthUsed for
480pxChecker/exposure form rows switch from stacked to inline
640pxScanner row inline layout; problem-card grids go to 3 columns; trust-strip grid to 3 columns
768pxHero H1/H2 jump to desktop sizes; pricing grids go to 3 columns
900pxSticky mobile CTA bar hides; snippet page grid goes 2-column

Accessibility notes

From the Aug 9 2026 a11y sweep (F2/F9/Attack-Audit P1-7), now standing rules for every new component:

  • Filled buttons use #d6202f, not the raw #ee2a3c accent — WCAG AA 4.5:1 text-contrast minimum.
  • All form inputs ≥16px font-size (prevents iOS Safari auto-zoom-on-focus).
  • A .skip-nav link is present site-wide, off-screen until keyboard focus, then a high-contrast visible target.
  • Scanner/email-capture inputs carry visually-hidden but screen-reader-visible .sr-only labels (WCAG 2.1 SC 1.3.1 / 4.1.2).
  • Minimum 44px tap target on every interactive control.
  • FAQ/case-study accordions use native <details>, giving keyboard and screen-reader support for free instead of a custom ARIA implementation.

Consistency rules across picks

Do

Import the shared ink-ramp tokens (--bg/--panel/--border/--text/--muted) — never redefine them per product.

Prefix every new class with your product's letters, matching the existing pp-/tw-/ss- convention.

Mirror an existing cross-product pattern (case-study accordion, exit-intent popup, countdown) exactly when the same content type recurs — see the meta-monitor.css comment citing cpsc-efile.css's .ss-case-study-* as the pattern to mirror.

Don't

Add Tailwind, a component library, or an icon package without updating this page and re-justifying the build-risk tradeoff.

Fork the accent hex — always reference the existing crimson token, never a fourth ad-hoc red.

Ship a new modal, table, or form pattern without checking whether an existing pp-/tw-/ss- class already solves it.

See also the companion Brand Style Guide for color, type, and voice tokens, and downloadable assets.