This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Open Profiler is an open-source Angular 21+ portfolio/personal profile template (v1.3.1). It's a single-page application designed to be forked and customized via JSON configuration files. Users showcase their skills, experience, projects, and contact info without modifying code.
Repository: https://github.com/Til0r/open-profiler
# Start dev server
npm start
# Production build (output: dist/open-profiler/)
npm run build
# Watch mode for development
npm run watch
# Run tests (Jasmine + Karma)
npm test
# Lint code (ESLint with Angular rules)
npx ng lint
# Format code with Prettier
npx prettier --write "src/**/*.{ts,json,scss,md}"- Standalone Components: All components use
standalone: true— no NgModules - Signals-based State: Uses Angular Signals (
input(),signal(),computed()) for component state - No Global State Management: No NgRx or other store — data flows from JSON config via signals
- OnPush Change Detection: Components use
ChangeDetectionStrategy.OnPush - Single Page: No routing (
app.routes.tsis empty) — all sections render on one page - Template Control Flow: Uses
@if,@for,@elsesyntax (Angular 17+) - Configuration-Driven: All portfolio content defined in
config/open-profiler.config.json
src/app/
├── app.component.ts # Root component — loads config, sets theme
├── app.config.ts # Angular bootstrap configuration
├── app.routes.ts # Empty (no routing)
├── shared/
│ ├── config/
│ │ └── open-profiler.config.ts # Config loader service
│ ├── constants/
│ │ └── theme.constant.ts # Theme CSS variable mappings
│ ├── directives/
│ │ └── image-error-handler.directive.ts # Hides broken images
│ ├── elements/ # Reusable UI components
│ │ ├── badges/ # Skills badges with icons
│ │ ├── experience/ # Experience/education cards
│ │ └── socials/ # Social link icons
│ ├── models/ # TypeScript interfaces (10+)
│ │ ├── open-profiler.model.ts # Root data model
│ │ ├── experience.model.ts
│ │ ├── project.model.ts
│ │ ├── badge.model.ts
│ │ └── ...
│ └── utils/
│ └── badges.util.ts # Icon link mapper
├── environments/
│ └── environments.ts
config/ # External config (project root)
├── open-profiler.config.json # Main portfolio data
└── seo.config.json # SEO metadata
All content comes from config/open-profiler.config.json implementing OpenProfilerModel:
| Field | Type | Purpose |
|---|---|---|
about |
string |
HTML-formatted bio |
banner |
BannerModel |
Name, role, city, avatar, socials |
badges |
BadgesModel |
Skills organized by category |
contact |
ContactModel |
Email and contact info |
projects |
ProjectModel[] |
Portfolio project cards |
color |
ColorModel |
Primary, dark/light theme colors |
education |
ExperienceModel[] |
Education entries (sorted by date) |
experiences |
ExperienceModel[] |
Work experience (nested children OK) |
- Component selectors:
open-profiler-*prefix (kebab-case) — e.g.,<open-profiler-badges> - Directive selectors: camelCase attribute — e.g.,
imageErrorHandler - Path aliases (
tsconfig.json):@open-profiler/models/*,@open-profiler/config/*,@open-profiler/constants/*@open-profiler/utils/*,@open-profiler/directives/*,@open-profiler/elements/*@open-profiler/env/*,@open-profiler/outer-config/*
- Tailwind CSS v4 + SCSS for component styles
- CSS custom properties for runtime theming (dark/light mode)
- Dark mode toggle persisted in
localStorage - Responsive design via Tailwind breakpoints
- Banner, About, Education, Skills, Experience, Projects, Contact sections
- Dark/Light theme toggle with localStorage persistence
- Simple Icons integration (fetches SVGs from simpleicons.org)
- Image error handler directive (hides broken images)
- Chronological sorting via lodash
orderBy - Nested experience support (sub-roles within a position)
Every component must be atomic. Before creating a new component, check if one already exists in the project. If not, create it as a shared, reusable unit (component, utility, or shared class).
Templates and widgets must reuse components from the shared elements. Never duplicate logic or UI that already exists.
All styling must use TailwindCSS classes. No inline styles or custom CSS unless strictly necessary.
Use enums, maps, or typed constants instead of raw strings. Never pass magic strings as props, action types, or identifiers. Never use any — always create proper interfaces, types, or generics.
Add TSDoc comments to all complex logic, non-obvious utilities, and public API surfaces of shared components.
Use Angular Signals for local component state. Follow standard Angular patterns (standalone components, dependency injection, lifecycle hooks).
Use Moment.js for all date manipulation and formatting.
Do not overcomplicate. Keep the configuration-driven approach — content changes should happen in JSON config files, not in component code.
Never call functions or methods in templates to return or compute values. Use instead: Signals, computed signals, pure Pipes, or Directives. The only acceptable methods in templates are:
- Event handlers (
(click),(submit),(change), etc.) - Memoized functions (functions whose output is cached and only recalculated when inputs change)
Follow strict naming conventions:
- Interfaces — Prefix with
I:IUser,IOrderDetail - Enums — Prefix with
E:EOrderStatus,EPaymentMethod - Generics — Use typed generics (
T,K,V) instead ofany - Never use
any— If the type is unknown, useunknownand narrow it.
Every component must use ChangeDetectionStrategy.OnPush.
Always provide a track expression in @for when iterating over lists. Use a unique ID.
@for (item of items; track item.id) {
<div>{{ item.name }}</div>
}Never leave subscriptions open. Use takeUntilDestroyed() from @angular/core/rxjs-interop or the | async pipe which auto-unsubscribes.
Each file should do one thing. One component, one service, one pipe, one directive per file.
Never mutate state directly. Always create new references using spread operators, map(), filter(), or structuredClone.
Every interactive element must be keyboard-accessible. Use semantic HTML. Add aria-label where visual context is missing. Never use <div> as a clickable element without a role.
Extract all hardcoded values into typed constants files.
- TypeScript strict mode enabled
- ESLint with angular-eslint rules
- Prettier formatting (100 char width, single quotes, 2-space indent)
- Husky + lint-staged pre-commit hooks (prettier + eslint + updatemetatag)
GitHub Actions workflow (.github/workflows/release.yml) creates releases on git tag push — builds the project, creates a tar archive, and uploads to GitHub Releases.