diff --git a/README.md b/README.md
index b52bd7dd..466b6a28 100644
--- a/README.md
+++ b/README.md
@@ -81,7 +81,7 @@ GraphCompose uses PDFBox under the hood as the rendering backend — the com
| You want to… | Surface | Entry point |
|---|---|---|
| Generate a one-off PDF programmatically | DSL | `GraphCompose.document(...).pageFlow(...)` — see [Hello world](#hello-world) below |
-| Generate a CV / cover letter / invoice / proposal from data | Templates v2 | `ModernProfessional.create(BusinessTheme.modern()).compose(session, spec)` — see [templates v2](./docs/templates/v1-classic/README.md) |
+| Generate a CV / cover letter from data | Layered templates | `ModernProfessional.create().compose(session, cvDocument)` — see [layered templates](./docs/templates/v2-layered/README.md) |
| Add a custom visual primitive | Engine extension | `NodeDefinition` + `PdfFragmentRenderHandler` — see [extension guide](./docs/contributing/extension-guide.md) |
| Regression-test generated layouts | Layout snapshots | `DocumentSession#layoutSnapshot()` — see [snapshot testing](./docs/operations/layout-snapshot-testing.md) |
@@ -146,7 +146,7 @@ For a Spring Boot `@RestController` streaming the PDF straight to the response,
## What's in v1.6 — "expressive"
-- **Templates v2** — 14 CV and 14 paired cover-letter presets, theme-driven via `BusinessTheme`, one-liner `create(theme)` factories. Inline markdown, slot-based multi-column layouts. See [`docs/templates/v1-classic/README.md`](./docs/templates/v1-classic/README.md).
+- **Layered templates** — 14 CV and 14 paired cover-letter presets on the layered `cv.v2` / `coverletter.v2` architecture (data β theme β components β widgets β presets), one-liner `create()` factories over a typed `CvDocument` / `CoverLetterDocument`. Inline markdown, multi-column layouts. The going-forward standard for new template families. See [`docs/templates/v2-layered/README.md`](./docs/templates/v2-layered/README.md). (The earlier `BusinessTheme`-based preset surface is now deprecated.)
- **Composed primitives** — `ListBuilder.addItem(label, Consumer)` (nested lists), `DocumentTableCell.node(...)` (any node inside a cell), `CanvasLayerNode` (pixel-precise free-canvas placement).
- **Architecture hardening** — `@Internal` API stability marker, public `PdfFragmentRenderHandler` SPI, `DocumentRenderingException` on the convenience render path, documented thread-safety contract.
@@ -193,7 +193,7 @@ document.pageFlow().addCanvas(523, 360, canvas -> canvas
### Templates
- π [**Templates β v2 layered architecture**](./docs/templates/v2-layered/README.md) — the canonical going-forward pattern for new template families (CV v2 is the reference implementation). Personas: [quickstart](./docs/templates/v2-layered/quickstart.md) Β· [using templates](./docs/templates/v2-layered/using-templates.md) Β· [authoring presets](./docs/templates/v2-layered/authoring-presets.md) Β· [contributing a new family](./docs/templates/v2-layered/contributor-guide.md).
-- [Templates v1-classic landing](./docs/templates/v1-classic/README.md) — CV / cover-letter / invoice / proposal preset library (v1.6 surface, still shipped). Cheat sheet: [authoring](./docs/templates/v1-classic/authoring.md).
+- [Templates v1-classic landing](./docs/templates/v1-classic/README.md) — the older `BusinessTheme` / `CvSpec` CV / cover-letter / invoice / proposal preset library (**deprecated** β CV + cover letter are superseded by [v2-layered](./docs/templates/v2-layered/README.md); invoice / proposal / schedule are not yet ported). Cheat sheet: [authoring](./docs/templates/v1-classic/authoring.md).
### Architecture & operations
- [Architecture overview](./docs/architecture/overview.md) Β· [Lifecycle](./docs/architecture/lifecycle.md) Β· [Production rendering](./docs/operations/production-rendering.md) Β· [Layout snapshot testing](./docs/operations/layout-snapshot-testing.md)
diff --git a/assets/readme/examples/cv-classic-serif.pdf b/assets/readme/examples/cv-classic-serif.pdf
index 8801d73c..c0ec3d9f 100644
Binary files a/assets/readme/examples/cv-classic-serif.pdf and b/assets/readme/examples/cv-classic-serif.pdf differ
diff --git a/assets/readme/examples/cv-compact-mono.pdf b/assets/readme/examples/cv-compact-mono.pdf
index 01058c54..1ecfdc02 100644
Binary files a/assets/readme/examples/cv-compact-mono.pdf and b/assets/readme/examples/cv-compact-mono.pdf differ
diff --git a/assets/readme/examples/cv-engineering-resume.pdf b/assets/readme/examples/cv-engineering-resume.pdf
new file mode 100644
index 00000000..1b2312a6
Binary files /dev/null and b/assets/readme/examples/cv-engineering-resume.pdf differ
diff --git a/assets/readme/examples/cv-modern-professional.pdf b/assets/readme/examples/cv-modern-professional.pdf
index b8854461..f75d5989 100644
Binary files a/assets/readme/examples/cv-modern-professional.pdf and b/assets/readme/examples/cv-modern-professional.pdf differ
diff --git a/assets/readme/examples/cv-nordic-clean.pdf b/assets/readme/examples/cv-nordic-clean.pdf
index 88dfbeb9..c99d9503 100644
Binary files a/assets/readme/examples/cv-nordic-clean.pdf and b/assets/readme/examples/cv-nordic-clean.pdf differ
diff --git a/assets/readme/examples/cv-panel.pdf b/assets/readme/examples/cv-panel.pdf
new file mode 100644
index 00000000..97dea47d
Binary files /dev/null and b/assets/readme/examples/cv-panel.pdf differ
diff --git a/assets/readme/examples/cv-product-leader.pdf b/assets/readme/examples/cv-product-leader.pdf
deleted file mode 100644
index 23901d0e..00000000
Binary files a/assets/readme/examples/cv-product-leader.pdf and /dev/null differ
diff --git a/assets/readme/examples/cv-tech-lead.pdf b/assets/readme/examples/cv-tech-lead.pdf
deleted file mode 100644
index 95ca00da..00000000
Binary files a/assets/readme/examples/cv-tech-lead.pdf and /dev/null differ
diff --git a/assets/readme/examples/cv-timeline-minimal.pdf b/assets/readme/examples/cv-timeline-minimal.pdf
index 010981ae..ce15da94 100644
Binary files a/assets/readme/examples/cv-timeline-minimal.pdf and b/assets/readme/examples/cv-timeline-minimal.pdf differ
diff --git a/docs/README.md b/docs/README.md
index 539c3e0e..304c3984 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -74,10 +74,11 @@ it does.
- **[adr/0002-theme-unification.md](adr/0002-theme-unification.md)** β single canonical theme model.
- **[adr/0003-api-stability-and-internal-marker.md](adr/0003-api-stability-and-internal-marker.md)** β public-API guarantees + `@Internal` marker.
- **[adr/0004-pdf-handler-spi-extension.md](adr/0004-pdf-handler-spi-extension.md)** β PDF render handler SPI.
-- **[adr/0011-templates-v2-architecture.md](adr/0011-templates-v2-architecture.md)** β the v1.6 templates restructure (spec/builder/presets/themes).
+- **[adr/0011-templates-v2-architecture.md](adr/0011-templates-v2-architecture.md)** β the v1.6 templates restructure (spec/builder/presets/themes); **superseded** for CV + cover letter by [0015](adr/0015-layered-template-architecture.md).
- **[adr/0012-nested-list-evolution.md](adr/0012-nested-list-evolution.md)** β nested list rendering evolution.
- **[adr/0013-composed-table-cell.md](adr/0013-composed-table-cell.md)** β composed table cell model.
- **[adr/0014-controlled-absolute-placement.md](adr/0014-controlled-absolute-placement.md)** β controlled absolute placement strategy.
+- **[adr/0015-layered-template-architecture.md](adr/0015-layered-template-architecture.md)** β the layered `cv.v2` / `coverletter.v2` authoring model (current standard); supersedes the preset/builder portion of 0011.
> **ADR numbering gap (0005β0010)** is intentional β those numbers
> were reserved during a v1.5 restructure that landed under ADR 0011
diff --git a/docs/adr/0015-layered-template-architecture.md b/docs/adr/0015-layered-template-architecture.md
new file mode 100644
index 00000000..49bb92ef
--- /dev/null
+++ b/docs/adr/0015-layered-template-architecture.md
@@ -0,0 +1,80 @@
+# ADR 0015 β Layered template architecture (cv.v2 / coverletter.v2)
+
+- **Status:** Accepted
+- **Date:** 2026-05-28
+- **Authors:** Artem Demchyshyn
+
+## Context
+
+ADR 0011 reorganised templates into a per-domain "Templates v2" surface
+(`templates/cv/{presets,builder,spec,layouts}`, `templates/coverletter/β¦`)
+built on `CvSpec` / `CoverLetterSpec` data records, `BusinessTheme`, and
+flat copy-and-tweak preset classes. That removed the v1.5 mess (one
+600β700-line composer per template), but two limits remained:
+
+- **Preset classes still re-implemented header / contact / section
+ rendering.** Each preset carried its own `addHeader`, contact row, and
+ body dispatch β the duplication ADR 0011 set out to kill kept creeping
+ back at the preset layer.
+- **Style, layout, and data were not cleanly separated.** A visual
+ re-skin still meant editing preset code; there was no single token
+ source a preset read from.
+
+A refined **layered** architecture was prototyped under `cv/v2`, proved
+out across all 14 CV presets (with a pixel-parity migration from the
+Gen-2 presets), then extended to all 14 cover letters under
+`coverletter/v2` (which reuses the CV theme + components so a CV and its
+paired letter render as a matched set).
+
+## Decision
+
+Adopt the **layered** template architecture as the canonical
+template-authoring pattern. A template family is built in five layers:
+
+1. **data** β typed input records (`CvDocument`, `CoverLetterDocument`,
+ reusing `CvIdentity`); no rendering logic.
+2. **theme** β `CvTheme` = palette + typography + spacing + decoration
+ tokens; the only place colour / font / spacing literals live.
+3. **components** β shared stateless renderers (`SectionDispatcher`,
+ `RichParagraphRenderer`, `MarkdownInline`, `CvTextStyles`,
+ `LetterBody`, β¦) reused across presets and families.
+4. **widgets** β composable visual blocks (`Headline`, `ContactLine`,
+ `Masthead`, `CardWidget`, `SectionHeader`, β¦).
+5. **presets** β thin orchestrators: a `create()` / `create(CvTheme)`
+ factory plus a `compose()` that lays out the page-flow and delegates
+ to components / widgets. No re-implemented parsing, no duplicated
+ headers.
+
+Presets are exposed through the generic `DocumentTemplate` contract.
+`cv.v2` is the reference implementation; `coverletter.v2` is the paired
+family.
+
+The earlier Gen-2 surface
+(`templates/cv/{presets,builder,spec,layouts}` and the equivalent
+`templates/coverletter/{presets,builder,spec,layouts}`) is **deprecated**
+(`@Deprecated(since = "1.7.0", forRemoval = true)`) and scheduled for
+removal in a future major. It keeps compiling and working until then β
+existing callers are not broken.
+
+## Consequences
+
+- **One documented authoring path.** New template families follow
+ `docs/templates/v2-layered/` (quickstart Β· using-templates Β·
+ authoring-presets Β· contributor-guide). A new preset is a thin
+ orchestrator, not a forked composer.
+- **Re-skin without code edits.** A new visual flavour is a new
+ `CvTheme.()` factory; the preset is unchanged.
+- **Naming overlap resolved.** Gen-2 package-info prose previously also
+ called itself "Templates v2", colliding with the `cv.v2` folder name.
+ Deprecating Gen-2 and correcting its package-info removes the
+ ambiguity; "the v2 / layered surface" now unambiguously means
+ `cv.v2` / `coverletter.v2`.
+- **Showcase + examples render the layered surface.** The CV and
+ cover-letter gallery examples and the committed README previews are
+ generated from `cv.v2` / `coverletter.v2`.
+- **Not yet ported:** invoice, proposal, and schedule remain on the
+ Gen-2 / builtins surface (`BusinessTheme`-based) and are **not**
+ deprecated. Porting them to the layered architecture is future work.
+- **Supersedes the preset / builder / spec portion of ADR 0011** while
+ keeping its domain-folder split (cv / coverletter / invoice /
+ proposal / schedule).
diff --git a/docs/roadmaps/migration-v1-5-to-v1-6.md b/docs/roadmaps/migration-v1-5-to-v1-6.md
index f2974e52..e1948aed 100644
--- a/docs/roadmaps/migration-v1-5-to-v1-6.md
+++ b/docs/roadmaps/migration-v1-5-to-v1-6.md
@@ -332,13 +332,13 @@ try (DocumentSession session = GraphCompose.document(out).create()) {
```
The full builder fluent surface and module / block taxonomy are
-documented in [`docs/templates/v1-classic/authoring.md`](template-authoring.md).
-The full gallery of v2 CV / cover-letter renders lives under
-[`assets/readme/examples/`](../assets/readme/examples/) and is
+documented in [`docs/templates/v1-classic/authoring.md`](../templates/v1-classic/authoring.md).
+The full gallery of CV / cover-letter renders lives under
+[`assets/readme/examples/`](../../assets/readme/examples/) and is
regenerable via
-[`examples/CvTemplateGalleryFileExample`](../examples/src/main/java/com/demcha/examples/CvTemplateGalleryFileExample.java)
+[`CvTemplateGalleryFileExample`](../../examples/src/main/java/com/demcha/examples/templates/cv/CvTemplateGalleryFileExample.java)
and
-[`CoverLetterTemplateGalleryFileExample`](../examples/src/main/java/com/demcha/examples/CoverLetterTemplateGalleryFileExample.java).
+[`CoverLetterTemplateGalleryFileExample`](../../examples/src/main/java/com/demcha/examples/templates/coverletter/CoverLetterTemplateGalleryFileExample.java).
### What v2 gives you for free
diff --git a/examples/README.md b/examples/README.md
index 86d8e536..97cd3d4b 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -214,8 +214,8 @@ Generates every v2 CV preset in one orchestrated run β 14 presets
covering single-column, two-column-sidebar, and three-column-magazine
layouts. Use this as the side-by-side catalogue when picking a base
preset for your own CV product. Each preset is a one-liner factory
-(`ModernProfessional.create(theme)`, `NordicClean.create(theme)`,
-β¦); see `templates/cv/presets/` for the full list.
+(`ModernProfessional.create()`, `NordicClean.create()`,
+β¦); see `cv/v2/presets/` for the full list.
| Variant | PDF |
|---|---|
@@ -224,8 +224,8 @@ preset for your own CV product. Each preset is a one-liner factory
| Classic serif | [PDF](../assets/readme/examples/cv-classic-serif.pdf) |
| Compact mono | [PDF](../assets/readme/examples/cv-compact-mono.pdf) |
| Timeline minimal | [PDF](../assets/readme/examples/cv-timeline-minimal.pdf) |
-| Engineering resume (was "Tech lead") | [PDF](../assets/readme/examples/cv-tech-lead.pdf) |
-| Panel (was "Product leader") | [PDF](../assets/readme/examples/cv-product-leader.pdf) |
+| Engineering resume | [PDF](../assets/readme/examples/cv-engineering-resume.pdf) |
+| Panel | [PDF](../assets/readme/examples/cv-panel.pdf) |
| Executive Β· BoxedSections Β· CenteredHeadline Β· BlueBanner Β· EditorialBlue Β· SidebarPortrait Β· MonogramSidebar | run the gallery to render |
[π Full source](src/main/java/com/demcha/examples/templates/cv/CvTemplateGalleryFileExample.java)
@@ -235,9 +235,9 @@ preset for your own CV product. Each preset is a one-liner factory
Generates all 14 paired v2 cover-letter presets in one run β one
letter style per CV preset so a candidate's CV and cover letter
share the same visual language end-to-end. Each preset is a
-one-liner factory (`ModernProfessionalLetter.create(theme)`,
-`NordicCleanLetter.create(theme)`, β¦) under
-`templates/coverletter/presets/`.
+one-liner factory (`ModernProfessionalLetter.create()`,
+`NordicCleanLetter.create()`, β¦) under
+`coverletter/v2/presets/`.
[π Full source](src/main/java/com/demcha/examples/templates/coverletter/CoverLetterTemplateGalleryFileExample.java)
diff --git a/examples/src/main/java/com/demcha/examples/support/ExampleDataFactory.java b/examples/src/main/java/com/demcha/examples/support/ExampleDataFactory.java
index c96d9b20..f1893ae6 100644
--- a/examples/src/main/java/com/demcha/examples/support/ExampleDataFactory.java
+++ b/examples/src/main/java/com/demcha/examples/support/ExampleDataFactory.java
@@ -9,6 +9,7 @@
import com.demcha.compose.document.templates.blocks.WorkHistoryBlock;
import com.demcha.compose.document.templates.coverletter.spec.CoverLetterHeader;
import com.demcha.compose.document.templates.coverletter.spec.CoverLetterSpec;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
import com.demcha.compose.document.templates.cv.spec.CvHeader;
import com.demcha.compose.document.templates.cv.spec.CvModule;
import com.demcha.compose.document.templates.cv.spec.CvSpec;
@@ -451,6 +452,53 @@ public static CoverLetterHeader sampleCoverLetterHeaderV2() {
// -- Templates v2 (cv/v2) sample data ------------------------------
+ /**
+ * Returns the canonical Jordan Rivera identity shared by the v2 CV
+ * sample and the v2 cover-letter sample, so a CV and its paired
+ * letter render an identical masthead.
+ *
+ * @return sample v2 identity block
+ */
+ public static CvIdentity sampleCvIdentityV2() {
+ return CvIdentity.builder()
+ .name("Jordan", "Rivera")
+ .jobTitle("Platform Engineer")
+ .contact("+44 20 5555 1000",
+ "jordan.rivera@example.com",
+ "London, UK")
+ .link("LinkedIn", "https://linkedin.com/in/jordan-rivera-demo")
+ .link("GitHub", "https://github.com/jrivera-demo")
+ .build();
+ }
+
+ /**
+ * Returns a sample {@code CoverLetterDocument} for the v2
+ * cover-letter pipeline. Reuses {@link #sampleCvIdentityV2()} so the
+ * letter masthead matches the paired CV exactly; the greeting / body
+ * / closing reuse the canonical v2 cover-letter sample content.
+ *
+ * @return sample v2 cover letter document
+ */
+ public static CoverLetterDocument sampleCoverLetterDocumentV2() {
+ return CoverLetterDocument.builder()
+ .identity(sampleCvIdentityV2())
+ .greeting("Dear Hiring Team at **Northwind Systems**,")
+ .paragraph("I am excited to share my interest in the Senior "
+ + "Platform Engineer role. My recent work has focused "
+ + "on building **reusable document-generation systems** "
+ + "that balance public API design, render quality, and "
+ + "maintainability.")
+ .paragraph("I enjoy translating fuzzy workflow requirements into "
+ + "clear template abstractions, reliable test coverage, "
+ + "and examples that make adoption easier for the rest "
+ + "of the team.")
+ .paragraph("I would welcome the opportunity to bring that same "
+ + "mix of engineering rigor and product thinking to your "
+ + "platform group.")
+ .closing("Sincerely,")
+ .build();
+ }
+
/**
* Returns a sample {@code CvDocument} for the v2 CV pipeline β
* the canonical Jordan Rivera content expressed in the v2
@@ -470,15 +518,7 @@ public static CoverLetterHeader sampleCoverLetterHeaderV2() {
* @return sample v2 CV document
*/
public static CvDocument sampleCvDocumentV2() {
- CvIdentity identity = CvIdentity.builder()
- .name("Jordan", "Rivera")
- .jobTitle("Platform Engineer")
- .contact("+44 20 5555 1000",
- "jordan.rivera@example.com",
- "London, UK")
- .link("LinkedIn", "https://linkedin.com/in/jordan-rivera-demo")
- .link("GitHub", "https://github.com/jrivera-demo")
- .build();
+ CvIdentity identity = sampleCvIdentityV2();
ParagraphSection summary = new ParagraphSection(
"Professional Summary",
diff --git a/examples/src/main/java/com/demcha/examples/support/PdfRasterizer.java b/examples/src/main/java/com/demcha/examples/support/PdfRasterizer.java
new file mode 100644
index 00000000..6a400acf
--- /dev/null
+++ b/examples/src/main/java/com/demcha/examples/support/PdfRasterizer.java
@@ -0,0 +1,50 @@
+package com.demcha.examples.support;
+
+import org.apache.pdfbox.Loader;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.rendering.PDFRenderer;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.nio.file.Path;
+
+/**
+ * Tiny dev/review utility: rasterizes each page of a PDF to a PNG via
+ * PDFBox so generated documents can be eyeballed without a system
+ * Ghostscript/poppler install.
+ *
+ * {@code
+ * exec:java -Dexec.mainClass=com.demcha.examples.support.PdfRasterizer \
+ * -Dexec.args="path/to/doc.pdf out/prefix 140"
+ * }
+ *
+ * Writes {@code prefix-p0.png}, {@code prefix-p1.png}, β¦ one per
+ * page.
+ */
+public final class PdfRasterizer {
+
+ private PdfRasterizer() {
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (args.length < 2) {
+ System.err.println("usage: PdfRasterizer [dpi]");
+ System.exit(2);
+ }
+ Path pdf = Path.of(args[0]);
+ String prefix = args[1];
+ float dpi = args.length > 2 ? Float.parseFloat(args[2]) : 140f;
+
+ try (PDDocument doc = Loader.loadPDF(pdf.toFile())) {
+ PDFRenderer renderer = new PDFRenderer(doc);
+ int pages = doc.getNumberOfPages();
+ for (int i = 0; i < pages; i++) {
+ BufferedImage image = renderer.renderImageWithDPI(i, dpi);
+ File out = new File(prefix + "-p" + i + ".png");
+ ImageIO.write(image, "png", out);
+ System.out.println("Wrote: " + out.getAbsolutePath());
+ }
+ }
+ }
+}
diff --git a/examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java b/examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java
index af4c1c69..f67a619e 100644
--- a/examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java
+++ b/examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java
@@ -57,7 +57,6 @@ record Entry(String title, String description, List tags, String codeUrl
cv("cv-panel", "Panel", "Soft-tinted panels per section, Product-Leader feel β was ProductLeader in v1.5.", "panel");
cv("cv-sidebar-portrait", "Sidebar Portrait", "Edge-to-edge grey sidebar with portrait photo, contact stack, and skills.", "sidebar", "portrait");
cv("cv-monogram-sidebar", "Monogram Sidebar", "Sidebar with monogram badge, accent rule, and structured contact + skills column.", "sidebar", "monogram");
- cv("cv-modern-professional", "Modern Professional CV", "Same data, ModernProfessional preset β single-file CvFileExample for canonical authoring.", "minimal");
// ===== Templates / Cover Letter =====
letter("cover-letter", "Cover Letter (canonical)", "Single-file canonical cover letter authored via CoverLetterFileExample.", "letter");
diff --git a/examples/src/main/java/com/demcha/examples/templates/coverletter/CoverLetterTemplateGalleryFileExample.java b/examples/src/main/java/com/demcha/examples/templates/coverletter/CoverLetterTemplateGalleryFileExample.java
index 98b06075..1f110bb2 100644
--- a/examples/src/main/java/com/demcha/examples/templates/coverletter/CoverLetterTemplateGalleryFileExample.java
+++ b/examples/src/main/java/com/demcha/examples/templates/coverletter/CoverLetterTemplateGalleryFileExample.java
@@ -4,51 +4,49 @@
import com.demcha.compose.document.api.DocumentPageSize;
import com.demcha.compose.document.api.DocumentSession;
import com.demcha.compose.document.templates.api.DocumentTemplate;
-import com.demcha.compose.document.templates.coverletter.presets.BlueBannerLetter;
-import com.demcha.compose.document.templates.coverletter.presets.BoxedSectionsLetter;
-import com.demcha.compose.document.templates.coverletter.presets.CenteredHeadlineLetter;
-import com.demcha.compose.document.templates.coverletter.presets.ClassicSerifLetter;
-import com.demcha.compose.document.templates.coverletter.presets.CompactMonoLetter;
-import com.demcha.compose.document.templates.coverletter.presets.EditorialBlueLetter;
-import com.demcha.compose.document.templates.coverletter.presets.EngineeringResumeLetter;
-import com.demcha.compose.document.templates.coverletter.presets.ExecutiveLetter;
-import com.demcha.compose.document.templates.coverletter.presets.ModernProfessionalLetter;
-import com.demcha.compose.document.templates.coverletter.presets.MonogramSidebarLetter;
-import com.demcha.compose.document.templates.coverletter.presets.NordicCleanLetter;
-import com.demcha.compose.document.templates.coverletter.presets.PanelLetter;
-import com.demcha.compose.document.templates.coverletter.presets.SidebarPortraitLetter;
-import com.demcha.compose.document.templates.coverletter.presets.TimelineMinimalLetter;
-import com.demcha.compose.document.templates.coverletter.spec.CoverLetterSpec;
-import com.demcha.compose.document.theme.BusinessTheme;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.coverletter.v2.presets.BlueBannerLetter;
+import com.demcha.compose.document.templates.coverletter.v2.presets.BoxedSectionsLetter;
+import com.demcha.compose.document.templates.coverletter.v2.presets.CenteredHeadlineLetter;
+import com.demcha.compose.document.templates.coverletter.v2.presets.ClassicSerifLetter;
+import com.demcha.compose.document.templates.coverletter.v2.presets.CompactMonoLetter;
+import com.demcha.compose.document.templates.coverletter.v2.presets.EditorialBlueLetter;
+import com.demcha.compose.document.templates.coverletter.v2.presets.EngineeringResumeLetter;
+import com.demcha.compose.document.templates.coverletter.v2.presets.ExecutiveLetter;
+import com.demcha.compose.document.templates.coverletter.v2.presets.ModernProfessionalLetter;
+import com.demcha.compose.document.templates.coverletter.v2.presets.MonogramSidebarLetter;
+import com.demcha.compose.document.templates.coverletter.v2.presets.NordicCleanLetter;
+import com.demcha.compose.document.templates.coverletter.v2.presets.PanelLetter;
+import com.demcha.compose.document.templates.coverletter.v2.presets.SidebarPortraitLetter;
+import com.demcha.compose.document.templates.coverletter.v2.presets.TimelineMinimalLetter;
import com.demcha.examples.support.ExampleDataFactory;
import com.demcha.examples.support.ExampleOutputPaths;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Function;
+import java.util.function.Supplier;
/**
- * Renders all 14 Templates v2 cover-letter presets against the same
- * shared sample data. Each PDF lands in
- * {@code examples/target/generated-pdfs/cover-letter-.pdf}
- * where {@code } is the paired CV preset's stable identifier
- * (e.g. {@code cover-letter-modern-professional.pdf}).
+ * Renders all 14 layered cover-letter presets ({@code
+ * coverletter.v2.presets.*} β the polished current standard) against
+ * the same shared sample {@link CoverLetterDocument}. Each PDF lands in
+ * {@code examples/target/generated-pdfs/templates/coverletter/cover-letter-.pdf}
+ * where {@code } is the paired CV preset's stable identifier (e.g.
+ * {@code cover-letter-modern-professional.pdf}).
*
* The 14 letter renders match the 14 CV renders in
- * {@link CvTemplateGalleryFileExample} β a writer can render both
- * galleries and pick a CV / cover-letter pair sharing the same
- * visual signature.
+ * {@link com.demcha.examples.templates.cv.CvTemplateGalleryFileExample}
+ * β a writer can render both galleries and pick a CV / cover-letter
+ * pair sharing the same visual signature.
*/
public final class CoverLetterTemplateGalleryFileExample {
- private static final BusinessTheme THEME = BusinessTheme.modern();
-
private CoverLetterTemplateGalleryFileExample() {
}
/**
- * Renders all 14 v2 cover-letter preset gallery PDFs.
+ * Renders all 14 layered cover-letter preset gallery PDFs.
*
* @return list of absolute paths of the rendered PDFs in source
* order
@@ -59,51 +57,49 @@ public static List generate() throws Exception {
}
/**
- * Renders one preset (when {@code presetId} matches its stable id)
- * or all presets when {@code presetId} is null.
+ * Renders one preset (when {@code presetId} matches its slug) or all
+ * presets when {@code presetId} is null.
*
- * @param presetId stable preset id to render exclusively, or null
- * to render all presets
+ * @param presetId slug to render exclusively, or null to render all
* @return list of absolute paths of the rendered PDFs
* @throws Exception if any rendering fails
*/
public static List generate(String presetId) throws Exception {
+ // The slug strips the "-letter" suffix so the example file name
+ // matches the paired CV (cover-letter-modern-professional.pdf
+ // pairs with cv-modern-professional.pdf).
List runs = List.of(
- // Stable id stripped of the "-letter" suffix so the
- // example file name matches the paired CV (e.g.
- // cover-letter-modern-professional.pdf pairs with
- // cv-modern-professional.pdf).
- run("modern-professional", ModernProfessionalLetter::create),
- run("nordic-clean", NordicCleanLetter::create),
- run("classic-serif", ClassicSerifLetter::create),
- run("compact-mono", CompactMonoLetter::create),
- run("executive", ExecutiveLetter::create),
- run("engineering-resume", EngineeringResumeLetter::create),
- run("timeline-minimal", TimelineMinimalLetter::create),
- run("boxed-sections", BoxedSectionsLetter::create),
- run("centered-headline", CenteredHeadlineLetter::create),
- run("blue-banner", BlueBannerLetter::create),
- run("editorial-blue", EditorialBlueLetter::create),
- run("panel", PanelLetter::create),
- run("sidebar-portrait", SidebarPortraitLetter::create),
- run("monogram-sidebar", MonogramSidebarLetter::create));
+ run("modern-professional", ModernProfessionalLetter.RECOMMENDED_MARGIN, ModernProfessionalLetter::create),
+ run("nordic-clean", NordicCleanLetter.RECOMMENDED_MARGIN, NordicCleanLetter::create),
+ run("classic-serif", ClassicSerifLetter.RECOMMENDED_MARGIN, ClassicSerifLetter::create),
+ run("compact-mono", CompactMonoLetter.RECOMMENDED_MARGIN, CompactMonoLetter::create),
+ run("executive", ExecutiveLetter.RECOMMENDED_MARGIN, ExecutiveLetter::create),
+ run("engineering-resume", EngineeringResumeLetter.RECOMMENDED_MARGIN, EngineeringResumeLetter::create),
+ run("timeline-minimal", TimelineMinimalLetter.RECOMMENDED_MARGIN, TimelineMinimalLetter::create),
+ run("boxed-sections", BoxedSectionsLetter.RECOMMENDED_MARGIN, BoxedSectionsLetter::create),
+ run("centered-headline", CenteredHeadlineLetter.RECOMMENDED_MARGIN, CenteredHeadlineLetter::create),
+ run("blue-banner", BlueBannerLetter.RECOMMENDED_MARGIN, BlueBannerLetter::create),
+ run("editorial-blue", EditorialBlueLetter.RECOMMENDED_MARGIN, EditorialBlueLetter::create),
+ run("panel", PanelLetter.RECOMMENDED_MARGIN, PanelLetter::create),
+ run("sidebar-portrait", SidebarPortraitLetter.RECOMMENDED_MARGIN, SidebarPortraitLetter::create),
+ run("monogram-sidebar", MonogramSidebarLetter.RECOMMENDED_MARGIN, MonogramSidebarLetter::create));
- CoverLetterSpec spec = ExampleDataFactory.sampleCoverLetterSpecV2();
+ CoverLetterDocument doc = ExampleDataFactory.sampleCoverLetterDocumentV2();
List generated = new ArrayList<>();
for (Run letter : runs) {
- if (presetId != null && !letter.id.equals(presetId)) {
+ if (presetId != null && !letter.id().equals(presetId)) {
continue;
}
- generated.add(renderOne(spec, letter));
+ generated.add(renderOne(doc, letter));
}
return List.copyOf(generated);
}
/**
- * Renders all v2 cover-letter preset gallery PDFs and prints each
- * path.
+ * Renders all layered cover-letter preset gallery PDFs and prints
+ * each path.
*
- * @param args optional first arg = preset id filter
+ * @param args optional first arg = slug filter
* @throws Exception if any rendering fails
*/
public static void main(String[] args) throws Exception {
@@ -113,24 +109,28 @@ public static void main(String[] args) throws Exception {
}
}
- private static Path renderOne(CoverLetterSpec spec, Run letter) throws Exception {
- Path outputFile = ExampleOutputPaths.prepare("templates/coverletter", "cover-letter-" + letter.id + ".pdf");
- DocumentTemplate template = letter.factory.apply(THEME);
+ private static Path renderOne(CoverLetterDocument doc, Run letter) throws Exception {
+ Path outputFile = ExampleOutputPaths.prepare(
+ "templates/coverletter", "cover-letter-" + letter.id() + ".pdf");
+ DocumentTemplate template = letter.factory().get();
+ float m = (float) letter.margin();
try (DocumentSession document = GraphCompose.document(outputFile)
.pageSize(DocumentPageSize.A4)
- .margin(48, 48, 48, 48)
+ .margin(m, m, m, m)
.create()) {
- template.compose(document, spec);
+ template.compose(document, doc);
document.buildPdf();
}
return outputFile;
}
- private static Run run(String id, Function> factory) {
- return new Run(id, factory);
+ private static Run run(String id, double margin,
+ Supplier> factory) {
+ return new Run(id, margin, factory);
}
- private record Run(String id, Function> factory) {
+ private record Run(String id, double margin,
+ Supplier> factory) {
}
}
diff --git a/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvBlueBannerLetterV2Example.java b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvBlueBannerLetterV2Example.java
new file mode 100644
index 00000000..60d4ea9b
--- /dev/null
+++ b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvBlueBannerLetterV2Example.java
@@ -0,0 +1,47 @@
+package com.demcha.examples.templates.coverletter.v2;
+
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.coverletter.v2.presets.BlueBannerLetter;
+import com.demcha.examples.support.ExampleDataFactory;
+import com.demcha.examples.support.ExampleOutputPaths;
+
+import java.nio.file.Path;
+
+/**
+ * Renders the v2 Blue Banner cover-letter preset β centred PT-Serif
+ * spaced-caps name over a compact centred contact row (brand carried by
+ * the blue-toned theme), then a single-column letter body.
+ *
+ * Output:
+ * {@code examples/target/generated-pdfs/templates/coverletter/cover-letter-blue-banner-v2.pdf}.
+ */
+public final class CvBlueBannerLetterV2Example {
+
+ private CvBlueBannerLetterV2Example() {
+ }
+
+ public static Path generate() throws Exception {
+ Path outputFile = ExampleOutputPaths.prepare(
+ "templates/coverletter", "cover-letter-blue-banner-v2.pdf");
+ CoverLetterDocument doc = ExampleDataFactory.sampleCoverLetterDocumentV2();
+ DocumentTemplate template = BlueBannerLetter.create();
+
+ float m = (float) BlueBannerLetter.RECOMMENDED_MARGIN;
+ try (DocumentSession document = GraphCompose.document(outputFile)
+ .pageSize(DocumentPageSize.A4)
+ .margin(m, m, m, m)
+ .create()) {
+ template.compose(document, doc);
+ document.buildPdf();
+ }
+ return outputFile;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Generated: " + generate());
+ }
+}
diff --git a/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvBoxedSectionsLetterV2Example.java b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvBoxedSectionsLetterV2Example.java
new file mode 100644
index 00000000..5e6ad803
--- /dev/null
+++ b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvBoxedSectionsLetterV2Example.java
@@ -0,0 +1,47 @@
+package com.demcha.examples.templates.coverletter.v2;
+
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.coverletter.v2.presets.BoxedSectionsLetter;
+import com.demcha.examples.support.ExampleDataFactory;
+import com.demcha.examples.support.ExampleOutputPaths;
+
+import java.nio.file.Path;
+
+/**
+ * Renders the v2 Boxed Sections cover-letter preset β centred
+ * spaced-caps PT-Serif masthead with rules, then a single-column
+ * letter body. Pair with {@code CvBoxedV2Example}.
+ *
+ * Output:
+ * {@code examples/target/generated-pdfs/templates/coverletter/cover-letter-boxed-sections-v2.pdf}.
+ */
+public final class CvBoxedSectionsLetterV2Example {
+
+ private CvBoxedSectionsLetterV2Example() {
+ }
+
+ public static Path generate() throws Exception {
+ Path outputFile = ExampleOutputPaths.prepare(
+ "templates/coverletter", "cover-letter-boxed-sections-v2.pdf");
+ CoverLetterDocument doc = ExampleDataFactory.sampleCoverLetterDocumentV2();
+ DocumentTemplate template = BoxedSectionsLetter.create();
+
+ float m = (float) BoxedSectionsLetter.RECOMMENDED_MARGIN;
+ try (DocumentSession document = GraphCompose.document(outputFile)
+ .pageSize(DocumentPageSize.A4)
+ .margin(m, m, m, m)
+ .create()) {
+ template.compose(document, doc);
+ document.buildPdf();
+ }
+ return outputFile;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Generated: " + generate());
+ }
+}
diff --git a/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvCenteredHeadlineLetterV2Example.java b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvCenteredHeadlineLetterV2Example.java
new file mode 100644
index 00000000..7614cd97
--- /dev/null
+++ b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvCenteredHeadlineLetterV2Example.java
@@ -0,0 +1,47 @@
+package com.demcha.examples.templates.coverletter.v2;
+
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.coverletter.v2.presets.CenteredHeadlineLetter;
+import com.demcha.examples.support.ExampleDataFactory;
+import com.demcha.examples.support.ExampleOutputPaths;
+
+import java.nio.file.Path;
+
+/**
+ * Renders the v2 Centered Headline cover-letter preset β centred
+ * spaced-caps Poppins name, spaced-caps subheadline, framing rules, and
+ * centred contact, then a single-column letter body.
+ *
+ * Output:
+ * {@code examples/target/generated-pdfs/templates/coverletter/cover-letter-centered-headline-v2.pdf}.
+ */
+public final class CvCenteredHeadlineLetterV2Example {
+
+ private CvCenteredHeadlineLetterV2Example() {
+ }
+
+ public static Path generate() throws Exception {
+ Path outputFile = ExampleOutputPaths.prepare(
+ "templates/coverletter", "cover-letter-centered-headline-v2.pdf");
+ CoverLetterDocument doc = ExampleDataFactory.sampleCoverLetterDocumentV2();
+ DocumentTemplate template = CenteredHeadlineLetter.create();
+
+ float m = (float) CenteredHeadlineLetter.RECOMMENDED_MARGIN;
+ try (DocumentSession document = GraphCompose.document(outputFile)
+ .pageSize(DocumentPageSize.A4)
+ .margin(m, m, m, m)
+ .create()) {
+ template.compose(document, doc);
+ document.buildPdf();
+ }
+ return outputFile;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Generated: " + generate());
+ }
+}
diff --git a/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvClassicSerifLetterV2Example.java b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvClassicSerifLetterV2Example.java
new file mode 100644
index 00000000..6a2724ac
--- /dev/null
+++ b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvClassicSerifLetterV2Example.java
@@ -0,0 +1,48 @@
+package com.demcha.examples.templates.coverletter.v2;
+
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.coverletter.v2.presets.ClassicSerifLetter;
+import com.demcha.examples.support.ExampleDataFactory;
+import com.demcha.examples.support.ExampleOutputPaths;
+
+import java.nio.file.Path;
+
+/**
+ * Renders the v2 Classic Serif cover-letter preset β centred
+ * spaced-caps PT-Serif masthead, tan rule, centred contact with
+ * tan-accent links, then a single-column letter body. Pair with
+ * {@code CvClassicSerifExample}.
+ *
+ * Output:
+ * {@code examples/target/generated-pdfs/templates/coverletter/cover-letter-classic-serif-v2.pdf}.
+ */
+public final class CvClassicSerifLetterV2Example {
+
+ private CvClassicSerifLetterV2Example() {
+ }
+
+ public static Path generate() throws Exception {
+ Path outputFile = ExampleOutputPaths.prepare(
+ "templates/coverletter", "cover-letter-classic-serif-v2.pdf");
+ CoverLetterDocument doc = ExampleDataFactory.sampleCoverLetterDocumentV2();
+ DocumentTemplate template = ClassicSerifLetter.create();
+
+ float m = (float) ClassicSerifLetter.RECOMMENDED_MARGIN;
+ try (DocumentSession document = GraphCompose.document(outputFile)
+ .pageSize(DocumentPageSize.A4)
+ .margin(m, m, m, m)
+ .create()) {
+ template.compose(document, doc);
+ document.buildPdf();
+ }
+ return outputFile;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Generated: " + generate());
+ }
+}
diff --git a/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvCompactMonoLetterV2Example.java b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvCompactMonoLetterV2Example.java
new file mode 100644
index 00000000..5161265b
--- /dev/null
+++ b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvCompactMonoLetterV2Example.java
@@ -0,0 +1,48 @@
+package com.demcha.examples.templates.coverletter.v2;
+
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.coverletter.v2.presets.CompactMonoLetter;
+import com.demcha.examples.support.ExampleDataFactory;
+import com.demcha.examples.support.ExampleOutputPaths;
+
+import java.nio.file.Path;
+
+/**
+ * Renders the v2 Compact Mono cover-letter preset β near-black rounded
+ * command-bar header (UPPERCASE name, left-aligned contact with cyan
+ * links) then a single-column letter body. Pair with
+ * {@code CvCompactMonoExample}.
+ *
+ * Output:
+ * {@code examples/target/generated-pdfs/templates/coverletter/cover-letter-compact-mono-v2.pdf}.
+ */
+public final class CvCompactMonoLetterV2Example {
+
+ private CvCompactMonoLetterV2Example() {
+ }
+
+ public static Path generate() throws Exception {
+ Path outputFile = ExampleOutputPaths.prepare(
+ "templates/coverletter", "cover-letter-compact-mono-v2.pdf");
+ CoverLetterDocument doc = ExampleDataFactory.sampleCoverLetterDocumentV2();
+ DocumentTemplate template = CompactMonoLetter.create();
+
+ float m = (float) CompactMonoLetter.RECOMMENDED_MARGIN;
+ try (DocumentSession document = GraphCompose.document(outputFile)
+ .pageSize(DocumentPageSize.A4)
+ .margin(m, m, m, m)
+ .create()) {
+ template.compose(document, doc);
+ document.buildPdf();
+ }
+ return outputFile;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Generated: " + generate());
+ }
+}
diff --git a/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvEditorialBlueLetterV2Example.java b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvEditorialBlueLetterV2Example.java
new file mode 100644
index 00000000..1b3cdbde
--- /dev/null
+++ b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvEditorialBlueLetterV2Example.java
@@ -0,0 +1,48 @@
+package com.demcha.examples.templates.coverletter.v2;
+
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.coverletter.v2.presets.EditorialBlueLetter;
+import com.demcha.examples.support.ExampleDataFactory;
+import com.demcha.examples.support.ExampleOutputPaths;
+
+import java.nio.file.Path;
+
+/**
+ * Renders the v2 Editorial Blue cover-letter preset β centred navy
+ * Helvetica masthead (name + job title), centred contact with blue
+ * links, then a single-column letter body. Pair with
+ * {@code CvEditorialBlueExample}.
+ *
+ * Output:
+ * {@code examples/target/generated-pdfs/templates/coverletter/cover-letter-editorial-blue-v2.pdf}.
+ */
+public final class CvEditorialBlueLetterV2Example {
+
+ private CvEditorialBlueLetterV2Example() {
+ }
+
+ public static Path generate() throws Exception {
+ Path outputFile = ExampleOutputPaths.prepare(
+ "templates/coverletter", "cover-letter-editorial-blue-v2.pdf");
+ CoverLetterDocument doc = ExampleDataFactory.sampleCoverLetterDocumentV2();
+ DocumentTemplate template = EditorialBlueLetter.create();
+
+ float m = (float) EditorialBlueLetter.RECOMMENDED_MARGIN;
+ try (DocumentSession document = GraphCompose.document(outputFile)
+ .pageSize(DocumentPageSize.A4)
+ .margin(m, m, m, m)
+ .create()) {
+ template.compose(document, doc);
+ document.buildPdf();
+ }
+ return outputFile;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Generated: " + generate());
+ }
+}
diff --git a/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvEngineeringResumeLetterV2Example.java b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvEngineeringResumeLetterV2Example.java
new file mode 100644
index 00000000..209bf300
--- /dev/null
+++ b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvEngineeringResumeLetterV2Example.java
@@ -0,0 +1,48 @@
+package com.demcha.examples.templates.coverletter.v2;
+
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.coverletter.v2.presets.EngineeringResumeLetter;
+import com.demcha.examples.support.ExampleDataFactory;
+import com.demcha.examples.support.ExampleOutputPaths;
+
+import java.nio.file.Path;
+
+/**
+ * Renders the v2 Engineering Resume cover-letter preset β full-width
+ * navy command header (UPPERCASE name + role subtitle, right-aligned
+ * contact with cyan-green links, green accent strip) then a
+ * single-column letter body. Pair with {@code CvEngineeringResumeExample}.
+ *
+ * Output:
+ * {@code examples/target/generated-pdfs/templates/coverletter/cover-letter-engineering-resume-v2.pdf}.
+ */
+public final class CvEngineeringResumeLetterV2Example {
+
+ private CvEngineeringResumeLetterV2Example() {
+ }
+
+ public static Path generate() throws Exception {
+ Path outputFile = ExampleOutputPaths.prepare(
+ "templates/coverletter", "cover-letter-engineering-resume-v2.pdf");
+ CoverLetterDocument doc = ExampleDataFactory.sampleCoverLetterDocumentV2();
+ DocumentTemplate template = EngineeringResumeLetter.create();
+
+ float m = (float) EngineeringResumeLetter.RECOMMENDED_MARGIN;
+ try (DocumentSession document = GraphCompose.document(outputFile)
+ .pageSize(DocumentPageSize.A4)
+ .margin(m, m, m, m)
+ .create()) {
+ template.compose(document, doc);
+ document.buildPdf();
+ }
+ return outputFile;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Generated: " + generate());
+ }
+}
diff --git a/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvExecutiveLetterV2Example.java b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvExecutiveLetterV2Example.java
new file mode 100644
index 00000000..db9fa34c
--- /dev/null
+++ b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvExecutiveLetterV2Example.java
@@ -0,0 +1,51 @@
+package com.demcha.examples.templates.coverletter.v2;
+
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.coverletter.v2.presets.ExecutiveLetter;
+import com.demcha.examples.support.ExampleDataFactory;
+import com.demcha.examples.support.ExampleOutputPaths;
+
+import java.nio.file.Path;
+
+/**
+ * Renders the v2 Executive cover-letter preset against the shared
+ * Jordan Rivera identity β the same masthead as the Executive CV
+ * (uppercase Poppins slate name, Lato meta + bronze link row,
+ * full-width muted rule) followed by a single-column letter body.
+ *
+ * Pair with {@code CvExecutiveExample} to view the CV and letter as
+ * a matched set.
+ *
+ * Output:
+ * {@code examples/target/generated-pdfs/templates/coverletter/cover-letter-executive-v2.pdf}.
+ */
+public final class CvExecutiveLetterV2Example {
+
+ private CvExecutiveLetterV2Example() {
+ }
+
+ public static Path generate() throws Exception {
+ Path outputFile = ExampleOutputPaths.prepare(
+ "templates/coverletter", "cover-letter-executive-v2.pdf");
+ CoverLetterDocument doc = ExampleDataFactory.sampleCoverLetterDocumentV2();
+ DocumentTemplate template = ExecutiveLetter.create();
+
+ float m = (float) ExecutiveLetter.RECOMMENDED_MARGIN;
+ try (DocumentSession document = GraphCompose.document(outputFile)
+ .pageSize(DocumentPageSize.A4)
+ .margin(m, m, m, m)
+ .create()) {
+ template.compose(document, doc);
+ document.buildPdf();
+ }
+ return outputFile;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Generated: " + generate());
+ }
+}
diff --git a/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvModernProfessionalLetterV2Example.java b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvModernProfessionalLetterV2Example.java
new file mode 100644
index 00000000..07bfe2e7
--- /dev/null
+++ b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvModernProfessionalLetterV2Example.java
@@ -0,0 +1,52 @@
+package com.demcha.examples.templates.coverletter.v2;
+
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.coverletter.v2.presets.ModernProfessionalLetter;
+import com.demcha.examples.support.ExampleDataFactory;
+import com.demcha.examples.support.ExampleOutputPaths;
+
+import java.nio.file.Path;
+
+/**
+ * Renders the v2 Modern Professional cover-letter preset against the
+ * shared Jordan Rivera identity β the same masthead as the Modern
+ * Professional CV (right-aligned slate-blue Helvetica name, two-row
+ * right-aligned contact stack with royal-blue links, bottom accent
+ * rule) followed by a single-column letter body.
+ *
+ * Pair with {@code CvModernV2Example} to view the CV and letter as
+ * a matched set.
+ *
+ * Output:
+ * {@code examples/target/generated-pdfs/templates/coverletter/cover-letter-modern-professional-v2.pdf}.
+ */
+public final class CvModernProfessionalLetterV2Example {
+
+ private CvModernProfessionalLetterV2Example() {
+ }
+
+ public static Path generate() throws Exception {
+ Path outputFile = ExampleOutputPaths.prepare(
+ "templates/coverletter", "cover-letter-modern-professional-v2.pdf");
+ CoverLetterDocument doc = ExampleDataFactory.sampleCoverLetterDocumentV2();
+ DocumentTemplate template = ModernProfessionalLetter.create();
+
+ float m = (float) ModernProfessionalLetter.RECOMMENDED_MARGIN;
+ try (DocumentSession document = GraphCompose.document(outputFile)
+ .pageSize(DocumentPageSize.A4)
+ .margin(m, m, m, m)
+ .create()) {
+ template.compose(document, doc);
+ document.buildPdf();
+ }
+ return outputFile;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Generated: " + generate());
+ }
+}
diff --git a/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvMonogramSidebarLetterV2Example.java b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvMonogramSidebarLetterV2Example.java
new file mode 100644
index 00000000..6b66fc6f
--- /dev/null
+++ b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvMonogramSidebarLetterV2Example.java
@@ -0,0 +1,48 @@
+package com.demcha.examples.templates.coverletter.v2;
+
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.coverletter.v2.presets.MonogramSidebarLetter;
+import com.demcha.examples.support.ExampleDataFactory;
+import com.demcha.examples.support.ExampleOutputPaths;
+
+import java.nio.file.Path;
+
+/**
+ * Renders the v2 Monogram Sidebar cover-letter preset β centred
+ * monogram-ring badge over a stacked spaced-caps name, gold role line,
+ * and centred contact, then a single-column letter body. Pair with
+ * {@code CvMonogramSidebarExample}.
+ *
+ * Output:
+ * {@code examples/target/generated-pdfs/templates/coverletter/cover-letter-monogram-sidebar-v2.pdf}.
+ */
+public final class CvMonogramSidebarLetterV2Example {
+
+ private CvMonogramSidebarLetterV2Example() {
+ }
+
+ public static Path generate() throws Exception {
+ Path outputFile = ExampleOutputPaths.prepare(
+ "templates/coverletter", "cover-letter-monogram-sidebar-v2.pdf");
+ CoverLetterDocument doc = ExampleDataFactory.sampleCoverLetterDocumentV2();
+ DocumentTemplate template = MonogramSidebarLetter.create();
+
+ float m = (float) MonogramSidebarLetter.RECOMMENDED_MARGIN;
+ try (DocumentSession document = GraphCompose.document(outputFile)
+ .pageSize(DocumentPageSize.A4)
+ .margin(m, m, m, m)
+ .create()) {
+ template.compose(document, doc);
+ document.buildPdf();
+ }
+ return outputFile;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Generated: " + generate());
+ }
+}
diff --git a/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvNordicCleanLetterV2Example.java b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvNordicCleanLetterV2Example.java
new file mode 100644
index 00000000..f72529d3
--- /dev/null
+++ b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvNordicCleanLetterV2Example.java
@@ -0,0 +1,48 @@
+package com.demcha.examples.templates.coverletter.v2;
+
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.coverletter.v2.presets.NordicCleanLetter;
+import com.demcha.examples.support.ExampleDataFactory;
+import com.demcha.examples.support.ExampleOutputPaths;
+
+import java.nio.file.Path;
+
+/**
+ * Renders the v2 Nordic Clean cover-letter preset β left-aligned
+ * UPPERCASE name with a teal accent bar + role sub-line, right-aligned
+ * stacked contact with teal links, then a single-column letter body.
+ * Pair with {@code CvNordicV2Example}.
+ *
+ * Output:
+ * {@code examples/target/generated-pdfs/templates/coverletter/cover-letter-nordic-clean-v2.pdf}.
+ */
+public final class CvNordicCleanLetterV2Example {
+
+ private CvNordicCleanLetterV2Example() {
+ }
+
+ public static Path generate() throws Exception {
+ Path outputFile = ExampleOutputPaths.prepare(
+ "templates/coverletter", "cover-letter-nordic-clean-v2.pdf");
+ CoverLetterDocument doc = ExampleDataFactory.sampleCoverLetterDocumentV2();
+ DocumentTemplate template = NordicCleanLetter.create();
+
+ float m = (float) NordicCleanLetter.RECOMMENDED_MARGIN;
+ try (DocumentSession document = GraphCompose.document(outputFile)
+ .pageSize(DocumentPageSize.A4)
+ .margin(m, m, m, m)
+ .create()) {
+ template.compose(document, doc);
+ document.buildPdf();
+ }
+ return outputFile;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Generated: " + generate());
+ }
+}
diff --git a/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvPanelLetterV2Example.java b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvPanelLetterV2Example.java
new file mode 100644
index 00000000..913e11a8
--- /dev/null
+++ b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvPanelLetterV2Example.java
@@ -0,0 +1,47 @@
+package com.demcha.examples.templates.coverletter.v2;
+
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.coverletter.v2.presets.PanelLetter;
+import com.demcha.examples.support.ExampleDataFactory;
+import com.demcha.examples.support.ExampleOutputPaths;
+
+import java.nio.file.Path;
+
+/**
+ * Renders the v2 Panel cover-letter preset β full-width pale-teal
+ * header card (centred Poppins name, job title, meta + teal links) then
+ * a single-column letter body. Pair with {@code CvPanelExample}.
+ *
+ * Output:
+ * {@code examples/target/generated-pdfs/templates/coverletter/cover-letter-panel-v2.pdf}.
+ */
+public final class CvPanelLetterV2Example {
+
+ private CvPanelLetterV2Example() {
+ }
+
+ public static Path generate() throws Exception {
+ Path outputFile = ExampleOutputPaths.prepare(
+ "templates/coverletter", "cover-letter-panel-v2.pdf");
+ CoverLetterDocument doc = ExampleDataFactory.sampleCoverLetterDocumentV2();
+ DocumentTemplate template = PanelLetter.create();
+
+ float m = (float) PanelLetter.RECOMMENDED_MARGIN;
+ try (DocumentSession document = GraphCompose.document(outputFile)
+ .pageSize(DocumentPageSize.A4)
+ .margin(m, m, m, m)
+ .create()) {
+ template.compose(document, doc);
+ document.buildPdf();
+ }
+ return outputFile;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Generated: " + generate());
+ }
+}
diff --git a/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvSidebarPortraitLetterV2Example.java b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvSidebarPortraitLetterV2Example.java
new file mode 100644
index 00000000..4bfb6174
--- /dev/null
+++ b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvSidebarPortraitLetterV2Example.java
@@ -0,0 +1,48 @@
+package com.demcha.examples.templates.coverletter.v2;
+
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.coverletter.v2.presets.SidebarPortraitLetter;
+import com.demcha.examples.support.ExampleDataFactory;
+import com.demcha.examples.support.ExampleOutputPaths;
+
+import java.nio.file.Path;
+
+/**
+ * Renders the v2 Sidebar Portrait cover-letter preset β full-width beige
+ * hero band (centred serif name + spaced-caps role) + centred contact,
+ * then a single-column letter body. Pair with
+ * {@code CvSidebarPortraitExample}.
+ *
+ * Output:
+ * {@code examples/target/generated-pdfs/templates/coverletter/cover-letter-sidebar-portrait-v2.pdf}.
+ */
+public final class CvSidebarPortraitLetterV2Example {
+
+ private CvSidebarPortraitLetterV2Example() {
+ }
+
+ public static Path generate() throws Exception {
+ Path outputFile = ExampleOutputPaths.prepare(
+ "templates/coverletter", "cover-letter-sidebar-portrait-v2.pdf");
+ CoverLetterDocument doc = ExampleDataFactory.sampleCoverLetterDocumentV2();
+ DocumentTemplate template = SidebarPortraitLetter.create();
+
+ float m = (float) SidebarPortraitLetter.RECOMMENDED_MARGIN;
+ try (DocumentSession document = GraphCompose.document(outputFile)
+ .pageSize(DocumentPageSize.A4)
+ .margin(m, m, m, m)
+ .create()) {
+ template.compose(document, doc);
+ document.buildPdf();
+ }
+ return outputFile;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Generated: " + generate());
+ }
+}
diff --git a/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvTimelineMinimalLetterV2Example.java b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvTimelineMinimalLetterV2Example.java
new file mode 100644
index 00000000..384e2f67
--- /dev/null
+++ b/examples/src/main/java/com/demcha/examples/templates/coverletter/v2/CvTimelineMinimalLetterV2Example.java
@@ -0,0 +1,48 @@
+package com.demcha.examples.templates.coverletter.v2;
+
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.coverletter.v2.presets.TimelineMinimalLetter;
+import com.demcha.examples.support.ExampleDataFactory;
+import com.demcha.examples.support.ExampleOutputPaths;
+
+import java.nio.file.Path;
+
+/**
+ * Renders the v2 Timeline Minimal cover-letter preset β left spaced-caps
+ * name + role over a right-aligned PNG-icon contact stack under a thin
+ * rule, then a single-column letter body. Pair with
+ * {@code CvTimelineMinimalExample}.
+ *
+ * Output:
+ * {@code examples/target/generated-pdfs/templates/coverletter/cover-letter-timeline-minimal-v2.pdf}.
+ */
+public final class CvTimelineMinimalLetterV2Example {
+
+ private CvTimelineMinimalLetterV2Example() {
+ }
+
+ public static Path generate() throws Exception {
+ Path outputFile = ExampleOutputPaths.prepare(
+ "templates/coverletter", "cover-letter-timeline-minimal-v2.pdf");
+ CoverLetterDocument doc = ExampleDataFactory.sampleCoverLetterDocumentV2();
+ DocumentTemplate template = TimelineMinimalLetter.create();
+
+ float m = (float) TimelineMinimalLetter.RECOMMENDED_MARGIN;
+ try (DocumentSession document = GraphCompose.document(outputFile)
+ .pageSize(DocumentPageSize.A4)
+ .margin(m, m, m, m)
+ .create()) {
+ template.compose(document, doc);
+ document.buildPdf();
+ }
+ return outputFile;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Generated: " + generate());
+ }
+}
diff --git a/examples/src/main/java/com/demcha/examples/templates/cv/CvFileExample.java b/examples/src/main/java/com/demcha/examples/templates/cv/CvFileExample.java
index 632f2017..b90ec116 100644
--- a/examples/src/main/java/com/demcha/examples/templates/cv/CvFileExample.java
+++ b/examples/src/main/java/com/demcha/examples/templates/cv/CvFileExample.java
@@ -4,19 +4,19 @@
import com.demcha.compose.document.api.DocumentPageSize;
import com.demcha.compose.document.api.DocumentSession;
import com.demcha.compose.document.templates.api.DocumentTemplate;
-import com.demcha.compose.document.templates.cv.presets.ModernProfessional;
-import com.demcha.compose.document.templates.cv.spec.CvSpec;
-import com.demcha.compose.document.theme.BusinessTheme;
+import com.demcha.compose.document.templates.cv.v2.data.CvDocument;
+import com.demcha.compose.document.templates.cv.v2.presets.ModernProfessional;
import com.demcha.examples.support.ExampleDataFactory;
import com.demcha.examples.support.ExampleOutputPaths;
import java.nio.file.Path;
/**
- * Renders the canonical Modern Professional CV using the Templates v2
- * preset {@link ModernProfessional}. This replaces the legacy
- * {@code CvTemplateV1} example wiring; the visual signature and
- * sample data both come from the v2 surface.
+ * Canonical single-file CV authoring demo using the layered
+ * {@code cv.v2} surface β the simplest "author one CV" path: build a
+ * {@link CvDocument}, pick a preset's {@code create()} factory, compose.
+ * The full 14-preset gallery lives in
+ * {@link CvTemplateGalleryFileExample}.
*/
public final class CvFileExample {
@@ -24,21 +24,22 @@ private CvFileExample() {
}
/**
- * Renders the example PDF to {@code generated-pdfs/cv-modern-professional.pdf}.
+ * Renders the example PDF to {@code generated-pdfs/templates/cv/cv-modern-professional.pdf}.
*
* @return absolute path of the rendered PDF
* @throws Exception if rendering fails
*/
public static Path generate() throws Exception {
Path outputFile = ExampleOutputPaths.prepare("templates/cv", "cv-modern-professional.pdf");
- CvSpec spec = ExampleDataFactory.sampleCvSpecV2();
- DocumentTemplate template = ModernProfessional.create(BusinessTheme.modern());
+ CvDocument doc = ExampleDataFactory.sampleCvDocumentV2();
+ DocumentTemplate template = ModernProfessional.create();
+ float m = (float) ModernProfessional.RECOMMENDED_MARGIN;
try (DocumentSession document = GraphCompose.document(outputFile)
.pageSize(DocumentPageSize.A4)
- .margin(28, 28, 28, 28)
+ .margin(m, m, m, m)
.create()) {
- template.compose(document, spec);
+ template.compose(document, doc);
document.buildPdf();
}
return outputFile;
diff --git a/examples/src/main/java/com/demcha/examples/templates/cv/CvTemplateGalleryFileExample.java b/examples/src/main/java/com/demcha/examples/templates/cv/CvTemplateGalleryFileExample.java
index 3251f6e0..5ecce10d 100644
--- a/examples/src/main/java/com/demcha/examples/templates/cv/CvTemplateGalleryFileExample.java
+++ b/examples/src/main/java/com/demcha/examples/templates/cv/CvTemplateGalleryFileExample.java
@@ -4,50 +4,48 @@
import com.demcha.compose.document.api.DocumentPageSize;
import com.demcha.compose.document.api.DocumentSession;
import com.demcha.compose.document.templates.api.DocumentTemplate;
-import com.demcha.compose.document.templates.cv.presets.BlueBanner;
-import com.demcha.compose.document.templates.cv.presets.BoxedSections;
-import com.demcha.compose.document.templates.cv.presets.CenteredHeadline;
-import com.demcha.compose.document.templates.cv.presets.ClassicSerif;
-import com.demcha.compose.document.templates.cv.presets.CompactMono;
-import com.demcha.compose.document.templates.cv.presets.EditorialBlue;
-import com.demcha.compose.document.templates.cv.presets.EngineeringResume;
-import com.demcha.compose.document.templates.cv.presets.Executive;
-import com.demcha.compose.document.templates.cv.presets.ModernProfessional;
-import com.demcha.compose.document.templates.cv.presets.MonogramSidebar;
-import com.demcha.compose.document.templates.cv.presets.NordicClean;
-import com.demcha.compose.document.templates.cv.presets.Panel;
-import com.demcha.compose.document.templates.cv.presets.SidebarPortrait;
-import com.demcha.compose.document.templates.cv.presets.TimelineMinimal;
-import com.demcha.compose.document.templates.cv.spec.CvSpec;
-import com.demcha.compose.document.theme.BusinessTheme;
+import com.demcha.compose.document.templates.cv.v2.data.CvDocument;
+import com.demcha.compose.document.templates.cv.v2.presets.BlueBanner;
+import com.demcha.compose.document.templates.cv.v2.presets.BoxedSections;
+import com.demcha.compose.document.templates.cv.v2.presets.CenteredHeadline;
+import com.demcha.compose.document.templates.cv.v2.presets.ClassicSerif;
+import com.demcha.compose.document.templates.cv.v2.presets.CompactMono;
+import com.demcha.compose.document.templates.cv.v2.presets.EditorialBlue;
+import com.demcha.compose.document.templates.cv.v2.presets.EngineeringResume;
+import com.demcha.compose.document.templates.cv.v2.presets.Executive;
+import com.demcha.compose.document.templates.cv.v2.presets.ModernProfessional;
+import com.demcha.compose.document.templates.cv.v2.presets.MonogramSidebar;
+import com.demcha.compose.document.templates.cv.v2.presets.NordicClean;
+import com.demcha.compose.document.templates.cv.v2.presets.Panel;
+import com.demcha.compose.document.templates.cv.v2.presets.SidebarPortrait;
+import com.demcha.compose.document.templates.cv.v2.presets.TimelineMinimal;
import com.demcha.examples.support.ExampleDataFactory;
import com.demcha.examples.support.ExampleOutputPaths;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Function;
+import java.util.function.Supplier;
/**
- * Renders all 14 Templates v2 CV presets against the same shared
- * sample data. Each PDF lands in
- * {@code examples/target/generated-pdfs/cv-.pdf} where
- * {@code } is the preset's stable identifier (e.g.
+ * Renders all 14 layered CV presets ({@code cv.v2.presets.*} β the
+ * polished current standard) against the same shared sample
+ * {@link CvDocument}. Each PDF lands in
+ * {@code examples/target/generated-pdfs/templates/cv/cv-.pdf}
+ * where {@code } is the preset's stable identifier (e.g.
* {@code cv-modern-professional.pdf}).
*
- * This is the single source of truth for the example CV gallery
- * in v2. The matching cover-letter gallery lives in
- * {@link CoverLetterTemplateGalleryFileExample}.
+ * This is the single source of truth for the CV showcase gallery.
+ * The matching cover-letter gallery lives in
+ * {@link com.demcha.examples.templates.coverletter.CoverLetterTemplateGalleryFileExample}.
*/
public final class CvTemplateGalleryFileExample {
- private static final BusinessTheme THEME = BusinessTheme.modern();
-
private CvTemplateGalleryFileExample() {
}
/**
- * Renders all 14 v2 CV preset gallery PDFs.
+ * Renders all 14 layered CV preset gallery PDFs.
*
* @return list of absolute paths of the rendered PDFs in source
* order
@@ -83,19 +81,19 @@ public static List generate(String presetId) throws Exception {
run(SidebarPortrait.ID, SidebarPortrait.RECOMMENDED_MARGIN, SidebarPortrait::create),
run(MonogramSidebar.ID, MonogramSidebar.RECOMMENDED_MARGIN, MonogramSidebar::create));
- CvSpec spec = ExampleDataFactory.sampleCvSpecV2();
+ CvDocument doc = ExampleDataFactory.sampleCvDocumentV2();
List generated = new ArrayList<>();
for (Run cv : runs) {
- if (presetId != null && !cv.id.equals(presetId)) {
+ if (presetId != null && !cv.id().equals(presetId)) {
continue;
}
- generated.add(renderOne(spec, cv));
+ generated.add(renderOne(doc, cv));
}
return List.copyOf(generated);
}
/**
- * Renders all v2 CV preset gallery PDFs and prints each path.
+ * Renders all layered CV preset gallery PDFs and prints each path.
*
* @param args optional first arg = preset id filter
* @throws Exception if any rendering fails
@@ -107,25 +105,27 @@ public static void main(String[] args) throws Exception {
}
}
- private static Path renderOne(CvSpec spec, Run cv) throws Exception {
- Path outputFile = ExampleOutputPaths.prepare("templates/cv", "cv-" + cv.id + ".pdf");
- DocumentTemplate template = cv.factory.apply(THEME);
+ private static Path renderOne(CvDocument doc, Run cv) throws Exception {
+ Path outputFile = ExampleOutputPaths.prepare("templates/cv", "cv-" + cv.id() + ".pdf");
+ DocumentTemplate template = cv.factory().get();
- float m = (float) cv.margin;
+ float m = (float) cv.margin();
try (DocumentSession document = GraphCompose.document(outputFile)
.pageSize(DocumentPageSize.A4)
.margin(m, m, m, m)
.create()) {
- template.compose(document, spec);
+ template.compose(document, doc);
document.buildPdf();
}
return outputFile;
}
- private static Run run(String id, double margin, Function> factory) {
+ private static Run run(String id, double margin,
+ Supplier> factory) {
return new Run(id, margin, factory);
}
- private record Run(String id, double margin, Function> factory) {
+ private record Run(String id, double margin,
+ Supplier> factory) {
}
}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/builder/CoverLetterBuilder.java b/src/main/java/com/demcha/compose/document/templates/coverletter/builder/CoverLetterBuilder.java
index e270faec..e41a8818 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/builder/CoverLetterBuilder.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/builder/CoverLetterBuilder.java
@@ -34,7 +34,15 @@
* {@code header}, {@code layout}, {@code bodyStyle}, {@code spacing},
* and at least implicit alignment via the layout / body style) must
* be configured before calling {@link #build()}.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard) β the layered model
+ * {@link com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument}
+ * plus the {@code coverletter.v2} presets. Kept for backward
+ * compatibility; scheduled for removal in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class CoverLetterBuilder {
private String id;
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/layouts/LetterFormat.java b/src/main/java/com/demcha/compose/document/templates/coverletter/layouts/LetterFormat.java
index b47202e1..4d5c5227 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/layouts/LetterFormat.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/layouts/LetterFormat.java
@@ -17,7 +17,15 @@
* letter is structurally simpler than a CV (one continuous reading
* flow), so the layout takes the rendered nodes in source order and
* emits one container.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard) β the layered model
+ * {@link com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument}
+ * plus the {@code coverletter.v2} presets. Kept for backward
+ * compatibility; scheduled for removal in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class LetterFormat {
private static final String LAYOUT_NAME = "layout.letterFormat";
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/layouts/package-info.java b/src/main/java/com/demcha/compose/document/templates/coverletter/layouts/package-info.java
index d0198827..106290a3 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/layouts/package-info.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/layouts/package-info.java
@@ -1,13 +1,28 @@
/**
- * Templates v2 cover-letter layouts β caΡΠΊΠ°ΡΡ that arrange a header
+ * Superseded Gen-2 cover-letter layouts β frames that arrange a header
* plus letter blocks (greeting, body paragraphs, closing) into a
* final document tree.
*
+ * Deprecated surface. These are the older Gen-2
+ * cover-letter layouts. They are not the current standard. The
+ * current standard is the layered surface
+ * {@code com.demcha.compose.document.templates.coverletter.v2} (data / theme /
+ * components / widgets / presets). This package is kept only for backward
+ * compatibility and is scheduled for removal in a future major.
+ *
* Currently a single layout
* ({@link com.demcha.compose.document.templates.coverletter.layouts.LetterFormat})
- * covers all 14 cover-letter pair presets. Additional layouts will
- * land here when their preset migrations require them.
+ * covers all 14 cover-letter pair presets.
+ *
+ * New code should target the layered {@code coverletter.v2} surface
+ * instead. See {@code docs/templates/v2-layered/}.
*
* @since 1.6.0
+ * @deprecated Superseded by the layered
+ * {@code com.demcha.compose.document.templates.coverletter.v2}
+ * surface (the current standard). This Gen-2 package is kept for
+ * backward compatibility and will be removed in a future major.
+ * See {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
package com.demcha.compose.document.templates.coverletter.layouts;
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/package-info.java b/src/main/java/com/demcha/compose/document/templates/coverletter/package-info.java
index 18e85a12..5e191747 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/package-info.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/package-info.java
@@ -1,16 +1,18 @@
/**
- * Templates v2 cover-letter domain β layouts, presets, builder, and spec data types.
+ * Superseded Gen-2 cover-letter domain β layout, presets, builder, and spec
+ * data types.
*
- * This package is the home of all cover-letter templates in the v2
- * architecture. The user requirement is one cover-letter preset paired
- * with each CV preset (same Header / Typography / Palette), so writers
- * can ship a CV and a matching cover letter with consistent visual
- * identity.
+ * Deprecated surface. This package is the older Gen-2
+ * cover-letter template stack. It is not the current standard. The
+ * current standard is the layered surface
+ * {@code com.demcha.compose.document.templates.coverletter.v2} (data / theme /
+ * components / widgets / presets). This package is kept only for backward
+ * compatibility and is scheduled for removal in a future major.
*
- * Sub-packages partition the domain by concern:
+ * Sub-packages partition the (deprecated) domain by concern:
*
*
- * - {@code coverletter.layouts} β slot ΠΊΠ°ΡΠΊΠ°ΡΡ (LetterFormat β a
+ *
- {@code coverletter.layouts} β slot frames (LetterFormat β a
* single-column layout with generous side margins for letter body
* text).
* - {@code coverletter.presets} β flat copy-and-tweak preset classes,
@@ -25,8 +27,8 @@
* with header, greeting, body paragraphs, closing).
*
*
- * Sub-packages will be populated during Phase E of the Templates v2
- * migration.
+ * New code should target the layered {@code coverletter.v2} surface
+ * instead. See {@code docs/templates/v2-layered/}.
*
* Naming note: the user-facing concept is
* "cover-letter" with a hyphen, but Java packages cannot contain hyphens.
@@ -35,5 +37,11 @@
* (e.g. {@code cover-letter-modern-professional.pdf}).
*
* @since 1.6.0
+ * @deprecated Superseded by the layered
+ * {@code com.demcha.compose.document.templates.coverletter.v2}
+ * surface (the current standard). This Gen-2 package is kept for
+ * backward compatibility and will be removed in a future major.
+ * See {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
package com.demcha.compose.document.templates.coverletter;
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/BlueBannerLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/BlueBannerLetter.java
index 3656110d..cf0f4b55 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/BlueBannerLetter.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/BlueBannerLetter.java
@@ -19,7 +19,13 @@
* BANNER_BG used by
* {@link com.demcha.compose.document.templates.cv.presets.BlueBanner}
* for accent runs.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.coverletter.v2.presets.BlueBannerLetter}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class BlueBannerLetter {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/BoxedSectionsLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/BoxedSectionsLetter.java
index b03ee997..4d37092e 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/BoxedSectionsLetter.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/BoxedSectionsLetter.java
@@ -17,7 +17,13 @@
*
* PT Serif throughout, dark grey ink β matches
* {@link com.demcha.compose.document.templates.cv.presets.BoxedSections}.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.coverletter.v2.presets.BoxedSectionsLetter}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class BoxedSectionsLetter {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/CenteredHeadlineLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/CenteredHeadlineLetter.java
index ce1c6093..1afe6de7 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/CenteredHeadlineLetter.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/CenteredHeadlineLetter.java
@@ -17,7 +17,13 @@
*
* Poppins headline + Lato body, dark grey ink β matches
* {@link com.demcha.compose.document.templates.cv.presets.CenteredHeadline}.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.coverletter.v2.presets.CenteredHeadlineLetter}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class CenteredHeadlineLetter {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/ClassicSerifLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/ClassicSerifLetter.java
index 6207d08f..69007a25 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/ClassicSerifLetter.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/ClassicSerifLetter.java
@@ -17,7 +17,13 @@
*
* PT Serif throughout with the bronze accent and warm INK palette
* of {@link com.demcha.compose.document.templates.cv.presets.ClassicSerif}.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.coverletter.v2.presets.ClassicSerifLetter}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class ClassicSerifLetter {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/CompactMonoLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/CompactMonoLetter.java
index 53681f5e..50412499 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/CompactMonoLetter.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/CompactMonoLetter.java
@@ -18,7 +18,13 @@
* IBM Plex Mono headline + Lato body, dark INK ink with the
* teal-blue ACCENT used by
* {@link com.demcha.compose.document.templates.cv.presets.CompactMono}.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.coverletter.v2.presets.CompactMonoLetter}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class CompactMonoLetter {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/EditorialBlueLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/EditorialBlueLetter.java
index 516f6f68..513f885c 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/EditorialBlueLetter.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/EditorialBlueLetter.java
@@ -18,7 +18,13 @@
* Helvetica throughout, deep navy ink with bright editorial blue
* accent β matches
* {@link com.demcha.compose.document.templates.cv.presets.EditorialBlue}.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.coverletter.v2.presets.EditorialBlueLetter}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class EditorialBlueLetter {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/EngineeringResumeLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/EngineeringResumeLetter.java
index 27a45246..70eeeecd 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/EngineeringResumeLetter.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/EngineeringResumeLetter.java
@@ -17,7 +17,13 @@
*
* Navy primary, green accent, Barlow headline + Lato body β matches
* {@link com.demcha.compose.document.templates.cv.presets.EngineeringResume}.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.coverletter.v2.presets.EngineeringResumeLetter}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class EngineeringResumeLetter {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/ExecutiveLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/ExecutiveLetter.java
index 7b280808..12101ddd 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/ExecutiveLetter.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/ExecutiveLetter.java
@@ -17,7 +17,13 @@
*
* Slate primary, bronze accent, Poppins headline + Lato body β
* matches {@link com.demcha.compose.document.templates.cv.presets.Executive}.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.coverletter.v2.presets.ExecutiveLetter}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class ExecutiveLetter {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/ModernProfessionalLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/ModernProfessionalLetter.java
index 227d823a..df537871 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/ModernProfessionalLetter.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/ModernProfessionalLetter.java
@@ -21,7 +21,13 @@
* contact links), same Helvetica body type. The cover letter itself
* is a single-column letter β header on top, greeting, body
* paragraphs separated by paragraph spacing, closing.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.coverletter.v2.presets.ModernProfessionalLetter}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class ModernProfessionalLetter {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/MonogramSidebarLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/MonogramSidebarLetter.java
index 3f1f6a8c..85ec4bb0 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/MonogramSidebarLetter.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/MonogramSidebarLetter.java
@@ -20,7 +20,13 @@
* {@link com.demcha.compose.document.templates.cv.presets.MonogramSidebar}.
* The cover letter is a simple single-column letter β the CV's
* monogram sidebar is intentionally not replicated.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.coverletter.v2.presets.MonogramSidebarLetter}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class MonogramSidebarLetter {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/NordicCleanLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/NordicCleanLetter.java
index f1340b2c..7b53d52a 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/NordicCleanLetter.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/NordicCleanLetter.java
@@ -19,7 +19,13 @@
* {@link com.demcha.compose.document.templates.cv.presets.NordicClean}
* CV. Single-column letter format β header on top, greeting, body
* paragraphs, closing.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.coverletter.v2.presets.NordicCleanLetter}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class NordicCleanLetter {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/PanelLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/PanelLetter.java
index 740dd5cb..c41726d3 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/PanelLetter.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/PanelLetter.java
@@ -17,7 +17,13 @@
*
* Deep-slate primary, teal accent, Poppins headline + Lato body β
* matches {@link com.demcha.compose.document.templates.cv.presets.Panel}.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.coverletter.v2.presets.PanelLetter}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class PanelLetter {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/SidebarPortraitLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/SidebarPortraitLetter.java
index 8faee868..8a7ce9c2 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/SidebarPortraitLetter.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/SidebarPortraitLetter.java
@@ -20,7 +20,13 @@
* {@link com.demcha.compose.document.templates.cv.presets.SidebarPortrait}.
* The cover letter is a simple single-column letter β the CV's
* portrait sidebar is intentionally not replicated.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.coverletter.v2.presets.SidebarPortraitLetter}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class SidebarPortraitLetter {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/TimelineMinimalLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/TimelineMinimalLetter.java
index 5f4607c2..20473f35 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/TimelineMinimalLetter.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/TimelineMinimalLetter.java
@@ -18,7 +18,13 @@
* All-grey palette with Barlow Condensed for the headline and Lato
* body β matches
* {@link com.demcha.compose.document.templates.cv.presets.TimelineMinimal}.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.coverletter.v2.presets.TimelineMinimalLetter}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class TimelineMinimalLetter {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/package-info.java b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/package-info.java
index ce7097e6..76661b47 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/presets/package-info.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/presets/package-info.java
@@ -1,18 +1,29 @@
/**
- * Templates v2 cover-letter presets β flat copy-and-tweak recipe
+ * Superseded Gen-2 cover-letter presets β flat copy-and-tweak recipe
* classes paired one-to-one with the
* {@link com.demcha.compose.document.templates.cv.presets} CV
* preset family.
*
+ * Deprecated surface. These are the older Gen-2
+ * cover-letter presets. They are not the current standard. The
+ * current standard is the layered surface
+ * {@code com.demcha.compose.document.templates.coverletter.v2.presets}. This
+ * package is kept only for backward compatibility and is scheduled for
+ * removal in a future major.
+ *
* Each preset shares the typography palette and spacing rhythm
* of its paired CV preset, so a writer's CV and cover letter ship
* as a matched set with consistent visual identity.
*
- * To customise: copy the {@code create(...)} method body of any
- * preset into your own class and tweak the {@code CoverLetterBuilder}
- * calls (header style, body text style, spacing tokens, layout
- * choice).
+ * New code should target the layered {@code coverletter.v2} presets
+ * instead. See {@code docs/templates/v2-layered/}.
*
* @since 1.6.0
+ * @deprecated Superseded by the layered
+ * {@code com.demcha.compose.document.templates.coverletter.v2}
+ * surface (the current standard). This Gen-2 package is kept for
+ * backward compatibility and will be removed in a future major.
+ * See {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
package com.demcha.compose.document.templates.coverletter.presets;
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/spec/CoverLetterHeader.java b/src/main/java/com/demcha/compose/document/templates/coverletter/spec/CoverLetterHeader.java
index fd16cbfa..ceff37f2 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/spec/CoverLetterHeader.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/spec/CoverLetterHeader.java
@@ -17,7 +17,14 @@
* @param email optional email address; empty string when absent
* @param links ordered list of {@link Link} entries (typically
* LinkedIn, GitHub); never null after construction
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard) β the layered model
+ * {@link com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument}
+ * plus the {@code coverletter.v2} presets. Kept for backward
+ * compatibility; scheduled for removal in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public record CoverLetterHeader(
String name,
String address,
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/spec/CoverLetterSpec.java b/src/main/java/com/demcha/compose/document/templates/coverletter/spec/CoverLetterSpec.java
index 7c251281..ba8ae9ac 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/spec/CoverLetterSpec.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/spec/CoverLetterSpec.java
@@ -25,7 +25,14 @@
* {@code *italic*})
* @param closing last body line (required, may be blank);
* typically {@code "Sincerely, Alex"}
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard) β the layered model
+ * {@link com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument}
+ * plus the {@code coverletter.v2} presets. Kept for backward
+ * compatibility; scheduled for removal in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public record CoverLetterSpec(
CoverLetterHeader header,
String greeting,
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/spec/package-info.java b/src/main/java/com/demcha/compose/document/templates/coverletter/spec/package-info.java
index 8a644fb4..3f591eb8 100644
--- a/src/main/java/com/demcha/compose/document/templates/coverletter/spec/package-info.java
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/spec/package-info.java
@@ -1,9 +1,17 @@
/**
- * Templates v2 cover-letter specification records β user-facing data
+ * Superseded Gen-2 cover-letter specification records β user-facing data
* types.
*
+ * Deprecated surface. These are the older Gen-2
+ * cover-letter spec records. They are not the current standard. The
+ * current standard is the layered model
+ * {@link com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument}
+ * in the {@code com.demcha.compose.document.templates.coverletter.v2}
+ * surface. This package is kept only for backward compatibility and is
+ * scheduled for removal in a future major.
+ *
* This package holds the immutable records a user fills with their
- * cover-letter content before passing the spec to a preset for
+ * cover-letter content before passing the spec to a Gen-2 preset for
* rendering:
*
*
@@ -15,6 +23,15 @@
* {@code *italic*}) for inline emphasis.
*
*
+ * New code should target the layered {@code coverletter.v2} data model
+ * instead. See {@code docs/templates/v2-layered/}.
+ *
* @since 1.6.0
+ * @deprecated Superseded by the layered
+ * {@code com.demcha.compose.document.templates.coverletter.v2}
+ * surface (the current standard). This Gen-2 package is kept for
+ * backward compatibility and will be removed in a future major.
+ * See {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
package com.demcha.compose.document.templates.coverletter.spec;
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/components/LetterBody.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/components/LetterBody.java
new file mode 100644
index 00000000..ca092fec
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/components/LetterBody.java
@@ -0,0 +1,123 @@
+package com.demcha.compose.document.templates.coverletter.v2.components;
+
+import com.demcha.compose.document.dsl.SectionBuilder;
+import com.demcha.compose.document.style.DocumentInsets;
+import com.demcha.compose.document.style.DocumentTextDecoration;
+import com.demcha.compose.document.style.DocumentTextStyle;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.components.CvTextStyles;
+import com.demcha.compose.document.templates.cv.v2.components.RichParagraphRenderer;
+import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
+
+/**
+ * Shared cover-letter body renderer β the letter analog of
+ * {@code SectionDispatcher}.
+ *
+ * Stacks the greeting line, the ordered body paragraphs, and the
+ * closing sign-off into a single host section. Each block is rendered
+ * with the theme's body font / size / ink and line spacing through the
+ * shared {@link RichParagraphRenderer}, so inline markdown
+ * ({@code **bold**}, {@code *italic*}) is honoured exactly as in the
+ * paired CV preset's body prose.
+ *
+ * Every v2 letter preset reuses this, so all letters share one
+ * identical reading rhythm and only the masthead differs by brand β
+ * which is what makes a CV and its letter read as a matched set.
+ */
+public final class LetterBody {
+
+ private LetterBody() {
+ }
+
+ /**
+ * Renders greeting + body paragraphs + closing into {@code host}
+ * using the theme's body style and line spacing.
+ *
+ * @param host host section (typically one page-flow section)
+ * @param doc cover-letter content
+ * @param theme active brand theme β share the same instance with the
+ * paired CV preset so the body colour / font / size match
+ */
+ public static void render(SectionBuilder host, CoverLetterDocument doc,
+ CvTheme theme) {
+ render(host, doc, theme, theme.typography().sizeBody());
+ }
+
+ /**
+ * Variant that renders the letter prose at an explicit point size
+ * instead of {@code theme.typography().sizeBody()}. Used by presets
+ * whose paired CV theme carries a very small body size tuned for a
+ * dense multi-column CV (e.g. Monogram Sidebar, Timeline Minimal) β
+ * a single-column letter needs a more readable size, so the preset
+ * supplies one without disturbing the CV.
+ *
+ * @param host host section
+ * @param doc cover-letter content
+ * @param theme active brand theme (font, line spacing, ink)
+ * @param bodySize body text size in points
+ */
+ public static void render(SectionBuilder host, CoverLetterDocument doc,
+ CvTheme theme, double bodySize) {
+ DocumentTextStyle bodyStyle = CvTextStyles.of(
+ theme.typography().bodyFont(),
+ bodySize,
+ DocumentTextDecoration.DEFAULT,
+ theme.palette().ink());
+ double lineSpacing = theme.typography().bodyLineSpacing();
+ // Letter paragraph rhythm scales with the body size so a compact
+ // brand stays tight and a roomy serif brand breathes, without a
+ // separate hand-tuned token per brand.
+ double gap = bodySize * 1.25;
+ // Clear breathing room below the masthead so the letter body never
+ // reads as "stuck" to the header. Normalised: the page-flow gap
+ // between the header section and this body section already supplies
+ // some space, so we top it up to a consistent ~1.8x the body size
+ // total and subtract what the brand's pageFlowSpacing already gives.
+ // The result is the same comfortable separation under every brand's
+ // masthead, whether its CV uses a dense (0pt) or roomy (8pt) gap.
+ double headerGap = Math.max(2.0,
+ bodySize * 1.8 - theme.spacing().pageFlowSpacing());
+ // The signed name sits on the line directly below the sign-off
+ // (standard letter convention), so it gets only a small gap.
+ double signatureGap = bodySize * 0.4;
+ DocumentTextStyle signatureStyle = CvTextStyles.of(
+ theme.typography().bodyFont(),
+ bodySize,
+ DocumentTextDecoration.ITALIC,
+ theme.palette().ink());
+
+ boolean[] emitted = {false};
+ emit(host, doc.greeting(), bodyStyle, lineSpacing, headerGap, gap, emitted);
+ for (String paragraph : doc.body()) {
+ emit(host, paragraph, bodyStyle, lineSpacing, headerGap, gap, emitted);
+ }
+ // Closing block: the sign-off ("Sincerely,") on one line, then the
+ // signer's name on the line directly below it. The name is pulled
+ // from the shared identity so the signature always matches the
+ // masthead and never drifts from the paired CV.
+ if (!doc.closing().isBlank()) {
+ double top = emitted[0] ? gap : headerGap;
+ RichParagraphRenderer.render(host, doc.closing(), bodyStyle,
+ lineSpacing, DocumentInsets.top(top));
+ String signature = doc.identity().name().full();
+ if (!signature.isBlank()) {
+ RichParagraphRenderer.render(host, signature, signatureStyle,
+ lineSpacing, DocumentInsets.top(signatureGap));
+ }
+ }
+ }
+
+ private static void emit(SectionBuilder host, String text,
+ DocumentTextStyle style, double lineSpacing,
+ double firstTop, double gap, boolean[] emitted) {
+ if (text == null || text.isBlank()) {
+ return;
+ }
+ // First emitted block gets the larger header gap (separation from
+ // the masthead); every subsequent block gets the inter-paragraph gap.
+ double top = emitted[0] ? gap : firstTop;
+ RichParagraphRenderer.render(host, text, style, lineSpacing,
+ DocumentInsets.top(top));
+ emitted[0] = true;
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/components/package-info.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/components/package-info.java
new file mode 100644
index 00000000..1c4a039b
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/components/package-info.java
@@ -0,0 +1,10 @@
+/**
+ * Shared rendering components for Templates v2 cover letters.
+ *
+ * {@link com.demcha.compose.document.templates.coverletter.v2.components.LetterBody}
+ * is the letter analog of the CV {@code SectionDispatcher}: every
+ * letter preset delegates its greeting / paragraphs / closing to it so
+ * all letters share one reading rhythm and inline-markdown handling
+ * (via the reused {@code cv.v2.components.RichParagraphRenderer}).
+ */
+package com.demcha.compose.document.templates.coverletter.v2.components;
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/data/CoverLetterDocument.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/data/CoverLetterDocument.java
new file mode 100644
index 00000000..58090336
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/data/CoverLetterDocument.java
@@ -0,0 +1,126 @@
+package com.demcha.compose.document.templates.coverletter.v2.data;
+
+import com.demcha.compose.document.templates.cv.v2.data.CvIdentity;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * User-facing data record for a Templates v2 cover letter.
+ *
+ * Reuses {@link CvIdentity} for the top-of-document identity block
+ * so a writer hands the same identity object to both their CV
+ * preset and their paired cover-letter preset. Because the masthead
+ * (name, contact, links) then renders through the identical widget
+ * path, the CV and the letter read as one matched set β which is the
+ * whole point of pairing them.
+ *
+ * The letter-specific content is deliberately tiny: an opening
+ * greeting, an ordered list of body paragraphs, and a closing sign-off.
+ * This mirrors {@code CvDocument} (identity + sections) but with the
+ * far simpler single-flow shape of a letter.
+ *
+ * @param identity top-of-document identity block (required) β share
+ * the same instance with the paired CV preset
+ * @param greeting opening line (e.g. {@code "Dear Hiring Team,"}); a
+ * blank value suppresses the line; may carry inline
+ * markdown ({@code **bold**}, {@code *italic*})
+ * @param body ordered body paragraphs; blank paragraphs are skipped
+ * at render; each may carry inline markdown
+ * @param closing sign-off line (e.g. {@code "Sincerely, Alex"}); a
+ * blank value suppresses the line; may carry inline
+ * markdown
+ */
+public record CoverLetterDocument(CvIdentity identity,
+ String greeting,
+ List body,
+ String closing) {
+
+ /**
+ * Compact constructor that normalises null strings to empty and
+ * defensively copies the body list.
+ *
+ * @throws NullPointerException if {@code identity} is null
+ */
+ public CoverLetterDocument {
+ Objects.requireNonNull(identity, "identity");
+ greeting = greeting == null ? "" : greeting;
+ closing = closing == null ? "" : closing;
+ body = body == null ? List.of() : List.copyOf(body);
+ }
+
+ /**
+ * @return new fluent builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Mutable builder for {@link CoverLetterDocument}.
+ */
+ public static final class Builder {
+ private CvIdentity identity;
+ private String greeting = "";
+ private final List body = new ArrayList<>();
+ private String closing = "";
+
+ private Builder() {
+ }
+
+ /**
+ * Sets the shared identity block.
+ *
+ * @param value non-null identity (reuse the paired CV's instance)
+ * @return this builder
+ */
+ public Builder identity(CvIdentity value) {
+ this.identity = value;
+ return this;
+ }
+
+ /**
+ * Sets the opening greeting line.
+ *
+ * @param value greeting; null treated as empty
+ * @return this builder
+ */
+ public Builder greeting(String value) {
+ this.greeting = value == null ? "" : value;
+ return this;
+ }
+
+ /**
+ * Appends one body paragraph.
+ *
+ * @param value non-null paragraph text
+ * @return this builder
+ */
+ public Builder paragraph(String value) {
+ Objects.requireNonNull(value, "paragraph");
+ this.body.add(value);
+ return this;
+ }
+
+ /**
+ * Sets the closing sign-off line.
+ *
+ * @param value closing; null treated as empty
+ * @return this builder
+ */
+ public Builder closing(String value) {
+ this.closing = value == null ? "" : value;
+ return this;
+ }
+
+ /**
+ * Builds an immutable {@link CoverLetterDocument}.
+ *
+ * @return new document
+ */
+ public CoverLetterDocument build() {
+ return new CoverLetterDocument(identity, greeting, body, closing);
+ }
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/data/package-info.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/data/package-info.java
new file mode 100644
index 00000000..7237adab
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/data/package-info.java
@@ -0,0 +1,10 @@
+/**
+ * User-facing data records for Templates v2 cover letters.
+ *
+ * {@link com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument}
+ * is the single input type β it reuses
+ * {@link com.demcha.compose.document.templates.cv.v2.data.CvIdentity}
+ * for the masthead so a CV and its paired letter share one identity
+ * object and render identical headers.
+ */
+package com.demcha.compose.document.templates.coverletter.v2.data;
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/BlueBannerLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/BlueBannerLetter.java
new file mode 100644
index 00000000..7d4653c0
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/BlueBannerLetter.java
@@ -0,0 +1,98 @@
+package com.demcha.compose.document.templates.coverletter.v2.presets;
+
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.dsl.PageFlowBuilder;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.components.LetterBody;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
+import com.demcha.compose.document.templates.cv.v2.widgets.ContactLine;
+import com.demcha.compose.document.templates.cv.v2.widgets.Headline;
+
+import java.util.Objects;
+
+/**
+ * v2 cover-letter pair for the {@code BlueBanner} CV preset.
+ *
+ * Renders the identical masthead as
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.BlueBanner}
+ * β a centred PT-Serif spaced-caps name over a compact centred contact
+ * row β then a single-column letter body via the shared
+ * {@link LetterBody}. Both documents read everything from
+ * {@link CvTheme#blueBanner()}.
+ *
+ * The CV's signature blue banners decorate section titles,
+ * which a letter has none of, so the brand identity here is carried by
+ * the theme: the compact PT-Serif headline scale and the dark-blue rule
+ * tone of the contact separators / links. No preset-local colour is
+ * needed.
+ */
+public final class BlueBannerLetter {
+
+ /** Stable template identifier. */
+ public static final String ID = "blue-banner-letter";
+
+ /** Human-readable display name. */
+ public static final String DISPLAY_NAME = "Blue Banner Letter";
+
+ /** Recommended page margin (in points) β generous business-letter feel. */
+ public static final double RECOMMENDED_MARGIN = 48.0;
+
+ private BlueBannerLetter() {
+ }
+
+ /**
+ * Builds the letter with its Blue Banner theme.
+ */
+ public static DocumentTemplate create() {
+ return create(CvTheme.blueBanner());
+ }
+
+ /**
+ * Builds the letter with a caller-supplied theme (share the paired
+ * CV's theme instance for a guaranteed visual match).
+ */
+ public static DocumentTemplate create(CvTheme theme) {
+ Objects.requireNonNull(theme, "theme");
+ return new Template(theme);
+ }
+
+ private static final class Template implements DocumentTemplate {
+
+ private final CvTheme theme;
+
+ Template(CvTheme theme) {
+ this.theme = theme;
+ }
+
+ @Override
+ public String id() {
+ return ID;
+ }
+
+ @Override
+ public String displayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void compose(DocumentSession document, CoverLetterDocument doc) {
+ Objects.requireNonNull(document, "document");
+ Objects.requireNonNull(doc, "doc");
+
+ PageFlowBuilder flow = document.dsl()
+ .pageFlow()
+ .name("CoverLetterV2BlueBannerRoot")
+ .spacing(theme.spacing().pageFlowSpacing())
+ .addSection("CoverLetterV2BlueBannerHeader", section ->
+ Headline.spacedCentered(section, doc.identity().name(), theme))
+ .addSection("CoverLetterV2BlueBannerContact", section ->
+ ContactLine.centered(section, doc.identity(), theme));
+
+ flow.addSection("CoverLetterV2BlueBannerBody", host ->
+ LetterBody.render(host, doc, theme));
+
+ flow.build();
+ }
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/BoxedSectionsLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/BoxedSectionsLetter.java
new file mode 100644
index 00000000..0e3b213e
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/BoxedSectionsLetter.java
@@ -0,0 +1,103 @@
+package com.demcha.compose.document.templates.coverletter.v2.presets;
+
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.dsl.PageFlowBuilder;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.components.LetterBody;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
+import com.demcha.compose.document.templates.cv.v2.widgets.ContactLine;
+import com.demcha.compose.document.templates.cv.v2.widgets.Headline;
+
+import java.util.Objects;
+
+/**
+ * v2 cover-letter pair for the {@code BoxedSections} CV preset.
+ *
+ * Renders the identical masthead as
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.BoxedSections}
+ * β a centred letter-spaced PT-Serif name with a thin rule beneath it,
+ * then a centred pipe-separated contact line with its own rule beneath
+ * β then a single-column letter body via the shared {@link LetterBody}.
+ * Both documents read everything from {@link CvTheme#boxedClassic()}.
+ *
+ * The header is composed entirely from shared widgets
+ * ({@link Headline#spacedCentered} + {@link ContactLine#centered}) at
+ * the theme's default styles, so this preset carries no preset-local
+ * colour β the cleanest possible matched-set letter.
+ */
+public final class BoxedSectionsLetter {
+
+ /** Stable template identifier. */
+ public static final String ID = "boxed-sections-letter";
+
+ /** Human-readable display name. */
+ public static final String DISPLAY_NAME = "Boxed Sections Letter";
+
+ /** Recommended page margin (in points) β generous business-letter feel. */
+ public static final double RECOMMENDED_MARGIN = 48.0;
+
+ private BoxedSectionsLetter() {
+ }
+
+ /**
+ * Builds the letter with its Boxed Sections theme.
+ */
+ public static DocumentTemplate create() {
+ return create(CvTheme.boxedClassic());
+ }
+
+ /**
+ * Builds the letter with a caller-supplied theme (share the paired
+ * CV's theme instance for a guaranteed visual match).
+ */
+ public static DocumentTemplate create(CvTheme theme) {
+ Objects.requireNonNull(theme, "theme");
+ return new Template(theme);
+ }
+
+ private static final class Template implements DocumentTemplate {
+
+ private final CvTheme theme;
+
+ Template(CvTheme theme) {
+ this.theme = theme;
+ }
+
+ @Override
+ public String id() {
+ return ID;
+ }
+
+ @Override
+ public String displayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void compose(DocumentSession document, CoverLetterDocument doc) {
+ Objects.requireNonNull(document, "document");
+ Objects.requireNonNull(doc, "doc");
+
+ PageFlowBuilder flow = document.dsl()
+ .pageFlow()
+ .name("CoverLetterV2BoxedRoot")
+ .spacing(theme.spacing().pageFlowSpacing())
+ .addSection("CoverLetterV2BoxedHeadline", section -> {
+ section.accentBottom(theme.palette().rule(),
+ theme.spacing().accentRuleWidth());
+ Headline.spacedCentered(section, doc.identity().name(), theme);
+ })
+ .addSection("CoverLetterV2BoxedContact", section -> {
+ section.accentBottom(theme.palette().rule(),
+ theme.spacing().accentRuleWidth());
+ ContactLine.centered(section, doc.identity(), theme);
+ });
+
+ flow.addSection("CoverLetterV2BoxedBody", host ->
+ LetterBody.render(host, doc, theme));
+
+ flow.build();
+ }
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/CenteredHeadlineLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/CenteredHeadlineLetter.java
new file mode 100644
index 00000000..39e10488
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/CenteredHeadlineLetter.java
@@ -0,0 +1,133 @@
+package com.demcha.compose.document.templates.coverletter.v2.presets;
+
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.dsl.LineBuilder;
+import com.demcha.compose.document.dsl.PageFlowBuilder;
+import com.demcha.compose.document.style.DocumentInsets;
+import com.demcha.compose.document.style.DocumentTextDecoration;
+import com.demcha.compose.document.style.DocumentTextStyle;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.components.LetterBody;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.components.CvTextStyles;
+import com.demcha.compose.document.templates.cv.v2.data.CvIdentity;
+import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
+import com.demcha.compose.document.templates.cv.v2.widgets.ContactLine;
+import com.demcha.compose.document.templates.cv.v2.widgets.Headline;
+import com.demcha.compose.document.templates.cv.v2.widgets.Subheadline;
+
+import java.util.Objects;
+
+/**
+ * v2 cover-letter pair for the {@code CenteredHeadline} CV preset.
+ *
+ * Renders the identical masthead as
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.CenteredHeadline}
+ * β a centred letter-spaced Poppins name, a small spaced-caps
+ * subheadline, and a centred contact line framed by thin full-width
+ * rules β then a single-column letter body via the shared
+ * {@link LetterBody}. Both documents read everything from
+ * {@link CvTheme#centeredHeadline()}.
+ *
+ * The subheadline uses the real {@link CvIdentity#jobTitle()} (the
+ * CV preset still shows a hard-coded placeholder pending its own
+ * jobTitle wiring); on a letter the writer's actual title reads more
+ * naturally and stays a faithful match to the CV's design. The
+ * rule that the CV places below the contact is dropped here because the
+ * shared {@code LetterBody} already supplies the gap to the greeting.
+ */
+public final class CenteredHeadlineLetter {
+
+ /** Stable template identifier. */
+ public static final String ID = "centered-headline-letter";
+
+ /** Human-readable display name. */
+ public static final String DISPLAY_NAME = "Centered Headline Letter";
+
+ /** Recommended page margin (in points) β generous business-letter feel. */
+ public static final double RECOMMENDED_MARGIN = 48.0;
+
+ private CenteredHeadlineLetter() {
+ }
+
+ /**
+ * Builds the letter with its Centered Headline theme.
+ */
+ public static DocumentTemplate create() {
+ return create(CvTheme.centeredHeadline());
+ }
+
+ /**
+ * Builds the letter with a caller-supplied theme (share the paired
+ * CV's theme instance for a guaranteed visual match).
+ */
+ public static DocumentTemplate create(CvTheme theme) {
+ Objects.requireNonNull(theme, "theme");
+ return new Template(theme);
+ }
+
+ private static final class Template implements DocumentTemplate {
+
+ private final CvTheme theme;
+
+ Template(CvTheme theme) {
+ this.theme = theme;
+ }
+
+ @Override
+ public String id() {
+ return ID;
+ }
+
+ @Override
+ public String displayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void compose(DocumentSession document, CoverLetterDocument doc) {
+ Objects.requireNonNull(document, "document");
+ Objects.requireNonNull(doc, "doc");
+
+ double width = document.canvas().innerWidth();
+ CvIdentity identity = doc.identity();
+
+ PageFlowBuilder flow = document.dsl()
+ .pageFlow()
+ .name("CoverLetterV2CenteredHeadlineRoot")
+ .spacing(theme.spacing().pageFlowSpacing())
+ .addSection("CoverLetterV2CenteredHeadlineHeadline", section -> {
+ Headline.spacedCentered(section, identity.name(), theme);
+ if (!identity.jobTitle().isBlank()) {
+ Subheadline.centeredSpacedCaps(section,
+ identity.jobTitle(), subheadlineStyle());
+ }
+ })
+ .addLine(line -> rule(line, "CoverLetterV2CenteredHeadlineRule",
+ width, 7, 0))
+ .addSection("CoverLetterV2CenteredHeadlineContact", section ->
+ ContactLine.centered(section, identity, theme))
+ .addLine(line -> rule(line, "CoverLetterV2CenteredContactRule",
+ width, 0, 0));
+
+ flow.addSection("CoverLetterV2CenteredHeadlineBody", host ->
+ LetterBody.render(host, doc, theme));
+
+ flow.build();
+ }
+
+ private DocumentTextStyle subheadlineStyle() {
+ return CvTextStyles.of(theme.typography().headlineFont(), 8.6,
+ DocumentTextDecoration.DEFAULT, theme.palette().muted());
+ }
+
+ private void rule(LineBuilder line, String name, double width,
+ double top, double bottom) {
+ line.name(name)
+ .horizontal(width)
+ .color(theme.palette().rule())
+ .thickness(theme.spacing().accentRuleWidth())
+ .margin(new DocumentInsets(top, 0, bottom, 0));
+ }
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/ClassicSerifLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/ClassicSerifLetter.java
new file mode 100644
index 00000000..0368c0f9
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/ClassicSerifLetter.java
@@ -0,0 +1,144 @@
+package com.demcha.compose.document.templates.coverletter.v2.presets;
+
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.dsl.PageFlowBuilder;
+import com.demcha.compose.document.style.DocumentColor;
+import com.demcha.compose.document.style.DocumentInsets;
+import com.demcha.compose.document.style.DocumentTextDecoration;
+import com.demcha.compose.document.style.DocumentTextStyle;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.components.LetterBody;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.components.CvTextStyles;
+import com.demcha.compose.document.templates.cv.v2.data.CvIdentity;
+import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
+import com.demcha.compose.document.templates.cv.v2.widgets.ContactLine;
+import com.demcha.compose.document.templates.cv.v2.widgets.Headline;
+
+import java.util.Objects;
+
+/**
+ * v2 cover-letter pair for the {@code ClassicSerif} CV preset.
+ *
+ * Renders the identical masthead as
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.ClassicSerif}
+ * β a centred letter-spaced PT-Serif name, a thin tan rule, and a
+ * centred contact line with tan-accent underlined links β then a
+ * single-column letter body via the shared {@link LetterBody}. Both
+ * documents read their palette / typography from
+ * {@link CvTheme#classicSerif()}.
+ *
+ * The header mirrors the CV's preset-local header DSL (spaced name +
+ * rule line + centred contact). The bronze {@code ACCENT} is mirrored
+ * from the CV, which keeps it preset-local there because no other brand
+ * shares that fifth colour token.
+ */
+public final class ClassicSerifLetter {
+
+ /** Stable template identifier. */
+ public static final String ID = "classic-serif-letter";
+
+ /** Human-readable display name. */
+ public static final String DISPLAY_NAME = "Classic Serif Letter";
+
+ /** Recommended page margin (in points) β generous business-letter feel. */
+ public static final double RECOMMENDED_MARGIN = 48.0;
+
+ /** Bronze accent for contact links. Mirrors the ClassicSerif CV's preset-local token. */
+ private static final DocumentColor ACCENT = DocumentColor.rgb(126, 93, 52);
+
+ private ClassicSerifLetter() {
+ }
+
+ /**
+ * Builds the letter with its Classic Serif theme.
+ */
+ public static DocumentTemplate create() {
+ return create(CvTheme.classicSerif());
+ }
+
+ /**
+ * Builds the letter with a caller-supplied theme (share the paired
+ * CV's theme instance for a guaranteed visual match).
+ */
+ public static DocumentTemplate create(CvTheme theme) {
+ Objects.requireNonNull(theme, "theme");
+ return new Template(theme);
+ }
+
+ private static final class Template implements DocumentTemplate {
+
+ private final CvTheme theme;
+
+ Template(CvTheme theme) {
+ this.theme = theme;
+ }
+
+ @Override
+ public String id() {
+ return ID;
+ }
+
+ @Override
+ public String displayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void compose(DocumentSession document, CoverLetterDocument doc) {
+ Objects.requireNonNull(document, "document");
+ Objects.requireNonNull(doc, "doc");
+
+ double width = document.canvas().innerWidth();
+ PageFlowBuilder flow = document.dsl()
+ .pageFlow()
+ .name("CoverLetterV2ClassicSerifRoot")
+ .spacing(theme.spacing().pageFlowSpacing());
+
+ addHeader(flow, doc.identity(), width);
+
+ flow.addSection("CoverLetterV2ClassicSerifBody", host ->
+ LetterBody.render(host, doc, theme));
+
+ flow.build();
+ }
+
+ private void addHeader(PageFlowBuilder flow, CvIdentity identity,
+ double width) {
+ flow.addSection("CoverLetterV2ClassicSerifHeader", section -> {
+ section.spacing(5);
+ Headline.spacedCentered(section, identity.name(), theme);
+ section.addLine(line -> line
+ .name("CoverLetterV2ClassicSerifHeaderRule")
+ .horizontal(width)
+ .color(theme.palette().rule())
+ .thickness(theme.spacing().accentRuleWidth())
+ .margin(new DocumentInsets(1, 0, 0, 0)));
+ ContactLine.centered(section, identity, theme,
+ contactMetaStyle(), contactLinkStyle(),
+ contactSeparatorStyle());
+ });
+ }
+
+ private DocumentTextStyle contactMetaStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.DEFAULT,
+ theme.palette().muted());
+ }
+
+ private DocumentTextStyle contactLinkStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.UNDERLINE,
+ ACCENT);
+ }
+
+ private DocumentTextStyle contactSeparatorStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.DEFAULT,
+ theme.palette().rule());
+ }
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/CompactMonoLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/CompactMonoLetter.java
new file mode 100644
index 00000000..654411df
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/CompactMonoLetter.java
@@ -0,0 +1,163 @@
+package com.demcha.compose.document.templates.coverletter.v2.presets;
+
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.dsl.PageFlowBuilder;
+import com.demcha.compose.document.style.DocumentColor;
+import com.demcha.compose.document.style.DocumentInsets;
+import com.demcha.compose.document.style.DocumentTextDecoration;
+import com.demcha.compose.document.style.DocumentTextStyle;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.components.LetterBody;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.components.CvTextStyles;
+import com.demcha.compose.document.templates.cv.v2.data.CvIdentity;
+import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
+import com.demcha.compose.document.templates.cv.v2.widgets.ContactLine;
+import com.demcha.compose.document.templates.cv.v2.widgets.Headline;
+
+import java.util.Objects;
+
+/**
+ * v2 cover-letter pair for the {@code CompactMono} CV preset.
+ *
+ * Carries the CV's signature dark command-bar header
+ * into the letter: a near-black rounded band holding the UPPERCASE
+ * left-aligned name over a left-aligned contact line with cyan links and
+ * grey separators β the same header as
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.CompactMono}.
+ * Below it, a single-column letter body via the shared
+ * {@link LetterBody}. Body palette / typography come from
+ * {@link CvTheme#compactMono()}.
+ *
+ * The four command-bar colours are mirrored from the CV, where they
+ * are preset-local. A near-invisible width rule (band-coloured, 0.1pt)
+ * pins the band to the full content width β the same trick the CV uses
+ * so the dark bar spans the page instead of shrinking to the name.
+ */
+public final class CompactMonoLetter {
+
+ /** Stable template identifier. */
+ public static final String ID = "compact-mono-letter";
+
+ /** Human-readable display name. */
+ public static final String DISPLAY_NAME = "Compact Mono Letter";
+
+ /** Recommended page margin (in points) β generous business-letter feel. */
+ public static final double RECOMMENDED_MARGIN = 48.0;
+
+ /** Near-black command-bar fill. Mirrors the CompactMono CV token. */
+ private static final DocumentColor HEADER = DocumentColor.rgb(18, 24, 32);
+
+ /** Contact metadata over the dark band. Mirrors the CV token. */
+ private static final DocumentColor HEADER_SOFT = DocumentColor.rgb(192, 207, 219);
+
+ /** Cyan contact-link colour over the band. Mirrors the CV token. */
+ private static final DocumentColor LINK_CYAN = DocumentColor.rgb(108, 213, 222);
+
+ /** Contact separator colour over the band. Mirrors the CV token. */
+ private static final DocumentColor SEPARATOR_GRAY = DocumentColor.rgb(102, 117, 132);
+
+ private CompactMonoLetter() {
+ }
+
+ /**
+ * Builds the letter with its Compact Mono theme.
+ */
+ public static DocumentTemplate create() {
+ return create(CvTheme.compactMono());
+ }
+
+ /**
+ * Builds the letter with a caller-supplied theme (share the paired
+ * CV's theme instance for a guaranteed visual match).
+ */
+ public static DocumentTemplate create(CvTheme theme) {
+ Objects.requireNonNull(theme, "theme");
+ return new Template(theme);
+ }
+
+ private static final class Template implements DocumentTemplate {
+
+ private final CvTheme theme;
+
+ Template(CvTheme theme) {
+ this.theme = theme;
+ }
+
+ @Override
+ public String id() {
+ return ID;
+ }
+
+ @Override
+ public String displayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void compose(DocumentSession document, CoverLetterDocument doc) {
+ Objects.requireNonNull(document, "document");
+ Objects.requireNonNull(doc, "doc");
+
+ double width = document.canvas().innerWidth();
+ PageFlowBuilder flow = document.dsl()
+ .pageFlow()
+ .name("CoverLetterV2CompactMonoRoot")
+ .spacing(theme.spacing().pageFlowSpacing());
+
+ addHeader(flow, doc.identity(), width);
+
+ flow.addSection("CoverLetterV2CompactMonoBody", host ->
+ LetterBody.render(host, doc, theme));
+
+ flow.build();
+ }
+
+ private void addHeader(PageFlowBuilder flow, CvIdentity identity,
+ double width) {
+ flow.addSection("CoverLetterV2CompactMonoHeader", section -> {
+ section.spacing(4)
+ .padding(new DocumentInsets(13, 16, 14, 16))
+ .fillColor(HEADER)
+ .cornerRadius(3);
+ section.addSection("Name", name ->
+ Headline.uppercaseLeftAligned(name, identity.name(),
+ theme, headerNameStyle()));
+ section.addSection("Contact", contact ->
+ ContactLine.leftAligned(contact, identity, theme,
+ headerMetaStyle(), headerLinkStyle(),
+ headerSeparatorStyle()));
+ section.addLine(line -> line
+ .name("CoverLetterV2CompactMonoHeaderWidthRule")
+ .horizontal(Math.max(0, width - 32))
+ .color(HEADER)
+ .thickness(0.1)
+ .margin(DocumentInsets.zero()));
+ });
+ }
+
+ private DocumentTextStyle headerNameStyle() {
+ return CvTextStyles.of(theme.typography().headlineFont(),
+ theme.typography().sizeHeadline(),
+ DocumentTextDecoration.BOLD, DocumentColor.WHITE);
+ }
+
+ private DocumentTextStyle headerMetaStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.DEFAULT, HEADER_SOFT);
+ }
+
+ private DocumentTextStyle headerLinkStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.UNDERLINE, LINK_CYAN);
+ }
+
+ private DocumentTextStyle headerSeparatorStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.DEFAULT, SEPARATOR_GRAY);
+ }
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/EditorialBlueLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/EditorialBlueLetter.java
new file mode 100644
index 00000000..24f12dac
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/EditorialBlueLetter.java
@@ -0,0 +1,126 @@
+package com.demcha.compose.document.templates.coverletter.v2.presets;
+
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.dsl.PageFlowBuilder;
+import com.demcha.compose.document.style.DocumentColor;
+import com.demcha.compose.document.style.DocumentInsets;
+import com.demcha.compose.document.style.DocumentTextDecoration;
+import com.demcha.compose.document.style.DocumentTextStyle;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.components.LetterBody;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.components.CvTextStyles;
+import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
+import com.demcha.compose.document.templates.cv.v2.widgets.Masthead;
+import com.demcha.compose.font.FontName;
+
+import java.util.Objects;
+
+/**
+ * v2 cover-letter pair for the {@code EditorialBlue} CV preset.
+ *
+ * Renders the identical masthead as
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.EditorialBlue}
+ * β a centred navy Helvetica name (with the job-title subtitle), centred
+ * contact metadata, and blue underlined profile links, via the shared
+ * {@link Masthead#centered} widget β then a single-column letter body
+ * via the shared {@link LetterBody}. Both documents read their palette /
+ * typography from {@link CvTheme#editorialBlue()}.
+ *
+ * Only the navy {@code NAME_COLOR} is mirrored from the CV (its
+ * preset-local token); everything else flows through {@code Masthead}
+ * and the theme.
+ */
+public final class EditorialBlueLetter {
+
+ /** Stable template identifier. */
+ public static final String ID = "editorial-blue-letter";
+
+ /** Human-readable display name. */
+ public static final String DISPLAY_NAME = "Editorial Blue Letter";
+
+ /** Recommended page margin (in points) β generous business-letter feel. */
+ public static final double RECOMMENDED_MARGIN = 48.0;
+
+ /** Navy display name. Mirrors the EditorialBlue CV's preset-local token. */
+ private static final DocumentColor NAME_COLOR = DocumentColor.rgb(18, 31, 72);
+
+ private EditorialBlueLetter() {
+ }
+
+ /**
+ * Builds the letter with its Editorial Blue theme.
+ */
+ public static DocumentTemplate create() {
+ return create(CvTheme.editorialBlue());
+ }
+
+ /**
+ * Builds the letter with a caller-supplied theme (share the paired
+ * CV's theme instance for a guaranteed visual match).
+ */
+ public static DocumentTemplate create(CvTheme theme) {
+ Objects.requireNonNull(theme, "theme");
+ return new Template(theme);
+ }
+
+ private static final class Template implements DocumentTemplate {
+
+ private final CvTheme theme;
+
+ Template(CvTheme theme) {
+ this.theme = theme;
+ }
+
+ @Override
+ public String id() {
+ return ID;
+ }
+
+ @Override
+ public String displayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void compose(DocumentSession document, CoverLetterDocument doc) {
+ Objects.requireNonNull(document, "document");
+ Objects.requireNonNull(doc, "doc");
+
+ PageFlowBuilder flow = document.dsl()
+ .pageFlow()
+ .name("CoverLetterV2EditorialBlueRoot")
+ .spacing(theme.spacing().pageFlowSpacing());
+
+ flow.addSection("CoverLetterV2EditorialBlueHeader", section ->
+ Masthead.centered(section, doc.identity(), theme,
+ mastheadStyle()));
+
+ flow.addSection("CoverLetterV2EditorialBlueBody", host ->
+ LetterBody.render(host, doc, theme));
+
+ flow.build();
+ }
+
+ private Masthead.Style mastheadStyle() {
+ DocumentTextStyle nameStyle = CvTextStyles.of(FontName.HELVETICA_BOLD,
+ theme.typography().sizeHeadline(),
+ DocumentTextDecoration.BOLD, NAME_COLOR);
+ DocumentTextStyle titleStyle = CvTextStyles.of(FontName.HELVETICA,
+ 10.0, DocumentTextDecoration.DEFAULT,
+ theme.palette().ink());
+ DocumentTextStyle linkStyle = CvTextStyles.of(FontName.HELVETICA,
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.UNDERLINE,
+ theme.palette().rule());
+ return Masthead.Style.builder()
+ .nameStyle(nameStyle)
+ .titleStyle(titleStyle)
+ .metaStyle(theme.contactStyle())
+ .linkStyle(linkStyle)
+ .separatorStyle(theme.contactStyle())
+ .lineMargin(DocumentInsets.top(1))
+ .build();
+ }
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/EngineeringResumeLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/EngineeringResumeLetter.java
new file mode 100644
index 00000000..39bfa281
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/EngineeringResumeLetter.java
@@ -0,0 +1,230 @@
+package com.demcha.compose.document.templates.coverletter.v2.presets;
+
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.dsl.PageFlowBuilder;
+import com.demcha.compose.document.dsl.SectionBuilder;
+import com.demcha.compose.document.node.DocumentLinkOptions;
+import com.demcha.compose.document.node.TextAlign;
+import com.demcha.compose.document.style.DocumentColor;
+import com.demcha.compose.document.style.DocumentCornerRadius;
+import com.demcha.compose.document.style.DocumentInsets;
+import com.demcha.compose.document.style.DocumentTextDecoration;
+import com.demcha.compose.document.style.DocumentTextStyle;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.components.LetterBody;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.components.CvTextStyles;
+import com.demcha.compose.document.templates.cv.v2.components.MarkdownInline;
+import com.demcha.compose.document.templates.cv.v2.data.CvIdentity;
+import com.demcha.compose.document.templates.cv.v2.data.CvLink;
+import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * v2 cover-letter pair for the {@code EngineeringResume} CV preset.
+ *
+ * Carries the CV's signature full-width navy command
+ * header into the letter: a deep-navy band (rounded top, green
+ * accent strip beneath) holding the UPPERCASE name + role subtitle on
+ * the left and a right-aligned contact stack with cyan-green underlined
+ * links β the same masthead as
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.EngineeringResume}.
+ * Below the band, a single-column letter body via the shared
+ * {@link LetterBody}. Body palette / typography come from
+ * {@link CvTheme#engineeringResume()}.
+ *
+ * The five navy-header colours are mirrored from the CV, where they
+ * are preset-local (the theme only covers body ink / muted / rule /
+ * profile-band fill β no other brand shares this navy command look).
+ */
+public final class EngineeringResumeLetter {
+
+ /** Stable template identifier. */
+ public static final String ID = "engineering-resume-letter";
+
+ /** Human-readable display name. */
+ public static final String DISPLAY_NAME = "Engineering Resume Letter";
+
+ /** Recommended page margin (in points) β generous business-letter feel. */
+ public static final double RECOMMENDED_MARGIN = 48.0;
+
+ /** Deep navy command-header fill. Mirrors the EngineeringResume CV token. */
+ private static final DocumentColor NAVY = DocumentColor.rgb(13, 32, 47);
+
+ /** Green accent strip beneath the header. Mirrors the CV token. */
+ private static final DocumentColor GREEN = DocumentColor.rgb(27, 145, 104);
+
+ /** Role-subtitle colour under the name. Mirrors the CV token. */
+ private static final DocumentColor SUBTITLE_COLOR = DocumentColor.rgb(190, 209, 219);
+
+ /** Contact metadata colour over the navy header. Mirrors the CV token. */
+ private static final DocumentColor CONTACT_META = DocumentColor.rgb(196, 211, 220);
+
+ /** Cyan-green contact-link colour over the navy header. Mirrors the CV token. */
+ private static final DocumentColor CONTACT_LINK = DocumentColor.rgb(78, 207, 161);
+
+ private EngineeringResumeLetter() {
+ }
+
+ /**
+ * Builds the letter with its Engineering Resume theme.
+ */
+ public static DocumentTemplate create() {
+ return create(CvTheme.engineeringResume());
+ }
+
+ /**
+ * Builds the letter with a caller-supplied theme (share the paired
+ * CV's theme instance for a guaranteed visual match).
+ */
+ public static DocumentTemplate create(CvTheme theme) {
+ Objects.requireNonNull(theme, "theme");
+ return new Template(theme);
+ }
+
+ private static final class Template implements DocumentTemplate {
+
+ private final CvTheme theme;
+
+ Template(CvTheme theme) {
+ this.theme = theme;
+ }
+
+ @Override
+ public String id() {
+ return ID;
+ }
+
+ @Override
+ public String displayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void compose(DocumentSession document, CoverLetterDocument doc) {
+ Objects.requireNonNull(document, "document");
+ Objects.requireNonNull(doc, "doc");
+
+ PageFlowBuilder flow = document.dsl()
+ .pageFlow()
+ .name("CoverLetterV2EngineeringResumeRoot")
+ .spacing(theme.spacing().pageFlowSpacing());
+
+ addHeader(flow, doc.identity());
+
+ flow.addSection("CoverLetterV2EngineeringResumeBody", host ->
+ LetterBody.render(host, doc, theme));
+
+ flow.build();
+ }
+
+ private void addHeader(PageFlowBuilder flow, CvIdentity identity) {
+ flow.addSection("CoverLetterV2EngineeringResumeHeader", section -> section
+ .spacing(5)
+ .padding(new DocumentInsets(13, 15, 13, 15))
+ .fillColor(NAVY)
+ .cornerRadius(DocumentCornerRadius.top(
+ theme.spacing().bannerCornerRadius()))
+ .accentBottom(GREEN, theme.spacing().accentRuleWidth())
+ .addRow("CoverLetterV2EngineeringResumeHeaderRow", row -> row
+ .spacing(12)
+ .weights(1.15, 0.85)
+ .addSection("CoverLetterV2EngineeringResumeIdentity",
+ block -> addIdentityBlock(block, identity))
+ .addSection("CoverLetterV2EngineeringResumeContact",
+ contact -> addContactStack(contact, identity))));
+ }
+
+ private void addIdentityBlock(SectionBuilder block, CvIdentity identity) {
+ block.padding(DocumentInsets.zero())
+ .spacing(3)
+ .addParagraph(paragraph -> paragraph
+ .text(identity.name().full().toUpperCase(Locale.ROOT))
+ .textStyle(nameStyle())
+ .autoSize(theme.typography().sizeHeadline(), 19.0)
+ .margin(DocumentInsets.zero()));
+ String subtitle = headerSubtitleText(identity);
+ if (!subtitle.isBlank()) {
+ block.addParagraph(paragraph -> paragraph
+ .text(subtitle)
+ .textStyle(subtitleStyle())
+ .margin(DocumentInsets.zero()));
+ }
+ }
+
+ private void addContactStack(SectionBuilder section, CvIdentity identity) {
+ section.spacing(2).padding(DocumentInsets.zero());
+ DocumentTextStyle meta = contactMetaStyle();
+ DocumentTextStyle link = contactLinkStyle();
+ for (ContactPart part : contactParts(identity)) {
+ section.addParagraph(paragraph -> paragraph
+ .text(part.text())
+ .textStyle(part.linkOptions() == null ? meta : link)
+ .link(part.linkOptions())
+ .align(TextAlign.RIGHT)
+ .margin(DocumentInsets.zero()));
+ }
+ }
+
+ private DocumentTextStyle nameStyle() {
+ return CvTextStyles.of(theme.typography().headlineFont(),
+ theme.typography().sizeHeadline(),
+ DocumentTextDecoration.BOLD, DocumentColor.WHITE);
+ }
+
+ private DocumentTextStyle subtitleStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(), 7.6,
+ DocumentTextDecoration.BOLD, SUBTITLE_COLOR);
+ }
+
+ private DocumentTextStyle contactMetaStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.DEFAULT, CONTACT_META);
+ }
+
+ private DocumentTextStyle contactLinkStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.UNDERLINE, CONTACT_LINK);
+ }
+
+ private static String headerSubtitleText(CvIdentity identity) {
+ String jobTitle = identity.jobTitle();
+ if (jobTitle == null || jobTitle.isBlank()) {
+ return "";
+ }
+ return MarkdownInline.plainText(jobTitle).toUpperCase(Locale.ROOT);
+ }
+
+ private static List contactParts(CvIdentity identity) {
+ List parts = new ArrayList<>();
+ addPart(parts, identity.contact().address(), null);
+ addPart(parts, identity.contact().phone(), null);
+ String email = identity.contact().email();
+ if (!email.isBlank()) {
+ addPart(parts, email, new DocumentLinkOptions("mailto:" + email));
+ }
+ for (CvLink link : identity.links()) {
+ addPart(parts, link.label(), link.url().isBlank()
+ ? null
+ : new DocumentLinkOptions(link.url().trim()));
+ }
+ return List.copyOf(parts);
+ }
+
+ private static void addPart(List parts, String text,
+ DocumentLinkOptions linkOptions) {
+ if (text != null && !text.isBlank()) {
+ parts.add(new ContactPart(text.trim(), linkOptions));
+ }
+ }
+
+ private record ContactPart(String text, DocumentLinkOptions linkOptions) {
+ }
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/ExecutiveLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/ExecutiveLetter.java
new file mode 100644
index 00000000..d2f8ee9b
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/ExecutiveLetter.java
@@ -0,0 +1,220 @@
+package com.demcha.compose.document.templates.coverletter.v2.presets;
+
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.dsl.PageFlowBuilder;
+import com.demcha.compose.document.dsl.SectionBuilder;
+import com.demcha.compose.document.node.DocumentLinkOptions;
+import com.demcha.compose.document.node.TextAlign;
+import com.demcha.compose.document.style.DocumentColor;
+import com.demcha.compose.document.style.DocumentInsets;
+import com.demcha.compose.document.style.DocumentTextDecoration;
+import com.demcha.compose.document.style.DocumentTextStyle;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.components.LetterBody;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.components.CvTextStyles;
+import com.demcha.compose.document.templates.cv.v2.components.TextOrnaments;
+import com.demcha.compose.document.templates.cv.v2.data.CvIdentity;
+import com.demcha.compose.document.templates.cv.v2.data.CvLink;
+import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
+import com.demcha.compose.document.templates.cv.v2.widgets.Headline;
+import com.demcha.compose.font.FontName;
+
+import java.util.Objects;
+
+/**
+ * v2 cover-letter pair for the {@code Executive} CV preset.
+ *
+ * Renders the identical masthead as
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.Executive}
+ * β UPPERCASE name in deep slate, a {@code address | phone} meta line, a
+ * bronze-underlined link row, and a thin full-width muted rule β then a
+ * single-column letter body (greeting, paragraphs, closing) via the
+ * shared {@link LetterBody}. Both documents read all colour, font, and
+ * spacing from {@link CvTheme#executive()}, so a writer's CV and cover
+ * letter ship as one matched set.
+ *
+ * The masthead block is preset-local inline DSL mirroring the CV's,
+ * because the CV's header is itself preset-local (V1 splits meta and
+ * links across two rows β no shared v2 contact widget has that exact
+ * shape today). When a second brand needs the same header shape, this
+ * block should be promoted to a shared {@code coverletter/v2/widgets}
+ * letterhead widget the CV preset can also adopt.
+ */
+public final class ExecutiveLetter {
+
+ /** Stable template identifier. */
+ public static final String ID = "executive-letter";
+
+ /** Human-readable display name. */
+ public static final String DISPLAY_NAME = "Executive Letter";
+
+ /** Recommended page margin (in points) β generous business-letter feel. */
+ public static final double RECOMMENDED_MARGIN = 48.0;
+
+ /**
+ * Deeper slate of the Executive masthead. Mirrors the preset-local
+ * {@code PRIMARY_NAME} token of the Executive CV (the theme's
+ * {@code palette().ink()} is the lighter body slate); kept local
+ * here for the same reason it is local there β no other brand
+ * shares it.
+ */
+ private static final DocumentColor PRIMARY_NAME =
+ DocumentColor.rgb(24, 35, 51);
+
+ /**
+ * Warm bronze of the Executive contact links. Mirrors the
+ * preset-local {@code ACCENT} token of the Executive CV.
+ */
+ private static final DocumentColor ACCENT =
+ DocumentColor.rgb(172, 112, 55);
+
+ private ExecutiveLetter() {
+ }
+
+ /**
+ * Builds the letter with its Executive theme.
+ */
+ public static DocumentTemplate create() {
+ return create(CvTheme.executive());
+ }
+
+ /**
+ * Builds the letter with a caller-supplied theme (share the paired
+ * CV's theme instance for a guaranteed visual match).
+ */
+ public static DocumentTemplate create(CvTheme theme) {
+ Objects.requireNonNull(theme, "theme");
+ return new Template(theme);
+ }
+
+ private static final class Template implements DocumentTemplate {
+
+ private final CvTheme theme;
+
+ Template(CvTheme theme) {
+ this.theme = theme;
+ }
+
+ @Override
+ public String id() {
+ return ID;
+ }
+
+ @Override
+ public String displayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void compose(DocumentSession document, CoverLetterDocument doc) {
+ Objects.requireNonNull(document, "document");
+ Objects.requireNonNull(doc, "doc");
+
+ double width = document.canvas().innerWidth();
+ PageFlowBuilder flow = document.dsl()
+ .pageFlow()
+ .name("CoverLetterV2ExecutiveRoot")
+ .spacing(theme.spacing().pageFlowSpacing());
+
+ addHeader(flow, doc.identity(), width);
+
+ flow.addSection("CoverLetterV2ExecutiveBody", host ->
+ LetterBody.render(host, doc, theme));
+
+ flow.build();
+ }
+
+ private void addHeader(PageFlowBuilder flow, CvIdentity identity,
+ double width) {
+ flow.addSection("CoverLetterV2ExecutiveHeader", section -> {
+ section.spacing(2)
+ .padding(DocumentInsets.zero());
+ Headline.uppercaseLeftAligned(section, identity.name(), theme,
+ nameStyle());
+ String meta = TextOrnaments.joinPipe(identity.contact().address(),
+ identity.contact().phone());
+ if (!meta.isBlank()) {
+ section.addParagraph(paragraph -> paragraph
+ .text(meta)
+ .textStyle(metaStyle())
+ .align(TextAlign.LEFT)
+ .margin(DocumentInsets.top(2)));
+ }
+ addLinkRow(section, identity);
+ section.addLine(line -> line
+ .name("CoverLetterV2ExecutiveHeaderRule")
+ .horizontal(width)
+ .color(theme.palette().rule())
+ .thickness(theme.spacing().accentRuleWidth())
+ .margin(DocumentInsets.top(5)));
+ });
+ }
+
+ private void addLinkRow(SectionBuilder section, CvIdentity identity) {
+ boolean hasEmail = !identity.contact().email().isBlank();
+ boolean hasLinks = !identity.links().isEmpty();
+ if (!hasEmail && !hasLinks) {
+ return;
+ }
+ DocumentTextStyle bodyStyle = linkRowBodyStyle();
+ DocumentTextStyle linkStyle = linkRowLinkStyle();
+ section.addParagraph(paragraph -> paragraph
+ .textStyle(bodyStyle)
+ .align(TextAlign.LEFT)
+ .margin(DocumentInsets.top(1))
+ .rich(rich -> {
+ boolean first = true;
+ String email = identity.contact().email();
+ if (!email.isBlank()) {
+ rich.with(email, linkStyle,
+ new DocumentLinkOptions("mailto:" + email));
+ first = false;
+ }
+ for (CvLink link : identity.links()) {
+ if (link.label().isBlank()) {
+ continue;
+ }
+ if (!first) {
+ rich.style(" | ", bodyStyle);
+ }
+ first = false;
+ if (link.url().isBlank()) {
+ rich.style(link.label(), bodyStyle);
+ } else {
+ rich.with(link.label(), linkStyle,
+ new DocumentLinkOptions(link.url()));
+ }
+ }
+ }));
+ }
+
+ private DocumentTextStyle nameStyle() {
+ return CvTextStyles.of(FontName.POPPINS,
+ theme.typography().sizeHeadline(),
+ DocumentTextDecoration.BOLD,
+ PRIMARY_NAME);
+ }
+
+ private DocumentTextStyle metaStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.DEFAULT,
+ theme.palette().ink());
+ }
+
+ private DocumentTextStyle linkRowBodyStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeBody(),
+ DocumentTextDecoration.DEFAULT,
+ theme.palette().ink());
+ }
+
+ private DocumentTextStyle linkRowLinkStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeBody(),
+ DocumentTextDecoration.UNDERLINE,
+ ACCENT);
+ }
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/ModernProfessionalLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/ModernProfessionalLetter.java
new file mode 100644
index 00000000..d2017538
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/ModernProfessionalLetter.java
@@ -0,0 +1,130 @@
+package com.demcha.compose.document.templates.coverletter.v2.presets;
+
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.dsl.PageFlowBuilder;
+import com.demcha.compose.document.style.DocumentColor;
+import com.demcha.compose.document.style.DocumentTextDecoration;
+import com.demcha.compose.document.style.DocumentTextStyle;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.components.LetterBody;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.components.CvTextStyles;
+import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
+import com.demcha.compose.document.templates.cv.v2.widgets.ContactLine;
+import com.demcha.compose.document.templates.cv.v2.widgets.Headline;
+import com.demcha.compose.font.FontName;
+
+import java.util.Objects;
+
+/**
+ * v2 cover-letter pair for the {@code ModernProfessional} CV preset.
+ *
+ * Renders the identical masthead as
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.ModernProfessional}
+ * β a right-aligned slate-blue Helvetica display name over a two-row,
+ * right-aligned contact stack with royal-blue underlined links and a
+ * bottom accent rule β then a single-column letter body via the shared
+ * {@link LetterBody}. Both documents read their scale and palette from
+ * {@link CvTheme#modernProfessional()}.
+ *
+ * Unlike Executive, the header is composed almost entirely from
+ * shared widgets ({@link Headline#rightAligned} +
+ * {@link ContactLine#twoRowRightAligned}); only the three preset-local
+ * colours (slate-blue name, royal-blue links) are mirrored from the CV,
+ * which keeps them preset-local there because no other brand shares
+ * them.
+ */
+public final class ModernProfessionalLetter {
+
+ /** Stable template identifier. */
+ public static final String ID = "modern-professional-letter";
+
+ /** Human-readable display name. */
+ public static final String DISPLAY_NAME = "Modern Professional Letter";
+
+ /** Recommended page margin (in points) β generous business-letter feel. */
+ public static final double RECOMMENDED_MARGIN = 48.0;
+
+ /** Slate-blue display name. Mirrors the ModernProfessional CV's preset-local token. */
+ private static final DocumentColor NAME_COLOR = DocumentColor.rgb(44, 62, 80);
+
+ /** Royal-blue contact links. Mirrors the ModernProfessional CV's preset-local token. */
+ private static final DocumentColor LINK_COLOR = DocumentColor.rgb(65, 105, 225);
+
+ private ModernProfessionalLetter() {
+ }
+
+ /**
+ * Builds the letter with its Modern Professional theme.
+ */
+ public static DocumentTemplate create() {
+ return create(CvTheme.modernProfessional());
+ }
+
+ /**
+ * Builds the letter with a caller-supplied theme (share the paired
+ * CV's theme instance for a guaranteed visual match).
+ */
+ public static DocumentTemplate create(CvTheme theme) {
+ Objects.requireNonNull(theme, "theme");
+ return new Template(theme);
+ }
+
+ private static final class Template implements DocumentTemplate {
+
+ private final CvTheme theme;
+
+ Template(CvTheme theme) {
+ this.theme = theme;
+ }
+
+ @Override
+ public String id() {
+ return ID;
+ }
+
+ @Override
+ public String displayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void compose(DocumentSession document, CoverLetterDocument doc) {
+ Objects.requireNonNull(document, "document");
+ Objects.requireNonNull(doc, "doc");
+
+ DocumentTextStyle nameStyle = CvTextStyles.of(FontName.HELVETICA_BOLD,
+ theme.typography().sizeHeadline(),
+ DocumentTextDecoration.BOLD, NAME_COLOR);
+ DocumentTextStyle contactBodyStyle = CvTextStyles.of(FontName.HELVETICA,
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.DEFAULT, theme.palette().ink());
+ DocumentTextStyle contactLinkStyle = CvTextStyles.of(FontName.HELVETICA,
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.UNDERLINE, LINK_COLOR);
+ DocumentTextStyle contactSeparatorStyle = CvTextStyles.of(FontName.HELVETICA,
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.DEFAULT, theme.palette().rule());
+
+ PageFlowBuilder flow = document.dsl()
+ .pageFlow()
+ .name("CoverLetterV2ModernRoot")
+ .spacing(theme.spacing().pageFlowSpacing())
+ .addSection("Header", section ->
+ Headline.rightAligned(section, doc.identity().name(),
+ theme, nameStyle))
+ .addSection("Contact", section -> {
+ section.accentBottom(theme.palette().rule(),
+ theme.spacing().accentRuleWidth());
+ ContactLine.twoRowRightAligned(section, doc.identity(),
+ theme, contactBodyStyle, contactLinkStyle,
+ contactSeparatorStyle);
+ });
+
+ flow.addSection("CoverLetterV2ModernBody", host ->
+ LetterBody.render(host, doc, theme));
+
+ flow.build();
+ }
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/MonogramSidebarLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/MonogramSidebarLetter.java
new file mode 100644
index 00000000..0c4e2bbe
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/MonogramSidebarLetter.java
@@ -0,0 +1,261 @@
+package com.demcha.compose.document.templates.coverletter.v2.presets;
+
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.dsl.EllipseBuilder;
+import com.demcha.compose.document.dsl.LayerStackBuilder;
+import com.demcha.compose.document.dsl.PageFlowBuilder;
+import com.demcha.compose.document.dsl.ParagraphBuilder;
+import com.demcha.compose.document.dsl.SectionBuilder;
+import com.demcha.compose.document.node.LayerAlign;
+import com.demcha.compose.document.node.LayerStackNode;
+import com.demcha.compose.document.node.SpacerNode;
+import com.demcha.compose.document.node.TextAlign;
+import com.demcha.compose.document.style.DocumentColor;
+import com.demcha.compose.document.style.DocumentInsets;
+import com.demcha.compose.document.style.DocumentStroke;
+import com.demcha.compose.document.style.DocumentTextDecoration;
+import com.demcha.compose.document.style.DocumentTextStyle;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.components.LetterBody;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.components.CvTextStyles;
+import com.demcha.compose.document.templates.cv.v2.components.TextOrnaments;
+import com.demcha.compose.document.templates.cv.v2.data.CvIdentity;
+import com.demcha.compose.document.templates.cv.v2.data.CvName;
+import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
+import com.demcha.compose.document.templates.cv.v2.widgets.ContactLine;
+import com.demcha.compose.font.FontName;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * v2 cover-letter pair for the {@code MonogramSidebar} CV preset.
+ *
+ * Carries the CV's signature monogram-ring badge
+ * into the letter: the dark-slate initials ring sits centred at the top,
+ * over the centred spaced-caps name (stacked first / last) and a muted
+ * gold spaced-caps role line, then a centred contact line and a
+ * single-column letter body via the shared {@link LetterBody}. The CV's
+ * pale-teal sidebar column (painted by {@code pageBackgrounds}) and its
+ * icon contact stack are sidebar-only and are dropped for the
+ * single-column letter; the badge + name treatment is what makes the two
+ * read as a set. Palette / typography come from
+ * {@link CvTheme#monogramSidebar()}.
+ *
+ * The gold accent, dark monogram ring, and the PT-Serif monogram font
+ * are mirrored from the CV, where they are preset-local.
+ */
+public final class MonogramSidebarLetter {
+
+ /** Stable template identifier. */
+ public static final String ID = "monogram-sidebar-letter";
+
+ /** Human-readable display name. */
+ public static final String DISPLAY_NAME = "Monogram Sidebar Letter";
+
+ /** Recommended page margin (in points) β generous business-letter feel. */
+ public static final double RECOMMENDED_MARGIN = 48.0;
+
+ /** Muted gold accent (name sub-line + links). Mirrors the CV token. */
+ private static final DocumentColor ACCENT = DocumentColor.rgb(158, 146, 104);
+
+ /** Dark slate monogram ring + initials. Mirrors the CV token. */
+ private static final DocumentColor MONOGRAM_RING = DocumentColor.rgb(54, 62, 74);
+
+ /** PT-Serif used only for the monogram initials. Mirrors the CV token. */
+ private static final FontName MONOGRAM_FONT = FontName.PT_SERIF;
+
+ /** Monogram ring diameter (matches the CV badge). */
+ private static final double MONOGRAM_DIAMETER = 122.0;
+
+ /**
+ * Letter body size. The Monogram Sidebar CV theme uses a 7.5pt body
+ * tuned for its dense two-column layout β too small for a
+ * single-column letter, so the prose is rendered a touch larger here.
+ */
+ private static final double LETTER_BODY_SIZE = 9.0;
+
+ private MonogramSidebarLetter() {
+ }
+
+ /**
+ * Builds the letter with its Monogram Sidebar theme.
+ */
+ public static DocumentTemplate create() {
+ return create(CvTheme.monogramSidebar());
+ }
+
+ /**
+ * Builds the letter with a caller-supplied theme (share the paired
+ * CV's theme instance for a guaranteed visual match).
+ */
+ public static DocumentTemplate create(CvTheme theme) {
+ Objects.requireNonNull(theme, "theme");
+ return new Template(theme);
+ }
+
+ private static final class Template implements DocumentTemplate {
+
+ private final CvTheme theme;
+
+ Template(CvTheme theme) {
+ this.theme = theme;
+ }
+
+ @Override
+ public String id() {
+ return ID;
+ }
+
+ @Override
+ public String displayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void compose(DocumentSession document, CoverLetterDocument doc) {
+ Objects.requireNonNull(document, "document");
+ Objects.requireNonNull(doc, "doc");
+
+ double innerWidth = document.canvas().innerWidth();
+ PageFlowBuilder flow = document.dsl()
+ .pageFlow()
+ .name("CoverLetterV2MonogramSidebarRoot")
+ .spacing(theme.spacing().pageFlowSpacing());
+
+ flow.addSection("CoverLetterV2MonogramSidebarHeader", section -> {
+ section.spacing(2).padding(DocumentInsets.zero());
+ addMonogramBlock(section, initials(doc.identity().name()),
+ innerWidth);
+ addNameBlock(section, doc.identity());
+ ContactLine.centered(section, doc.identity(), theme,
+ contactMetaStyle(), contactLinkStyle(),
+ contactSeparatorStyle());
+ });
+
+ flow.addSection("CoverLetterV2MonogramSidebarBody", host ->
+ LetterBody.render(host, doc, theme, LETTER_BODY_SIZE));
+
+ flow.build();
+ }
+
+ private void addMonogramBlock(SectionBuilder section, String initialsText,
+ double innerWidth) {
+ LayerStackNode badge = new LayerStackBuilder()
+ .name("CoverLetterV2MonogramSidebarBadge")
+ .back(new EllipseBuilder()
+ .name("CoverLetterV2MonogramSidebarRing")
+ .size(MONOGRAM_DIAMETER, MONOGRAM_DIAMETER)
+ .stroke(DocumentStroke.of(MONOGRAM_RING, 1.25))
+ .build())
+ .layer(new ParagraphBuilder()
+ .name("CoverLetterV2MonogramSidebarInitials")
+ .text(initialsText)
+ .textStyle(CvTextStyles.of(MONOGRAM_FONT, 44.0,
+ DocumentTextDecoration.BOLD, MONOGRAM_RING))
+ .align(TextAlign.LEFT)
+ .build(), LayerAlign.CENTER)
+ .build();
+
+ section.addLayerStack(outer -> outer
+ .name("CoverLetterV2MonogramSidebarFrame")
+ .margin(DocumentInsets.bottom(20))
+ .back(new SpacerNode(
+ "CoverLetterV2MonogramSidebarSpace",
+ Math.max(MONOGRAM_DIAMETER, innerWidth),
+ MONOGRAM_DIAMETER,
+ DocumentInsets.zero(),
+ DocumentInsets.zero()))
+ .layer(badge, LayerAlign.TOP_CENTER));
+ }
+
+ private void addNameBlock(SectionBuilder section, CvIdentity identity) {
+ CvName name = identity.name();
+ List parts = new ArrayList<>();
+ if (!name.first().isBlank()) {
+ parts.add(name.first());
+ }
+ if (!name.last().isBlank()) {
+ parts.add(name.last());
+ }
+ if (parts.isEmpty()) {
+ parts.add("");
+ }
+ DocumentTextStyle nameStyle = nameStyle();
+ DocumentTextStyle titleStyle = subtitleStyle();
+
+ for (int index = 0; index < parts.size(); index++) {
+ String part = parts.get(index);
+ DocumentInsets margin = index == parts.size() - 1
+ ? DocumentInsets.zero()
+ : DocumentInsets.bottom(6);
+ section.addParagraph(paragraph -> paragraph
+ .text(TextOrnaments.spacedUpper(part))
+ .textStyle(nameStyle)
+ .align(TextAlign.CENTER)
+ .lineSpacing(1.0)
+ .margin(margin));
+ }
+ String jobTitle = identity.jobTitle();
+ if (jobTitle != null && !jobTitle.isBlank()) {
+ section.addParagraph(paragraph -> paragraph
+ .text(TextOrnaments.spacedUpper(jobTitle))
+ .textStyle(titleStyle)
+ .align(TextAlign.CENTER)
+ .margin(new DocumentInsets(12, 0, 18, 0)));
+ }
+ }
+
+ private DocumentTextStyle nameStyle() {
+ return CvTextStyles.of(theme.typography().headlineFont(),
+ theme.typography().sizeHeadline(),
+ DocumentTextDecoration.DEFAULT, theme.palette().ink());
+ }
+
+ private DocumentTextStyle subtitleStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.BOLD, ACCENT);
+ }
+
+ private DocumentTextStyle contactMetaStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.DEFAULT, theme.palette().muted());
+ }
+
+ private DocumentTextStyle contactLinkStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.UNDERLINE, ACCENT);
+ }
+
+ private DocumentTextStyle contactSeparatorStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.DEFAULT, theme.palette().rule());
+ }
+
+ private static String initials(CvName name) {
+ if (name == null) {
+ return "";
+ }
+ StringBuilder builder = new StringBuilder();
+ appendInitial(builder, name.first());
+ appendInitial(builder, name.last());
+ return builder.toString();
+ }
+
+ private static void appendInitial(StringBuilder builder, String value) {
+ if (builder.length() >= 2 || value == null) {
+ return;
+ }
+ String trimmed = value.trim();
+ if (!trimmed.isEmpty() && Character.isLetter(trimmed.charAt(0))) {
+ builder.append(Character.toUpperCase(trimmed.charAt(0)));
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/NordicCleanLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/NordicCleanLetter.java
new file mode 100644
index 00000000..507bd092
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/NordicCleanLetter.java
@@ -0,0 +1,155 @@
+package com.demcha.compose.document.templates.coverletter.v2.presets;
+
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.dsl.PageFlowBuilder;
+import com.demcha.compose.document.style.DocumentColor;
+import com.demcha.compose.document.style.DocumentInsets;
+import com.demcha.compose.document.style.DocumentTextDecoration;
+import com.demcha.compose.document.style.DocumentTextStyle;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.components.LetterBody;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.components.CvTextStyles;
+import com.demcha.compose.document.templates.cv.v2.components.MarkdownInline;
+import com.demcha.compose.document.templates.cv.v2.data.CvIdentity;
+import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
+import com.demcha.compose.document.templates.cv.v2.widgets.ContactLine;
+import com.demcha.compose.document.templates.cv.v2.widgets.Headline;
+
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * v2 cover-letter pair for the {@code NordicClean} CV preset.
+ *
+ * Reproduces the CV's signature header: a left-aligned UPPERCASE
+ * Barlow name with a short teal accent bar beneath it
+ * and an UPPERCASE role sub-line, balanced by a right-aligned stacked
+ * contact list with teal links β the same masthead as
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.NordicClean}.
+ * Below it, a single-column letter body via the shared
+ * {@link LetterBody}. Body palette / typography come from
+ * {@link CvTheme#nordicClean()}; the CV's tinted profile band is a
+ * CV-body element and is intentionally not part of the letter.
+ *
+ * The teal {@code ACCENT} is mirrored from the CV's default accent
+ * (the CV exposes it via an Options knob; the letter uses the default).
+ */
+public final class NordicCleanLetter {
+
+ /** Stable template identifier. */
+ public static final String ID = "nordic-clean-letter";
+
+ /** Human-readable display name. */
+ public static final String DISPLAY_NAME = "Nordic Clean Letter";
+
+ /** Recommended page margin (in points) β generous business-letter feel. */
+ public static final double RECOMMENDED_MARGIN = 48.0;
+
+ /** Teal accent bar + link colour. Mirrors the NordicClean CV default accent. */
+ private static final DocumentColor ACCENT = DocumentColor.rgb(28, 128, 135);
+
+ private NordicCleanLetter() {
+ }
+
+ /**
+ * Builds the letter with its Nordic Clean theme.
+ */
+ public static DocumentTemplate create() {
+ return create(CvTheme.nordicClean());
+ }
+
+ /**
+ * Builds the letter with a caller-supplied theme (share the paired
+ * CV's theme instance for a guaranteed visual match).
+ */
+ public static DocumentTemplate create(CvTheme theme) {
+ Objects.requireNonNull(theme, "theme");
+ return new Template(theme);
+ }
+
+ private static final class Template implements DocumentTemplate {
+
+ private final CvTheme theme;
+
+ Template(CvTheme theme) {
+ this.theme = theme;
+ }
+
+ @Override
+ public String id() {
+ return ID;
+ }
+
+ @Override
+ public String displayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void compose(DocumentSession document, CoverLetterDocument doc) {
+ Objects.requireNonNull(document, "document");
+ Objects.requireNonNull(doc, "doc");
+
+ PageFlowBuilder flow = document.dsl()
+ .pageFlow()
+ .name("CoverLetterV2NordicCleanRoot")
+ .spacing(theme.spacing().pageFlowSpacing());
+
+ addHeader(flow, doc.identity());
+
+ flow.addSection("CoverLetterV2NordicCleanBody", host ->
+ LetterBody.render(host, doc, theme));
+
+ flow.build();
+ }
+
+ private void addHeader(PageFlowBuilder flow, CvIdentity identity) {
+ flow.addRow("CoverLetterV2NordicCleanHeader", row -> row
+ .spacing(14)
+ .weights(1.2, 0.8)
+ .addSection("Identity", id -> {
+ id.spacing(3).padding(new DocumentInsets(1, 0, 2, 0));
+ Headline.uppercaseLeftAligned(id, identity.name(), theme,
+ headlineStyle());
+ id.addShape(shape -> shape
+ .name("CoverLetterV2NordicCleanNameAccent")
+ .size(64, 2.6)
+ .fillColor(ACCENT)
+ .cornerRadius(1.3)
+ .margin(DocumentInsets.zero()));
+ if (!identity.jobTitle().isBlank()) {
+ id.addParagraph(paragraph -> paragraph
+ .text(MarkdownInline.plainText(identity.jobTitle())
+ .toUpperCase(Locale.ROOT))
+ .textStyle(CvTextStyles.of(
+ theme.typography().bodyFont(), 7.7,
+ DocumentTextDecoration.BOLD,
+ theme.palette().muted()))
+ .margin(DocumentInsets.zero()));
+ }
+ })
+ .addSection("Contact", contact ->
+ ContactLine.rightAlignedStacked(contact, identity,
+ theme, contactMetaStyle(), contactLinkStyle())));
+ }
+
+ private DocumentTextStyle headlineStyle() {
+ return CvTextStyles.of(theme.typography().headlineFont(),
+ theme.typography().sizeHeadline(),
+ DocumentTextDecoration.BOLD, theme.palette().ink());
+ }
+
+ private DocumentTextStyle contactMetaStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.DEFAULT, theme.palette().muted());
+ }
+
+ private DocumentTextStyle contactLinkStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.UNDERLINE, ACCENT);
+ }
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/PanelLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/PanelLetter.java
new file mode 100644
index 00000000..2db1fb80
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/PanelLetter.java
@@ -0,0 +1,231 @@
+package com.demcha.compose.document.templates.coverletter.v2.presets;
+
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.dsl.PageFlowBuilder;
+import com.demcha.compose.document.dsl.SectionBuilder;
+import com.demcha.compose.document.node.DocumentLinkOptions;
+import com.demcha.compose.document.node.TextAlign;
+import com.demcha.compose.document.style.DocumentColor;
+import com.demcha.compose.document.style.DocumentInsets;
+import com.demcha.compose.document.style.DocumentStroke;
+import com.demcha.compose.document.style.DocumentTextDecoration;
+import com.demcha.compose.document.style.DocumentTextStyle;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.components.LetterBody;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.components.CvTextStyles;
+import com.demcha.compose.document.templates.cv.v2.components.TextOrnaments;
+import com.demcha.compose.document.templates.cv.v2.data.CvIdentity;
+import com.demcha.compose.document.templates.cv.v2.data.CvLink;
+import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
+import com.demcha.compose.document.templates.widgets.CardWidget;
+import com.demcha.compose.font.FontName;
+
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * v2 cover-letter pair for the {@code Panel} CV preset.
+ *
+ * Carries the CV's signature pale-teal header card
+ * into the letter: a full-width rounded card (thin teal stroke) holding
+ * the centred UPPERCASE Poppins name, job title, centred meta line, and
+ * a centred link row with teal accent links β the same header card as
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.Panel}.
+ * Below it, a single-column letter body via the shared
+ * {@link LetterBody}. Card shell + body palette come from
+ * {@link CvTheme#panel()}.
+ *
+ * The two masthead colours (deep-navy header text, teal accent) are
+ * mirrored from the CV, where they are preset-local. The header card is
+ * pinned to the full content width with a zero-height spacer
+ * ({@code widthAnchor}) so it spans the page rather than shrinking to
+ * fit the name β the same trick the CV uses to keep its panels aligned.
+ */
+public final class PanelLetter {
+
+ /** Stable template identifier. */
+ public static final String ID = "panel-letter";
+
+ /** Human-readable display name. */
+ public static final String DISPLAY_NAME = "Panel Letter";
+
+ /** Recommended page margin (in points) β generous business-letter feel. */
+ public static final double RECOMMENDED_MARGIN = 48.0;
+
+ /** Deep navy masthead text. Mirrors the Panel CV's preset-local token. */
+ private static final DocumentColor HEADER_TEXT = DocumentColor.rgb(20, 44, 66);
+
+ /** Teal accent for header links. Mirrors the Panel CV's preset-local token. */
+ private static final DocumentColor ACCENT = DocumentColor.rgb(0, 128, 128);
+
+ /** Thin card stroke β single value keeps the card outline crisp. Mirrors the CV. */
+ private static final double PANEL_STROKE_THICKNESS = 0.45;
+
+ private PanelLetter() {
+ }
+
+ /**
+ * Builds the letter with its Panel theme.
+ */
+ public static DocumentTemplate create() {
+ return create(CvTheme.panel());
+ }
+
+ /**
+ * Builds the letter with a caller-supplied theme (share the paired
+ * CV's theme instance for a guaranteed visual match).
+ */
+ public static DocumentTemplate create(CvTheme theme) {
+ Objects.requireNonNull(theme, "theme");
+ return new Template(theme);
+ }
+
+ private static final class Template implements DocumentTemplate {
+
+ private final CvTheme theme;
+
+ Template(CvTheme theme) {
+ this.theme = theme;
+ }
+
+ @Override
+ public String id() {
+ return ID;
+ }
+
+ @Override
+ public String displayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void compose(DocumentSession document, CoverLetterDocument doc) {
+ Objects.requireNonNull(document, "document");
+ Objects.requireNonNull(doc, "doc");
+
+ double innerWidth = document.canvas().innerWidth();
+ double cardPadding = theme.spacing().bannerInnerPadding();
+ double cardContentWidth = innerWidth - 2 * cardPadding;
+
+ PageFlowBuilder flow = document.dsl()
+ .pageFlow()
+ .name("CoverLetterV2PanelRoot")
+ .spacing(theme.spacing().pageFlowSpacing());
+
+ addHeader(flow, doc.identity(), cardContentWidth);
+
+ flow.addSection("CoverLetterV2PanelBody", host ->
+ LetterBody.render(host, doc, theme));
+
+ flow.build();
+ }
+
+ private void addHeader(PageFlowBuilder flow, CvIdentity identity,
+ double anchorWidth) {
+ CardWidget.render(flow, "CoverLetterV2PanelHeader", headerStyle(),
+ card -> {
+ widthAnchor(card, anchorWidth);
+ card.addParagraph(paragraph -> paragraph
+ .text(identity.name().full().toUpperCase(Locale.ROOT))
+ .textStyle(nameStyle())
+ .align(TextAlign.CENTER)
+ .margin(DocumentInsets.zero()));
+ if (!identity.jobTitle().isBlank()) {
+ card.addParagraph(paragraph -> paragraph
+ .text(identity.jobTitle())
+ .textStyle(headerBodyStyle())
+ .align(TextAlign.CENTER)
+ .margin(DocumentInsets.zero()));
+ }
+ String contact = TextOrnaments.joinPipe(identity.contact().address(),
+ identity.contact().phone());
+ if (!contact.isBlank()) {
+ card.addParagraph(paragraph -> paragraph
+ .text(contact)
+ .textStyle(headerMetaStyle())
+ .align(TextAlign.CENTER)
+ .margin(DocumentInsets.zero()));
+ }
+ addLinkRow(card, identity);
+ });
+ }
+
+ private void addLinkRow(SectionBuilder section, CvIdentity identity) {
+ boolean hasEmail = !identity.contact().email().isBlank();
+ boolean hasLinks = !identity.links().isEmpty();
+ if (!hasEmail && !hasLinks) {
+ return;
+ }
+ DocumentTextStyle bodyStyle = headerMetaStyle();
+ DocumentTextStyle linkStyle = headerLinkStyle();
+ section.addParagraph(paragraph -> paragraph
+ .textStyle(bodyStyle)
+ .align(TextAlign.CENTER)
+ .margin(DocumentInsets.zero())
+ .rich(rich -> {
+ boolean first = true;
+ String email = identity.contact().email();
+ if (!email.isBlank()) {
+ rich.with(email, linkStyle,
+ new DocumentLinkOptions("mailto:" + email));
+ first = false;
+ }
+ for (CvLink link : identity.links()) {
+ if (link.label().isBlank()) {
+ continue;
+ }
+ if (!first) {
+ rich.style(" | ", bodyStyle);
+ }
+ first = false;
+ if (link.url().isBlank()) {
+ rich.style(link.label(), bodyStyle);
+ } else {
+ rich.with(link.label(), linkStyle,
+ new DocumentLinkOptions(link.url()));
+ }
+ }
+ }));
+ }
+
+ private void widthAnchor(SectionBuilder card, double width) {
+ card.spacer(width, 0.0);
+ }
+
+ private CardWidget.Style headerStyle() {
+ return CardWidget.Style.builder()
+ .spacing(4)
+ .padding(DocumentInsets.of(theme.spacing().bannerInnerPadding()))
+ .fillColor(theme.palette().banner())
+ .stroke(DocumentStroke.of(theme.palette().rule(),
+ PANEL_STROKE_THICKNESS))
+ .cornerRadius(theme.spacing().bannerCornerRadius())
+ .build();
+ }
+
+ private DocumentTextStyle nameStyle() {
+ return CvTextStyles.of(FontName.POPPINS,
+ theme.typography().sizeHeadline(),
+ DocumentTextDecoration.BOLD, HEADER_TEXT);
+ }
+
+ private DocumentTextStyle headerBodyStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeBody(),
+ DocumentTextDecoration.DEFAULT, theme.palette().ink());
+ }
+
+ private DocumentTextStyle headerMetaStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.DEFAULT, theme.palette().ink());
+ }
+
+ private DocumentTextStyle headerLinkStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.UNDERLINE, ACCENT);
+ }
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/SidebarPortraitLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/SidebarPortraitLetter.java
new file mode 100644
index 00000000..b7c2c05f
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/SidebarPortraitLetter.java
@@ -0,0 +1,160 @@
+package com.demcha.compose.document.templates.coverletter.v2.presets;
+
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.dsl.PageFlowBuilder;
+import com.demcha.compose.document.dsl.SectionBuilder;
+import com.demcha.compose.document.node.TextAlign;
+import com.demcha.compose.document.style.DocumentInsets;
+import com.demcha.compose.document.style.DocumentTextDecoration;
+import com.demcha.compose.document.style.DocumentTextStyle;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.components.LetterBody;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.components.CvTextStyles;
+import com.demcha.compose.document.templates.cv.v2.components.TextOrnaments;
+import com.demcha.compose.document.templates.cv.v2.data.CvIdentity;
+import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
+import com.demcha.compose.document.templates.cv.v2.widgets.ContactLine;
+
+import java.util.Objects;
+
+/**
+ * v2 cover-letter pair for the {@code SidebarPortrait} CV preset.
+ *
+ * The CV's identity treatment is a beige "hero strip" carrying the
+ * centred serif name + spaced-caps role sub-line. The letter keeps that
+ * centred name treatment but drops the beige fill β a
+ * coloured box read as out of place on a single-column letter β leaving a
+ * clean centred letterhead, followed by a centred contact line and a
+ * single-column letter body via the shared {@link LetterBody}. The CV's
+ * circular portrait, icon contact stack, and pale sidebar column (painted
+ * via {@code pageBackgrounds}) are sidebar-only and are intentionally
+ * dropped for the single-column letter. Palette / typography come from
+ * {@link CvTheme#sidebarPortrait()}.
+ */
+public final class SidebarPortraitLetter {
+
+ /** Stable template identifier. */
+ public static final String ID = "sidebar-portrait-letter";
+
+ /** Human-readable display name. */
+ public static final String DISPLAY_NAME = "Sidebar Portrait Letter";
+
+ /** Recommended page margin (in points) β generous business-letter feel. */
+ public static final double RECOMMENDED_MARGIN = 48.0;
+
+ private SidebarPortraitLetter() {
+ }
+
+ /**
+ * Builds the letter with its Sidebar Portrait theme.
+ */
+ public static DocumentTemplate create() {
+ return create(CvTheme.sidebarPortrait());
+ }
+
+ /**
+ * Builds the letter with a caller-supplied theme (share the paired
+ * CV's theme instance for a guaranteed visual match).
+ */
+ public static DocumentTemplate create(CvTheme theme) {
+ Objects.requireNonNull(theme, "theme");
+ return new Template(theme);
+ }
+
+ private static final class Template implements DocumentTemplate {
+
+ private final CvTheme theme;
+
+ Template(CvTheme theme) {
+ this.theme = theme;
+ }
+
+ @Override
+ public String id() {
+ return ID;
+ }
+
+ @Override
+ public String displayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void compose(DocumentSession document, CoverLetterDocument doc) {
+ Objects.requireNonNull(document, "document");
+ Objects.requireNonNull(doc, "doc");
+
+ PageFlowBuilder flow = document.dsl()
+ .pageFlow()
+ .name("CoverLetterV2SidebarPortraitRoot")
+ .spacing(theme.spacing().pageFlowSpacing());
+
+ addHeroBand(flow, doc.identity());
+ addContact(flow, doc.identity());
+
+ flow.addSection("CoverLetterV2SidebarPortraitBody", host ->
+ LetterBody.render(host, doc, theme));
+
+ flow.build();
+ }
+
+ private void addHeroBand(PageFlowBuilder flow, CvIdentity identity) {
+ String displayName = identity.name().full();
+ String jobTitle = identity.jobTitle();
+ String subline = jobTitle == null || jobTitle.isBlank()
+ ? ""
+ : TextOrnaments.spacedUpper(jobTitle);
+ flow.addSection("CoverLetterV2SidebarPortraitHero", hero -> {
+ // No fill: the CV's beige hero band reads as a coloured box
+ // on a single-column letter, which clashed with the concept,
+ // so the name treatment is kept but the background dropped.
+ hero.padding(new DocumentInsets(19, 34, 17, 34))
+ .spacing(3)
+ .addParagraph(paragraph -> paragraph
+ .text(displayName)
+ .textStyle(nameStyle())
+ .align(TextAlign.CENTER)
+ .lineSpacing(1.0)
+ .margin(DocumentInsets.zero()));
+ if (!subline.isBlank()) {
+ hero.addParagraph(paragraph -> paragraph
+ .text(subline)
+ .textStyle(subtitleStyle())
+ .align(TextAlign.CENTER)
+ .margin(DocumentInsets.zero()));
+ }
+ });
+ }
+
+ private void addContact(PageFlowBuilder flow, CvIdentity identity) {
+ flow.addSection("CoverLetterV2SidebarPortraitContact", section ->
+ ContactLine.centered(section, identity, theme,
+ contactStyle(), contactLinkStyle(), contactStyle()));
+ }
+
+ private DocumentTextStyle nameStyle() {
+ return CvTextStyles.of(theme.typography().headlineFont(),
+ theme.typography().sizeHeadline(),
+ DocumentTextDecoration.BOLD, theme.palette().ink());
+ }
+
+ private DocumentTextStyle subtitleStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeEntryDate(),
+ DocumentTextDecoration.DEFAULT, theme.palette().ink());
+ }
+
+ private DocumentTextStyle contactStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.DEFAULT, theme.palette().ink());
+ }
+
+ private DocumentTextStyle contactLinkStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.UNDERLINE, theme.palette().muted());
+ }
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/TimelineMinimalLetter.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/TimelineMinimalLetter.java
new file mode 100644
index 00000000..f4341c2f
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/TimelineMinimalLetter.java
@@ -0,0 +1,294 @@
+package com.demcha.compose.document.templates.coverletter.v2.presets;
+
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.dsl.PageFlowBuilder;
+import com.demcha.compose.document.dsl.SectionBuilder;
+import com.demcha.compose.document.image.DocumentImageData;
+import com.demcha.compose.document.node.DocumentLinkOptions;
+import com.demcha.compose.document.node.InlineImageAlignment;
+import com.demcha.compose.document.node.TextAlign;
+import com.demcha.compose.document.style.DocumentInsets;
+import com.demcha.compose.document.style.DocumentTextDecoration;
+import com.demcha.compose.document.style.DocumentTextStyle;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.components.LetterBody;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.components.CvTextStyles;
+import com.demcha.compose.document.templates.cv.v2.components.SectionLookup;
+import com.demcha.compose.document.templates.cv.v2.components.TextOrnaments;
+import com.demcha.compose.document.templates.cv.v2.data.CvIdentity;
+import com.demcha.compose.document.templates.cv.v2.data.CvLink;
+import com.demcha.compose.document.templates.cv.v2.theme.CvTheme;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * v2 cover-letter pair for the {@code TimelineMinimal} CV preset.
+ *
+ * Reproduces the CV's masthead: a left spaced-caps Barlow-Condensed
+ * name + UPPERCASE role line, balanced by a right-aligned contact stack
+ * where each line ends with its PNG glyph icon (LinkedIn / GitHub /
+ * location / phone / email), all under a thin full-width rule β the same
+ * header as
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.TimelineMinimal}.
+ * Below it, a single-column letter body via the shared {@link LetterBody}.
+ * Palette / typography come from {@link CvTheme#timelineMinimal()}; the
+ * CV's three-column timeline axis is a body element and is not part of
+ * the letter.
+ *
+ * The contact icons reuse the CV's icon set
+ * ({@code /templates/cv/timeline-minimal/icons/}) and its text-glyph
+ * fallback, so no new assets are introduced.
+ */
+public final class TimelineMinimalLetter {
+
+ /** Stable template identifier. */
+ public static final String ID = "timeline-minimal-letter";
+
+ /** Human-readable display name. */
+ public static final String DISPLAY_NAME = "Timeline Minimal Letter";
+
+ /** Recommended page margin (in points) β generous business-letter feel. */
+ public static final double RECOMMENDED_MARGIN = 48.0;
+
+ /**
+ * Letter body size. The Timeline Minimal CV theme uses a 7.8pt body
+ * tuned for its dense three-column layout β too small for a
+ * single-column letter, so the prose is rendered a touch larger here.
+ */
+ private static final double LETTER_BODY_SIZE = 9.0;
+
+ private static final double CONTACT_ICON_SIZE = 10.5;
+ private static final double CONTACT_ICON_BASELINE_OFFSET = -1.35;
+ private static final String CONTACT_ICON_ROOT =
+ "/templates/cv/timeline-minimal/icons/";
+ private static final Map CONTACT_ICON_CACHE =
+ new ConcurrentHashMap<>();
+
+ private TimelineMinimalLetter() {
+ }
+
+ /**
+ * Builds the letter with its Timeline Minimal theme.
+ */
+ public static DocumentTemplate create() {
+ return create(CvTheme.timelineMinimal());
+ }
+
+ /**
+ * Builds the letter with a caller-supplied theme (share the paired
+ * CV's theme instance for a guaranteed visual match).
+ */
+ public static DocumentTemplate create(CvTheme theme) {
+ Objects.requireNonNull(theme, "theme");
+ return new Template(theme);
+ }
+
+ private static final class Template implements DocumentTemplate {
+
+ private final CvTheme theme;
+
+ Template(CvTheme theme) {
+ this.theme = theme;
+ }
+
+ @Override
+ public String id() {
+ return ID;
+ }
+
+ @Override
+ public String displayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void compose(DocumentSession document, CoverLetterDocument doc) {
+ Objects.requireNonNull(document, "document");
+ Objects.requireNonNull(doc, "doc");
+
+ double width = document.canvas().innerWidth();
+ PageFlowBuilder flow = document.dsl()
+ .pageFlow()
+ .name("CoverLetterV2TimelineMinimalRoot")
+ .spacing(theme.spacing().pageFlowSpacing())
+ .addRow("CoverLetterV2TimelineMinimalHeader", row -> row
+ .spacing(3)
+ .weights(1.00, 0.61)
+ .addSection("CoverLetterV2TimelineMinimalName",
+ section -> addNameBlock(section, doc.identity()))
+ .addSection("CoverLetterV2TimelineMinimalContact",
+ section -> addContact(section, doc.identity())))
+ .addLine(line -> line
+ .name("CoverLetterV2TimelineMinimalHeaderRule")
+ .horizontal(width)
+ .color(theme.palette().rule())
+ .thickness(theme.spacing().accentRuleWidth())
+ .margin(DocumentInsets.zero()));
+
+ flow.addSection("CoverLetterV2TimelineMinimalBody", host ->
+ LetterBody.render(host, doc, theme, LETTER_BODY_SIZE));
+
+ flow.build();
+ }
+
+ private void addNameBlock(SectionBuilder section, CvIdentity identity) {
+ section.spacing(4)
+ .addParagraph(paragraph -> paragraph
+ .text(TextOrnaments.spacedUpper(identity.name().full()))
+ .textStyle(nameStyle())
+ .margin(DocumentInsets.zero()));
+ String jobTitle = identity.jobTitle();
+ if (!jobTitle.isBlank()) {
+ section.addParagraph(paragraph -> paragraph
+ .text(jobTitle.toUpperCase(Locale.ROOT))
+ .textStyle(jobTitleStyle())
+ .margin(DocumentInsets.zero()));
+ }
+ }
+
+ private void addContact(SectionBuilder section, CvIdentity identity) {
+ section.spacing(3);
+ DocumentTextStyle textStyle = contactTextStyle();
+ DocumentTextStyle fallbackIconStyle = fallbackIconStyle();
+ for (ContactItem item : contactItems(identity)) {
+ section.addParagraph(paragraph -> paragraph
+ .textStyle(textStyle)
+ .align(TextAlign.RIGHT)
+ .link(item.linkOptions())
+ .margin(DocumentInsets.zero())
+ .rich(rich -> {
+ rich.style(item.text(), textStyle);
+ rich.plain(" ");
+ if (item.iconFile() != null) {
+ rich.image(contactIcon(item.iconFile()),
+ CONTACT_ICON_SIZE,
+ CONTACT_ICON_SIZE,
+ InlineImageAlignment.CENTER,
+ CONTACT_ICON_BASELINE_OFFSET,
+ item.linkOptions());
+ } else {
+ rich.style(item.fallbackIcon(), fallbackIconStyle);
+ }
+ }));
+ }
+ }
+
+ private List contactItems(CvIdentity identity) {
+ if (identity == null) {
+ return List.of();
+ }
+ List items = new ArrayList<>();
+ addContactItem(items, "LOC", "location.png",
+ identity.contact().address(), null);
+ addContactItem(items, "TEL", "phone.png",
+ identity.contact().phone(), null);
+ String email = identity.contact().email();
+ if (!email.isBlank()) {
+ addContactItem(items, "@", "email.png", email,
+ new DocumentLinkOptions("mailto:" + email));
+ }
+ for (CvLink link : identity.links()) {
+ String label = link.label();
+ if (label.isBlank()) {
+ continue;
+ }
+ String url = link.url();
+ addContactItem(items, pickFallbackIcon(label),
+ pickIconFile(label), label,
+ url.isBlank() ? null : new DocumentLinkOptions(url.trim()));
+ }
+ return List.copyOf(items);
+ }
+
+ private DocumentImageData contactIcon(String iconFile) {
+ return DocumentImageData.fromBytes(
+ CONTACT_ICON_CACHE.computeIfAbsent(iconFile,
+ TimelineMinimalLetter::readIconBytes));
+ }
+
+ private DocumentTextStyle nameStyle() {
+ return CvTextStyles.of(theme.typography().headlineFont(),
+ theme.typography().sizeHeadline(),
+ DocumentTextDecoration.DEFAULT, theme.palette().ink());
+ }
+
+ private DocumentTextStyle jobTitleStyle() {
+ return CvTextStyles.of(theme.typography().headlineFont(), 9.5,
+ DocumentTextDecoration.BOLD, theme.palette().ink());
+ }
+
+ private DocumentTextStyle contactTextStyle() {
+ return CvTextStyles.of(theme.typography().bodyFont(),
+ theme.typography().sizeContact(),
+ DocumentTextDecoration.BOLD, theme.palette().muted());
+ }
+
+ private DocumentTextStyle fallbackIconStyle() {
+ return CvTextStyles.of(theme.typography().headlineFont(), 8.0,
+ DocumentTextDecoration.BOLD, theme.palette().muted());
+ }
+ }
+
+ private static void addContactItem(List items,
+ String fallbackIcon, String iconFile,
+ String text, DocumentLinkOptions linkOptions) {
+ if (text != null && !text.isBlank()) {
+ items.add(new ContactItem(fallbackIcon, iconFile, text, linkOptions));
+ }
+ }
+
+ private static String pickIconFile(String label) {
+ String normalized = SectionLookup.normalize(label);
+ if (normalized.contains("linkedin")) {
+ return "linkedin.png";
+ }
+ if (normalized.contains("github")) {
+ return "github.png";
+ }
+ if (normalized.contains("dribbble")) {
+ return "dribbble.png";
+ }
+ if (normalized.contains("google")) {
+ return "google.png";
+ }
+ return null;
+ }
+
+ private static String pickFallbackIcon(String label) {
+ String normalized = SectionLookup.normalize(label);
+ if (normalized.contains("linkedin")) {
+ return "in";
+ }
+ if (normalized.contains("github")) {
+ return "GH";
+ }
+ return "@";
+ }
+
+ private static byte[] readIconBytes(String iconFile) {
+ try (InputStream input = TimelineMinimalLetter.class.getResourceAsStream(
+ CONTACT_ICON_ROOT + iconFile)) {
+ if (input == null) {
+ throw new IllegalStateException(
+ "Missing timeline minimal contact icon: " + iconFile);
+ }
+ return input.readAllBytes();
+ } catch (IOException e) {
+ throw new UncheckedIOException(
+ "Failed to read timeline minimal contact icon: " + iconFile, e);
+ }
+ }
+
+ private record ContactItem(String fallbackIcon, String iconFile,
+ String text, DocumentLinkOptions linkOptions) {
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/package-info.java b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/package-info.java
new file mode 100644
index 00000000..2388b451
--- /dev/null
+++ b/src/main/java/com/demcha/compose/document/templates/coverletter/v2/presets/package-info.java
@@ -0,0 +1,11 @@
+/**
+ * Templates v2 cover-letter presets β one per paired CV preset.
+ *
+ * Each preset is a thin orchestrator that reads colour, font, and
+ * spacing from its paired {@code CvTheme.()} (the single source
+ * of truth shared with the CV), renders the same masthead treatment as
+ * the CV, and delegates the letter body to the shared
+ * {@code coverletter.v2.components.LetterBody}. The result is a CV and a
+ * cover letter that read as one matched set.
+ */
+package com.demcha.compose.document.templates.coverletter.v2.presets;
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/builder/CvBuilder.java b/src/main/java/com/demcha/compose/document/templates/cv/builder/CvBuilder.java
index 982ec4ab..d3ac5201 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/builder/CvBuilder.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/builder/CvBuilder.java
@@ -36,7 +36,15 @@
* must be configured before calling {@link #build()}; missing values
* are rejected at build time with an explicit {@code NullPointerException}
* naming the missing field.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard) β the layered model
+ * {@link com.demcha.compose.document.templates.cv.v2.data.CvDocument}
+ * plus the {@code cv.v2} presets. Kept for backward compatibility;
+ * scheduled for removal in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class CvBuilder {
private String id;
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/builder/package-info.java b/src/main/java/com/demcha/compose/document/templates/cv/builder/package-info.java
index 30d7e0ee..4a9fd87c 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/builder/package-info.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/builder/package-info.java
@@ -1,14 +1,29 @@
/**
- * Templates v2 CV preset builders β fluent assembly of
+ * Superseded Gen-2 CV preset builder β fluent assembly of
* {@link com.demcha.compose.document.templates.api.DocumentTemplate}
* instances from layouts, components, and spec data.
*
+ * Deprecated surface. This is the older Gen-2 CV
+ * builder. It is not the current standard. The current standard is
+ * the layered surface
+ * {@code com.demcha.compose.document.templates.cv.v2} (data / theme /
+ * components / widgets / presets). This package is kept only for backward
+ * compatibility and is scheduled for removal in a future major.
+ *
* The single class of interest is
* {@link com.demcha.compose.document.templates.cv.builder.CvBuilder}.
* Preset classes wrap one builder call inside their
- * {@code create(BusinessTheme)} factory; users wanting a custom preset
- * copy that factory body and tweak the chain.
+ * {@code create(BusinessTheme)} factory.
+ *
+ * New code should target the layered {@code cv.v2} surface instead. See
+ * {@code docs/templates/v2-layered/}.
*
* @since 1.6.0
+ * @deprecated Superseded by the layered
+ * {@code com.demcha.compose.document.templates.cv.v2} surface (the
+ * current standard). This Gen-2 package is kept for backward
+ * compatibility and will be removed in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
package com.demcha.compose.document.templates.cv.builder;
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/layouts/CvLayout.java b/src/main/java/com/demcha/compose/document/templates/cv/layouts/CvLayout.java
index ef72b2d1..f733627c 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/layouts/CvLayout.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/layouts/CvLayout.java
@@ -29,7 +29,15 @@
* construction time. Theming and spacing live with the components and
* theme tokens; layouts only decide where blocks of pre-rendered
* content sit on the page.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard) β the layered model
+ * {@link com.demcha.compose.document.templates.cv.v2.data.CvDocument}
+ * plus the {@code cv.v2} presets. Kept for backward compatibility;
+ * scheduled for removal in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public interface CvLayout {
/**
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/layouts/SingleColumn.java b/src/main/java/com/demcha/compose/document/templates/cv/layouts/SingleColumn.java
index 8fe40e04..27d992a1 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/layouts/SingleColumn.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/layouts/SingleColumn.java
@@ -18,7 +18,15 @@
*
* Used by presets such as Modern Professional and Classic Serif
* where a tight, focused single-page layout is the goal.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard) β the layered model
+ * {@link com.demcha.compose.document.templates.cv.v2.data.CvDocument}
+ * plus the {@code cv.v2} presets. Kept for backward compatibility;
+ * scheduled for removal in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class SingleColumn implements CvLayout {
/** Stable slot name that holds all module content. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/layouts/ThreeColumnMagazine.java b/src/main/java/com/demcha/compose/document/templates/cv/layouts/ThreeColumnMagazine.java
index d9b683a1..4a28b69f 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/layouts/ThreeColumnMagazine.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/layouts/ThreeColumnMagazine.java
@@ -25,7 +25,15 @@
* Column weights default to equal thirds and are configurable via
* {@link #weights(double, double, double)}. Inter-column gap and
* inter-module gap are also tunable.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard) β the layered model
+ * {@link com.demcha.compose.document.templates.cv.v2.data.CvDocument}
+ * plus the {@code cv.v2} presets. Kept for backward compatibility;
+ * scheduled for removal in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class ThreeColumnMagazine implements CvLayout {
/** Stable slot name for the first column. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/layouts/TwoColumnSidebar.java b/src/main/java/com/demcha/compose/document/templates/cv/layouts/TwoColumnSidebar.java
index e02d59cf..caf29711 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/layouts/TwoColumnSidebar.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/layouts/TwoColumnSidebar.java
@@ -25,7 +25,15 @@
* and are configurable via {@link #mainWeight(double)} /
* {@link #sidebarWeight(double)}. Inter-column gap and inter-module
* gap are also tunable.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard) β the layered model
+ * {@link com.demcha.compose.document.templates.cv.v2.data.CvDocument}
+ * plus the {@code cv.v2} presets. Kept for backward compatibility;
+ * scheduled for removal in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class TwoColumnSidebar implements CvLayout {
/** Stable slot name for the primary (wider) content column. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/layouts/package-info.java b/src/main/java/com/demcha/compose/document/templates/cv/layouts/package-info.java
index a19b5fbe..a546def8 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/layouts/package-info.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/layouts/package-info.java
@@ -1,7 +1,14 @@
/**
- * Templates v2 CV layouts β slot caΡΠΊΠ°ΡΡ that arrange a header plus
+ * Superseded Gen-2 CV layouts β slot frames that arrange a header plus
* named slot content into a final document tree.
*
+ * Deprecated surface. These are the older Gen-2 CV
+ * layouts. They are not the current standard. The current standard
+ * is the layered surface
+ * {@code com.demcha.compose.document.templates.cv.v2} (data / theme /
+ * components / widgets / presets). This package is kept only for backward
+ * compatibility and is scheduled for removal in a future major.
+ *
* Layouts are pure structural composers. They expose a fixed set of
* named slots ({@code "main"}, {@code "sidebar"}, {@code "col-1"} etc.)
* and a single composition seam that takes a pre-rendered header node
@@ -19,10 +26,15 @@
* β col-1 / col-2 / col-3 slots, weighted row beneath the header.
*
*
- *
Additional layouts ({@code HeroAndTwoColumn}, etc.) will land
- * alongside the presets that need them in Phase E of the Templates v2
- * migration.
+ * New code should target the layered {@code cv.v2} surface instead. See
+ * {@code docs/templates/v2-layered/}.
*
* @since 1.6.0
+ * @deprecated Superseded by the layered
+ * {@code com.demcha.compose.document.templates.cv.v2} surface (the
+ * current standard). This Gen-2 package is kept for backward
+ * compatibility and will be removed in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
package com.demcha.compose.document.templates.cv.layouts;
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/package-info.java b/src/main/java/com/demcha/compose/document/templates/cv/package-info.java
index 9031678d..b4a00e9d 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/package-info.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/package-info.java
@@ -1,15 +1,22 @@
/**
- * Templates v2 CV domain β layouts, presets, builder, and spec data types.
+ * Superseded Gen-2 CV domain β slot-based layouts, presets, builder, and
+ * spec data types.
*
- * This package is the home of all CV (rΓ©sumΓ©) templates in the v2
- * architecture. Sub-packages partition the domain by concern:
+ * Deprecated surface. This package is the older Gen-2
+ * CV (rΓ©sumΓ©) template stack. It is not the current standard. The
+ * current standard is the layered surface
+ * {@code com.demcha.compose.document.templates.cv.v2} (data / theme /
+ * components / widgets / presets). This package is kept only for backward
+ * compatibility and is scheduled for removal in a future major.
+ *
+ * Sub-packages partition the (deprecated) domain by concern:
*
*
- * - {@code cv.layouts} β slot caΡΠΊΠ°ΡΡ (single-column, two-column-sidebar,
- * three-column-magazine, hero-and-two-column).
+ * - {@code cv.layouts} β slot frames (single-column, two-column-sidebar,
+ * three-column-magazine).
* - {@code cv.presets} β flat copy-and-tweak preset classes
* (ModernProfessional, NordicClean, ClassicSerif, CompactMono,
- * Executive, EngineeringResume, Panel, Sidebar, MonogramSidebar,
+ * Executive, EngineeringResume, Panel, SidebarPortrait, MonogramSidebar,
* TimelineMinimal, BoxedSections, CenteredHeadline, BlueBanner,
* EditorialBlue).
* - {@code cv.builder} β {@code CvBuilder} for users composing
@@ -18,10 +25,15 @@
* {@code CvModule}) describing the user's CV content.
*
*
- * Sub-packages will be populated during Phases BβE of the Templates v2
- * migration. Top-level marker file lives here to register the package
- * with the build.
+ * New code should target the layered {@code cv.v2} surface instead. See
+ * {@code docs/templates/v2-layered/}.
*
* @since 1.6.0
+ * @deprecated Superseded by the layered
+ * {@code com.demcha.compose.document.templates.cv.v2} surface (the
+ * current standard). This Gen-2 package is kept for backward
+ * compatibility and will be removed in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
package com.demcha.compose.document.templates.cv;
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/BlueBanner.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/BlueBanner.java
index 25b4ec96..da72f90b 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/presets/BlueBanner.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/BlueBanner.java
@@ -46,7 +46,13 @@
*
* Inline markdown ({@code **bold**}, {@code *italic*}) is parsed
* through the shared {@link MarkdownText} helper.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.BlueBanner}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class BlueBanner {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/BoxedSections.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/BoxedSections.java
index a54c87ac..f86bb769 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/presets/BoxedSections.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/BoxedSections.java
@@ -45,7 +45,13 @@
* description below. Visual signature ported from the legacy
* {@code BoxedSectionsCvTemplateComposer}: PT Serif throughout, dark
* grey ink, soft grey banner.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.BoxedSections}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class BoxedSections {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/CenteredHeadline.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/CenteredHeadline.java
index e7b7ca2d..865b10bc 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/presets/CenteredHeadline.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/CenteredHeadline.java
@@ -46,7 +46,13 @@
* Inline markdown ({@code **bold**}, {@code *italic*}) is parsed
* through the shared {@link MarkdownText} helper so spec authors can
* carry inline emphasis in body text without preprocessing.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.CenteredHeadline}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class CenteredHeadline {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/ClassicSerif.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/ClassicSerif.java
index e1868dc6..4416d534 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/presets/ClassicSerif.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/ClassicSerif.java
@@ -47,7 +47,13 @@
* {@link com.demcha.compose.document.templates.cv.builder.CvBuilder}
* abstraction exposes. To customise, copy this class and rewrite the
* row / section calls.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.ClassicSerif}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class ClassicSerif {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/CompactMono.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/CompactMono.java
index c29d9204..7810fd4a 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/presets/CompactMono.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/CompactMono.java
@@ -46,7 +46,13 @@
* structure is richer than what the slot-based
* {@link com.demcha.compose.document.templates.cv.builder.CvBuilder}
* abstraction exposes.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.CompactMono}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class CompactMono {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/EditorialBlue.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/EditorialBlue.java
index 3666fc88..858f7666 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/presets/EditorialBlue.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/EditorialBlue.java
@@ -43,7 +43,13 @@
* Skills render as a four-column table; Education / Projects /
* Employment History render structured entries with bold leading
* titles, bold accent dates, and italic muted subtitles.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.EditorialBlue}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class EditorialBlue {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/EngineeringResume.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/EngineeringResume.java
index 85409b27..6ee658c1 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/presets/EngineeringResume.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/EngineeringResume.java
@@ -39,7 +39,13 @@
* Leadership Experience plus Technical Evidence on the right. Visual
* signature ported from the legacy {@code TechLeadCvTemplateComposer}:
* Barlow headings, Lato body, navy primary, green accent.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.EngineeringResume}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class EngineeringResume {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/Executive.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/Executive.java
index 9772f811..7352757e 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/presets/Executive.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/Executive.java
@@ -37,7 +37,13 @@
* over a single-column body. Visual signature ported from the legacy
* {@code ExecutiveSlateCvTemplate}: Poppins for headings, Lato for
* body, slate primary, bronze accent.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.Executive}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class Executive {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/ModernProfessional.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/ModernProfessional.java
index 91e01085..efed7089 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/presets/ModernProfessional.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/ModernProfessional.java
@@ -35,7 +35,13 @@
* {@link CvSpec} must declare modules with these names; alternative
* orderings are achieved by copying the preset and changing the
* {@code .place(...)} calls.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.ModernProfessional}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class ModernProfessional {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/MonogramSidebar.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/MonogramSidebar.java
index de330c28..e946da75 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/presets/MonogramSidebar.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/MonogramSidebar.java
@@ -55,7 +55,13 @@
* Visual signature ported from the legacy
* {@code MonogramSidebarCvTemplateComposer}: Crimson Text headline,
* PT Serif monogram, muted gold accent.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.MonogramSidebar}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class MonogramSidebar {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/NordicClean.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/NordicClean.java
index 7f05ec61..41384833 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/presets/NordicClean.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/NordicClean.java
@@ -54,7 +54,13 @@
* {@code "additional"}, {@code "experience"}, {@code "projects"}) so
* naming variants like "Professional Summary" / "Profile" or
* "Technical Skills" / "Skills" all work without configuration.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.NordicClean}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class NordicClean {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/Panel.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/Panel.java
index 8e1cfd17..f167a8cd 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/presets/Panel.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/Panel.java
@@ -46,7 +46,13 @@
* Additional panel closes the document. Visual signature ported from
* {@code PanelCvTemplateComposer.Layout.stacked}: Poppins headlines,
* Lato body, deep slate ink, teal accent.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.Panel}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class Panel {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/SidebarPortrait.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/SidebarPortrait.java
index 97ae0345..44509a90 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/presets/SidebarPortrait.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/SidebarPortrait.java
@@ -49,7 +49,13 @@
* ported from the legacy {@code SidebarPortraitCvTemplateComposer}:
* Crimson Text serif for the hero name, Lato body, restrained grey
* palette.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.SidebarPortrait}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class SidebarPortrait {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/TimelineMinimal.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/TimelineMinimal.java
index d714efcf..6c6183b8 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/presets/TimelineMinimal.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/TimelineMinimal.java
@@ -47,7 +47,13 @@
* {@code TimelineMinimalCvTemplateComposer}: spaced caps name in
* Barlow Condensed, contact stack with PNG icons, all-grey palette,
* three timeline dots.
+ *
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard). Kept for backward compatibility; scheduled for removal
+ * in a future major. See {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.cv.v2.presets.TimelineMinimal}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public final class TimelineMinimal {
/** Stable template identifier. */
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/package-info.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/package-info.java
index 297821b1..daf4b0b9 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/presets/package-info.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/package-info.java
@@ -1,24 +1,28 @@
/**
- * Templates v2 CV presets β flat copy-and-tweak recipe classes.
+ * Superseded Gen-2 CV presets β flat copy-and-tweak recipe classes.
+ *
+ * Deprecated surface. These are the older Gen-2 CV
+ * presets. They are not the current standard. The current standard
+ * is the layered surface
+ * {@code com.demcha.compose.document.templates.cv.v2.presets}. This package
+ * is kept only for backward compatibility and is scheduled for removal in a
+ * future major.
*
* Each preset is a small final class with one static
* {@code create(BusinessTheme)} factory method whose body fully
* configures a {@link com.demcha.compose.document.templates.cv.builder.CvBuilder}
* to produce a ready-to-use
- * {@link com.demcha.compose.document.templates.api.DocumentTemplate}.
- * No inheritance, no abstract base β every visual choice is visible
- * in the preset's source.
- *
- * To customise a preset: copy the {@code create(...)} method body
- * into your own class and adjust the {@code CvBuilder} calls (slot
- * placements, module style, spacing tokens, layout choice). The
- * surrounding session lifecycle is unchanged.
+ * {@link com.demcha.compose.document.templates.api.DocumentTemplate}.
*
- * Phase D ships the pilot preset
- * {@link com.demcha.compose.document.templates.cv.presets.ModernProfessional};
- * the remaining 13 CV presets land in Phase E of the Templates v2
- * migration.
+ * New code should target the layered {@code cv.v2} presets instead. See
+ * {@code docs/templates/v2-layered/}.
*
* @since 1.6.0
+ * @deprecated Superseded by the layered
+ * {@code com.demcha.compose.document.templates.cv.v2} surface (the
+ * current standard). This Gen-2 package is kept for backward
+ * compatibility and will be removed in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
package com.demcha.compose.document.templates.cv.presets;
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/spec/CvHeader.java b/src/main/java/com/demcha/compose/document/templates/cv/spec/CvHeader.java
index a1eefc72..77934c2d 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/spec/CvHeader.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/spec/CvHeader.java
@@ -22,7 +22,14 @@
* @param email optional email address; empty string when absent
* @param links ordered list of {@link Link} entries (typically
* LinkedIn, GitHub); never null, may be empty
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard) β the layered model
+ * {@link com.demcha.compose.document.templates.cv.v2.data.CvDocument}
+ * plus the {@code cv.v2} presets. Kept for backward compatibility;
+ * scheduled for removal in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public record CvHeader(
String name,
String jobTitle,
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/spec/CvModule.java b/src/main/java/com/demcha/compose/document/templates/cv/spec/CvModule.java
index 013fdb06..913c041e 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/spec/CvModule.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/spec/CvModule.java
@@ -20,7 +20,14 @@
* @param title heading text rendered above the body (may be empty to
* suppress the heading row)
* @param body body content block (must not be null)
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard) β the layered model
+ * {@link com.demcha.compose.document.templates.cv.v2.data.CvDocument}
+ * plus the {@code cv.v2} presets. Kept for backward compatibility;
+ * scheduled for removal in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public record CvModule(String name, String title, Block body) {
/**
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/spec/CvSpec.java b/src/main/java/com/demcha/compose/document/templates/cv/spec/CvSpec.java
index 68392b2d..dff8f212 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/spec/CvSpec.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/spec/CvSpec.java
@@ -17,7 +17,14 @@
* @param header identity block (required)
* @param modules ordered list of named modules; insertion order
* preserved
+ * @deprecated Superseded by the layered β¦v2β¦ surface (the current
+ * standard) β the layered model
+ * {@link com.demcha.compose.document.templates.cv.v2.data.CvDocument}
+ * plus the {@code cv.v2} presets. Kept for backward compatibility;
+ * scheduled for removal in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public record CvSpec(CvHeader header, List modules) {
/**
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/spec/package-info.java b/src/main/java/com/demcha/compose/document/templates/cv/spec/package-info.java
index 179a52de..ecc170bb 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/spec/package-info.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/spec/package-info.java
@@ -1,8 +1,16 @@
/**
- * Templates v2 CV specification records β user-facing data types.
+ * Superseded Gen-2 CV specification records β user-facing data types.
+ *
+ * Deprecated surface. These are the older Gen-2 CV spec
+ * records. They are not the current standard. The current standard
+ * is the layered model
+ * {@link com.demcha.compose.document.templates.cv.v2.data.CvDocument} in the
+ * {@code com.demcha.compose.document.templates.cv.v2} surface. This package
+ * is kept only for backward compatibility and is scheduled for removal in a
+ * future major.
*
* This package holds the immutable records a user fills with their
- * CV content before passing the spec to a preset for rendering:
+ * CV content before passing the spec to a Gen-2 preset for rendering:
*
*
* - {@link com.demcha.compose.document.templates.cv.spec.CvHeader}
@@ -16,11 +24,15 @@
* lookups.
*
*
- * This is the v2 replacement for the legacy
- * {@code com.demcha.compose.document.templates.data.cv.*} mutable
- * Lombok beans. The legacy package remains during the migration
- * window and will be removed in Phase G.
+ * New code should target the layered {@code cv.v2} data model instead. See
+ * {@code docs/templates/v2-layered/}.
*
* @since 1.6.0
+ * @deprecated Superseded by the layered
+ * {@code com.demcha.compose.document.templates.cv.v2} surface (the
+ * current standard). This Gen-2 package is kept for backward
+ * compatibility and will be removed in a future major. See
+ * {@code docs/templates/v2-layered/}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
package com.demcha.compose.document.templates.cv.spec;
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/components/TextOrnaments.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/components/TextOrnaments.java
index f8c01028..14af837c 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/v2/components/TextOrnaments.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/components/TextOrnaments.java
@@ -43,4 +43,27 @@ public static String spacedUpper(String value) {
}
return out.toString();
}
+
+ /**
+ * Joins the non-blank parts with a {@code " | "} pipe separator
+ * (e.g. {@code joinPipe("London", "", "+44") -> "London | +44"}).
+ * Null / blank parts are skipped; each kept part is trimmed. Used to
+ * build single-line contact/meta strings in headers.
+ *
+ * @param parts ordered parts (null / blank entries ignored)
+ * @return pipe-joined string, empty when no non-blank parts
+ */
+ public static String joinPipe(String... parts) {
+ StringBuilder sb = new StringBuilder();
+ for (String part : parts) {
+ if (part == null || part.isBlank()) {
+ continue;
+ }
+ if (sb.length() > 0) {
+ sb.append(" | ");
+ }
+ sb.append(part.trim());
+ }
+ return sb.toString();
+ }
}
diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/package-info.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/package-info.java
index 5d33e9cf..caa65510 100644
--- a/src/main/java/com/demcha/compose/document/templates/cv/v2/package-info.java
+++ b/src/main/java/com/demcha/compose/document/templates/cv/v2/package-info.java
@@ -205,9 +205,11 @@
* A free-form layout engine. The engine lives in
* {@code com.demcha.compose.document.engine.*}; this package
* is a thin author-facing layer on top of its public DSL.
- * A replacement for v1 yet. The legacy
- * {@code com.demcha.compose.document.templates.cv.*} surface is
- * untouched; both pipelines coexist while v2 stabilises.
*
+ *
+ * This layered surface is the current standard for CV documents. The
+ * older Gen-2 stack at
+ * {@code com.demcha.compose.document.templates.cv.*} is deprecated and
+ * kept only for backward compatibility.
*/
package com.demcha.compose.document.templates.cv.v2;
diff --git a/src/main/java/com/demcha/compose/document/templates/data/coverletter/CoverLetterDocumentSpec.java b/src/main/java/com/demcha/compose/document/templates/data/coverletter/CoverLetterDocumentSpec.java
index fa19f033..a7ae0149 100644
--- a/src/main/java/com/demcha/compose/document/templates/data/coverletter/CoverLetterDocumentSpec.java
+++ b/src/main/java/com/demcha/compose/document/templates/data/coverletter/CoverLetterDocumentSpec.java
@@ -15,7 +15,13 @@
* @param body cover-letter body text
* @param jobDetails target role metadata used by templates
* @author Artem Demchyshyn
+ * @deprecated Test-only dead code; the live CV/cover-letter model is
+ * {@code cv.v2} / {@code coverletter.v2}. Kept for backward
+ * compatibility; scheduled for removal in a future major. See
+ * {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public record CoverLetterDocumentSpec(
Header header,
String body,
diff --git a/src/main/java/com/demcha/compose/document/templates/data/cv/CvDocumentSpec.java b/src/main/java/com/demcha/compose/document/templates/data/cv/CvDocumentSpec.java
index 4b525993..08e5217f 100644
--- a/src/main/java/com/demcha/compose/document/templates/data/cv/CvDocumentSpec.java
+++ b/src/main/java/com/demcha/compose/document/templates/data/cv/CvDocumentSpec.java
@@ -19,7 +19,13 @@
* @param header optional header block rendered at the top of the document
* @param modules ordered content modules rendered after the header
* @author Artem Demchyshyn
+ * @deprecated Test-only dead code; the live CV/cover-letter model is
+ * {@code cv.v2} / {@code coverletter.v2}. Kept for backward
+ * compatibility; scheduled for removal in a future major. See
+ * {@code docs/templates/v2-layered/} and
+ * {@link com.demcha.compose.document.templates.cv.v2.data.CvDocument}.
*/
+@Deprecated(since = "1.7.0", forRemoval = true)
public record CvDocumentSpec(
Header header,
List modules
diff --git a/src/test/java/com/demcha/compose/document/templates/coverletter/v2/presets/CoverLetterV2SmokeTest.java b/src/test/java/com/demcha/compose/document/templates/coverletter/v2/presets/CoverLetterV2SmokeTest.java
new file mode 100644
index 00000000..85452b24
--- /dev/null
+++ b/src/test/java/com/demcha/compose/document/templates/coverletter/v2/presets/CoverLetterV2SmokeTest.java
@@ -0,0 +1,100 @@
+package com.demcha.compose.document.templates.coverletter.v2.presets;
+
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.data.CvIdentity;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Smoke test for every v2 cover-letter preset β one parametrized check
+ * instead of a near-identical file per preset, because all letters
+ * share the same {@code LetterBody} shape and differ only in their
+ * (already pixel-gated) masthead.
+ *
+ * Asserts each preset's stable {@code id()} / {@code displayName()}
+ * and that it composes a non-empty document. Pixel fidelity of each
+ * masthead is covered separately by {@link CoverLetterV2VisualParityTest}.
+ */
+class CoverLetterV2SmokeTest {
+
+ @ParameterizedTest(name = "{1}")
+ @MethodSource("presets")
+ void exposes_stable_identity_and_renders(
+ Supplier> factory,
+ String id,
+ String displayName) throws Exception {
+ DocumentTemplate template = factory.get();
+ assertThat(template.id()).isEqualTo(id);
+ assertThat(template.displayName()).isEqualTo(displayName);
+
+ try (DocumentSession session = GraphCompose.document()
+ .pageSize(DocumentPageSize.A4)
+ .margin(48, 48, 48, 48)
+ .create()) {
+ template.compose(session, sampleDocument());
+ assertThat(session.roots()).isNotEmpty();
+ }
+ }
+
+ private static Stream presets() {
+ return Stream.of(
+ Arguments.of((Supplier>) ExecutiveLetter::create,
+ "executive-letter", "Executive Letter"),
+ Arguments.of((Supplier>) ModernProfessionalLetter::create,
+ "modern-professional-letter", "Modern Professional Letter"),
+ Arguments.of((Supplier>) BoxedSectionsLetter::create,
+ "boxed-sections-letter", "Boxed Sections Letter"),
+ Arguments.of((Supplier>) ClassicSerifLetter::create,
+ "classic-serif-letter", "Classic Serif Letter"),
+ Arguments.of((Supplier>) EditorialBlueLetter::create,
+ "editorial-blue-letter", "Editorial Blue Letter"),
+ Arguments.of((Supplier>) CenteredHeadlineLetter::create,
+ "centered-headline-letter", "Centered Headline Letter"),
+ Arguments.of((Supplier>) BlueBannerLetter::create,
+ "blue-banner-letter", "Blue Banner Letter"),
+ Arguments.of((Supplier>) EngineeringResumeLetter::create,
+ "engineering-resume-letter", "Engineering Resume Letter"),
+ Arguments.of((Supplier>) PanelLetter::create,
+ "panel-letter", "Panel Letter"),
+ Arguments.of((Supplier>) CompactMonoLetter::create,
+ "compact-mono-letter", "Compact Mono Letter"),
+ Arguments.of((Supplier>) NordicCleanLetter::create,
+ "nordic-clean-letter", "Nordic Clean Letter"),
+ Arguments.of((Supplier>) SidebarPortraitLetter::create,
+ "sidebar-portrait-letter", "Sidebar Portrait Letter"),
+ Arguments.of((Supplier>) MonogramSidebarLetter::create,
+ "monogram-sidebar-letter", "Monogram Sidebar Letter"),
+ Arguments.of((Supplier>) TimelineMinimalLetter::create,
+ "timeline-minimal-letter", "Timeline Minimal Letter"));
+ }
+
+ private static CoverLetterDocument sampleDocument() {
+ return CoverLetterDocument.builder()
+ .identity(CvIdentity.builder()
+ .name("Jordan", "Rivera")
+ .jobTitle("Platform Engineer")
+ .contact("+44 20 5555 1000", "jordan.rivera@example.com",
+ "London, UK")
+ .link("LinkedIn", "https://linkedin.com/in/jordan-rivera-demo")
+ .link("GitHub", "https://github.com/jrivera-demo")
+ .build())
+ .greeting("Dear Hiring Team at **Northwind Systems**,")
+ .paragraph("I am excited to share my interest in the Senior "
+ + "Platform Engineer role, building **reusable "
+ + "document-generation systems**.")
+ .paragraph("I enjoy turning fuzzy requirements into clear template "
+ + "abstractions and reliable test coverage.")
+ .closing("Sincerely,")
+ .build();
+ }
+}
diff --git a/src/test/java/com/demcha/compose/document/templates/coverletter/v2/presets/CoverLetterV2VisualParityTest.java b/src/test/java/com/demcha/compose/document/templates/coverletter/v2/presets/CoverLetterV2VisualParityTest.java
new file mode 100644
index 00000000..bb796752
--- /dev/null
+++ b/src/test/java/com/demcha/compose/document/templates/coverletter/v2/presets/CoverLetterV2VisualParityTest.java
@@ -0,0 +1,156 @@
+package com.demcha.compose.document.templates.coverletter.v2.presets;
+
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+import com.demcha.compose.document.templates.api.DocumentTemplate;
+import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
+import com.demcha.compose.document.templates.cv.v2.data.CvIdentity;
+import com.demcha.testing.visual.PdfVisualRegression;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.nio.file.Path;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+/**
+ * Pixel-diff visual parity gate for the v2 layered cover-letter
+ * presets β the letter sibling of {@code CvV2VisualParityTest}.
+ *
+ * Each preset renders the same canonical {@link CoverLetterDocument}
+ * on full A4 with the preset's {@code RECOMMENDED_MARGIN}; the
+ * resulting PDF is rasterised page-by-page and compared per-pixel
+ * against a checked-in baseline PNG. Failures write the actual render +
+ * diff image next to the baseline.
+ *
+ * Re-blessing baselines β after a deliberate
+ * visual change, re-run with
+ * {@code -Dgraphcompose.visual.approve=true} (or environment variable
+ * {@code GRAPHCOMPOSE_VISUAL_APPROVE=true}) to overwrite the baselines
+ * with the current rendering. Commit the updated PNGs as part of the
+ * same change.
+ *
+ * Baselines live under
+ * {@code src/test/resources/visual-baselines/coverletter-v2-layered/}.
+ * Budget mirrors {@code CvV2VisualParityTest} (50 000 mismatched
+ * pixels at per-channel tolerance 8) β sized for the worst-case
+ * Helvetica cross-platform drift between Windows-recorded baselines
+ * and Linux CI.
+ */
+class CoverLetterV2VisualParityTest {
+
+ private static final Path BASELINE_ROOT = Path.of(
+ "src", "test", "resources", "visual-baselines", "coverletter-v2-layered");
+
+ private static final long PIXEL_DIFF_BUDGET = 50_000L;
+ private static final int PER_PIXEL_TOLERANCE = 8;
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("presets")
+ void rendersWithinPixelDiffBudget(String slug,
+ double margin,
+ Supplier> factory)
+ throws Exception {
+ DocumentTemplate template = factory.get();
+ float m = (float) margin;
+ byte[] pdfBytes;
+ try (DocumentSession document = GraphCompose.document()
+ .pageSize(DocumentPageSize.A4)
+ .margin(m, m, m, m)
+ .create()) {
+ template.compose(document, canonicalLetter());
+ pdfBytes = document.toPdfBytes();
+ }
+
+ PdfVisualRegression.standard()
+ .baselineRoot(BASELINE_ROOT)
+ .perPixelTolerance(PER_PIXEL_TOLERANCE)
+ .mismatchedPixelBudget(PIXEL_DIFF_BUDGET)
+ .assertMatchesBaseline(slug, pdfBytes);
+ }
+
+ private static Stream presets() {
+ return Stream.of(
+ Arguments.of("executive",
+ ExecutiveLetter.RECOMMENDED_MARGIN,
+ (Supplier>) ExecutiveLetter::create),
+ Arguments.of("modern_professional",
+ ModernProfessionalLetter.RECOMMENDED_MARGIN,
+ (Supplier>) ModernProfessionalLetter::create),
+ Arguments.of("boxed_sections",
+ BoxedSectionsLetter.RECOMMENDED_MARGIN,
+ (Supplier>) BoxedSectionsLetter::create),
+ Arguments.of("classic_serif",
+ ClassicSerifLetter.RECOMMENDED_MARGIN,
+ (Supplier>) ClassicSerifLetter::create),
+ Arguments.of("editorial_blue",
+ EditorialBlueLetter.RECOMMENDED_MARGIN,
+ (Supplier>) EditorialBlueLetter::create),
+ Arguments.of("centered_headline",
+ CenteredHeadlineLetter.RECOMMENDED_MARGIN,
+ (Supplier>) CenteredHeadlineLetter::create),
+ Arguments.of("blue_banner",
+ BlueBannerLetter.RECOMMENDED_MARGIN,
+ (Supplier>) BlueBannerLetter::create),
+ Arguments.of("engineering_resume",
+ EngineeringResumeLetter.RECOMMENDED_MARGIN,
+ (Supplier>) EngineeringResumeLetter::create),
+ Arguments.of("panel",
+ PanelLetter.RECOMMENDED_MARGIN,
+ (Supplier>) PanelLetter::create),
+ Arguments.of("compact_mono",
+ CompactMonoLetter.RECOMMENDED_MARGIN,
+ (Supplier>) CompactMonoLetter::create),
+ Arguments.of("nordic_clean",
+ NordicCleanLetter.RECOMMENDED_MARGIN,
+ (Supplier>) NordicCleanLetter::create),
+ Arguments.of("sidebar_portrait",
+ SidebarPortraitLetter.RECOMMENDED_MARGIN,
+ (Supplier>) SidebarPortraitLetter::create),
+ Arguments.of("monogram_sidebar",
+ MonogramSidebarLetter.RECOMMENDED_MARGIN,
+ (Supplier>) MonogramSidebarLetter::create),
+ Arguments.of("timeline_minimal",
+ TimelineMinimalLetter.RECOMMENDED_MARGIN,
+ (Supplier>) TimelineMinimalLetter::create));
+ }
+
+ /**
+ * Canonical sample letter β the same Jordan Rivera identity as
+ * {@code CvV2VisualParityTest} so the letter masthead is verified
+ * against the same content the CV gate uses, plus a greeting, three
+ * body paragraphs with inline markdown, and a closing.
+ *
+ * Kept inline (not pulled from the examples module) so the test
+ * depends only on main + main-test code.
+ */
+ private static CoverLetterDocument canonicalLetter() {
+ return CoverLetterDocument.builder()
+ .identity(CvIdentity.builder()
+ .name("Jordan", "Rivera")
+ .jobTitle("Platform Engineer")
+ .contact("+44 20 5555 1000",
+ "jordan.rivera@example.com",
+ "London, UK")
+ .link("LinkedIn", "https://linkedin.com/in/jordan-rivera-demo")
+ .link("GitHub", "https://github.com/jrivera-demo")
+ .build())
+ .greeting("Dear Hiring Team at **Northwind Systems**,")
+ .paragraph("I am excited to share my interest in the Senior "
+ + "Platform Engineer role. My recent work has focused "
+ + "on building **reusable document-generation systems** "
+ + "that balance public API design, render quality, and "
+ + "maintainability.")
+ .paragraph("I enjoy translating fuzzy workflow requirements into "
+ + "clear template abstractions, reliable test coverage, "
+ + "and examples that make adoption easier for the rest "
+ + "of the team.")
+ .paragraph("I would welcome the opportunity to bring that same "
+ + "mix of engineering rigor and product thinking to your "
+ + "platform group.")
+ .closing("Sincerely,")
+ .build();
+ }
+}
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/blue_banner-page-0.png b/src/test/resources/visual-baselines/coverletter-v2-layered/blue_banner-page-0.png
new file mode 100644
index 00000000..61189b5b
Binary files /dev/null and b/src/test/resources/visual-baselines/coverletter-v2-layered/blue_banner-page-0.png differ
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/boxed_sections-page-0.png b/src/test/resources/visual-baselines/coverletter-v2-layered/boxed_sections-page-0.png
new file mode 100644
index 00000000..7c555734
Binary files /dev/null and b/src/test/resources/visual-baselines/coverletter-v2-layered/boxed_sections-page-0.png differ
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/centered_headline-page-0.png b/src/test/resources/visual-baselines/coverletter-v2-layered/centered_headline-page-0.png
new file mode 100644
index 00000000..a9a7bfb6
Binary files /dev/null and b/src/test/resources/visual-baselines/coverletter-v2-layered/centered_headline-page-0.png differ
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/classic_serif-page-0.png b/src/test/resources/visual-baselines/coverletter-v2-layered/classic_serif-page-0.png
new file mode 100644
index 00000000..ad38a57b
Binary files /dev/null and b/src/test/resources/visual-baselines/coverletter-v2-layered/classic_serif-page-0.png differ
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/compact_mono-page-0.png b/src/test/resources/visual-baselines/coverletter-v2-layered/compact_mono-page-0.png
new file mode 100644
index 00000000..6e34b928
Binary files /dev/null and b/src/test/resources/visual-baselines/coverletter-v2-layered/compact_mono-page-0.png differ
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/editorial_blue-page-0.png b/src/test/resources/visual-baselines/coverletter-v2-layered/editorial_blue-page-0.png
new file mode 100644
index 00000000..b331a7da
Binary files /dev/null and b/src/test/resources/visual-baselines/coverletter-v2-layered/editorial_blue-page-0.png differ
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/engineering_resume-page-0.png b/src/test/resources/visual-baselines/coverletter-v2-layered/engineering_resume-page-0.png
new file mode 100644
index 00000000..808897ee
Binary files /dev/null and b/src/test/resources/visual-baselines/coverletter-v2-layered/engineering_resume-page-0.png differ
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/executive-page-0.png b/src/test/resources/visual-baselines/coverletter-v2-layered/executive-page-0.png
new file mode 100644
index 00000000..bd13449a
Binary files /dev/null and b/src/test/resources/visual-baselines/coverletter-v2-layered/executive-page-0.png differ
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/modern_professional-page-0.png b/src/test/resources/visual-baselines/coverletter-v2-layered/modern_professional-page-0.png
new file mode 100644
index 00000000..333545d9
Binary files /dev/null and b/src/test/resources/visual-baselines/coverletter-v2-layered/modern_professional-page-0.png differ
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/monogram_sidebar-page-0.png b/src/test/resources/visual-baselines/coverletter-v2-layered/monogram_sidebar-page-0.png
new file mode 100644
index 00000000..ff0ec8e5
Binary files /dev/null and b/src/test/resources/visual-baselines/coverletter-v2-layered/monogram_sidebar-page-0.png differ
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/nordic_clean-page-0.png b/src/test/resources/visual-baselines/coverletter-v2-layered/nordic_clean-page-0.png
new file mode 100644
index 00000000..40ca8b4d
Binary files /dev/null and b/src/test/resources/visual-baselines/coverletter-v2-layered/nordic_clean-page-0.png differ
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/panel-page-0.png b/src/test/resources/visual-baselines/coverletter-v2-layered/panel-page-0.png
new file mode 100644
index 00000000..5ed6a4d1
Binary files /dev/null and b/src/test/resources/visual-baselines/coverletter-v2-layered/panel-page-0.png differ
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/sidebar_portrait-page-0.png b/src/test/resources/visual-baselines/coverletter-v2-layered/sidebar_portrait-page-0.png
new file mode 100644
index 00000000..12e3502d
Binary files /dev/null and b/src/test/resources/visual-baselines/coverletter-v2-layered/sidebar_portrait-page-0.png differ
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/timeline_minimal-page-0.png b/src/test/resources/visual-baselines/coverletter-v2-layered/timeline_minimal-page-0.png
new file mode 100644
index 00000000..97c5765c
Binary files /dev/null and b/src/test/resources/visual-baselines/coverletter-v2-layered/timeline_minimal-page-0.png differ