From 0f185c838af91900499fda39ba20dd15d44e9d23 Mon Sep 17 00:00:00 2001 From: Chad Nuttall Date: Sat, 3 Oct 2026 18:33:10 -0400 Subject: [PATCH 1/3] feat(brand): wire hoppy turtle brand system into site and trade email - Regenerate favicon.ico/PNG icons and app icons from the Option C circular badge; add light/dark PNG variants and adaptive favicon.svg - Update layout icon metadata (SVG + media-hinted PNG fallbacks) and manifest icon purposes - Add reusable BrandMark component (black-on-light / white-on-dark) and wire into headers and footer - Wrap outbound trade email in a branded table shell with the email mark resolved to an absolute siteUrl URL; plain-text part unchanged - Polish admin trade timeline: subject display, quieter quoted text via splitQuotedEmailText, outbound brand avatar, separated actions - Document the brand mark hierarchy in THEME_AND_BRANDING.md and the email shell in TECHNICAL.md Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- THEME_AND_BRANDING.md | 34 ++++ app/layout.tsx | 31 +++- components/admin-trade-workspace.tsx | 150 ++++++++++++------ components/brand-mark.tsx | 46 ++++++ components/site-footer.tsx | 6 +- components/site-header-default.tsx | 4 +- components/site-header.tsx | 4 +- docs/TECHNICAL.md | 7 +- lib/trade-leads-email-common.ts | 99 ++++++++++++ lib/trade-leads-email.ts | 8 +- public/android-chrome-192x192.png | Bin 20102 -> 14842 bytes public/android-chrome-512x512.png | Bin 54540 -> 34044 bytes public/apple-touch-icon.png | Bin 17728 -> 13636 bytes public/brand/avatar-dark-512.png | Bin 0 -> 19350 bytes public/brand/avatar-light-512.png | Bin 0 -> 20950 bytes public/brand/avatar-transparent-black-512.png | Bin 0 -> 33747 bytes public/brand/avatar-transparent-white-512.png | Bin 0 -> 30765 bytes public/brand/email-mark-black.png | Bin 0 -> 51498 bytes public/brand/email-mark-white.png | Bin 0 -> 49158 bytes public/brand/hoppy-turtle-black-1600.png | Bin 0 -> 164406 bytes public/brand/hoppy-turtle-white-1600.png | Bin 0 -> 148216 bytes public/favicon-16x16-dark.png | Bin 0 -> 528 bytes public/favicon-16x16.png | Bin 477 -> 521 bytes public/favicon-32x32-dark.png | Bin 0 -> 1221 bytes public/favicon-32x32.png | Bin 1253 -> 1212 bytes public/favicon-48x48.png | Bin 0 -> 1969 bytes public/favicon.ico | Bin 15406 -> 15086 bytes public/favicon.svg | 6 + public/site.webmanifest | 25 ++- tests/brand-assets.test.ts | 102 ++++++++++++ tests/lib/trade-leads-email.test.ts | 90 +++++++++++ 31 files changed, 558 insertions(+), 54 deletions(-) create mode 100644 components/brand-mark.tsx create mode 100644 public/brand/avatar-dark-512.png create mode 100644 public/brand/avatar-light-512.png create mode 100644 public/brand/avatar-transparent-black-512.png create mode 100644 public/brand/avatar-transparent-white-512.png create mode 100644 public/brand/email-mark-black.png create mode 100644 public/brand/email-mark-white.png create mode 100644 public/brand/hoppy-turtle-black-1600.png create mode 100644 public/brand/hoppy-turtle-white-1600.png create mode 100644 public/favicon-16x16-dark.png create mode 100644 public/favicon-32x32-dark.png create mode 100644 public/favicon-48x48.png create mode 100644 public/favicon.svg create mode 100644 tests/brand-assets.test.ts diff --git a/THEME_AND_BRANDING.md b/THEME_AND_BRANDING.md index f94ffaf..9a92aaf 100644 --- a/THEME_AND_BRANDING.md +++ b/THEME_AND_BRANDING.md @@ -376,6 +376,40 @@ colors: { --- +## Brand Marks + +The brewery's primary brand mark is the **hoppy turtle** — a monochrome +line-art anchor/turtle/hop mark. The stacked wordmark is secondary and is +not used at icon sizes. + +### Mark inventory (`public/`) + +| File | Artwork | Used for | +| --- | --- | --- | +| `brand/hoppy-turtle-{black,white}-1600.png` | Full mark, 1600px master | Large placements only — never icons or email headers | +| `brand/email-mark-{black,white}.png` | Same mark, 600×503 | Site UI (`components/brand-mark.tsx`), outbound trade email header (black variant) | +| `brand/avatar-{light,dark}-512.png` | Circular badge on solid bg | App-icon source (apple-touch-icon, android-chrome) | +| `brand/avatar-transparent-{black,white}-512.png` | Circular badge, transparent | Small admin/UI badges (trade timeline outbound avatar) | +| `favicon.ico`, `favicon-{16,32,48}x48*.png`, `favicon.svg` | Circular badge raster set | Browser favicons — the SVG adapts to dark browser chrome via `prefers-color-scheme` | + +### Rules + +* **Monochrome only.** Black artwork on light surfaces, white artwork on + dark surfaces. Never recolor, invert with CSS filters, or place color + variants. +* **Tiny contexts use the circular badge** (`avatar-*`, `favicon-*`), + never the full mark or wordmark — the linework collapses below ~32px. +* Site UI uses `components/brand-mark.tsx` (`tone="black"|"white"`, + `decorative` when adjacent text already names the brand). +* Email uses absolute URLs on the canonical site origin — relative paths + never resolve inside a mail client. +* App icons are `purpose: "any"`; no maskable assets exist yet (the badge + ring would clip in the safe zone) — a dedicated maskable asset is a + possible follow-up. +* BIMI (the turtle in the inbox sender column) is a future + email-infrastructure task — it needs a VMC + DNS work and is not + implemented. + ## Success Criteria The theme is correct when: * The site is calm and readable diff --git a/app/layout.tsx b/app/layout.tsx index 06d38b5..f4442d4 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -105,8 +105,35 @@ export const metadata: Metadata = { }, icons: { icon: [ - { url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" }, - { url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" }, + // SVG first — the circular badge mark adapts to light/dark browser + // chrome internally via prefers-color-scheme. PNG fallbacks below + // carry explicit media hints for browsers that ignore the SVG. + { url: "/favicon.svg", type: "image/svg+xml" }, + { + url: "/favicon-16x16.png", + sizes: "16x16", + type: "image/png", + media: "(prefers-color-scheme: light)", + }, + { + url: "/favicon-16x16-dark.png", + sizes: "16x16", + type: "image/png", + media: "(prefers-color-scheme: dark)", + }, + { + url: "/favicon-32x32.png", + sizes: "32x32", + type: "image/png", + media: "(prefers-color-scheme: light)", + }, + { + url: "/favicon-32x32-dark.png", + sizes: "32x32", + type: "image/png", + media: "(prefers-color-scheme: dark)", + }, + { url: "/favicon-48x48.png", sizes: "48x48", type: "image/png" }, { url: "/favicon.ico" }, ], apple: "/apple-touch-icon.png", diff --git a/components/admin-trade-workspace.tsx b/components/admin-trade-workspace.tsx index 1b5cdad..2c41ab6 100644 --- a/components/admin-trade-workspace.tsx +++ b/components/admin-trade-workspace.tsx @@ -51,6 +51,7 @@ import { type TradeLeadView, } from "@/lib/trade-leads-admin-common"; import { + splitQuotedEmailText, TRADE_EMAIL_BODY_MAX, TRADE_EMAIL_SUBJECT_MAX, type TradeLeadCommunicationView, @@ -1340,12 +1341,28 @@ export function AdminTradeWorkspace({ user }: { user: AdminPanelUser }) { const comm = commId ? communicationsById.get(commId) : undefined; + const isOutboundEmail = + comm?.direction === "outbound"; return (
  • -