Document 14 of 14 · studio/README.md
One folder per template. fields.json says what the customizer exposes and
meta.json is the catalogue record. template.html and template.css are
the design itself and do not exist until the template is built.
The four folders here are drafts written at plan stage (2026-09-16) for the first four templates, so the build sessions start from a spec instead of a blank file. The build may change a limit or a default; when it does, the change is the truth and this note goes.
Contract: ARCHITECTURE.md section 2. Rules for the designs: DESIGN.md
section 6. A field's max is enforced twice, in the customizer and in
api/checkout.js; a theme is a set of --t-* variables and nothing else.
studio/invoice/meta.json{
"slug": "invoice",
"title": "Invoice Template",
"promise": "A clean invoice with your logo, your line items and the totals done for you, in Letter and A4.",
"category": "business",
"tier": 12,
"pages": 1,
"orientation": "portrait",
"sizes": [
"letter",
"a4"
],
"themes": {
"ledger": {
"label": "Ledger",
"--t-paper": "#ffffff",
"--t-ink": "#1a1a1a",
"--t-accent": "#1f4e79",
"--t-muted": "#5b6b7a"
},
"studio": {
"label": "Studio",
"--t-paper": "#ffffff",
"--t-ink": "#1a1a1a",
"--t-accent": "#8f2d2d",
"--t-muted": "#6b6f76"
},
"slate": {
"label": "Slate",
"--t-paper": "#ffffff",
"--t-ink": "#1a1a1a",
"--t-accent": "#3f4a5a",
"--t-muted": "#7a838f"
},
"forest": {
"label": "Forest",
"--t-paper": "#ffffff",
"--t-ink": "#1a1a1a",
"--t-accent": "#2f5d46",
"--t-muted": "#6f7f76"
},
"kraft": {
"label": "Kraft",
"--t-paper": "#f6efe2",
"--t-ink": "#2a2420",
"--t-accent": "#6e5330",
"--t-muted": "#8a7a66"
},
"light-ink": {
"label": "Light ink",
"--t-paper": "#ffffff",
"--t-ink": "#222222",
"--t-accent": "#444444",
"--t-muted": "#777777",
"printerFriendly": true
}
},
"fonts": [
"newsreader",
"inter-tight"
],
"phrase": "invoice template pdf",
"family": "business-set",
"free": {
"size": "letter",
"theme": "ledger",
"credit": true
},
"version": 1,
"copy": {
"description": "WRITTEN IN THE BUILD SESSION. 300 to 600 words: who it is for (freelancers and small studios sending a handful of invoices a month), how the layout reads (business block, bill-to block, the table, totals in the corner where the eye lands), what to print it on if printed at all, and the one design decision worth explaining (the totals column is wider than it needs to be so long amounts never wrap). Human-voice pass before commit.",
"faq": [
{
"q": "Does it add up the totals for me?",
"a": "Yes. Type a quantity and a rate for each line and the line totals, subtotal, tax and total are worked out as you go. You cannot type a total by hand, which is how invoices stop matching their own lines."
},
{
"q": "Can I put my logo on it?",
"a": "Yes, on the paid version. A PNG or JPEG, and it sits at the top left. The free version carries the sample logo mark and a small credit line."
},
{
"q": "Which currencies does it handle?",
"a": "US dollars, euros, pounds, and Canadian and Australian dollars, with the symbol and the number format that goes with each."
},
{
"q": "Is it Letter or A4?",
"a": "Both. One purchase gives you both files, made from the same design, so the layout is identical on either paper."
},
{
"q": "Can I use it for my business?",
"a": "Yes. The paid version includes small-business use. What you cannot do is resell the template itself or offer it as a download. The license page says exactly that."
}
],
"pin": {
"title": "Invoice template, PDF, free plain version",
"description": "A clean invoice you fill in the browser: your logo, your lines, the totals done for you. Free plain version, or made yours in Letter and A4."
},
"og": "A clean invoice, made yours in a minute. Totals done for you. Letter and A4."
}
}
studio/invoice/fields.json{
"groups": [
{ "title": "Your business", "fields": [
{ "key": "logo", "label": "Logo", "type": "image", "hint": "PNG or JPEG. Square or wide both work. Optional." },
{ "key": "business_name", "label": "Business name", "type": "text", "max": 40, "default": "Studio & Co." },
{ "key": "address", "label": "Address", "type": "text", "max": 90, "default": "12 Bay Street, Portland, OR 97209" },
{ "key": "contact", "label": "Email or phone", "type": "text", "max": 60, "default": "hello@studioandco.example · (503) 555-0142" }
]},
{ "title": "The invoice", "fields": [
{ "key": "invoice_number", "label": "Invoice number", "type": "text", "max": 16, "default": "INV-0042" },
{ "key": "date", "label": "Date", "type": "date", "default": "today" },
{ "key": "due_date", "label": "Due", "type": "date", "default": "today+14" },
{ "key": "currency", "label": "Currency", "type": "select", "options": ["USD", "EUR", "GBP", "CAD", "AUD"], "default": "USD" },
{ "key": "tax_rate", "label": "Tax rate", "type": "number", "min": 0, "max": 30, "step": 0.25, "default": 0, "suffix": "%" }
]},
{ "title": "Bill to", "fields": [
{ "key": "client_name", "label": "Client", "type": "text", "max": 40, "default": "Harbor Bakery" },
{ "key": "client_address", "label": "Client address", "type": "text", "max": 90, "default": "48 Front Street, Astoria, OR 97103" }
]},
{ "title": "Line items", "fields": [
{ "key": "items", "label": "Items", "type": "list", "min": 1, "max": 12,
"row": [
{ "key": "description", "label": "Description", "type": "text", "max": 60 },
{ "key": "qty", "label": "Qty", "type": "number", "min": 0, "max": 9999, "step": 0.5 },
{ "key": "rate", "label": "Rate", "type": "number", "min": 0, "max": 999999, "step": 0.01 }
],
"default": [
{ "description": "Brand identity, phase one", "qty": 1, "rate": 1200 },
{ "description": "Menu design, two sizes", "qty": 1, "rate": 450 },
{ "description": "Print coordination (hours)", "qty": 3, "rate": 85 }
]
}
]},
{ "title": "Notes and payment", "fields": [
{ "key": "show_notes", "label": "Show a note", "type": "toggle", "default": true },
{ "key": "notes", "label": "Note", "type": "long", "max": 220, "assist": true,
"assistBrief": "A short, warm note at the foot of an invoice: thanks, and how or when to pay.",
"default": "Thank you. Payment is due within 14 days by bank transfer; details below." },
{ "key": "show_payment", "label": "Show payment details", "type": "toggle", "default": true },
{ "key": "payment_details", "label": "Payment details", "type": "long", "max": 160,
"default": "Studio & Co. · Routing 123456789 · Account 000123456789" }
]},
{ "title": "Look", "fields": [
{ "key": "theme", "label": "Theme", "type": "theme" },
{ "key": "size", "label": "Paper", "type": "size", "default": "letter" }
]}
],
"compute": "var items = v.items || []; items.forEach(function (i) { i.line_total = (Number(i.qty) || 0) * (Number(i.rate) || 0); }); v.subtotal = items.reduce(function (s, i) { return s + i.line_total; }, 0); v.tax = v.subtotal * ((Number(v.tax_rate) || 0) / 100); v.total = v.subtotal + v.tax;"
}
studio/weekly-planner/meta.json{
"slug": "weekly-planner",
"title": "Weekly Planner Template",
"promise": "One week on one sheet, with your name on it and the sections you actually use, dated or not.",
"category": "planners",
"tier": 7,
"pages": 1,
"orientation": "landscape",
"sizes": [
"letter",
"a4"
],
"themes": {
"studio": {
"label": "Studio",
"--t-paper": "#ffffff",
"--t-ink": "#1a1a1a",
"--t-accent": "#8f2d2d",
"--t-muted": "#8a8f96"
},
"ledger": {
"label": "Ledger",
"--t-paper": "#ffffff",
"--t-ink": "#1a1a1a",
"--t-accent": "#1f4e79",
"--t-muted": "#8a8f96"
},
"sage": {
"label": "Sage",
"--t-paper": "#ffffff",
"--t-ink": "#1f2a24",
"--t-accent": "#5f7f6b",
"--t-muted": "#8f9b94"
},
"blush": {
"label": "Blush",
"--t-paper": "#fffafa",
"--t-ink": "#2b2224",
"--t-accent": "#c4737e",
"--t-muted": "#a08f92"
},
"sand": {
"label": "Sand",
"--t-paper": "#fbf7f0",
"--t-ink": "#2b2620",
"--t-accent": "#b48a4a",
"--t-muted": "#9c8f7c"
},
"slate": {
"label": "Slate",
"--t-paper": "#ffffff",
"--t-ink": "#1a1a1a",
"--t-accent": "#3f4a5a",
"--t-muted": "#8a8f96"
},
"kraft": {
"label": "Kraft",
"--t-paper": "#f6efe2",
"--t-ink": "#2a2420",
"--t-accent": "#6e5330",
"--t-muted": "#8a7a66"
},
"light-ink": {
"label": "Light ink",
"--t-paper": "#ffffff",
"--t-ink": "#222222",
"--t-accent": "#555555",
"--t-muted": "#8c8c8c",
"printerFriendly": true
}
},
"fonts": [
"fraunces",
"inter-tight"
],
"phrase": "weekly planner printable pdf",
"family": "planner-set",
"free": {
"size": "letter",
"theme": "studio",
"credit": true
},
"version": 1,
"copy": {
"description": "WRITTEN IN THE BUILD SESSION. Who it is for (anyone who plans a week on paper and prints the same sheet fifty times), why the seven columns are equal and the weekend is not squeezed, why the sections are toggles instead of a second template, and the light-ink theme for the person printing it weekly. Human-voice pass before commit.",
"faq": [
{
"q": "Dated or undated?",
"a": "Either. Undated prints the day names only, so one sheet lasts a year of Mondays. Dated fills in the dates for the week you pick."
},
{
"q": "Can the week start on Sunday?",
"a": "Yes. It is a setting, and the columns reorder."
},
{
"q": "Will it use a lot of ink?",
"a": "Not in the light-ink theme, which is rules and one accent and nothing filled. It is there for exactly this sheet."
},
{
"q": "Is the free version different?",
"a": "It is the same design in the Studio theme, undated, on Letter, with a small credit line at the foot. The paid version adds your name, the other themes, dates, and A4."
},
{
"q": "Can I print it double-sided?",
"a": "Yes, and the print help page shows how to get the back the right way up on a home printer."
}
],
"pin": {
"title": "Weekly planner printable, free PDF",
"description": "One week on one sheet, in your colours, dated or undated, Monday or Sunday start. Free plain version."
},
"og": "One week, one sheet, your name on it. Free plain version."
}
}
studio/weekly-planner/fields.json{
"groups": [
{ "title": "Header", "fields": [
{ "key": "title", "label": "Title", "type": "text", "max": 30, "default": "This week" },
{ "key": "name", "label": "Name", "type": "text", "max": 24, "default": "", "hint": "Optional. Sits under the title." }
]},
{ "title": "The week", "fields": [
{ "key": "week_start", "label": "Week starts on", "type": "select", "options": ["Monday", "Sunday"], "default": "Monday" },
{ "key": "dated", "label": "Dated", "type": "toggle", "default": false, "hint": "Off gives an undated sheet you can print every week." },
{ "key": "start_date", "label": "Week of", "type": "date", "default": "next-week-start", "showWhen": { "dated": true } }
]},
{ "title": "Sections", "fields": [
{ "key": "show_priorities", "label": "Top three", "type": "toggle", "default": true },
{ "key": "show_meals", "label": "Meals row", "type": "toggle", "default": false },
{ "key": "show_habits", "label": "Habit strip", "type": "toggle", "default": false },
{ "key": "show_notes", "label": "Notes", "type": "toggle", "default": true }
]},
{ "title": "Look", "fields": [
{ "key": "theme", "label": "Theme", "type": "theme" },
{ "key": "size", "label": "Paper", "type": "size", "default": "letter" }
]}
],
"compute": "var d = v.dated && v.start_date ? new Date(v.start_date) : null; v.days = []; for (var i = 0; i < 7; i++) { var names = v.week_start === 'Sunday' ? ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'] : ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday']; var day = { name: names[i], date: '' }; if (d) { var x = new Date(d); x.setDate(d.getDate() + i); day.date = String(x.getDate()); } v.days.push(day); }"
}
studio/certificate-completion/meta.json{
"slug": "certificate-completion",
"title": "Certificate of Completion Template",
"promise": "A certificate that sets the name properly, with your wording, your signature lines and a seal, on Letter or A4.",
"category": "certificates",
"tier": 7,
"pages": 1,
"orientation": "landscape",
"sizes": [
"letter",
"a4"
],
"themes": {
"ledger": {
"label": "Ledger",
"--t-paper": "#ffffff",
"--t-ink": "#1a1a1a",
"--t-accent": "#1f4e79",
"--t-muted": "#6b6f76"
},
"studio": {
"label": "Studio",
"--t-paper": "#ffffff",
"--t-ink": "#1a1a1a",
"--t-accent": "#8f2d2d",
"--t-muted": "#6b6f76"
},
"gilt": {
"label": "Gilt",
"--t-paper": "#fffdf8",
"--t-ink": "#1f1b14",
"--t-accent": "#9a7b2f",
"--t-muted": "#8c8270"
},
"forest": {
"label": "Forest",
"--t-paper": "#ffffff",
"--t-ink": "#1a1a1a",
"--t-accent": "#2f5d46",
"--t-muted": "#6f7f76"
},
"kraft": {
"label": "Kraft",
"--t-paper": "#f6efe2",
"--t-ink": "#2a2420",
"--t-accent": "#6e5330",
"--t-muted": "#8a7a66"
},
"light-ink": {
"label": "Light ink",
"--t-paper": "#ffffff",
"--t-ink": "#222222",
"--t-accent": "#444444",
"--t-muted": "#777777",
"printerFriendly": true
}
},
"fonts": [
"newsreader",
"inter-tight"
],
"phrase": "certificate of completion template",
"family": "certificate-set",
"free": {
"size": "letter",
"theme": "ledger",
"credit": true
},
"version": 1,
"copy": {
"description": "WRITTEN IN THE BUILD SESSION. Who hands these out (workshops, courses, clubs, employers), why the name is the largest thing on the page and why the customizer refuses a name that runs off the ribbon, the three frames, what paper it deserves (heavier stock, and why 65 lb cover feels right), and the seal. Human-voice pass before commit.",
"faq": [
{
"q": "What if the name is too long?",
"a": "The customizer tells you, and the download waits until it fits. Twenty-six characters fit the ribbon at full size. A longer name can go on two lines by shortening a middle name to an initial, or you can drop the second signature to buy room."
},
{
"q": "Can I add my own wording?",
"a": "Yes, one hundred and forty characters under the name. If you would rather not write it, the Write it for me button drafts a line you can edit."
},
{
"q": "Does it have a seal?",
"a": "A drawn one, with up to fourteen characters in it, on or off."
},
{
"q": "What should I print it on?",
"a": "Something heavier than copier paper. The print help page names weights that go through a home printer."
},
{
"q": "Is the free version usable?",
"a": "It is the same design with the sample name and a small credit line, so it is good for seeing it on paper. A real certificate needs the real name, which is the paid version."
}
],
"pin": {
"title": "Certificate of completion template, PDF",
"description": "A certificate that sets the name properly. Your wording, your signature lines, a seal, three frames. Free plain version."
},
"og": "A certificate that sets the name properly. Made yours in a minute."
}
}
studio/certificate-completion/fields.json{
"groups": [
{ "title": "Who", "fields": [
{ "key": "recipient", "label": "Recipient", "type": "text", "max": 26, "default": "Avery Lane" }
]},
{ "title": "What for", "fields": [
{ "key": "course", "label": "Course or achievement", "type": "text", "max": 60, "default": "the Spring Letterpress Workshop" },
{ "key": "wording", "label": "Wording", "type": "long", "max": 140, "assist": true,
"assistBrief": "The one or two lines on a certificate of completion that say what was completed and how, formal but warm, no cliché.",
"default": "has completed, with care and to the standard of the studio," },
{ "key": "date", "label": "Date", "type": "date", "default": "today" }
]},
{ "title": "Presented by", "fields": [
{ "key": "organization", "label": "Organization", "type": "text", "max": 40, "default": "Bay Street Print Studio" },
{ "key": "logo", "label": "Logo", "type": "image", "hint": "Optional. Sits above the organization name." },
{ "key": "presenter", "label": "Signature name", "type": "text", "max": 40, "default": "Morgan Reyes" },
{ "key": "presenter_title", "label": "Signature title", "type": "text", "max": 30, "default": "Instructor" },
{ "key": "second_signature", "label": "Second signature", "type": "toggle", "default": false },
{ "key": "presenter2", "label": "Second name", "type": "text", "max": 40, "default": "", "showWhen": { "second_signature": true } },
{ "key": "presenter2_title", "label": "Second title", "type": "text", "max": 30, "default": "", "showWhen": { "second_signature": true } }
]},
{ "title": "Marks", "fields": [
{ "key": "seal", "label": "Seal", "type": "toggle", "default": true },
{ "key": "seal_text", "label": "Seal text", "type": "text", "max": 14, "default": "COMPLETED", "showWhen": { "seal": true } },
{ "key": "frame", "label": "Frame", "type": "select", "options": ["Classic", "Modern", "Plain"], "default": "Classic" }
]},
{ "title": "Look", "fields": [
{ "key": "theme", "label": "Theme", "type": "theme" },
{ "key": "size", "label": "Paper", "type": "size", "default": "letter" }
]}
],
"compute": ""
}
studio/monthly-calendar-2027/meta.json{
"slug": "monthly-calendar-2027",
"title": "2027 Monthly Calendar Template",
"promise": "Twelve months on twelve sheets, with holidays marked, a notes column, and your name on every page.",
"category": "calendars",
"tier": 7,
"pages": 12,
"orientation": "landscape",
"sizes": [
"letter",
"a4"
],
"months": true,
"monthPages": {
"slugPattern": "{month}-2027-calendar",
"titlePattern": "{Month} 2027 Calendar",
"promisePattern": "{Month} 2027 on one sheet, printable, with holidays marked and a notes column. Free, or made yours.",
"phrasePattern": "{month} 2027 calendar printable"
},
"themes": {
"studio": {
"label": "Studio",
"--t-paper": "#ffffff",
"--t-ink": "#1a1a1a",
"--t-accent": "#8f2d2d",
"--t-muted": "#8a8f96"
},
"ledger": {
"label": "Ledger",
"--t-paper": "#ffffff",
"--t-ink": "#1a1a1a",
"--t-accent": "#1f4e79",
"--t-muted": "#8a8f96"
},
"sage": {
"label": "Sage",
"--t-paper": "#ffffff",
"--t-ink": "#1f2a24",
"--t-accent": "#5f7f6b",
"--t-muted": "#8f9b94"
},
"sand": {
"label": "Sand",
"--t-paper": "#fbf7f0",
"--t-ink": "#2b2620",
"--t-accent": "#b48a4a",
"--t-muted": "#9c8f7c"
},
"slate": {
"label": "Slate",
"--t-paper": "#ffffff",
"--t-ink": "#1a1a1a",
"--t-accent": "#3f4a5a",
"--t-muted": "#8a8f96"
},
"light-ink": {
"label": "Light ink",
"--t-paper": "#ffffff",
"--t-ink": "#222222",
"--t-accent": "#555555",
"--t-muted": "#8c8c8c",
"printerFriendly": true
}
},
"fonts": [
"fraunces",
"inter-tight"
],
"phrase": "2027 monthly calendar printable",
"family": "planner-set",
"free": {
"size": "letter",
"theme": "studio",
"credit": true
},
"version": 1,
"copy": {
"description": "WRITTEN IN THE BUILD SESSION. Why the grid is six rows always (so every month is the same height and a binder of twelve lines up), the holiday list and where it comes from (the federal list, dated, in data/holidays-us-2027.json with its source), the notes column, and the monthly pages that show one month each. Human-voice pass before commit.",
"faq": [
{
"q": "Which holidays are marked?",
"a": "The eleven US federal holidays for 2027, from the official list, plus the observed dates where one falls on a weekend. It is a switch, so a calendar with no holidays is one click."
},
{
"q": "Can I print just one month?",
"a": "Yes. Every month has its own page on this site with its own free download, and the full twelve-sheet set is the paid version with your header on every page."
},
{
"q": "Sunday or Monday start?",
"a": "Either. The grid reorders and the weekend columns move with it."
},
{
"q": "Will there be a 2028 version?",
"a": "Yes, from the same design, in the autumn of 2027."
},
{
"q": "Why are all the months the same height?",
"a": "Six rows always, so twelve printed sheets line up in a binder or on a wall. Months that need five rows get an empty sixth rather than a taller grid."
}
],
"pin": {
"title": "2027 monthly calendar printable, free PDF",
"description": "Twelve months, holidays marked, a notes column, Sunday or Monday start. Free plain version, or made yours with your name on every page."
},
"og": "2027, twelve sheets, holidays marked, your name on every page."
}
}
studio/monthly-calendar-2027/fields.json{
"groups": [
{ "title": "The year", "fields": [
{ "key": "year", "label": "Year", "type": "select", "options": ["2027"], "default": "2027" },
{ "key": "week_start", "label": "Week starts on", "type": "select", "options": ["Monday", "Sunday"], "default": "Sunday" },
{ "key": "header", "label": "Header", "type": "text", "max": 30, "default": "", "hint": "Optional. Your name or a title, top left of every month." }
]},
{ "title": "On the page", "fields": [
{ "key": "show_holidays", "label": "US federal holidays", "type": "toggle", "default": true },
{ "key": "notes_column", "label": "Notes column", "type": "toggle", "default": true },
{ "key": "mini_months", "label": "Last and next month, small", "type": "toggle", "default": true }
]},
{ "title": "Look", "fields": [
{ "key": "theme", "label": "Theme", "type": "theme" },
{ "key": "size", "label": "Paper", "type": "size", "default": "letter" }
]}
],
"compute": "var y = Number(v.year) || 2027; var sunday = v.week_start === 'Sunday'; v.months = []; for (var m = 0; m < 12; m++) { var first = new Date(y, m, 1); var offset = sunday ? first.getDay() : (first.getDay() + 6) % 7; var count = new Date(y, m + 1, 0).getDate(); var cells = []; for (var b = 0; b < offset; b++) cells.push({ day: '' }); for (var d = 1; d <= count; d++) cells.push({ day: String(d), holiday: (v.holidays || {})[y + '-' + String(m + 1).padStart(2, '0') + '-' + String(d).padStart(2, '0')] || '' }); while (cells.length % 7) cells.push({ day: '' }); v.months.push({ name: ['January','February','March','April','May','June','July','August','September','October','November','December'][m], cells: cells }); }",
"data": { "holidays": "data/holidays-us-2027.json" }
}