Skip to content
Design system
v1.0
Tier 4, Client production chassis

Client social chassis

Confirmed

The social templates with Marathon pulled out and a client brand dropped in.

Champion r02Confirmed in r02Repo folder build/chassis-social/v/best/
Client social chassis, primary render

Primary render. Select it to open full size.

Renders

Every render from the champion round

Showing 40 of 129 renders. The rest are in build/chassis-social/v/best/renders/.

Rules

How to use it, from the piece README

Use this for client posts. It is the same post and carousel mechanics as Marathon's own social kit, with Marathon taken out and the client's brand dropped in through one theme file. Marathon does not appear on the post. The copy is written in the client's voice, not ours.

What is in here

FileWhat it isEdit it?
post.htmlRenders one post. Pick the client and post in the address bar.No
chassis.cssThe layout. Sizes and safe areas come from the Marathon token layer. No colours, no fonts.No
chassis.jsBuilds the post, detects photo orientation, fits the type, and runs the guards.No
clients/<name>/theme.cssThe client's colours and typefaces. The only place either is allowed.Yes, once per client
clients/<name>/client.jsThe client's name, wordmark, and post copy.Yes, every post
clients/<name>/fonts/The client's font files, copied in. Never hotlinked.Once per client
photos/Copies of the photos from brief/real-photos/.Add the client's photos here
icons-inline.jsThree icons from the Marathon icon set. Rebuild with python3 build_icons.py.No
jobs.py, render-all.mjs, board.htmlRender every post and the review boards.No

Shipped themes: cangelosi (stone showroom), rosehill-reserve (master-planned community), halvorsen-roofing (the Houston roofer example).

Templates

post=TemplateSize
squarePhoto post1080 x 1080
portraitPhoto post with a supporting line1080 x 1350
storyStory, text clear of the reply bar1080 x 1920
carousel-1Carousel cover, photo1080 x 1350
carousel-2, carousel-3Numbered point1080 x 1350
carousel-4Stat1080 x 1350
carousel-5End card with call to action1080 x 1350

Make a post

  1. Open clients/<name>/client.js and write the copy in the client's words. A roofer says "Call or text," not "Let's connect." Read three of the client's own posts first.
  2. Put the photo in photos/ and set photo: to its file name without .jpg.
  3. Open post.html?client=<name>&post=square in Chrome. You will see the post at exact size.
  4. If a red bar appears across the top, the copy is too long. Do what it says. Do not export a post with the red bar showing.
  5. Render: node build/_shared/render.mjs png 'build/chassis-social/v/best/post.html?client=<name>&post=square' out.png 1080 1080 0. Use 1080 1350 for portrait and carousel, 1080 1920 for story.

Copy lengths

Aim for these. The template holds up to about twice this, then the red bar stops you.

LineAim forHard stop
Eyebrow3 to 5 wordsone line
Headline60 charactersabout 120, 4 lines
Supporting line (portrait, story)90 characters3 lines
Factsthree pairs, label plus valuethree, 2 lines each
Carousel body2 short sentencesfills the slide
Client name (wordmark)the name the client signs withtwo lines at 65% size
Any single wordstreet names, web addressesmust fit the box at 60% of the headline size
Stata number and a short unit, like 15 min or 1 in.one line

Facts are label and value pairs, so they work for any listing: acreage and frontage for land, square feet for a showroom, anything for a service business. There is no fixed beds and baths row.

What the template does for you

  • Type never sits on the photo. Every word is on a solid plate in the client's colour. A busy aerial, a bright marble wall, or a red boundary line drawn on a lot cannot hurt legibility, and the client's accent never lands on top of a burned-in red line.
  • The photo split is fixed. You cannot shrink the photo to fit more words.
  • Portrait photos are detected. In a square post a portrait photo switches to a side by side split instead of a hard crop. In 4:5 and story the photo window gets taller.
  • Type fits itself to the client's typeface. A wide caps face like Cinzel and a condensed face like Oswald both fill the same plate. The headline gets priority, the supporting line and facts step down first.
  • Long client names fit or get flagged. The wordmark sets on one line, steps down to 75%, then breaks onto two balanced lines and steps down to 65%. Past that it is outlined in a red dashed box and the red bar names it. Fix it with the client's logo file or a shorter wordmark: in client.js. A long name never pushes the call to action off the plate.
  • Words never split mid-word. A long street name or web address sets the headline size, down to 60% of normal. If one word is still wider than the box, it is outlined and the red bar names the word. Abbreviate it (Pkwy, Blvd, Dr), hyphenate it, or move a web address to the caption.
  • Weak accents are caught. If a client accent is too light to read on the plate, it falls back to the text colour. If the theme's text colour fails contrast, the red bar says so.

To test a longer client name before a client signs off on it, add &wordmark=Full Legal Name to the address.

To recrop a photo, add &focus=50% 30% (left to right, top to bottom) or set focus: in the post.

Add a new client

  1. Copy clients/cangelosi/ to clients/<new-name>/.
  2. In theme.css, set every --cl- value from the client's brand file. Put the font files in fonts/ with their licence. If a value is missing, the chassis falls back to neutral grey and the post still works.
  3. plate: in client.js picks the panel colour: ink for the client's dark colour, paper for light.
  4. If the client has a logo SVG, put it in the folder and set logo: 'clients/<new-name>/logo.svg'. Until then the wordmark is set in the client's display face.
  5. Run python3 jobs.py and node render-all.mjs after adding the client to CLIENTS in jobs.py, and look at every board in renders/ before anything goes to the client.

Rules

  • Client hexes and client fonts go in clients/<name>/theme.css only. Never in chassis.css.
  • No Marathon logo or credit on client posts.
  • No em dashes in client copy either. Commas instead.

For Jacob to confirm

  • Client palettes for Cangelosi and Rosehill Reserve are sampled from the shoot photos, not from their brand files. Swap in the real values when the clients send them.
  • Client typefaces (Cinzel and Lato, Libre Baskerville and Nunito Sans, Oswald and Barlow) are open-licence stand-ins chosen to match each client's signage. Confirm with each client.
  • All client copy is drafted. Claims like appointment-only visits, a 48 hour response time, retail at the Rosehill entrance, and the roofing service areas need the client's sign-off.
  • Halvorsen Roofing is an invented example. Its posts use folder aerials as stand-ins because the photo set has no roofing jobs. Use the roofer's own job photos for real posts.

Renders

renders/primary.png is the contact sheet. photo-interior.png, photo-aerial.png, photo-portrait.png, and photo-boundary.png show every photo-bearing template in every theme on mc_retouched_05, mc_rosehill_16, mc_retouched_08, and mc_fullres_12. stress-long-copy.png is every template at twice the copy. guard-overlimit.png shows the red bar. guard-long-names.png shows long client names and long single words in every theme, first fitting, then past the limit and flagged.

Use it

Files to download and code to copy

  • Whole piece, source files50 files, zip, 18.9 MB. Paths inside follow the repo layout, so put tokens.css beside them as the README says.
    Download

Copy-paste code

chassis.css169 lines, 13.2 KB
/* Marathon client chassis, social and carousel. v1.0.0
   The layout lives here. The client brand lives in clients/<name>/theme.css and nowhere else.
   This file never names a colour or a typeface. It reads --cl-* slots from the theme, and
   structural values (sizes, safe areas, spacing) from the Marathon token layer.
   If a theme leaves a slot out, the default below keeps the post neutral and readable. */

