Changelog
What's new in Hitman's Library
Every Interaction
A craft pass over every control in the app, against Emil Kowalski's design engineering principles. Most of this is meant to be felt rather than noticed.
Nothing in the app confirmed a press. All 27 controls now scale to 0.97 on press over 140ms — the single biggest reason the interface read as unresponsive, no matter how fast it actually was
Cards are too large to scale without looking cheap, so pressing one lights its border instead. The press registers through the whole card, wherever you hit it
No hover state was gated to fine pointers, so tapping on a phone left cards zoomed and buttons lit long after you had moved on. Movement-bearing hover is now pointer-gated
One easing curve was doing every job. Split into three: the signature curve for entering and leaving, symmetric acceleration for movement across the screen, and a plainer curve for hover and press that never overshoots
Reduced motion was flattening every transition to 0.01ms, including the colour and opacity changes that tell you something responded. It now neutralises movement only, and keeps the feedback
Keyboard-driven actions no longer animate: arrow keys through presentation mode and the detail tabs are repeated constantly, and animating them made every step feel like it lagged the key. Pointer and swipe navigation keep the motion
The detail tab underline moved from 300ms to 200ms on a movement curve — it is clicked often enough that the old timing read as drag
Two places used transition-all, animating layout properties off the GPU
Filtering, Rebuilt
Filtering worked but did not feel like it. Two real bugs, and a pass on the language around them.
Two quick category clicks both branched from the same URL snapshot, so the second silently dropped the first — picking SaaS then Finance left you with only Finance. Consecutive edits now compose
Filtering left you where you were scrolled, so a new result set opened halfway down and read as though nothing had changed. Changing a filter returns you to the top; opening a site still does not move the page
The sidebar All row was counting the filtered results, so selecting Finance made it read "All 13"
Selected categories now carry a rule on the leading edge and go to full ink, instead of a background wash you had to look for. The rule holds its slot when inactive, so nothing shifts
A filter bar above the grid shows everything currently applied — categories, tags and the search term as removable chips, with a result count and Clear all. Applied state used to be split between the sidebar, the header and a separate tag list
Result sets crossfade with the grid height held, rather than the whole grid dropping to 40% opacity and the page collapsing between sets
The favicon was v0's logo — the SVG shipped in the template was theirs, not ours. Rebuilt as the library's own hash mark, legible at 16px. Also dropped leftover v0 authorship metadata
Locked Down, Server-Rendered, Linkable
A full audit pass. Every write endpoint now has real authentication, the proxy can no longer be pointed at internal hosts, the gallery ships as HTML instead of an empty shell, and every view you can reach has a URL you can send someone.
Admin was a client-side illusion: the gate was a sessionStorage flag, so every write route — delete, extract, re-extract, bulk import, deduplicate, Figma backfill, mobile capture, element capture — was callable by anyone with curl. All of them now verify an HMAC-signed httpOnly session cookie server-side, with a bearer-token path for scripts
The link proxy validated only that a URL started with http — it would happily fetch cloud metadata endpoints and internal hosts and hand back the response. It now resolves DNS and rejects loopback, link-local, private, CGNAT and multicast ranges, re-checking on every redirect hop
The gallery is server-rendered: the grid, categories and counts arrive as HTML. Crawlers previously saw 141 characters of text on a library of 199 sites
Every view has a URL — category, tag, search, sort and the open site all live in the query string, so views are shareable and the back button works. A ?site= link opens that site even when it sits outside the first page of results
robots.txt, sitemap.xml and a web manifest, none of which existed; SVG favicon instead of a 28KB JPEG; filtered views are marked noindex so they stop competing with the index
Tag filtering never worked — clicking a tag refetched with the tag silently dropped, so nothing changed. Tags now filter properly, stack with categories, and show as removable chips
Titles were raw <title> tags: HTML entities rendered literally ("International Tax & Legal") and brands repeated the domain right below them. Entities are decoded and echoed brand suffixes trimmed, but only when what remains still describes the page
Broken thumbnails: some stored URLs kept their HTML entities and 404d; those are decoded on read. A new scripts/repair-screenshots.mjs finds sites whose screenshot uploaded as zero bytes (5 of 199) and re-extracts them
Root cause of those empty screenshots: Chrome returns an empty buffer rather than an error when a page is too tall to encode, and the result was uploaded unchecked. Captures are now clamped to 12000px and an empty buffer is never uploaded. Three of the five recovered by falling back to the site OG image; the two that resist capture entirely (sarvam.ai, vap.studio) show their domain, as designed
Search no longer replaces the grid with 32 skeletons on every keystroke — results stay put and dim while refetching, and out-of-order responses can no longer overwrite newer ones
The preloader was a flat 2.1s block unrelated to loading, and it server-rendered on every route, so /changelog shipped a blank overlay reading 000. It now races the page with a 900ms ceiling, is client-only, and sits out entirely for reduced-motion visitors
Cards: the visit link was invisible until hover, so touch users could never reach it — now always visible. Removed a link nested inside a role="button", and the five colour swatches announce as one palette instead of five hex codes
List and category responses are cached at the edge instead of no-store on every request; sort is its own labelled row on mobile rather than sharing a scroll strip with categories
Escape now closes the detail panel at every breakpoint, and the mobile focus trap no longer runs against a hidden sheet on desktop
Design Language Refinement
Same character, sharper execution. The language now runs on a real system: a six-step type scale, four ink levels, three edge weights, one radius, one easing — swept across every surface. Plus a roomier inspector.
Foundation tokens: ink levels (100/62/40/24) replace eleven ad-hoc text opacities; edge weights (70/50/30) replace four border treatments; 4px radius everywhere; one signature easing with four tokenized durations
Type scale: micro 10px / meta 11px / ui 12px / body 13px / title 14px — nothing in the UI renders below 10px anymore (specimen footers were 7.5px)
Roomier inspector: desktop split is now sidebar 2 / gallery 6 / panel 4 — specimens, palettes, and asset grids finally breathe; cards are wider at typical desktop widths (2-up, 3-up on very large screens)
Press / to focus search (keycap hint in the field); panel load failures now offer Retry
Panel tabs: sliding active underline (shared layout animation), legible 10px labels
One spinner, one copy-feedback pattern (in-place check morph, no layout shift — export buttons no longer jump), one section-label treatment, one focus ring
Calmer motion: scale-pop removed from panel content, card hover eased to 1.015 on the signature curve, all durations tokenized
Skeleton cards now match real card geometry exactly; mobile filter pills returned to library materials (mono, tabular counts); sheet respects safe-area
Presentation mode: white-alpha steps mirroring the ink system, keycap hints for pointer users, tokenized progress motion
Type specimens: fade-in-up keyframe was never defined in globals — specimens could render invisible; now defined
viewport themeColor now matches the real background tokens (#f7f7f5 / #0e0e0e)
Removed dead components theme-toggle.tsx and typography-display.tsx; duplicate Google Fonts link on specimens
Design Language Lift & Figma Tab Removed
Warmer, more refined UI across every surface — same density, better craft. Figma tab removed entirely.
Design cards: subtler hover scale (1.02×), visit link replaced with a blurred pill that reveals on hover, metadata section has cleaner hierarchy — title in tight tracking, domain in 40% opacity mono, industry in uppercase caps
Header title: "Hitman's Library" split so the apostrophe-s renders in font-light — intentional weight contrast, not a typo
Sidebar: active category now uses bg-muted/70 instead of full bg-foreground inversion — less aggressive, more editorial
Panel tabs: labels now uppercase with wider letter-spacing, 1.5px border indicator, slightly smaller icons — tighter and more refined
Panel header: hostname uses tighter tracking, industry/tag shows as uppercase badge below
Empty panel state: stale "figma" removed from hint text, type treatment more restrained
Globals: 2.2% SVG fractal noise grain overlay on body — gives all surfaces a material, warm quality instead of flat digital feel
Skeleton cards match the updated 3px border-radius of real cards
Removed Figma tab entirely — panel-tabs, site-detail-panel updated; figma-tab.tsx deleted
Designed States — No More Screenshot Crutches
Every "loading" and "blocked" state is now a proper designed UI — honest, intentional, and consistent with the design language. Screenshots are no longer used as automatic fallbacks when a live preview fails.
Preview tab loading state: clean domain name + three staggered pulsing dots — no screenshot background while the iframe loads. What you see while loading matches what you'll see when it's ready
Preview tab proxy-failed state: domain name + "Live preview unavailable" label + "Open site ↗" CTA. Honest about what's happening instead of silently showing a static screenshot as if it were live
Presentation mode proxy-failed state: subtle dot-grid background, large domain name, industry label, color palette swatches, and a "Visit site ↗" link. Blocked sites now feel like reference cards, not broken frames
Design card selected state: top accent line (2px) appears when a card is active — stronger visual signal that pairs with the brighter border
Design card color swatches are slightly larger (14px) for better readability at a glance
Empty panel state: "Select a site" now shows the list of available tabs (preview · colors · type · assets · figma) as a quiet visual hint of what's inside
Figma Layers — Copy Any Site as Editable Layers
New "Layers" button in the Figma tab copies the full site as real Figma layers — text, shapes, and structure — not a flat screenshot. No plugin required.
Layers button in the Figma tab — opens the site in a capture window using Figma's own publicly-hosted capture.js script (the same tool that powers their AI integrations). Click "Copy to clipboard" in the toolbar that appears, then ⌘V in Figma to get fully editable text, shapes, and layout — not a rasterized PNG
Element selection in capture mode — the capture toolbar lets you select any component (nav, card, pricing section) instead of capturing the full page, so you can pull only what you need into Figma
Performance & Design Polish
Faster perceived load across preview and presentation mode. Sharper visual hierarchy in the sidebar and detail panel.
Presentation mode shows the site thumbnail immediately while the proxy iframe loads — navigating between sites now feels instant instead of flashing to black
Presentation mode: added always-visible prev/next buttons to the bottom HUD — arrows are now immediately discoverable instead of requiring hover over the preview area
Sidebar active category now has a left border accent — clearer visual signal for which filter is selected, especially when multiple industries are active
Panel tabs: active tab label is now font-medium — stronger differentiation from inactive tabs at a glance
Critical: App Crash on Site Preview
Fixed a root-cause bug that crashed the entire app when previewing certain sites.
Added sandbox attribute to all proxy iframes — the root cause of "Application error" crashes. The proxy serves external sites at the hitmanslibrary.xyz origin, so their JavaScript ran same-origin, letting any unhandled JS error in those sites fire window.onerror on the parent and trigger the Next.js error page. Sandboxing the iframes without allow-same-origin isolates errors to the iframe while keeping postMessage (used for proxy-failed signals and the Figma picker) fully functional.
Presentation Mode Redesign & Touch Targets
Presentation mode rebuilt from the ground up with a minimal overlay HUD. Touch targets increased to 44px+ across the entire UI.
Presentation mode: minimal overlay HUD — controls float at top-right and fade in after a beat so the first thing you see is the site, not chrome
Presentation mode: nav arrows appear only on hover over the preview area, keeping the experience immersive when you're just watching
Presentation mode: thin animated progress bar at the very top shows your position across the full collection at a glance
Presentation mode: bottom HUD shows domain, industry, counter, and color palette in one compact row instead of two separate top/bottom bars
Presentation mode: info row slides in the direction of navigation — left when going back, right when going forward — giving a physical sense of movement
Presentation mode: swipe left/right to navigate on touch devices — works on any phone or tablet without tapping arrows
Header icon buttons increased from 32px to 36px — easier to tap accurately on touch screens
Mobile category filter pills increased from 28px to 40px height — were consistently too small to tap without mis-tapping adjacent pill
Sidebar filter rows increased to 36px — matches minimum recommended touch target for dense lists
Detail panel action buttons (re-extract, close) increased from 32px to 36px
Panel tabs increased from 40px to 44px height — meets Apple HIG minimum touch target recommendation
Color copy button expanded to 28px minimum — was previously a 12px hit target from a 4px padding box
Type specimen action icons (copy, Google Fonts link) increased from 24px to 32px
Performance, Bug Fixes & Figma Tab
Fixed site-wide performance regression, wired the missing Figma tab, and patched several data display bugs.
Figma tab now appears in the detail panel — it was fully implemented but never wired into the panel tabs
Replaced transition-all on all buttons and links with transition-colors — eliminates browser compositing cost across all CSS properties on every click target
Sidebar and mobile filter "All" count now shows the real total (e.g. 177) instead of the current page load (32)
Removed invalid workflow key from next.config.mjs that was generating startup warnings on every dev server boot
TypeScript Bug Fixes
Full type-error sweep — zero TS errors across the entire codebase.
Created missing UI components: Label, Select, Badge, Tabs, ScrollArea, Textarea, Slider — many node and panel components were importing these non-existent files
Created missing lib/node-utils module — all node components importing getStatusColor() were silently broken at runtime
Node components now use correct @xyflow/react v12 NodeProps generic — NodeProps<FlowNode<DataType>> instead of NodeProps<DataType>
fetch() timeout option removed — not part of RequestInit; replaced with AbortController signal for proper 8s timeout on URL extraction
motion/react Variants type — spring transition type is now a string literal (as const) so it satisfies AnimationGeneratorType
unknown → ReactNode errors in 6 node files — output conditions now use !! to guarantee boolean before &&
Missing CheckCircle import in excel-parser node
prompt-node onFocus handler accepts SyntheticEvent instead of MouseEvent
Implicit any on Slider onValueChange callbacks — destructured value now typed as number[]
Implicit any on forEach callbacks in browser-extraction.ts — all font string arrays now typed explicitly
Accessibility, Polish & Code Health
Keyboard navigation, screen-reader labels, design-token alignment, and bundle cleanup from a full quality audit.
Removed unused DesignBrowser component — eliminates dead code and reduces bundle
Type specimen sample text and glyphs now render at correct opacity — was using hsl() around an oklch token which browsers silently ignored
Design cards are now keyboard-navigable — Tab to reach, Enter or Space to open the detail panel
Status colors (success, error, loading) now use design tokens — consistent across nodes, type specimens, figma tab, and changelog
Consolidated to single icon library (@phosphor-icons) — removed lucide-react dependency
Search inputs now have accessible labels for screen readers via aria-label
Filter and sort buttons now announce their active/inactive state to screen readers via aria-pressed
Color swatch copy button now announces its action and value to screen readers
Re-extract button uses aria-label instead of title — now accessible to screen readers and keyboard users
Mobile detail sheet upgraded to native <dialog> with showModal() — gains proper focus trapping, Escape to close, and system backdrop
Presentation mode screenshot alt text now describes the site by name, not just domain
Extracted shared NodeStatus component — eliminates duplicated status icon logic across all node components
Google Fonts stylesheet now only injected once per URL — prevents duplicate network requests when multiple type specimens share a font
Theme toggle button now announces the destination mode ("Switch to dark mode") rather than just "Toggle theme"
Preloader is now hidden from screen readers via aria-hidden — decorative counter is not meaningful content
Preloader setTimeout calls now properly cleaned up on unmount — eliminates potential state updates after component is removed
Color swatches on gallery cards now have role="img" and aria-label with the hex value for screen readers
Replaced all spring/bounce easing (0.34,1.56,0.64,1) with ease-out-expo (0.22,1,0.36,1) — animations feel faster and more intentional
Extracted DesignCard component and getDomain helper to their own files — reduces page.tsx by ~130 lines
Assets tab checkerboard background now uses design tokens — adapts correctly to light and dark mode
Preview tab screenshot image now uses loading="lazy" — deferred until the panel is actually viewed
Re-extract and close icon buttons in the detail panel increased from 28px to 32px for easier touch activation
Package name corrected from "ai-agent-builder" to "hitmans-library"
Figma Native Paste, Thumbnail Fix & UI Polish
Copy any screenshot directly to your clipboard and paste into Figma without any plugin. All 177 site cards now show their own captured screenshots reliably.
Figma tab — copy Desktop or Mobile screenshot as a PNG image, paste directly into Figma with ⌘V — no html.to.design plugin required
Sort pills in header — New / Old / A–Z / Top (quality)
Multi-select industry filters — hold multiple categories at once
Card hover reveals a visit ↗ button without opening the detail panel
Tags shown on gallery cards and in the detail panel header
Detail panel hostname is now a direct link to the site
All 177 thumbnails now use captured blob screenshots — eliminates blank cards from rate-limited external screenshot services
Sidebar category counts now match exactly what the gallery shows
Font 404 errors — removed dead @font-face declarations for missing SuisseIntl files
Extraction error UI in Preview tab was never visible due to an impossible condition
Re-extract could crash Colors/Type/Assets tabs by not normalizing null arrays
Quality sort silently fell back to "newest" — now correctly sorts by extracted quality score
SVG assets tab had an XSS vector from inline rendering of raw DB content
Breakpoints, Full-Page Copy & Bug Fixes
Figma tab now lets you simulate any breakpoint and copy the full page. Stability fixes eliminate the client-side crash and missing site cards.
Breakpoint selector in Figma tab — switch between Auto / 390 / 768 / 1440px and see the site respond live
Full-page copy — one click captures the entire page as Figma layers, not just a single element
Client-side exception on some sites — API error responses now handled gracefully, no more crash on load
Site cards not visible — gallery query was referencing non-existent DB columns, now uses safe correlated subqueries
Retina screenshots — desktop now captured at 2× DPR, mobile at 3×, with lazy-image scroll-through before capture
Card image hover — CSS transition replaces motion.img for better performance and no layout shift
Figma Element Picker & Live Preview
The Figma tab now shows the live site — hover to inspect, click any element to copy it to Figma instantly. Full-page copy is pre-loaded so it's immediate too.
Element picker — hover any element to highlight it, click to copy directly to Figma
Full-page Figma layers pre-loaded on tab open — copy is instant, no wait
HTML proxy — loads any site inside the panel regardless of X-Frame-Options restrictions
Live preview — embedded iframe replaces static screenshots entirely
Figma tab — dedicated panel tab with auto-capture on first open
Search input in header — search across all sites in real time
Panel header redesigned — compact icon row for visit, re-extract, and close
Preview simplified — responsive iframe with no desktop/mobile toggle needed
Filter effect dep used JSON.stringify on every render — replaced with stable primitives
Copy to Figma, Mobile Views & More
A batch of tools that make the library more actionable — export designs, capture mobile layouts, and send layers directly to Figma.
Copy to Figma — paste any captured site as editable layers directly into Figma
Mobile screenshots alongside desktop — captured at 390×844 during extraction
Desktop / Mobile toggle in the preview tab
Palette export — copy colors as CSS custom properties or Tailwind config in one click
Bulk add — paste multiple URLs at once and watch them process in a live queue
Preloader — 000→100 counter on first visit
HEX and OKLCH values are now separately copyable from the colors tab
UI Redesign & Sound
The whole interface got a quiet, high-contrast overhaul. Sound feedback added for copy and success actions.
Full gallery layout redesign — cleaner grid, tighter type, better dark mode
Subtle sound effects on copy and successful extraction
Admin CMS — passcode protection and one-click duplicate removal
Admin page was unscrollable due to a global overflow:hidden — resolved
Performance, Polish & Mobile
Faster perceived load, better mobile interactions, and more color control.
Skeleton loading on first page load and category filtering
HEX / OKLCH format toggle on the colors tab
Dark / light theme toggle — instant, no flash of wrong theme
Category labels rewritten to better reflect actual design context
Mobile sheet — safe-area insets and 44px touch targets throughout
Prevented scroll chaining between preview panel and page body on mobile
Edge case: achromatic and scientific-notation OKLCH values now parse correctly
Real Screenshots
Cards now show actual screenshots of the captured site instead of color blocks.
Live website screenshots displayed on every design card
Switched to a reliable headless screenshot pipeline after testing multiple providers
Copy Feedback & Social
Small interactions that make copying feel right.
Copy feedback on color swatches — animated toast on click
OG image and social meta for sharing the library
Microinteractions across the UI following Emil Kowalski's principles
Initial Launch
Hitman's Library ships. Extract, browse, and reference design systems from any URL.
Color palette extraction — brand colors with OKLCH values
Typography extraction — font families, roles, and Google Fonts links
Asset catalog — logos, icons, images discovered per site
Industry categorization for every captured site
Browse and search across your entire design library
Excel import for bulk seeding the library