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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ follow semantic versioning; release dates are ISO 8601.
overflow that freezes the two-page table continuation), and the invoice pixel-parity
gate; the examples showcase gains `invoice-classic-v2`.

- **The first structured proposal preset: `NorthlineProposal`.** A two-page
teal-and-navy business proposal on the Spectral/Lato pair — brand header with logo
mark and wordmark, three stacked title lines on the reference's own pitch, the
executive summary beside an at-a-glance fact card, icon goal cells, a numbered
scope list, deliverable columns, the phase grid, an investment table with subtotal /
optional / total row styling, and a signing card — with the navy footer band, the
teal page-number block and the `01`/`02` page numbers as page chrome rather than
flow content. Ships as `proposal.presets.NorthlineProposal` consuming the new
structured proposal model, with its icon set packaged in the templates artifact,
porting the rendered layout of the published standalone `northline-proposal`
template. Guarded by a smoke test (including the empty document and the
phase-grid header-count contract), an exact two-page layout snapshot, and a
pixel-parity gate; the examples showcase gains `proposal-northline-v2`.

- **Monogram Sidebar draws the employer.** Its experience entries rendered the position,
the date and the description, and never `CvEntry.subtitle()` — so every company name
was missing from the rendered CV while the education block, which does render its
Expand Down
Binary file added assets/readme/examples/proposal-northline-v2.pdf
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
import com.demcha.examples.templates.invoice.InvoiceCinematicFileExample;
import com.demcha.examples.templates.invoice.ModernInvoiceV2Example;
import com.demcha.examples.templates.proposal.CinematicProposalFileExample;
import com.demcha.examples.templates.proposal.NorthlineProposalV2Example;
import com.demcha.examples.templates.proposal.ProposalCinematicFileExample;
import com.demcha.examples.templates.proposal.ModernProposalV2Example;
import com.demcha.examples.templates.schedule.WeeklyScheduleFileExample;
Expand Down Expand Up @@ -168,6 +169,7 @@ public static void main(String[] args) throws Exception {
// Proposals
System.out.println("Generated: " + ProposalCinematicFileExample.generate());
System.out.println("Generated: " + ModernProposalV2Example.generate());
System.out.println("Generated: " + NorthlineProposalV2Example.generate());
System.out.println("Generated: " + CinematicProposalFileExample.generate());

// Schedule
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ record Entry(String title, String description, List<String> tags, String codeUrl
proposal("proposal-cinematic", "ProposalCinematicFileExample", "Cinematic Proposal", "Layered ModernProposal layout with cover panel, hero spread, and rich typography.", "proposal", "cinematic");
proposal("project-proposal-cinematic", "CinematicProposalFileExample", "Project Proposal (cinematic)", "End-to-end project proposal with mountain hero, scope panels, and pricing summary.", "proposal", "cinematic");
proposal("proposal-modern-v2", "v2/ModernProposalV2Example", "Modern Proposal", "The ModernProposal preset composed straight from its document spec — cover, scope sections and pricing table themed through BrandTheme.", "proposal");
proposal("proposal-northline-v2", "v2/NorthlineProposalV2Example", "Northline Proposal", "The NorthlineProposal preset on the structured proposal model — brand header, stacked title, glance card, goal cells, numbered scope, phase grid, investment table and signing card across two pages.", "proposal");

// ===== Templates / Schedule =====
schedule("weekly-schedule", "Weekly Schedule", "Multi-day weekly schedule with shift assignments, category fills, and repeated header.", "schedule", "table");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package com.demcha.examples.support;

import com.demcha.compose.document.templates.data.proposal.ProposalAcceptance;
import com.demcha.compose.document.templates.data.proposal.ProposalBrand;
import com.demcha.compose.document.templates.data.proposal.ProposalDeliverables;
import com.demcha.compose.document.templates.data.proposal.ProposalGlance;
import com.demcha.compose.document.templates.data.proposal.ProposalGoals;
import com.demcha.compose.document.templates.data.proposal.ProposalInvestment;
import com.demcha.compose.document.templates.data.proposal.ProposalMetaLine;
import com.demcha.compose.document.templates.data.proposal.ProposalPhaseGrid;
import com.demcha.compose.document.templates.data.proposal.ProposalScope;
import com.demcha.compose.document.templates.data.proposal.ProposalSummaryBlock;
import com.demcha.compose.document.templates.data.proposal.ProposalTermsBlock;
import com.demcha.compose.document.templates.data.proposal.ProposalTitleLines;
import com.demcha.compose.document.templates.data.proposal.StructuredProposalData;
import com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec;

import java.util.List;

/**
* Shared sample data for the structured proposal examples.
*
* <p>Kept in lockstep with the qa module's {@code NorthlineProposalFixtures}
* — the two modules cannot share a source file, so a content change here
* belongs there too.</p>
*
* <p>The sample is the preset's reference content: a two-page
* brand-refresh proposal exercising every band — all four glance facts
* (one with the optional note), four goal cells, five scope rows, both
* deliverable columns, the four-phase grid, an investment table with
* ordinary, subtotal and optional rows plus the total band, four terms,
* and the three-field signing card.</p>
*/
public final class StructuredProposalSampleData {

private StructuredProposalSampleData() {
}

public static StructuredProposalDocumentSpec sample() {
return StructuredProposalDocumentSpec.from(StructuredProposalData.builder()
.brand(new ProposalBrand("N", "NORTHLINE", "STUDIO", "PROPOSAL",
"northlinestudio.com", "NORTHLINE STUDIO"))
.title(new ProposalTitleLines("Proposal –", "Brand Refresh &",
"Website Redesign"))
.meta(new ProposalMetaLine("PREPARED FOR ASTERA HEALTH CO.",
"PREPARED BY NORTHLINE STUDIO", "25 AUGUST 2026"))
.executiveSummary(new ProposalSummaryBlock("EXECUTIVE SUMMARY",
"badge-summary", List.of(
"Thank you for the opportunity to partner with Astera Health Co. "
+ "on your next chapter. This proposal outlines our approach to "
+ "refreshing your brand identity and redesigning your website to "
+ "better reflect your mission, elevate credibility, and deliver a "
+ "seamless experience for the people you serve.",
"Our process blends strategic thinking with thoughtful design to "
+ "create a cohesive brand system and a modern, conversion-focused "
+ "website. The outcome will be a clear, confident presence that "
+ "strengthens trust, improves engagement, and supports your "
+ "growth goals.")))
.glance(new ProposalGlance("PROJECT AT A GLANCE", List.of(
new ProposalGlance.Fact("fact-duration", "PROJECT DURATION",
"6 weeks", null),
new ProposalGlance.Fact("fact-start", "START WINDOW",
"September 2026", null),
new ProposalGlance.Fact("fact-contact", "PRIMARY CONTACT",
"Project Lead", null),
new ProposalGlance.Fact("fact-validity", "PROPOSAL VALIDITY",
"14 days", "(until 8 September 2026)"))))
.goals(new ProposalGoals("PROJECT GOALS", "badge-goals", List.of(
new ProposalGoals.Goal("goal-brand",
"Strengthen brand credibility and visual consistency."),
new ProposalGoals.Goal("goal-usability",
"Improve website usability and drive meaningful action."),
new ProposalGoals.Goal("goal-message",
"Communicate your value clearly across all touchpoints."),
new ProposalGoals.Goal("goal-growth",
"Build a scalable digital foundation for future growth."))))
.scope(new ProposalScope("SCOPE OF WORK", "badge-scope", List.of(
new ProposalScope.Item("01", "Discovery & Research",
"Stakeholder interviews, brand audit, competitor review, and "
+ "audience insights to inform strategy."),
new ProposalScope.Item("02", "Visual Identity Refinement",
"Refine logo system, color palette, typography, and visual "
+ "guidelines for a cohesive, modern identity."),
new ProposalScope.Item("03", "Website UX/UI Design",
"Design a responsive, accessibility-minded website with "
+ "intuitive navigation and clear conversion pathways."),
new ProposalScope.Item("04", "Content Structure & Messaging",
"Define site architecture, key messaging, and content "
+ "hierarchy to communicate value and build trust."),
new ProposalScope.Item("05", "Launch Support",
"Developer handoff, QA support, training, and go-live "
+ "assistance to ensure a smooth launch."))))
.deliverables(new ProposalDeliverables("DELIVERABLES", "badge-deliverables",
List.of("Brand strategy summary",
"Refined logo suite & brand guidelines",
"Color, typography & design system",
"Website UX wireframes",
"High-fidelity website designs"),
List.of("Responsive design for desktop, tablet & mobile",
"Content strategy & page templates",
"Developer-ready assets & specifications",
"Launch checklist & training session")))
.timeline(new ProposalPhaseGrid("TIMELINE", "badge-timeline",
List.of("PHASE", "FOCUS", "DURATION", "OUTPUT"), List.of(
new ProposalPhaseGrid.Phase("01", "Discover",
"Research, audit, insights, and strategy", "1 week",
"Discovery report & creative direction"),
new ProposalPhaseGrid.Phase("02", "Design",
"Brand refinement and website UX/UI design", "2 weeks",
"Design system & high-fidelity designs"),
new ProposalPhaseGrid.Phase("03", "Develop Prep",
"Content structure, developer handoff & asset preparation",
"1 week", "Specs, assets & content framework"),
new ProposalPhaseGrid.Phase("04", "Launch Support",
"QA, training & go-live assistance", "2 weeks",
"QA sign-off & successful launch"))))
.investment(new ProposalInvestment("INVESTMENT", "badge-investment",
"ITEM", "AMOUNT (GBP)", List.of(
new ProposalInvestment.Row("Discovery & Strategy", "£2,500",
ProposalInvestment.Role.NONE),
new ProposalInvestment.Row("Brand Identity Refinement", "£4,500",
ProposalInvestment.Role.NONE),
new ProposalInvestment.Row("Website UX/UI Design", "£7,000",
ProposalInvestment.Role.NONE),
new ProposalInvestment.Row("Content Structure & Messaging", "£2,000",
ProposalInvestment.Role.NONE),
new ProposalInvestment.Row("Launch Support", "£1,500",
ProposalInvestment.Role.NONE),
new ProposalInvestment.Row("Subtotal", "£17,500",
ProposalInvestment.Role.SUBTOTAL),
new ProposalInvestment.Row("Optional: 3 Months Post-Launch Support",
"£1,750", ProposalInvestment.Role.OPTIONAL)),
"TOTAL INVESTMENT", "£19,250"))
.terms(new ProposalTermsBlock("TERMS", "badge-terms", List.of(
"50% deposit is required to secure the project and schedule the "
+ "kick-off.",
"Includes two rounds of revisions per major deliverable.",
"Final files and handoff delivered upon final payment.",
"Ownership of all final deliverables transfers to Astera Health Co. "
+ "after full payment is received.")))
.acceptance(new ProposalAcceptance("ACCEPTANCE", "badge-acceptance",
"By signing below, you agree to the scope, timeline, investment, "
+ "and terms outlined in this proposal.",
List.of("Accepted by:", "Signature:", "Date:")))
.build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.demcha.examples.templates.proposal;

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.data.proposal.StructuredProposalDocumentSpec;
import com.demcha.compose.document.templates.proposal.presets.NorthlineProposal;
import com.demcha.examples.support.ExampleOutputPaths;
import com.demcha.examples.support.StructuredProposalSampleData;

import java.nio.file.Path;

/**
* Renders the layered {@code proposal.v2} Northline Proposal preset
* against the shared structured proposal sample data.
*
* <p>Output:
* {@code examples/target/generated-pdfs/templates/proposal/proposal-northline-v2.pdf}.</p>
*
* <p>The preset owns its page geometry — size, margins, the footer band
* and the page background — so the session starts unconfigured; see
* {@code NorthlineProposal.RECOMMENDED_MARGIN}.</p>
*/
public final class NorthlineProposalV2Example {

private NorthlineProposalV2Example() {
}

/**
* @return absolute path of the rendered PDF
* @throws Exception if rendering fails
*/
public static Path generate() throws Exception {
Path outputFile = ExampleOutputPaths.prepare(
"templates/proposal", "proposal-northline-v2.pdf");
StructuredProposalDocumentSpec spec = StructuredProposalSampleData.sample();
DocumentTemplate<StructuredProposalDocumentSpec> template = NorthlineProposal.create();

try (DocumentSession document = GraphCompose.document(outputFile).create()) {
template.compose(document, spec);
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