Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,34 @@ follow semantic versioning; release dates are ISO 8601.

### Templates

- **A banded single-column CV preset: `VioletGrid`.** A one-page sheet with no page-level
grid at all: a two-tone name beside the contact list, three opening lines, a six-up
grid of marked skills divided by dotted rules, a strip of tools on inline discs, the
roles held on a dated timeline whose dates sit outside the rail, the projects behind
tinted tiles, education and languages side by side, and a tinted band closing on a
quotation. Ships as `cv.presets.VioletGrid` on the existing `CvDocument` model,
porting the rendered layout of a published standalone template; like `SlateOrange` it
reads a rated language's wording from `CvSkill.note`. Every split is local to one
band — the masthead splits where the contact marks begin, the timeline into dates,
rail and content, a project into tile and copy, the credentials into two halves — and
none of the four knows about the others, which is why every horizontal arrangement is
either a top-level row or a table and none of them nests. Three marks are
relationships rather than lengths: a section rule starts where its heading ends
because it is a weighted column taking what an auto column leaves; the timeline rail
is the left border of every entry but the last, so consecutive entries butt into one
line; and a project's hairline is its copy's left border, so it is exactly as tall as
the copy. Seven berths reach their sections by title, including a quotation berth
whose body is drawn and whose title is not. Unlike its ported siblings this sheet
flows: each experience entry is held together, so a longer CV runs onto a second page
rather than cutting a role in half. As on the presets before it a link is drawn as its
own label with the address behind it, the one deliberate departure from the ported
sheet, measured at 1 882 of 2 173 720 pixels. Guarded by a smoke test (including the
unknown-mark data error, a skill with no mark, the two-tone name, the quotation's
title staying off the sheet, the link targets on every kind of title, an identity with
no links, a document with nothing but an identity, and the run onto a second page), an
exact layout snapshot and a pixel-parity gate; the examples showcase gains
`cv-violet-grid-v2`.

