Case Study
Building DevinMarshall.info - A Full-Stack Personal Brand Platform
Personal brand websites typically solve the portfolio problem while creating a logistics problem: content, community, commerce, and marketing each live on a different third-party platform. DevinMarshall.info was designed to replace that fragmented ecosystem with a single, fully owned application; a Next.js 16 / Supabase platform with 30+ public pages, a 22-section-type CMS, integrated print-on-demand commerce, community subscriptions, email marketing with first-party attribution, and an admin dashboard with real-time site health metrics. The result is a platform that handles every operational function of a personal brand, without external SaaS dependencies, and doubles as a live demonstration of the web development and brand architecture services it promotes.

Background & Context
Devin Marshall is an Army veteran, entrepreneur, and creator of the Imagineer Framework — a personal design philosophy built around intentional living, captured by the tagline "Live by design, not by default." His brand spans six pillars: The Imagineer, The Builder, The Analyst, The Storyteller, The Creator, and The Veteran.
DevinMarshall.info is more than a website — it is the central hub of my personal brand and the digital home of an Imagineer. I built this platform to house the full scope of my work: my philosophy, creative output, professional portfolio, community initiatives, and the evolving vision behind Project Lifescape. Rather than scattering my identity across disconnected platforms, I wanted one intentional space that reflects who I am and what I am building.
What makes this project especially meaningful to me is that it embodies the values I stand for: intentionality, craftsmanship, clarity, and disciplined creativity. This site is proof of concept for the way I approach life and work, not by default, but by design. It is both a portfolio piece and a living expression of my long-term vision as a builder, strategist, storyteller, and creator.
A brand this multi-dimensional needs a platform that can carry all of it — not a landing page, not a portfolio, but a full operating base. The site needed to represent every pillar, serve multiple audience types (prospective clients, community members, readers, collaborators), support active business operations, and remain maintainable as a solo developer project.
Problem Statement
The Question: How do you build a personal brand website that functions as a full business platform, without becoming a maintenance burden?
The Problem: Most personal brand platforms are stitched together: portfolio on one CMS, newsletter on Substack, community on Circle, merch on Shopify, analytics spread across four dashboards. Every integration is a liability; a pricing change, an API deprecation, or a platform pivot can break a critical workflow. For someone whose entire brand is built around intentional, designed living, that fragmentation is structurally at odds with the message.
The Challenge: Building a consolidated platform at the quality level required by a brand-architecture services offering means no shortcuts. The stack, the CMS, the admin dashboard, the commerce flow, and the marketing system all had to be production-grade. The site itself had to be the proof of concept.
Methodology / Approach
The platform was designed in four distinct layers: public site, admin dashboard, data layer, and marketing system. Each layer was built with a strict rule: no third-party SaaS for functions that could be owned, and no feature beyond what the current use case required.
Stack rationale:
- Next.js 16 App Router for server-first rendering, route-level caching with
unstable_cache, and ISR without configuration overhead - Supabase (PostgreSQL + Auth + Storage) for typed data access, Row Level Security at the database layer, and signed-URL media uploads
- Stripe for payment processing; Printful for print-on-demand fulfillment without inventory risk
- TypeScript 5 strict + Tailwind CSS 4 for a typesafe UI development loop with zero runtime CSS overhead
Architectural decisions were made to ensure the codebase remained safe to extend: public data flows through cached server components, admin mutations call revalidateTag() after every write, auth is checked at the middleware layer before any route renders, and every API response follows a consistent { data } / { error } shape.
Analysis and Findings
The single most consequential architectural decision was treating the CMS as a typed section model, not a freeform page builder. Rather than allowing arbitrary block structures, 22 section types were defined — each with a TypeScript interface, a Zod validation schema, an admin editor component, and a public renderer. This discipline delivered three compounding benefits:
- Correctness at the boundary - invalid section content is rejected at the API layer, not discovered at render time
- Predictable extensibility - adding a new section type requires changes to exactly five files; nothing else shifts
- Consistent rendering - every public page uses the same renderer dispatch logic, so design changes propagate everywhere at once
The marketing analysis revealed a second lesson: first-party campaign attribution built in early is worth ten times the same feature added later. Every tracked event you own is an event you'll never lose to a platform privacy policy change. The Marketing Command Center was wired into the platform from the start, not bolted on — every content view, CTA click, email open, and purchase flows through the same first-party event pipeline.
Solutions and Implementation
The platform shipped across six phases:
Phase 1 - Core Public Site
30+ public pages covering every brand pillar: About (Imagineer Framework, philosophy, Project Lifescape), Portfolio (projects, creative works, case studies), Content (blog, vlogs, gallery, IndieWeb notes feed), Community (book club, events), Shop, Resume, Services, Connect, and Now. SEO and GEO optimization, structured JSON-LD, AI crawler configuration, and entity-level metadata were wired into the global layout from day one.
Phase 2 - Admin Dashboard
30+ admin pages organized into six categories: Marketing, Content, Brand, Commerce, Community, and System. The dashboard home aggregates site health into a single view: banner performance, commerce funnel health, inbox SLA, subscriber growth, content freshness, and recent audit failures.
Phase 3 - CMS (22 Section Types)
A fully typed page content system covering every common content shape: heading, rich_text, card_grid, cta, quote, image, image_gallery, video, accordion, code_block, table, embed, button_group, parallax_panel, testimonial, timeline, stats, newsletter_signup, logos_row, pricing_table, spotify_now_playing, and divider.
Each section carries a display_config JSONB field governing three sub-systems: parallax (scroll-driven background depth), animation (scroll/hover/click/event triggers with custom keyframes and chained events), and visibility (conditional rendering by date range, URL parameter, or user auth state). Global sections allow a single section definition to be embedded across multiple pages. Draft Mode enables live preview via a server-side secret. Scheduled publishing sets future published_at timestamps without a separate cron job.
Phase 4 - Marketing Command Center
A unified campaign editor where every marketing push, funding drive, product launch, newsletter, event, flows through one pipeline from creation to analytics. Components: a 5-step campaign launch wizard, a health score rubric that grades campaign setup completeness (0–100 based on landing page, channels, short links, banners, emails, date range, and recent traffic), UTM-linked short links, a banner system with zone/page targeting and rotation groups, email drafting with open/click tracking injection, and multi-touch attribution (first-touch, last-touch, and linear models) computed from first-party events.
Phase 5 - Commerce & Community
Stripe Checkout with print-on-demand fulfillment via Printful and a webhook-driven state machine tracking order lifecycle. A paid Book Club with Stripe subscription tiers, a subscriber-gated library, and automated member management. IndieWeb publishing via Micropub and IndieAuth enables posting from any compliant third-party client — no platform lock-in for the public notes feed.
Phase 6 - Services & Estimators
Web Development and Brand Architecture service pages with public quote intake forms (Turnstile-protected, rate-limited). COCOMO-PERT cost models, implemented in TypeScript, power interactive admin estimators that pre-fill from incoming quote requests, turning inbound interest directly into scoped project estimates.
Conclusion and Lessons Learned
The platform now handles every operational function of an active personal brand in a single owned codebase: content creation, community management, print-on-demand commerce, email marketing with first-party attribution, client services intake, and a full admin observability layer. The 1,068-test suite enforces correctness across all layers. The layered caching architecture, typed CMS, and modular admin dashboard mean new features ship safely without risk to existing pages.
The site accomplishes its meta-goal: it is simultaneously the platform and the portfolio. Every decision, the CMS architecture, the marketing system, the performance tuning, the admin UX, is a visible demonstration of the web development and brand architecture practice it supports.
Three lessons that transfer beyond this project:
Type your content models before you build your editors. The discipline of defining section schemas first catches a class of bugs at design time that would otherwise surface in production. Freeform JSON accumulates silently; typed schemas fail loudly and early.
First-party analytics are a strategic asset, not a nice-to-have. Every tracked event you own is an event that can't be taken from you. Building the marketing attribution layer into the platform from the start, not added as a plugin later, made campaign measurement meaningfully more reliable.
A CMS for a solo operator should optimize for trust, not flexibility. Fixed section types with validation beat freeform blocks. Content rot is a real risk when there are no guardrails; a well-typed section library prevents the kind of silent drift that makes a site feel inconsistent two years later.
Project README
devinmarshall.info
Personal brand website for Devin Marshall — "Live by design, NOT by default."
Built with Next.js 16, React 19, TypeScript, and Tailwind CSS 4.
Includes IndieWeb publishing support with IndieAuth, Micropub, Webmention discovery, and lightweight note/reply/bookmark permalinks.
Tech Stack
| Layer | Technology | Version |
|---|---|---|
| Framework | Next.js (App Router, Turbopack) | 16.2.2 |
| UI Library | React | 19.2.3 |
| Language | TypeScript (strict) | 5 |
| Styling | Tailwind CSS (CSS custom properties) | 4 |
| Database | Supabase (PostgreSQL) | latest |
| Payments | Stripe (Checkout, Cash App Pay) | 20.4.1 |
| Fulfillment | Printful (REST API) | — |
| Google Workspace (Gmail API) | — | |
| Validation | Zod | 4 |
| Testing | Vitest | 4 |
| Icons | Lucide React, React Icons | latest |
| Fonts | Geist Sans & Geist Mono | built-in |
Getting Started
# Install dependencies
npm install
# Copy environment file and configure
cp .env.example .env.local
# Add Supabase credentials (from Supabase Dashboard → Settings → API)
# NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
# NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
# SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
# Create an admin user in Supabase Dashboard → Authentication → Users
# Run database migration (paste in Supabase SQL Editor)
# See supabase/migration.sql
# Seed database from JSON data (first-time setup only)
node scripts/seed-supabase.mjs
# Start development server
npm run dev
Open localhost or devinmarshall.info to view the site.
Environment Variables
See .env.example for required variables:
| Variable | Description |
|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Supabase project URL (from Dashboard → Settings → API) |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase anonymous/public key (from Dashboard → Settings → API) |
SUPABASE_SERVICE_ROLE_KEY |
Supabase service role key (server-side only, bypasses RLS) |
STRIPE_SECRET_KEY |
Stripe secret key (commerce — server-side only) |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY |
Stripe publishable key (commerce — client-side) |
STRIPE_WEBHOOK_SECRET |
Stripe webhook signing secret |
PRINTFUL_API_KEY |
Printful API token (commerce — server-side only) |
PRINTFUL_STORE_ID |
Printful store ID |
GMAIL_CLIENT_ID |
Google OAuth2 client ID (inbox — server-side only) |
GMAIL_CLIENT_SECRET |
Google OAuth2 client secret (inbox — server-side only) |
GMAIL_REFRESH_TOKEN |
Google OAuth2 refresh token (inbox — server-side only) |
GMAIL_USER_EMAIL |
Gmail user email address for @devinmarshall.info |
PUBLISH_API_KEY |
API key for external publishing via Obsidian plugin |
BLUESKY_HANDLE |
Bluesky handle (e.g. devinmarshall.bsky.social) |
BLUESKY_APP_PASSWORD |
Bluesky app password (AT Proto auth) |
MASTODON_INSTANCE_URL |
Mastodon instance base URL (e.g. https://mastodon.social) |
MASTODON_ACCESS_TOKEN |
Mastodon OAuth2 access token |
MEDIUM_INTEGRATION_TOKEN |
Medium integration token |
MEDIUM_AUTHOR_ID |
Medium author ID |
TWITTER_API_KEY |
Twitter/X OAuth 1.0a consumer key |
TWITTER_API_SECRET |
Twitter/X OAuth 1.0a consumer secret |
TWITTER_ACCESS_TOKEN |
Twitter/X OAuth 1.0a access token |
TWITTER_ACCESS_TOKEN_SECRET |
Twitter/X OAuth 1.0a access token secret |
LINKEDIN_ACCESS_TOKEN |
LinkedIn OAuth2 access token |
LINKEDIN_PERSON_URN |
LinkedIn person URN (urn:li:person:...) |
REDDIT_CLIENT_ID |
Reddit app client ID |
REDDIT_CLIENT_SECRET |
Reddit app client secret |
REDDIT_USERNAME |
Reddit account username |
REDDIT_PASSWORD |
Reddit account password |
REDDIT_DEFAULT_SUBREDDIT |
Default subreddit for Reddit posts (optional) |
THREADS_USER_ID |
Threads user ID |
THREADS_ACCESS_TOKEN |
Threads Graph API access token |
FACEBOOK_PAGE_ID |
Facebook Page ID |
FACEBOOK_PAGE_ACCESS_TOKEN |
Facebook Page access token |
SPOTIFY_CLIENT_ID |
Spotify app client ID (Now Playing widget) |
SPOTIFY_CLIENT_SECRET |
Spotify app client secret (Now Playing widget) |
SPOTIFY_REFRESH_TOKEN |
Spotify OAuth2 refresh token — run node scripts/get-spotify-token.mjs to generate |
AWS_SNS_ACCESS_KEY_ID |
AWS IAM access key ID (SMS notifications via SNS) |
AWS_SNS_SECRET_ACCESS_KEY |
AWS IAM secret access key (SMS notifications via SNS) |
AWS_SNS_REGION |
AWS region for SNS (e.g. us-east-1) |
Project Structure
src/
├── app/ # Next.js App Router pages
│ ├── layout.tsx # Root layout (ThemeProvider, Header, Footer)
│ ├── page.tsx # Landing page (/)
│ ├── globals.css # Design tokens & base styles
│ ├── home/ # /home
│ ├── about/ # /about, /philosophy, /imagineer-framework, /project-lifescape
│ ├── portfolio/ # /portfolio plus item detail pages for projects, creative works, and case studies
│ ├── content/ # /blog, /vlogs, /gallery
│ ├── resources/ # /resources, /reading-list, /tools, /downloads
│ ├── login/ # /login — unified login (email+password; role-based redirect; ?next= support)
│ ├── signup/ # /signup — unified signup (email confirmation; password requirements)
│ ├── recover/ # /recover — password recovery (?mode=reset shows reset form)
│ ├── account/ # /account — unified account (auth required; subscription + security)
│ ├── community/ # /community — layout + Book Club (/community/book-club/*), Events (planned)
│ ├── book-club/ # old /book-club/* — files still present; all URLs 301-redirect to /community/book-club/* via next.config.ts
│ ├── resume/ # /resume
│ ├── connect/ # /connect
│ ├── brand-identity/ # /brand-identity (public brand guidelines & visual language)
│ ├── go/ # /go/[slug] — tracked short link redirects (302 + click recording)
│ ├── admin/ # Admin dashboard (authenticated, 6-category sidebar, no footer)
│ │ ├── marketing/ # Marketing Intelligence Dashboard
│ │ ├── campaigns/ # Campaign CRUD + /campaigns/new (Launch Wizard)
│ │ ├── landing-pages/ # Landing page editor + templates
│ │ └── portfolio/ # Portfolio CRUD + relationship editor with duplicate and case-study-from-project shortcuts
│ ├── api/ # API routes (auth, images, social-links, upload, contact, gallery, checkout, webhooks, newsletter, admin CRUD, book-club, campaigns, marketing, portfolio relationships)
│ │ ├── campaigns/track/ # Public event tracking (page_view, cta_click, purchase) + email pixel/click
│ │ └── admin/marketing/ # Marketing dashboard data, comparison, attribution
│ └── ... # booking, community, fund, now, media, shop, support, search, legal
├── components/
│ ├── layout/ # Header (with UserMenu), Footer (hidden on admin via body.admin-page CSS)
│ ├── ui/ # ContactForm, NewsletterForm, ThemeToggle, ThemeProvider, ThemeLogo, HeroSection
│ ├── shop/ # BuyButton (client-side Stripe checkout)
│ ├── admin/ # AdminStub, RevisionHistory, SyndicationPublisher (unified POSSE compose/publish UI)
│ │ # SyndicationTextGenerator, SyndicationPanel (deprecated — superseded by SyndicationPublisher)
│ │ # NotificationBell (bell icon + unread badge + dropdown; Supabase Realtime-powered)
│ └── ui/ # BannerZone (server component — affiliate ad renderer)
├── lib/
│ ├── data/ # Supabase data access layer (social-links, images, contact, uploads, products, orders, revisions, inbox, banners, events, resources, community, funding, content, testimonials, now, order-analytics, syndications, webmentions, notifications, campaigns, campaign-updates, landing-pages, short-links, featured, search-telemetry, search-index, resume, donations, donation-methods, impact-areas, brand-config, site-settings, cart, page-content, book-club, newsletters)
│ ├── syndication-connectors/ # POSSE platform connectors — lazy-loaded per dispatch
│ │ ├── types.ts # Shared interfaces: PlatformConnector, SyndicationPayload, SyndicationResult, ConnectorOptions
│ │ ├── registry.ts # Capability map, isPlatformConfigured(), loadConnector() dynamic import
│ │ ├── media.ts # fetchImageAsBuffer(), selectImageUrl(), PLATFORM_IMAGE_LIMITS
│ │ ├── bridgy.ts # pingBridgy(), pingBridgyForResults() — IndieWeb backfeed via brid.gy
│ │ ├── bluesky.ts # AT Proto (com.atproto + app.bsky.feed.post), blob upload
│ │ ├── mastodon.ts # Mastodon v2/media + v1/statuses
│ │ ├── medium.ts # Medium API v1 with canonical URL footer
│ │ ├── twitter.ts # OAuth 1.0a built on Node crypto, v2/tweets
│ │ ├── linkedin.ts # LinkedIn REST API (202401), x-restli-id header
│ │ ├── reddit.ts # Password grant auth + oauth/api/submit
│ │ ├── threads.ts # Two-step container + publish flow (5s delay)
│ │ └── facebook.ts # Graph API v21.0 feed post
│ ├── types/ # TypeScript type definitions (images.ts, notification.ts)
│ ├── supabase.ts # Supabase client (lazy-initialized, server-side only)
│ ├── constants.ts # Brand pillars, nav config, social link defaults, CAMPAIGN_TRANSITIONS, DEFAULT_CHANNELS_BY_CATEGORY, CAMPAIGN_STATUSES
│ ├── notification-constants.ts # Notification event types, categories, channels (single source of truth)
│ ├── platform-icons.tsx # 34 platform brand icon mappings
│ ├── auth.ts # Supabase Auth session helpers
│ ├── env.ts # Central environment validation (fail-fast, commerce + SYNDICATION_ENV + SNS_ENV)
│ ├── rate-limit.ts # Supabase-backed persistent rate limiter
│ ├── commerce.ts # Shared commerce helpers (normalization, mapping)
│ ├── stripe.ts # Stripe server helpers (Checkout, webhooks)
│ ├── printful.ts # Printful API client (fulfillment)
│ ├── gmail.ts # Gmail API client (OAuth2, fetch-based)
│ ├── gmail-sync.ts # Gmail sync engine (full + incremental)
│ ├── audit-log.ts # Audit log helper
│ ├── notifications.ts # Notification dispatcher (fire-and-forget, dedup, email + SMS channels)
│ ├── sns.ts # AWS SNS client (lazy-initialized, E.164 validation, SMS transactional)
│ ├── newsletter-helpers.ts # Newsletter campaign helpers (send, segment, instrumentEmailHtml for tracking pixel/click injection)
│ ├── campaign-tracking.ts # Campaign event tracking helpers, visitor cookie management
│ ├── syndication-helpers.ts # POSSE text generators for 13 platforms (Twitter/X, LinkedIn, Bluesky, Mastodon, Threads, Reddit, + 7 more)
│ ├── markdown.ts # Markdown → HTML rendering; `renderMarkdown()` (block, GFM+breaks) + `renderInlineMarkdown()` (inline, no block wrappers, XSS-sanitized)
├── middleware.ts # Auth guard (admin + /community/book-club/library|account), CSRF, rate limiting, CSP headers; unauthenticated redirect to /login?next=
supabase/
├── migration.sql # Database schema (10 tables, indexes, RLS policies)
├── phase4.sql # Content entries schema
├── phase5.sql # Commerce schema (10 commerce tables)
├── phase6.sql # Inbox schema (4 tables)
├── phase7.sql # Banners schema (affiliate ads)
├── phase8.sql # RLS policies for banners/banner_events
├── phase9.sql # Newsletter campaigns schema
├── phase10-notifications.sql # Notifications table + Realtime publication + site_settings seed (43 preference rows)
├── phase11-landing-positions.sql # Normalize JSONB field names in landing_sections + seed image position defaults
├── migrations/ # Incremental migrations (hero media, now page, resume, donations, syndication+webmentions,
│ # book club, landing pages, hero style, page content, campaigns,
│ # syndication_enhancements, section injection, short links, visitor tracking,
│ # campaign emails, campaign channels, campaign creatives, LP templates)
scripts/
├── seed-supabase.mjs # One-time data migration from JSON to Supabase
├── seed-founding-supporters.mjs # Seed founding supporters data
├── seed-page-content.mjs # Seed per-page content slots
├── seed-resume.mjs # Seed resume/profile data
tests/
├── env.test.ts # Environment validation tests
├── password-validation.test.ts # Password strength rule tests
├── upload-validation.test.ts # SVG safety & filename validation tests
├── contact-validation.test.ts # Contact form validation tests
├── middleware-auth.test.ts # RBAC & route classification tests
├── recovery-validation.test.ts # Password recovery tests
├── commerce-foundation.test.ts # Commerce env, Stripe params, Printful helpers
├── commerce-runtime.test.ts # Checkout normalization, order mapping
├── webhook-checkout.test.ts # Checkout normalization, shipping, fulfillment
├── validation.test.ts # Shared validation helpers (parseBody, Zod schemas)
├── banner-validation.test.ts # Banner URL, zone, size, scheduling validation
├── inbox-sync.test.ts # Gmail sync engine tests
├── reading-time.test.ts # Blog reading time estimation
├── order-analytics.test.ts # Order analytics snapshot & trend queries
├── newsletter-subscribe.test.ts # Newsletter subscribe endpoint (rate limit, idempotent)
├── admin-analytics-api.test.ts # Analytics API (site health, banner, commerce)
├── admin-content-api.test.ts # Content admin CRUD + auth guards
├── admin-events-api.test.ts # Events admin CRUD + auth guards
├── admin-resources-api.test.ts # Resources admin CRUD + auth guards
├── admin-community-api.test.ts # Community admin CRUD + duplicate handling
├── admin-funding-api.test.ts # Funding campaigns & tiers CRUD + auth guards
├── admin-products-api.test.ts # Products admin CRUD + auth guards
├── newsletter-helpers.test.ts # Newsletter campaign helper tests
├── admin-syndications-api.test.ts # Syndications admin CRUD + auth guards
├── webmention-endpoint.test.ts # Public /api/webmention endpoint (W3C spec, Bridgy detection)
├── syndication-connectors.test.ts # POSSE connector unit tests (Bluesky, Mastodon, Medium — dry-run, success, failure)
├── syndication-publish.test.ts # /api/admin/syndications/publish endpoint tests
├── bridgy-backfeed.test.ts # Bridgy backfeed + pingBridgy / pingBridgyForResults unit tests
├── cart-context.test.ts # Cart context helpers
├── checkout-custom-mode.test.ts # Checkout custom mode tests
├── markdown-sanitization.test.ts # Markdown XSS sanitization tests
├── page-content-slots.test.ts # Page content slot tests
├── portal-api.test.ts # Shop portal API tests
├── portal-onboarding.test.ts # Shop portal onboarding tests
├── admin-newsletters-api.test.ts # Newsletters admin CRUD + send + auth guards
├── notifications.test.ts # Notification dispatcher (channels, dedup, error isolation, auditLog)
├── notifications-api.test.ts # Notifications + notification-preferences API route tests
├── marketing-command-center.test.ts # Marketing Command Center (84 tests — state machine, wizard, health score rubric, attribution, email tracking)
├── campaign-analytics.test.ts # Campaign analytics & metrics
├── campaign-marketing.test.ts # Campaign marketing helpers
├── featured-items.test.ts # Featured item spotlight tests
├── search-telemetry.test.ts # Search telemetry tracking tests
└── gallery-image-metadata.test.ts # Gallery alt text editing rules, isFilenameAlt, updateImageMetaInAlbum (14 tests)
├── BRAND_IDENTITY.md # Full brand identity & design system
├── SITE_MAP.md # Website information architecture
├── Unfinished_Implementations.md # Known gaps and partial implementations
├── OBSIDIAN_PLUGIN.md # Obsidian publishing plugin setup
└── README.md # This file
Key Features
- Full-bleed hero sections — viewport-height hero images and videos on all 16 pages, hidden by default until media is uploaded. Admin-configurable fit, position, and text overlay controls. Supports video heroes (MP4, WebM, OGG) via reusable
HeroSectioncomponent. - Dark mode default with light mode toggle (persisted via localStorage, anti-FOUC inline script prevents theme flash)
- Admin dashboard with Supabase Auth (cookie-based sessions via
@supabase/ssr) for managing hero images, card images, gallery albums, social links, and hero content - Image management — upload images and videos (up to 100MB) via signed URL direct-to-Supabase-Storage flow, fit/position controls, profile images, gallery albums — all from the admin panel. Gallery images support click-to-edit alt text + caption via a modal overlay, missing-alt indicators (amber dot per thumbnail, count badge per album), and hover/tap gradient overlays on public gallery surfaces (portfolio detail pages + gallery page album modal).
- 55+ social platform links with conditional visibility and brand icons
- SEO optimized — OpenGraph/Twitter cards, JSON-LD structured data, dynamic sitemap, robots.txt
- Accessible — skip-to-content, focus-visible styles, ARIA attributes, keyboard navigation
- Security hardened — CSP headers, CSRF validation, Content-Type enforcement, HSTS, Supabase-backed audit logging, SVG upload sanitization
- RBAC enforced — admin role via Supabase
user_metadata.role, checked in middleware andisAdmin()helper - Password strength — minimum 12 characters with uppercase, lowercase, digit, and special character requirements
- Account recovery — email-based password recovery for locked-out admins via Supabase Auth
- Fully responsive — mobile-first layouts with
100dvhdynamic viewport units, tested at 375px, 768px, 1280px+ - Supabase-backed — all data stored in PostgreSQL via Supabase with Row Level Security, persistent rate limiting and audit logging. List queries support pagination.
- Commerce system — Stripe + Cash App Pay checkout, Printful print-on-demand fulfillment, admin product & variant management, order tracking, webhook-driven state machine for payments and fulfillment
- Gmail inbox — Google Workspace integration for
@devinmarshall.infoemail; admin inbox with read, reply, archive, star, trash; incremental sync via Gmail History API; on-demand attachment downloads - Affiliate banners — admin-managed banner ads with 7 IAB sizes, 6 placement zones (hero-below, mid-content, pre-footer, sidebar, in-grid, header-bar), page targeting, date-based scheduling, priority resolution; zero-footprint
BannerZoneserver component renders nothing when inactive. Impression/click tracking viabanner_eventstable, tag-based cache invalidation, weighted rotation for same-priority banners, device-aware rendering, and admin analytics dashboard. - Newsletter — functional subscribe/unsubscribe flow; public POST endpoint with rate limiting (3 per 15 min per IP), email validation, idempotent for existing subscribers; unsubscribe via GET endpoint returning styled HTML page;
NewsletterFormcomponent with loading/success/error states - Admin dashboard — dashboard home page displays site health metrics, banner performance (impressions, clicks, CTR, top banners), commerce metrics (orders, revenue, AOV, status breakdowns, top products, daily trends), brand performance, security status, and recent audit activity. Sidebar navigation organized into six categories (Marketing, Content, Brand, Commerce, Community, System). Site footer hidden on admin pages.
- Marketing Command Center — unified marketing system from idea to analytics. Unified campaign editor at
/admin/campaigns/[id]with tab registry (11 tabs filtered bycampaign_type). Marketing campaigns: settings, channels, banners, emails, creatives, analytics, revisions. Funding campaigns: settings, channels, banners, tiers, milestones, updates, supporters, analytics, revisions. Launch Wizard with Step 0 type-select (marketing: 5 steps; funding: adds goal/dates/initial-tier step). Lifecycle state machine (draft → scheduled → active → paused → completed). Marketing Intelligence Dashboard at/admin/marketingwith health scores (0-100, setup-completeness rubric withfailingChecksbreakdown), funnel visualization, campaign comparison, channel performance, email analytics, and multi-touch attribution (first-touch, last-touch, linear models). Email tracking via pixel + click redirect withinstrumentEmailHtml(). Visitor identity stitching via first-partyvisitor_idcookie. Campaign ↔ newsletter bidirectional linking. Clone campaigns with deep copy. Default channel templates per category. Phase 1 schema migration unified marketing and funding campaigns viacampaign_type_enumandfunding_campaigns.marketing_campaign_idFK. - Page Content CMS — 15-section-type CMS powering all static pages and enabling true new-page creation from the admin. Section types:
heading,rich_text,card_grid,cta,quote,image,image_gallery,video,divider,accordion,code_block,table,embed,button_group,spotify_now_playing. Pages created through the CMS publish as first-class public routes at/{slug}via a catch-all dynamic route. Reserved slugs (all existing route folders) are blocked at the API and admin UI. New pages start from one of 4 starter templates (Standard, Services, Landing, Announcement) that auto-seed sections on creation. Every text field is markdown-native: multi-line prose fields use the MarkdownToolbar +renderMarkdown()(block), and short fields (titles, captions, labels, table cells) userenderInlineMarkdown()(inline, no<p>wrapper). Admin hint text appears beneath every inline-capable input. Sections have an editablesectionKey, custom injection point + mode, and a Change Type control that resets content to empty. Default keys likeintrocan now replace page H1/subtitle/description content directly, and the Aboutbiokey uses a dedicated structured editor with headline, summary, role tags, and a public-style preview. - Spotify Now Playing —
spotify_now_playingCMS section type renders a live widget showing what Devin is listening to via the Spotify Web API. Falls back to most-recently played, and now shows a usable playback action even when Spotify preview clips are unavailable. Card or compact style. RequiresSPOTIFY_CLIENT_ID,SPOTIFY_CLIENT_SECRET,SPOTIFY_REFRESH_TOKEN. Token generated vianode scripts/get-spotify-token.mjs. - Now Page — markdown-native
/nowpage showing what Devin is currently focused on. Editable from admin dashboard at/admin/nowwith draft/published visibility toggle and live markdown preview. Inspired by the nownownow.com movement. - Obsidian publishing — Obsidian plugin publishes notes directly to the blog via
/api/publish(API key auth, upsert-by-slug). Supports multiple sites — add any website running the publish endpoint. - Public brand identity page —
/brand-identitydisplays brand pillars, narrative, voice & tone, color palette (with copy-hex buttons), typography, visual assets, and moodboard grid — all driven by the admin brand config - POSSE / IndieWeb — Full POSSE stack (Publish on your Own Site, Syndicate Elsewhere). One-click parallel dispatch to up to 8 live platforms (Twitter/X, LinkedIn, Bluesky, Mastodon, Threads, Reddit, Medium, Facebook) via the
SyndicationPublishercomponent andPOST /api/admin/syndications/publish. Each connector lives insrc/lib/syndication-connectors/. Compose step: per-platform custom text with char counters, image attach, article mode, dry-run. Platform health dashboard on/admin/syndications. Bridgy backfeed auto-pings after publishing to Twitter/Mastodon/Bluesky so likes, reposts, and replies flow back as webmentions.content_syndicationsstoreserror_message,platform_post_id, andmedia_urlper attempt. Blog posts display syndication links withu-syndicationmicroformat class. W3C-compliant webmention receiving endpoint (/api/webmention) with rate limiting, form-encoded + JSON support, and 202 Accepted responses. Verified webmentions show like/reply/repost counts on blog posts.<link rel="webmention">discovery header on all blog posts. Admin manages incoming webmentions at/admin/webmentionswith per-type stats, bulk-verify, and individual approve/reject. - Book Club — Subscription-gated digital library with Stripe billing. Members get unlimited access to PDF/EPUB books stored in a private Supabase bucket. Features: 127+ genre taxonomy with hierarchy, curated collections, per-user reading progress, star ratings & reviews, discussion prompts, book club events, 5% free preview enforcement, admin CRUD at
/admin/book-club, and admin notification emails to all active subscribers via Gmail. Public routes live at/community/book-club/**, with the older/book-club/**URLs permanently redirected. - Admin contact inbox — Admin
/admin/inboxincludes a "Contact Form" tab alongside Gmail, showing all contact form submissions with full detail view. Delete functionality hard-removes submissions with audit log. - Admin notification system — Real-time admin dashboard notifications for 10 event types (new orders, contact submissions, new subscribers, Stripe errors, fulfillment updates, webmentions, community activity, donations, content published, system errors). Bell icon in top bar with unread badge and dropdown preview; dedicated
/admin/notificationspage with date grouping, filters, bulk delete, and cleanup. Three delivery channels: dashboard (Supabase), email (Gmail), and SMS (AWS SNS). Per-event channel toggles in Settings → Notifications. Fire-and-forget dispatcher with SHA-256 deduplication (5-minute window),Promise.allSettledper-channel isolation, andauditLogon failures. Realtime badge updates via Supabase Realtime. - Tested — 847 tests via Vitest across 76 files covering env validation, password strength, upload/SVG safety, contact validation, recovery, RBAC middleware, commerce logic, inbox sync, banner validation, newsletter helpers, newsletter admin, order analytics, webmention endpoint, syndications admin, syndication connectors, syndication publish endpoint, Bridgy backfeed, marketing command center (84 tests: state machine, wizard, health score rubric with HealthScoreInput/HealthScoreResult, attribution, email tracking), campaign analytics, featured items, search telemetry, markdown sanitization (block + inline XSS), page content slots, page section rendering parity, intro-heading editing, the About bio editor, CMS page creation (reserved slugs, starter templates), all admin CRUD routes (content, events, resources, community, funding, products, newsletters), and gallery image metadata (isFilenameAlt, alt length validation, updateImageMetaInAlbum state transform)
- Vercel Analytics — privacy-friendly analytics via
@vercel/analytics - Performance optimized — public data getters cached via
unstable_cache(300s TTL, tag-based invalidation on admin writes), responsivesizeson all fill images, anti-FOUC theme detection
Scripts
npm run dev # Start dev server (Turbopack)
npm run build # Production build
npm run start # Start production server
npm run lint # Run ESLint
npm test # Run Vitest test suite
npm run test:watch # Run tests in watch mode
# Database
node scripts/seed-supabase.mjs # Seed Supabase from JSON files (first-time only)
node scripts/seed-page-content.mjs # Seed per-page content slots
node scripts/seed-resume.mjs # Seed resume/profile data
node scripts/seed-founding-supporters.mjs # Seed founding supporters
Brand
| Element | Value |
|---|---|
| Primary Color | Flame Red #D33D3C |
| Secondary Color | Steel Gray #AFAEAE |
| Dark Background | #1A1A1A |
| Tagline | "Live by design, NOT by default." |
| Motto | "Imagine. Design. Build." |
| Brand Pillars | The Imagineer · The Builder · The Analyst · The Storyteller · The Creator · The Veteran |
Full brand identity documented in BRAND_IDENTITY.md.
Documentation
| Document | Description |
|---|---|
| BRAND_IDENTITY.md | Brand identity, color theory, typography, voice & tone, pillars |
| SITE_MAP.md | Complete website information architecture & page descriptions |
| Unfinished_Implementations.md | Known gaps, partial implementations, and deferred work |
| OBSIDIAN_PLUGIN.md | Obsidian publishing plugin setup, usage & architecture |
License
Private project. All rights reserved.
Project Gallery
This is the gallery album for the 'DevinMarshall.info — Digital Home of an Imagineer' project.


