
스큐어모피즘의 재탄생: 노트북 포트폴리오 디자인
요약
플랫 디자인에서 벗어나 스큐어모피즘을 활용한 인터랙티브 노트북 포트폴리오 제작 과정을 다룹니다. CSS 3D 변환을 통한 페이지 넘기기 효과와 Angular 19 Signals, Gemini AI 통합 등 기술적 구현 방식을 설명합니다.
핵심 포인트
- CSS 3D Transforms를 활용한 사실적인 페이지 넘기기 구현
- 스큐어모피즘 디자인을 통한 몰입감 있는 UX 제공
- Angular 19 Signals 기반의 상태 관리 및 아키텍처
- Gemini AI를 활용한 노트북 제어 어시스턴트 통합
플랫 디자인 (Flat Design)과 획일적인 SaaS 대시보드가 지배하는 시대에, 촉각적인 인터페이스는 향수를 불러일으키며 깊은 몰입감을 선사합니다. Notebook Portfolio는 종이 여백, 그리드 패턴, 손으로 그린 연필 스케치, 그리고 실제와 같은 3D 페이지 넘기기 애니메이션을 갖춘 실제 수기 엔지니어링 노트북의 감각적인 느낌을 되살리기 위해 설계되었습니다 (subhrangsu.in에서 라이브 애플리케이션을 체험해 보세요).).
이 글에서는 CSS 3D 힌지 (Hinges)부터 반응형 Angular 19 Signals, 그리고 노트북을 동적으로 제어하는 Gemini 기반 AI 어시스턴트의 깊은 통합에 이르기까지, 이 인터랙티브 사이트를 구동하는 기술적 결정, UX 디자인 패턴 및 아키텍처 세부 사항을 탐구합니다.
1. 핵심 컨셉: 플랫한 웹 시대의 스큐어모피즘 (Skeuomorphic) 디자인
스큐어모피즘 (Skeuomorphic) 디자인은 실제 세계의 사물을 모방함으로써 디지털과 물리적 사이의 간극을 메웁니다. 플랫 디자인이 유용성과 미니멀리즘에 집중하는 동안, 물리적 매체가 가진 유희적이고 인터랙티브한 매력을 놓치는 경우가 많습니다.
이 포트폴리오는 실제 다이어리처럼 작동합니다. 구현된 주요 가이드라인은 다음과 같습니다:
- 종이 질감 (Paper Textures): 격자무늬 그래프 용지, 줄이 그어진 노트, 스케치북과 일치하는 동적 배경 패턴.
- 손그림 미학 (Hand-Drawn Aesthetics): 잉크 펜의 낙서나 연필 자국을 흉내 내기 위해 약간의 불규칙함을 더해 디자인된 테두리, 인디케이터 및 차트.
- 3D 페이지 넘기기 (3D Page Flipping): CSS 3D 변환 (Transforms) 및 깊이 정렬 (Depth Sorting)을 사용하여 중앙 스파인을 따라 종이 한 장을 회전시키는 인터랙티브 내비게이션.
2. CSS의 마법: 3D 변환 (Transforms) 및 커스텀 그라데이션 (Custom Gradients)
바닐라 CSS (Vanilla CSS)로 페이지 넘기기 전환 효과를 구축하려면 원근감 (Perspective)과 변환 원점 (Transformation Origins)에 대한 엄격한 제어가 필요했습니다. 우리는 캔버스 (Canvas) 기반 렌더링을 피하고, GPU 가속을 위해 전적으로 현대적인 CSS 엔진에 의존합니다.
페이지 힌지 (Hinge)를 구현하기 위해:
- 책 컨테이너는
perspective: 1500px와transform-style: preserve-3d를 사용하여 3D 컨텍스트 (3D context)를 설정합니다. - 각 페이지 시트 (page sheet)는 절대 위치 (absolute position)로 배치되며, 왼쪽 측면에 힌지 원점 (hinge origin)이 부여됩니다 (
transform-origin: left center). - 페이지를 넘기는 동작은 Y축을 기준으로 회전시켜 구현합니다 (
transform: rotateY(-180deg)).
.book-page {
position: absolute;
width: 50%;
...
preserve-3d를 활용하고 회전축 (pivot point)을 왼쪽 페이지 가장자리에 설정함으로써, 페이지가 뒤로 깔끔하게 힌지 (hinge) 됩니다. 여기에 동적인 z-index 레이어링 (z-index layering)을 결합하여, 페이지가 넘어가는 어느 시점에서도 활성화된 페이지가 더미의 가장 위에 머물도록 보장합니다. 그림자 오버레이 (shadow overlay)는 회전 중에 불투명도 (opacity)를 동적으로 변화시켜 사실적인 페이지 조명과 깊이감을 시뮬레이션합니다.
3. 기술적 토대: Angular 19 Signals 및 URL 기반 네비게이션 (URL-Driven Navigation)
애플리케이션의 전체 상태는 현대적인 Angular 19 Signals를 사용하여 반응형 (reactively)으로 관리됩니다. 무거운 상태 관리 라이브러리 대신, Signals는 정밀하고 세밀한 DOM 업데이트를 제공합니다:
- 계산된 상태 (Computed States): 동적인 경력 추적기 (experience tracker)는 오늘 날짜와 연결된 계산된 시그널 (computed signal)을 사용하여 경력 기간을 도출합니다:
readonly yearsOfExperience = computed(() => {
const start = new Date(this.profile().careerStartDate || '2024-01-01');
const diff = new Date().getTime() - start.getTime();
...
- URL 기반 페이지 상태 (URL-Driven Page State): 내비게이션(Navigation)은 완전히 URL을 기반으로 동작합니다. 각 챕터는 실제 브라우저 경로(
/about,/skills,/projects,/blogs/:id,/contact등)에 매핑됩니다. 사용자나 Inkpot AI가 내비게이션할 때, Angular의Router.navigate()는activeTab시그널(signal)과 브라우저 URL을 동시에 동기화하여 업데이트합니다. 이는 모든 페이지가 딥 링크(deep link)를 통해 직접 공유 가능하다는 것을 의미합니다. 예를 들어, 주소창에/experience를 붙여넣으면 첫 로드 시 서버 사이드 프리렌더링(server-side pre-rendering)이 적용된 상태로 노트북이 해당 챕터로 즉시 열립니다. - 반응형 URL 동기화 (Reactive URL Sync):
BookComponent내부의effect()가NavigationEnd라우터 이벤트를 감싸는toSignal래퍼(wrapper)를 감시합니다. URL이 변경될 때마다(탭 클릭, AI 명령, 또는 브라우저 뒤로 가기 버튼 등 모든 소스로부터), 이 이펙트(effect)는activeTab을 업데이트하고, 3D 페이지 넘김 애니메이션을 트리거하며, 선택적으로 일치하는 블로그 또는 프로젝트 상세 정보를 선택합니다.
4. SSR 및 점진적 하이드레이션 (Incremental Hydration)
이 포트폴리오는 Express를 사용한 Angular SSR을 통해 완전히 서버 사이드 렌더링(server-rendered)됩니다. 모든 챕터 경로(/about, /contact 등)는 app.routes.server.ts에 RenderMode.Server로 등록되어 있습니다. 첫 요청 시, 서버는 완전히 그려진 HTML 문서를 전달하므로, 어떤 JavaScript가 실행되기 전에도 노트북을 보고 읽을 수 있습니다.
하이드레이션(Hydration)은 Angular의 @defer 블록을 통해 관리됩니다:
@defer (on idle; hydrate on idle) {
<app-about-page></app-about-page>
} @placeholder {
...
on idle 트리거는 브라우저가 한가해질 때까지 컴포넌트 청크(chunk)의 _로딩(loading)_을 지연시킵니다. hydrate on idle 전략은 브라우저의 idle 콜백(callback)이 실행되는 즉시 Angular가 이벤트 리스너를 부착하고 컴포넌트를 완전히 상호작용 가능한 상태로 만들도록 보장하며, 사용자의 상호작용을 기다리지 않습니다. 이를 통해 hydrate on interaction 방식에서 발생하던 스타일이 적용되지 않은 콘텐츠의 깜빡임(Flash of Unstyled Content, FOUC) 현상을 제거합니다.
깜빡임 없는 딥 링크 새로고침
미묘한 SSR (Server-Side Rendering) 과제: 사용자가 /contact를 새로고침할 때, 서버는 연락처 페이지에서 펼쳐진 책을 사전 렌더링(pre-render)합니다. 클라이언트는 Angular의 하이드레이션 (Hydration) 단계에서 클라이언트 DOM을 서버 HTML과 비교하기 _전_에 isOpen = true 및 activeTab = 'contact' 상태로 초기화되어야 합니다. 그렇지 않으면 템플릿 불일치(template mismatch)로 인해 표지 페이지가 잠시 깜빡이는 현상이 발생합니다.
해결책은 루트 App 생성자 내부에서 window.location.pathname을 동기적으로 읽는 것입니다 (서버에서는 router.url로 대체). 이를 통해 내비게이션 구독(navigation subscription) 없이도 양쪽 환경 모두에서 올바른 초기 상태를 결정할 수 있습니다:
constructor() {
const initialUrl = typeof window !== 'undefined'
? window.location.pathname
...
Canvas API 가드 (Guard)
낙서 그리기 캔버스(PencilCanvasComponent)는 HTMLCanvasElement.getContext('2d')를 사용하는데, 이는 Node.js SSR 환경에 구현되어 있지 않아 NotYetImplemented 에러를 발생시킵니다. 해결책은 PLATFORM_ID를 주입하고 모든 캔버스 호출을 isPlatformBrowser로 감싸는 것입니다:
private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
ngAfterViewInit(): void {
...
5. Inkpot AI: 기발한 동반자
노트북의 중심에는 기발한 연필 스케치 동반자인 Inkpot AI가 자리 잡고 있습니다. 사이드 패널에 수동적으로 머무는 기본적인 챗봇과 달리, Inkpot AI는 인터페이스를 능동적으로 제어하는 컨트롤러 역할을 수행하며, 페이지를 넘기거나, 배경 스타일을 변경하거나, 소리를 음소거하거나, 프로젝트를 검색할 수 있습니다.
AI 엔진: Gemini 2.5 Flash
이 어시스턴트는 Google의 Gemini 2.5 Flash 모델을 기반으로 작동합니다. 이 모델은 낮은 지연 시간(low latency), 빠른 추론 속도, 그리고 실시간 UI 상호작용에 필수적인 함수 호출(function calling, 도구 실행)에 대한 강력한 네이티브 지원 덕분에 선택되었습니다.
SDK: Hashbrown AI
Gemini를 Angular 라이프사이클(lifecycle)에 연결하기 위해, 이 프로젝트는 Hashbrown AI SDK (구체적으로 @hashbrownai/angular 및 @hashbrownai/core)를 활용합니다. Hashbrown은 생성형 AI와 반응형 프론트엔드(reactive frontends)를 연결하기 위해 설계된 현대적인 통합 프레임워크입니다.
이 프레임워크는 두 가지 주요 빌딩 블록(building blocks)을 제공합니다:
chatResource: 대화의 생명주기(lifecycle)를 관리하고, 토큰 교환을 처리하며, 메시지 큐(messaging queues)를 관리하고,.isLoading()및.value()와 같은 상태 시그널(status signals)을 노출하는 반응형 Angular 래퍼(wrapper)입니다.createTool: 개발자가 Gemini가 호출할 수 있는 엄격한 스키마(schemas)를 가진 클라이언트 측 함수(도구)를 정의할 수 있게 해주는 헬퍼(helper)입니다.
6. 내부 구조: 심층 도구 통합 및 스키마 강제(Schema Enforcement)
페이지를 제어하는 Inkpot AI의 능력은 Hashbrown의 s 검증기(validator)를 사용하여 정의된 선언적 스키마(declarative schemas)에서 비롯됩니다. 인터페이스 작업이 AI에 노출되는 방식은 다음과 같습니다:
페이지 탐색 도구 (Page Navigation Tool)
AI가 책을 어떤 인덱스 페이지로든 넘길 수 있게 합니다.
- 스키마 (Schema): 대상 탭을 나타내는 문자열(
'about' | 'skills' | 'projects' | 'experience' | 'blogs' | 'contact')을 기대합니다. - 핸들러 (Handler):
Router.navigate(['/' + tab])를 호출하며, 이는 브라우저 히스토리에 실제 URL을 푸시합니다.BookComponent의effect()가 결과로 발생하는NavigationEnd이벤트에 반응하여activeTab시그널을 업데이트하고 3D 페이지 넘기기 애니메이션을 트리거합니다. 이를 통해 AI 탐색을 브라우저의 주소창 및 뒤로 가기 버튼과 완전히 동기화합니다.
종이 테마 커스텀 도구 (Paper Theme Customizer)
사용자가 텍스트 명령(예: "페이지를 모눈종이처럼 만들어줘")을 통해 종이 패턴을 변경할 수 있게 합니다.
- 스키마 (Schema): 배경 스타일을 나타내는 문자열(
'lined' | 'grid' | 'dot' | 'blank')을 기대합니다. - 핸들러 (Handler): 종이 스타일 시그널(paper style signal)을 업데이트하여 노트북 페이지의 CSS 클래스를 즉시 변경합니다.
야간 독서등 토글 (Night Reading Lamp Toggle)
나이트 모드(night mode)로 전환할 수 있게 합니다.
- 스키마 (Schema): 빈 파라미터(Empty parameters).
- 핸들러 (Handler): 테마 서비스(theme service)를 호출하여 나이트 모드를 토글하며, 표준 스타일을 부드러운 호박색(amber)의 주변광(ambient glow)으로 교체합니다.
대화형 검색 도구 (프로젝트 및 블로그)
방문자는 AI에게 프로젝트나 기사를 찾아달라고 요청할 수 있습니다 (예: "tRPC 프로젝트를 보여줘").
- 스키마 (Schema): 검색 쿼리 문자열을 기대합니다.
- 핸들러 (Handler): Angular의
PortfolioService시그널 (signals)을 동적으로 쿼리합니다. 일치하는 항목이 발견되면, 페이지가 레지스트리(registry)로 뒤집히며 일치하는 아이템 카드를 자동으로 강조 표시하고 해당 미리보기 콘텐츠를 렌더링합니다.
7. AI 기반 UI 명령의 라이프사이클 (Lifecycle)
이 통합 기능이 엔드 투 엔드 (end-to-end)로 어떻게 작동하는지 이해하기 위해, 단일 사용자 프롬프트의 라이프사이클은 다음과 같습니다:
- 사용자 입력 (User Input): 방문자가 다음과 같이 입력합니다: "헤이 Inkpot, 밤등을 켜고 네 경력 페이지를 보여줘."
- 메시지 디스패치 (Message Dispatch):
AiAssistantService가 반응형(reactive)messages시그널 배열을 업데이트하고, Hashbrown의chatResource를 통해 Gemini로 텍스트 페이로드 (payload)를 전송합니다. - 모델 추론 (Model Reasoning): Gemini가 요청을 파싱하여 사용자의 의도가 두 가지임을 판단하고, 두 가지 적절한 도구인
toggle_night_lamp와navigate_to_page(tab: 'experience')를 선택합니다. - 실행 루프 (Execution Loop): Hashbrown이 도구 호출을 가로채어 로컬 TypeScript 핸들러 함수를 순차적으로 실행하고, 실행 상태를 모델에 반환합니다.
toggle_night_lamp()실행 ->ThemeService.isNightMode시그널을true로 업데이트 -> UI가 호박색 밤 테마로 전환됩니다.navigate_to_page({ tab: 'experience' })실행 -> 페이지 상태 시그널을 업데이트 -> 노트북이 3D 페이지 뒤집기 동작을 수행합니다.
- 최종 출력 (Final Output): Gemini가 두 도구로부터 성공 상태를 수신하면, 최종적인 친절한 응답("물론이죠! 등불을 켜고 경력 기록 페이지를 펼쳤습니다.")을 합성하여 채팅 스트림 (chat stream)에 추가합니다.
8. 페르소나 엔지니어링 (Persona Engineering) 및 ASCII 아트
8. 페르소나 엔지니어링 (Persona Engineering) 및 ASCII 아트
AI 동반자(AI companion)는 그 성격만큼만 훌륭할 수 있습니다. Inkpot AI는 여백 속에 살아가는 연필 스케치 도우미 스타일로 디자인되었습니다.
- 작성 제약 사항 (Writing Constraints): 시스템 지침(System instructions)은 응답이 가상 노트북 메모 안에 깔끔하게 들어갈 수 있도록 간결하게(2~3문장 미만) 작성할 것을 명령하며, 일반적인 LLM(Large Language Models)에서 흔히 나타나는 거대한 텍스트 블록을 방지합니다.
- 창의적 텍스트 렌더링 (Creative Text Rendering): 스케치를 그려달라는 요청을 받으면, 시스템 지침이 Gemini를 트리거하여 터미널 박스 안에 작은 ASCII 아트 낙서(커피 컵, 연필, 깃펜 등)를 렌더링합니다.
- 미적 통합 (Aesthetic Integration): 채팅 버블은 손글씨 폰트로 스타일링되었으며, 미묘한 낙서 형태의 밑줄과 노트북 테두리 정렬이 포함되어 있습니다.
참고 문헌 및 추가 읽기
- 라이브 데모 (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
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기