Skip to content

Commit bd11f55

Browse files
Changes
Co-authored-by: ghostcodedynamics <295822717+ghostcodedynamics@users.noreply.github.com>
1 parent c07d53a commit bd11f55

1 file changed

Lines changed: 40 additions & 68 deletions

File tree

src/components/brand/logo.tsx

Lines changed: 40 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,54 @@
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";
2115
import { 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

3622
interface 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

11075
interface 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

157130
function Wordmark() {
@@ -167,5 +140,4 @@ function Wordmark() {
167140
);
168141
}
169142

170-
// Re-export the icon under its previous name to keep older imports working.
171143
export { LogoMark as GhostIcon };

0 commit comments

Comments
 (0)