From f77bb87e9b21c6abf4a49f8fb956e71d8563eab4 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:42:54 +0200 Subject: [PATCH 1/3] feat: rebrand atlas frontend --- docker-compose.yml | 2 + frontend/src/App.tsx | 4 +- frontend/src/assets/defaultLogos.ts | 9 + frontend/src/assets/evolve-logo-light.svg | 20 + frontend/src/assets/evolve-logo.svg | 20 + frontend/src/components/BrandPrimitives.tsx | 245 ++++++ frontend/src/components/CopyButton.tsx | 2 +- frontend/src/components/Error.tsx | 7 +- frontend/src/components/Layout.tsx | 318 ++++---- frontend/src/components/Loading.tsx | 4 +- frontend/src/components/Pagination.tsx | 68 +- frontend/src/components/SearchBar.tsx | 116 ++- frontend/src/components/index.ts | 1 + frontend/src/context/BrandingContext.tsx | 37 +- frontend/src/hooks/useChartColors.ts | 2 +- frontend/src/index.css | 776 +++++++++++++++---- frontend/src/pages/AddressesPage.tsx | 30 +- frontend/src/pages/BlockDetailPage.tsx | 52 +- frontend/src/pages/BlockTransactionsPage.tsx | 34 +- frontend/src/pages/BlocksPage.tsx | 123 ++- frontend/src/pages/FaucetPage.tsx | 50 +- frontend/src/pages/NFTsPage.tsx | 19 +- frontend/src/pages/NotFoundPage.tsx | 22 +- frontend/src/pages/SearchResultsPage.tsx | 32 +- frontend/src/pages/StatusPage.tsx | 37 +- frontend/src/pages/TokensPage.tsx | 21 +- frontend/src/pages/TransactionDetailPage.tsx | 28 +- frontend/src/pages/TransactionsPage.tsx | 28 +- frontend/src/pages/WelcomePage.tsx | 119 ++- frontend/src/utils/color.test.ts | 49 ++ frontend/src/utils/color.ts | 132 +++- frontend/tailwind.config.js | 10 +- 32 files changed, 1696 insertions(+), 721 deletions(-) create mode 100644 frontend/src/assets/defaultLogos.ts create mode 100644 frontend/src/assets/evolve-logo-light.svg create mode 100644 frontend/src/assets/evolve-logo.svg create mode 100644 frontend/src/components/BrandPrimitives.tsx create mode 100644 frontend/src/utils/color.test.ts diff --git a/docker-compose.yml b/docker-compose.yml index c6b7b3c..8fc2a03 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -35,6 +35,8 @@ services: FAUCET_ENABLED: ${FAUCET_ENABLED:-false} CHAIN_NAME: ${CHAIN_NAME:-Unknown} CHAIN_LOGO_URL: ${CHAIN_LOGO_URL:-} + CHAIN_LOGO_URL_LIGHT: ${CHAIN_LOGO_URL_LIGHT:-} + CHAIN_LOGO_URL_DARK: ${CHAIN_LOGO_URL_DARK:-} ACCENT_COLOR: ${ACCENT_COLOR:-} BACKGROUND_COLOR_DARK: ${BACKGROUND_COLOR_DARK:-} BACKGROUND_COLOR_LIGHT: ${BACKGROUND_COLOR_LIGHT:-} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e558eed..2283894 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -24,8 +24,8 @@ const StatusPage = lazy(() => import('./pages/StatusPage')); function PageLoader() { return ( -
- Loading... +
+ Loading route
); } diff --git a/frontend/src/assets/defaultLogos.ts b/frontend/src/assets/defaultLogos.ts new file mode 100644 index 0000000..92c5981 --- /dev/null +++ b/frontend/src/assets/defaultLogos.ts @@ -0,0 +1,9 @@ +import darkLogo from './evolve-logo.svg'; +import lightLogo from './evolve-logo-light.svg'; +import type { Theme } from '../context/theme-context'; + +export function getDefaultLogo(theme: Theme): string { + return theme === 'light' ? lightLogo : darkLogo; +} + +export { darkLogo as defaultDarkLogo, lightLogo as defaultLightLogo }; diff --git a/frontend/src/assets/evolve-logo-light.svg b/frontend/src/assets/evolve-logo-light.svg new file mode 100644 index 0000000..b92eab9 --- /dev/null +++ b/frontend/src/assets/evolve-logo-light.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/evolve-logo.svg b/frontend/src/assets/evolve-logo.svg new file mode 100644 index 0000000..16d6bfe --- /dev/null +++ b/frontend/src/assets/evolve-logo.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/components/BrandPrimitives.tsx b/frontend/src/components/BrandPrimitives.tsx new file mode 100644 index 0000000..e8602b3 --- /dev/null +++ b/frontend/src/components/BrandPrimitives.tsx @@ -0,0 +1,245 @@ +import type { ReactNode } from 'react'; + +type EntityKind = + | 'search' + | 'block' + | 'blocks' + | 'transaction' + | 'transactions' + | 'address' + | 'addresses' + | 'token' + | 'tokens' + | 'nft' + | 'nfts' + | 'status' + | 'faucet' + | 'notfound'; + +interface PageHeroProps { + eyebrow?: string; + title: ReactNode; + description?: ReactNode; + actions?: ReactNode; + meta?: ReactNode; + visual?: ReactNode; + compact?: boolean; + className?: string; +} + +export function PageHero({ + eyebrow, + title, + description, + actions, + meta, + visual, + compact = false, + className = '', +}: PageHeroProps) { + return ( +
+
+ {eyebrow &&

{eyebrow}

} +

{title}

+ {description &&

{description}

} + {actions &&
{actions}
} + {meta &&
{meta}
} +
+
+ {visual ?? } +
+
+ ); +} + +interface StatCardProps { + label: ReactNode; + value: ReactNode; + hint?: ReactNode; + className?: string; +} + +export function StatCard({ label, value, hint, className = '' }: StatCardProps) { + return ( +
+

{label}

+
{value}
+ {hint &&

{hint}

} +
+ ); +} + +interface SectionPanelProps { + eyebrow?: string; + title?: ReactNode; + actions?: ReactNode; + className?: string; + children: ReactNode; +} + +export function SectionPanel({ + eyebrow, + title, + actions, + className = '', + children, +}: SectionPanelProps) { + return ( +
+ {(eyebrow || title || actions) && ( +
+
+ {eyebrow &&

{eyebrow}

} + {title &&

{title}

} +
+ {actions &&
{actions}
} +
+ )} + {children} +
+ ); +} + +interface EmptyStateProps { + title: ReactNode; + description?: ReactNode; + action?: ReactNode; + className?: string; +} + +export function EmptyState({ + title, + description, + action, + className = '', +}: EmptyStateProps) { + return ( +
+
+
+

{title}

+ {description &&

{description}

} +
+ {action &&
{action}
} +
+ ); +} + +export function BrandOrnament({ compact = false }: { compact?: boolean }) { + return ( +
+
+
+
+
+
+ Atlas + Explorer +
+
+ ); +} + +export function EntityHeroVisual({ kind }: { kind: EntityKind }) { + return ( +
+
+
+
+
+
+ +
+
+ ); +} + +function EntityGlyph({ kind }: { kind: EntityKind }) { + const cls = 'h-16 w-16 md:h-20 md:w-20 text-fg'; + + if (kind === 'blocks' || kind === 'block') { + return ( + + + + + + ); + } + + if (kind === 'transactions' || kind === 'transaction') { + return ( + + + + + + + ); + } + + if (kind === 'addresses' || kind === 'address') { + return ( + + + + + + ); + } + + if (kind === 'tokens' || kind === 'token') { + return ( + + + + + ); + } + + if (kind === 'nfts' || kind === 'nft') { + return ( + + + + + + ); + } + + if (kind === 'status') { + return ( + + + + + ); + } + + if (kind === 'faucet') { + return ( + + + + + + ); + } + + if (kind === 'notfound') { + return ( + + + + + ); + } + + return ( + + + + + ); +} diff --git a/frontend/src/components/CopyButton.tsx b/frontend/src/components/CopyButton.tsx index 45b6c45..69bc2a0 100644 --- a/frontend/src/components/CopyButton.tsx +++ b/frontend/src/components/CopyButton.tsx @@ -21,7 +21,7 @@ export default function CopyButton({ text, className = '' }: CopyButtonProps) { return ( +
- {/* Navigation - centered on desktop */} -
- - {/* Mobile navigation */} -
- {/* In-flow search bar under the header (hidden on home hero) */} {!isHome && ( -
-
-
- +
+
+
+
)} - {/* Main content */} -
-
+
+
- {/* Footer */} -