diff --git a/job-tracker-ui/src/cv-builder-deep-link.test.tsx b/job-tracker-ui/src/cv-builder-deep-link.test.tsx
index aa6b954..fb80d86 100644
--- a/job-tracker-ui/src/cv-builder-deep-link.test.tsx
+++ b/job-tracker-ui/src/cv-builder-deep-link.test.tsx
@@ -184,6 +184,7 @@ test('choosing a template restores its own structure instead of inheriting the p
sidebarWidthMm: 72,
sidebarSections: ['skills'],
accentColor: '#123456',
+ pageMarginMm: 9,
},
};
mockedApi.get.mockImplementation((url: string) => {
@@ -212,7 +213,8 @@ test('choosing a template restores its own structure instead of inheriting the p
headingStyle: null,
sidebarWidthMm: null,
sidebarSections: null,
- accentColor: '#123456',
+ accentColor: null,
+ pageMarginMm: null,
}),
})));
});
diff --git a/job-tracker-ui/src/cvBuilder.test.ts b/job-tracker-ui/src/cvBuilder.test.ts
index 908e377..1e71afa 100644
--- a/job-tracker-ui/src/cvBuilder.test.ts
+++ b/job-tracker-ui/src/cvBuilder.test.ts
@@ -63,12 +63,12 @@ describe("CV language compatibility", () => {
test("fits the physical page to the available preview width", () => {
expect(getCvPreviewFitZoom(500, 800)).toBeCloseTo(0.595);
expect(getCvPreviewFitZoom(1200, 800)).toBe(1);
- expect(getCvPreviewFitZoom(120, 800)).toBe(0.32);
+ expect(getCvPreviewFitZoom(120, 800, 24, 0.2)).toBe(0.2);
});
});
describe("CV template selection", () => {
- test("restores the selected template structure without discarding user content or design choices", () => {
+ test("restores the selected template visual defaults without discarding user content", () => {
const settings = {
themeId: "minimal",
layout: "single" as const,
@@ -77,6 +77,9 @@ describe("CV template selection", () => {
sidebarWidthMm: 70,
sidebarSections: ["skills"],
accentColor: "#123456",
+ headingFont: "Georgia, 'Times New Roman', serif",
+ pageMarginMm: 9,
+ skillsStyle: "text" as const,
showPhoto: false,
showPageNumbers: false,
showIcons: true,
@@ -92,7 +95,10 @@ describe("CV template selection", () => {
headingStyle: null,
sidebarWidthMm: null,
sidebarSections: null,
- accentColor: "#123456",
+ accentColor: null,
+ headingFont: null,
+ pageMarginMm: null,
+ skillsStyle: null,
sections: [{ key: "experience", title: "Career" }],
});
});
diff --git a/job-tracker-ui/src/cvBuilder.ts b/job-tracker-ui/src/cvBuilder.ts
index 2209ac3..f8d22da 100644
--- a/job-tracker-ui/src/cvBuilder.ts
+++ b/job-tracker-ui/src/cvBuilder.ts
@@ -182,16 +182,30 @@ export function getCvPreviewFitZoom(containerWidthPx: number, pageWidthPx: numbe
return Math.min(1, Math.max(minZoom, usableWidth / pageWidthPx));
}
-// Selecting a template is a structural choice. Clear only the presentation overrides that can
-// flatten every template into the previous template's layout; keep content, colour, typography,
-// spacing and document settings intact.
+// Selecting a template restores that template's complete visual identity. CV content and document
+// metadata remain untouched; users can customise the newly selected template afterwards.
export function applyCvTheme(settings: CvVariantSettings, themeId: string): CvVariantSettings {
return {
...settings,
themeId,
+ accentColor: null,
+ headingFont: null,
+ bodyFont: null,
+ density: null,
+ textColor: null,
+ mutedColor: null,
+ headingColor: null,
+ backgroundColor: null,
+ baseFontSizePt: null,
+ headingSizePt: null,
+ lineHeight: null,
+ pageMarginMm: null,
+ sectionGapMm: null,
+ entryGapMm: null,
layout: null,
headerStyle: null,
headingStyle: null,
+ skillsStyle: null,
sidebarWidthMm: null,
sidebarSections: null,
};
diff --git a/job-tracker-ui/src/i18n/translations.ts b/job-tracker-ui/src/i18n/translations.ts
index dd6f27b..4a8329f 100644
--- a/job-tracker-ui/src/i18n/translations.ts
+++ b/job-tracker-ui/src/i18n/translations.ts
@@ -506,6 +506,8 @@ export const translations = {
cvEditorUpdating: "Updating…",
cvEditorPublic: "Public",
cvEditorPrivate: "Private",
+ cvEditorShare: "Share",
+ cvEditorStopSharing: "Stop sharing",
cvEditorCopyLink: "Copy link",
cvEditorExporting: "Exporting…",
cvEditorDownloadPdf: "Download PDF",
@@ -2926,6 +2928,8 @@ export const translations = {
cvEditorUpdating: "Oppdaterer…",
cvEditorPublic: "Offentlig",
cvEditorPrivate: "Privat",
+ cvEditorShare: "Del",
+ cvEditorStopSharing: "Stopp deling",
cvEditorCopyLink: "Kopier lenke",
cvEditorExporting: "Eksporterer…",
cvEditorDownloadPdf: "Last ned PDF",
diff --git a/job-tracker-ui/src/views/CvBuilderEditor.tsx b/job-tracker-ui/src/views/CvBuilderEditor.tsx
index 8e163b1..2bd9d11 100644
--- a/job-tracker-ui/src/views/CvBuilderEditor.tsx
+++ b/job-tracker-ui/src/views/CvBuilderEditor.tsx
@@ -3,7 +3,7 @@ import { Link as RouterLink, useBlocker, useNavigate, useParams } from "react-ro
import {
Alert, Box, Button, Chip, Collapse, Dialog, DialogActions, DialogContent, DialogTitle, Divider, IconButton,
- MenuItem, Paper, Skeleton, Slider, Stack, Tab, Tabs, TextField, Tooltip, Typography,
+ MenuItem, Paper, Skeleton, Stack, Tab, Tabs, TextField, Tooltip, Typography,
} from "@mui/material";
import useMediaQuery from "@mui/material/useMediaQuery";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
@@ -18,8 +18,6 @@ import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
-import ZoomInIcon from "@mui/icons-material/ZoomIn";
-import ZoomOutIcon from "@mui/icons-material/ZoomOut";
import UndoIcon from "@mui/icons-material/Undo";
import RedoIcon from "@mui/icons-material/Redo";
import AutoFixHighIcon from "@mui/icons-material/AutoFixHigh";
@@ -42,14 +40,14 @@ import {
import { useDialogActions } from "../dialogs";
import { useI18n } from "../i18n/I18nProvider";
-const MIN_PREVIEW_ZOOM = 0.32;
+const MIN_PREVIEW_ZOOM = 0.2;
type SaveState = "idle" | "unsaved" | "saving" | "saved" | "error";
function previewDocumentHtml(html: string, pageHeightPx: number, pageMarginMm: number, pageIndex = 0): string {
const height = Math.max(1, Math.round(pageHeightPx * 100) / 100);
const margin = Math.max(0, Math.round(pageMarginMm * (96 / 25.4) * 100) / 100);
const offset = Math.max(0, pageIndex) * height;
- const previewCss = ``;
+ const previewCss = ``;
// Screen media does not apply Chromium's paged-media fragmentation. Reproduce the important
// keep-together decisions before each preview frame crops the selected physical page.
const previewScript = ``;
@@ -75,13 +73,11 @@ export default function CvBuilderEditor() {
const [tab, setTab] = useState(0);
const [html, setHtml] = useState("");
const [zoom, setZoom] = useState(0.62);
- const [autoFitPreview, setAutoFitPreview] = useState(true);
const [previewing, setPreviewing] = useState(false);
const [previewError, setPreviewError] = useState(false);
const [previewRevision, setPreviewRevision] = useState(0);
const [previewOverflow, setPreviewOverflow] = useState(false);
const [pages, setPages] = useState(1);
- const [page, setPage] = useState(1);
const [saveState, setSaveState] = useState("idle");
const [exporting, setExporting] = useState(false);
const [publishing, setPublishing] = useState(false);
@@ -381,7 +377,6 @@ export default function CvBuilderEditor() {
);
const pageCount = getCvPageCount(h, pageMetrics.heightPx);
setPages(pageCount);
- setPage((current) => Math.min(current, pageCount));
const viewportWidth = doc?.documentElement?.clientWidth ?? pageMetrics.widthPx;
const contentWidth = Math.max(doc?.body?.scrollWidth ?? 0, doc?.documentElement?.scrollWidth ?? 0);
setPreviewOverflow(contentWidth > viewportWidth + 2);
@@ -394,19 +389,11 @@ export default function CvBuilderEditor() {
else measure();
};
- const goToPage = (p: number) => {
- const clamped = Math.min(Math.max(1, p), pages);
- setPage(clamped);
- const target = scrollRef.current?.querySelector(`[data-cv-page="${clamped}"]`);
- if (target && scrollRef.current) scrollRef.current.scrollTo({ top: Math.max(0, target.offsetTop - 12), behavior: "smooth" });
- };
-
const fitPreview = useCallback(() => {
- setZoom(getCvPreviewFitZoom(scrollRef.current?.clientWidth ?? pageMetrics.widthPx, pageMetrics.widthPx, 24, MIN_PREVIEW_ZOOM));
+ setZoom(getCvPreviewFitZoom(scrollRef.current?.clientWidth ?? pageMetrics.widthPx, pageMetrics.widthPx, 16, MIN_PREVIEW_ZOOM));
}, [pageMetrics.widthPx]);
useEffect(() => {
- if (!autoFitPreview) return;
const container = scrollRef.current;
if (!container) return;
const resize = () => fitPreview();
@@ -418,7 +405,7 @@ export default function CvBuilderEditor() {
window.removeEventListener("resize", resize);
observer?.disconnect();
};
- }, [autoFitPreview, compactEditor, fitPreview, mobilePane]);
+ }, [compactEditor, fitPreview, mobilePane]);
if (loadError) {
return (
@@ -453,7 +440,7 @@ export default function CvBuilderEditor() {
} disabled={!name.trim()} onClick={() => void duplicateVariant()}>{t("cvEditorDuplicate")}
} disabled={publishing || exporting || !name.trim()} onClick={togglePublic}>
- {publishing ? t("cvEditorUpdating") : isPublic ? t("cvEditorPublic") : t("cvEditorPrivate")}
+ {publishing ? t("cvEditorUpdating") : isPublic ? t("cvEditorStopSharing") : t("cvEditorShare")}
{isPublic && } onClick={copyPublicLink}>{t("cvEditorCopyLink")}}
} disabled={exporting || publishing || !name.trim()} onClick={exportPdf}>{exporting ? t("cvEditorExporting") : t("cvEditorDownloadPdf")}
@@ -478,35 +465,23 @@ export default function CvBuilderEditor() {
{tab === 4 && }
-
-
+
+
{t("cvEditorLivePreview")}
{previewing && }
{previewError && }
{previewError && }
- {pages >= 3 && }
-
-
- {t("cvEditorPageOf", { page, pages })}
-
-
-
- { setAutoFitPreview(false); setZoom((z) => Math.max(MIN_PREVIEW_ZOOM, +(z - 0.1).toFixed(2))); }}>
- { setAutoFitPreview(false); setZoom(v as number); }} sx={{ width: 90 }} aria-label={t("cvEditorZoom")} />
- { setAutoFitPreview(false); setZoom((z) => Math.min(1, +(z + 0.1).toFixed(2))); }}>
- {Math.round(zoom * 100)}%
-
+ {t("cvEditorPageCount", { count: pages })}
{previewOverflow && {t("cvEditorOverflowWarning")}}
{pages >= 3 && {t("cvEditorLongDocument", { pages })}}
-
-
+
+
{Array.from({ length: pages }).map((_, index) => (
- {t("cvEditorPage", { page: index + 1 })}
-
+
-
+
);