The head configured GA4 behind a gtag.js script tag, so a network that dropped Google's host held the load event hostage. Configure it inline and fetch gtag.js once the page has loaded, with a linker across the Hanzo domains a visit crosses, and the same GPC/DNT opt-out the site's own telemetry honors. The ID lived in both hanzo.yml and index.html. vite.config.ts now reads it from hanzo.yml into index.html. ScrollToTop sent its own page_view on every route change, on top of GA4's history listener, so each page counted twice. Drop it.
7.3 KiB
Hanzo Agency Website Project
Project Overview
This project is a React application built with TypeScript, Vite, shadcn-ui, and Tailwind CSS. It's a portfolio website for Hanzo AI agency showcasing their case studies and services.
How it ships
.github/workflows/deploy.yml on GitHub (linux-amd64, the platform's runners;
the forge copy is a pull mirror with Actions off, so .hanzo/workflows runs
nowhere): build dist -> POST /v1/projects/agency/deploy (202, carrying a presigned
upload grant) -> POST each file under that grant -> POST .../complete with the
file manifest as keys. The bytes never pass through the API; BodyLimit is
16 MiB. No GitHub Pages, no Cloudflare Pages, and no image -- a static export has
no compute to run.
This repo holds NO S3 credential. The grant is confined to this site's prefix and
expires in 30 minutes, so a leak here cannot reach another org's site the way the
old shared bucket keys could. Deletion rides the manifest: the grant permits
writes only, so cloud prunes the prefix against keys. The deploy key is in KMS;
the workflow holds only the hanzoai org's KMS_CLIENT_ID/KMS_CLIENT_SECRET.
Telemetry is @hanzo/event (src/analytics.tsx, mounted inside the router in src/App.tsx) posting to api.hanzo.ai/v1/event. One
client for pageviews, events and errors: no Meta Pixel, no Plausible, no
separate error SDK.
GA4 is hanzo.agency's own stream, stated once in hanzo.yml
(analytics.googleAnalyticsId); vite.config.ts writes it into index.html's
%GA%. The head configures it inline and fetches gtag.js after load. GA4's
history listener counts route changes, so no component sends page_view.
Project Structure
/src: Main source code/components: Reusable UI components/pages: Top-level page components/data: Static data files/data/case-studies: Individual case study data files/data/caseStudies.ts: Main file that imports and exports all case studies
/hooks: Custom React hooks/utils: Utility functions/types: TypeScript type definitions
/components: Top-level components (Header, Navigation)/styles: CSS files/styles/navigation.css: Navigation menu styling/styles/variables.css: CSS variables
Routing System
The application uses react-router-dom for routing. The main routes are defined in App.tsx:
/: Homepage/case-studies: All case studies overview/case-studies-alt: Alternative case studies view/case-study/:id: Individual case study pages (using the CaseStudy component)/contact: Contact page/subscribe: Newsletter subscription/onboarding: Client onboarding form
Case Studies System
Case studies are a key part of the website and follow a consistent pattern:
-
Data Organization:
- Each case study has its own file in
src/data/case-studies/directory:damon.tsbellabeat.tstriller.tsunikrn.tscover.tscasper.tsmyle.ts
- All case studies are imported and exported together in
src/data/caseStudies.ts
- Each case study has its own file in
-
Rendering Components:
src/pages/CaseStudy.tsx: Dynamic page component that renders any case study based on the URL parametersrc/components/CaseStudyPage.tsx: Component for rendering a case study's full contentsrc/components/CaseStudy.tsx: Component used on the homepage to showcase featured case studies
-
Case Study IDs:
- Each case study file exports an object with an
idproperty matching its filename - IDs are used consistently throughout the application
- The ID
damon-motorcyclesis used in data but sometimes referred to asdamonin UI components
- Each case study file exports an object with an
-
Routing Structure:
- All case studies are accessed via the route pattern:
/case-study/:id - The ID in the route corresponds to the ID in the case study file
- All case studies are accessed via the route pattern:
UI Components and Navigation
-
Header and Navigation:
- Header component (
/components/Header.tsx) contains the main navigation - Navigation component (
/components/Navigation.tsx) implements the main menu - Desktop navigation includes dropdown menus for Services and Solutions sections
- Mobile navigation collapses into a hamburger menu at screen widths below 768px
- Header component (
-
Navigation Structure:
- The navigation uses a combination of CSS classes for styling
- Dropdown menus are positioned absolutely in relation to the header
- CSS variables in
variables.cssdefine key measurements, including header height - Navigation is responsive with different behaviors at desktop and mobile breakpoints
-
UI Design Pattern:
- Navigation now uses shadcn's NavigationMenu component for dropdowns
- Navigation content is stored in a separate data file (
src/data/navigationItems.ts) - Hero headline updated to "AI is our super power"
- Fixed header with proper dropdown positioning
- Navigation dropdown fixes applied via CSS overrides to ensure visibility on hover
- Multiple CSS files used to fix dropdown issues:
NavigationDropdownFix.css- Basic hover and pointer-events fixesnavbar-dropdown-final-fix.css- Comprehensive overflow and z-index fixesnavigation-dropdown-override.css- High-specificity overrides
Services Section Redesign
- Updated Services component to match two-card pricing layout from design mockup
- Left card: Dark green (#3a4a42) with flexible pricing information
- Right card: Beige (#f5f3e9) with included features checklist
- Responsive design: Cards stack vertically on mobile/tablet, side-by-side on desktop
- Service categories displayed in responsive grid below the main cards
- Custom CSS file (Services.css) for precise styling and animations
Creative Portfolio Carousel
- Added CreativePortfolioCarousel component (
src/components/CreativePortfolioCarousel.tsx) - Uses shadcn/ui carousel component with Embla Carousel for smooth scrolling
- Features 13 creative works spanning design and AI capabilities:
- Design Work: Presentation Design, Branding Services, Illustration, Ad Creative, Social Media, Video Production, Web Design, Motion Design, 3D/AR Design
- AI Solutions: AI Enhanced Creative, AI Pipeline Development, LLM Fine Tuning, RAG System Implementation
- Images stored in
public/images/carousel/with exact filenames from desktop - Responsive layout: 3 items on desktop, 2 on tablet, 1 on mobile
- Category badges overlay on images for easy identification
- Hover effects with scale transformation and shadow enhancement
- Mobile-friendly with swipe gesture support and scroll indicator
- Added to homepage between Services and AICapabilities sections
AI Capabilities Section
- Enhanced AICapabilities component (
src/components/AICapabilities.tsx) with improved image handling - Features three main capabilities cards with SVG graphics:
- Intelligent Systems (ai-nodes.svg)
- Predictive Analytics (data-wave.svg)
- Custom Development (code-blocks.svg)
- Displays the frontier models and platforms we build on (a tools-we-use strip, not a partner claim)
- The three feature-card graphics render as plain
<img>. They deliberately carry noloading="lazy"and no skeleton: a lazy image that starts lifedisplay:nonenever firesonLoad, so the skeleton used to latch on forever and the cards showed empty. These SVGs are bundled — there is nothing to wait for and nothing to fall back to. - All images stored in
public/images/logo/andpublic/images/graphics/