PB_URL falls back through PB_URL → NEXT_PUBLIC_PB_URL → localhost so
internal Docker hostname is used server-side without leaking into the
client bundle. cache: force-cache replaced with next: { tags, revalidate }
for ISR tag-based invalidation.
Discriminated union types (AsButton | AsAnchor), isAnchorProps type guard
eliminates all 'as' casts. as const satisfies for VARIANTS/SIZES lookup
tables. brutal-border replaces border-[3px] in ghost variant.
Variants now use brutal shadow tokens. On hover the button translates
up-left (−0.5px), on active down-right (+0.5px). Only transform animates
(130ms ease-out); shadow snaps instantly so the eye reads button movement
not shadow resize. Primary keeps rgba alpha shadow; secondary/outline use
solid brutal tokens.
Add animation tokens to :root (--ease-spring, --ease-decelerate,
--ease-default, --duration-fast/normal/slow/spring). Apply spring easing
to section title enter. Add separate section-body transition: fast
blur-out exit (100ms), clean slide-in enter (350ms) delayed by 200ms so
content appears after the title animation completes.
Enable experimental.viewTransition in Next.js config. Wrap active section
in ViewTransitionWrapper so the browser cross-fades between sections on
navigation. Replace animate-fadeIn keyframe with @starting-style + CSS
transition for the initial render enter animation.
Wraps children in React's ViewTransition (canary API) when available,
falling back to Fragment in environments where ViewTransition is undefined
(test env, stable react-dom). Add react/canary to tsconfig types to
expose the ViewTransition component type.
Add html-react-parser-backed RichText component that converts HTML
strings from PocketBase rich-text fields into React elements without
dangerouslySetInnerHTML. Replace raw <p> render in IntroSection and
BioSection, and drop the invalid slug filters those collections lacked.
Remove slug field from PageContentRecord (intro/bio collections have none).
Remove number field from SectionRecord (not stored in PocketBase); derive
zero-padded display number from the order field at render time.
Replace ochre-clay, carbon-black, burnt-oxide, slate-indigo with clean
two-color system: --cream (#f4f0e8) and --blue (#041cf3). Update every
component, utility class, and test assertion.
Installs @storybook/nextjs-vite. Stories co-located with components,
grouped by layer (Shared/Entities/Widgets). Multi-variant cases use
render functions instead of one story per variant/size.