Developer Notes
Scheduling sections (purpose, entities, services, architecture) arrive with step 5.
Branded Templates
Slot / plate model
A library design is a Template: a swappable background photo plus absolutely positioned layers. Each layer fills a named slot (headline, subhead, accent_fill, accent_text, footer_band, footer_text, logo) and declares a plate — the neutral backing (white, dark, scrim, or the brand band) it sits on. Positions are stored per aspect ratio (square, portrait, story) in percent of canvas width, so one definition drives thumbnails, the live preview and PNG export.
Contrast rules (src/lib/contrast.ts)
- Brand-colored text only sits on a neutral plate; if contrast is under 3:1 it is darkened/lightened step by step.
- Colored fills pick black or white text, whichever has higher WCAG contrast.
- Nothing touches the photo without a backing: plate, brand band, or dark scrim with white text.
- The logo always sits on a plate: white variant on dark/colored, dark variant on white.
Derived palette
Computed from the BrandKit, never stored: primary, primaryText, accent (derived if missing), accentText, primaryDarkened, footerBand, footerText, plus the contrast ratios and a list of adjustments (surfaced as the “Adjusted” badge). Neutral mode derives the palette from the template’s original colors instead.
Production
Rendering should move server-side at publish time (headless browser or an SVG-to-PNG service) so output is identical everywhere. The brand kit should be collected once per client during onboarding/design setup. Logo white/dark variants are generated by recoloring the logo’s non-transparent pixels.
Template JSON (Build Up)
{
"id": "tpl_build_up",
"name": "Build Up",
"category": "Movement and Mobility",
"moods": [
"bold"
],
"backgroundImageUrl": "https://images.unsplash.com/photo-1476480862126-209bfaa8edc8?w=1200&q=80",
"aspectRatios": [
"square",
"portrait",
"story"
],
"originalColors": {
"primary": "#1f5fae",
"accent": "#f2a33a"
},
"layers": [
{
"id": "headline",
"type": "text",
"slot": "headline",
"plate": "white",
"layout": {
"square": {
"x": 18,
"y": 6,
"w": 64,
"h": 14,
"fontSize": 9.5
},
"portrait": {
"x": 18,
"y": 6,
"w": 64,
"h": 14,
"fontSize": 9.5
},
"story": {
"x": 18,
"y": 6,
"w": 64,
"h": 14,
"fontSize": 9.5
}
},
"shape": "pill",
"content": "BUILD UP"
},
{
"id": "circle_0",
"type": "shape",
"slot": "accent_fill",
"plate": "none",
"layout": {
"square": {
"x": 5,
"y": 34,
"w": 21,
"h": 21,
"fontSize": 3.6
},
"portrait": {
"x": 5,
"y": 46.5,
"w": 21,
"h": 21,
"fontSize": 3.6
},
"story": {
"x": 5,
"y": 72.89,
"w": 21,
"h": 21,
"fontSize": 3.6
}
},
"shape": "circle",
"content": "10\nMINUTES"
},
{
"id": "week_0",
"type": "text",
"slot": "subhead",
"plate": "dark",
"layout": {
"square": {
"x": 7,
"y": 58,
"w": 17,
"h": 6,
"fontSize": 3
},
"portrait": {
"x": 7,
"y": 70.5,
"w": 17,
"h": 6,
"fontSize": 3
},
"story": {
"x": 7,
"y": 96.89,
"w": 17,
"h": 6,
"fontSize": 3
}
},
"content": "WEEK 1"
},
{
"id": "circle_1",
"type": "shape",
"slot": "accent_fill",
"plate": "none",
"layout": {
"square": {
"x": 28,
"y": 34,
"w": 21,
"h": 21,
"fontSize": 3.6
},
"portrait": {
"x": 28,
"y": 46.5,
"w": 21,
"h": 21,
"fontSize": 3.6
},
"story": {
"x": 28,
"y": 72.89,
"w": 21,
"h": 21,
"fontSize": 3.6
}
},
"shape": "circle",
"content": "15\nMINUTES"
},
{
"id": "week_1",
"type": "text",
"slot": "subhead",
"plate": "dark",
"layout": {
"square": {
"x": 30,
"y": 58,
"w": 17,
"h": 6,
"fontSize": 3
},
"portrait": {
"x": 30,
"y": 70.5,
"w": 17,
"h": 6,
"fontSize": 3
},
"story": {
"x": 30,
"y": 96.89,
"w": 17,
"h": 6,
"fontSize": 3
}
},
"content": "WEEK 2"
},
{
"id": "circle_2",
"type": "shape",
"slot": "accent_fill",
"plate": "none",
"layout": {
"square": {
"x": 51,
"y": 34,
"w": 21,
"h": 21,
"fontSize": 3.6
},
"portrait": {
"x": 51,
"y": 46.5,
"w": 21,
"h": 21,
"fontSize": 3.6
},
"story": {
"x": 51,
"y": 72.89,
"w": 21,
"h": 21,
"fontSize": 3.6
}
},
"shape": "circle",
"content": "20\nMINUTES"
},
{
"id": "week_2",
"type": "text",
"slot": "subhead",
"plate": "dark",
"layout": {
"square": {
"x": 53,
"y": 58,
"w": 17,
"h": 6,
"fontSize": 3
},
"portrait": {
"x": 53,
"y": 70.5,
"w": 17,
"h": 6,
"fontSize": 3
},
"story": {
"x": 53,
"y": 96.89,
"w": 17,
"h": 6,
"fontSize": 3
}
},
"content": "WEEK 3"
},
{
"id": "circle_3",
"type": "shape",
"slot": "accent_fill",
"plate": "none",
"layout": {
"square": {
"x": 74,
"y": 34,
"w": 21,
"h": 21,
"fontSize": 3.6
},
"portrait": {
"x": 74,
"y": 46.5,
"w": 21,
"h": 21,
"fontSize": 3.6
},
"story": {
"x": 74,
"y": 72.89,
"w": 21,
"h": 21,
"fontSize": 3.6
}
},
"shape": "circle",
"content": "25\nMINUTES"
},
{
"id": "week_3",
"type": "text",
"slot": "subhead",
"plate": "dark",
"layout": {
"square": {
"x": 76,
"y": 58,
"w": 17,
"h": 6,
"fontSize": 3
},
"portrait": {
"x": 76,
"y": 70.5,
"w": 17,
"h": 6,
"fontSize": 3
},
"story": {
"x": 76,
"y": 96.89,
"w": 17,
"h": 6,
"fontSize": 3
}
},
"content": "WEEK 4"
},
{
"id": "band",
"type": "shape",
"slot": "footer_band",
"plate": "none",
"layout": {
"square": {
"x": 0,
"y": 81,
"w": 100,
"h": 19,
"fontSize": 8,
"align": "left"
},
"portrait": {
"x": 0,
"y": 106,
"w": 100,
"h": 19,
"fontSize": 8,
"align": "left"
},
"story": {
"x": 0,
"y": 158.78,
"w": 100,
"h": 19,
"fontSize": 8,
"align": "left"
}
},
"shape": "band",
"content": "GRADUALLY"
}
],
"logoSlots": [
{
"id": "band_right",
"label": "Bottom right (on band)",
"plate": "band",
"layout": {
"square": {
"x": 70,
"y": 84,
"w": 26,
"h": 13
},
"portrait": {
"x": 70,
"y": 109,
"w": 26,
"h": 13
},
"story": {
"x": 70,
"y": 161.78,
"w": 26,
"h": 13
}
}
},
{
"id": "top_right",
"label": "Top right (white plate)",
"plate": "white",
"layout": {
"square": {
"x": 80,
"y": 23,
"w": 16,
"h": 8
},
"portrait": {
"x": 80,
"y": 23,
"w": 16,
"h": 8
},
"story": {
"x": 80,
"y": 23,
"w": 16,
"h": 8
}
}
}
]
}