11/**
22 * GhostCode Dynamics — Brand Logo System
33 * ---------------------------------------------------------------
4- * This file is the single source of truth for the brand mark across
5- * the navbar, footer, mobile menu, favicon, OG images, etc.
4+ * Single source of truth for the brand mark.
65 *
7- * REPLACING THE PLACEHOLDER WITH THE FINAL PNG (≤ 2 min):
8- * 1. Drop the final files into `src/assets/brand/`:
9- * - logo-mark.png (square icon, ≥ 512×512)
10- * - logo-mark-light.png (optional, for light backgrounds)
11- * - logo-full.png (optional full lockup; if absent we
12- * keep the SVG monogram + typeset wordmark)
13- * 2. Flip `USE_PNG_MARK = true` below.
14- * 3. (Optional) Replace `public/favicon.svg` with the new icon.
15- *
16- * Nothing else in the codebase needs to change — every surface
17- * imports <Logo />, <LogoCompact />, or <LogoMark /> from this file.
6+ * REPLACING THE PLACEHOLDER:
7+ * Drop the final file at `src/assets/brand/logo.png` (same path).
8+ * No code change needed — every surface reads from this file.
9+ * If the PNG is ever missing, the inline SVG fallback renders
10+ * automatically so layout never breaks.
1811 * ---------------------------------------------------------------
1912 */
2013
14+ import { useState } from "react" ;
2115import { cn } from "@/lib/utils" ;
22-
23- // Toggle this to `true` once the founder ships the final PNG assets.
24- const USE_PNG_MARK = false ;
25-
26- // When USE_PNG_MARK is true these are imported lazily by <LogoMark />.
27- // Keeping them as string paths means the build never fails if the
28- // PNGs are not yet present in the repo.
29- const PNG_MARK_DARK = "/brand/logo-mark.png" ;
30- const PNG_MARK_LIGHT = "/brand/logo-mark-light.png" ;
16+ import logoPng from "@/assets/brand/logo.png" ;
3117
3218// ---------------------------------------------------------------
33- // Variant: <LogoMark /> — icon only (favicon, app icon, avatar)
19+ // <LogoMark /> — icon only, height-driven, aspect-preserving
3420// ---------------------------------------------------------------
3521
3622interface LogoMarkProps {
37- size ?: number ;
23+ /** Rendered height in px. Width auto-scales to preserve aspect ratio. */
24+ height ?: number ;
3825 className ?: string ;
39- /** Force a specific contrast mode. Defaults to currentColor (auto). */
4026 tone ?: "auto" | "dark" | "light" ;
4127}
4228
43- /**
44- * Ghost-in-monogram mark. Pure SVG so it is sharp on every density,
45- * inherits theme color via `currentColor`, and ships zero bytes of
46- * raster data until the real PNG is wired in.
47- */
48- export function LogoMark ( { size = 32 , className, tone = "auto" } : LogoMarkProps ) {
49- if ( USE_PNG_MARK ) {
50- const src = tone === "light" ? PNG_MARK_LIGHT : PNG_MARK_DARK ;
29+ export function LogoMark ( { height = 36 , className, tone = "auto" } : LogoMarkProps ) {
30+ const [ failed , setFailed ] = useState ( false ) ;
31+
32+ if ( ! failed && logoPng ) {
5133 return (
5234 < img
53- src = { src }
54- width = { size }
55- height = { size }
35+ src = { logoPng }
5636 alt = "GhostCode Dynamics"
57- className = { cn ( "shrink-0 select-none" , className ) }
37+ onError = { ( ) => setFailed ( true ) }
38+ style = { { height, width : "auto" } }
39+ className = { cn ( "shrink-0 select-none object-contain" , className ) }
5840 draggable = { false }
5941 />
6042 ) ;
6143 }
6244
45+ // ---- SVG fallback (only renders if PNG fails to load) ----
6346 const fg =
64- tone === "dark"
65- ? "#0a0a0f"
66- : tone === "light"
67- ? "#ffffff"
68- : "currentColor" ;
69-
47+ tone === "dark" ? "#0a0a0f" : tone === "light" ? "#ffffff" : "currentColor" ;
7048 return (
7149 < svg
72- width = { size }
73- height = { size }
50+ height = { height }
51+ width = { height }
7452 viewBox = "0 0 64 64"
7553 fill = "none"
7654 xmlns = "http://www.w3.org/2000/svg"
@@ -79,42 +57,30 @@ export function LogoMark({ size = 32, className, tone = "auto" }: LogoMarkProps)
7957 aria-label = "GhostCode Dynamics"
8058 shapeRendering = "geometricPrecision"
8159 >
82- { /* Rounded squircle backdrop — the monogram "G" */ }
8360 < rect x = "1" y = "1" width = "62" height = "62" rx = "16" fill = { fg } />
84-
85- { /* Ghost silhouette cut out of the squircle */ }
8661 < path
8762 d = "M32 16c-7 0-12 5-12 12v18.2c0 1.4 1.6 2.2 2.7 1.3l2.2-1.7c.6-.5 1.5-.5 2.1 0l2.2 1.7c.6.5 1.5.5 2.1 0l2.2-1.7c.6-.5 1.5-.5 2.1 0l2.2 1.7c1.1.9 2.7.1 2.7-1.3V28c0-7-5-12-12-12Z"
8863 fill = "var(--background)"
8964 />
90-
91- { /* Eyes — restate foreground for a crisp 3-tone read */ }
9265 < circle cx = "28.4" cy = "29.5" r = "1.9" fill = { fg } />
9366 < circle cx = "35.6" cy = "29.5" r = "1.9" fill = { fg } />
94-
95- { /* Subtle "C / D" tick: opening on the right side of the G */ }
96- < path
97- d = "M46 30v6"
98- stroke = "var(--background)"
99- strokeWidth = "3"
100- strokeLinecap = "round"
101- />
10267 </ svg >
10368 ) ;
10469}
10570
10671// ---------------------------------------------------------------
107- // Variant: <Logo /> — full lockup (icon + "GhostCode Dynamics" )
72+ // <Logo /> — full lockup (mark + wordmark )
10873// ---------------------------------------------------------------
10974
11075interface LogoProps {
11176 className ?: string ;
112- size ?: number ;
113- /** Hide the wordmark, keep only the mark. */
77+ /** Mark height in px. Defaults sized for navbar use. */
78+ height ?: number ;
11479 iconOnly ?: boolean ;
11580}
11681
117- export function Logo ( { className, size = 32 , iconOnly = false } : LogoProps ) {
82+ export function Logo ( { className, height, iconOnly = false } : LogoProps ) {
83+ // Responsive default: 36px mobile → 42px desktop. Caller can override.
11884 return (
11985 < span
12086 className = { cn (
@@ -123,17 +89,24 @@ export function Logo({ className, size = 32, iconOnly = false }: LogoProps) {
12389 ) }
12490 aria-label = "GhostCode Dynamics"
12591 >
126- < LogoMark size = { size } />
92+ { height ? (
93+ < LogoMark height = { height } />
94+ ) : (
95+ < >
96+ < LogoMark height = { 36 } className = "sm:hidden" />
97+ < LogoMark height = { 42 } className = "hidden sm:block" />
98+ </ >
99+ ) }
127100 { ! iconOnly && < Wordmark /> }
128101 </ span >
129102 ) ;
130103}
131104
132105// ---------------------------------------------------------------
133- // Variant: <LogoCompact /> — icon + "GCD" (tight navs, mobile)
106+ // <LogoCompact /> — icon + "GCD"
134107// ---------------------------------------------------------------
135108
136- export function LogoCompact ( { className, size = 28 } : LogoProps ) {
109+ export function LogoCompact ( { className, height = 32 } : LogoProps ) {
137110 return (
138111 < span
139112 className = { cn (
@@ -142,7 +115,7 @@ export function LogoCompact({ className, size = 28 }: LogoProps) {
142115 ) }
143116 aria-label = "GhostCode Dynamics"
144117 >
145- < LogoMark size = { size } />
118+ < LogoMark height = { height } />
146119 < span className = "font-display text-base font-semibold tracking-[0.18em] text-foreground" >
147120 GCD
148121 </ span >
@@ -151,7 +124,7 @@ export function LogoCompact({ className, size = 28 }: LogoProps) {
151124}
152125
153126// ---------------------------------------------------------------
154- // Wordmark — geometric, Linear / Vercel / Stripe inspired
127+ // Wordmark
155128// ---------------------------------------------------------------
156129
157130function Wordmark ( ) {
@@ -167,5 +140,4 @@ function Wordmark() {
167140 ) ;
168141}
169142
170- // Re-export the icon under its previous name to keep older imports working.
171143export { LogoMark as GhostIcon } ;
0 commit comments