4.0 KiB
Hanzo Gallery
Overview
This is a Next.js project bootstrapped with create-next-app.
Tech Stack
- Language: TypeScript/JavaScript
- UI:
@hanzo/uion@hanzo/gui. No utility engine, no Radix.
Build & Run
npm install && npm run build
npm run e2e # drives the exported site: filters, dialog, size switch, 404
npm run css-check # every class in the markup has a rule in a sheet it delivers
Structure
gallery/
LICENSE
README.md
app/ # the gallery itself; app/lib/shot.ts addresses a capture
app/lib/design.ts # the palette and the type scale, stated once
public/screenshots/ # <name>.webp, <name>-tablet.webp, <name>-mobile.webp
scripts/ # gen-templates-json.mjs runs on prebuild
.hanzo/workflows/ # deploy.yml publishes out/ to the Sites plane
eslint.config.mjs
gui.d.ts # names the motion scale so `transition="quickest"` types
next-env.d.ts
next.config.ts
package-lock.json
package.json
Key Files
README.md-- Project documentationpackage.json-- Dependencies and scripts
Styling
Everything is a gui style prop. app/lib/design.ts holds the two things a call
site needs and nothing else: c (colours) and t (type steps). Spread a type
step, never assign its size alone — <H1 {...t.xl8}> — because a step carries
its leading, and display type set without one renders sliced.
Four things earned their way into app/globals.css, each because no style prop
can say it: the document's own margin, a @keyframes, the WebKit half of hiding
a scrollbar, and the hover that reaches from a card to its picture.
Learned the expensive way, all measured here:
- The type scale is the system's, the palette is not.
--text-*are defined and fluid, sotnames them.--purple-500and friends are NOT defined — the system ships--neutral-*and--white-*and stops — so asking for one by name yields an undefined variable and the property is dropped in silence. The colours are therefore literals, read out of the sheet the page used to render, andat(colour, alpha)puts the alpha INSIDE theoklch()where its syntax wants it. A hex-style33suffix produces a value the browser drops. borderStyle: 'solid'sets the style on all four sides. Give one side a width and the other three keep CSS's initialmedium— 3px — so a single top rule renders as a box. react-native-web's base already suppliessolidat width 0, so the prop is never needed: state widths only.- Frames take frame props; text props go on text.
color,fontSize,textAlignandtextDecorationLineare dropped by a Stack. An icon button that tints its SVG throughcurrentColorhas to BE aText. - gui drops an unknown prop without a word. Read the installed
.d.ts. The motion prop istransition, notanimation;Spinner'ssizeis a number of pixels;Anchortypes neithertitlenortransitionProperty. react-nativemust alias toreact-native-web(seenext.config.ts), and so must@react-native/assets-registry/registry, which otherwise forwards to the internals the first alias just replaced and throws on any asset.ToggleGroupdoes not typecheck here — its prop union exceeds TypeScript's complexity limit — so the gallery's view switch is two buttons.
The static export is fully styled with JavaScript off: gui inlines the atomics
it generates during the build into the HTML. npm run css-check is what says
so, and it is the check that catches a page whose classes have no rules — a
green build cannot.
License
Dual-licensed MIT OR Apache-2.0 (LICENSE-MIT, LICENSE-APACHE), replacing the
previous BSD-3-Clause declaration. Original Hanzo work standardises on this pair per
HIP-0137 "One License" (hanzoai/hips, HIPs/hip-0137-one-license.md); forks keep
their upstream licence unchanged.