- **A masthead-and-rail CV preset: `SlateOrange`.** A one-page sheet built as a
full-bleed slate band over a two-column body: an orange monogram tile beside the name,
the role line and a tracked specialism strip, with the contact lines across an orange
Expand Down
Binary file added assets/readme/examples/cv-violet-grid-v2.pdf
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import com.demcha.examples.templates.coverletter.CvTimelineMinimalLetterV2Example;
import com.demcha.examples.templates.cv.v2.CharcoalGoldExample;
import com.demcha.examples.templates.cv.v2.SlateOrangeExample;
import com.demcha.examples.templates.cv.v2.VioletGridExample;
import com.demcha.examples.templates.cv.v2.TealPulseExample;
import com.demcha.examples.templates.cv.v2.TerracottaRailExample;
import com.demcha.examples.templates.cv.v2.CvBlueBannerExample;
Expand Down Expand Up @@ -160,6 +161,7 @@ public static void main(String[] args) throws Exception {
System.out.println("Generated: " + TerracottaRailExample.generate());
System.out.println("Generated: " + TealPulseExample.generate());
System.out.println("Generated: " + SlateOrangeExample.generate());
System.out.println("Generated: " + VioletGridExample.generate());

// Cover letters (v2 layered — 15 paired letters, one per CV preset)
System.out.println("Generated: " + CvBlueBannerLetterV2Example.generate());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ record Entry(String title, String description, List<String> tags, String codeUrl
cv("cv-terracotta-rail-v2", "TerracottaRailExample", "Terracotta Rail", "Serif monogram over a terracotta rule, contact channels behind their marks and two bulleted lists, beside a letter-spaced masthead, roles on a ringed rail, a projects grid and the degrees.", "sidebar", "monogram", "terracotta");
cv("cv-teal-pulse-v2", "TealPulseExample", "Teal Pulse", "Clinical sheet in five bands: a heart crossed by a pulse beside the name, a contact strip on rules, competencies beside the summary and roles, and a three-column closing band over a tracked tagline.", "clinical", "badges", "teal");
cv("cv-slate-orange-v2", "SlateOrangeExample", "Slate Orange", "Full-bleed slate masthead with an orange monogram tile, over a sidebar of marked competencies, trophied achievements and rated languages beside a profile, a dated experience rail and a credentials footer.", "sidebar", "masthead", "orange");
cv("cv-violet-grid-v2", "VioletGridExample", "Violet Grid", "Single-column sheet in bands: a two-tone name beside the contact list, a six-up grid of marked skills on dotted rules, a tools strip, a dated timeline, tinted project tiles and a closing quotation.", "single-column", "grid", "violet");

// ===== Templates / Cover Letter (v2 layered, paired 1:1 with CV) =====
// Registered directly: letter() points at the layered preset examples under
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package com.demcha.examples.support;

import com.demcha.compose.document.templates.core.identity.Contact;
import com.demcha.compose.document.templates.core.identity.Link;
import com.demcha.compose.document.templates.cv.data.CvDocument;
import com.demcha.compose.document.templates.cv.data.CvEntry;
import com.demcha.compose.document.templates.cv.data.CvIdentity;
import com.demcha.compose.document.templates.cv.data.CvName;
import com.demcha.compose.document.templates.cv.data.CvSkill;
import com.demcha.compose.document.templates.cv.data.EntriesSection;
import com.demcha.compose.document.templates.cv.data.ParagraphSection;
import com.demcha.compose.document.templates.cv.data.SkillGroup;
import com.demcha.compose.document.templates.cv.data.SkillsSection;
import com.demcha.compose.document.templates.cv.data.Slot;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
* Shared sample data for the Violet Grid CV example.
*
* <p>The sample is the preset's reference content: a one-page CV with five
* contact lines including two links, three opening lines, six marked skills,
* eleven tools, three roles, two projects, a degree, three rated languages
* and the closing quotation.</p>
*
* <p>Kept in lockstep with the qa module's {@code VioletGridFixtures} — the
* two modules cannot share a source file, so a content change here belongs
* there too.</p>
*/
public final class VioletGridSampleData {

/** The break a body stacks its lines on. */
private static final String NEWLINE = String.valueOf((char) 10);

private VioletGridSampleData() {
}

/** The single-page reference CV. */
public static CvDocument sample() {
List<CvDocument.Placement> placements = new ArrayList<>();
placements.add(new CvDocument.Placement(Slot.MAIN, summary()));
placements.add(new CvDocument.Placement(Slot.MAIN, skills()));
placements.add(new CvDocument.Placement(Slot.MAIN, tools()));
placements.add(new CvDocument.Placement(Slot.MAIN, experience()));
placements.add(new CvDocument.Placement(Slot.MAIN, projects()));
placements.add(new CvDocument.Placement(Slot.FOOTER, education()));
placements.add(new CvDocument.Placement(Slot.FOOTER, languages()));
placements.add(new CvDocument.Placement(Slot.FOOTER, quote()));
return new CvDocument(identity(), placements);
}

private static CvIdentity identity() {
return new CvIdentity(
CvName.of("SOFIA", "MARTINEZ"),
"UX / UI DESIGNER",
new Contact("+1 (415) 555-7842",
"sofia.martinez.design@gmail.com",
"San Francisco, CA, USA"),
List.of(
new Link("sofiamartinez.design", "https://sofiamartinez.design"),
new Link("LinkedIn", "https://www.linkedin.com/in/sofia-martinez-ux")),
Optional.empty());
}

/** The opening lines, set as written rather than wrapped. */
private static ParagraphSection summary() {
return new ParagraphSection("SUMMARY", String.join(NEWLINE,
"User-centered designer with 5+ years of experience "
+ "crafting intuitive digital experiences for web and "
+ "mobile products.",
"I combine research, design thinking, and visual design "
+ "to solve complex problems and deliver accessible, "
+ "elegant solutions.",
"Passionate about collaborating with cross-functional "
+ "teams to create products that delight users and drive "
+ "business impact."));
}

/** The six-up grid: a mark, a label and a description each. */
private static EntriesSection skills() {
return new EntriesSection("DESIGN SKILLS", List.of(
CvEntry.builder("UX RESEARCH")
.icon("ux-research")
.body("User interviews, surveys, personas, journey maps, "
+ "competitive analysis")
.build(),
CvEntry.builder("INFORMATION ARCHITECTURE")
.icon("information-architecture")
.body("Sitemaps, user flows, card sorting, content strategy")
.build(),
CvEntry.builder("WIREFRAMING")
.icon("wireframing")
.body("Low to high-fidelity wireframes, layout design, "
+ "interaction flows")
.build(),
CvEntry.builder("PROTOTYPING")
.icon("prototyping")
.body("Interactive prototypes, microinteractions, transitions")
.build(),
CvEntry.builder("USABILITY TESTING")
.icon("usability-testing")
.body("Test planning, usability tests, heuristic evaluation, "
+ "insights & iteration")
.build(),
CvEntry.builder("DESIGN SYSTEMS")
.icon("design-systems")
.body("Component libraries, design tokens, pattern "
+ "documentation")
.build()));
}

/** The tools strip — names without levels. */
private static SkillsSection tools() {
List<CvSkill> entries = new ArrayList<>();
for (String name : new String[] {
"Figma", "FigJam", "Adobe XD", "Sketch", "Miro", "Notion", "Jira", "Slack", "Maze", "Hotjar", "Airtable"}) {
entries.add(CvSkill.of(name));
}
return new SkillsSection("TOOLS",
List.of(new SkillGroup("TOOLS", entries)));
}

/** The roles held, each a headline over its bullets. */
private static EntriesSection experience() {
return new EntriesSection("EXPERIENCE", List.of(
CvEntry.builder("Senior UX/UI Designer")
.subtitle("NovaFin (FinTech Startup)")
.place("San Francisco, CA")
.date("2022 - Present")
.body(String.join(NEWLINE,
"Led end-to-end design for a personal finance platform, "
+ "from discovery to launch, improving task success rate by "
+ "32%.",
"Conducted user research, built personas and journey "
+ "maps, and translated insights into clear product "
+ "opportunities.",
"Designed wireframes, prototypes, and high-fidelity "
+ "interfaces for web and mobile using Figma.",
"Established and maintained a scalable design system, "
+ "increasing design consistency and development speed.",
"Collaborated with product managers, engineers, and data "
+ "analysts in an agile environment."))
.build(),
CvEntry.builder("UX Designer")
.subtitle("BrightHealth (HealthTech)")
.place("San Francisco, CA")
.date("2020 - 2022")
.body(String.join(NEWLINE,
"Redesigned patient portal experience, reducing support "
+ "tickets by 28% and improving user satisfaction.",
"Planned and facilitated usability tests and synthesized "
+ "findings to drive iterative improvements.",
"Partnered with engineers to ensure feasible solutions "
+ "and pixel-perfect implementation.",
"Created reusable components and style guides to support "
+ "a cohesive cross-platform experience."))
.build(),
CvEntry.builder("Junior UX/UI Designer")
.subtitle("Lumen Digital Agency")
.place("Austin, TX")
.date("2018 - 2020")
.body(String.join(NEWLINE,
"Supported discovery workshops, user flows, and "
+ "wireframes for clients across e-commerce and SaaS.",
"Designed responsive websites and landing pages following "
+ "best practices in accessibility and usability.",
"Collaborated with copywriters and developers to deliver "
+ "high-quality, user-centered solutions."))
.build()));
}

/** The projects, each behind its tinted tile. */
private static EntriesSection projects() {
return new EntriesSection("SELECTED PROJECTS", List.of(
CvEntry.builder("NovaFin Mobile App")
.subtitle("Personal Finance Management")
.date("2023")
.icon("project-wallet")
.body("Led UX/UI design for budgeting, goal tracking, and "
+ "insights features. Conducted user interviews and "
+ "usability tests that informed a simplified navigation "
+ "and data visualization. Resulted in 32% improvement in "
+ "task success rate and 22% increase in weekly active "
+ "users.")
.build(),
CvEntry.builder("BrightHealth Patient Portal")
.subtitle("Healthcare Platform Redesign")
.date("2021")
.icon("project-health")
.body("Redesigned key patient flows including appointment "
+ "scheduling, test results, and messaging. Created a new "
+ "design system and component library used across web and "
+ "mobile. Reduced support tickets by 28%.")
.build()));
}

/** The degree, with the place and the years on its third line. */
private static EntriesSection education() {
return new EntriesSection("EDUCATION", List.of(
CvEntry.builder("Bachelor of Fine Arts in Graphic Design")
.subtitle("California College of the Arts")
.place("San Francisco, CA")
.date("2014 - 2018")
.icon("graduation")
.build()));
}

/** The languages, each carrying both the rating and the word for it. */
private static SkillsSection languages() {
return new SkillsSection("LANGUAGES", List.of(new SkillGroup("LANGUAGES", List.of(
CvSkill.of("English", 1.0, "Native"),
CvSkill.of("Spanish", 1.0, "Native"),
CvSkill.of("Portuguese", 0.6, "Conversational")))));
}

/** The closing line. Its title names the berth and is not drawn. */
private static ParagraphSection quote() {
return new ParagraphSection("QUOTE",
"I design with empathy, iterate with purpose, and "
+ "collaborate to build products people love.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.demcha.examples.templates.cv.v2;

import com.demcha.compose.GraphCompose;
import com.demcha.compose.document.api.DocumentSession;
import com.demcha.compose.document.templates.api.DocumentTemplate;
import com.demcha.compose.document.templates.cv.data.CvDocument;
import com.demcha.compose.document.templates.cv.presets.VioletGrid;
import com.demcha.examples.support.ExampleOutputPaths;
import com.demcha.examples.support.VioletGridSampleData;

import java.nio.file.Path;

/**
* Renders the layered {@code cv.v2} Violet Grid preset against the design
* sample.
*
* <p>Output:
* {@code examples/target/generated-pdfs/templates/cv/cv-violet-grid-v2.pdf}.</p>
*
* <p>The preset owns its page geometry — every length is a share of the
* design's own grid — so the session starts unconfigured.</p>
*/
public final class VioletGridExample {

private VioletGridExample() {
}

/**
* @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-violet-grid-v2.pdf");
CvDocument doc = VioletGridSampleData.sample();
DocumentTemplate<CvDocument> template = VioletGrid.create();

try (DocumentSession document = GraphCompose.document(outputFile).create()) {
template.compose(document, doc);
document.buildPdf();
}
return outputFile;
}

/**
* @param args ignored
* @throws Exception if rendering fails
*/
public static void main(String[] args) throws Exception {
System.out.println("Generated: " + generate());
}
}
Loading
Loading