chore: import upstream snapshot with attribution
Publish Docker Image / publish (push) Has been cancelled
Publish Docker Image / publish (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# Adding Resume Templates
|
||||
|
||||
> Guide for adding new resume template layouts.
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Create `components/resume/resume-{name}.tsx`
|
||||
2. Export from `components/resume/index.ts`
|
||||
3. Add to `FormattingControls` template selector
|
||||
4. Create thumbnail image
|
||||
|
||||
## Template Component
|
||||
|
||||
```tsx
|
||||
interface TemplateProps {
|
||||
resumeData: ResumeData;
|
||||
settings: TemplateSettings;
|
||||
}
|
||||
|
||||
export function ResumeNewTemplate({ resumeData, settings }: TemplateProps) {
|
||||
return (
|
||||
<div className="resume-print">
|
||||
{/* Header */}
|
||||
<header className="resume-section">
|
||||
<h1>{resumeData.personalInfo?.name}</h1>
|
||||
</header>
|
||||
|
||||
{/* Sections */}
|
||||
{getSortedSections(resumeData).map(section => (
|
||||
<section key={section.id} className="resume-section">
|
||||
<h3 className="resume-section-title">{section.displayName}</h3>
|
||||
<div className="resume-items">
|
||||
{/* Items */}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## CSS Classes Required
|
||||
|
||||
```css
|
||||
.resume-print /* Root container (Playwright waits for this) */
|
||||
.resume-section /* Section wrapper */
|
||||
.resume-section-title /* Section heading */
|
||||
.resume-items /* Items container */
|
||||
.resume-item /* Individual entry (won't page-break) */
|
||||
```
|
||||
|
||||
## Export Template
|
||||
|
||||
```typescript
|
||||
// components/resume/index.ts
|
||||
export { ResumeNewTemplate } from './resume-new-template';
|
||||
```
|
||||
|
||||
## Add to Selector
|
||||
|
||||
```typescript
|
||||
// components/builder/formatting-controls.tsx
|
||||
const TEMPLATES = [
|
||||
{ id: 'swiss-single', name: 'Single Column' },
|
||||
{ id: 'swiss-two-column', name: 'Two Column' },
|
||||
{ id: 'new-template', name: 'New Template' }, // Add here
|
||||
];
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
1. Load builder with new template
|
||||
2. Check all sections render
|
||||
3. Verify PDF generation works
|
||||
4. Test with 2+ page content
|
||||
@@ -0,0 +1,77 @@
|
||||
# Application Tracker Feature
|
||||
|
||||
> **A Kanban board for managing the job-application pipeline, auto-populated from the tailor flow.**
|
||||
|
||||
## Overview
|
||||
|
||||
The Application Tracker (`/tracker`) gives each tailored resume a place in a
|
||||
seven-column Kanban pipeline. Tailoring a resume to a job auto-creates an
|
||||
`applied` card; users can also add cards manually from a pasted job
|
||||
description. Cards are drag-and-drop reorderable within and across columns.
|
||||
|
||||
## Columns (stable keys, decoupled from i18n labels)
|
||||
|
||||
`saved` · `applied` · `no_response` · `response` · `interview` · `accepted` · `rejected`
|
||||
|
||||
Auto-created cards from the tailor flow land in **`applied`**. Manual cards
|
||||
default to `applied` but can be created as `saved`.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Auto-create:** `POST /resumes/improve/confirm` (and the legacy
|
||||
`POST /resumes/improve`) create an `applied` card after persisting the
|
||||
tailored resume — best-effort (a tracker failure never breaks tailoring).
|
||||
Company/role come from the cached keyword-extraction pass, so there is **no
|
||||
extra LLM call** on this path.
|
||||
2. **Manual add:** `POST /applications` creates the job from the pasted JD then
|
||||
the card; when company/role aren't supplied it runs one best-effort
|
||||
extraction call (falls back to blank/editable).
|
||||
3. **Drag/drop:** cards reorder within a column or move across columns; the
|
||||
board updates optimistically and reverts on a failed `PATCH`.
|
||||
4. **Detail modal:** shows the JD + the applied resume; **Edit** opens
|
||||
`/builder?id=<resume_id>`. Tolerates a deleted resume (`resume: null`).
|
||||
5. **Bulk actions:** multi-select cards to move or delete in one request.
|
||||
|
||||
## Data Model
|
||||
|
||||
`Application` (SQLite, `apps/backend/app/models.py`): `application_id` (PK),
|
||||
`job_id`, `resume_id` (the applied/tailored resume), `master_resume_id`
|
||||
(optional base — powers the "shared resume" badge), `status` (7-key enum),
|
||||
`company`, `role`, `applied_at`, `notes`, `position` (per-column order,
|
||||
server-renumbered on PATCH), `created_at`, `updated_at`. `create_application`
|
||||
dedupes on `(job_id, resume_id)` to survive double-submit.
|
||||
|
||||
## API (`prefix=/applications`, mounted under `/api/v1`)
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| GET | `/applications` | All cards grouped by column (all 7 keys present) |
|
||||
| POST | `/applications` | Manual add (creates job + card; best-effort extraction) |
|
||||
| GET | `/applications/{id}` | Card + embedded JD + resume (resume null if deleted) |
|
||||
| PATCH | `/applications/{id}` | Update status/position/notes/company/role/applied_at |
|
||||
| PATCH | `/applications/bulk` | Move many cards to one column |
|
||||
| DELETE | `/applications/{id}` | Delete one card |
|
||||
| POST | `/applications/bulk-delete` | Delete many cards |
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `apps/backend/app/models.py` | `Application` ORM model |
|
||||
| `apps/backend/app/schemas/applications.py` | Pydantic request/response schemas + status enum |
|
||||
| `apps/backend/app/routers/applications.py` | The tracker endpoints |
|
||||
| `apps/backend/app/database.py` | Facade CRUD/bulk/reorder methods |
|
||||
| `apps/backend/app/routers/resumes.py` | `_auto_create_tracker_application` hook (both confirm paths) |
|
||||
| `apps/backend/app/services/improver.py` + `app/prompts/templates.py` | Company/role added to keyword extraction |
|
||||
| `apps/frontend/app/(default)/tracker/page.tsx` | Route |
|
||||
| `apps/frontend/components/tracker/*` | Board, column, card, detail modal, bulk bar, manual-add dialog |
|
||||
| `apps/frontend/components/tracker/reorder.ts` | Pure drag-end resolution (`planMove`) |
|
||||
| `apps/frontend/lib/api/tracker.ts` | Typed API client |
|
||||
|
||||
## Tests
|
||||
|
||||
- Backend: `tests/integration/test_applications_api.py` (CRUD, grouping, detail
|
||||
tolerance, bulk), `tests/integration/test_tracker_autocreate.py` (confirm
|
||||
auto-creates an `applied` card), `tests/unit/test_database.py::TestApplications`.
|
||||
- Frontend: `tests/tracker-reorder.test.ts` (`planMove` within/cross-column +
|
||||
empty-column drop), `tests/api-tracker.test.ts` (client payloads/URLs).
|
||||
@@ -0,0 +1,68 @@
|
||||
# Custom Sections System
|
||||
|
||||
> **Dynamic resume sections with full customization.**
|
||||
|
||||
## Section Types
|
||||
|
||||
| Type | Description | Example Uses |
|
||||
|------|-------------|--------------|
|
||||
| `personalInfo` | Special type for header (always first) | Name, contact details |
|
||||
| `text` | Single text block | Summary, objective, statement |
|
||||
| `itemList` | Array of items with title, subtitle, years, description | Experience, projects, publications |
|
||||
| `stringList` | Simple array of strings | Skills, languages, hobbies |
|
||||
|
||||
## Section Features
|
||||
|
||||
- **Rename sections**: Change display names (e.g., "Education" → "Academic Background")
|
||||
- **Reorder sections**: Up/down buttons to change section order
|
||||
- **Hide sections**: Toggle visibility (hidden sections still editable, just not in PDF)
|
||||
- **Delete sections**: Remove custom sections entirely
|
||||
- **Add custom sections**: Create new sections with any name and type
|
||||
|
||||
## Section Controls (UI)
|
||||
|
||||
Each section (except Personal Info) has these controls in the header:
|
||||
|
||||
| Control | Icon | Function |
|
||||
|---------|------|----------|
|
||||
| Visibility | 👁 Eye / EyeOff | Toggle show/hide in PDF preview |
|
||||
| Move Up | ⬆ ChevronUp | Move section earlier in order |
|
||||
| Move Down | ⬇ ChevronDown | Move section later in order |
|
||||
| Rename | ✏️ Pencil | Edit section display name |
|
||||
| Delete | 🗑 Trash | Hide (default) or delete (custom) |
|
||||
|
||||
## Hidden Section Behavior
|
||||
|
||||
- Hidden sections appear in the form with:
|
||||
- Dashed border and 60% opacity
|
||||
- "Hidden from PDF" badge (amber)
|
||||
- Hidden sections are still editable
|
||||
- Only PDF/preview hides them (uses `getSortedSections` which filters by visibility)
|
||||
- Form shows all sections (uses `getAllSections`)
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `apps/backend/app/schemas/models.py` | `SectionType`, `SectionMeta`, `CustomSection` models |
|
||||
| `apps/frontend/lib/utils/section-helpers.ts` | Section management utilities |
|
||||
| `apps/frontend/components/builder/section-header.tsx` | Section controls UI |
|
||||
| `apps/frontend/components/builder/add-section-dialog.tsx` | Add custom section dialog |
|
||||
| `apps/frontend/components/builder/resume-form.tsx` | Dynamic form rendering |
|
||||
| `apps/frontend/components/resume/dynamic-resume-section.tsx` | Renders custom sections in templates |
|
||||
|
||||
## Data Structure
|
||||
|
||||
```typescript
|
||||
interface ResumeData {
|
||||
// ... existing fields (personalInfo, summary, etc.)
|
||||
sectionMeta?: SectionMeta[]; // Section order, names, visibility
|
||||
customSections?: Record<string, CustomSection>; // Custom section data
|
||||
}
|
||||
```
|
||||
|
||||
## Migration
|
||||
|
||||
Existing resumes are automatically migrated via lazy normalization - default section metadata is added when a resume is fetched if `sectionMeta` is missing.
|
||||
|
||||
> **Important**: The `normalize_resume_data()` function uses `copy.deepcopy(DEFAULT_SECTION_META)` to avoid shared mutable reference bugs. Always use deep copies when assigning default mutable values.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Resume Enrichment Feature
|
||||
|
||||
> **AI-powered resume improvement with targeted questions.**
|
||||
|
||||
## Overview
|
||||
|
||||
The enrichment feature helps users improve their master resume with more detailed content by:
|
||||
|
||||
1. Analyzing the resume for weak/generic descriptions
|
||||
2. Asking targeted questions about their experience
|
||||
3. Generating additional bullet points based on answers
|
||||
|
||||
## How It Works
|
||||
|
||||
1. User clicks "Enhance Resume" on the master resume page
|
||||
2. AI analyzes the resume and identifies weak/generic descriptions
|
||||
3. User answers targeted questions (max 6 questions total) about their experience
|
||||
4. AI generates additional bullet points based on user answers
|
||||
5. New bullets are **added** to existing content (not replaced)
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
- **Maximum 6 questions**: To avoid overwhelming users, the AI generates at most 6 questions across all items
|
||||
- **Additive enhancement**: Original bullet points are preserved; new enhanced bullets are appended after them
|
||||
- **Question prioritization**: AI prioritizes the most impactful questions that will yield the best improvements
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `apps/backend/app/prompts/enrichment.py` | AI prompts for analysis and enhancement |
|
||||
| `apps/backend/app/routers/enrichment.py` | API endpoints for enrichment workflow |
|
||||
| `apps/frontend/hooks/use-enrichment-wizard.ts` | React state management for wizard flow |
|
||||
| `apps/frontend/components/enrichment/*.tsx` | UI components for enrichment modal |
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Endpoint | Description |
|
||||
|----------|-------------|
|
||||
| `POST /enrichment/analyze/{resume_id}` | Analyze resume and generate questions |
|
||||
| `POST /enrichment/enhance` | Generate enhanced descriptions from answers |
|
||||
| `POST /enrichment/apply/{resume_id}` | Apply enhancements to resume |
|
||||
@@ -0,0 +1,66 @@
|
||||
# i18n Preparation Guide
|
||||
|
||||
> Plan for internationalizing Resume Matcher.
|
||||
|
||||
## Current State
|
||||
|
||||
- UI uses `next-intl` with locale files in `messages/`
|
||||
- Content language preference stored via `LanguageProvider`
|
||||
- Supported: en, es, zh, ja
|
||||
|
||||
## Translation File Location
|
||||
|
||||
```
|
||||
apps/frontend/messages/
|
||||
├── en.json
|
||||
├── es.json
|
||||
├── zh.json
|
||||
└── ja.json
|
||||
```
|
||||
|
||||
## Adding New Locale
|
||||
|
||||
1. Create `messages/{locale}.json`
|
||||
2. Add locale to `i18n/config.ts`:
|
||||
```typescript
|
||||
export const locales = ['en', 'es', 'zh', 'ja', 'de'] as const;
|
||||
```
|
||||
3. Add to `SUPPORTED_LANGUAGES` in backend `config.py`
|
||||
|
||||
## Translation Keys
|
||||
|
||||
```json
|
||||
{
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"masterResume": "Master Resume"
|
||||
},
|
||||
"builder": {
|
||||
"save": "Save",
|
||||
"download": "Download PDF"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage in Components
|
||||
|
||||
```tsx
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export function MyComponent() {
|
||||
const t = useTranslations('dashboard');
|
||||
return <h1>{t('title')}</h1>;
|
||||
}
|
||||
```
|
||||
|
||||
## Content Language vs UI Language
|
||||
|
||||
- **UI Language:** Controlled by `next-intl`, affects interface text
|
||||
- **Content Language:** Controlled by `LanguageProvider`, affects LLM-generated content (cover letters, tailored resumes)
|
||||
|
||||
## Backend i18n (Future)
|
||||
|
||||
Currently prompts are English-only. To support multiple languages:
|
||||
1. Create `app/i18n/locales/{lang}.json`
|
||||
2. Add language parameter to prompt templates
|
||||
3. Pass `Accept-Language` header from frontend
|
||||
@@ -0,0 +1,64 @@
|
||||
# Internationalization (i18n)
|
||||
|
||||
> **Multi-language UI and content generation.**
|
||||
|
||||
## Supported Languages
|
||||
|
||||
| Code | Language | Native Name | Flag | Message File |
|
||||
|------|----------|-------------|------|--------------|
|
||||
| `en` | English | English | 🇺🇸 | `messages/en.json` |
|
||||
| `es` | Spanish | Español | 🇪🇸 | `messages/es.json` |
|
||||
| `zh` | Chinese (Simplified) | 中文 | 🇨🇳 | `messages/zh.json` |
|
||||
| `ja` | Japanese | 日本語 | 🇯🇵 | `messages/ja.json` |
|
||||
| `pt` | Portuguese (Brazilian) | Português | 🇧🇷 | `messages/pt-BR.json` |
|
||||
|
||||
> Source of truth: `apps/frontend/i18n/config.ts` (`locales`, `localeNames`, `localeFlags`). Note `pt` loads from `messages/pt-BR.json` (imported as `pt` in `apps/frontend/lib/i18n/messages.ts`).
|
||||
|
||||
## Two Language Settings
|
||||
|
||||
1. **UI Language** - Interface text (buttons, labels, navigation)
|
||||
2. **Content Language** - LLM-generated content (resumes, cover letters)
|
||||
|
||||
Both are configured independently in the Settings page.
|
||||
|
||||
## How It Works
|
||||
|
||||
- **UI translations**: Simple JSON import approach, no external dependencies
|
||||
- **Content generation**: Backend receives language, passes to LLM prompts via `{output_language}`
|
||||
- **Existing content** in database remains in original language
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `apps/frontend/messages/*.json` | UI translation files (`en`, `es`, `zh`, `ja`, `pt-BR`) |
|
||||
| `apps/frontend/lib/i18n/translations.ts` | `useTranslations` hook |
|
||||
| `apps/frontend/lib/context/language-context.tsx` | LanguageProvider (UI + content) |
|
||||
| `apps/backend/app/prompts/templates.py` | LLM prompts with `{output_language}` |
|
||||
|
||||
## Using Translations
|
||||
|
||||
```typescript
|
||||
import { useTranslations } from '@/lib/i18n';
|
||||
|
||||
const { t } = useTranslations();
|
||||
<button>{t('common.save')}</button>
|
||||
```
|
||||
|
||||
## Storage
|
||||
|
||||
| Key | Purpose |
|
||||
|-----|---------|
|
||||
| `resume_matcher_ui_language` | UI language (localStorage only) |
|
||||
| `resume_matcher_content_language` | Content language (localStorage + backend) |
|
||||
|
||||
## Adding a New Language
|
||||
|
||||
1. Create `apps/frontend/messages/{code}.json` with all translations
|
||||
2. Add locale to `apps/frontend/i18n/config.ts`
|
||||
3. Add language name to `apps/backend/app/prompts/templates.py`
|
||||
4. Update `SUPPORTED_LANGUAGES` in backend config router
|
||||
|
||||
## Related Docs
|
||||
|
||||
- [i18n-preparation.md](i18n-preparation.md) - Detailed extraction plan
|
||||
@@ -0,0 +1,39 @@
|
||||
# JD Match Feature
|
||||
|
||||
> **Shows how well a tailored resume matches the original job description.**
|
||||
|
||||
## Overview
|
||||
|
||||
The Resume Builder includes a "JD Match" tab that shows how well a tailored resume matches the original job description.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. User tailors a resume against a job description
|
||||
2. Opens the tailored resume in the Builder
|
||||
3. "JD MATCH" tab appears (only for tailored resumes)
|
||||
4. Shows side-by-side comparison:
|
||||
- **Left panel**: Original job description (read-only)
|
||||
- **Right panel**: Resume with matching keywords highlighted in yellow
|
||||
|
||||
## Features
|
||||
|
||||
- **Keyword extraction**: Extracts significant keywords from JD (filters common stop words)
|
||||
- **Case-insensitive matching**: Matches keywords regardless of case
|
||||
- **Match statistics**: Shows total keywords, matches found, and match percentage
|
||||
- **Color-coded percentage**: Green (≥50%), yellow (≥30%), red (<30%)
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `apps/frontend/lib/utils/keyword-matcher.ts` | Keyword extraction and matching utilities |
|
||||
| `apps/frontend/components/builder/jd-comparison-view.tsx` | Main split-view component |
|
||||
| `apps/frontend/components/builder/jd-display.tsx` | Read-only JD display |
|
||||
| `apps/frontend/components/builder/highlighted-resume-view.tsx` | Resume with keyword highlighting |
|
||||
| `apps/backend/app/routers/resumes.py` | `GET /{resume_id}/job-description` endpoint |
|
||||
|
||||
## API Endpoint
|
||||
|
||||
| Endpoint | Description |
|
||||
|----------|-------------|
|
||||
| `GET /resumes/{resume_id}/job-description` | Fetch JD used to tailor a resume |
|
||||
@@ -0,0 +1,71 @@
|
||||
# Resume Template Settings
|
||||
|
||||
> **Template types and extensive formatting controls.**
|
||||
|
||||
## Template Types
|
||||
|
||||
| Template | Description |
|
||||
|----------|-------------|
|
||||
| `swiss-single` | Traditional single-column layout with maximum content density |
|
||||
| `swiss-two-column` | 65%/35% split with experience in main column, skills in sidebar |
|
||||
| `modern` | Single-column with colorful accent headers and customizable theme colors |
|
||||
| `modern-two-column` | Two-column layout combining modern accents with space-efficient design |
|
||||
| `latex` | Classic serif single-column with Title-Case ruled headers and company-first entries (LaTeX-style). Single-typeface — driven by the Header Font control |
|
||||
| `clean` | Minimal sans single-column with large understated gray UPPERCASE headers and single-line entries. Single-typeface — driven by the Body Font control |
|
||||
| `vivid` | Colorful two-column (Awesome-CV lineage): two-tone accent name, monospace contact with circular icons, accent small-caps headers, accent arrow bullets. Supports the Accent Color control |
|
||||
|
||||
## Formatting Controls
|
||||
|
||||
| Control | Range | Default | Effect |
|
||||
|---------|-------|---------|--------|
|
||||
| Margins | 5-25mm | 8mm | Page margins |
|
||||
| Section Spacing | 1-5 | 3 | Gap between major sections |
|
||||
| Item Spacing | 1-5 | 2 | Gap between items within sections |
|
||||
| Line Height | 1-5 | 3 | Text line height |
|
||||
| Base Font Size | 1-5 | 3 | Overall text scale (11-16px) |
|
||||
| Header Scale | 1-5 | 3 | Name/section header size multiplier |
|
||||
| Header Font | serif/sans-serif/mono | serif | Font family for headers |
|
||||
| Body Font | serif/sans-serif/mono | sans-serif | Font family for body text |
|
||||
| Compact Mode | boolean | false | Apply 0.6x spacing multiplier (spacing only; margins unchanged) |
|
||||
| Contact Icons | boolean | false | Show icons next to contact info |
|
||||
| Accent Color | blue/green/orange/red | blue | Accent color for color templates (modern, modern-two-column, vivid) |
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `apps/frontend/lib/types/template-settings.ts` | Type definitions, defaults, CSS variable mapping |
|
||||
| `apps/frontend/components/resume/styles/_tokens.css` | Global design tokens (colors) |
|
||||
| `apps/frontend/components/resume/styles/_base.module.css` | Shared typography and layout styles |
|
||||
| `apps/frontend/components/builder/formatting-controls.tsx` | UI controls for template settings |
|
||||
| `apps/frontend/components/resume/resume-single-column.tsx` | Single column template |
|
||||
| `apps/frontend/components/resume/resume-two-column.tsx` | Two column template |
|
||||
| `apps/frontend/components/resume/resume-modern.tsx` | Modern single column template |
|
||||
| `apps/frontend/components/resume/resume-modern-two-column.tsx` | Modern two column template |
|
||||
| `apps/backend/app/routers/resumes.py` | PDF generation endpoint with accentColor support |
|
||||
|
||||
## CSS Variables
|
||||
|
||||
Templates use CSS custom properties for styling:
|
||||
|
||||
- `--section-gap`, `--item-gap`, `--line-height` - Spacing
|
||||
- `--font-size-base`, `--header-scale`, `--section-header-scale` - Typography
|
||||
- `--header-font` - Header font family
|
||||
- `--body-font` - Body text font family
|
||||
- `--margin-top/bottom/left/right` - Page margins
|
||||
- `--accent-primary`, `--accent-light` - Accent colors for Modern templates
|
||||
|
||||
> **Note**: Templates should use the styles exported from `apps/frontend/components/resume/styles/_base.module.css` (e.g., `baseStyles['resume-section']`, `baseStyles['resume-item-subtitle']`) to ensure all spacing and typography respond to template settings.
|
||||
|
||||
### Typography Classes
|
||||
|
||||
The base stylesheet includes specialized classes for improved subtitle visibility:
|
||||
|
||||
| Class | Font Size | Weight | Usage |
|
||||
|-------|-----------|--------|-------|
|
||||
| `resume-item-subtitle` | 0.95× base | 600 | Company names, education degrees, project roles |
|
||||
| `resume-item-subtitle-sm` | 0.88× base | 600 | Same fields in compact two-column layouts |
|
||||
|
||||
These classes provide **better visibility** than the generic `resume-meta` class (0.82× base, weight 400), making subtitles 13-16% larger and semi-bold.
|
||||
|
||||
Formatting controls include an "Effective Output" summary that reflects compact-mode adjustments for spacing/line-height.
|
||||
Reference in New Issue
Block a user