486555dbf2
- Shared SubpageShell (HeaderClient paper strip + ink footer) and SubpageIntro, btn/ghost/card helpers in app/_ui/subpage.tsx - Projects list: poster cards with hard ink shadows, filter pills, ink search field; no fake 800ms skeleton and no framer-motion opacity:0 in SSR - Project detail: poster hero, .plakat-prose markdown, links/stack aside, back link works without JS - Books: server-side fetch (visible without JS), cover + rating pills - Legal notice / privacy: moved to app/_ui/*Client.tsx, root routes removed (middleware always redirects to /[locale]), consent reset via useConsent - ProjectThumbnail rebuilt on Plakat tokens - Old Header.tsx + test removed; brown/cream/sand palettes and unused glass-panel/admin-glass(-light)/liquid-bg-blob/float CSS dropped (liquid palette, glass-card, admin-glass-card stay for admin + 404) - .plakat-prose replaces the missing typography plugin Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1.8 KiB
1.8 KiB
paths
| paths | ||
|---|---|---|
|
Component Rules
SSR animation safety (critical)
Never use initial={{ opacity: 0 }} on server-rendered elements.
This bakes style="opacity:0" into HTML — content is invisible if hydration fails.
Use ScrollFadeIn instead:
import ScrollFadeIn from "@/app/components/ScrollFadeIn"
<ScrollFadeIn><MyComponent /></ScrollFadeIn>
AnimatePresence is fine for modals and overlays that only appear after user interaction.
Design system (Plakat)
- Colors: only the Plakat tokens
signal,ink,paper,paper-2,ok— no hex, no raw Tailwind palette, noliquid-*(left over only in admin and 404) - Ink and paper swap roles in dark mode on their own;
signalstays red - Fonts:
font-display(Bricolage,[font-variation-settings:'wdth'_80]for posters),font-sans(Geist),font-mono(Geist Mono) - Cards:
rounded-[18px] border-2 border-ink+ hard offset shadowshadow-[6px_6px_0_rgb(var(--plakat-ink))] - Pills:
rounded-fullwithshadow-[inset_0_0_0_1.5px_rgb(var(--plakat-ink))], filled variantbg-ink text-paper - Labels:
font-mono text-[13px]; nothing below 12px - Subpages: wrap in
app/_ui/SubpageShell.tsx, reuseSubpageIntro,btn,ghost,cardfromapp/_ui/subpage.tsx; long text in.plakat-prose - Motion:
ease-out/ease-drawer, press feedbackactive:scale-[0.97], nevertransition-all
Async components
Every component that fetches data must have a Skeleton loading state shown while data loads.
i18n
- Client:
useTranslations("namespace")fromnext-intl - Server:
getTranslations("namespace")fromnext-intl/server - New client sections need a wrapper in
ClientWrappers.tsxwith scopedNextIntlClientProvider
TypeScript
- No
any— define interfaces inlib/directus.tsortypes/ - No emojis in code