Always keep this file up to date whenever anything changes in the codebase.
React 18 + Vite 6 + Tailwind CSS 4 + Framer Motion + Bun + TypeScript
Other key deps: @react-spring/web (CountUp animations), @tabler/icons-react, react-markdown, moment-timezone, clsx + tailwind-merge (cn() utility), class-variance-authority + @radix-ui/react-slot (component variants), opentype.js (signature SVG rendering), shadcn (UI component scaffolding)
bun install # Install dependencies
bun run dev # Dev server (Vite)
bun run build # Production build
bun run check # Lint + format (Biome)
bun run start # Serve production build (serve -s dist)
bun run knip # Dead code detectionsrc/
├── main.tsx # Entry point
├── App.tsx # Root layout
├── styles.css # Global CSS (@theme, @font-face, CSS variables, animations)
├── vite-env.d.ts # Asset module declarations (.webp, .png, .svg, .mp4)
├── components/ # Page sections
│ ├── NavBar/ # Floating nav with IntersectionObserver + signature
│ ├── About/ # Hero + profile card + skills/stacks marquees
│ ├── Experience/ # Work timeline with markdown descriptions
│ ├── Achievements/ # Hackathon wins with image galleries
│ ├── Projects/ # Project cards with badge filtering
│ ├── Footer/ # Copyright + Jakarta clock + shimmer pill
│ ├── badge.tsx # Badge component (CVA variants)
│ ├── marquee.tsx # Custom marquee with fade edges
│ ├── pop-button.tsx # Animated pop button
│ ├── rich-button.tsx # Rich button with shadow/color variants
│ ├── shimmer-text.tsx # Animated shimmer text effect
│ ├── signature.tsx # SVG signature via opentype.js
│ └── slide-up-text.tsx # Word/char slide-up animation
├── blocks/ # Reusable animation components
│ └── Animations/ # BlurFade, FlickeringGrid, HoverBorderGradient, IosSpinner
│ └── TextAnimations/ # CountUp (from reactbits.dev)
├── data/ # Static data (experiences, projects, achievements, stacks)
├── lib/ # Hooks (DesktopView, TabletView), ThemeContext, utils (cn)
└── assets/ # Images, fonts, org logos, project previews
- Single-page app with anchor-based scroll navigation (no React Router)
- NavBar uses IntersectionObserver to highlight active section
- Sections: About → Experience → Achievements → Projects
- Each section has paired IDs:
id="aboutsec"(section) +id="about"(scroll anchor) - ThemeContext provides light/dark mode toggle (localStorage + system preference fallback)
- Light/dark theme support via CSS variables and
.darkclass on<html> - Dark: bg
#03020F, cards#0d0d0d–#131313; Light: bg#f5f5f5, cards#e8e8e8–#f0f0f0 - Accent: blurple
#3643FC(active states, hovers, glows, gradients) - Fonts (self-hosted variable): Inter (body), Instrument Serif (headings), JetBrains Mono (monospace), Maple Mono (footer/pills)
- Glow effects via custom
--shadow-*tokens in@theme(shadow-glowblurple*) - Profile card: metallic 3D tilt (perspective + rotateX/Y), holographic shimmer, touch support, idle wobble hint animation
- TypeScript strict mode — tsconfig.json with
@/*path alias - Tailwind v4 CSS-based config (
@themein styles.css, no tailwind.config.js) @tailwindcss/viteplugin (no PostCSS config needed)- Biome is the sole linter + formatter (with Tailwind class sorting via
useSortedClasses+tailwindDirectivesCSS parsing) - Husky pre-commit hook runs
bun run check - Single quotes, 2-space indent
- Tailwind utility classes + custom responsive hooks (
DesktopView.ts>= 1024px,TabletView.ts>= 768px)
- After every turn where files are edited, always run
bun run checkandbun run knipto ensure lint/format compliance and no dead code is introduced.
redirects.json at the repo root maps slugs to external URLs (e.g. tracklist-for-gina → Spotify playlist). At build time, scripts/generate-redirects.js reads it and writes dist/serve.json with a redirects array. serve (via serve-handler) natively reads serve.json from the served directory and emits real HTTP 302 responses — no SPA load, no meta-refresh.
- Default status: 302 (so destinations can be changed without browser cache lock-in).
- Both
/slugand/slug/are emitted per entry to handle stray trailing slashes. - Add a new redirect: edit
redirects.json, then redeploy. No code changes. ⚠️ Slugs must not collide with real paths underdist/(e.g.assets, anything that exists as a static file/dir) — redirects run before static file serving and would shadow real assets.⚠️ Vite dev server does NOT honorserve.json, so redirects only work afterbun run build && bun run start(or in production).
Heroku via Procfile (web: bun run start → bun run serve -s dist). Site live at abhipraya.dev. OG images hosted on Cloudinary.
- App name:
absolutepraya-portfolio - Stack: heroku-24 (Cedar generation)
- Buildpacks: chrome-for-testing, heroku-buildpack-bun, heroku/nodejs
- Git remotes:
origin(GitHub),heroku(Heroku git) - Deploy:
git push heroku core:main(pushes core branch to Heroku main)
# Deployment & releases
heroku releases -a absolutepraya-portfolio # List recent releases
heroku releases:info -a absolutepraya-portfolio # Latest release details
heroku releases:rollback -a absolutepraya-portfolio # Rollback to previous release
heroku releases:rollback v201 -a absolutepraya-portfolio # Rollback to specific version
# Logs & monitoring
heroku logs -a absolutepraya-portfolio -n 100 # Last 100 log lines
heroku logs -a absolutepraya-portfolio --tail # Stream logs in real-time
heroku logs -a absolutepraya-portfolio -s app # App logs only (not Heroku router)
# Dyno management
heroku ps -a absolutepraya-portfolio # Check dyno status
heroku ps:restart -a absolutepraya-portfolio # Restart all dynos
# Config & info
heroku config -a absolutepraya-portfolio # View config/env vars
heroku buildpacks -a absolutepraya-portfolio # List buildpacks