The favicon was a white heart. Not a stylised Hanzo mark -- an unrelated
heart glyph, 1150 bytes, 16x16, sitting at /favicon.ico on every origin.
Beside it /favicon.svg drew a three-block descending staircase with a
single hardcoded fill="white", so on a light tab strip it rendered as
nothing at all. And /og-image.png was not an image: 119 bytes of ASCII
reading "This is a placeholder for the PNG file", which is what every
link preview of this site has been sharing.
All three now come from @hanzo/logo, byte-for-byte, no redrawing:
favicon.svg the 67x67 block-H, five paths, carrying its own
prefers-color-scheme rule -- identical bytes to
what hanzo.ai serves today
favicon.ico six sizes, white mark on an opaque black tile.
Opaque on purpose: a transparent black mark
disappears against a dark tab strip, and ICO has
no media queries to save it
og-image.png the real 1200x630 card
apple-touch-icon 180x180, previously missing entirely
og:image is now absolute, because scrapers do not resolve relative ones.
The sideways scroll was not the marquee. That was the obvious suspect --
an 11166px logo strip -- but its parent is overflow-hidden and it clips
correctly. Measuring every box against clientWidth and discarding any
with a clipping ancestor left exactly one culprit: a decorative blur at
-right-20 in the hero, which float-and-pulse scales to 1.5x and pushes
to x=1492 in a 1280 viewport. It is display:none below sm, which is why
390px always measured clean and 1280px did not. Clipped at the hero
root with overflow-x-clip rather than -hidden, so it does not become a
scroll container and break sticky positioning.
Two more things measurement turned up that the eye would not:
The marquee had its animation declared twice -- 0 to -25% in Hero.css
next to the markup, and 0 to -100% in index.css. index.css won the
cascade, so the whole 4x-repeated strip travelled fully off screen and
the client row sat blank for the tail of every 80s cycle. One home now.
Thirteen carousel images had spaces in their filenames, two with a
trailing space before the extension. They 404 on the Sites plane while
working on the apex, which was blocking the cutover. Renamed to
kebab-case, references rewritten. Filenames with spaces in URLs are a
defect on their own.
Also: the legal entity read "Hanzo Industries, Inc." in four places
where the estate is "Hanzo AI, Inc.", and the Contact page was still
carrying scaffold contact details -- a personal gmail address, a
Singapore phone number and a different street address than the footer.
Both surfaces now read from src/data/contact.ts so they cannot drift
again. Three testimonial cards on /services pointed at logo files that
have never existed in this repo; the <img> tags are gone, the copy is
untouched, and no logo was invented to fill the hole.
package.json was still "vite_react_shadcn_ts" at 0.0.0 -- the untouched
scaffold project name. Now hanzo-agency at 0.1.4, following the 0.1.3
that shipped as an image. The scaffold's "Image Not Available" placeholder
and a 13KB debug page that was serving publicly are gone too.
Verified in Chromium at 390 and 1280 across seven routes: zero broken
images, zero 404s, zero page errors, and documentElement.scrollWidth
equal to clientWidth on every sample.
Co-authored-by: Hanzo Dev <[email protected]>
7.0 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
.hanzo/workflows/deploy.yml on the git.hanzo.ai forge (hanzo-build-linux-amd64):
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 one secret is
HANZO_DEPLOY_TOKEN, set ON THE FORGE -- GitHub's secret store is not in this
path at all.
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 GA, no Meta Pixel, no Plausible, no
separate error SDK.
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/