feat(onboarding): illustrated halftone backdrop from marketing-site art (#1080)
* feat(onboarding): illustrated halftone backdrop from marketing-site art Ports the gnubok-website halftone illustration set into the onboarding flow so signup -> app feels like one product: - OnboardingBackdrop: petal field across the paper background (radially masked to stay calm behind the form), Stockholm skyline dissolving into the bottom edge, two clouds drifting at reading pace that bounce off the viewport and the onboarding panel (IllustrationFloaters, physics ported from the website's BouncingFloaters). - Per-step instrument ghosted in white ink on the dark card header: pencil -> notebook -> adding machine -> calculator, one per step. - Dark mode via invert/hue-rotate filters; prefers-reduced-motion parks the floaters; all art is aria-hidden and pointer-events-none. Applies to /onboarding, /onboarding/agent and /select-company via the shared (onboarding) layout. No new strings, no API changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(onboarding): strip backdrop to skyline + step art per founder review Founder kept the Stadshuset skyline and the per-step header instruments; the petal field and drifting clouds are cut. Removes the now-unused floater physics component and the petals/cloud assets. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
bd816e190c
commit
e2d6c92e3a
@@ -236,3 +236,4 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
|
||||
[2026-07-20] MCP gnubok_book_salary_run walks the whole review->approved->paid->booked chain in ONE staged op instead of mirroring the dashboard's four separate clicks: the human approval of the pending operation (high-risk, confirmed=true) IS the authorization act, and a four-op chain over MCP would just be approval theater. Missing bank details downgrade from overridable block to warnings (dashboard force-approve semantics): the payment-file generators hard-block on them where it matters.
|
||||
[2026-07-20] No gnubok_archive_employee tool: gnubok_update_employee already takes is_active=false (soft-archive, BFL retention) and the v1 REST surface has the DELETE verb; a dedicated tool would only bloat the tools/list budget.
|
||||
[2026-07-20] Booking core extracted to lib/salary/book-run.ts and shared by the dashboard route + book_salary_run executor; the v1 book route intentionally keeps its own strict-mode mirror (optimistic locking, period pre-check, its own envelope) rather than being folded in.
|
||||
[2026-07-20] Onboarding backdrop reuses marketing-site halftone webp assets copied into public/illustrations/ (not hotlinked, not regenerated): keeps app self-contained and signup->app visually continuous; decorative art uses plain <img> (physics sizes by %, next/image adds nothing for 1-35KB webp).
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Link from 'next/link'
|
||||
import { Settings } from 'lucide-react'
|
||||
import { createClient, createServiceClient } from '@/lib/supabase/server'
|
||||
import OnboardingBackdrop from '@/components/onboarding/OnboardingBackdrop'
|
||||
|
||||
export default async function OnboardingLayout({
|
||||
children,
|
||||
@@ -34,7 +35,9 @@ export default async function OnboardingLayout({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
<div className="w-full max-w-lg px-5">
|
||||
<OnboardingBackdrop />
|
||||
|
||||
<div className="relative z-10 w-full max-w-lg px-5">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -19,6 +19,22 @@ import Step1EntityType from '@/components/onboarding/Step1EntityType'
|
||||
import Step2CompanyDetails from '@/components/onboarding/Step2CompanyDetails'
|
||||
import Step3TaxRegistration from '@/components/onboarding/Step3TaxRegistration'
|
||||
import Step4VatAccounting from '@/components/onboarding/Step4VatAccounting'
|
||||
import {
|
||||
ILLUSTRATIONS,
|
||||
illustrationSrc,
|
||||
type IllustrationName,
|
||||
} from '@/components/onboarding/onboarding-illustrations'
|
||||
|
||||
// One halftone instrument per step, ghosted in white ink on the dark card
|
||||
// header. The tools get more computational as the steps get more fiscal:
|
||||
// pencil (choose form) -> notebook (company details) -> adding machine
|
||||
// (tax registration) -> calculator (VAT & method).
|
||||
const STEP_ART: { name: IllustrationName; className: string }[] = [
|
||||
{ name: 'pencil', className: 'w-28 -rotate-12 right-5 -bottom-6' },
|
||||
{ name: 'notebook', className: 'w-28 rotate-3 right-4 -bottom-3' },
|
||||
{ name: 'key-adding-machine', className: 'w-32 -rotate-3 right-2 -bottom-10' },
|
||||
{ name: 'calculator', className: 'w-28 rotate-3 right-4 -bottom-8' },
|
||||
]
|
||||
|
||||
type TFn = (key: string, values?: Record<string, string | number>) => string
|
||||
|
||||
@@ -289,6 +305,29 @@ export default function WelcomeOnboarding({
|
||||
background: 'radial-gradient(ellipse at 30% -20%, rgba(255,255,255,0.04) 0%, transparent 50%)',
|
||||
}}
|
||||
/>
|
||||
{/* Step instrument, ghosted in white ink. Keyed by step so it
|
||||
crossfades together with the form below. */}
|
||||
{(() => {
|
||||
const art = STEP_ART[currentStep - 1]
|
||||
const dims = ILLUSTRATIONS[art.name]
|
||||
return (
|
||||
<div
|
||||
key={`art-${currentStep}`}
|
||||
className={cn('absolute animate-fade-in', art.className)}
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={illustrationSrc(art.name)}
|
||||
width={dims.w}
|
||||
height={dims.h}
|
||||
alt=""
|
||||
loading="eager"
|
||||
decoding="async"
|
||||
className="block h-auto w-full opacity-30 [filter:grayscale(1)_invert(1)]"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
|
||||
<div className="relative z-10">
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ILLUSTRATIONS, illustrationSrc } from './onboarding-illustrations'
|
||||
|
||||
// Ambient scene behind the onboarding flow, built from the marketing site's
|
||||
// halftone illustration set so signup -> app feels like one product: the
|
||||
// Stockholm skyline dissolving into the bottom edge. Purely decorative
|
||||
// (aria-hidden, pointer-events-none); the content column sits above it on
|
||||
// z-10.
|
||||
export default function OnboardingBackdrop() {
|
||||
const skyline = ILLUSTRATIONS['about-stockholm']
|
||||
|
||||
return (
|
||||
<div aria-hidden className="pointer-events-none fixed inset-0 z-0 overflow-hidden">
|
||||
{/* Stadshuset skyline anchored to the bottom: translated down so its
|
||||
water reflection falls below the fold and only the silhouette hugs
|
||||
the edge, masked so it dissolves upward into the page. min-width
|
||||
keeps the towers readable on narrow viewports (crops at the sides). */}
|
||||
<div className="absolute bottom-0 left-1/2 w-full min-w-[1100px] -translate-x-1/2">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={illustrationSrc('about-stockholm')}
|
||||
width={skyline.w}
|
||||
height={skyline.h}
|
||||
alt=""
|
||||
loading="eager"
|
||||
decoding="async"
|
||||
className="block h-auto w-full translate-y-[36%] opacity-[0.12] dark:opacity-10 dark:invert"
|
||||
style={{
|
||||
maskImage: 'linear-gradient(to top, black 55%, transparent 95%)',
|
||||
WebkitMaskImage: 'linear-gradient(to top, black 55%, transparent 95%)',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Halftone line-art illustrations shared with the marketing site
|
||||
// (gnubok-website public/illustrations/). Intrinsic dimensions are declared
|
||||
// up front so <img> layers get width/height and avoid layout shift. If you
|
||||
// copy more pieces from the website repo, add their manifest.json entry here.
|
||||
export const ILLUSTRATIONS = {
|
||||
'about-stockholm': { w: 2648, h: 1318 },
|
||||
calculator: { w: 745, h: 525 },
|
||||
'key-adding-machine': { w: 1650, h: 1318 },
|
||||
notebook: { w: 636, h: 525 },
|
||||
pencil: { w: 542, h: 525 },
|
||||
} as const
|
||||
|
||||
export type IllustrationName = keyof typeof ILLUSTRATIONS
|
||||
|
||||
export function illustrationSrc(name: IllustrationName): string {
|
||||
return `/illustrations/${name}.webp`
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Reference in New Issue
Block a user