33 * ---------------------------------------------------------------
44 * Single source of truth for the brand mark.
55 *
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.
6+ * Assets live in `src/assets/brand/`:
7+ * - logo-dark.png (white art, used in dark mode)
8+ * - logo-light.png (black art, used in light mode)
9+ * - icon-dark.png (ghost-G mark, white, dark mode)
10+ * - icon-light.png (ghost-G mark, black, light mode)
11+ *
12+ * To replace the brand: swap the PNGs at the same paths. No code change.
1113 * ---------------------------------------------------------------
1214 */
1315
14- import { useState } from "react" ;
1516import { cn } from "@/lib/utils" ;
16- import logoPng from "@/assets/brand/logo.png" ;
17+ import logoDark from "@/assets/brand/logo-dark.png" ;
18+ import logoLight from "@/assets/brand/logo-light.png" ;
19+ import iconDark from "@/assets/brand/icon-dark.png" ;
20+ import iconLight from "@/assets/brand/icon-light.png" ;
1721
1822// ---------------------------------------------------------------
19- // <LogoMark /> — icon only, height-driven, aspect-preserving
23+ // <LogoMark /> — ghost icon only
2024// ---------------------------------------------------------------
2125
2226interface LogoMarkProps {
23- /** Rendered height in px. Width auto-scales to preserve aspect ratio. */
2427 height ?: number ;
2528 className ?: string ;
26- tone ?: "auto" | "dark" | "light" ;
2729}
2830
29- export function LogoMark ( { height = 36 , className, tone = "auto" } : LogoMarkProps ) {
30- const [ failed , setFailed ] = useState ( false ) ;
31-
32- if ( ! failed && logoPng ) {
33- return (
34- < img
35- src = { logoPng }
36- alt = "GhostCode Dynamics"
37- onError = { ( ) => setFailed ( true ) }
38- style = { { height, width : "auto" } }
39- className = { cn ( "shrink-0 select-none object-contain" , className ) }
40- draggable = { false }
41- />
42- ) ;
43- }
44-
45- // ---- SVG fallback (only renders if PNG fails to load) ----
46- const fg =
47- tone === "dark" ? "#0a0a0f" : tone === "light" ? "#ffffff" : "currentColor" ;
31+ export function LogoMark ( { height = 36 , className } : LogoMarkProps ) {
4832 return (
49- < svg
50- height = { height }
51- width = { height }
52- viewBox = "0 0 64 64"
53- fill = "none"
54- xmlns = "http://www.w3.org/2000/svg"
55- className = { cn ( "shrink-0" , className ) }
56- role = "img"
33+ < span
34+ className = { cn ( "inline-block shrink-0" , className ) }
35+ style = { { height, width : height } }
5736 aria-label = "GhostCode Dynamics"
58- shapeRendering = "geometricPrecision "
37+ role = "img "
5938 >
60- < rect x = "1" y = "1" width = "62" height = "62" rx = "16" fill = { fg } />
61- < path
62- 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"
63- fill = "var(--background)"
39+ < img
40+ src = { iconLight }
41+ alt = ""
42+ draggable = { false }
43+ style = { { height, width : height } }
44+ className = "block object-contain select-none dark:hidden"
45+ />
46+ < img
47+ src = { iconDark }
48+ alt = ""
49+ draggable = { false }
50+ style = { { height, width : height } }
51+ className = "hidden object-contain select-none dark:block"
6452 />
65- < circle cx = "28.4" cy = "29.5" r = "1.9" fill = { fg } />
66- < circle cx = "35.6" cy = "29.5" r = "1.9" fill = { fg } />
67- </ svg >
53+ </ span >
6854 ) ;
6955}
7056
7157// ---------------------------------------------------------------
72- // <Logo /> — full lockup (mark + wordmark )
58+ // <Logo /> — full horizontal lockup (ghost-G + hostCode / DYNAMICS )
7359// ---------------------------------------------------------------
7460
7561interface LogoProps {
7662 className ?: string ;
77- /** Mark height in px. Defaults sized for navbar use. */
63+ /** Rendered height in px. Defaults sized for navbar use. */
7864 height ?: number ;
7965 iconOnly ?: boolean ;
8066}
8167
82- export function Logo ( { className, height, iconOnly = false } : LogoProps ) {
83- // Responsive default: 36px mobile → 42px desktop. Caller can override.
68+ export function Logo ( { className, height = 40 , iconOnly = false } : LogoProps ) {
69+ if ( iconOnly ) {
70+ return < LogoMark height = { height } className = { className } /> ;
71+ }
8472 return (
8573 < span
8674 className = { cn (
87- "inline-flex items-center gap-2.5 transition-transform duration-300 ease-out group-hover:scale-[1.02]" ,
75+ "inline-flex items-center transition-transform duration-300 ease-out group-hover:scale-[1.02]" ,
8876 className ,
8977 ) }
9078 aria-label = "GhostCode Dynamics"
9179 >
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- ) }
100- { ! iconOnly && < Wordmark /> }
80+ < img
81+ src = { logoLight }
82+ alt = "GhostCode Dynamics"
83+ draggable = { false }
84+ style = { { height, width : "auto" } }
85+ className = "block object-contain select-none dark:hidden"
86+ />
87+ < img
88+ src = { logoDark }
89+ alt = "GhostCode Dynamics"
90+ draggable = { false }
91+ style = { { height, width : "auto" } }
92+ className = "hidden object-contain select-none dark:block"
93+ />
10194 </ span >
10295 ) ;
10396}
10497
10598// ---------------------------------------------------------------
106- // <LogoCompact /> — icon + "GCD"
99+ // <LogoCompact /> — ghost icon + "GCD"
107100// ---------------------------------------------------------------
108101
109102export function LogoCompact ( { className, height = 32 } : LogoProps ) {
@@ -123,21 +116,4 @@ export function LogoCompact({ className, height = 32 }: LogoProps) {
123116 ) ;
124117}
125118
126- // ---------------------------------------------------------------
127- // Wordmark
128- // ---------------------------------------------------------------
129-
130- function Wordmark ( ) {
131- return (
132- < span className = "font-display text-[1.02rem] leading-none tracking-tight text-foreground" >
133- < span className = "font-semibold" > Ghost</ span >
134- < span className = "font-medium text-foreground/85" > Code</ span >
135- < span className = "mx-1 text-muted-foreground/60" > ·</ span >
136- < span className = "font-medium tracking-[0.02em] text-muted-foreground" >
137- Dynamics
138- </ span >
139- </ span >
140- ) ;
141- }
142-
143119export { LogoMark as GhostIcon } ;
0 commit comments