In an era dominated by flat design and uniform SaaS dashboards, there is something nostalgic and deeply engaging about tactile interfaces. The Notebook Portfolio was designed to bring back the sensory feel of a real, handwritten engineering notebook—complete with paper margins, grid patterns, hand-drawn pencil sketches, and authentic 3D page-flip animations (experience the live application at subhrangsu.in).
This article explores the technical decisions, UX design patterns, and architectural details that power this interactive site—from CSS 3D hinges to reactive Angular 19 Signals, and the deep integration of a Gemini-powered AI assistant that dynamically controls the notebook.
1. The Core Concept: Skeuomorphic Design in a Flat Web Era
Skeuomorphic design bridges the digital-physical divide by imitating real-world objects. While flat design focuses on utility and minimalism, it often loses the playful, interactive charm of physical mediums.
This portfolio behaves like a physical diary. Key guidelines implemented include:
- Paper Textures: Dynamic background patterns matching gridded graph paper, ruled composition lines, and sketchpads.
- Hand-Drawn Aesthetics: Borders, indicators, and charts designed with slight irregularities to mimic ink pen scribbles and pencil markings.
- 3D Page Flipping: Interactive navigation that rotates sheets of paper along a central spine using CSS 3D transforms and depth sorting.
2. The CSS Magic: 3D Transforms and Custom Gradients
Building the page-flip transition in vanilla CSS required strict control over perspective and transformation origins. We avoid canvas-based rendering and rely entirely on modern CSS engines for GPU acceleration.
To implement a page hinge:
- The book container establishes a 3D context using
perspective: 1500pxandtransform-style: preserve-3d. - Each page sheet is positioned absolutely and given a hinge origin on its left side (
transform-origin: left center). - Turning a page is achieved by rotating it on the Y-axis (
transform: rotateY(-180deg)).
.book-page {
position: absolute;
width: 50%;
height: 100%;
top: 0;
transform-style: preserve-3d;
transition: transform 0.6s cubic-bezier(0.645, 0.045, 0.355, 1);
transform-origin: left center;
z-index: 1;
}
.book-page.flipped {
transform: rotateY(-180deg);
z-index: 10;
}
By leveraging preserve-3d and setting the pivot point at the left page edge, the page cleanly hinges backward. We combine this with dynamic z-index layering to ensure the active pages remain on top of the pile at any point during the flip. A shadow overlay shifts opacity dynamically during rotation to simulate realistic page lighting and depth.
3. Technical Foundation: Angular 19 Signals & URL-Driven Navigation
The entire application state is managed reactively using modern Angular 19 Signals. Instead of heavy state-management libraries, signals provide precise, granular DOM updates:
- Computed States: The dynamic experience tracker derives your career length using a computed signal linked to today's date:
readonly yearsOfExperience = computed(() => {
const start = new Date(this.profile().careerStartDate || '2024-01-01');
const diff = new Date().getTime() - start.getTime();
const years = diff / (1000 * 60 * 60 * 24 * 365.25);
return `${years.toFixed(1)}+ years`;
});
-
URL-Driven Page State: Navigation is fully URL-backed. Chapters map to real browser routes (
/about,/skills,/projects,/blogs/:id,/contact, etc.). When the user or Inkpot AI navigates, Angular'sRouter.navigate()updates both theactiveTabsignal and the browser URL in sync. This means every page is directly shareable via a deep link — pasting/experienceinto the address bar opens the notebook directly to that chapter, with full server-side pre-rendering on first load. -
Reactive URL Sync: An
effect()insideBookComponentwatches atoSignalwrapper aroundNavigationEndrouter events. Whenever the URL changes (from any source — tab click, AI command, or browser back button), the effect updatesactiveTab, triggers the 3D page-flip animation, and optionally selects the matching blog or project detail.
4. SSR & Incremental Hydration
The portfolio is fully server-rendered using Angular SSR with Express. Every chapter route (/about, /contact, etc.) is registered in app.routes.server.ts with RenderMode.Server. On first request, the server delivers a fully painted HTML document — the notebook is visible and readable before any JavaScript executes.
Hydration is managed with Angular's @defer blocks:
@defer (on idle; hydrate on idle) {
<app-about-page></app-about-page>
} @placeholder {
<app-page-placeholder></app-page-placeholder>
}
The on idle trigger defers loading the component chunk until the browser is free. The hydrate on idle strategy ensures Angular attaches event listeners and makes the component fully interactive as soon as the browser's idle callback fires — without waiting for user interaction. This eliminates the Flash of Unstyled Content (FOUC) that hydrate on interaction caused.
Deep-Link Refresh Without Flash
A subtle SSR challenge: when a user refreshes /contact, the server pre-renders the open book at the contact page. The client must initialize with isOpen = true and activeTab = 'contact' before Angular's hydration phase compares client DOM against server HTML — otherwise the template mismatch triggers a brief cover-page flash.
The fix reads window.location.pathname synchronously inside the root App constructor (falling back to router.url on the server), allowing both environments to resolve the correct initial state without a navigation subscription:
constructor() {
const initialUrl = typeof window !== 'undefined'
? window.location.pathname
: this.router.url;
const tab = tabFromUrl(initialUrl);
if (tab) {
this.isOpen.set(true);
this.initialTab.set(tab);
}
}
Canvas API Guard
The doodle drawing canvas (PencilCanvasComponent) uses HTMLCanvasElement.getContext('2d'), which is not implemented in Node.js SSR environments and throws a NotYetImplemented error. The fix injects PLATFORM_ID and wraps all canvas calls behind isPlatformBrowser:
private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
ngAfterViewInit(): void {
if (!this.isBrowser) return; // safe no-op on the server
this.ctx = this.canvasRef.nativeElement.getContext('2d')!;
this.resizeCanvas(true);
}
5. Inkpot AI: The Whimsical Companion
At the heart of the notebook sits Inkpot AI, a whimsical, pencil-sketch companion. Unlike basic chatbots that sit passively in a side panel, Inkpot AI acts as an active controller for the interface, capable of flipping pages, changing backing styles, muting sound, or searching through your projects.
The AI Engine: Gemini 2.5 Flash
The assistant is powered by Google's Gemini 2.5 Flash model. It was chosen for its low latency, high reasoning speed, and robust native support for function calling (tool execution), which are crucial for real-time UI interaction.
The SDK: Hashbrown AI
To connect Gemini to the Angular lifecycle, the project leverages the Hashbrown AI SDK (specifically @hashbrownai/angular and @hashbrownai/core). Hashbrown is a modern integration framework designed to bridge generative AI with reactive frontends.
It provides two main building blocks:
-
chatResource: A reactive Angular wrapper that manages the lifecycle of the conversation, handles token exchange, manages messaging queues, and exposes status signals like.isLoading()and.value(). -
createTool: A helper that allows developers to define client-side functions (tools) with strict schemas that Gemini can call.
6. Under the Hood: Deep Tool Integration & Schema Enforcement
Inkpot AI's ability to control the page comes from declarative schemas defined using Hashbrown's s validator. Here is how the interface operations are exposed to the AI:
Page Navigation Tool
Enables the AI to flip the book to any index page.
-
Schema: Expects a string representing the target tab (
'about' | 'skills' | 'projects' | 'experience' | 'blogs' | 'contact'). -
Handler: Calls
Router.navigate(['/' + tab]), which pushes a real URL to the browser history. Aneffect()inBookComponentreacts to the resultingNavigationEndevent, updates theactiveTabsignal, and triggers the 3D page-flip animation — keeping the AI navigation fully in sync with the browser's address bar and back button.
Paper Theme Customizer
Lets the user change the paper pattern via text commands (e.g., "Make the page look like graph paper").
-
Schema: Expects a string representing the backing style (
'lined' | 'grid' | 'dot' | 'blank'). - Handler: Updates the paper style signal, updating the CSS class on the notebook pages instantly.
Night Reading Lamp Toggle
Allows switching to night mode.
- Schema: Empty parameters.
- Handler: Invokes the theme service to toggle night mode, replacing standard styling with a soft amber ambient glow.
Interactive Search Tools (Projects & Blogs)
Visitors can ask the AI to find projects or articles (e.g., "Show me your tRPC projects").
- Schema: Expects a search query string.
-
Handler: Queries the Angular
PortfolioServicesignals dynamically. If a match is found, the page flips to the registry and automatically highlights the matching item card, rendering its preview content.
7. The Lifecycle of an AI-Driven UI Command
To understand how this integration operates end-to-end, here is the lifecycle of a single user prompt:
- User Input: The visitor types: "Hey Inkpot, turn on the night lamp and show me your experience page."
-
Message Dispatch: The
AiAssistantServiceupdates its reactivemessagessignal array and sends the text payload to Gemini via Hashbrown'schatResource. -
Model Reasoning: Gemini parses the request, determines that the user has two intents, and selects two appropriate tools:
toggle_night_lampandnavigate_to_page(tab: 'experience'). -
Execution Loop: Hashbrown intercepts the tool calls, executes the local TypeScript handler functions sequentially, and returns the execution status to the model.
-
toggle_night_lamp()fires -> updatesThemeService.isNightModesignal totrue-> UI shifts to amber night theme. -
navigate_to_page({ tab: 'experience' })fires -> updates page state signals -> notebook performs a 3D page flip.
-
- Final Output: Gemini receives the success status from both tools, synthesizes a final friendly response ("Certainly! I've lit the lamp and turned the pages to your experience logs."), and appends it to the chat stream.
8. Persona Engineering & ASCII Art
An AI companion is only as good as its personality. Inkpot AI is styled as a pencil sketch helper living inside the margins:
- Writing Constraints: System instructions mandate that responses be concise (under 2-3 sentences) to fit neatly inside a virtual notebook note, avoiding large, wall-of-text blocks common with vanilla LLMs.
- Creative Text Rendering: When asked to draw a sketch, the system instructions trigger Gemini to render small ASCII art doodles (like coffee cups, pencils, or quills) inside the terminal box.
- Aesthetic Integration: Chat bubbles are styled with handwritten fonts, complete with subtle scribble underlines and notebook border alignments.
References & Further Reading
- Live Demo: subhrangsu.in
- Angular Architects: An AI Assistant for Your Angular Application — Tool Calling in the Frontend with Hashbrown
- Google Gemini API Docs: ai.google.dev
- Hashbrown AI SDK: github.com/liveloveapp/hashbrown


Top comments (0)