Compare commits

...
50 Commits
Author SHA1 Message Date
Amruth Pillai b89a31d4ae update translation sources 2026-01-28 11:42:38 +01:00
Fynn FischbachandAmruth Pillai 07e06a6ed4 feat(builder): arrange sidebar builder depending on section type in template #2564 (#2603)
* feat: introduce template sidebar type

* feat: align sidebar builder depending on template alignment

* revert: keep full width toggle for now until we merge SortableLayoutItem on template change.

* fix: typo

* use string literals instead of enum, modify logic for layout display slightly

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-01-28 11:37:44 +01:00
Amruth Pillai 89102d6612 Bring back Undo/Redo functionality in the resume builder (#2629) 2026-01-28 11:20:11 +01:00
Amruth PillaiandCopilot feda173b3b Sync Translations from Crowdin (#2628)
* New translations en-us.po (Swedish)
[ci skip]

* Update locales/sv-SE.po

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update locales/sv-SE.po

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update locales/sv-SE.po

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-28 10:44:44 +01:00
Amruth PillaiandCopilot 02f812ce96 Sync Translations from Crowdin (#2625)
* New translations en-us.po (French)
[ci skip]

* Update locales/fr-FR.po

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-27 20:59:56 +01:00
Fynn Fischbach 7250bed701 Fix/#2607 remove empty proficiency spacing (#2626)
* fix: remove empty proficiency and keywords spacing in skills items

Only render proficiency and keywords fields when they contain values,
preventing empty inline-block elements from reserving unwanted space
in templates like Pikachu.

Fixes #2607

* style: biome check --write
2026-01-27 20:59:40 +01:00
Amruth PillaiandCopilot 2b11b1a234 Sync Translations from Crowdin (#2623)
* New translations en-us.po (French)
[ci skip]

* Update locales/fr-FR.po

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-27 17:10:57 +01:00
Amruth PillaiandCursor Agent 18e8aadf18 Fix GitHub OAuth login for migrated users (#2620)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-01-27 13:03:09 +01:00
G 4840a292df Fixed trailing Git Merge text (#2619)
updated French locale to remove failed gitmerge chars
2026-01-27 12:39:53 +01:00
Amruth Pillai 76ca1995ca New translations en-us.po (Portuguese, Brazilian) (#2616)
[ci skip]
2026-01-27 01:45:52 +01:00
Amruth Pillai 0f333b1f24 another attempt to improve printer service communication 2026-01-26 20:37:27 +01:00
Amruth Pillai 1f8eaa423f Merge branch 'main' of github.com:amruthpillai/reactive-resume 2026-01-26 19:36:52 +01:00
Amruth Pillai 4e4d3670a2 desperate attempts to reduce chrome usage 2026-01-26 19:36:50 +01:00
Amruth Pillai cd9a6a80e3 New translations en-us.po (Persian) (#2602)
[ci skip]
2026-01-26 14:57:51 +01:00
Amruth Pillai 429618d5f8 fixes #2542, fixes #2573 2026-01-26 12:38:37 +01:00
Amruth Pillai 1e814a77cc Refactor Pikachu template layout to conditionally render header and page picture based on isFirstPage property 2026-01-26 11:19:08 +01:00
Amruth Pillai 41b0c57725 fix pikachu template to respect the fullWidth property 2026-01-26 11:16:05 +01:00
Amruth Pillai 470822f4a9 Update package dependencies and fix README link for self-hosting guide 2026-01-26 11:11:45 +01:00
Amruth Pillai 946bf9ec38 - fixes #2598 2026-01-26 06:38:59 +01:00
Amruth Pillai 49585940f7 Sync Translations from Crowdin (#2597)
* New translations en-us.po (Romanian)
[ci skip]

* New translations en-us.po (French)
[ci skip]

* New translations en-us.po (Spanish)
[ci skip]

* New translations en-us.po (Afrikaans)
[ci skip]

* New translations en-us.po (Arabic)
[ci skip]

* New translations en-us.po (Bulgarian)
[ci skip]

* New translations en-us.po (Catalan)
[ci skip]

* New translations en-us.po (Czech)
[ci skip]

* New translations en-us.po (Danish)
[ci skip]

* New translations en-us.po (German)
[ci skip]

* New translations en-us.po (Greek)
[ci skip]

* New translations en-us.po (Finnish)
[ci skip]

* New translations en-us.po (Hebrew)
[ci skip]

* New translations en-us.po (Hungarian)
[ci skip]

* New translations en-us.po (Italian)
[ci skip]

* New translations en-us.po (Japanese)
[ci skip]

* New translations en-us.po (Korean)
[ci skip]

* New translations en-us.po (Lithuanian)
[ci skip]

* New translations en-us.po (Dutch)
[ci skip]

* New translations en-us.po (Norwegian)
[ci skip]

* New translations en-us.po (Polish)
[ci skip]

* New translations en-us.po (Portuguese)
[ci skip]

* New translations en-us.po (Russian)
[ci skip]

* New translations en-us.po (Slovak)
[ci skip]

* New translations en-us.po (Albanian)
[ci skip]

* New translations en-us.po (Serbian (Cyrillic))
[ci skip]

* New translations en-us.po (Swedish)
[ci skip]

* New translations en-us.po (Turkish)
[ci skip]

* New translations en-us.po (Ukrainian)
[ci skip]

* New translations en-us.po (Chinese Simplified)
[ci skip]

* New translations en-us.po (Chinese Traditional)
[ci skip]

* New translations en-us.po (Vietnamese)
[ci skip]

* New translations en-us.po (Portuguese, Brazilian)
[ci skip]

* New translations en-us.po (Indonesian)
[ci skip]

* New translations en-us.po (Persian)
[ci skip]

* New translations en-us.po (Khmer)
[ci skip]

* New translations en-us.po (Tamil)
[ci skip]

* New translations en-us.po (Bengali)
[ci skip]

* New translations en-us.po (Marathi)
[ci skip]

* New translations en-us.po (Thai)
[ci skip]

* New translations en-us.po (Latvian)
[ci skip]

* New translations en-us.po (Azerbaijani)
[ci skip]

* New translations en-us.po (Hindi)
[ci skip]

* New translations en-us.po (Malay)
[ci skip]

* New translations en-us.po (Telugu)
[ci skip]

* New translations en-us.po (Malayalam)
[ci skip]

* New translations en-us.po (Uzbek)
[ci skip]

* New translations en-us.po (Kannada)
[ci skip]

* New translations en-us.po (Odia)
[ci skip]

* New translations en-us.po (Amharic)
[ci skip]

* New translations en-us.po (Nepali)
[ci skip]
2026-01-26 00:11:24 +01:00
Amruth Pillai 9257d62216 update links to pdf examples 2026-01-25 23:44:45 +01:00
Amruth Pillai 3a9d2e7652 New Feature: Free-Form Page Formats for PDFs (#2595) 2026-01-25 23:39:16 +01:00
Amruth Pillai 4b2e3c0ded Sync Translations from Crowdin (#2594)
* New translations en-us.po (Afrikaans)
[ci skip]

* New translations en-us.po (Tamil)
[ci skip]
2026-01-25 22:36:03 +01:00
Amruth Pillai 6978df4c3c fix a lot of the templates 2026-01-25 22:28:06 +01:00
Amruth Pillai 1f13189ec3 Merge branch 'main' of github.com:amruthpillai/reactive-resume 2026-01-25 22:06:15 +01:00
Amruth Pillai c08e8081fe remove dead code, update screenshots, add PWA support 2026-01-25 22:06:12 +01:00
Amruth Pillai d8c9b5a936 New translations en-us.po (Afrikaans) (#2592)
[ci skip]
2026-01-25 20:37:15 +01:00
Amruth Pillai fa823e1296 Merge branch 'main' of github.com:amruthpillai/reactive-resume 2026-01-25 19:41:43 +01:00
Amruth Pillai de89ab957b 📦 v5.0.3 - Changelog: https://docs.rxresu.me/changelog 2026-01-25 19:41:32 +01:00
Amruth Pillai 96920495d0 Sync Translations from Crowdin (#2590)
* New translations en-us.po (Romanian)
[ci skip]

* New translations en-us.po (French)
[ci skip]

* New translations en-us.po (Spanish)
[ci skip]

* New translations en-us.po (Arabic)
[ci skip]

* New translations en-us.po (Bulgarian)
[ci skip]

* New translations en-us.po (Catalan)
[ci skip]

* New translations en-us.po (Czech)
[ci skip]

* New translations en-us.po (Danish)
[ci skip]

* New translations en-us.po (German)
[ci skip]

* New translations en-us.po (Greek)
[ci skip]

* New translations en-us.po (Finnish)
[ci skip]

* New translations en-us.po (Hebrew)
[ci skip]

* New translations en-us.po (Hungarian)
[ci skip]

* New translations en-us.po (Italian)
[ci skip]

* New translations en-us.po (Japanese)
[ci skip]

* New translations en-us.po (Korean)
[ci skip]

* New translations en-us.po (Lithuanian)
[ci skip]

* New translations en-us.po (Dutch)
[ci skip]

* New translations en-us.po (Norwegian)
[ci skip]

* New translations en-us.po (Polish)
[ci skip]

* New translations en-us.po (Portuguese)
[ci skip]

* New translations en-us.po (Russian)
[ci skip]

* New translations en-us.po (Slovak)
[ci skip]

* New translations en-us.po (Albanian)
[ci skip]

* New translations en-us.po (Serbian (Cyrillic))
[ci skip]

* New translations en-us.po (Swedish)
[ci skip]

* New translations en-us.po (Turkish)
[ci skip]

* New translations en-us.po (Ukrainian)
[ci skip]

* New translations en-us.po (Chinese Simplified)
[ci skip]

* New translations en-us.po (Chinese Traditional)
[ci skip]

* New translations en-us.po (Vietnamese)
[ci skip]

* New translations en-us.po (Portuguese, Brazilian)
[ci skip]

* New translations en-us.po (Indonesian)
[ci skip]

* New translations en-us.po (Persian)
[ci skip]

* New translations en-us.po (Khmer)
[ci skip]

* New translations en-us.po (Tamil)
[ci skip]

* New translations en-us.po (Bengali)
[ci skip]

* New translations en-us.po (Marathi)
[ci skip]

* New translations en-us.po (Thai)
[ci skip]

* New translations en-us.po (Latvian)
[ci skip]

* New translations en-us.po (Azerbaijani)
[ci skip]

* New translations en-us.po (Hindi)
[ci skip]

* New translations en-us.po (Malay)
[ci skip]

* New translations en-us.po (Telugu)
[ci skip]

* New translations en-us.po (Malayalam)
[ci skip]

* New translations en-us.po (Uzbek)
[ci skip]

* New translations en-us.po (Kannada)
[ci skip]

* New translations en-us.po (Odia)
[ci skip]

* New translations en-us.po (Amharic)
[ci skip]

* New translations en-us.po (Nepali)
[ci skip]
2026-01-25 19:35:38 +01:00
Amruth Pillai 92044de6b8 more chip input fixes 2026-01-25 19:35:20 +01:00
Amruth Pillai 8e8ef300ec fix #2585 - allow for re-ordering keywords, editing keywords in-place and deleting keywords (#2591) 2026-01-25 19:23:15 +01:00
Amruth Pillai 982220430e fix #2587 - allow for customizable endpoints for all AI providers (#2589) 2026-01-25 18:29:36 +01:00
IbrahimandAmruth Pillai 3f55c24e36 Improved the Right to Left Direction implementation for relevant languages. (#2583)
* Added support for right to left direction of bullet points in the page render and text editor

* Added right to left direction support by adding a toggle button in the layout and enhancing the css element of the page render to support it without affecting the default left to right direction

* Moved the Right to Life Direction Toggle feature to be integrated with the Languages section using locale

* change all occurrances of pl, pr, ml, mr, left, right to start/end equivalents

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-01-25 18:18:20 +01:00
Amruth Pillai 0c65612368 - an assortment of bugfixes and improvements
- remove line numbers from generated locale files
- add .gitattributes to not display diffs of .po files
2026-01-25 17:49:23 +01:00
Amruth Pillai 3071b7dd37 Sync Translations from Crowdin (#2582)
* New translations en-us.po (Romanian)
[ci skip]

* New translations en-us.po (French)
[ci skip]

* New translations en-us.po (Spanish)
[ci skip]

* New translations en-us.po (Afrikaans)
[ci skip]

* New translations en-us.po (Arabic)
[ci skip]

* New translations en-us.po (Bulgarian)
[ci skip]

* New translations en-us.po (Catalan)
[ci skip]

* New translations en-us.po (Czech)
[ci skip]

* New translations en-us.po (Danish)
[ci skip]

* New translations en-us.po (German)
[ci skip]

* New translations en-us.po (Greek)
[ci skip]

* New translations en-us.po (Finnish)
[ci skip]

* New translations en-us.po (Hebrew)
[ci skip]

* New translations en-us.po (Hungarian)
[ci skip]

* New translations en-us.po (Italian)
[ci skip]

* New translations en-us.po (Japanese)
[ci skip]

* New translations en-us.po (Korean)
[ci skip]

* New translations en-us.po (Lithuanian)
[ci skip]

* New translations en-us.po (Dutch)
[ci skip]

* New translations en-us.po (Norwegian)
[ci skip]

* New translations en-us.po (Polish)
[ci skip]

* New translations en-us.po (Portuguese)
[ci skip]

* New translations en-us.po (Russian)
[ci skip]

* New translations en-us.po (Slovak)
[ci skip]

* New translations en-us.po (Albanian)
[ci skip]

* New translations en-us.po (Serbian (Cyrillic))
[ci skip]

* New translations en-us.po (Swedish)
[ci skip]

* New translations en-us.po (Turkish)
[ci skip]

* New translations en-us.po (Ukrainian)
[ci skip]

* New translations en-us.po (Chinese Simplified)
[ci skip]

* New translations en-us.po (Chinese Traditional)
[ci skip]

* New translations en-us.po (Vietnamese)
[ci skip]

* New translations en-us.po (Portuguese, Brazilian)
[ci skip]

* New translations en-us.po (Indonesian)
[ci skip]

* New translations en-us.po (Persian)
[ci skip]

* New translations en-us.po (Khmer)
[ci skip]

* New translations en-us.po (Tamil)
[ci skip]

* New translations en-us.po (Bengali)
[ci skip]

* New translations en-us.po (Marathi)
[ci skip]

* New translations en-us.po (Thai)
[ci skip]

* New translations en-us.po (Latvian)
[ci skip]

* New translations en-us.po (Azerbaijani)
[ci skip]

* New translations en-us.po (Hindi)
[ci skip]

* New translations en-us.po (Malay)
[ci skip]

* New translations en-us.po (Telugu)
[ci skip]

* New translations en-us.po (Malayalam)
[ci skip]

* New translations en-us.po (Uzbek)
[ci skip]

* New translations en-us.po (Kannada)
[ci skip]

* New translations en-us.po (Odia)
[ci skip]

* New translations en-us.po (Amharic)
[ci skip]

* New translations en-us.po (Nepali)
[ci skip]
2026-01-25 08:43:44 +01:00
Amruth Pillai 5daac24e73 Update package dependencies and synchronize translations
- Updated @tanstack/react-router and related packages to version 1.157.5 in package.json.
- Adjusted pnpm-lock.yaml to reflect the updated package versions.
- Modified localization files to update the "Last Updated" message reference and the resume deletion success message across multiple languages.
- Removed obsolete components related to resume cards and creation from the dashboard.
2026-01-25 03:15:19 +01:00
Amruth Pillai bcd2828305 update intro timelapse video landing page 2026-01-25 01:05:05 +01:00
Amruth Pillai b5f4e9af46 increase screenshot ttl to outdated resumes 2026-01-25 00:27:58 +01:00
Amruth Pillai 92f6a6a16b Sync Translations from Crowdin (#2576)
* New translations en-us.po (Romanian)
[ci skip]

* New translations en-us.po (French)
[ci skip]

* New translations en-us.po (Spanish)
[ci skip]

* New translations en-us.po (Afrikaans)
[ci skip]

* New translations en-us.po (Arabic)
[ci skip]

* New translations en-us.po (Bulgarian)
[ci skip]

* New translations en-us.po (Catalan)
[ci skip]

* New translations en-us.po (Czech)
[ci skip]

* New translations en-us.po (Danish)
[ci skip]

* New translations en-us.po (German)
[ci skip]

* New translations en-us.po (Greek)
[ci skip]

* New translations en-us.po (Finnish)
[ci skip]

* New translations en-us.po (Hebrew)
[ci skip]

* New translations en-us.po (Hungarian)
[ci skip]

* New translations en-us.po (Italian)
[ci skip]

* New translations en-us.po (Japanese)
[ci skip]

* New translations en-us.po (Korean)
[ci skip]

* New translations en-us.po (Lithuanian)
[ci skip]

* New translations en-us.po (Dutch)
[ci skip]

* New translations en-us.po (Norwegian)
[ci skip]

* New translations en-us.po (Polish)
[ci skip]

* New translations en-us.po (Portuguese)
[ci skip]

* New translations en-us.po (Russian)
[ci skip]

* New translations en-us.po (Slovak)
[ci skip]

* New translations en-us.po (Albanian)
[ci skip]

* New translations en-us.po (Serbian (Cyrillic))
[ci skip]

* New translations en-us.po (Swedish)
[ci skip]

* New translations en-us.po (Turkish)
[ci skip]

* New translations en-us.po (Ukrainian)
[ci skip]

* New translations en-us.po (Chinese Simplified)
[ci skip]

* New translations en-us.po (Chinese Traditional)
[ci skip]

* New translations en-us.po (Vietnamese)
[ci skip]

* New translations en-us.po (Portuguese, Brazilian)
[ci skip]

* New translations en-us.po (Indonesian)
[ci skip]

* New translations en-us.po (Persian)
[ci skip]

* New translations en-us.po (Khmer)
[ci skip]

* New translations en-us.po (Tamil)
[ci skip]

* New translations en-us.po (Bengali)
[ci skip]

* New translations en-us.po (Marathi)
[ci skip]

* New translations en-us.po (Thai)
[ci skip]

* New translations en-us.po (Latvian)
[ci skip]

* New translations en-us.po (Azerbaijani)
[ci skip]

* New translations en-us.po (Hindi)
[ci skip]

* New translations en-us.po (Malay)
[ci skip]

* New translations en-us.po (Telugu)
[ci skip]

* New translations en-us.po (Malayalam)
[ci skip]

* New translations en-us.po (Uzbek)
[ci skip]

* New translations en-us.po (Kannada)
[ci skip]

* New translations en-us.po (Odia)
[ci skip]

* New translations en-us.po (Amharic)
[ci skip]

* New translations en-us.po (Nepali)
[ci skip]

* New translations en-us.po (Afrikaans)
[ci skip]

* New translations en-us.po (Arabic)
[ci skip]

* New translations en-us.po (German)
[ci skip]

* New translations en-us.po (Albanian)
[ci skip]

* New translations en-us.po (Amharic)
[ci skip]

* update translations

* New translations en-us.po (Romanian)
[ci skip]

* New translations en-us.po (French)
[ci skip]

* New translations en-us.po (Spanish)
[ci skip]

* New translations en-us.po (Afrikaans)
[ci skip]

* New translations en-us.po (Arabic)
[ci skip]

* New translations en-us.po (Bulgarian)
[ci skip]

* New translations en-us.po (Catalan)
[ci skip]

* New translations en-us.po (Czech)
[ci skip]

* New translations en-us.po (Danish)
[ci skip]

* New translations en-us.po (German)
[ci skip]

* New translations en-us.po (Greek)
[ci skip]

* New translations en-us.po (Finnish)
[ci skip]

* New translations en-us.po (Hebrew)
[ci skip]

* New translations en-us.po (Hungarian)
[ci skip]

* New translations en-us.po (Italian)
[ci skip]

* New translations en-us.po (Japanese)
[ci skip]

* New translations en-us.po (Korean)
[ci skip]

* New translations en-us.po (Lithuanian)
[ci skip]

* New translations en-us.po (Dutch)
[ci skip]

* New translations en-us.po (Norwegian)
[ci skip]

* New translations en-us.po (Polish)
[ci skip]

* New translations en-us.po (Portuguese)
[ci skip]

* New translations en-us.po (Russian)
[ci skip]

* New translations en-us.po (Slovak)
[ci skip]

* New translations en-us.po (Albanian)
[ci skip]

* New translations en-us.po (Serbian (Cyrillic))
[ci skip]

* New translations en-us.po (Swedish)
[ci skip]

* New translations en-us.po (Turkish)
[ci skip]

* New translations en-us.po (Ukrainian)
[ci skip]

* New translations en-us.po (Chinese Simplified)
[ci skip]

* New translations en-us.po (Chinese Traditional)
[ci skip]

* New translations en-us.po (Vietnamese)
[ci skip]

* New translations en-us.po (Portuguese, Brazilian)
[ci skip]

* New translations en-us.po (Indonesian)
[ci skip]

* New translations en-us.po (Persian)
[ci skip]

* New translations en-us.po (Khmer)
[ci skip]

* New translations en-us.po (Tamil)
[ci skip]

* New translations en-us.po (Bengali)
[ci skip]

* New translations en-us.po (Marathi)
[ci skip]

* New translations en-us.po (Thai)
[ci skip]

* New translations en-us.po (Latvian)
[ci skip]

* New translations en-us.po (Azerbaijani)
[ci skip]

* New translations en-us.po (Hindi)
[ci skip]

* New translations en-us.po (Malay)
[ci skip]

* New translations en-us.po (Telugu)
[ci skip]

* New translations en-us.po (Malayalam)
[ci skip]

* New translations en-us.po (Uzbek)
[ci skip]

* New translations en-us.po (Kannada)
[ci skip]

* New translations en-us.po (Odia)
[ci skip]

* New translations en-us.po (Amharic)
[ci skip]

* New translations en-us.po (Nepali)
[ci skip]
2026-01-24 23:43:08 +01:00
Amruth Pillai 019a20d027 rename agent skill name 2026-01-24 23:21:55 +01:00
Amruth Pillai 1378feaf6a add additional launch args to browser 2026-01-24 23:19:58 +01:00
Amruth Pillai 22221e6fae add additional launch args to browser 2026-01-24 23:17:46 +01:00
Amruth Pillai d773ff5391 fix: add print:hidden to page decorators 2026-01-24 23:05:12 +01:00
Amruth Pillai be84f0cca6 📦 v5.0.2 · Changelog: https://docs.rxresu.me/changelog 2026-01-24 23:00:54 +01:00
Amruth Pillai 1419c16d04 add CLAUDE.md 2026-01-24 20:05:33 +01:00
Amruth Pillai 89f209014e fix issue with irregular heights of PDFs on resumes with sidebar backgrounds 2026-01-24 17:30:38 +01:00
Amruth Pillai 21aec46763 updates to printer, added changelog entry, restored deploy script in CI 2026-01-24 16:58:14 +01:00
Amruth Pillai 333b5e50c4 remove "-r require-metadata" from dockerfile 2026-01-24 14:18:37 +01:00
Amruth Pillai e94550b7b2 update product hunt launch date 2026-01-24 13:29:06 +01:00
212 changed files with 78507 additions and 72479 deletions
+1
View File
@@ -0,0 +1 @@
locales/*.po linguist-generated=true
+11 -3
View File
@@ -2,8 +2,6 @@ name: Build Docker Image
on:
workflow_dispatch:
push:
tags: [ "v*.*.*" ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -204,4 +202,14 @@ jobs:
- name: Inspect image
run: |
docker buildx imagetools inspect ghcr.io/${{ env.IMAGE }}:v${{ steps.version.outputs.version }}
docker buildx imagetools inspect docker.io/${{ env.IMAGE }}:v${{ steps.version.outputs.version }}
docker buildx imagetools inspect docker.io/${{ env.IMAGE }}:v${{ steps.version.outputs.version }}
- name: Redeploy Stack
uses: appleboy/ssh-action@v1
with:
key: ${{ secrets.SSH_KEY }}
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
script: |
cd docker
./manage_stack.sh up reactive_resume
+123
View File
@@ -0,0 +1,123 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Reactive Resume is a free, open-source resume builder built with TanStack Start (React 19 + Vite), using ORPC for type-safe RPC APIs, Drizzle ORM with PostgreSQL, and Better Auth for authentication.
## Development Commands
```bash
# Start development server (runs on port 3000)
pnpm dev
# Build for production
pnpm build
# Start production server
pnpm start
# Linting (uses Biome)
pnpm lint
# Type checking
pnpm typecheck
# Database operations
pnpm db:generate # Generate migration files
pnpm db:migrate # Run migrations
pnpm db:push # Push schema changes directly
pnpm db:studio # Open Drizzle Studio
# Extract i18n strings for translation
pnpm lingui:extract
# Find unused exports
pnpm knip
```
## Local Development Setup
1. Copy `.env.example` to `.env` and configure environment variables
2. Start required services: `docker compose -f compose.dev.yml up -d`
- PostgreSQL (port 5432)
- Browserless/Chromium for PDF generation (port 4000)
- SeaweedFS for S3-compatible storage (port 8333)
- Mailpit for email testing (ports 1025, 8025)
- Adminer for DB management (port 8080)
3. Run `pnpm dev`
## Architecture
### Directory Structure
- `src/routes/` - TanStack Router file-based routing
- `src/integrations/` - External service integrations (auth, database, ORPC, AI, email)
- `src/integrations/orpc/router` - oRPC server routers
- `src/integrations/orpc/services` - oRPC server services
- `src/components/` - React components organized by feature
- `src/schema/` - Zod schemas for validation
- `plugins/` - Nitro server plugins (eg. auto-migration on startup)
- `migrations/` - Drizzle database migrations
- `locales/` - i18n translation files (managed by Lingui)
### Key Integrations (`src/integrations/`)
- **auth/** - Better Auth configuration and client
- **drizzle/** - Database schema and client (PostgreSQL)
- **orpc/** - Type-safe RPC router with procedures for ai, auth, flags, printer, resume, statistics, storage
- **query/** - TanStack Query client configuration
- **ai/** - AI provider integrations (OpenAI, Anthropic, Google Gemini, Ollama)
### Resume Data Model
The resume schema is defined in `src/schema/resume/data.ts`. Key concepts:
- **ResumeData** - Complete resume data including basics, sections, customSections, metadata
- **Sections** - Built-in sections (profiles, experience, education, skills, etc.)
- **CustomSections** - User-created sections that follow one of the built-in section types
- **Metadata** - Template, layout, typography, design settings, custom CSS
### Resume Templates
Templates are React components in `src/components/resume/templates/`. Each template (azurill, bronzor, chikorita, etc.) renders the resume data with different visual styles. Templates use shared components from `src/components/resume/shared/`.
### Database Schema
Defined in `src/integrations/drizzle/schema.ts`:
- `user`, `session`, `account`, `verification`, `twoFactor`, `passkey`, `apikey` - Better Auth tables
- `resume` - Stores Resume Data as JSONB (defined in `src/schema/resume/data.ts`)
- `resumeStatistics` - Views/Download for Resume Tracking
### Routing
Uses TanStack Router with file-based routing. Key routes:
- `/_home/` - Public landing pages
- `/auth/` - Authentication flows
- `/dashboard/` - User dashboard and resume management
- `/builder/$resumeId/` - Resume editor
- `/printer/$resumeId/` - PDF rendering endpoint
- `/api/` - Public API endpoints
### State Management
- **Zustand** - Client-side state (resume editor state in `src/components/resume/store/`)
- **TanStack Query** - Server state and caching (configured via ORPC integration)
## Code Style
- Uses Biome for linting and formatting
- Tab indentation, double quotes, 120 character line width
- Path alias: `@/` maps to `src/`
- Tailwind CSS v4 with sorted class names (enforced by Biome)
- Uses `cn()` utility for conditional class names
## Environment Variables
Key variables (see `.env.example` for full list):
- `APP_URL` - Application URL
- `DATABASE_URL` - PostgreSQL connection string
- `AUTH_SECRET` - Secret for authentication
- `PRINTER_ENDPOINT` - WebSocket endpoint for PDF printer service
- `S3_*` - S3-compatible storage configuration
- `FLAG_*` - Feature flags
+1 -1
View File
@@ -60,4 +60,4 @@ EXPOSE 3000/tcp
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:3000/api/health || exit 1
ENTRYPOINT ["node", "-r", "reflect-metadata", ".output/server/index.mjs"]
ENTRYPOINT ["node", ".output/server/index.mjs"]
+12 -2
View File
@@ -29,7 +29,7 @@
<img src="https://img.shields.io/github/stars/amruthpillai/Reactive-Resume?style=flat-square" alt="Stars" />
<img src="https://img.shields.io/github/license/amruthpillai/Reactive-Resume?style=flat-square" alt="License" />
<img src="https://img.shields.io/docker/pulls/amruthpillai/reactive-resume?style=flat-square" alt="Docker Pulls" />
<a href="https://discord.gg/hzwkZbyvUW"><img src="https://img.shields.io/discord/1173518977851473940?label=Discord&style=flat-square&logo=discord" alt="Discord" /></a>
<a href="https://discord.gg/EE8yFqW4"><img src="https://img.shields.io/discord/1173518977851473940?label=Discord&style=flat-square&logo=discord" alt="Discord" /></a>
<a href="https://crowdin.com/project/reactive-resume"><img src="https://badges.crowdin.net/reactive-resume/localized.svg?style=flat-square" alt="Crowdin" /></a>
<a href="https://opencollective.com/reactive-resume"><img src="https://img.shields.io/opencollective/all/reactive-resume?style=flat-square" alt="Open Collective" /></a>
</p>
@@ -180,7 +180,7 @@ Comprehensive guides are available at [docs.rxresu.me](https://docs.rxresu.me):
| Guide | Description |
| --------------------------------------------------------------------------- | --------------------------------- |
| [Getting Started](https://docs.rxresu.me/getting-started) | First-time setup and basic usage |
| [Self-Hosting](https://docs.rxresu.me/guides/self-hosting-with-docker) | Deploy on your own server |
| [Self-Hosting](https://docs.rxresu.me/self-hosting/docker) | Deploy on your own server |
| [Development Setup](https://docs.rxresu.me/contributing/development) | Local development environment |
| [Project Architecture](https://docs.rxresu.me/contributing/architecture) | Codebase structure and patterns |
| [Exporting Your Resume](https://docs.rxresu.me/guides/exporting-your-resume)| PDF and JSON export options |
@@ -225,6 +225,16 @@ Other ways to support:
- Improve documentation
- Help with translations
## Star History
<a href="https://www.star-history.com/#amruthpillai/reactive-resume&type=date&legend=top-left">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=amruthpillai/reactive-resume&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=amruthpillai/reactive-resume&type=date&legend=top-left" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=amruthpillai/reactive-resume&type=date&legend=top-left" />
</picture>
</a>
## Contributing
Contributions make open-source thrive. Whether fixing a typo or adding a feature, all contributions are welcome.
+7 -1
View File
@@ -8,7 +8,13 @@
},
"files": {
"ignoreUnknown": true,
"includes": ["**", "!**/webfontlist.json", "!**/docs/spec.json", "!**/src/routeTree.gen.ts"]
"includes": [
"**",
"!**/webfontlist.json",
"!**/schema/schema.json",
"!**/docs/spec.json",
"!**/src/routeTree.gen.ts"
]
},
"formatter": {
"lineWidth": 120,
+1 -1
View File
@@ -22,10 +22,10 @@ services:
restart: unless-stopped
environment:
- QUEUED=30
- TIMEOUT=-1
- HEALTH=true
- CONCURRENT=20
- TOKEN=1234567890
- EXTERNAL=http://localhost:4000
healthcheck:
test:
["CMD", "curl", "-f", "http://localhost:3000/pressure?token=1234567890"]
+58
View File
@@ -0,0 +1,58 @@
---
title: "Changelog"
description: "List of all notable changes and updates to Reactive Resume"
rss: true
---
<Update label="v5.0.3" description="25th January 2026">
- Implement the ability to print Free-Form PDFs which do not have a fixed page height, allowing the user to fit the content on a page as they see fit. [(link)](/guides/selecting-page-format)
- Allow the user to override the default endpoint for all AI providers, not just Ollama. Also display the default endpoint for each provider in the AI settings page.
- Updated the chip input component, to allow the user to add, edit, remove or reorder keywords for skills and other sections.
- Improved RTL support across the app, thanks to @obreo for the contribution. [(link)](https://github.com/amruthpillai/reactive-resume/pull/2583)
- Updates to the translation configuration to remove line numbers from the translation files, as this was causing unnecessary diffs.
- Updated the video on the homepage to be lighter and faster to load, while still maintaining the same quality.
- Increased the screenshot TTL for resumes, to avoid regenerating screenshots unnecessarily unless the resume has been updated recently (in the last hour).
- Update dependencies and translations to the latest versions.
</Update>
<Update label="v5.0.2" description="24th January 2026">
- Added an agent skill `skills/resume-builder` for agentic AI assistants to build resumes for Reactive Resume through conversational AI.
- Added a new guide on how to fit content on a page, to avoid issues when exporting to PDF. [(link)](/guides/fitting-content-on-a-page)
- Display an alert when the content is too tall for a page, to help the user fit the content on a page.
- Fix an issue with the Ditgar template, where the page was not respecting the `fullWidth` setting.
- Updated the JSON Schema to conform to a proper format.
- Updated the Discord Server invite link to a new one.
- Updated dependencies to the latest versions.
</Update>
<Update label="v5.0.1" description="23th January 2026">
- Updated translations from Crowdin.
- Added a Community Spotlight section to the documentation.
- Remove `-r require-metadata` from the Dockerfile as it was not needed.
- Fixed inconsistencies in the docker compose examples in the documentation.
- Fixed an issue with usernames not allowing hyphens in them.
- Fixed issues with the printer service, when using the `getResumeScreenshot` or `printResumeAsPDF` endpoints.
</Update>
<Update label="v5.0.0" description="22th January 2026">
This has been a major overhaul from the previous version of Reactive Resume. The app has been completely redesigned and rebuilt from scratch, to be more intuitive and user-friendly.
**Here are some of the key changes from the previous version:**
- 2 new templates: _Ditgar_ and _Lapras_
- Authentication via Passkeys
- New user interface and refreshed design
- API Access and Reference Documentation
- AI Integration with OpenAI, Google, Anthropic, and Ollama
- A better font selector, with real-time preview of the font
- More comprehensive documentation, with guides for specific features
- An improved templates gallery, with clear overview of each template
- Ability to import resumes from PDF or DOCX (requires AI Integration)
- Reliable server infrastructure, with improved performance and scalability
- Ability to move items between sections and pages, for better organization
- A more powerful CSS Editor, with better autocomplete and syntax highlighting
- Extended Custom Sections to include a type, for better customization and organization
- Ability to choose the kind of icons to display for level indicators (stars, circles, custom icons, etc.)
- Ability to resize how wide/narrow the sidebar should be on the resume, or convert a page to be full width
There's still a lot more that I'm forgetting, but I'm sure you'll find out soon enough as you explore the new version. I hope you enjoy building your resume now, better than ever. If you have any feedback, please feel free to [contact me](https://amruthpillai.com/#contact) or [open an issue](https://github.com/amruthpillai/reactive-resume/issues) on GitHub.
</Update>
+11 -2
View File
@@ -27,6 +27,7 @@
"navigation": {
"tabs": [
{
"icon": "book",
"tab": "Documentation",
"groups": [
{
@@ -34,8 +35,8 @@
"pages": [
"getting-started/index",
"getting-started/quickstart",
"guides/accessing-the-previous-version",
"getting-started/changelog"
"guides/checking-service-status",
"guides/accessing-the-previous-version"
]
},
{
@@ -52,7 +53,9 @@
"pages": [
"guides/creating-your-first-resume",
"guides/choosing-a-template",
"guides/selecting-page-format",
"guides/moving-items-between-sections",
"guides/fitting-content-on-a-page",
"guides/exporting-your-resume",
"guides/sharing-your-resume-publicly",
"guides/using-private-notes",
@@ -86,6 +89,12 @@
]
},
{
"icon": "clock",
"tab": "Changelog",
"pages": ["changelog/index"]
},
{
"icon": "code",
"tab": "API Reference",
"openapi": "/spec.json"
}
-28
View File
@@ -1,28 +0,0 @@
---
title: "Changelog"
description: "List of all notable changes and updates to Reactive Resume"
rss: true
---
<Update label="v5.0.0" description="26th January 2026">
This has been a major overhaul from the previous version of Reactive Resume. The app has been completely redesigned and rebuilt from scratch, to be more intuitive and user-friendly.
**Here are some of the key changes from the previous version:**
- 2 new templates: _Ditgar_ and _Lapras_
- Authentication via Passkeys
- New user interface and refreshed design
- API Access and Reference Documentation
- AI Integration with OpenAI, Google, Anthropic, and Ollama
- A better font selector, with real-time preview of the font
- More comprehensive documentation, with guides for specific features
- An improved templates gallery, with clear overview of each template
- Ability to import resumes from PDF or DOCX (requires AI Integration)
- Reliable server infrastructure, with improved performance and scalability
- Ability to move items between sections and pages, for better organization
- A more powerful CSS Editor, with better autocomplete and syntax highlighting
- Extended Custom Sections to include a type, for better customization and organization
- Ability to choose the kind of icons to display for level indicators (stars, circles, custom icons, etc.)
- Ability to resize how wide/narrow the sidebar should be on the resume, or convert a page to be full width
There's still a lot more that I'm forgetting, but I'm sure you'll find out soon enough as you explore the new version. I hope you enjoy building your resume now, better than ever. If you have any feedback, please feel free to [contact me](https://amruthpillai.com/#contact) or [open an issue](https://github.com/amruthpillai/reactive-resume/issues) on GitHub.
</Update>
+1 -1
View File
@@ -104,7 +104,7 @@ Reactive Resume is built with modern web technologies:
<Card title="Reddit" icon="reddit" href="https://reddit.com/r/reactiveresume">
Join our Reddit community to get help and connect with other users.
</Card>
<Card title="Discord" icon="discord" href="https://discord.gg/hzwkZbyvUW">
<Card title="Discord" icon="discord" href="https://discord.gg/EE8yFqW4">
Join our Discord server to get help and connect with other users.
</Card>
<Card title="Sponsor" icon="heart" href="https://opencollective.com/reactive-resume">
+94
View File
@@ -0,0 +1,94 @@
---
title: "Checking Service Status"
description: "Learn how to check the status of Reactive Resume's servers and what to do if the service is experiencing issues"
---
## Status Page
You can monitor the health and availability of Reactive Resume's servers at any time by visiting our status page:
<Card title="Status Page" icon="signal" href="https://status.rxresu.me">
View real-time server metrics including uptime, CPU usage, memory usage, and more.
</Card>
The status page provides information such as:
- **Uptime**: How long the servers have been running without interruption
- **CPU Usage**: Current processor utilization
- **Memory Usage**: RAM consumption across services
- **Response Times**: How quickly the servers are responding to requests
---
## What to Do If Servers Are Down
If you notice the servers are experiencing high load or downtime, here are some recommendations:
<Steps>
<Step title="Check the status page">
Visit [status.rxresu.me](https://status.rxresu.me) to confirm if there's an ongoing issue. The page will show you the current state of all services.
</Step>
<Step title="Wait and try again later">
If the servers are under heavy load, the best course of action is to wait a bit and try again later. Peak usage times can cause temporary slowdowns.
<Info>
Reactive Resume is a **free, open-source service** used by thousands of people worldwide. During peak times, the servers may experience higher than usual load.
</Info>
</Step>
<Step title="Check for announcements">
For major outages or planned maintenance, announcements may be posted on our [GitHub repository](https://github.com/amruthpillai/reactive-resume).
</Step>
</Steps>
---
## A Note on Server Capacity
<Warning>
Reactive Resume is a **free service** that runs on limited server resources. As an open-source project maintained by a single developer, it's not feasible to invest in powerful dedicated servers without community support.
</Warning>
The reality is:
- **High demand**: Thousands of users rely on this service daily
- **Limited resources**: As a free service, server capacity is constrained
- **No corporate backing**: This isn't a venture-funded startup with unlimited cloud budgets
If you find Reactive Resume valuable and want to help keep the servers running smoothly (and maybe even help scale them up), please consider supporting the project.
---
## Support the Project
Your donations directly help cover server costs, improve infrastructure, and keep Reactive Resume free for everyone.
<Card title="Donate on Open Collective" icon="heart" href="https://opencollective.com/reactive-resume">
Support Reactive Resume's development and server costs through Open Collective. Every contribution helps keep the service running.
</Card>
<CardGroup cols={2}>
<Card title="One-time Donation" icon="gift">
Make a single contribution of any amount to help with immediate server costs.
</Card>
<Card title="Recurring Support" icon="repeat">
Become a backer with a monthly contribution to provide sustainable support.
</Card>
</CardGroup>
---
## Self-Hosting as an Alternative
If you need guaranteed uptime or want to avoid shared server limitations, you can always self-host Reactive Resume on your own infrastructure.
<Card title="Self-Hosting Guide" icon="server" href="/self-hosting/docker">
Learn how to deploy Reactive Resume on your own servers using Docker.
</Card>
Self-hosting gives you:
- **Full control** over your data and infrastructure
- **Guaranteed availability** based on your own server capacity
- **No shared resources** with other users
+155
View File
@@ -0,0 +1,155 @@
---
title: "Fitting content on a page"
description: "Learn how to reorganize and format your resume content to fit within a single page, whether you're using A4 or Letter format."
---
When your resume content overflows the page, Reactive Resume displays a warning just below the page. This guide explains how to reorganize and adjust your content so everything fits within your chosen page format.
<Frame caption="Screenshot of the overflow warning message in the resume builder">
<img src="/images/guides/fitting-content-on-a-page/screenshot-1.webp" alt="Screenshot of the overflow warning message in the resume builder" />
</Frame>
<Warning>
While Reactive Resume supports multi-page resumes, each page has a fixed height based on your chosen format (A4 or Letter). If a single page's content exceeds this height, parts of your resume may be rendered improperly when printed or exported.
</Warning>
## Quick fixes
Here are several ways to fit your content within a page, ordered from simplest to most involved.
### 1. Switch to Free-Form format
If you don't plan on printing your resume, the simplest solution is to switch to **Free-Form** format. Free-Form creates a single continuous page with no height limit, eliminating overflow concerns entirely.
With Free-Form:
- Your entire resume renders as one seamless document
- No awkward page breaks to manage
- ATS parsers and AI scanners can still read your content perfectly
- You focus on content, not arbitrary page constraints
To switch formats, go to the **Page** section in the right sidebar and change the **Format** to Free-Form. For more details, see [Selecting the right page format](/guides/selecting-page-format).
<Tip>
Since most resumes are viewed digitally today, Free-Form is often the best choice unless you specifically need to print physical copies.
</Tip>
### 2. Shorten text blocks
Long paragraphs take up space without adding proportional value. Review each section and cut ruthlessly:
- **Use bullet points** instead of paragraphs. Bullets are easier to scan and take less vertical space.
- **Remove filler words.** "Was responsible for managing" becomes "Managed."
- **Focus on impact.** Keep measurable achievements; cut generic descriptions.
- **Limit bullets per entry.** Three to five bullets per job is usually enough.
<Tip>
Read each bullet point and ask: "Does this help me get an interview?" If not, cut it.
</Tip>
### 3. Use multi-column layouts
Some sections work better in multiple columns, especially lists of short items.
In the left sidebar, find the section you want to adjust, click on the section heading (not an item), and change the **Columns** setting.
<Frame caption="Screenshot of the columns setting for a section">
<img src="/images/guides/fitting-content-on-a-page/screenshot-2.webp" alt="Screenshot of the columns setting for a section" />
</Frame>
Good candidates for multi-column layouts:
| Section | Recommended Columns |
|---------|---------------------|
| Skills | > 2 columns |
| Languages | > 3 columns |
| Interests | > 2 columns |
| Profiles | > 3 columns |
| Certifications (if brief) | > 2 columns |
<Info>
Multi-column layouts work best for sections with short, uniform items. Sections with long descriptions (like Experience or Projects) usually work better in a single column.
</Info>
### 4. Move items to another page
If you have more content than fits on one page, move less important items to page two. This keeps your first page focused on your most relevant experience.
Use the **Move to** feature to relocate items:
1. Open the item's dropdown menu (three-dot icon)
2. Hover over **Move to**
3. Select the destination page
For detailed instructions, see [Moving items between sections](/guides/moving-items-between-sections).
<Tip>
Keep your most recent and relevant experience on page one. Move older positions or less critical sections (like older projects or volunteer work) to subsequent pages.
</Tip>
### 5. Adjust layout and design settings
The right sidebar contains settings that control how much space your content uses. Small adjustments here can make a big difference.
Open the right sidebar and explore these options:
| Setting | Where to find it | Effect |
|---------|------------------|--------|
| **Font size** | Typography | Smaller fonts fit more text per line and per page |
| **Line height** | Typography | Tighter line spacing reduces vertical space |
| **Margins** | Page | Smaller margins give you more usable area |
| **Section gaps** | Page | Reducing gaps between sections saves space |
| **Sidebar width** | Layout | Adjusting the sidebar ratio can balance content better |
| **Picture size** | Picture (left sidebar) | A smaller photo leaves more room for text |
<Info>
**Reducing font size is the best option** when you need to fit more content while keeping A4 or Letter format. Reducing body font from 11pt to 10.5pt (or even 10pt) can free up significant space while remaining readable. The editor supports 0.1pt increments, so you can fine-tune precisely.
</Info>
### 6. Hide less important sections
If you're still short on space, consider hiding sections that aren't essential for your target role:
- **Interests** — Nice to have, but rarely a deciding factor
- **References** — "Available upon request" is assumed; you don't need to list them
- **Older certifications** — Keep only those relevant to the job
- **Volunteer work** — Include only if it strengthens your application
To hide a section, click on the section heading in the left sidebar and toggle the **Hidden** switch.
## Finding the right balance
If you need to stick with A4 or Letter format, start with content changes (steps 2-4) before adjusting design settings (steps 5-6). The best resumes fit their content naturally rather than forcing everything into a cramped layout.
Try this order:
1. Consider switching to Free-Form if printing isn't required
2. Cut unnecessary text first
3. Reorganize with columns where appropriate
4. Move secondary content to page two if needed
5. Fine-tune font size and spacing last
<Tip>
Use the live preview to see changes as you make them. Small adjustments add up—reducing font size by 0.5pt combined with slightly smaller margins can recover enough space for several lines of content.
</Tip>
## Troubleshooting
### Content still overflows after trying everything
If you've tried all the above and content still overflows:
- **Re-evaluate what's essential.** Every item should earn its place. Cut aggressively.
- **Try a different template.** Some templates are more space-efficient than others.
### The preview looks different from the PDF
The PDF export matches the preview exactly. If they appear different, try:
- Refreshing the page
- Checking that all fonts have loaded
- Ensuring your browser zoom is at 100%
### I made the font too small and now it's hard to read
Resume fonts should stay between 9pt and 12pt for body text. If you've gone below 9pt to fit content, you're trying to include too much. Go back to step 1 and cut more content instead.
File diff suppressed because it is too large Load Diff
+143
View File
@@ -0,0 +1,143 @@
---
title: "Selecting the right page format"
description: "Learn about the three page format options in Reactive Resume—A4, Letter, and Free-Form—and choose the best one for your needs."
---
Reactive Resume offers three page format options: **A4**, **Letter**, and **Free-Form**. Each format affects how your resume is rendered and exported as a PDF. This guide explains the differences and helps you choose the right format for your situation.
## Available formats
### A4
A4 is the international standard paper size used in most countries outside North America. When you select A4, your resume pages conform to these dimensions:
| Property | Value |
|----------|-------|
| Width | 210mm (794px) |
| Height | 297mm (1123px) |
Choose A4 if you're applying to jobs internationally or in regions that use the metric system.
### Letter
Letter is the standard paper size in the United States and Canada. When you select Letter, your resume pages conform to these dimensions:
| Property | Value |
|----------|-------|
| Width | 216mm (816px) |
| Height | 279mm (1056px) |
Choose Letter if you're applying to jobs in North America.
### Free-Form
Free-Form is a modern format designed for digital-first resumes. Instead of conforming to a physical page size, Free-Form produces a **single continuous page** with no height limit. The width matches A4 (210mm), but the height extends as needed to fit all your content.
| Property | Value |
|----------|-------|
| Width | 210mm (794px) |
| Height | Unlimited |
<Info>
With Free-Form, there are no page breaks. Your entire resume renders as one seamless document, regardless of how much content you have.
</Info>
## Why Free-Form exists
The reality of modern job applications is that **nobody prints resumes anymore**. Your resume is almost always viewed digitally—on a screen, in an applicant tracking system (ATS), or parsed by AI-powered screening tools.
When your resume is processed digitally:
- **ATS parsers** extract text content regardless of page dimensions
- **AI scanners** analyze the full document as a single unit
- **Recruiters** scroll through PDFs on their screens rather than printing them
- **PDF parsing tools** read the entire content regardless of page height
Since physical page constraints no longer matter for most use cases, Free-Form lets you focus on content rather than worrying about fitting everything into arbitrary page limits.
<Tip>
If you don't plan on printing your resume, Free-Form is often the simplest choice. You never have to worry about content overflowing or awkward page breaks.
</Tip>
## How to change your page format
<Steps>
<Step title="Open your resume in the builder">
Navigate to your Dashboard and click on the resume you want to edit.
</Step>
<Step title="Open the right sidebar">
In the resume builder, look for the right sidebar on the right side of the screen.
</Step>
<Step title="Navigate to the Page section">
In the right sidebar, find and click on the **Page** section to expand it.
</Step>
<Step title="Select your format">
Find the **Format** dropdown and select your preferred option: A4, Letter, or Free-Form.
<Frame caption="Screenshot of the Format dropdown in the Page section">
<img src="/images/guides/selecting-page-format/screenshot-1.webp" alt="Screenshot of the Format dropdown in the Page section" />
</Frame>
</Step>
<Step title="Review the preview">
Your resume preview updates immediately to reflect the new format. Check that your content displays correctly.
</Step>
</Steps>
## Choosing the right format
Use this quick reference to decide which format fits your needs:
| Situation | Recommended Format |
|-----------|-------------------|
| Applying to jobs in North America | Letter |
| Applying to jobs internationally | A4 |
| Digital-only applications (no printing) | Free-Form |
| Uploading to ATS or job portals | Free-Form |
| Need to print physical copies | A4 or Letter |
| Long resume with lots of content | Free-Form |
| Traditional industries (law, finance) | A4 or Letter |
<Warning>
If you switch from Free-Form to A4 or Letter, your content will be split across multiple pages. Review the result carefully to ensure page breaks don't occur in awkward places.
</Warning>
## Example PDFs
Download these sample resumes to see how different formats affect the final PDF output:
<CardGroup cols={1}>
<Card arrow horizontal title="A4 Format Example" icon="file-pdf" href="https://github.com/user-attachments/files/24849548/a4.pdf">
A sample resume in A4 format showing traditional page breaks and constraints.
</Card>
<Card arrow horizontal title="Free-Form Example" icon="file-lines" href="https://github.com/user-attachments/files/24849552/free-form.pdf">
A sample resume in Free-Form format showing a continuous single-page layout.
</Card>
</CardGroup>
## Frequently asked questions
<AccordionGroup>
<Accordion title="Does Free-Form work with ATS systems?">
Yes. ATS systems parse the text content of your PDF, not the page dimensions. Free-Form resumes are fully compatible with applicant tracking systems.
</Accordion>
<Accordion title="Can I switch formats after creating my resume?">
Absolutely. You can change the format at any time from the Page section in the right sidebar. Your content is preserved—only the layout changes.
</Accordion>
<Accordion title="What if a job posting asks for a 'one-page resume'?">
If the employer specifically requests a one-page resume and expects a traditional format, use A4 or Letter and ensure your content fits within a single page. See [Fitting content on a page](/guides/fitting-content-on-a-page) for tips.
</Accordion>
<Accordion title="Does Free-Form affect the file size?">
Slightly. A longer single page may produce a marginally larger PDF than a paginated version of the same content, but the difference is negligible for typical resume lengths.
</Accordion>
<Accordion title="Which format should I use for LinkedIn?">
LinkedIn doesn't display uploaded resumes in their original format—it extracts the content. Free-Form works perfectly for LinkedIn uploads.
</Accordion>
</AccordionGroup>
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

+3
View File
@@ -62,6 +62,9 @@ export default defineConfig({
"zu-ZA": "en-US",
default: "en-US",
},
formatOptions: {
lineNumbers: false,
},
catalogs: [
{
path: "<rootDir>/locales/{locale}",
BIN
View File
Binary file not shown.
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1473 -1402
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1146 -1075
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+926 -854
View File
File diff suppressed because it is too large Load Diff
+1514 -1443
View File
File diff suppressed because it is too large Load Diff
+1530 -1459
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+937 -866
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1520 -1449
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1494 -1423
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+1533 -1462
View File
File diff suppressed because it is too large Load Diff
+1524 -1453
View File
File diff suppressed because it is too large Load Diff
+926 -855
View File
File diff suppressed because it is too large Load Diff
+926 -855
View File
File diff suppressed because it is too large Load Diff
+926 -855
View File
File diff suppressed because it is too large Load Diff
+926 -855
View File
File diff suppressed because it is too large Load Diff
+926 -855
View File
File diff suppressed because it is too large Load Diff
+926 -855
View File
File diff suppressed because it is too large Load Diff
+1516 -1445
View File
File diff suppressed because it is too large Load Diff
+1521 -1450
View File
File diff suppressed because it is too large Load Diff
+925 -853
View File
File diff suppressed because it is too large Load Diff
+33 -30
View File
@@ -1,10 +1,10 @@
{
"name": "reactive-resume",
"description": "Reactive Resume is a free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume.",
"version": "5.0.1",
"version": "5.0.3",
"license": "MIT",
"type": "module",
"packageManager": "pnpm@10.28.1+sha512.7d7dbbca9e99447b7c3bf7a73286afaaf6be99251eb9498baefa7d406892f67b879adb3a1d7e687fc4ccc1a388c7175fbaae567a26ab44d1067b54fcb0d6a316",
"packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264",
"repository": {
"type": "git",
"url": "https://github.com/amruthpillai/reactive-resume.git"
@@ -31,12 +31,12 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@ai-sdk/anthropic": "^3.0.23",
"@ai-sdk/google": "^3.0.13",
"@ai-sdk/openai": "^3.0.18",
"@ai-sdk/anthropic": "^3.0.28",
"@ai-sdk/google": "^3.0.15",
"@ai-sdk/openai": "^3.0.21",
"@aws-sdk/client-s3": "^3.975.0",
"@better-auth/core": "1.5.0-beta.9",
"@better-auth/passkey": "1.5.0-beta.9",
"@better-auth/core": "1.5.0-beta.10",
"@better-auth/passkey": "1.5.0-beta.10",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
@@ -56,42 +56,43 @@
"@sindresorhus/slugify": "^3.0.0",
"@t3-oss/env-core": "^0.13.10",
"@tanstack/react-query": "5.90.20",
"@tanstack/react-router": "^1.156.0",
"@tanstack/react-router-ssr-query": "^1.156.0",
"@tanstack/react-start": "^1.156.0",
"@tanstack/zod-adapter": "^1.156.0",
"@tiptap/extension-highlight": "^3.17.0",
"@tiptap/extension-table": "^3.17.0",
"@tiptap/extension-text-align": "^3.17.0",
"@tiptap/pm": "^3.17.0",
"@tiptap/react": "^3.17.0",
"@tiptap/starter-kit": "^3.17.0",
"ai": "^6.0.49",
"@tanstack/react-router": "^1.157.16",
"@tanstack/react-router-ssr-query": "^1.157.16",
"@tanstack/react-start": "^1.157.16",
"@tanstack/zod-adapter": "^1.157.16",
"@tiptap/extension-highlight": "^3.17.1",
"@tiptap/extension-table": "^3.17.1",
"@tiptap/extension-text-align": "^3.17.1",
"@tiptap/pm": "^3.17.1",
"@tiptap/react": "^3.17.1",
"@tiptap/starter-kit": "^3.17.1",
"ai": "^6.0.57",
"ai-sdk-ollama": "^3.3.0",
"bcrypt": "^6.0.0",
"better-auth": "1.5.0-beta.9",
"better-auth": "1.5.0-beta.10",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"dompurify": "^3.3.1",
"drizzle-orm": "^1.0.0-beta.12-a5629fb",
"es-toolkit": "^1.44.0",
"fast-deep-equal": "^3.1.3",
"fuse.js": "^7.1.0",
"immer": "^11.1.3",
"input-otp": "^1.4.2",
"js-cookie": "^3.0.5",
"monaco-editor": "^0.55.1",
"motion": "^12.29.0",
"nodemailer": "^7.0.12",
"motion": "^12.29.2",
"nodemailer": "^7.0.13",
"pg": "^8.17.2",
"puppeteer-core": "^24.36.0",
"puppeteer-core": "^24.36.1",
"qrcode.react": "^4.2.0",
"radix-ui": "^1.4.3",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-hook-form": "^7.71.1",
"react-hotkeys-hook": "^5.2.3",
"react-resizable-panels": "^4.4.2",
"react-resizable-panels": "^4.5.2",
"react-window": "^2.2.5",
"react-zoom-pan-pinch": "^3.7.0",
"reflect-metadata": "^0.2.2",
@@ -106,11 +107,13 @@
"unique-names-generator": "^4.7.1",
"usehooks-ts": "^3.1.1",
"uuid": "^13.0.0",
"workbox-window": "^7.4.0",
"zod": "^4.3.6",
"zundo": "^2.3.0",
"zustand": "^5.0.10"
},
"devDependencies": {
"@biomejs/biome": "^2.3.12",
"@biomejs/biome": "^2.3.13",
"@lingui/babel-plugin-lingui-macro": "^5.9.0",
"@lingui/cli": "^5.9.0",
"@lingui/vite-plugin": "^5.9.0",
@@ -118,9 +121,9 @@
"@types/bcrypt": "^6.0.0",
"@types/js-cookie": "^3.0.6",
"@types/node": "^25.0.10",
"@types/nodemailer": "^7.0.5",
"@types/nodemailer": "^7.0.9",
"@types/pg": "^8.16.0",
"@types/react": "^19.2.9",
"@types/react": "^19.2.10",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.2",
"babel-plugin-macros": "^3.1.0",
@@ -128,8 +131,8 @@
"knip": "^5.82.1",
"nitro": "npm:nitro-nightly@latest",
"node-addon-api": "^8.5.0",
"node-gyp": "^12.1.0",
"npm-check-updates": "^19.3.1",
"node-gyp": "^12.2.0",
"npm-check-updates": "^19.3.2",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
"vite": "npm:rolldown-vite@latest",
+1353 -1686
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.
Binary file not shown.
+128
View File
@@ -0,0 +1,128 @@
---
name: resume-builder
description: Generate professional resumes that conform to the Reactive Resume schema. Use when the user wants to create, build, or generate a resume through conversational AI, or asks about resume structure, sections, or content. This skill guides the agent to ask clarifying questions, avoid hallucination, and produce valid JSON output for https://rxresu.me.
---
# Resume Builder for Reactive Resume
Build professional resumes through conversational AI for [Reactive Resume](https://rxresu.me), a free and open-source resume builder.
## Core Principles
1. **Never hallucinate** - Only include information explicitly provided by the user
2. **Ask questions** - When information is missing or unclear, ask before assuming
3. **Be concise** - Use clear, direct language; avoid filler words
4. **Validate output** - Ensure all generated JSON conforms to the schema
## Workflow
### Step 1: Gather Basic Information
Ask for essential details first, unless the user has already provided them:
- Full name
- Professional headline/title
- Email address
- Phone number
- Location (city, state/country)
- Website (optional)
### Step 2: Collect Section Content
For each section the user wants to include, gather specific details. Never invent dates, company names, or achievements.
**Experience**: company, position, location, period (e.g., "Jan 2020 - Present"), description of responsibilities/achievements
**Education**: school, degree, area of study, grade (optional), location, period
**Skills**: name, proficiency level (Beginner/Intermediate/Advanced/Expert), keywords
**Projects**: name, period, website (optional), description
**Other sections**: languages, certifications, awards, publications, volunteer work, interests, references
### Step 3: Configure Layout and Design
Ask about preferences:
- Template preference (13 available: azurill, bronzor, chikorita, ditto, ditgar, gengar, glalie, kakuna, lapras, leafish, onyx, pikachu, rhyhorn)
- Page format: A4 or Letter
- Which sections to include and their order
### Step 4: Generate Valid JSON
Output must conform to the Reactive Resume schema. See [references/schema.md](references/schema.md) for the complete schema structure.
Key requirements:
- All item `id` fields must be valid UUIDs
- Description fields accept HTML-formatted strings
- Website fields require both `url` and `label` properties
- Colors use `rgba(r, g, b, a)` format
- Fonts must be available on Google Fonts
## Resume Writing Tips
Share these tips when helping users craft their resume content:
### Content Guidelines
- **Lead with impact**: Start bullet points with action verbs (Led, Developed, Increased, Managed)
- **Quantify achievements**: Use numbers when possible ("Increased sales by 25%", "Managed team of 8")
- **Tailor to the role**: Emphasize relevant experience for the target position
- **Be specific**: Replace vague terms with concrete examples
- **Keep it concise**: 1-2 pages maximum for most professionals
### Section Order Recommendations
For most professionals:
1. Summary (if experienced)
2. Experience
3. Education
4. Skills
5. Projects (if relevant)
6. Certifications/Awards
For students/recent graduates:
1. Education
2. Projects
3. Skills
4. Experience (if any)
5. Activities/Volunteer
### Common Mistakes to Avoid
- Including personal pronouns ("I", "my")
- Using passive voice
- Listing job duties instead of achievements
- Including irrelevant personal information
- Inconsistent date formatting
## Output Format
When generating the resume, output a complete JSON object that conforms to the Reactive Resume schema. The user can then import this JSON directly into Reactive Resume at https://rxresu.me.
Example minimal structure:
```json
{
"picture": { "hidden": true, "url": "", "size": 80, "rotation": 0, "aspectRatio": 1, "borderRadius": 0, "borderColor": "rgba(0, 0, 0, 0.5)", "borderWidth": 0, "shadowColor": "rgba(0, 0, 0, 0.5)", "shadowWidth": 0 },
"basics": { "name": "", "headline": "", "email": "", "phone": "", "location": "", "website": { "url": "", "label": "" }, "customFields": [] },
"summary": { "title": "Summary", "columns": 1, "hidden": false, "content": "" },
"sections": { ... },
"customSections": [],
"metadata": { "template": "onyx", "layout": { ... }, ... }
}
```
For the complete schema, see [references/schema.md](references/schema.md).
## Asking Good Questions
When information is missing, ask specific questions:
- "What was your job title at [Company]?"
- "What dates did you work there? (e.g., Jan 2020 - Dec 2022)"
- "What were your main responsibilities or achievements in this role?"
- "Do you have a specific target role or industry in mind?"
Avoid compound questions. Ask one thing at a time for clarity.
+365
View File
@@ -0,0 +1,365 @@
# Reactive Resume Schema Reference
The complete JSON Schema is available at: https://rxresu.me/schema.json
This reference provides key structural information for generating valid resume data.
## Top-Level Structure
```json
{
"picture": { ... },
"basics": { ... },
"summary": { ... },
"sections": { ... },
"customSections": [ ... ],
"metadata": { ... }
}
```
## Picture Configuration
```json
{
"hidden": false,
"url": "https://example.com/photo.jpg",
"size": 80,
"rotation": 0,
"aspectRatio": 1,
"borderRadius": 0,
"borderColor": "rgba(0, 0, 0, 0.5)",
"borderWidth": 0,
"shadowColor": "rgba(0, 0, 0, 0.5)",
"shadowWidth": 0
}
```
## Basics
```json
{
"name": "John Doe",
"headline": "Senior Software Engineer",
"email": "john@example.com",
"phone": "+1 (555) 123-4567",
"location": "San Francisco, CA",
"website": { "url": "https://johndoe.com", "label": "Portfolio" },
"customFields": []
}
```
## Summary
```json
{
"title": "Summary",
"columns": 1,
"hidden": false,
"content": "<p>HTML-formatted summary content here.</p>"
}
```
## Sections
All sections share a common structure:
```json
{
"title": "Section Title",
"columns": 1,
"hidden": false,
"items": [ ... ]
}
```
### Experience Items
```json
{
"id": "uuid-here",
"hidden": false,
"company": "Acme Corp",
"position": "Software Engineer",
"location": "San Francisco, CA",
"period": "Jan 2020 - Present",
"website": { "url": "https://acme.com", "label": "" },
"description": "<ul><li>Built scalable microservices</li><li>Led team of 5 engineers</li></ul>"
}
```
### Education Items
```json
{
"id": "uuid-here",
"hidden": false,
"school": "Stanford University",
"degree": "Bachelor of Science",
"area": "Computer Science",
"grade": "3.8 GPA",
"location": "Stanford, CA",
"period": "2012 - 2016",
"website": { "url": "", "label": "" },
"description": ""
}
```
### Skills Items
```json
{
"id": "uuid-here",
"hidden": false,
"icon": "",
"name": "JavaScript",
"proficiency": "Expert",
"level": 5,
"keywords": ["React", "Node.js", "TypeScript"]
}
```
Level: 0-5 (0 hides the visual indicator)
### Project Items
```json
{
"id": "uuid-here",
"hidden": false,
"name": "Open Source Project",
"period": "2023 - Present",
"website": { "url": "https://github.com/user/project", "label": "GitHub" },
"description": "<p>Description of the project and your contributions.</p>"
}
```
### Language Items
```json
{
"id": "uuid-here",
"hidden": false,
"language": "English",
"fluency": "Native",
"level": 5
}
```
Fluency examples: Native, Fluent, Conversational, Basic, or CEFR levels (A1-C2)
### Certification Items
```json
{
"id": "uuid-here",
"hidden": false,
"title": "AWS Solutions Architect",
"issuer": "Amazon Web Services",
"date": "March 2023",
"website": { "url": "https://aws.amazon.com/certification/", "label": "Verify" },
"description": ""
}
```
### Award Items
```json
{
"id": "uuid-here",
"hidden": false,
"title": "Employee of the Year",
"awarder": "Acme Corp",
"date": "2022",
"website": { "url": "", "label": "" },
"description": ""
}
```
### Publication Items
```json
{
"id": "uuid-here",
"hidden": false,
"title": "Research Paper Title",
"publisher": "IEEE",
"date": "2021",
"website": { "url": "https://doi.org/...", "label": "DOI" },
"description": ""
}
```
### Volunteer Items
```json
{
"id": "uuid-here",
"hidden": false,
"organization": "Local Food Bank",
"location": "San Francisco, CA",
"period": "2020 - Present",
"website": { "url": "", "label": "" },
"description": "<p>Volunteer activities and impact.</p>"
}
```
### Interest Items
```json
{
"id": "uuid-here",
"hidden": false,
"icon": "",
"name": "Photography",
"keywords": ["Landscape", "Portrait", "Street"]
}
```
### Reference Items
```json
{
"id": "uuid-here",
"hidden": false,
"name": "Jane Smith",
"position": "Engineering Manager at Acme Corp",
"website": { "url": "https://linkedin.com/in/janesmith", "label": "LinkedIn" },
"phone": "+1 (555) 987-6543",
"description": "<p>Optional testimonial or quote.</p>"
}
```
### Profile Items (Social Links)
```json
{
"id": "uuid-here",
"hidden": false,
"icon": "linkedin-logo",
"network": "LinkedIn",
"username": "johndoe",
"website": { "url": "https://linkedin.com/in/johndoe", "label": "" }
}
```
Icons use @phosphor-icons/web names. Common icons: `linkedin-logo`, `github-logo`, `twitter-logo`, `globe`
## Metadata
### Template Options
Available templates: `azurill`, `bronzor`, `chikorita`, `ditto`, `ditgar`, `gengar`, `glalie`, `kakuna`, `lapras`, `leafish`, `onyx`, `pikachu`, `rhyhorn`
### Layout Configuration
```json
{
"sidebarWidth": 35,
"pages": [
{
"fullWidth": false,
"main": ["profiles", "summary", "experience", "education", "projects"],
"sidebar": ["skills", "languages", "certifications", "interests"]
}
]
}
```
Section IDs for layout: `profiles`, `experience`, `education`, `projects`, `skills`, `languages`, `interests`, `awards`, `certifications`, `publications`, `volunteer`, `references`, `summary`
### Page Settings
```json
{
"gapX": 4,
"gapY": 6,
"marginX": 14,
"marginY": 12,
"format": "a4",
"locale": "en-US",
"hideIcons": false
}
```
Format options: `a4`, `letter`
### Design Settings
```json
{
"colors": {
"primary": "rgba(220, 38, 38, 1)",
"text": "rgba(0, 0, 0, 1)",
"background": "rgba(255, 255, 255, 1)"
},
"level": {
"icon": "star",
"type": "circle"
}
}
```
Level types: `hidden`, `circle`, `square`, `rectangle`, `rectangle-full`, `progress-bar`, `icon`
### Typography
```json
{
"body": {
"fontFamily": "IBM Plex Serif",
"fontWeights": ["400", "500"],
"fontSize": 10,
"lineHeight": 1.5
},
"heading": {
"fontFamily": "IBM Plex Serif",
"fontWeights": ["600"],
"fontSize": 14,
"lineHeight": 1.5
}
}
```
Font family must be available on Google Fonts. Font weights: 100-900.
### CSS Customization
```json
{
"enabled": false,
"value": ""
}
```
## Custom Sections
Custom sections extend the base section structure with additional fields:
```json
{
"id": "uuid-here",
"title": "Custom Section Title",
"columns": 1,
"hidden": false,
"type": "experience",
"items": [ ... ]
}
```
The `type` field determines the item schema. Valid types: `profiles`, `experience`, `education`, `projects`, `skills`, `languages`, `interests`, `awards`, `certifications`, `publications`, `volunteer`, `references`
## UUID Generation
All `id` fields require valid UUIDs version 7. If available, use 'v7()' from the 'uuid' package to generate a valid UUID. Otherwise, fallback to generating a valid UUID v4 using crypto.randomUUID() from 'node' or the equivalent.
## HTML Content
Fields marked as "HTML-formatted string" accept basic HTML:
- `<p>` for paragraphs
- `<ul><li>` for bullet lists
- `<ol><li>` for numbered lists
- `<strong>` for bold
- `<em>` for italic
- `<a href="...">` for links
Keep formatting minimal for best rendering across templates.
+8 -8
View File
@@ -33,10 +33,10 @@ export const Spotlight = ({
<motion.div
animate={{ x: [0, xOffset, 0] }}
transition={{ duration, repeat: Infinity, repeatType: "reverse", ease: "easeInOut" }}
className="pointer-events-none absolute top-0 left-0 z-40 h-svh w-svw"
className="pointer-events-none absolute start-0 top-0 z-40 h-svh w-svw"
>
<div
className="absolute top-0 left-0"
className="absolute start-0 top-0"
style={{
width: `${width}px`,
height: `${height}px`,
@@ -46,7 +46,7 @@ export const Spotlight = ({
/>
<div
className="absolute top-0 left-0 origin-top-left"
className="absolute start-0 top-0 origin-top-left"
style={{
height: `${height}px`,
width: `${smallWidth}px`,
@@ -56,7 +56,7 @@ export const Spotlight = ({
/>
<div
className="absolute top-0 left-0 origin-top-left"
className="absolute start-0 top-0 origin-top-left"
style={{
height: `${height}px`,
width: `${smallWidth}px`,
@@ -68,7 +68,7 @@ export const Spotlight = ({
<motion.div
animate={{ x: [0, -xOffset, 0] }}
className="pointer-events-none absolute top-0 right-0 z-40 h-svh w-svw"
className="pointer-events-none absolute end-0 top-0 z-40 h-svh w-svw"
transition={{
duration,
repeat: Infinity,
@@ -77,7 +77,7 @@ export const Spotlight = ({
}}
>
<div
className="absolute top-0 right-0"
className="absolute end-0 top-0"
style={{
width: `${width}px`,
height: `${height}px`,
@@ -87,7 +87,7 @@ export const Spotlight = ({
/>
<div
className="absolute top-0 right-0 origin-top-right"
className="absolute end-0 top-0 origin-top-right"
style={{
height: `${height}px`,
width: `${smallWidth}px`,
@@ -97,7 +97,7 @@ export const Spotlight = ({
/>
<div
className="absolute top-0 right-0 origin-top-right"
className="absolute end-0 top-0 origin-top-right"
style={{
height: `${height}px`,
width: `${smallWidth}px`,
+198 -58
View File
@@ -1,8 +1,88 @@
import { XIcon } from "@phosphor-icons/react";
import { Trans } from "@lingui/react/macro";
import { PencilSimpleIcon, XIcon } from "@phosphor-icons/react";
import { motion, Reorder, useDragControls } from "motion/react";
import * as React from "react";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { useControlledState } from "@/hooks/use-controlled-state";
import { cn } from "@/utils/style";
import { Kbd } from "../ui/kbd";
const RETURN_KEY = "Enter";
const COMMA_KEY = ",";
type ChipItemProps = {
chip: string;
index: number;
isEditing: boolean;
onEdit: (index: number) => void;
onRemove: (index: number) => void;
};
function ChipItem({ chip, index, isEditing, onEdit, onRemove }: ChipItemProps) {
const controls = useDragControls();
const [isHovered, setIsHovered] = React.useState(false);
return (
<Reorder.Item
value={chip}
dragListener={false}
dragControls={controls}
initial={{ opacity: 1, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
className="relative"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<Badge
variant="outline"
className={cn(
"flex h-7 cursor-grab select-none items-center gap-0 overflow-hidden px-3 active:cursor-grabbing",
isEditing && "border-primary ring-1 ring-primary",
)}
onPointerDown={(e) => {
e.preventDefault();
controls.start(e);
}}
>
<span className="max-w-32 truncate">{chip}</span>
<motion.div
initial={false}
animate={{ width: isHovered ? 40 : 0, marginInlineStart: isHovered ? 8 : 0, opacity: isHovered ? 1 : 0 }}
transition={{ duration: 0.15, ease: "linear" }}
className="flex shrink-0 items-center gap-x-1 overflow-hidden"
>
<button
type="button"
tabIndex={-1}
aria-label={`Edit ${chip}`}
onClick={(e) => {
e.stopPropagation();
onEdit(index);
}}
className="rounded-sm p-0.5 hover:bg-secondary hover:text-foreground focus:outline-none"
>
<PencilSimpleIcon className="size-3.5" />
</button>
<button
type="button"
tabIndex={-1}
aria-label={`Remove ${chip}`}
onClick={(e) => {
e.stopPropagation();
onRemove(index);
}}
className="rounded-sm p-0.5 hover:bg-destructive/10 hover:text-destructive focus:outline-none"
>
<XIcon className="size-3.5" />
</button>
</motion.div>
</Badge>
</Reorder.Item>
);
}
type Props = Omit<React.ComponentProps<"div">, "value" | "onChange"> & {
value?: string[];
@@ -18,6 +98,7 @@ export function ChipInput({ value, defaultValue = [], onChange, className, ...pr
});
const [input, setInput] = React.useState("");
const [editingIndex, setEditingIndex] = React.useState<number | null>(null);
const inputRef = React.useRef<HTMLInputElement>(null);
const addChip = React.useCallback(
@@ -30,18 +111,82 @@ export function ChipInput({ value, defaultValue = [], onChange, className, ...pr
[chips, setChips],
);
const updateChip = React.useCallback(
(index: number, newValue: string) => {
const trimmed = newValue.trim();
if (!trimmed || index < 0 || index >= chips.length) return;
// Check if the new value already exists at a different index
const existingIndex = chips.findIndex((c, i) => c === trimmed && i !== index);
if (existingIndex !== -1) return;
const newChips = [...chips];
newChips[index] = trimmed;
setChips(newChips);
},
[chips, setChips],
);
const removeChip = React.useCallback(
(index: number) => {
if (index < 0 || index >= chips.length) return;
const newChips = chips.slice(0, index).concat(chips.slice(index + 1));
setChips(Array.from(new Set(newChips)));
setChips(newChips);
// If we were editing this chip, clear edit mode
if (editingIndex === index) {
setEditingIndex(null);
setInput("");
} else if (editingIndex !== null && editingIndex > index) {
// Adjust editing index if we removed a chip before it
setEditingIndex(editingIndex - 1);
}
},
[chips, setChips],
[chips, setChips, editingIndex],
);
const handleEdit = React.useCallback(
(index: number) => {
setEditingIndex(index);
setInput(chips[index]);
inputRef.current?.focus();
},
[chips],
);
const handleReorder = React.useCallback(
(newOrder: string[]) => {
// Update editingIndex to follow the chip being edited
if (editingIndex !== null) {
const editingChip = chips[editingIndex];
const newIndex = newOrder.indexOf(editingChip);
if (newIndex !== -1 && newIndex !== editingIndex) {
setEditingIndex(newIndex);
}
}
setChips(newOrder);
},
[chips, editingIndex, setChips],
);
const handleInputChange = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value;
// When editing, don't split on comma - allow full text editing
if (editingIndex !== null) {
if (newValue.includes(",")) {
// Save current edit
updateChip(editingIndex, newValue.replace(",", ""));
setEditingIndex(null);
setInput("");
} else {
setInput(newValue);
}
return;
}
// When adding new chips, split on comma
if (newValue.includes(",")) {
const parts = newValue.split(",");
parts.slice(0, -1).forEach(addChip);
@@ -50,74 +195,69 @@ export function ChipInput({ value, defaultValue = [], onChange, className, ...pr
setInput(newValue);
}
},
[addChip],
[addChip, editingIndex, updateChip],
);
const removeLastChip = React.useCallback(() => {
if (chips.length === 0) return;
const newChips = chips.slice(0, -1);
setChips(newChips);
}, [chips, setChips]);
const handleKeyDown = React.useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if ((e.key === "Enter" || e.key === ",") && input.trim() !== "") {
if (e.key === "Enter" || e.key === ",") {
e.preventDefault();
addChip(input);
if (editingIndex !== null) {
// Save edit
if (input.trim()) {
updateChip(editingIndex, input);
}
setEditingIndex(null);
setInput("");
} else if (input.trim()) {
// Add new chip
addChip(input);
setInput("");
}
} else if (e.key === "Escape" && editingIndex !== null) {
// Cancel edit
setEditingIndex(null);
setInput("");
} else if (e.key === "Backspace" && input === "") {
removeLastChip();
}
},
[input, addChip, removeLastChip],
[input, addChip, editingIndex, updateChip],
);
const handleWrapperClick = React.useCallback(() => {
inputRef.current?.focus();
}, []);
return (
<div
tabIndex={-1}
onClick={handleWrapperClick}
className={cn(
"flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border bg-background px-3 py-1.5 focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50",
className,
<div className={cn("flex flex-col gap-2", className)} {...props}>
{chips.length > 0 && (
<Reorder.Group axis="x" values={chips} onReorder={handleReorder} className="flex flex-wrap gap-1.5">
{chips.map((chip, idx) => (
<ChipItem
key={`${chip}-${idx}`}
chip={chip}
index={idx}
isEditing={editingIndex === idx}
onEdit={handleEdit}
onRemove={removeChip}
/>
))}
</Reorder.Group>
)}
{...props}
>
<div className="flex flex-wrap items-center gap-1.5">
{chips.map((chip, idx) => (
<div key={chip + idx} className="relative">
<Badge variant="outline" className="flex select-none items-center gap-1 pr-1 pl-2">
<span>{chip}</span>
<button
type="button"
tabIndex={-1}
aria-label={`Remove ${chip}`}
onClick={(e) => {
e.stopPropagation();
removeChip(idx);
}}
className="ml-0.5 hover:text-destructive focus:outline-none"
>
<XIcon className="size-3" />
</button>
</Badge>
</div>
))}
</div>
<input
type="text"
value={input}
ref={inputRef}
autoComplete="off"
aria-label="Add chip"
onKeyDown={handleKeyDown}
onChange={handleInputChange}
className="min-w-0 grow border-none bg-transparent outline-none focus:outline-none focus:ring-0"
/>
<div className="flex flex-col gap-2">
<Input
ref={inputRef}
type="text"
value={input}
autoComplete="off"
aria-label={editingIndex !== null ? "Edit keyword" : "Add keyword"}
placeholder={editingIndex !== null ? "Editing keyword..." : "Add a keyword..."}
onKeyDown={handleKeyDown}
onChange={handleInputChange}
/>
<p className="text-muted-foreground text-xs">
<Trans>
Press <Kbd>{RETURN_KEY}</Kbd> or <Kbd>{COMMA_KEY}</Kbd> to add or save the current keyword.
</Trans>
</p>
</div>
</div>
);
}
+58 -3
View File
@@ -1,6 +1,8 @@
.tiptap_content {
--list-margin: 0.25rem;
--table-border-color: var(--page-text-color);
word-wrap: break-word;
overflow-wrap: break-word;
:where(p) {
margin-top: 0.5rem;
@@ -30,14 +32,15 @@
}
:where(li) {
margin-inline-start: 1rem;
margin-top: var(--list-margin);
margin-bottom: var(--list-margin);
margin-left: 1rem;
list-style-position: outside;
}
:where(li ul li, li ol li) {
margin: var(--list-margin) 1rem;
margin-block: var(--list-margin);
margin-inline: 1rem;
}
:where(mark) {
@@ -54,7 +57,7 @@
:where(th) {
font-weight: inherit;
text-align: left;
text-align: start;
}
:where(th, td) {
@@ -62,6 +65,56 @@
border-right: 1px solid var(--table-border-color);
border-bottom: 1px solid var(--table-border-color);
}
/* RTL Support: Apply RTL direction when text is right-aligned */
p[style*="text-align: right"],
h1[style*="text-align: right"],
h2[style*="text-align: right"],
h3[style*="text-align: right"],
h4[style*="text-align: right"],
h5[style*="text-align: right"],
h6[style*="text-align: right"],
li[style*="text-align: right"],
ul[style*="text-align: right"],
ol[style*="text-align: right"] {
direction: rtl;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}
/* LTR Support: Apply LTR direction when text is left-aligned */
p[style*="text-align: left"],
h1[style*="text-align: left"],
h2[style*="text-align: left"],
h3[style*="text-align: left"],
h4[style*="text-align: left"],
h5[style*="text-align: left"],
h6[style*="text-align: left"],
li[style*="text-align: left"],
ul[style*="text-align: left"],
ol[style*="text-align: left"] {
direction: ltr;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}
/* When list items are RTL, make bullets/numbers appear on the right */
li[style*="text-align: right"] {
text-align: right;
}
/* Parent list inherits direction from its items */
ul:has(li[style*="text-align: right"]),
ol:has(li[style*="text-align: right"]) {
direction: rtl;
}
ul:has(li[style*="text-align: left"]),
ol:has(li[style*="text-align: left"]) {
direction: ltr;
}
}
.editor_content {
@@ -70,6 +123,8 @@
font-weight: var(--page-body-font-weight);
line-height: var(--page-body-line-height);
color: var(--page-text-color);
word-wrap: break-word;
overflow-wrap: break-word;
[class*="dark"] & {
--table-border-color: var(--color-foreground);
+1 -1
View File
@@ -88,7 +88,7 @@ const extensions = [
class: "rounded-md px-0.5 py-px",
},
}),
TextAlign.configure({ types: ["heading", "paragraph"] }),
TextAlign.configure({ types: ["heading", "paragraph", "listItem"] }),
TableKit.configure(),
];
+1 -1
View File
@@ -54,7 +54,7 @@ export function URLInput({ value, onChange, ...props }: Props) {
<InputGroupInput
value={urlValue}
className={cn(props.className, "pl-0!")}
className={cn(props.className, "ps-0!")}
onChange={handleUrlChange}
{...props}
/>
@@ -11,8 +11,8 @@ export function BreakpointIndicator({ position = "bottom-right" }: BreakpointInd
const isBottom = position.includes("bottom");
const top = isTop ? "top-0" : "bottom-0";
const right = isRight ? "right-0" : "left-0";
const left = isLeft ? "left-0" : "right-0";
const right = isRight ? "end-0" : "start-0";
const left = isLeft ? "start-0" : "end-0";
const bottom = isBottom ? "bottom-0" : "top-0";
return (
@@ -1,18 +1,8 @@
import { useMemo } from "react";
import type z from "zod";
import { pageDimensionsAsMillimeters } from "@/schema/page";
import type { resumeDataSchema } from "@/schema/resume/data";
const pageDimensions = {
a4: {
width: "210mm",
height: "297mm",
},
letter: {
width: "216mm",
height: "279mm",
},
} as const;
type UseCssVariablesProps = Pick<z.infer<typeof resumeDataSchema>, "picture" | "metadata">;
export const useCSSVariables = ({ picture, metadata }: UseCssVariablesProps) => {
@@ -28,8 +18,8 @@ export const useCSSVariables = ({ picture, metadata }: UseCssVariablesProps) =>
return {
"--picture-border-radius": `${picture.borderRadius}pt`,
"--page-width": pageDimensions[metadata.page.format].width,
"--page-height": pageDimensions[metadata.page.format].height,
"--page-width": pageDimensionsAsMillimeters[metadata.page.format].width,
"--page-height": pageDimensionsAsMillimeters[metadata.page.format].height,
"--page-sidebar-width": `${metadata.layout.sidebarWidth}%`,
"--page-text-color": metadata.design.colors.text,
"--page-primary-color": metadata.design.colors.primary,

Some files were not shown because too many files have changed in this diff Show More