open-design/apps/landing-page/app/_components/header.tsx
Joey-nexu e3a848a33a
feat(landing-page): replace Ø wordmark with PNG logo across nav/footer/favicon (#1449)
* feat(landing-page): replace Ø wordmark with PNG logo across nav/footer/favicon

Switches the brand mark from the Unicode 'Ø' glyph to the new circular
gradient paper-plane PNG. Header nav and footer share the same image, and
the browser tab + iOS home screen icons are regenerated from the same
500x500 source.

- public/logo.png (500x500, brand source)
- public/favicon.png (32x32, replaces favicon.svg)
- public/apple-touch-icon.png (180x180, regenerated)
- header.tsx + page.tsx footer: <span>Ø</span> -> <img src=/logo.png />
- globals.css: simplify .brand-mark (drop Ø-era border/font, add object-fit
  contain on child img)
- index.astro: link rel=icon now points at favicon.png

* fix(landing-page): apply logo + favicon swap to sub-page layout too

Review on #1449 caught two cross-page consistency issues:

- P1: sub-page-layout.astro still linked /favicon.svg, which this PR
  deletes — every Skills/Systems/Templates/Craft page would request a
  missing asset. Updated to /favicon.png to match index.astro.
- P2: sub-page-layout.astro still rendered the Ø wordmark in its footer
  brand block, leaving the public site with mixed brand marks. Replaced
  with the same <img src=/logo.png /> wrapper pattern used on the
  homepage header and footer.

Repo-wide grep now shows 0 favicon.svg references and 0 Ø brand-mark
spans. typecheck still 25 files / 0 errors / 0 warnings.

---------

Co-authored-by: Joey-nexu <236967869+joeylee12629-star@users.noreply.github.com>
2026-05-13 12:30:32 +08:00

114 lines
3.7 KiB
TypeScript

/*
* Sticky Header — static markup rendered at build time. Headroom-style
* hide/show and the live GitHub star count are attached by the tiny inline
* script in `app/pages/index.astro`, so this marketing page ships no React
* runtime to the browser.
*
* The nav links go to internal multi-page routes (`/skills/`, `/systems/`,
* `/templates/`, `/craft/`) so Google sees a real site hierarchy. Numbers
* reflect the live counts of the canonical Markdown bundles in the repo
* root and are kept in sync with `getCatalogCounts()` at build time.
*/
const REPO = 'https://github.com/nexu-io/open-design';
const REPO_RELEASES = `${REPO}/releases`;
const ext = {
target: '_blank',
rel: 'noreferrer noopener',
} as const;
export interface HeaderProps {
/** Nav highlight target. `'home'` is the default for `/`. */
active?: 'home' | 'skills' | 'systems' | 'templates' | 'craft';
/**
* Live counts from the Markdown catalogs. Required so we can never
* silently render stale fallback numbers when a caller forgets to
* thread `getCatalogCounts()` through. Header only consumes these
* four scalar fields; the homepage passes the wider `CatalogCounts`
* value (with `byMode` / `byPlatform`) by structural subtyping.
*/
counts: {
skills: number;
systems: number;
templates: number;
craft: number;
};
/** Brand link target — `#top` on the homepage, `/` on sub-pages. */
brandHref?: string;
}
export function Header({
active = 'home',
counts,
brandHref = '#top',
}: HeaderProps) {
const linkClass = (key: NonNullable<HeaderProps['active']>) =>
active === key ? 'is-active' : undefined;
return (
<header className='nav' data-od-id='nav' data-nav-headroom>
<div className='container nav-inner'>
<a href={brandHref} className='brand'>
<span className='brand-mark'>
<img src='/logo.png' alt='' width={36} height={36} />
</span>
<span>Open Design</span>
<span className='brand-meta'>
<b>Studio 01</b>Berlin / Open / Earth
</span>
</a>
<nav>
<ul className='nav-links'>
<li>
<a href='/skills/' className={linkClass('skills')}>
Skills<span className='num'>{counts.skills}</span>
</a>
</li>
<li>
<a href='/systems/' className={linkClass('systems')}>
Systems<span className='num'>{counts.systems}</span>
</a>
</li>
<li>
<a href='/templates/' className={linkClass('templates')}>
Templates<span className='num'>{counts.templates}</span>
</a>
</li>
<li>
<a href='/craft/' className={linkClass('craft')}>
Craft<span className='num'>{counts.craft}</span>
</a>
</li>
<li>
<a href={brandHref === '#top' ? '#contact' : '/#contact'}>
Contact
</a>
</li>
</ul>
</nav>
<div className='nav-side'>
<a
className='nav-cta ghost'
href={REPO_RELEASES}
aria-label='Download Open Design desktop'
title='Download the desktop app'
{...ext}
>
Download
</a>
<a
className='nav-cta'
href={REPO}
aria-label='Star Open Design on GitHub'
title='Click to star us on GitHub'
{...ext}
>
Star · <span data-github-stars>0</span>
</a>
<span className='status-dot' aria-hidden='true' />
</div>
</div>
</header>
);
}