:root {
  --cl-font-display: var(--mc-font);
  --cl-font-text: var(--mc-font);
  --cl-display-weight: var(--mc-weight-bold);
  --cl-display-transform: none;
  --cl-display-tracking: -0.01em;
  --cl-display-leading: 1.1;
  --cl-display-scale: 1;
  --cl-text-weight: var(--mc-weight-regular);
  --cl-strong-weight: var(--mc-weight-bold);
  --cl-label-weight: var(--mc-weight-bold);
  --cl-label-tracking: 0.2em;
  --cl-wordmark-tracking: 0.04em;
  --cl-wordmark-transform: none;
  --cl-ink: var(--mc-charcoal);
  --cl-on-ink: var(--mc-white);
  --cl-on-ink-muted: var(--mc-gray-300);
  --cl-paper: var(--mc-white);
  --cl-on-paper: var(--mc-charcoal);
  --cl-on-paper-muted: var(--mc-slate-500);
  --cl-accent: var(--mc-slate-700);
  --cl-accent-on-ink: var(--mc-gray-300);
  --cl-on-accent: var(--mc-white);
  --cl-rule-on-paper: var(--mc-gray-200);
  --cl-rule-on-ink: var(--mc-slate-700);
  --cl-radius: var(--mc-radius-none);
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { background: var(--mc-white); }
body { width: max-content; }

/* Surfaces. Every text colour is chosen by the surface it sits on, never by the author. */
[data-surface="ink"] { --s-bg: var(--cl-ink); --s-fg: var(--cl-on-ink); --s-muted: var(--cl-on-ink-muted); --s-accent: var(--cl-accent-on-ink); --s-rule: var(--cl-rule-on-ink); --s-btn-fg: var(--cl-ink); }
[data-surface="paper"] { --s-bg: var(--cl-paper); --s-fg: var(--cl-on-paper); --s-muted: var(--cl-on-paper-muted); --s-accent: var(--cl-accent); --s-rule: var(--cl-rule-on-paper); --s-btn-fg: var(--cl-on-accent); }

/* Canvas, exact platform sizes from the token layer. */
.cs-canvas {
  position: relative; overflow: hidden; display: grid; grid-template-columns: minmax(0, 1fr);
  font-family: var(--cl-font-text); font-weight: var(--cl-text-weight);
  background: var(--s-bg); color: var(--s-fg);
  font-kerning: normal; text-rendering: geometricPrecision; -webkit-font-smoothing: antialiased;
}
.cs-canvas[data-format="square"] { width: var(--mc-social-square-w); height: var(--mc-social-square-h); --pad-t: var(--mc-social-square-safe-top); --pad-b: var(--mc-social-square-safe-bottom); --pad-x: var(--mc-social-square-safe-side); }
.cs-canvas[data-format="portrait"] { width: var(--mc-social-portrait-w); height: var(--mc-social-portrait-h); --pad-t: var(--mc-social-portrait-safe-top); --pad-b: var(--mc-social-portrait-safe-bottom); --pad-x: var(--mc-social-portrait-safe-side); }
.cs-canvas[data-format="story"] { width: var(--mc-social-story-w); height: var(--mc-social-story-h); --pad-t: var(--mc-social-story-safe-top); --pad-b: var(--mc-social-story-safe-bottom); --pad-x: var(--mc-social-story-safe-side); }

/* Photo window. The photo is the hero and nothing is ever typeset on top of it.
   The split between photo and plate is fixed per format and per photo orientation, so a
   non-designer cannot push type onto the image or shrink the image to fit more words. */
.cs-canvas[data-tpl="photo"], .cs-canvas[data-tpl="cover"] { grid-template-rows: var(--photo-h) minmax(0, 1fr); }
.cs-canvas[data-format="square"][data-orient="landscape"] { --photo-h: 560px; }
.cs-canvas[data-format="portrait"][data-orient="landscape"] { --photo-h: 640px; }
.cs-canvas[data-format="portrait"][data-orient="portrait"] { --photo-h: 680px; }
.cs-canvas[data-format="story"][data-orient="landscape"] { --photo-h: 1060px; }
.cs-canvas[data-format="story"][data-orient="portrait"] { --photo-h: 1120px; }
/* A portrait photo in a square post becomes a side by side split instead of a hard crop. */
.cs-canvas[data-format="square"][data-orient="portrait"] { grid-template-rows: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }

.cs-photo { position: relative; overflow: hidden; min-height: 0; background: var(--s-bg); }
.cs-photo img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* Plate, the solid client-colour panel that carries every word. */
.cs-plate {
  position: relative; display: flex; flex-direction: column; min-height: 0; min-width: 0;
  padding: var(--mc-space-11) var(--pad-x) var(--pad-b);
  background: var(--s-bg); color: var(--s-fg);
}
.cs-canvas[data-format="square"][data-orient="landscape"] .cs-plate { padding-top: var(--mc-space-10); padding-bottom: var(--mc-space-11); }
.cs-canvas[data-format="square"][data-orient="portrait"] .cs-plate { padding: var(--pad-t) var(--mc-space-11) var(--pad-b); }

.cs-bar { flex: none; width: var(--mc-space-11); height: var(--mc-space-02); background: var(--s-accent); margin-bottom: var(--mc-space-07); border-radius: var(--cl-radius); }

.cs-eyebrow {
  flex: none; font-family: var(--cl-font-text); font-weight: var(--cl-label-weight);
  font-size: 22px; line-height: 1.2; letter-spacing: var(--cl-label-tracking); text-transform: uppercase;
  color: var(--s-accent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  margin-bottom: var(--mc-space-05);
}

.cs-box { flex: 1 1 auto; min-height: 0; overflow: hidden; display: flex; flex-direction: column; justify-content: flex-start; }
.cs-head {
  font-family: var(--cl-font-display); font-weight: var(--cl-display-weight);
  text-transform: var(--cl-display-transform); letter-spacing: var(--cl-display-tracking);
  line-height: var(--cl-display-leading); color: var(--s-fg);
  overflow-wrap: normal; hyphens: manual; text-wrap: balance;
}
.cs-sub {
  flex: none; font-size: 30px; line-height: 1.38; color: var(--s-muted);
  margin-top: var(--mc-space-06); max-width: 34em; text-wrap: pretty;
}

/* Fact row. Label and value pairs, any listing type. Never a fixed beds and baths row. */
.cs-facts { flex: none; list-style: none; display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: var(--mc-space-07); margin-top: var(--mc-space-07); padding-top: var(--mc-space-06); border-top: var(--mc-border-width-hairline) solid var(--s-rule); }
.cs-facts li { min-width: 0; }
.cs-facts .k { display: block; font-weight: var(--cl-label-weight); font-size: 16px; letter-spacing: var(--cl-label-tracking); text-transform: uppercase; color: var(--s-muted); margin-bottom: var(--mc-space-03); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cs-facts .v { display: block; font-weight: var(--cl-strong-weight); font-size: 26px; line-height: 1.22; color: var(--s-fg); font-variant-numeric: tabular-nums; }
.cs-canvas[data-format="square"][data-orient="portrait"] .cs-facts { grid-auto-flow: row; grid-auto-columns: auto; gap: var(--mc-space-05); }

/* Sign-off row: client wordmark left, call to action right. */
.cs-sign { flex: none; display: flex; align-items: center; justify-content: space-between; gap: var(--mc-space-07); margin-top: var(--mc-space-07); min-width: 0; }
.cs-wordmark { font-family: var(--cl-font-display); font-weight: var(--cl-display-weight); font-size: 30px; line-height: 1; letter-spacing: var(--cl-wordmark-tracking); text-transform: var(--cl-wordmark-transform); color: var(--s-fg); white-space: nowrap; flex: 0 1 auto; min-width: 0; max-width: 100%; overflow: hidden; }
.cs-wordmark img { display: block; height: 44px; width: auto; max-width: 100%; object-fit: contain; object-position: left center; }
/* Each sign-off row gives the wordmark a fixed share, so a long name cannot push the call to action off the plate. */
.cs-sign .cs-wordmark { max-width: 62%; }
.cs-progress .cs-wordmark { max-width: 56%; }
.cs-top .cs-wordmark { max-width: 78%; }
.cs-end-sign .cs-wordmark { max-width: 64%; }
.cs-canvas[data-format="square"][data-orient="portrait"] .cs-sign .cs-wordmark { max-width: 100%; }
.cs-wordmark.cs-wm-wrap { white-space: normal; line-height: 1.06; text-wrap: balance; overflow-wrap: normal; }
.cs-sign .cs-cta { flex: 0 1 auto; }
.cs-cta { display: flex; align-items: center; gap: var(--mc-space-04); min-width: 0; font-weight: var(--cl-strong-weight); font-size: 24px; color: var(--s-fg); white-space: nowrap; overflow: hidden; }
.cs-cta span { overflow: hidden; text-overflow: ellipsis; }
.cs-icon { width: 28px; height: 28px; flex: none; color: var(--s-accent); stroke-width: var(--mc-icon-stroke-24, 2); }
.cs-canvas[data-format="square"][data-orient="portrait"] .cs-sign { flex-direction: column; align-items: flex-start; gap: var(--mc-space-05); }

/* Carousel chrome: count, progress, and swipe cue. */
.cs-top { flex: none; display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--mc-space-11); }
.cs-top .cs-wordmark { font-size: 24px; }
.cs-count { font-weight: var(--cl-strong-weight); font-size: 22px; color: var(--s-muted); font-variant-numeric: tabular-nums; letter-spacing: 0.06em; }
.cs-progress { flex: none; display: flex; align-items: center; gap: var(--mc-space-03); margin-top: var(--mc-space-09); }
.cs-progress i { display: block; height: var(--mc-space-02); flex: 1; background: var(--s-rule); border-radius: var(--cl-radius); }
.cs-progress i.on { background: var(--s-accent); }
.cs-progress .cs-cta { flex: none; margin-left: var(--mc-space-06); }

/* Full-surface slides (point, stat, end). */
.cs-canvas[data-tpl="point"], .cs-canvas[data-tpl="stat"], .cs-canvas[data-tpl="end"] { grid-template-rows: minmax(0, 1fr); }
.cs-slide { display: flex; flex-direction: column; min-height: 0; min-width: 0; padding: var(--pad-t) var(--pad-x) var(--pad-b); }
.cs-num { flex: none; margin-top: auto; font-family: var(--cl-font-display); font-weight: var(--cl-display-weight); font-size: calc(200px * var(--cl-display-scale)); line-height: 0.9; color: var(--s-accent); font-variant-numeric: tabular-nums; margin-bottom: var(--mc-space-08); }
.cs-headbox { flex: none; max-height: 420px; overflow: hidden; }
.cs-body { font-size: 36px; line-height: 1.42; color: var(--s-fg); text-wrap: pretty; }
.cs-bodybox { flex: 0 1 auto; min-height: 0; overflow: hidden; margin-top: var(--mc-space-08); padding-top: var(--mc-space-08); border-top: var(--mc-border-width-hairline) solid var(--s-rule); }

.cs-statbox { flex: none; overflow: hidden; margin-top: auto; }
.cs-canvas[data-tpl="stat"] .cs-box { flex: 0 1 auto; }
.cs-stat { font-family: var(--cl-font-display); font-weight: var(--cl-display-weight); line-height: 1; letter-spacing: -0.02em; color: var(--s-accent); white-space: nowrap; font-variant-numeric: tabular-nums; text-transform: var(--cl-display-transform); }
.cs-stat-label { font-family: var(--cl-font-text); font-weight: var(--cl-strong-weight); line-height: 1.3; color: var(--s-fg); text-wrap: pretty; }
.cs-source { flex: none; font-size: 26px; line-height: 1.4; color: var(--s-muted); margin-top: var(--mc-space-07); }
.cs-canvas[data-tpl="stat"] .cs-bar { margin-top: var(--mc-space-08); margin-bottom: var(--mc-space-08); }

.cs-lines { flex: none; list-style: none; margin-top: var(--mc-space-09); display: grid; gap: var(--mc-space-06); }
.cs-lines li { display: flex; gap: var(--mc-space-05); align-items: flex-start; font-size: 30px; line-height: 1.3; color: var(--s-fg); }
.cs-lines .cs-icon { margin-top: var(--mc-space-01); }
.cs-button { flex: none; align-self: flex-start; max-width: 100%; display: inline-flex; align-items: center; gap: var(--mc-space-04); margin-top: var(--mc-space-10); padding: var(--mc-space-06) var(--mc-space-08); background: var(--s-accent); color: var(--s-btn-fg); border-radius: var(--cl-radius); font-weight: var(--cl-strong-weight); font-size: 30px; white-space: nowrap; overflow: hidden; }
.cs-button span { overflow: hidden; text-overflow: ellipsis; }
.cs-button .cs-icon { color: var(--s-btn-fg); }
.cs-canvas[data-tpl="end"] .cs-bar { margin-top: auto; }
.cs-end-sign { flex: none; margin-top: var(--mc-space-12); padding-top: var(--mc-space-08); border-top: var(--mc-border-width-hairline) solid var(--s-rule); display: flex; justify-content: space-between; align-items: flex-end; gap: var(--mc-space-07); }
.cs-end-sign .cs-wordmark { font-size: 40px; }
.cs-descriptor { font-size: 22px; color: var(--s-muted); text-align: right; }

/* Last-resort guard. If copy is still too long at the smallest allowed size, the post says so in
   plain words, visibly, instead of quietly shrinking type to nothing. It is meant to be seen in
   preview and fixed before export. */
.cs-word-over { overflow-wrap: anywhere; }
.cs-word-over, .cs-over-wm { outline: var(--mc-space-02) dashed var(--mc-status-error); outline-offset: var(--mc-space-03); }
.cs-over { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: var(--clamp, 4); overflow: hidden; }
.cs-flag {
  position: absolute; left: 0; right: 0; top: 0; z-index: var(--mc-z-toast);
  padding: var(--mc-space-05) var(--mc-space-08); background: var(--mc-status-error); color: var(--mc-text-inverse);
  font-family: var(--mc-font); font-weight: var(--mc-weight-bold); font-size: 22px; line-height: 1.3;
}
clients/cangelosi/theme.css35 lines, 1.4 KB
/* Cangelosi client theme. Client palette and client typefaces live here and nowhere else.
   Palette sampled from the Cangelosi shoot: black granite floor, white marble wall, rose granite
   monument sign. Swap for the client's brand file values when Cangelosi supplies them. */
@font-face { font-family: "Cinzel"; src: url("fonts/Cinzel-Variable.ttf") format("truetype"); font-weight: 400 900; font-display: block; }
@font-face { font-family: "Lato"; src: url("fonts/Lato-Regular.ttf") format("truetype"); font-weight: 400; font-display: block; }
@font-face { font-family: "Lato"; src: url("fonts/Lato-Bold.ttf") format("truetype"); font-weight: 700; font-display: block; }
:root {
  --cl-font-display: "Cinzel", serif;
  --cl-font-text: "Lato", sans-serif;
  --cl-display-weight: 600;
  --cl-display-transform: none;
  --cl-display-tracking: 0.01em;
  --cl-display-leading: 1.12;
  --cl-display-scale: 0.86;
  --cl-text-weight: 400;
  --cl-strong-weight: 700;
  --cl-label-weight: 700;
  --cl-label-tracking: 0.24em;
  --cl-wordmark-tracking: 0.16em;
  --cl-wordmark-transform: uppercase;

  --cl-ink: #1c1a18;
  --cl-on-ink: #f5f2ee;
  --cl-on-ink-muted: #b8aea4;
  --cl-paper: #f4f1ec;
  --cl-on-paper: #1c1a18;
  --cl-on-paper-muted: #675f57;
  --cl-accent: #8a6656;
  --cl-accent-on-ink: #c9a796;
  --cl-on-accent: #f5f2ee;
  --cl-rule-on-paper: #d9d2c9;
  --cl-rule-on-ink: #3b3733;
  --cl-radius: 0px;
}
clients/halvorsen-roofing/theme.css35 lines, 1.5 KB
/* Halvorsen Roofing client theme, the Houston roofer example. Client palette and client
   typefaces live here and nowhere else. Charcoal, white, and job-site safety orange. */
@font-face { font-family: "Oswald"; src: url("fonts/Oswald-Variable.ttf") format("truetype"); font-weight: 200 700; font-display: block; }
@font-face { font-family: "Barlow"; src: url("fonts/Barlow-Regular.ttf") format("truetype"); font-weight: 400; font-display: block; }
@font-face { font-family: "Barlow"; src: url("fonts/Barlow-SemiBold.ttf") format("truetype"); font-weight: 600; font-display: block; }
@font-face { font-family: "Barlow"; src: url("fonts/Barlow-Bold.ttf") format("truetype"); font-weight: 700; font-display: block; }
:root {
  --cl-font-display: "Oswald", sans-serif;
  --cl-font-text: "Barlow", sans-serif;
  --cl-display-weight: 700;
  --cl-display-transform: uppercase;
  --cl-display-tracking: 0.005em;
  --cl-display-leading: 1.04;
  --cl-display-scale: 1.12;
  --cl-text-weight: 400;
  --cl-strong-weight: 700;
  --cl-label-weight: 700;
  --cl-label-tracking: 0.16em;
  --cl-wordmark-tracking: 0.04em;
  --cl-wordmark-transform: uppercase;

  --cl-ink: #1e2226;
  --cl-on-ink: #ffffff;
  --cl-on-ink-muted: #aab1b8;
  --cl-paper: #ffffff;
  --cl-on-paper: #1e2226;
  --cl-on-paper-muted: #5a636c;
  --cl-accent: #c2500f;
  --cl-accent-on-ink: #f26a1b;
  --cl-on-accent: #1e2226;
  --cl-rule-on-paper: #dde1e5;
  --cl-rule-on-ink: #3a4047;
  --cl-radius: 2px;
}
clients/rosehill-reserve/theme.css34 lines, 1.3 KB
/* Rosehill Reserve client theme. Client palette and client typefaces live here and nowhere else.
   Palette taken from the entry monument and the community: evergreen landscaping, limestone
   monument, rose accent from the community name. Replace with the developer's brand values. */
@font-face { font-family: "Libre Baskerville"; src: url("fonts/LibreBaskerville-Variable.ttf") format("truetype"); font-weight: 400 700; font-display: block; }
@font-face { font-family: "Nunito Sans"; src: url("fonts/NunitoSans-Variable.ttf") format("truetype"); font-weight: 200 1000; font-display: block; }
:root {
  --cl-font-display: "Libre Baskerville", serif;
  --cl-font-text: "Nunito Sans", sans-serif;
  --cl-display-weight: 400;
  --cl-display-transform: none;
  --cl-display-tracking: -0.01em;
  --cl-display-leading: 1.16;
  --cl-display-scale: 0.92;
  --cl-text-weight: 400;
  --cl-strong-weight: 700;
  --cl-label-weight: 800;
  --cl-label-tracking: 0.2em;
  --cl-wordmark-tracking: 0.02em;
  --cl-wordmark-transform: none;

  --cl-ink: #1f3a33;
  --cl-on-ink: #f7f4ee;
  --cl-on-ink-muted: #b9c7c0;
  --cl-paper: #f7f4ee;
  --cl-on-paper: #1f3a33;
  --cl-on-paper-muted: #5b6b65;
  --cl-accent: #a84756;
  --cl-accent-on-ink: #eaa3ad;
  --cl-on-accent: #ffffff;
  --cl-rule-on-paper: #dcd6ca;
  --cl-rule-on-ink: #34524a;
  --cl-radius: 6px;
}
board.html51 lines, 3.6 KB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Client chassis board</title>
<link rel="stylesheet" href="../../../tokens/v/best/tokens.css">
<style>
  /* Marathon's internal review sheet. The posts inside it are client work, the frame is Marathon. */
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body { width: 2400px; background: var(--mc-surface-subtle); font-family: var(--mc-font); color: var(--mc-text-primary); padding: var(--mc-space-12); }
  header { display: flex; justify-content: space-between; align-items: flex-end; padding-bottom: var(--mc-space-08); margin-bottom: var(--mc-space-10); border-bottom: var(--mc-border-width-hairline) solid var(--mc-border-strong); }
  .eyebrow { font-size: 18px; font-weight: var(--mc-weight-bold); letter-spacing: 0.2em; text-transform: uppercase; color: var(--mc-text-label); margin-bottom: var(--mc-space-04); }
  .rule { width: 60px; height: var(--mc-space-02); background: var(--mc-accent); margin-bottom: var(--mc-space-06); }
  h1 { font-size: 56px; font-weight: var(--mc-weight-extrabold); letter-spacing: -0.02em; color: var(--mc-text-heading); line-height: 1.05; }
  .sub { font-size: 24px; font-weight: var(--mc-weight-light); color: var(--mc-text-secondary); margin-top: var(--mc-space-04); max-width: 1500px; line-height: 1.4; }
  header img { height: 40px; }
  section { margin-bottom: var(--mc-space-11); }
  h2 { font-size: 30px; font-weight: var(--mc-weight-bold); color: var(--mc-text-heading); margin-bottom: var(--mc-space-06); }
  .row { display: flex; gap: var(--mc-space-07); align-items: flex-end; flex-wrap: wrap; }
  figure img { display: block; height: var(--row-h); width: auto; box-shadow: var(--mc-shadow-1); background: var(--mc-white); }
  figcaption { font-size: 16px; font-weight: var(--mc-weight-semibold); color: var(--mc-text-tertiary); margin-top: var(--mc-space-03); }
</style>
</head>
<body>
<script src="boards.js"></script>
<script>
  (function () {
    var key = new URLSearchParams(location.search).get('board') || 'primary', B = window.CS_BOARDS[key];
    var hd = document.createElement('header');
    hd.innerHTML = '<div><div class="rule"></div><div class="eyebrow">Marathon client chassis, social</div><h1></h1><p class="sub"></p></div><img src="../../../tokens/v/best/logos/marathon-logo-dark.svg" alt="Marathon Creative">';
    hd.querySelector('h1').textContent = B.title; hd.querySelector('.sub').textContent = B.sub; document.body.appendChild(hd);
    var pending = 0;
    B.groups.forEach(function (g) {
      var s = document.createElement('section'), h = document.createElement('h2'); h.textContent = g.name; s.appendChild(h);
      var r = document.createElement('div'); r.className = 'row';
      /* Row height so the row fills the sheet width: 2240px of content minus the gaps. */
      var ratio = g.items.reduce(function (a, it) { return a + it.w / it.h; }, 0);
      r.style.setProperty('--row-h', Math.min(1000, Math.floor((2240 - 24 * (g.items.length - 1)) / ratio)) + 'px');
      g.items.forEach(function (it) {
        var f = document.createElement('figure'), i = document.createElement('img'), c = document.createElement('figcaption');
        pending++; i.onload = i.onerror = function () { if (--pending === 0) document.body.dataset.ready = '1'; };
        i.src = it.src + '?v=' + Date.now(); c.textContent = it.label; f.appendChild(i); f.appendChild(c); r.appendChild(f);
      });
      s.appendChild(r); document.body.appendChild(s);
    });
    if (!pending) document.body.dataset.ready = '1';
  })();
</script>
</body>
</html>
post.html24 lines, 854 bytes
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Client chassis post</title>
<link rel="stylesheet" href="../../../tokens/v/best/tokens.css">
<link rel="stylesheet" href="../../../icons/v/best/icons.css">
<link rel="stylesheet" href="chassis.css">
<script>
  /* Pick the client from ?client=<slug>. The theme loads after chassis.css so it wins. */
  (function () {
    var slug = (new URLSearchParams(location.search).get('client') || 'cangelosi').replace(/[^a-z0-9-]/g, '');
    document.write('<link rel="stylesheet" href="clients/' + slug + '/theme.css">');
    document.write('<script src="clients/' + slug + '/client.js"><\/script>');
  })();
</script>
<script src="icons-inline.js"></script>
</head>
<body>
<script src="chassis.js"></script>
</body>
</html>
boards.js939 lines, 22.9 KB
/* Generated by jobs.py. */
window.CS_BOARDS = {
 "primary": {
  "title": "Client chassis, social and carousel",
  "sub": "Every template in every client theme, at exact size, normal copy. Same layout file, three theme files.",
  "groups": [
   {
    "name": "Cangelosi, posts",
    "items": [
     {
      "src": "renders/cangelosi/square.png",
      "label": "Feed post 1080 x 1080",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/cangelosi/portrait.png",
      "label": "Feed post 1080 x 1350",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/cangelosi/story.png",
      "label": "Story 1080 x 1920",
      "w": 1080,
      "h": 1920
     }
    ]
   },
   {
    "name": "Cangelosi, carousel",
    "items": [
     {
      "src": "renders/cangelosi/carousel-1.png",
      "label": "Carousel cover",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/cangelosi/carousel-2.png",
      "label": "Carousel point",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/cangelosi/carousel-3.png",
      "label": "Carousel point",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/cangelosi/carousel-4.png",
      "label": "Carousel stat",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/cangelosi/carousel-5.png",
      "label": "Carousel end card",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Rosehill Reserve, posts",
    "items": [
     {
      "src": "renders/rosehill-reserve/square.png",
      "label": "Feed post 1080 x 1080",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/rosehill-reserve/portrait.png",
      "label": "Feed post 1080 x 1350",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/rosehill-reserve/story.png",
      "label": "Story 1080 x 1920",
      "w": 1080,
      "h": 1920
     }
    ]
   },
   {
    "name": "Rosehill Reserve, carousel",
    "items": [
     {
      "src": "renders/rosehill-reserve/carousel-1.png",
      "label": "Carousel cover",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/rosehill-reserve/carousel-2.png",
      "label": "Carousel point",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/rosehill-reserve/carousel-3.png",
      "label": "Carousel point",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/rosehill-reserve/carousel-4.png",
      "label": "Carousel stat",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/rosehill-reserve/carousel-5.png",
      "label": "Carousel end card",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Halvorsen Roofing, posts",
    "items": [
     {
      "src": "renders/halvorsen-roofing/square.png",
      "label": "Feed post 1080 x 1080",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/halvorsen-roofing/portrait.png",
      "label": "Feed post 1080 x 1350",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/halvorsen-roofing/story.png",
      "label": "Story 1080 x 1920",
      "w": 1080,
      "h": 1920
     }
    ]
   },
   {
    "name": "Halvorsen Roofing, carousel",
    "items": [
     {
      "src": "renders/halvorsen-roofing/carousel-1.png",
      "label": "Carousel cover",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/halvorsen-roofing/carousel-2.png",
      "label": "Carousel point",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/halvorsen-roofing/carousel-3.png",
      "label": "Carousel point",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/halvorsen-roofing/carousel-4.png",
      "label": "Carousel stat",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/halvorsen-roofing/carousel-5.png",
      "label": "Carousel end card",
      "w": 1080,
      "h": 1350
     }
    ]
   }
  ]
 },
 "photo-interior": {
  "title": "Photo robustness: interior",
  "sub": "Tight interior, Cangelosi showroom, mc_retouched_05. Every photo-bearing template, every client theme, same photo, no manual adjustment.",
  "groups": [
   {
    "name": "Cangelosi",
    "items": [
     {
      "src": "renders/robust/interior/cangelosi-square.png",
      "label": "Feed post 1080 x 1080",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/robust/interior/cangelosi-portrait.png",
      "label": "Feed post 1080 x 1350",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/robust/interior/cangelosi-story.png",
      "label": "Story 1080 x 1920",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/robust/interior/cangelosi-carousel-1.png",
      "label": "Carousel cover",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Rosehill Reserve",
    "items": [
     {
      "src": "renders/robust/interior/rosehill-reserve-square.png",
      "label": "Feed post 1080 x 1080",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/robust/interior/rosehill-reserve-portrait.png",
      "label": "Feed post 1080 x 1350",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/robust/interior/rosehill-reserve-story.png",
      "label": "Story 1080 x 1920",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/robust/interior/rosehill-reserve-carousel-1.png",
      "label": "Carousel cover",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Halvorsen Roofing",
    "items": [
     {
      "src": "renders/robust/interior/halvorsen-roofing-square.png",
      "label": "Feed post 1080 x 1080",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/robust/interior/halvorsen-roofing-portrait.png",
      "label": "Feed post 1080 x 1350",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/robust/interior/halvorsen-roofing-story.png",
      "label": "Story 1080 x 1920",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/robust/interior/halvorsen-roofing-carousel-1.png",
      "label": "Carousel cover",
      "w": 1080,
      "h": 1350
     }
    ]
   }
  ]
 },
 "photo-aerial": {
  "title": "Photo robustness: aerial",
  "sub": "Wide aerial, busiest rooftops, mc_rosehill_16. Every photo-bearing template, every client theme, same photo, no manual adjustment.",
  "groups": [
   {
    "name": "Cangelosi",
    "items": [
     {
      "src": "renders/robust/aerial/cangelosi-square.png",
      "label": "Feed post 1080 x 1080",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/robust/aerial/cangelosi-portrait.png",
      "label": "Feed post 1080 x 1350",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/robust/aerial/cangelosi-story.png",
      "label": "Story 1080 x 1920",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/robust/aerial/cangelosi-carousel-1.png",
      "label": "Carousel cover",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Rosehill Reserve",
    "items": [
     {
      "src": "renders/robust/aerial/rosehill-reserve-square.png",
      "label": "Feed post 1080 x 1080",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/robust/aerial/rosehill-reserve-portrait.png",
      "label": "Feed post 1080 x 1350",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/robust/aerial/rosehill-reserve-story.png",
      "label": "Story 1080 x 1920",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/robust/aerial/rosehill-reserve-carousel-1.png",
      "label": "Carousel cover",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Halvorsen Roofing",
    "items": [
     {
      "src": "renders/robust/aerial/halvorsen-roofing-square.png",
      "label": "Feed post 1080 x 1080",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/robust/aerial/halvorsen-roofing-portrait.png",
      "label": "Feed post 1080 x 1350",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/robust/aerial/halvorsen-roofing-story.png",
      "label": "Story 1080 x 1920",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/robust/aerial/halvorsen-roofing-carousel-1.png",
      "label": "Carousel cover",
      "w": 1080,
      "h": 1350
     }
    ]
   }
  ]
 },
 "photo-portrait": {
  "title": "Photo robustness: portrait",
  "sub": "Portrait original, slab rack, mc_retouched_08. Every photo-bearing template, every client theme, same photo, no manual adjustment.",
  "groups": [
   {
    "name": "Cangelosi",
    "items": [
     {
      "src": "renders/robust/portrait/cangelosi-square.png",
      "label": "Feed post 1080 x 1080",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/robust/portrait/cangelosi-portrait.png",
      "label": "Feed post 1080 x 1350",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/robust/portrait/cangelosi-story.png",
      "label": "Story 1080 x 1920",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/robust/portrait/cangelosi-carousel-1.png",
      "label": "Carousel cover",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Rosehill Reserve",
    "items": [
     {
      "src": "renders/robust/portrait/rosehill-reserve-square.png",
      "label": "Feed post 1080 x 1080",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/robust/portrait/rosehill-reserve-portrait.png",
      "label": "Feed post 1080 x 1350",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/robust/portrait/rosehill-reserve-story.png",
      "label": "Story 1080 x 1920",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/robust/portrait/rosehill-reserve-carousel-1.png",
      "label": "Carousel cover",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Halvorsen Roofing",
    "items": [
     {
      "src": "renders/robust/portrait/halvorsen-roofing-square.png",
      "label": "Feed post 1080 x 1080",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/robust/portrait/halvorsen-roofing-portrait.png",
      "label": "Feed post 1080 x 1350",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/robust/portrait/halvorsen-roofing-story.png",
      "label": "Story 1080 x 1920",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/robust/portrait/halvorsen-roofing-carousel-1.png",
      "label": "Carousel cover",
      "w": 1080,
      "h": 1350
     }
    ]
   }
  ]
 },
 "photo-boundary": {
  "title": "Photo robustness: boundary",
  "sub": "Red boundary line burned in, mc_fullres_12. Every photo-bearing template, every client theme, same photo, no manual adjustment.",
  "groups": [
   {
    "name": "Cangelosi",
    "items": [
     {
      "src": "renders/robust/boundary/cangelosi-square.png",
      "label": "Feed post 1080 x 1080",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/robust/boundary/cangelosi-portrait.png",
      "label": "Feed post 1080 x 1350",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/robust/boundary/cangelosi-story.png",
      "label": "Story 1080 x 1920",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/robust/boundary/cangelosi-carousel-1.png",
      "label": "Carousel cover",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Rosehill Reserve",
    "items": [
     {
      "src": "renders/robust/boundary/rosehill-reserve-square.png",
      "label": "Feed post 1080 x 1080",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/robust/boundary/rosehill-reserve-portrait.png",
      "label": "Feed post 1080 x 1350",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/robust/boundary/rosehill-reserve-story.png",
      "label": "Story 1080 x 1920",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/robust/boundary/rosehill-reserve-carousel-1.png",
      "label": "Carousel cover",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Halvorsen Roofing",
    "items": [
     {
      "src": "renders/robust/boundary/halvorsen-roofing-square.png",
      "label": "Feed post 1080 x 1080",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/robust/boundary/halvorsen-roofing-portrait.png",
      "label": "Feed post 1080 x 1350",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/robust/boundary/halvorsen-roofing-story.png",
      "label": "Story 1080 x 1920",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/robust/boundary/halvorsen-roofing-carousel-1.png",
      "label": "Carousel cover",
      "w": 1080,
      "h": 1350
     }
    ]
   }
  ]
 },
 "stress-long-copy": {
  "title": "Functional test: copy twice as long",
  "sub": "Every template rewritten at about twice the intended length and dropped in as is. Type refits inside the same plate. The photo never shrinks to make room.",
  "groups": [
   {
    "name": "Cangelosi, posts",
    "items": [
     {
      "src": "renders/stress/cangelosi-square-long.png",
      "label": "Feed post 1080 x 1080, long copy",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/stress/cangelosi-portrait-long.png",
      "label": "Feed post 1080 x 1350, long copy",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/stress/cangelosi-story-long.png",
      "label": "Story 1080 x 1920, long copy",
      "w": 1080,
      "h": 1920
     }
    ]
   },
   {
    "name": "Cangelosi, carousel",
    "items": [
     {
      "src": "renders/stress/cangelosi-carousel-1-long.png",
      "label": "Carousel cover, long copy",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/stress/cangelosi-carousel-2-long.png",
      "label": "Carousel point, long copy",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/stress/cangelosi-carousel-3-long.png",
      "label": "Carousel point, long copy",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/stress/cangelosi-carousel-4-long.png",
      "label": "Carousel stat, long copy",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/stress/cangelosi-carousel-5-long.png",
      "label": "Carousel end card, long copy",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Rosehill Reserve, posts",
    "items": [
     {
      "src": "renders/stress/rosehill-reserve-square-long.png",
      "label": "Feed post 1080 x 1080, long copy",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/stress/rosehill-reserve-portrait-long.png",
      "label": "Feed post 1080 x 1350, long copy",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/stress/rosehill-reserve-story-long.png",
      "label": "Story 1080 x 1920, long copy",
      "w": 1080,
      "h": 1920
     }
    ]
   },
   {
    "name": "Rosehill Reserve, carousel",
    "items": [
     {
      "src": "renders/stress/rosehill-reserve-carousel-1-long.png",
      "label": "Carousel cover, long copy",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/stress/rosehill-reserve-carousel-2-long.png",
      "label": "Carousel point, long copy",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/stress/rosehill-reserve-carousel-3-long.png",
      "label": "Carousel point, long copy",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/stress/rosehill-reserve-carousel-4-long.png",
      "label": "Carousel stat, long copy",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/stress/rosehill-reserve-carousel-5-long.png",
      "label": "Carousel end card, long copy",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Halvorsen Roofing, posts",
    "items": [
     {
      "src": "renders/stress/halvorsen-roofing-square-long.png",
      "label": "Feed post 1080 x 1080, long copy",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/stress/halvorsen-roofing-portrait-long.png",
      "label": "Feed post 1080 x 1350, long copy",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/stress/halvorsen-roofing-story-long.png",
      "label": "Story 1080 x 1920, long copy",
      "w": 1080,
      "h": 1920
     }
    ]
   },
   {
    "name": "Halvorsen Roofing, carousel",
    "items": [
     {
      "src": "renders/stress/halvorsen-roofing-carousel-1-long.png",
      "label": "Carousel cover, long copy",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/stress/halvorsen-roofing-carousel-2-long.png",
      "label": "Carousel point, long copy",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/stress/halvorsen-roofing-carousel-3-long.png",
      "label": "Carousel point, long copy",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/stress/halvorsen-roofing-carousel-4-long.png",
      "label": "Carousel stat, long copy",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/stress/halvorsen-roofing-carousel-5-long.png",
      "label": "Carousel end card, long copy",
      "w": 1080,
      "h": 1350
     }
    ]
   }
  ]
 },
 "guard-overlimit": {
  "title": "Guard: copy past the limit",
  "sub": "Three to four times too long, on a portrait photo and on the boundary aerial. The post refuses quietly shrinking type and says what to cut.",
  "groups": [
   {
    "name": "Over the limit",
    "items": [
     {
      "src": "renders/guard/halvorsen-roofing-square-mc_fullres_12.png",
      "label": "Halvorsen Roofing, square, mc_fullres_12",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/guard/cangelosi-square-mc_retouched_08.png",
      "label": "Cangelosi, square, mc_retouched_08",
      "w": 1080,
      "h": 1080
     }
    ]
   }
  ]
 },
 "guard-long-names": {
  "title": "Guard: long client names and long words",
  "sub": "Row one fits: a long name steps down and wraps, a long street name sets the headline size. Row two is past the limit: the name or the word is outlined and named in the red bar instead of being cut off at the plate edge.",
  "groups": [
   {
    "name": "Cangelosi, fits: Cangelosi Marble and Granite",
    "items": [
     {
      "src": "renders/guard/names/cangelosi-square-fits.png",
      "label": "Feed post 1080 x 1080, fits",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/guard/names/cangelosi-story-fits.png",
      "label": "Story 1080 x 1920, fits",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/guard/names/cangelosi-carousel-1-fits.png",
      "label": "Carousel cover, fits",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/guard/names/cangelosi-carousel-5-fits.png",
      "label": "Carousel end card, fits",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Cangelosi, past the limit: Cangelosi Natural Stone, Marble, Granite, and Quartzite Design Galleries",
    "items": [
     {
      "src": "renders/guard/names/cangelosi-square-over.png",
      "label": "Feed post 1080 x 1080, over",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/guard/names/cangelosi-story-over.png",
      "label": "Story 1080 x 1920, over",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/guard/names/cangelosi-carousel-1-over.png",
      "label": "Carousel cover, over",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/guard/names/cangelosi-carousel-5-over.png",
      "label": "Carousel end card, over",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Rosehill Reserve, fits: Rosehill Reserve Homeowners Association",
    "items": [
     {
      "src": "renders/guard/names/rosehill-reserve-square-fits.png",
      "label": "Feed post 1080 x 1080, fits",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/guard/names/rosehill-reserve-story-fits.png",
      "label": "Story 1080 x 1920, fits",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/guard/names/rosehill-reserve-carousel-1-fits.png",
      "label": "Carousel cover, fits",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/guard/names/rosehill-reserve-carousel-5-fits.png",
      "label": "Carousel end card, fits",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Rosehill Reserve, past the limit: Rosehill Reserve Master Planned Community Homeowners Association of Tomball Texas",
    "items": [
     {
      "src": "renders/guard/names/rosehill-reserve-square-over.png",
      "label": "Feed post 1080 x 1080, over",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/guard/names/rosehill-reserve-story-over.png",
      "label": "Story 1080 x 1920, over",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/guard/names/rosehill-reserve-carousel-1-over.png",
      "label": "Carousel cover, over",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/guard/names/rosehill-reserve-carousel-5-over.png",
      "label": "Carousel end card, over",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Halvorsen Roofing, fits: Halvorsen Roofing and Exteriors",
    "items": [
     {
      "src": "renders/guard/names/halvorsen-roofing-square-fits.png",
      "label": "Feed post 1080 x 1080, fits",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/guard/names/halvorsen-roofing-story-fits.png",
      "label": "Story 1080 x 1920, fits",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/guard/names/halvorsen-roofing-carousel-1-fits.png",
      "label": "Carousel cover, fits",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/guard/names/halvorsen-roofing-carousel-5-fits.png",
      "label": "Carousel end card, fits",
      "w": 1080,
      "h": 1350
     }
    ]
   },
   {
    "name": "Halvorsen Roofing, past the limit: Halvorsen Roofing, Gutters, Siding, and Storm Restoration of Northwest Houston",
    "items": [
     {
      "src": "renders/guard/names/halvorsen-roofing-square-over.png",
      "label": "Feed post 1080 x 1080, over",
      "w": 1080,
      "h": 1080
     },
     {
      "src": "renders/guard/names/halvorsen-roofing-story-over.png",
      "label": "Story 1080 x 1920, over",
      "w": 1080,
      "h": 1920
     },
     {
      "src": "renders/guard/names/halvorsen-roofing-carousel-1-over.png",
      "label": "Carousel cover, over",
      "w": 1080,
      "h": 1350
     },
     {
      "src": "renders/guard/names/halvorsen-roofing-carousel-5-over.png",
      "label": "Carousel end card, over",
      "w": 1080,
      "h": 1350
     }
    ]
   }
  ]
 }
};
chassis.js248 lines, 16.6 KB
/* Marathon client chassis, social and carousel. Builds one post from clients/<name>/client.js.
   No framework, no build step. Query string:
     client=<slug>   post=<id>   copy=long   photo=<file name without .jpg>   focus=<x% y%>
     headline=, eyebrow=, sub=, body=  (override any line for a quick test)
   Sets body[data-ready] when type is fitted. */
(function () {
  var Q = new URLSearchParams(location.search);
  var C = window.CS_CLIENT, ICON = window.CS_ICONS || {};
  var id = Q.get('post') || 'square';
  var base = C.posts[id];
  var P = Object.assign({}, base, Q.get('copy') === 'long' && base.long ? base.long : {});
  ['photo', 'focus', 'headline', 'eyebrow', 'sub', 'body', 'cta', 'value', 'label'].forEach(function (k) { if (Q.get(k) !== null) P[k] = Q.get(k); });
  if (Q.get('wordmark') !== null) C = Object.assign({}, C, { wordmark: Q.get('wordmark'), logo: '' });
  var total = Object.keys(C.posts).filter(function (k) { return k.indexOf('carousel-') === 0; }).length;

  /* Default crop focus per photo in the folder. Anything not listed is centred. */
  var FOCUS = { mc_retouched_02: '50% 60%', mc_retouched_07: '50% 30%', mc_retouched_08: '50% 35%', mc_retouched_09: '50% 30%', mc_rosehill_17: '40% 62%', mc_rosehill_18: '50% 55%' };

  function h(tag, cls, text) { var e = document.createElement(tag); if (cls) e.className = cls; if (text != null) e.textContent = text; return e; }
  function icon(name) { var w = document.createElement('span'); w.innerHTML = ICON[name] || ''; return w.firstChild || document.createTextNode(''); }
  function wordmark() { var w = h('span', 'cs-wordmark'); if (C.logo) { var i = h('img'); i.src = C.logo; i.alt = C.name; w.appendChild(i); } else w.textContent = C.wordmark; return w; }
  function cta(text) { var c = h('span', 'cs-cta'); c.appendChild(h('span', null, text)); c.appendChild(icon('arrow-right')); return c; }

  var canvas = h('div', 'cs-canvas');
  canvas.dataset.format = P.format; canvas.dataset.tpl = P.tpl;
  var flags = [];

  function photoTemplate() {
    canvas.dataset.surface = C.plate;
    var fig = h('figure', 'cs-photo'), img = h('img');
    img.src = 'photos/' + P.photo + '.jpg'; img.alt = '';
    img.style.objectPosition = P.focus || FOCUS[P.photo] || '50% 50%';
    fig.appendChild(img); canvas.appendChild(fig);
    var plate = h('section', 'cs-plate'); plate.dataset.surface = C.plate;
    plate.appendChild(h('div', 'cs-bar'));
    if (P.eyebrow) plate.appendChild(h('p', 'cs-eyebrow', P.eyebrow));
    var box = h('div', 'cs-box'), head = h('h1', 'cs-head', P.headline); box.appendChild(head); plate.appendChild(box);
    if (P.sub && P.format !== 'square') plate.appendChild(h('p', 'cs-sub', P.sub));
    if (P.facts && P.facts.length) {
      var ul = h('ul', 'cs-facts');
      P.facts.slice(0, 3).forEach(function (f) { var li = h('li'); li.appendChild(h('span', 'k', f[0])); li.appendChild(h('span', 'v', f[1])); ul.appendChild(li); });
      if (P.facts.length > 3) flags.push('Only three facts fit. Cut ' + (P.facts.length - 3) + '.');
      plate.appendChild(ul);
    }
    if (P.tpl === 'cover') {
      var pr = h('div', 'cs-progress'); pr.appendChild(wordmark()); pr.firstChild.style.marginRight = 'auto';
      pr.appendChild(h('span', 'cs-count', P.n + ' / ' + total)); pr.appendChild(cta(P.cta || 'Swipe'));
      plate.appendChild(pr);
    } else {
      var sign = h('div', 'cs-sign'); sign.appendChild(wordmark()); if (P.cta) sign.appendChild(cta(P.cta)); plate.appendChild(sign);
    }
    canvas.appendChild(plate);
    return { img: img, head: head, box: box };
  }

  function top(slide) { var t = h('div', 'cs-top'); t.appendChild(wordmark()); t.appendChild(h('span', 'cs-count', P.n + ' / ' + total)); slide.appendChild(t); }
  function progress(slide, last) {
    var pr = h('div', 'cs-progress');
    for (var i = 1; i <= total; i++) { var seg = h('i'); if (i === P.n) seg.className = 'on'; pr.appendChild(seg); }
    if (!last) pr.appendChild(cta('Swipe'));
    slide.appendChild(pr);
  }

  function pointTemplate() {
    canvas.dataset.surface = 'paper';
    var s = h('section', 'cs-slide'); top(s);
    s.appendChild(h('div', 'cs-num', P.num));
    var hb = h('div', 'cs-headbox'), head = h('h2', 'cs-head', P.headline); hb.appendChild(head); s.appendChild(hb);
    var bb = h('div', 'cs-bodybox'), body = h('p', 'cs-body', P.body); bb.appendChild(body); s.appendChild(bb);
    progress(s); canvas.appendChild(s);
    return { head: head, hb: hb, body: body, bb: bb };
  }

  function statTemplate() {
    canvas.dataset.surface = 'ink';
    var s = h('section', 'cs-slide'); top(s);
    var sb = h('div', 'cs-statbox'), v = h('div', 'cs-stat', P.value); sb.appendChild(v); s.appendChild(sb);
    s.appendChild(h('div', 'cs-bar'));
    var lb = h('div', 'cs-box'), label = h('p', 'cs-stat-label', P.label); lb.appendChild(label); s.appendChild(lb);
    if (P.source) s.appendChild(h('p', 'cs-source', P.source));
    progress(s); canvas.appendChild(s);
    return { v: v, sb: sb, label: label, lb: lb };
  }

  function endTemplate() {
    canvas.dataset.surface = C.endSurface || 'ink';
    var s = h('section', 'cs-slide'); top(s);
    s.appendChild(h('div', 'cs-bar'));
    var hb = h('div', 'cs-headbox'), head = h('h2', 'cs-head', P.headline); hb.appendChild(head); s.appendChild(hb);
    var ul = h('ul', 'cs-lines');
    (P.lines || []).slice(0, 3).forEach(function (t) { var li = h('li'); li.appendChild(icon('check')); li.appendChild(h('span', null, t)); ul.appendChild(li); });
    s.appendChild(ul);
    var b = h('div', 'cs-button'); b.appendChild(h('span', null, P.cta)); b.appendChild(icon('arrow-right')); s.appendChild(b);
    var es = h('div', 'cs-end-sign'); es.appendChild(wordmark()); es.appendChild(h('span', 'cs-descriptor', C.descriptor)); s.appendChild(es);
    canvas.appendChild(s);
    return { head: head, hb: hb, ul: ul, button: b };
  }

  var parts = ({ photo: photoTemplate, cover: photoTemplate, point: pointTemplate, stat: statTemplate, end: endTemplate })[P.tpl]();
  document.body.appendChild(canvas);

  /* ---------- fitting ---------- */
  var scale = parseFloat(getComputedStyle(canvas).getPropertyValue('--cl-display-scale')) || 1;
  function px(e, s) { e.style.fontSize = s + 'px'; }
  function lh(e) { var c = getComputedStyle(e); var v = parseFloat(c.lineHeight); return isNaN(v) ? parseFloat(c.fontSize) * 1.2 : v; }
  function lines(e) { return Math.round(e.getBoundingClientRect().height / lh(e)); }
  /* The widest single word in e at its current size, measured unbroken. Words never split
     mid-word on their own (overflow-wrap is off in chassis.css), so a street name or a model
     number that is wider than its box makes the type step down until it fits. */
  function widestWord(e) {
    var m = e.cloneNode(false), best = 0, word = '';
    m.classList.remove('cs-over', 'cs-word-over');
    m.style.position = 'absolute'; m.style.visibility = 'hidden'; m.style.whiteSpace = 'nowrap';
    m.style.width = 'auto'; m.style.maxWidth = 'none'; m.style.display = 'inline-block'; m.style.left = '0'; m.style.top = '0';
    e.parentNode.appendChild(m);
    (e.textContent || '').split(/\s+/).forEach(function (w) {
      w.split(/(?<=-)/).forEach(function (part) { if (!part) return; m.textContent = part; var x = m.getBoundingClientRect().width; if (x > best) { best = x; word = part; } });
    });
    m.remove();
    return { w: best, word: word.replace(/[.,;:!?]+$/, '') };
  }
  /* One long word may pull a line down to 60% of its design size, no further. A URL or an
     unbroken street name that needs more than that is flagged instead of shrinking the headline to a caption. */
  var WORD_FLOOR = 0.6;
  function wordOver(e, width) { var r = widestWord(e); return r.w > width + 1 ? r.word : ''; }
  function clear(e) { e.classList.remove('cs-over', 'cs-word-over'); }
  /* Last resort for a word that is still too wide at the floor size: let it break so nothing is
     hidden, outline it, and name the word in the red bar. */
  function flagWord(e, word, what) {
    e.classList.add('cs-word-over');
    flags.push('The word "' + word + '" is wider than the ' + what.replace(/^The /, '').toLowerCase() + ' box, so it would split mid-word. Abbreviate it (Pkwy, Blvd, Dr), hyphenate it, or cut it.');
  }
  /* Shrink e until it fits inside box (height and width), between max and min. */
  function fitBox(e, box, max, min, what, maxLines) {
    var s = max; clear(e); px(e, s);
    function over() { return e.scrollHeight > box.clientHeight + 1 || e.scrollWidth > box.clientWidth + 1 || (maxLines && lines(e) > maxLines) || !!wordOver(e, box.clientWidth); }
    var wf = Math.max(min, max * WORD_FLOOR);
    while (over() && s > min && !(s <= wf && wordOver(e, box.clientWidth))) { s -= 2; px(e, s); }
    var w = wordOver(e, box.clientWidth);
    if (w) flagWord(e, w, what);
    if (over()) { e.classList.add('cs-over'); e.style.setProperty('--clamp', Math.max(1, Math.floor(box.clientHeight / lh(e)))); if (!w) flags.push(what + ' is too long for this template. Cut it by about a third.'); }
    return s;
  }
  /* Shrink e until it is at most n lines and its widest word fits. */
  function fitLines(e, max, min, n, what) {
    var s = max; clear(e); px(e, s);
    function over() { return lines(e) > n || !!wordOver(e, e.clientWidth); }
    var wf = Math.max(min, max * WORD_FLOOR);
    while (over() && s > min && !(s <= wf && wordOver(e, e.clientWidth))) { s -= 1; px(e, s); }
    var w = wordOver(e, e.clientWidth);
    if (w) flagWord(e, w, what);
    if (lines(e) > n) { e.classList.add('cs-over'); e.style.setProperty('--clamp', n); flags.push(what + ' runs past ' + n + ' lines. Cut it.'); }
  }
  /* Client wordmark. Set on one line at its design size, then stepped down to 75%. If the name
     still does not fit it breaks onto two balanced lines and steps down again to 65%. Past that
     it is cut off, so it is outlined and the red bar says so. A logo image scales to its slot. */
  function fitWordmark(w) {
    if (w.querySelector('img')) return;
    w.classList.remove('cs-wm-wrap', 'cs-over-wm'); w.style.fontSize = '';
    var base = parseFloat(getComputedStyle(w).fontSize), s = base;
    function wide() { return w.scrollWidth > w.clientWidth + 1; }
    while (wide() && s > base * 0.75) { s -= 1; px(w, s); }
    if (!wide()) return;
    w.classList.add('cs-wm-wrap'); s = Math.round(base * 0.9); px(w, s);
    function over() { return wide() || lines(w) > 2 || !!wordOver(w, w.clientWidth); }
    while (over() && s > base * 0.65) { s -= 1; px(w, s); }
    if (over()) { w.classList.add('cs-over-wm'); flags.push('The client name "' + (C.wordmark || C.name) + '" does not fit its slot even at the smallest size and is cut off. Add the client logo file, or set a shorter wordmark in client.js.'); }
  }
  /* Shrink a single line element until it fits its own width. */
  function fitWidth(e, max, min, what) {
    var s = max; px(e, s);
    while (e.scrollWidth > e.clientWidth + 1 && s > min) { s -= 1; px(e, s); }
    if (e.scrollWidth > e.clientWidth + 1) flags.push(what + ' is cut off. Shorten it.');
  }
  function textOnly(e) { return e.querySelector('span') || e; }

  /* ---------- guards ---------- */
  function rgb(c) { var m = c.match(/[\d.]+/g); return m ? m.slice(0, 3).map(Number) : [0, 0, 0]; }
  function lum(c) { return rgb(c).map(function (v) { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); }).reduce(function (a, v, i) { return a + v * [0.2126, 0.7152, 0.0722][i]; }, 0); }
  function contrast(a, b) { var x = lum(a), y = lum(b); return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05); }
  function guardContrast() {
    var holder = canvas.querySelector('.cs-plate') || canvas;
    var cs = getComputedStyle(holder), bg = cs.backgroundColor, fg = cs.color;
    var probe = h('span'); probe.style.color = 'var(--s-accent)'; holder.appendChild(probe);
    var ac = getComputedStyle(probe).color; probe.remove();
    if (contrast(fg, bg) < 4.5) flags.push('Theme text colour fails contrast on this surface. Fix the theme file.');
    /* A weak accent never carries words. It drops back to the text colour automatically. */
    if (contrast(ac, bg) < 3) { holder.style.setProperty('--s-accent', fg); }
  }

  function fitAll() {
    var f = P.format, sq = f === 'square', split = canvas.dataset.orient === 'portrait' && sq;
    /* Sign-off rows first. A wordmark that has to wrap takes height, and the headline refits around it. */
    canvas.querySelectorAll('.cs-cta').forEach(function (c) { var t = textOnly(c); t.style.display = 'block'; });
    canvas.querySelectorAll('.cs-wordmark').forEach(fitWordmark);
    if (P.tpl === 'photo' || P.tpl === 'cover') {
      var e = canvas.querySelector('.cs-eyebrow'); if (e) fitWidth(e, 22, 16, 'Eyebrow');
      canvas.querySelectorAll('.cs-cta').forEach(function (c) { var t = textOnly(c); t.style.display = 'block'; fitWidth(t, 24, 18, 'The call to action'); });
      var max = { square: split ? 72 : 80, portrait: P.tpl === 'cover' ? 96 : 84, story: 96 }[f] * scale;
      var min = { square: 36, portrait: 38, story: 44 }[f] * Math.min(scale, 1);
      /* The headline has priority. The supporting line and the facts step down first, together,
         and only when they are at their floor does the headline go to its own floor. */
      var sub = canvas.querySelector('.cs-sub'), vals = canvas.querySelectorAll('.cs-facts .v');
      var subMax = f === 'story' ? 34 : 30, valMax = sq && !split ? 26 : 28, step = 0, fitted = false, keep = flags.length;
      while (!fitted) {
        flags.length = keep;
        if (sub) { sub.classList.remove('cs-over'); fitLines(sub, Math.max(22, subMax - step), 22, 3, 'The supporting line'); }
        vals.forEach(function (v) { v.classList.remove('cs-over'); fitLines(v, Math.max(20, valMax - step), 20, 2, 'A fact'); });
        parts.head.classList.remove('cs-over');
        var s0 = fitBox(parts.head, parts.box, max, min, 'The headline', split ? 7 : 4);
        var floor = (!sub || subMax - step <= 22) && valMax - step <= 20;
        fitted = !parts.head.classList.contains('cs-over') && (s0 >= max * 0.7 || floor) || floor;
        step += 2;
      }
    } else if (P.tpl === 'point') {
      fitLines(parts.head, 88 * scale, 44 * Math.min(scale, 1), 4, 'The headline');
      fitBox(parts.body, parts.bb, 38, 26, 'The body copy');
    } else if (P.tpl === 'stat') {
      parts.v.style.display = 'inline-block'; var s = 300 * scale; px(parts.v, s);
      while (parts.v.getBoundingClientRect().width > parts.sb.clientWidth && s > 120) { s -= 4; px(parts.v, s); }
      if (parts.v.getBoundingClientRect().width > parts.sb.clientWidth) flags.push('The stat is too wide. Use a shorter number or unit.');
      fitBox(parts.label, parts.lb, 50, 30, 'The stat label');
    } else if (P.tpl === 'end') {
      fitLines(parts.head, 92 * scale, 48 * Math.min(scale, 1), 3, 'The headline');
      parts.ul.querySelectorAll('span').forEach(function (t) { fitLines(t, 30, 24, 2, 'A contact line'); });
      fitWidth(textOnly(parts.button), 30, 22, 'The button text');
      if (parts.button.scrollWidth > parts.button.clientWidth + 1) flags.push('The button text is cut off. Shorten it.');
    }
    if (flags.length) { var fl = h('div', 'cs-flag', 'Fix before export: ' + flags.join(' ')); canvas.appendChild(fl); }
    document.body.dataset.ready = '1';
  }

  function go() { guardContrast(); fitAll(); }
  /* Load the client faces explicitly so fitting never measures a fallback font. */
  var rs = getComputedStyle(canvas), fd = rs.getPropertyValue('--cl-font-display').trim(), ft = rs.getPropertyValue('--cl-font-text').trim();
  var fonts = document.fonts ? Promise.all([
    document.fonts.load(rs.getPropertyValue('--cl-display-weight').trim() + ' 80px ' + fd, P.headline || 'A'),
    document.fonts.load('400 30px ' + ft), document.fonts.load('700 30px ' + ft),
    document.fonts.load(rs.getPropertyValue('--cl-label-weight').trim() + ' 22px ' + ft)
  ]).then(function () { return document.fonts.ready; }) : Promise.resolve();
  if (parts.img) {
    var probe = new Image();
    probe.onload = function () { canvas.dataset.orient = probe.naturalWidth / probe.naturalHeight < 0.9 ? 'portrait' : 'landscape'; fonts.then(function () { requestAnimationFrame(go); }); };
    probe.onerror = function () { canvas.dataset.orient = 'landscape'; flags.push('Photo ' + P.photo + ' not found in photos/.'); fonts.then(go); };
    probe.src = parts.img.src;
  } else { canvas.dataset.orient = 'none'; fonts.then(function () { requestAnimationFrame(go); }); }
})();
clients/cangelosi/client.js50 lines, 5.3 KB
/* Cangelosi content. Copy is drafted in Cangelosi's voice, a family stone showroom, not Marathon's.
   Confirm every claim with the client before posting. "long" holds the same post written twice as
   long, used to prove the template holds. */
window.CS_CLIENT = {
  slug: 'cangelosi', name: 'Cangelosi', wordmark: 'Cangelosi', descriptor: 'Natural stone and tile, Houston',
  plate: 'ink', endSurface: 'ink',
  posts: {
    square: { tpl: 'photo', format: 'square', photo: 'mc_retouched_03',
      eyebrow: 'In the showroom', headline: 'Walk the slabs before you pick one.',
      facts: [['Stone', 'Marble, quartzite, granite'], ['Visits', 'By appointment'], ['Where', 'Houston']], cta: 'Book a visit',
      long: { eyebrow: 'Now on the floor at the Houston showroom', headline: 'Walk every slab in person, side by side, under real light, before you put one on your kitchen island.',
        facts: [['Stone', 'Marble, quartzite, granite, porcelain, and quartz'], ['Visits', 'By appointment, Monday through Saturday'], ['Where', 'Our Houston showroom and slab yard']], cta: 'Book a showroom visit today' } },
    portrait: { tpl: 'photo', format: 'portrait', photo: 'mc_retouched_08',
      eyebrow: 'The sample wall', headline: 'Pull the sample. Hold it next to your cabinet door. Then decide.',
      sub: 'Bring a door, a tile, or a paint chip. We will lay the stone right next to it.',
      facts: [['Bring', 'A cabinet door'], ['Leave with', 'A short list']], cta: 'Book a visit',
      long: { eyebrow: 'The sample wall, open during every visit', headline: 'Pull the sample off the wall, hold it right next to your cabinet door and your floor tile, and only then decide.',
        sub: 'Bring a cabinet door, a floor tile, a paint chip, or a photo of the room. We will lay the stone right next to it under showroom light and daylight, so you see both.',
        facts: [['Bring', 'A cabinet door, tile, or paint chip'], ['Leave with', 'A short list and fabrication pricing']], cta: 'Book a sample wall visit' } },
    story: { tpl: 'photo', format: 'story', photo: 'mc_retouched_02',
      eyebrow: 'Find us', headline: 'Look for the stone sign. The showroom is right behind it.',
      sub: 'Free parking out front. Slabs, samples, and the team inside.', cta: 'Get directions',
      long: { eyebrow: 'Finding the showroom the first time', headline: 'Look for the big rose granite sign out front. The showroom and the slab yard are right behind it.',
        sub: 'Free parking out front, right by the door. Slabs on the floor, samples on the wall, and the whole team inside to walk you through what you are looking at.', cta: 'Tap for directions to the showroom' } },
    'carousel-1': { tpl: 'cover', format: 'portrait', photo: 'mc_retouched_06', n: 1,
      eyebrow: 'Countertop guide', headline: 'Before you pick a countertop stone.', cta: 'Swipe',
      long: { eyebrow: 'Countertop guide for kitchens and baths', headline: 'Read this before you pick a countertop stone for the kitchen you will cook in every day.' } },
    'carousel-2': { tpl: 'point', format: 'portrait', n: 2, num: '1',
      headline: 'Start with how you cook.',
      body: 'Marble etches when lemon juice or wine sits on it. Quartzite and granite shrug it off. A kitchen that works hard should start there.',
      long: { headline: 'Start with how you actually cook, not how the kitchen looks in photos.',
        body: 'Marble etches when lemon juice, wine, or tomato sauce sits on it. Quartzite and granite shrug it off. A kitchen that gets used hard every night should start with the harder stones, and save marble for the bath or a baking station.' } },
    'carousel-3': { tpl: 'point', format: 'portrait', n: 3, num: '2',
      headline: 'Look at the whole slab, not a chip.',
      body: 'Veining moves across a full slab. A 4 inch sample cannot show you where the seam lands on a 10 foot island.',
      long: { headline: 'Look at the whole slab on the floor, not a small chip on a sample ring.',
        body: 'Veining moves and changes across a full slab. A 4 inch sample cannot show you where the heaviest vein runs, or where the seam lands on a 10 foot island. Ask to see the actual slab and mark your layout on it.' } },
    'carousel-4': { tpl: 'stat', format: 'portrait', n: 4,
      value: '15 min', label: 'Leave a drop of lemon juice on the sample. If the polish dulls in 15 minutes, it will etch on your counter too.',
      source: 'Try it on any sample from our wall.',
      long: { label: 'Put a drop of lemon juice on the sample and set a timer. If the polish dulls inside 15 minutes, that stone will etch on your counter too, every time something acidic sits on it.',
        source: 'Try it on any sample from our wall, we keep lemons at the front desk.' } },
    'carousel-5': { tpl: 'end', format: 'portrait', n: 5,
      headline: 'Come see the slabs in person.',
      lines: ['Visits by appointment', 'Bring a cabinet door or tile', 'Message us to book a time'], cta: 'Book a visit',
      long: { headline: 'Come see the slabs in person, and bring the rest of the kitchen with you.',
        lines: ['Visits by appointment, Monday through Saturday', 'Bring a cabinet door, a floor tile, or a paint chip', 'Message us here or call the showroom to book a time'], cta: 'Book a showroom visit today' } }
  }
};
clients/halvorsen-roofing/client.js50 lines, 5.3 KB
/* Halvorsen Roofing content, the Houston roofer example. Copy is in a roofer's voice: short,
   blunt, and practical. A roofer does not talk like a designer. Photos are stand-ins from the
   folder until the roofer's own job photos arrive. "long" is each post at twice the length. */
window.CS_CLIENT = {
  slug: 'halvorsen-roofing', name: 'Halvorsen Roofing', wordmark: 'Halvorsen Roofing', descriptor: 'Roofing in Cypress, Tomball, and Katy',
  plate: 'ink', endSurface: 'ink',
  posts: {
    square: { tpl: 'photo', format: 'square', photo: 'mc_rosehill_16',
      eyebrow: 'Storm season', headline: 'Hail does not wait. Get your roof checked before it leaks.',
      facts: [['Inspection', 'Free'], ['Report', 'Photos to your phone'], ['Claims', 'We meet your adjuster']], cta: 'Call or text',
      long: { eyebrow: 'Storm season is here in northwest Houston', headline: 'Hail does not wait for a good time. Get your roof checked now, before the next rain finds the damage for you.',
        facts: [['Inspection', 'Free, no pressure, no contract'], ['Report', 'Photos of every problem, sent to your phone'], ['Claims', 'We meet your insurance adjuster on the roof']], cta: 'Call or text us today' } },
    portrait: { tpl: 'photo', format: 'portrait', photo: 'mc_rosehill_18',
      eyebrow: 'After the storm', headline: 'Shingles in the yard? Call us before you file the claim.',
      sub: 'We document the damage first, so your adjuster sees all of it.',
      facts: [['Response', 'Within 48 hours'], ['Areas', 'Cypress, Tomball, Katy']], cta: 'Call or text',
      long: { eyebrow: 'After the storm, before the insurance call', headline: 'Shingles in the yard or a stain on the ceiling? Call us before you file the insurance claim.',
        sub: 'We get on the roof and document every bit of the damage with photos first, so your adjuster sees all of it and you are not stuck paying for what they missed.',
        facts: [['Response', 'On your roof within 48 hours'], ['Areas', 'Cypress, Tomball, Katy, and Jersey Village']], cta: 'Call or text us right now' } },
    story: { tpl: 'photo', format: 'story', photo: 'mc_rosehill_17',
      eyebrow: 'Commercial roofs', headline: 'Flat roof over your storefront? We fix those too.',
      sub: 'TPO, metal, and modified bitumen. Same crew, same free inspection.', cta: 'Call or text',
      long: { eyebrow: 'Commercial roofing for Houston businesses', headline: 'Leaking flat roof over your storefront or your warehouse? We repair and replace those too.',
        sub: 'TPO, metal, and modified bitumen roofs on retail, office, and warehouse buildings. Same crew, same free inspection, and we work around your business hours.', cta: 'Call or text for a free inspection' } },
    'carousel-1': { tpl: 'cover', format: 'portrait', photo: 'mc_rosehill_16', n: 1,
      eyebrow: 'Roof check', headline: 'Signs your roof needs work.', cta: 'Swipe',
      long: { eyebrow: 'Roof check you can do from the driveway', headline: 'Signs your roof needs work that you can spot from the driveway, no ladder needed.' } },
    'carousel-2': { tpl: 'point', format: 'portrait', n: 2, num: '1',
      headline: 'Granules in the gutters.',
      body: 'Those black grains are the shingle wearing off. A handful after a storm is a warning. A gutter full means the roof is done.',
      long: { headline: 'Shingle granules piling up in the gutters and at the downspouts.',
        body: 'Those little black grains are the top layer of the shingle wearing off. A handful after a big storm is a warning sign. A gutter full of them, or a pile at the bottom of the downspout, means the roof is near the end.' } },
    'carousel-3': { tpl: 'point', format: 'portrait', n: 3, num: '2',
      headline: 'Shingles curling or lifting.',
      body: 'Curled edges let wind get under the shingle. The next strong storm takes it off, and water gets in behind it.',
      long: { headline: 'Shingles curling, cupping, or lifting at the edges.',
        body: 'Curled or cupped edges let the wind get under the shingle. The next strong storm peels it off, and water gets in behind it and into the decking. Catch it early and it is a repair, not a whole new roof.' } },
    'carousel-4': { tpl: 'stat', format: 'portrait', n: 4,
      value: '1 in.', label: 'Hail about an inch across, the size of a quarter, is where shingle damage usually starts.',
      source: 'Saw hail that size? Get it checked.',
      long: { label: 'Hail about an inch across, roughly the size of a quarter, is where shingle damage usually starts, even when you cannot see it from the ground.',
        source: 'Saw hail that size at your house? Get it checked before you file anything.' } },
    'carousel-5': { tpl: 'end', format: 'portrait', n: 5,
      headline: 'Free roof inspection.',
      lines: ['Photos of every problem, sent to your phone', 'We meet your insurance adjuster', 'Serving Cypress, Tomball, and Katy'], cta: 'Call or text',
      long: { headline: 'Free roof inspection, with no pressure and no contract to sign.',
        lines: ['Photos of every single problem, sent straight to your phone', 'We meet your insurance adjuster on the roof', 'Serving Cypress, Tomball, Katy, and Jersey Village'], cta: 'Call or text us today' } }
  }
};
clients/rosehill-reserve/client.js50 lines, 5.2 KB
/* Rosehill Reserve content. Copy is drafted in the community's voice, warm and neighborly, not
   Marathon's. Confirm every claim with the developer before posting. "long" is the same post at
   twice the length, used to prove the template holds. */
window.CS_CLIENT = {
  slug: 'rosehill-reserve', name: 'Rosehill Reserve', wordmark: 'Rosehill Reserve', descriptor: 'A master-planned community',
  plate: 'paper', endSurface: 'ink',
  posts: {
    square: { tpl: 'photo', format: 'square', photo: 'mc_rosehill_16',
      eyebrow: 'Life at Rosehill', headline: 'Ponds, trails, and a pool you can walk to.',
      facts: [['Homes', 'New construction'], ['Outside', 'Ponds and trails'], ['Amenity', 'Community pool']], cta: 'Schedule a tour',
      long: { eyebrow: 'Life at Rosehill Reserve, all year long', headline: 'Ponds, walking trails, open green space, and a community pool you can walk to from your front door.',
        facts: [['Homes', 'New construction from several builders'], ['Outside', 'Ponds, trails, and open green space'], ['Amenity', 'Community pool and amenity center']], cta: 'Schedule your model home tour' } },
    portrait: { tpl: 'photo', format: 'portrait', photo: 'mc_rosehill_17',
      eyebrow: 'At the front gate', headline: 'Coffee, groceries, and errands, right at the entrance.',
      sub: 'Shops sit just outside the Rosehill monument, so the quick trip stays quick.',
      facts: [['Retail', 'At the entrance'], ['Homes', 'Minutes inside']], cta: 'Schedule a tour',
      long: { eyebrow: 'Everyday errands, right at the front gate', headline: 'Morning coffee, groceries, fuel, and the rest of your errands, right at the entrance to the neighborhood.',
        sub: 'The shops sit just outside the Rosehill Reserve monument, so the quick trip for milk or coffee actually stays quick, and you are home again before the kids notice you left.',
        facts: [['Retail', 'Right at the community entrance'], ['Homes', 'A few minutes inside the gate']], cta: 'Schedule your tour this weekend' } },
    story: { tpl: 'photo', format: 'story', photo: 'mc_rosehill_18',
      eyebrow: 'New streets', headline: 'New homes are going up on every street.',
      sub: 'Pick your lot while the best ones are still open.', cta: 'Tour the models',
      long: { eyebrow: 'New streets opening this season', headline: 'New homes are going up on every street, and the next phase of homesites is opening now.',
        sub: 'Pick your lot while the best ones, backing onto the green space and the ponds, are still open. Walk the model homes first, then walk the lot.', cta: 'Tap to tour the model homes' } },
    'carousel-1': { tpl: 'cover', format: 'portrait', photo: 'mc_rosehill_17', n: 1,
      eyebrow: 'New home tips', headline: 'Touring a model home? Ask these first.', cta: 'Swipe',
      long: { eyebrow: 'New home tips from the Rosehill team', headline: 'Touring a model home this weekend? Ask these questions first, before you fall for the kitchen.' } },
    'carousel-2': { tpl: 'point', format: 'portrait', n: 2, num: '1',
      headline: 'Ask what is standard and what is an upgrade.',
      body: 'Model homes show the upgrades. Ask for the standard features list and walk the house with it in hand.',
      long: { headline: 'Ask what comes standard and what is an upgrade, room by room.',
        body: 'Model homes are built to show off the upgrades, from the flooring to the lighting. Ask for the standard features list before you start, and walk the whole house with it in your hand so you know what your price actually includes.' } },
    'carousel-3': { tpl: 'point', format: 'portrait', n: 3, num: '2',
      headline: 'Ask when your street will be finished.',
      body: 'Find out which phases are built, which are next, and when the trails and pool near your lot open.',
      long: { headline: 'Ask when your street, and the streets around it, will be finished.',
        body: 'Find out which phases are already built, which are next, and when the trails, ponds, and pool closest to your lot will open. It changes what move-in week actually looks like, and how long you live next to construction.' } },
    'carousel-4': { tpl: 'stat', format: 'portrait', n: 4,
      value: '7:30 am', label: 'Drive to work from here at 7:30 am on a weekday before you sign. That is your real commute.',
      source: 'Do it once. It answers more than any map.',
      long: { label: 'Drive from the front gate to your office at 7:30 am on a real weekday before you sign anything. That drive, not the one on the map app, is your real commute.',
        source: 'Do it once, and do it in the rain if you can. It answers more than any map.' } },
    'carousel-5': { tpl: 'end', format: 'portrait', n: 5,
      headline: 'Come walk Rosehill Reserve.',
      lines: ['Model homes open for tours', 'Homesites open in new phases', 'Message us to schedule'], cta: 'Schedule a tour',
      long: { headline: 'Come walk Rosehill Reserve, the model homes, the trails, and the lots.',
        lines: ['Model homes open for tours most days of the week', 'Homesites available now in the newest phases', 'Message us here and we will set up a time that works'], cta: 'Schedule your tour this weekend' } }
  }
};
icons-inline.js8 lines, 1.6 KB
/* Generated by build_icons.py from the Marathon icon set. Do not edit. */
window.CS_ICONS = {
 "arrow-right": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" class=\"mc-icon cs-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"1.5\" data-mc-icon=\"arrow-right\" role=\"img\" aria-label=\"Next\"><path d=\"M4 12h15M13 5l7 7-7 7\"/></svg>",
 "check": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" class=\"mc-icon cs-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"1.5\" data-mc-icon=\"check\" role=\"img\" aria-label=\"Done\"><path d=\"M4 12.5l5.5 5.5L20 6.5\"/></svg>",
 "calendar": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" class=\"mc-icon cs-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"1.5\" data-mc-icon=\"calendar\" role=\"img\" aria-label=\"Calendar\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"16\" rx=\"2\"/><path d=\"M3 10h18M8 3v4M16 3v4\"/></svg>",
 "home": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" class=\"mc-icon cs-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"1.5\" data-mc-icon=\"home\" role=\"img\" aria-label=\"Home\"><path d=\"M3 11l9-8 9 8\"/><path d=\"M5 9.5V21h14V9.5\"/><path d=\"M10 21v-6h4v6\"/></svg>"
};
jobs.json920 lines, 26.0 KB
[
 {
  "page": "post.html",
  "q": "client=cangelosi&post=square",
  "w": 1080,
  "h": 1080,
  "out": "renders/cangelosi/square.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=portrait",
  "w": 1080,
  "h": 1350,
  "out": "renders/cangelosi/portrait.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=story",
  "w": 1080,
  "h": 1920,
  "out": "renders/cangelosi/story.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-1",
  "w": 1080,
  "h": 1350,
  "out": "renders/cangelosi/carousel-1.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-2",
  "w": 1080,
  "h": 1350,
  "out": "renders/cangelosi/carousel-2.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-3",
  "w": 1080,
  "h": 1350,
  "out": "renders/cangelosi/carousel-3.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-4",
  "w": 1080,
  "h": 1350,
  "out": "renders/cangelosi/carousel-4.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-5",
  "w": 1080,
  "h": 1350,
  "out": "renders/cangelosi/carousel-5.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=square",
  "w": 1080,
  "h": 1080,
  "out": "renders/rosehill-reserve/square.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=portrait",
  "w": 1080,
  "h": 1350,
  "out": "renders/rosehill-reserve/portrait.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=story",
  "w": 1080,
  "h": 1920,
  "out": "renders/rosehill-reserve/story.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-1",
  "w": 1080,
  "h": 1350,
  "out": "renders/rosehill-reserve/carousel-1.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-2",
  "w": 1080,
  "h": 1350,
  "out": "renders/rosehill-reserve/carousel-2.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-3",
  "w": 1080,
  "h": 1350,
  "out": "renders/rosehill-reserve/carousel-3.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-4",
  "w": 1080,
  "h": 1350,
  "out": "renders/rosehill-reserve/carousel-4.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-5",
  "w": 1080,
  "h": 1350,
  "out": "renders/rosehill-reserve/carousel-5.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=square",
  "w": 1080,
  "h": 1080,
  "out": "renders/halvorsen-roofing/square.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=portrait",
  "w": 1080,
  "h": 1350,
  "out": "renders/halvorsen-roofing/portrait.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=story",
  "w": 1080,
  "h": 1920,
  "out": "renders/halvorsen-roofing/story.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-1",
  "w": 1080,
  "h": 1350,
  "out": "renders/halvorsen-roofing/carousel-1.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-2",
  "w": 1080,
  "h": 1350,
  "out": "renders/halvorsen-roofing/carousel-2.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-3",
  "w": 1080,
  "h": 1350,
  "out": "renders/halvorsen-roofing/carousel-3.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-4",
  "w": 1080,
  "h": 1350,
  "out": "renders/halvorsen-roofing/carousel-4.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-5",
  "w": 1080,
  "h": 1350,
  "out": "renders/halvorsen-roofing/carousel-5.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=square&photo=mc_retouched_05",
  "w": 1080,
  "h": 1080,
  "out": "renders/robust/interior/cangelosi-square.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=portrait&photo=mc_retouched_05",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/interior/cangelosi-portrait.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=story&photo=mc_retouched_05",
  "w": 1080,
  "h": 1920,
  "out": "renders/robust/interior/cangelosi-story.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-1&photo=mc_retouched_05",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/interior/cangelosi-carousel-1.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=square&photo=mc_retouched_05",
  "w": 1080,
  "h": 1080,
  "out": "renders/robust/interior/rosehill-reserve-square.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=portrait&photo=mc_retouched_05",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/interior/rosehill-reserve-portrait.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=story&photo=mc_retouched_05",
  "w": 1080,
  "h": 1920,
  "out": "renders/robust/interior/rosehill-reserve-story.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-1&photo=mc_retouched_05",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/interior/rosehill-reserve-carousel-1.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=square&photo=mc_retouched_05",
  "w": 1080,
  "h": 1080,
  "out": "renders/robust/interior/halvorsen-roofing-square.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=portrait&photo=mc_retouched_05",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/interior/halvorsen-roofing-portrait.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=story&photo=mc_retouched_05",
  "w": 1080,
  "h": 1920,
  "out": "renders/robust/interior/halvorsen-roofing-story.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-1&photo=mc_retouched_05",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/interior/halvorsen-roofing-carousel-1.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=square&photo=mc_rosehill_16",
  "w": 1080,
  "h": 1080,
  "out": "renders/robust/aerial/cangelosi-square.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=portrait&photo=mc_rosehill_16",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/aerial/cangelosi-portrait.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=story&photo=mc_rosehill_16",
  "w": 1080,
  "h": 1920,
  "out": "renders/robust/aerial/cangelosi-story.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-1&photo=mc_rosehill_16",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/aerial/cangelosi-carousel-1.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=square&photo=mc_rosehill_16",
  "w": 1080,
  "h": 1080,
  "out": "renders/robust/aerial/rosehill-reserve-square.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=portrait&photo=mc_rosehill_16",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/aerial/rosehill-reserve-portrait.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=story&photo=mc_rosehill_16",
  "w": 1080,
  "h": 1920,
  "out": "renders/robust/aerial/rosehill-reserve-story.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-1&photo=mc_rosehill_16",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/aerial/rosehill-reserve-carousel-1.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=square&photo=mc_rosehill_16",
  "w": 1080,
  "h": 1080,
  "out": "renders/robust/aerial/halvorsen-roofing-square.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=portrait&photo=mc_rosehill_16",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/aerial/halvorsen-roofing-portrait.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=story&photo=mc_rosehill_16",
  "w": 1080,
  "h": 1920,
  "out": "renders/robust/aerial/halvorsen-roofing-story.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-1&photo=mc_rosehill_16",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/aerial/halvorsen-roofing-carousel-1.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=square&photo=mc_retouched_08",
  "w": 1080,
  "h": 1080,
  "out": "renders/robust/portrait/cangelosi-square.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=portrait&photo=mc_retouched_08",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/portrait/cangelosi-portrait.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=story&photo=mc_retouched_08",
  "w": 1080,
  "h": 1920,
  "out": "renders/robust/portrait/cangelosi-story.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-1&photo=mc_retouched_08",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/portrait/cangelosi-carousel-1.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=square&photo=mc_retouched_08",
  "w": 1080,
  "h": 1080,
  "out": "renders/robust/portrait/rosehill-reserve-square.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=portrait&photo=mc_retouched_08",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/portrait/rosehill-reserve-portrait.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=story&photo=mc_retouched_08",
  "w": 1080,
  "h": 1920,
  "out": "renders/robust/portrait/rosehill-reserve-story.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-1&photo=mc_retouched_08",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/portrait/rosehill-reserve-carousel-1.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=square&photo=mc_retouched_08",
  "w": 1080,
  "h": 1080,
  "out": "renders/robust/portrait/halvorsen-roofing-square.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=portrait&photo=mc_retouched_08",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/portrait/halvorsen-roofing-portrait.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=story&photo=mc_retouched_08",
  "w": 1080,
  "h": 1920,
  "out": "renders/robust/portrait/halvorsen-roofing-story.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-1&photo=mc_retouched_08",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/portrait/halvorsen-roofing-carousel-1.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=square&photo=mc_fullres_12",
  "w": 1080,
  "h": 1080,
  "out": "renders/robust/boundary/cangelosi-square.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=portrait&photo=mc_fullres_12",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/boundary/cangelosi-portrait.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=story&photo=mc_fullres_12",
  "w": 1080,
  "h": 1920,
  "out": "renders/robust/boundary/cangelosi-story.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-1&photo=mc_fullres_12",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/boundary/cangelosi-carousel-1.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=square&photo=mc_fullres_12",
  "w": 1080,
  "h": 1080,
  "out": "renders/robust/boundary/rosehill-reserve-square.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=portrait&photo=mc_fullres_12",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/boundary/rosehill-reserve-portrait.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=story&photo=mc_fullres_12",
  "w": 1080,
  "h": 1920,
  "out": "renders/robust/boundary/rosehill-reserve-story.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-1&photo=mc_fullres_12",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/boundary/rosehill-reserve-carousel-1.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=square&photo=mc_fullres_12",
  "w": 1080,
  "h": 1080,
  "out": "renders/robust/boundary/halvorsen-roofing-square.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=portrait&photo=mc_fullres_12",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/boundary/halvorsen-roofing-portrait.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=story&photo=mc_fullres_12",
  "w": 1080,
  "h": 1920,
  "out": "renders/robust/boundary/halvorsen-roofing-story.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-1&photo=mc_fullres_12",
  "w": 1080,
  "h": 1350,
  "out": "renders/robust/boundary/halvorsen-roofing-carousel-1.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=square&copy=long",
  "w": 1080,
  "h": 1080,
  "out": "renders/stress/cangelosi-square-long.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=portrait&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/cangelosi-portrait-long.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=story&copy=long",
  "w": 1080,
  "h": 1920,
  "out": "renders/stress/cangelosi-story-long.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-1&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/cangelosi-carousel-1-long.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-2&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/cangelosi-carousel-2-long.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-3&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/cangelosi-carousel-3-long.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-4&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/cangelosi-carousel-4-long.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-5&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/cangelosi-carousel-5-long.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=square&copy=long",
  "w": 1080,
  "h": 1080,
  "out": "renders/stress/rosehill-reserve-square-long.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=portrait&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/rosehill-reserve-portrait-long.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=story&copy=long",
  "w": 1080,
  "h": 1920,
  "out": "renders/stress/rosehill-reserve-story-long.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-1&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/rosehill-reserve-carousel-1-long.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-2&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/rosehill-reserve-carousel-2-long.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-3&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/rosehill-reserve-carousel-3-long.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-4&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/rosehill-reserve-carousel-4-long.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-5&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/rosehill-reserve-carousel-5-long.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=square&copy=long",
  "w": 1080,
  "h": 1080,
  "out": "renders/stress/halvorsen-roofing-square-long.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=portrait&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/halvorsen-roofing-portrait-long.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=story&copy=long",
  "w": 1080,
  "h": 1920,
  "out": "renders/stress/halvorsen-roofing-story-long.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-1&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/halvorsen-roofing-carousel-1-long.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-2&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/halvorsen-roofing-carousel-2-long.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-3&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/halvorsen-roofing-carousel-3-long.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-4&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/halvorsen-roofing-carousel-4-long.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-5&copy=long",
  "w": 1080,
  "h": 1350,
  "out": "renders/stress/halvorsen-roofing-carousel-5-long.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=square&photo=mc_fullres_12&copy=long&headline=Hail%20does%20not%20wait%20for%20a%20good%20time%2C%20and%20neither%20does%20water.%20Get%20your%20roof%20checked%20now%2C%20before%20the%20next%20big%20Gulf%20storm%20rolls%20through%20northwest%20Houston%20and%20the%20rain%20finds%20every%20single%20bit%20of%20the%20damage%20for%20you%2C%20right%20through%20the%20ceiling.",
  "w": 1080,
  "h": 1080,
  "out": "renders/guard/halvorsen-roofing-square-mc_fullres_12.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=square&photo=mc_retouched_08&copy=long&headline=Walk%20every%20slab%20we%20have%20in%20person%2C%20side%20by%20side%2C%20under%20real%20showroom%20light%20and%20real%20daylight%2C%20with%20your%20cabinet%20door%20and%20floor%20tile%20in%20hand%2C%20before%20you%20put%20a%20single%20one%20of%20them%20on%20the%20kitchen%20island%20you%20will%20cook%20on%20for%20the%20next%2020%20years.",
  "w": 1080,
  "h": 1080,
  "out": "renders/guard/cangelosi-square-mc_retouched_08.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=square&wordmark=Cangelosi%20Marble%20and%20Granite&headline=Slabs%20are%20in%20at%20the%20Kuykendahl%20showroom%2C%20off%20Spring-Cypress.",
  "w": 1080,
  "h": 1080,
  "out": "renders/guard/names/cangelosi-square-fits.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=story&wordmark=Cangelosi%20Marble%20and%20Granite&headline=Slabs%20are%20in%20at%20the%20Kuykendahl%20showroom%2C%20off%20Spring-Cypress.",
  "w": 1080,
  "h": 1920,
  "out": "renders/guard/names/cangelosi-story-fits.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-1&wordmark=Cangelosi%20Marble%20and%20Granite&headline=Slabs%20are%20in%20at%20the%20Kuykendahl%20showroom%2C%20off%20Spring-Cypress.",
  "w": 1080,
  "h": 1350,
  "out": "renders/guard/names/cangelosi-carousel-1-fits.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-5&wordmark=Cangelosi%20Marble%20and%20Granite",
  "w": 1080,
  "h": 1350,
  "out": "renders/guard/names/cangelosi-carousel-5-fits.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=square&wordmark=Cangelosi%20Natural%20Stone%2C%20Marble%2C%20Granite%2C%20and%20Quartzite%20Design%20Galleries&headline=Book%20a%20slab%20visit%20at%20cangelosimarbleandgranite.com/appointments",
  "w": 1080,
  "h": 1080,
  "out": "renders/guard/names/cangelosi-square-over.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=story&wordmark=Cangelosi%20Natural%20Stone%2C%20Marble%2C%20Granite%2C%20and%20Quartzite%20Design%20Galleries&headline=Book%20a%20slab%20visit%20at%20cangelosimarbleandgranite.com/appointments",
  "w": 1080,
  "h": 1920,
  "out": "renders/guard/names/cangelosi-story-over.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-1&wordmark=Cangelosi%20Natural%20Stone%2C%20Marble%2C%20Granite%2C%20and%20Quartzite%20Design%20Galleries&headline=Book%20a%20slab%20visit%20at%20cangelosimarbleandgranite.com/appointments",
  "w": 1080,
  "h": 1350,
  "out": "renders/guard/names/cangelosi-carousel-1-over.png"
 },
 {
  "page": "post.html",
  "q": "client=cangelosi&post=carousel-5&wordmark=Cangelosi%20Natural%20Stone%2C%20Marble%2C%20Granite%2C%20and%20Quartzite%20Design%20Galleries",
  "w": 1080,
  "h": 1350,
  "out": "renders/guard/names/cangelosi-carousel-5-over.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=square&wordmark=Rosehill%20Reserve%20Homeowners%20Association&headline=Model%20homes%20open%20on%20Rosehill%20Reserve%20Boulevard%20this%20weekend.",
  "w": 1080,
  "h": 1080,
  "out": "renders/guard/names/rosehill-reserve-square-fits.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=story&wordmark=Rosehill%20Reserve%20Homeowners%20Association&headline=Model%20homes%20open%20on%20Rosehill%20Reserve%20Boulevard%20this%20weekend.",
  "w": 1080,
  "h": 1920,
  "out": "renders/guard/names/rosehill-reserve-story-fits.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-1&wordmark=Rosehill%20Reserve%20Homeowners%20Association&headline=Model%20homes%20open%20on%20Rosehill%20Reserve%20Boulevard%20this%20weekend.",
  "w": 1080,
  "h": 1350,
  "out": "renders/guard/names/rosehill-reserve-carousel-1-fits.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-5&wordmark=Rosehill%20Reserve%20Homeowners%20Association",
  "w": 1080,
  "h": 1350,
  "out": "renders/guard/names/rosehill-reserve-carousel-5-fits.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=square&wordmark=Rosehill%20Reserve%20Master%20Planned%20Community%20Homeowners%20Association%20of%20Tomball%20Texas&headline=Tour%20the%20models%20at%20rosehillreservecommunity.com/modelhomes",
  "w": 1080,
  "h": 1080,
  "out": "renders/guard/names/rosehill-reserve-square-over.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=story&wordmark=Rosehill%20Reserve%20Master%20Planned%20Community%20Homeowners%20Association%20of%20Tomball%20Texas&headline=Tour%20the%20models%20at%20rosehillreservecommunity.com/modelhomes",
  "w": 1080,
  "h": 1920,
  "out": "renders/guard/names/rosehill-reserve-story-over.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-1&wordmark=Rosehill%20Reserve%20Master%20Planned%20Community%20Homeowners%20Association%20of%20Tomball%20Texas&headline=Tour%20the%20models%20at%20rosehillreservecommunity.com/modelhomes",
  "w": 1080,
  "h": 1350,
  "out": "renders/guard/names/rosehill-reserve-carousel-1-over.png"
 },
 {
  "page": "post.html",
  "q": "client=rosehill-reserve&post=carousel-5&wordmark=Rosehill%20Reserve%20Master%20Planned%20Community%20Homeowners%20Association%20of%20Tomball%20Texas",
  "w": 1080,
  "h": 1350,
  "out": "renders/guard/names/rosehill-reserve-carousel-5-over.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=square&wordmark=Halvorsen%20Roofing%20and%20Exteriors&headline=Free%20inspections%20along%20Kuykendahl%20and%20Spring-Cypress.",
  "w": 1080,
  "h": 1080,
  "out": "renders/guard/names/halvorsen-roofing-square-fits.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=story&wordmark=Halvorsen%20Roofing%20and%20Exteriors&headline=Free%20inspections%20along%20Kuykendahl%20and%20Spring-Cypress.",
  "w": 1080,
  "h": 1920,
  "out": "renders/guard/names/halvorsen-roofing-story-fits.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-1&wordmark=Halvorsen%20Roofing%20and%20Exteriors&headline=Free%20inspections%20along%20Kuykendahl%20and%20Spring-Cypress.",
  "w": 1080,
  "h": 1350,
  "out": "renders/guard/names/halvorsen-roofing-carousel-1-fits.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-5&wordmark=Halvorsen%20Roofing%20and%20Exteriors",
  "w": 1080,
  "h": 1350,
  "out": "renders/guard/names/halvorsen-roofing-carousel-5-fits.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=square&wordmark=Halvorsen%20Roofing%2C%20Gutters%2C%20Siding%2C%20and%20Storm%20Restoration%20of%20Northwest%20Houston&headline=Book%20at%20halvorsenroofingandexteriors.com/freeinspection",
  "w": 1080,
  "h": 1080,
  "out": "renders/guard/names/halvorsen-roofing-square-over.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=story&wordmark=Halvorsen%20Roofing%2C%20Gutters%2C%20Siding%2C%20and%20Storm%20Restoration%20of%20Northwest%20Houston&headline=Book%20at%20halvorsenroofingandexteriors.com/freeinspection",
  "w": 1080,
  "h": 1920,
  "out": "renders/guard/names/halvorsen-roofing-story-over.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-1&wordmark=Halvorsen%20Roofing%2C%20Gutters%2C%20Siding%2C%20and%20Storm%20Restoration%20of%20Northwest%20Houston&headline=Book%20at%20halvorsenroofingandexteriors.com/freeinspection",
  "w": 1080,
  "h": 1350,
  "out": "renders/guard/names/halvorsen-roofing-carousel-1-over.png"
 },
 {
  "page": "post.html",
  "q": "client=halvorsen-roofing&post=carousel-5&wordmark=Halvorsen%20Roofing%2C%20Gutters%2C%20Siding%2C%20and%20Storm%20Restoration%20of%20Northwest%20Houston",
  "w": 1080,
  "h": 1350,
  "out": "renders/guard/names/halvorsen-roofing-carousel-5-over.png"
 },
 {
  "page": "board.html",
  "q": "board=primary",
  "w": 2400,
  "h": 1200,
  "full": 1,
  "out": "renders/primary.png"
 },
 {
  "page": "board.html",
  "q": "board=photo-interior",
  "w": 2400,
  "h": 1200,
  "full": 1,
  "out": "renders/photo-interior.png"
 },
 {
  "page": "board.html",
  "q": "board=photo-aerial",
  "w": 2400,
  "h": 1200,
  "full": 1,
  "out": "renders/photo-aerial.png"
 },
 {
  "page": "board.html",
  "q": "board=photo-portrait",
  "w": 2400,
  "h": 1200,
  "full": 1,
  "out": "renders/photo-portrait.png"
 },
 {
  "page": "board.html",
  "q": "board=photo-boundary",
  "w": 2400,
  "h": 1200,
  "full": 1,
  "out": "renders/photo-boundary.png"
 },
 {
  "page": "board.html",
  "q": "board=stress-long-copy",
  "w": 2400,
  "h": 1200,
  "full": 1,
  "out": "renders/stress-long-copy.png"
 },
 {
  "page": "board.html",
  "q": "board=guard-overlimit",
  "w": 2400,
  "h": 1200,
  "full": 1,
  "out": "renders/guard-overlimit.png"
 },
 {
  "page": "board.html",
  "q": "board=guard-long-names",
  "w": 2400,
  "h": 1200,
  "full": 1,
  "out": "renders/guard-long-names.png"
 }
]
clients/cangelosi/fonts/OFL-Cinzel.txt94 lines, 4.4 KB
Copyright 2020 The Cinzel Project Authors (https://github.com/NDISCOVER/Cinzel)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded, 
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
clients/cangelosi/fonts/OFL-Lato.txt94 lines, 4.4 KB
Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato"

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded, 
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
clients/halvorsen-roofing/fonts/OFL-Barlow.txt94 lines, 4.4 KB
Copyright 2017 The Barlow Project Authors (https://github.com/jpt/barlow)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded, 
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

6 more source files are in the zip above.