@rubytech/create-maxy 1.0.879 → 1.0.881
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +1 -0
- package/payload/platform/plugins/admin/skills/plainly/SKILL.md +105 -0
- package/payload/platform/plugins/admin/skills/plainly/references/worked-examples.md +301 -0
- package/payload/platform/plugins/cloudflare/PLUGIN.md +1 -1
- package/payload/platform/plugins/docs/references/platform.md +5 -1
- package/payload/platform/plugins/docs/references/plugins-guide.md +3 -3
- package/payload/platform/templates/agents/admin/IDENTITY.md +5 -1
- package/payload/platform/templates/agents/public/IDENTITY.md +6 -0
- package/payload/platform/templates/specialists/agents/content-producer.md +6 -0
- package/payload/platform/templates/specialists/agents/database-operator.md +6 -0
- package/payload/platform/templates/specialists/agents/personal-assistant.md +6 -0
- package/payload/platform/templates/specialists/agents/project-manager.md +6 -0
- package/payload/platform/templates/specialists/agents/research-assistant.md +6 -0
- package/payload/premium-plugins/real-agency/BUNDLE.md +4 -2
- package/payload/premium-plugins/real-agency/plugins/brochures/PLUGIN.md +36 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/commands/make-brochure.md +11 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/a4-print-documents/SKILL.md +288 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/brand-design/SKILL.md +185 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/make-brochure/SKILL.md +239 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/SKILL.md +306 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/template.html +1824 -0
- package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-extract/SKILL.md +228 -0
- package/payload/server/chunk-KMVUUVHM.js +11438 -0
- package/payload/server/chunk-LVC7NKZ2.js +689 -0
- package/payload/server/cloudflare-task-tracker-CY6QL6CY.js +22 -0
- package/payload/server/maxy-edge.js +2 -1
- package/payload/server/public/assets/{Checkbox-CqsIsmEi.js → Checkbox-CeujDRv0.js} +1 -1
- package/payload/server/public/assets/{admin-uVxIhs_u.js → admin-BN_z-2Bm.js} +4 -4
- package/payload/server/public/assets/data-LYciLZK9.js +1 -0
- package/payload/server/public/assets/graph-C-SKAbGX.js +1 -0
- package/payload/server/public/assets/{graph-labels-D0qUVHtZ.js → graph-labels-Co03qEv5.js} +1 -1
- package/payload/server/public/assets/jsx-runtime-BcZkJOEw.css +1 -0
- package/payload/server/public/assets/{page-CnyySOZF.js → page-C4E0CWHe.js} +1 -1
- package/payload/server/public/assets/{page-DcK36vDf.js → page-DGLz4ozf.js} +1 -1
- package/payload/server/public/assets/{public-SXA00FTv.js → public-rILg7e8-.js} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-DcByEBLy.js → useVoiceRecorder-D3Upd7Q3.js} +1 -1
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +8 -8
- package/payload/server/public/public.html +5 -5
- package/payload/server/server.js +80 -5
- package/payload/server/public/assets/data-CH-nQ7oX.js +0 -1
- package/payload/server/public/assets/graph-mpWDe4rf.js +0 -1
- package/payload/server/public/assets/jsx-runtime-Cy_HdZWV.css +0 -1
- /package/payload/server/public/assets/{jsx-runtime-BEjEWeaF.js → jsx-runtime-BWYXu1CT.js} +0 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: make-brochure
|
|
3
|
+
description: Use when the user gives both an estate agent's website URL (or existing brand-pack directory) and a property listing URL (or existing property-assets directory) in one request, asking for the brochure as an end-to-end deliverable. Trigger phrases include "build a brochure for <listing> using the agent at <agent-url>", "create a brochure for the property at <X> with the brand at <Y>", "end-to-end brochure for <listing>", "do everything from brand to brochure for <listing>", "make me the brochure for <listing> on <agent-site>", or any request that names both a brand source and a property source in the same turn.
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Bash
|
|
6
|
+
- Read
|
|
7
|
+
- Write
|
|
8
|
+
- Edit
|
|
9
|
+
- Glob
|
|
10
|
+
- Grep
|
|
11
|
+
- WebFetch
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# make-brochure
|
|
15
|
+
|
|
16
|
+
Single-entry orchestrator for the `brand-design` → `property-extract` → `property-brochure` pipeline. Routes inputs to the right skill in the right order, **skipping any upstream step whose output already exists on disk for the requested source URL**.
|
|
17
|
+
|
|
18
|
+
This skill is a playbook, not a programmatic dispatcher. It tells you (the agent) the routing rules; you execute each step using the underlying skills' own contracts.
|
|
19
|
+
|
|
20
|
+
## Outcome contract
|
|
21
|
+
|
|
22
|
+
The deliverable is identical to `property-brochure`'s outcome — a print-ready `output/brochure.html` plus per-page print PNGs, written into `<property-dir>/brochure/`. The orchestrator's value is in what it does **not** redo:
|
|
23
|
+
|
|
24
|
+
- If the brand pack for the agent already exists and matches the requested agent URL, `brand-design` is **skipped**.
|
|
25
|
+
- If the property assets for the listing already exist and match the requested listing URL, `property-extract` is **skipped**.
|
|
26
|
+
- `property-brochure` always runs (the brochure itself is the deliverable; re-running it is the point).
|
|
27
|
+
|
|
28
|
+
A run that re-tokenises a brand pack already on disk for the same URL is a **defect**, not an extra-careful pass. Re-tokenisation burns tokens, can mutate logo PNGs that downstream brochures already reference, and risks divergence between the brand pack and the brochures that consumed it earlier.
|
|
29
|
+
|
|
30
|
+
## Inputs
|
|
31
|
+
|
|
32
|
+
Required, in either form for each:
|
|
33
|
+
|
|
34
|
+
| Input | URL form | Path form |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| **brand source** | `https://<agent>.co.uk` | `<dir-with-DESIGN.md>` |
|
|
37
|
+
| **property source** | `https://<agent>.co.uk/property/<slug>/<id>/` | `<dir-with-property.json>` |
|
|
38
|
+
| **orientation** | `portrait` or `landscape` (A4) — see **Orientation routing** below |
|
|
39
|
+
|
|
40
|
+
Optional:
|
|
41
|
+
|
|
42
|
+
- `force` — one of `brand`, `property`, `all`, or absent. When set, the named upstream step runs even if its output already exists.
|
|
43
|
+
- `output_root` — defaults to the parent of the existing brand pack if one is given, otherwise `./` in the caller's working directory.
|
|
44
|
+
|
|
45
|
+
If only one input is supplied, **decline and ask for the other** — the orchestrator's reason for being is having both. Route a single-input request to the appropriate sub-skill directly.
|
|
46
|
+
|
|
47
|
+
## Orientation routing
|
|
48
|
+
|
|
49
|
+
The brochure ships in either portrait (210×297mm) or landscape (297×210mm) A4. The two are not visually interchangeable — page geometry, render-slot dimensions, print-snapshot pixel sizes, and the PDF call all change with the choice. See `property-brochure`'s **Orientation** section for the full dimensional contract.
|
|
50
|
+
|
|
51
|
+
Apply this rule **before** running any of the upstream steps:
|
|
52
|
+
|
|
53
|
+
| Did the user state orientation? | Action |
|
|
54
|
+
|---|---|
|
|
55
|
+
| Yes — explicit `portrait` / `landscape`, or named A4 dimensions in one of the two orders | Use the stated value. Pass it through to `property-brochure`. |
|
|
56
|
+
| No — but a prior brochure exists at `<property_dir>/brochure/output/brochure.html` | Read the existing `@page { size: A4 portrait \| landscape }` rule and confirm with the user in one line ("Keeping existing landscape — OK?"). Default to the inherited value if they don't object. |
|
|
57
|
+
| No — and there's no prior brochure | **Stop and ask.** One-line prompt: "Portrait or landscape A4?" Do not silently default to portrait. |
|
|
58
|
+
|
|
59
|
+
The reason orientation is hoisted to the orchestrator (not deferred to `property-brochure`) is the same as for missing brand/property sources: the question costs one round-trip; rebuilding a wrong-orientation brochure costs a full pipeline run plus user re-review. Always pay the cheap cost.
|
|
60
|
+
|
|
61
|
+
When the user answers, pass the orientation through to `property-brochure` as an explicit parameter. Do not re-ask in the downstream skill — the contract is "asked once at the orchestrator level".
|
|
62
|
+
|
|
63
|
+
## Routing rules
|
|
64
|
+
|
|
65
|
+
The skill performs four checks in order. Each rule fully specifies its *condition* and its *action*; do not improvise extra branches.
|
|
66
|
+
|
|
67
|
+
### 1. Resolve the brand identity
|
|
68
|
+
|
|
69
|
+
If `brand_source` is a URL: derive `brand_slug` as the lowercase domain root with no TLD (e.g. `https://muvin.co.uk` → `muvin`, `https://example-agent.co.uk` → `example-agent`).
|
|
70
|
+
|
|
71
|
+
If `brand_source` is a path: `brand_slug` = the directory's basename.
|
|
72
|
+
|
|
73
|
+
Resolve `brand_dir` as:
|
|
74
|
+
- `<output_root>/<brand_slug>/` when `brand_source` was a URL.
|
|
75
|
+
- the supplied path when `brand_source` was a path.
|
|
76
|
+
|
|
77
|
+
### 2. Decide whether to run `brand-design`
|
|
78
|
+
|
|
79
|
+
Read `<brand_dir>/DESIGN.md` if it exists. The first paragraph should record the source URL (per the `brand-design` artefact contract).
|
|
80
|
+
|
|
81
|
+
| `<brand_dir>/DESIGN.md` exists? | Recorded source URL matches request? | `force` value | Action |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| No | — | any | Run `brand-design` against the URL |
|
|
84
|
+
| Yes | Yes | absent | **Skip**. Reuse the existing brand pack. |
|
|
85
|
+
| Yes | Yes | `brand` or `all` | Run `brand-design` (overwrites existing pack) |
|
|
86
|
+
| Yes | No | absent | **Stop and ask the user** — the directory belongs to a different brand. Do not silently overwrite. |
|
|
87
|
+
| Yes | No | `brand` or `all` | Run `brand-design` (overwrites — user opted in) |
|
|
88
|
+
|
|
89
|
+
If `brand_source` was a path and `DESIGN.md` is missing, treat that as an authoring error — the user pointed at a directory that isn't a brand pack. Stop and ask for the actual brand-pack directory or a URL.
|
|
90
|
+
|
|
91
|
+
### 3. Resolve the property identity
|
|
92
|
+
|
|
93
|
+
If `property_source` is a URL: derive `property_slug` from the URL's `/property/<slug>/<id>/` path segment. The folder name is `<property_slug>-<id>` (e.g. `henham-road-debden-green-hamperden-end-cb11-3lz-949931`).
|
|
94
|
+
|
|
95
|
+
If `property_source` is a path: the folder name is the directory's basename.
|
|
96
|
+
|
|
97
|
+
Resolve `property_dir` as:
|
|
98
|
+
- `<brand_dir>/properties/<property_slug>-<id>/` when `property_source` was a URL. The property directory always lives **inside** the brand workspace, never as a sibling of it. This is non-negotiable — `property-extract`, `property-brochure`, and the brand pack all assume this nesting.
|
|
99
|
+
- the supplied path when `property_source` was a path. If the supplied path is not under `<brand_dir>/properties/`, stop and ask — silently re-rooting the property would orphan it from its brand pack.
|
|
100
|
+
|
|
101
|
+
### 4. Decide whether to run `property-extract`
|
|
102
|
+
|
|
103
|
+
Read `<property_dir>/property.json` if it exists.
|
|
104
|
+
|
|
105
|
+
| `<property_dir>/property.json` exists? | `source.url` matches request? | `force` value | Action |
|
|
106
|
+
|---|---|---|---|
|
|
107
|
+
| No | — | any | Run `property-extract` against the URL |
|
|
108
|
+
| Yes | Yes | absent | **Skip**. Reuse the existing property assets. |
|
|
109
|
+
| Yes | Yes | `property` or `all` | Run `property-extract` (overwrites existing) |
|
|
110
|
+
| Yes | No | absent | **Stop and ask the user** — the directory holds a different listing. |
|
|
111
|
+
| Yes | No | `property` or `all` | Run `property-extract` (overwrites — user opted in) |
|
|
112
|
+
|
|
113
|
+
### 5. Always run `property-brochure`
|
|
114
|
+
|
|
115
|
+
Once steps 2 and 4 have either completed or been skipped, invoke `property-brochure` with both directories as inputs. The brochure HTML and print PNGs land in `<property_dir>/brochure/output/` — that exact path, with the exact filenames listed in the Reference standard. No alternate names, no flat layout under `brochure/`.
|
|
116
|
+
|
|
117
|
+
If `<property_dir>/brochure/output/brochure.html` already exists from a prior run and the user has **not** asked to re-render, ask before overwriting. Re-rendering the brochure is cheap, but it invalidates whatever review state the user had on the previous version.
|
|
118
|
+
|
|
119
|
+
## Image preview safety
|
|
120
|
+
|
|
121
|
+
**Hard rule: never `Read` an image file whose longest edge exceeds 2000px.** The harness loads images into context as multimodal inputs and large ones break the session — the encoding budget overruns, the conversation can drop, and recovery is expensive.
|
|
122
|
+
|
|
123
|
+
Property photographs almost always exceed this threshold. Concrete examples in the validated reference directory:
|
|
124
|
+
|
|
125
|
+
| Filename pattern | Typical longest-edge |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `DSC*` (DSLR) | 6000–8000px |
|
|
128
|
+
| `IMG*` (iPhone wide) | 4000–5000px |
|
|
129
|
+
| `DJI_*` (drone) | 4000–8000px |
|
|
130
|
+
| `ChatGPT-Image-*` (AI render) | 1024 or 2048px (borderline — measure) |
|
|
131
|
+
| `Screenshot-*` (desktop capture) | 1200–2880px (borderline — measure) |
|
|
132
|
+
|
|
133
|
+
### Safe inspection workflow
|
|
134
|
+
|
|
135
|
+
Before any `Read` against an image:
|
|
136
|
+
|
|
137
|
+
1. **Measure first.** On macOS: `sips -g pixelWidth -g pixelHeight <file>`. With ImageMagick: `identify -format "%wx%h\n" <file>`. Both are fast and require no decode.
|
|
138
|
+
2. **If the longest edge ≤ 2000px**, reading the original is safe.
|
|
139
|
+
3. **If the longest edge > 2000px**, generate a downscaled preview into `/tmp/`, read the preview, then delete it:
|
|
140
|
+
```bash
|
|
141
|
+
sips -Z 2000 "<src>" --out "/tmp/preview-$(basename "<src>")"
|
|
142
|
+
# ...Read the preview...
|
|
143
|
+
rm "/tmp/preview-$(basename "<src>")"
|
|
144
|
+
```
|
|
145
|
+
Or with ImageMagick: `magick "<src>" -resize '2000x2000>' "/tmp/preview-..."`.
|
|
146
|
+
4. **Often you don't need the pixels at all** — filename, byte-size, dimensions, and EXIF tags answer most QA questions ("is this the kitchen wide shot?", "is this in landscape?") without a multimodal read.
|
|
147
|
+
|
|
148
|
+
The constraint applies to **every** image-reading touchpoint in the pipeline: shortlisting cover candidates, picking the kitchen hero, sanity-checking floorplans, verifying a downloaded asset, eyeballing AI-render quality. Treat the rule as non-negotiable; the failure mode is loss of the entire session, not a graceful error.
|
|
149
|
+
|
|
150
|
+
Generated brochure print snapshots (`cover-print.png`, `pageN-print.png`) are produced at A4 @ 96dpi which is **794×1123px** — those are safe to read directly.
|
|
151
|
+
|
|
152
|
+
## Process — high level
|
|
153
|
+
|
|
154
|
+
1. **Parse and validate inputs.** Confirm both sources are present; pick form (URL or path) per source; bail early with a clear question if either is missing or ambiguous. Apply the **Orientation routing** table — if orientation is not stated and not inheritable, stop and ask before doing any other work.
|
|
155
|
+
2. **Apply Routing rule 1–4.** For each upstream skill, determine `run` / `skip` / `ask` based on the table; record the decision in a one-line log so the user can see what was reused.
|
|
156
|
+
3. **Run the upstream skills in sequence**, brand first then property. Each runs under its own SKILL.md contract — do not adapt or shortcut their completion criteria. If either reports `DONE_WITH_CONCERNS` or `STATUS: BLOCKED`, surface that to the user and stop before invoking `property-brochure`.
|
|
157
|
+
4. **Run `property-brochure`** pointing at both directories. Confirm the brochure landed in `<property_dir>/brochure/output/` and that all per-page print PNGs are present.
|
|
158
|
+
5. **Report.** Tell the user, in order: which steps ran fresh, which were reused, the final brochure path, and any `DONE_WITH_CONCERNS` items from the upstream steps.
|
|
159
|
+
|
|
160
|
+
The orchestrator does not bundle the upstream skills' outputs — each leaves its artefacts where its own SKILL.md says to. The brochure is the only thing this skill is responsible for delivering.
|
|
161
|
+
|
|
162
|
+
## Reference standard
|
|
163
|
+
|
|
164
|
+
After a complete run, the on-disk layout is **exactly** this — no deeper nesting, no extra wrappers, no sibling `properties/`:
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
<output_root>/
|
|
168
|
+
<brand_slug>/ # = brand workspace = <brand_dir>
|
|
169
|
+
DESIGN.md # produced by brand-design
|
|
170
|
+
description.md # produced by brand-design
|
|
171
|
+
<brand_slug>-logo-light.png # produced by brand-design
|
|
172
|
+
<brand_slug>-logo-dark.png # produced by brand-design
|
|
173
|
+
properties/ # nested INSIDE the brand workspace
|
|
174
|
+
<property_slug>-<id>/ # = <property_dir>
|
|
175
|
+
property.json # produced by property-extract
|
|
176
|
+
description.md # produced by property-extract
|
|
177
|
+
images/ # produced by property-extract — original photo filenames preserved
|
|
178
|
+
floorplans/ # produced by property-extract
|
|
179
|
+
epc/ # produced by property-extract (may be empty)
|
|
180
|
+
brochure/ # owned by property-brochure
|
|
181
|
+
output/ # the only child of brochure/ — final deliverables; self-contained
|
|
182
|
+
brochure.html
|
|
183
|
+
cover-print.png
|
|
184
|
+
page2-print.png … page9-print.png
|
|
185
|
+
backpage-print.png
|
|
186
|
+
images/ # renamed + per-slot-optimised brochure photos: <property_slug>-NN.webp
|
|
187
|
+
<brand_slug>-light.png # brand logo for dark surfaces
|
|
188
|
+
<brand_slug>-dark.png # brand logo for light surfaces (when used)
|
|
189
|
+
<property_slug>-logo-light.svg # property line-drawing logo
|
|
190
|
+
<property_slug>-logo-dark.svg
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The `output/` folder is a portable deliverable: every image referenced by `brochure.html` resolves to a file under `output/images/`. References that escape `output/` (`../images/`, `../../floorplans/`, `../../epc/`) are a defect — the brochure must not depend on the sibling property-extract folders staying in place.
|
|
194
|
+
|
|
195
|
+
Reference instances that already conform:
|
|
196
|
+
- `/Users/neo/estate-agents/muvin/` (brand_slug = `muvin`)
|
|
197
|
+
- `/Users/neo/estate-agents/beacons/` (brand_slug = `beacons`)
|
|
198
|
+
|
|
199
|
+
Determinism rules — a run that violates any of these is wrong, even if the brochure renders:
|
|
200
|
+
- `properties/` is **inside** `<brand_dir>/`, never a sibling.
|
|
201
|
+
- `brochure/` contains exactly one child: `output/`. No `<name>-brochure-brand.html`, no flat HTML/PDF at the `brochure/` level.
|
|
202
|
+
- The brochure's HTML lives at `<property_dir>/brochure/output/brochure.html`. The filename is literal `brochure.html` — not `<property_slug>-brochure.html`.
|
|
203
|
+
- Print snapshots are named `cover-print.png`, `page2-print.png` … `page9-print.png`, `backpage-print.png` — never `cover-print-brand.png` or other variants.
|
|
204
|
+
- Anything that would otherwise pollute `brochure/` (legacy versions, scratch HTML, branding experiments) is moved out of `brochure/` entirely. `_archive/` at the property root is acceptable; extra files inside `brochure/` are not.
|
|
205
|
+
|
|
206
|
+
## Common mistakes
|
|
207
|
+
|
|
208
|
+
| Mistake | Why it's wrong |
|
|
209
|
+
|---|---|
|
|
210
|
+
| Running `brand-design` when `DESIGN.md` already exists for the same URL | Wastes tokens and risks regenerating logo PNGs that earlier brochures point at. |
|
|
211
|
+
| Treating "directory exists" as enough for skip-check | The directory may exist with a different brand's content. The check is **content match** (recorded source URL), not filesystem presence. |
|
|
212
|
+
| Silently overwriting a brand pack whose recorded URL differs from the request | The user almost certainly meant a sibling directory or a typo. Asking takes one turn; an overwrite costs them work. |
|
|
213
|
+
| Reading a 6000px DSLR photo with `Read` to "have a look" | Breaks the session. Always measure first; downscale if needed. |
|
|
214
|
+
| Skipping `property-brochure` because the brochure already exists from a prior run | The user asked for the end-to-end deliverable. If they want to keep the existing brochure, they will say so — confirm before overwriting, but don't unilaterally skip. |
|
|
215
|
+
| Bundling `brand-design`'s outputs into the property directory | The artefacts are separable on purpose. The brand pack is reused across listings; collapsing it into a single property's folder defeats reuse. |
|
|
216
|
+
| Inferring `force` from the user's tone ("redo it properly") | `force` is an explicit lever. If the user's intent is unclear, ask — don't assume `force=all`. |
|
|
217
|
+
| Silently defaulting to portrait when the user did not specify orientation | The brochure's geometry, render slots, and PDF call all change with orientation. A wrong choice means a full rebuild. One-line ask is cheaper than a wasted run. |
|
|
218
|
+
| Asking for orientation again in `property-brochure` after the orchestrator already collected it | The contract is "asked once". Re-asking annoys the user and risks a divergent answer mid-pipeline. Pass the value through. |
|
|
219
|
+
|
|
220
|
+
## Completion criteria
|
|
221
|
+
|
|
222
|
+
Report `DONE` only after:
|
|
223
|
+
|
|
224
|
+
- Both upstream artefact contracts are satisfied at their expected paths (`<brand_dir>/DESIGN.md`, `<property_dir>/property.json`).
|
|
225
|
+
- Orientation was explicit before any layout work began — either stated by the user, inherited from a prior brochure with confirmation, or asked-and-answered.
|
|
226
|
+
- The brochure HTML exists at `<property_dir>/brochure/output/brochure.html` and its `@page { size: A4 portrait | landscape }` matches the chosen orientation.
|
|
227
|
+
- The PDF deliverable exists alongside `brochure.html` (typically `<property_slug>-brochure.pdf`) and was emitted with the matching `landscape: true` flag (or omitted for portrait).
|
|
228
|
+
- All per-page print snapshots referenced by the HTML's print CSS exist at expected names and at the orientation-correct pixel dimensions (794×1123 portrait / 1123×794 landscape).
|
|
229
|
+
- The user-facing report names which steps ran and which were reused, **and the chosen orientation**.
|
|
230
|
+
- No image was read in violation of the 2000px rule (verifiable: every image read should have been preceded by a `sips`/`identify` measurement, and any over-threshold image was previewed via `/tmp/`).
|
|
231
|
+
|
|
232
|
+
If any condition is unmet, report `DONE_WITH_CONCERNS` and list the gap.
|
|
233
|
+
|
|
234
|
+
## When NOT to use this skill
|
|
235
|
+
|
|
236
|
+
- The user supplies only a brand URL (no property) → route directly to `brand-design`.
|
|
237
|
+
- The user supplies only a property URL (no brand context) → ask which agent's brand to use; if they say "use the existing one in `<dir>`", route to `property-extract` and then `property-brochure` with that brand directory.
|
|
238
|
+
- The user supplies pre-staged photos and asks for the brochure → route directly to `property-brochure`. There's nothing to orchestrate.
|
|
239
|
+
- The user wants to refresh a single piece (e.g. just regenerate the dark logo, just re-download images) → call the underlying skill directly with whatever scope they specified. The orchestrator is for end-to-end runs, not à la carte fixes.
|
package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/SKILL.md
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: property-brochure
|
|
3
|
+
description: Create A4 property brochures (landscape default, portrait optional) from raw photos, transcripts, and floorplans. Produces print-ready HTML and a recompressed PDF deliverable. The skill ships an editorial "folio" template by default; if the property has a DESIGN doc that specifies its own visual scheme, follow that instead. Use when asked to create an estate agent brochure, property marketing material, or property listing document.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Property Brochure
|
|
7
|
+
|
|
8
|
+
Produce an A4 property brochure from raw assets. The default deliverable is an 11-page editorial **folio** in landscape A4 — magazine-style chapters with Cormorant Garamond display type, Poppins body, drop caps, pull quotes, and asymmetric photo grids. The brochure renders in-browser for live editing and exports to pixel-perfect PDF via page-level image snapshots and a Ghostscript recompression pass.
|
|
9
|
+
|
|
10
|
+
## Reference template
|
|
11
|
+
|
|
12
|
+
See `references/template.html` — the canonical HTML template for the editorial folio (landscape by default; see the **Orientation** section for the portrait adaptation). Read it, copy to `output/brochure.html` in the property's project directory, and populate all `<!-- REPLACE: -->` placeholders with real content. The CSS is final; do not modify it except for the orientation swap documented under **Orientation**.
|
|
13
|
+
|
|
14
|
+
The folio comprises eleven pages: cover, contents, opening chapter, three editorial room spreads (house at ease / hall, hearth & sun / bedrooms & baths), a flexible feature page (annexe / studio / outbuilding — repurpose if the property has none), garden & ground, plan & particulars, material information, and a back page. Every chapter carries a Roman-numeral eyebrow, an italic-emphasised headline, an editorial deck, and folio numbers in the footer.
|
|
15
|
+
|
|
16
|
+
## Default visual scheme — and when to override it
|
|
17
|
+
|
|
18
|
+
If the property does not come with a DESIGN doc, **default to the scheme baked into `references/template.html`**:
|
|
19
|
+
|
|
20
|
+
- **Type**: Cormorant Garamond (display, italics for emphasis) + Poppins (body)
|
|
21
|
+
- **Palette**: warm paper (`#f6f1ea`), deep teal (`#0f4f5a`), gold (`#b8945c`), ink (`#1a2426`)
|
|
22
|
+
- **Voice**: editorial, considered, country-house tone — chapters numbered in Roman, dropcaps, pull quotes
|
|
23
|
+
- **Layout**: A4 landscape, 11 pages, asymmetric photo grids, full-bleed feature spreads, ornamental rules between sections
|
|
24
|
+
|
|
25
|
+
If a DESIGN doc *is* provided, **follow it**. Do not impose the default scheme over a brand's stated tokens, fonts, or layout system. The DESIGN doc's font stack, palette, and tone-of-voice take precedence over everything in this section. Adapt the structure (still 11 pages, still landscape by default) but re-skin: swap fonts at the `--display` / `--body` custom properties, replace palette tokens, adjust the editorial voice to match the brand's register.
|
|
26
|
+
|
|
27
|
+
## Required inputs
|
|
28
|
+
|
|
29
|
+
- **Property photographs** — minimum 16, ideally 25+. See image requirements below.
|
|
30
|
+
- **Property name and address**
|
|
31
|
+
- **Floor plan image** — used for room dimensions, total area, and compass orientation
|
|
32
|
+
- **EPC rating** — set the `current` class on the correct A-G band on page 9
|
|
33
|
+
- **Property walkthrough transcript or written description** — source for room descriptions, features, renovation history
|
|
34
|
+
- **Agent details** — read from the brand pack at `<brand_dir>/description.md` (registered office, phone, email, agency name). Logos: `<brand>-light.png` (logo for warm/light backgrounds) and `<brand>-dark.png` (logo for dark/photo backgrounds) as documented under **Brand logo** below. The cover and back page use the dark-background variant; the TOC credit line uses the light-background variant.
|
|
35
|
+
- **Page orientation** — `landscape` (297×210mm, default) or `portrait` (210×297mm). See the **Orientation** section.
|
|
36
|
+
- **DESIGN doc** (optional) — if present, overrides the default visual scheme.
|
|
37
|
+
|
|
38
|
+
## Orientation
|
|
39
|
+
|
|
40
|
+
Brochures ship in either **landscape** (297×210mm A4, default — the editorial folio is designed for it) or portrait (210×297mm A4). The choice changes page geometry, render-slot dimensions, print snapshot pixel sizes, the PDF call, and which page layouts make visual sense — it is **not a cosmetic flag**. Every downstream measurement in this SKILL has a landscape value and a portrait value.
|
|
41
|
+
|
|
42
|
+
### When to ask
|
|
43
|
+
|
|
44
|
+
Default to landscape unless the user says otherwise. The folio's editorial layouts (split heros, wide caption strips, full-bleed feature spreads, three-column plan + particulars + EPC pages) are tuned for landscape proportions; portrait works but compresses the spreads. If the user explicitly asks for portrait, fine — re-test every page's `scrollHeight` against the new fit budget before generating snapshots. If a prior brochure for this property exists at `<property_dir>/brochure/output/brochure.html`, inherit its orientation by reading the CSS (`@page { size: A4 landscape | portrait }`) rather than asking again.
|
|
45
|
+
|
|
46
|
+
### Dimensional contract
|
|
47
|
+
|
|
48
|
+
| Quantity | Landscape (default) | Portrait |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `.page` width × height | 297mm × 210mm | 210mm × 297mm |
|
|
51
|
+
| Page fit budget (px @ 96dpi) | 1123 × **794** | 794 × **1123** |
|
|
52
|
+
| Print snapshot pixel dimensions | 1123×794 | 794×1123 |
|
|
53
|
+
| `@page` rule | `size: A4 landscape` | `size: A4 portrait` |
|
|
54
|
+
| Playwright `page.pdf()` | `landscape: true` | (default; portrait) |
|
|
55
|
+
| "Safe to read" snapshot ceiling | longest-edge 1123px | longest-edge 1123px |
|
|
56
|
+
|
|
57
|
+
The 1600px image-source ceiling from the **Image renaming and optimisation** section is unchanged — it provides 2× retina headroom for full-bleed images at either orientation.
|
|
58
|
+
|
|
59
|
+
### Reference template
|
|
60
|
+
|
|
61
|
+
`references/template.html` ships **landscape** (`width: 297mm; min-height: 210mm` across `.page`, `.cover`, `.backpage`, plus `@page { size: A4 landscape }` in the print CSS). For portrait, swap every `297mm` ↔ `210mm` in those rules and change `@page { size: A4 landscape }` to `@page { size: A4 portrait }`. The page-internal flex/grid layouts then redistribute against the new aspect — re-test every page's `scrollHeight` against the new fit budget (1123 instead of 794) before generating snapshots, and expect the editorial feel to compress: the wide caption strips and side-by-side spread layouts will need to stack rather than sit alongside.
|
|
62
|
+
|
|
63
|
+
## Image preview safety
|
|
64
|
+
|
|
65
|
+
**Hard rule: never `Read` an image whose longest edge exceeds 2000px.** The harness loads images into context as multimodal inputs and large ones break the session — the encoding budget overruns and the conversation can drop. Property photographs from DSLRs (`DSC*`, 6000–8000px) and drones (`DJI_*`, 4000–8000px) routinely exceed the limit; AI renders (`ChatGPT-Image-*`, 1024 or 2048px) and screenshots are borderline.
|
|
66
|
+
|
|
67
|
+
Before any `Read` against an image:
|
|
68
|
+
|
|
69
|
+
1. **Measure first.** `sips -g pixelWidth -g pixelHeight <file>` (macOS) or `identify -format "%wx%h\n" <file>` (ImageMagick).
|
|
70
|
+
2. If the longest edge ≤ 2000px, reading the original is safe.
|
|
71
|
+
3. If it exceeds 2000px, generate a downscaled preview into `/tmp/`, read the preview, then delete it: `sips -Z 2000 "<src>" --out "/tmp/preview-$(basename "<src>")"`.
|
|
72
|
+
4. Often you don't need pixels at all — filename, byte-size, dimensions, and EXIF answer most layout-decision questions ("is this the kitchen wide shot?", "landscape or portrait?") without a multimodal read.
|
|
73
|
+
|
|
74
|
+
Generated brochure print snapshots (`cover-print.png`, `pageN-print.png`) are A4 @ 96dpi — 1123×794px in landscape, 794×1123px in portrait. Either way they are safe to read directly.
|
|
75
|
+
|
|
76
|
+
The constraint is non-negotiable; the failure mode is loss of the entire session, not a graceful error.
|
|
77
|
+
|
|
78
|
+
## Image requirements
|
|
79
|
+
|
|
80
|
+
Rename all photos to `{property-slug}-NN.webp` in an `images/` subfolder. The folio template references images by index, not by descriptive name — index 01 is the cover hero, index 02 is the principal elevation, etc. Slot up to 17 distinct images; missing slots can be replaced by re-using a strong photo from elsewhere, or the spread can be reflowed to omit the cell.
|
|
81
|
+
|
|
82
|
+
| Index | Page | Role |
|
|
83
|
+
|------|------|------|
|
|
84
|
+
| 01 | Cover | Hero — golden-hour exterior or atmospheric front shot |
|
|
85
|
+
| 02 | Opener (p3) | Principal front elevation |
|
|
86
|
+
| 04 | Contents (p2), House at ease (p4) | Wide open-plan reception or kitchen-dining hero |
|
|
87
|
+
| 05 | Hall, hearth & sun (p5) | Open reception or hero room |
|
|
88
|
+
| 06 | House at ease (p4) — strip | Kitchen / breakfast room |
|
|
89
|
+
| 07 | House at ease (p4) — strip | Kitchen wide or patio doors |
|
|
90
|
+
| 08 | House at ease (p4) — strip | Separate living / sitting room |
|
|
91
|
+
| 09 | Hall, hearth & sun (p5) | Entrance hallway |
|
|
92
|
+
| 10 | Hall, hearth & sun (p5) | Garden room / sun room / second reception |
|
|
93
|
+
| 13 | Bedrooms & baths (p6) | Principal bedroom (tall feature cell) |
|
|
94
|
+
| 14 | Bedrooms & baths (p6) | Shower room or secondary bathroom |
|
|
95
|
+
| 15 | Bedrooms & baths (p6), Annexe (p7) | Bedroom two — and the feature page if the feature is annexe-like |
|
|
96
|
+
| 18 | Bedrooms & baths (p6) | Bedroom three |
|
|
97
|
+
| 19 | Bedrooms & baths (p6) | Family bathroom |
|
|
98
|
+
| 24 | Garden & ground (p8) | Aerial / drone / wide garden hero |
|
|
99
|
+
| 25 | Plan & particulars (p9) | Composite floor plan |
|
|
100
|
+
| 26 | Back page | Atmospheric rear or aerial at golden hour |
|
|
101
|
+
|
|
102
|
+
Indexes 03, 11–12, 16–17, 20–23, 27+ are intentionally unused — leaving gaps means a property with more rooms can use them without renumbering. Adapt the page layout when images are missing — remove the slot, don't leave a placeholder.
|
|
103
|
+
|
|
104
|
+
## Output folder structure
|
|
105
|
+
|
|
106
|
+
All final artefacts go in **exactly one** location: `<property_dir>/brochure/output/`, where `<property_dir>` is `<brand_dir>/properties/<property_slug>-<id>/`. The folder is the deliverable; no other path under `brochure/` is acceptable.
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
<brand_dir>/properties/<property_slug>-<id>/brochure/output/
|
|
110
|
+
brochure.html # literal filename — NOT <property_slug>-brochure.html
|
|
111
|
+
cover-print.png # cover snapshot
|
|
112
|
+
page2-print.png … page10-print.png
|
|
113
|
+
backpage-print.png
|
|
114
|
+
images/
|
|
115
|
+
<property_slug>-01.webp # cover hero (optimised)
|
|
116
|
+
<property_slug>-02.webp # front elevation
|
|
117
|
+
<property_slug>-NN.webp … # remaining photos by index
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The brochure folder is **self-contained**: every image the HTML references must live under `output/images/`. Never reference `../images/`, `../../floorplans/`, `../../epc/`, or any other path that escapes `output/`. Those upstream `property-extract` directories are inputs to the brochure, not dependencies of the deliverable. A brochure that breaks if its source property assets move is wrong.
|
|
121
|
+
|
|
122
|
+
If any prior run left a flat brochure under `brochure/`, move it to `<property_dir>/_archive/` before writing the canonical layout — never overwrite, never leave it adjacent to `output/`.
|
|
123
|
+
|
|
124
|
+
## Image renaming and optimisation
|
|
125
|
+
|
|
126
|
+
Source photos are oversized for the brochure's render slots. Optimise as you copy.
|
|
127
|
+
|
|
128
|
+
Rename **and re-encode** every photo to `{property-slug}-NN.webp` per the index table above. Use zero-padded two-digit numbers. The brochure HTML must reference `images/{property-slug}-NN.webp` — not the original camera/WhatsApp filenames, not the source path under `<property_dir>/images/`, not a path that escapes `output/`.
|
|
129
|
+
|
|
130
|
+
### Render-slot sizing
|
|
131
|
+
|
|
132
|
+
| Slot type | Folio positions | Max long edge | WebP quality |
|
|
133
|
+
|---|---|---|---|
|
|
134
|
+
| **Cover hero** | page 01 cover, full-bleed | 1600px | 85 |
|
|
135
|
+
| **Page banner** | aerial garden hero (p8), feature image (p7), back-page bg | 1600px | 82 |
|
|
136
|
+
| **Story photo** | hero spread images (p4 main, p5 main) | 1280px | 82 |
|
|
137
|
+
| **Gallery thumb** | strip cells (p4), gallery cells (p6), stacked images (p5) | 1024px | 80 |
|
|
138
|
+
| **Floor plan** | page 9 — line/text content, q80 produces visible mosquito noise on rules | 1600px | 90 |
|
|
139
|
+
| **Map / screenshot** | screenshot with text labels | 1600px | 85 |
|
|
140
|
+
| **EPC chart** | small, already <100KB — copy through unchanged | as-is | as-is |
|
|
141
|
+
|
|
142
|
+
The numbers are calibrated against the print snapshot pipeline (A4 @ 96dpi → 1123×794px landscape or 794×1123px portrait) at 2× retina headroom. Going beyond 1600px on the long edge buys nothing the print snapshot can use; going below 1024px on a gallery thumb produces softening that's visible at print scale. The slot-type table is orientation-agnostic; what changes is *which slots a given page uses*, not the long-edge ceilings.
|
|
143
|
+
|
|
144
|
+
### Encoder
|
|
145
|
+
|
|
146
|
+
Use `cwebp -q <quality> -m 6 -mt -resize <width> 0 <src> -o <dst>`. For source images already encoded at the same quality and dimensions (a previously-optimised WebP), re-encoding rarely saves more than 1–2% — verify by comparing output size to source size, and if it grew or stayed flat, copy the source through instead.
|
|
147
|
+
|
|
148
|
+
### What to optimise
|
|
149
|
+
|
|
150
|
+
Optimise every image referenced by the HTML, including those that started as `.png` or `.jpeg`. PNG photography is the highest-leverage target. Discard the originals from `output/images/` after the WebP is in place; the source images under `<property_dir>/images/`, `<property_dir>/floorplans/`, and `<property_dir>/epc/` remain as the canonical untouched copies.
|
|
151
|
+
|
|
152
|
+
After optimisation, sanity-check: open the brochure in a browser, watch for 404s in the console, visually inspect cover and floorplan pages at 1× zoom. A typical 17-photo folio lands in the 3–6 MB range total; anything over 10 MB suggests an over-sized slot.
|
|
153
|
+
|
|
154
|
+
## Brand logo
|
|
155
|
+
|
|
156
|
+
The cover masthead, back-page masthead, and TOC credit line use the brand's logo image — never a text-only "BRAND · Property Folio" string. Two variants are required:
|
|
157
|
+
|
|
158
|
+
| Variant | Use on | Filename pattern |
|
|
159
|
+
|---|---|---|
|
|
160
|
+
| **Light/white logo** (white pixels on transparent) | Cover masthead, back-page masthead — both sit over dark golden-hour photography | `<brand>-light.png` or `<brand>-on-dark.png` |
|
|
161
|
+
| **Dark/coloured logo** (brand colour on transparent) | TOC credit line — sits on warm paper background | `<brand>-dark.png` or `<brand>-on-light.png` |
|
|
162
|
+
|
|
163
|
+
Copy both into `output/images/` alongside the property photos. Reference them in the template at:
|
|
164
|
+
|
|
165
|
+
- `.cover-mast img` — `<img src="images/{brand}-light.png" alt="{Brand}">`
|
|
166
|
+
- `.back-mast img` — same as cover
|
|
167
|
+
- `.toc-credit img` — `<img src="images/{brand}-dark.png" alt="{Brand}">`
|
|
168
|
+
|
|
169
|
+
The masthead lockup is `[logo] | [PROPERTY FOLIO]` — the `<span class="pipe">` is a hairline divider; the `<span class="label">` is the small-caps "Property Folio" tag. Don't drop either; the lockup gives the logo air and signals what the document is.
|
|
170
|
+
|
|
171
|
+
If only **one** logo variant exists (e.g. a white-on-transparent only), use it everywhere — but on the warm-paper TOC background, either wrap it in a dark-teal pill or skip the TOC instance rather than ship an invisible logo. If **no** logo asset exists, fall back to the original text masthead (`<div class="cover-mast">BRAND · Property Folio</div>`) and remove the `<img>` and `<span>` children.
|
|
172
|
+
|
|
173
|
+
Logo height: 26px (cover), 28px (back page), 22px (TOC credit). Don't let it exceed 30px — the masthead is a piece of trim, not a billboard.
|
|
174
|
+
|
|
175
|
+
## AI hero image prompt
|
|
176
|
+
|
|
177
|
+
The cover and back page benefit from a golden-hour version of the front and rear exteriors. The prompt must:
|
|
178
|
+
- Reference the floor plan and aerial image to determine compass orientation
|
|
179
|
+
- Position the setting sun on the correct side of the frame relative to the front elevation
|
|
180
|
+
- Specify warm amber directional light on the facade from the sun side
|
|
181
|
+
- Sky gradient from deep amber at sun to pale peach opposite
|
|
182
|
+
- Keep the house structure sharp and unmodified
|
|
183
|
+
- Professional UK estate agent photography style
|
|
184
|
+
- Output the prompt text for the user to execute in their image tool of choice
|
|
185
|
+
|
|
186
|
+
## 11-page structure
|
|
187
|
+
|
|
188
|
+
Each page is a `<section class="page">` (or `.cover` / `.backpage`) sized to the chosen orientation: `width: 297mm; min-height: 210mm` (landscape) or `width: 210mm; min-height: 297mm` (portrait). Every page must fit within the orientation's short-edge budget (794px landscape, 1123px portrait — both at 96dpi). Measure with `element.scrollHeight` during development against that budget and adjust image heights with inline styles if content overflows.
|
|
189
|
+
|
|
190
|
+
| Page | Class | Chapter | Key content |
|
|
191
|
+
|------|-------|---------|-------------|
|
|
192
|
+
| 1 | `.cover` | Cover | Full-bleed hero, masthead, edition line, location eyebrow, property title, tagline, guide price, folio number |
|
|
193
|
+
| 2 | `.page` | Contents (TOC) | Editorial image with caption, eight-chapter table of contents (Roman i–viii), credit line |
|
|
194
|
+
| 3 | `.page` | I · Of place & pedigree | Opener — property name, county/postcode, 5-stat row, two-paragraph editorial intro with dropcap, principal elevation image with caption |
|
|
195
|
+
| 4 | `.page` | II · A house at ease | Hero open-plan image, text column, three-image caption strip |
|
|
196
|
+
| 5 | `.page` | III · Hall, hearth & sun | Split header, large left image, two stacked right images |
|
|
197
|
+
| 6 | `.page` | IV · Bedrooms & baths | Text column with 3-stat row, 5-cell gallery (one tall feature) |
|
|
198
|
+
| 7 | `.page` | V · Feature page | Full-bleed feature image (annexe/studio/outbuilding), narrow text panel with attribute grid |
|
|
199
|
+
| 8 | `.page` | VI · Garden & ground | Full-bleed aerial with overlaid title, four short-text panels |
|
|
200
|
+
| 9 | `.page` | VII · Plan & particulars | Wide chapter header, large floor plan + meta, 8-9 item particulars list, EPC bar chart |
|
|
201
|
+
| 10 | `.page` | VIII · Material information | Three-section disclosure grid (Parts A/B/C), boilerplate footer |
|
|
202
|
+
| 11 | `.backpage` | Back page | Atmospheric background, masthead, CTA headline, two-cell contact grid (agent + office), folio footer, disclaimer |
|
|
203
|
+
|
|
204
|
+
## Material Information (UK regulatory compliance)
|
|
205
|
+
|
|
206
|
+
Three tiers per National Trading Standards and DMCCA 2024. Populate all known fields; mark unknowns as "TBC":
|
|
207
|
+
|
|
208
|
+
- **Part A** (all transactions): price, tenure, council tax, property type, rooms, floor area, utilities (electricity, water, sewerage, heating), broadband, mobile, parking, EPC
|
|
209
|
+
- **Part B** (where applicable): building safety, covenants, rights of way, easements, listed status, conservation area, TPOs, accessibility
|
|
210
|
+
- **Part C** (additional facts): flood risk, coastal erosion, planning, construction type, structural issues, subsidence, damp, asbestos, knotweed, mining
|
|
211
|
+
|
|
212
|
+
## Floorplan sizing (page 9)
|
|
213
|
+
|
|
214
|
+
Floorplans are the most information-dense images in the brochure. The plan area on page 9 is the largest rectangle that does not push the particulars list, EPC bar, or chapter header off A4. Use `object-fit: contain; width: 100%; height: 100%` so the image fills the slot without distortion. Wide-aspect floorplans leave vertical headroom; tall ones leave horizontal headroom — that's expected and fine. After any change, measure both `scrollHeight` against the orientation's page-fit budget (794px landscape / 1123px portrait) **and** the spec-side column's `scrollHeight` against `clientHeight` — text overflow is the silent failure that a screenshot can hide.
|
|
215
|
+
|
|
216
|
+
If a brochure has multiple floor plans (per-floor breakdowns), tune the layout to the images' aspect ratios rather than splitting evenly. A 2:1 wide-aspect plan needs less vertical room than a 1.16:1 nearly-square one.
|
|
217
|
+
|
|
218
|
+
## Live editing workflow
|
|
219
|
+
|
|
220
|
+
1. Copy template to property directory, populate with content
|
|
221
|
+
2. Stage `output/images/` per the **Image renaming and optimisation** section
|
|
222
|
+
3. Serve locally: `python3 -m http.server <port>` from the project root
|
|
223
|
+
4. Navigate Playwright to the brochure URL
|
|
224
|
+
5. Present to user in browser for review
|
|
225
|
+
6. User requests changes — iterate on content, image sizing, page balance
|
|
226
|
+
7. Verify every page fits A4: measure `scrollHeight` for all `.page` elements
|
|
227
|
+
8. Verify no 404s in the browser console
|
|
228
|
+
9. Repeat 5–8 until user approves the final copy
|
|
229
|
+
10. **Only after user approval**: capture all print snapshots
|
|
230
|
+
11. User downloads PDF via the Download button
|
|
231
|
+
|
|
232
|
+
Do not capture snapshots during the editing cycle — they are expensive and invalidated by every change.
|
|
233
|
+
|
|
234
|
+
## Print snapshot capture
|
|
235
|
+
|
|
236
|
+
Every page is pre-captured as a PNG that swaps in during print. **Run this only once, after the user approves the final version.**
|
|
237
|
+
|
|
238
|
+
Capture all pages in one pass via Playwright `browser_run_code`:
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
async (page) => {
|
|
242
|
+
await page.evaluate(() => {
|
|
243
|
+
document.querySelector('.download-bar').style.display = 'none';
|
|
244
|
+
document.body.style.overflow = 'hidden';
|
|
245
|
+
});
|
|
246
|
+
const base = '/path/to/property/';
|
|
247
|
+
const cover = page.locator('.cover');
|
|
248
|
+
await cover.scrollIntoViewIfNeeded();
|
|
249
|
+
await cover.screenshot({ path: base + 'cover-print.png', type: 'png' });
|
|
250
|
+
// .page selector includes .cover and .backpage in this template; filter to the middle nine.
|
|
251
|
+
const pages = page.locator('.page:not(.cover):not(.backpage)');
|
|
252
|
+
for (let i = 0; i < await pages.count(); i++) {
|
|
253
|
+
await pages.nth(i).scrollIntoViewIfNeeded();
|
|
254
|
+
await pages.nth(i).screenshot({ path: base + `page${i+2}-print.png`, type: 'png' });
|
|
255
|
+
}
|
|
256
|
+
const back = page.locator('.backpage');
|
|
257
|
+
await back.scrollIntoViewIfNeeded();
|
|
258
|
+
await back.screenshot({ path: base + 'backpage-print.png', type: 'png' });
|
|
259
|
+
await page.evaluate(() => {
|
|
260
|
+
document.querySelector('.download-bar').style.display = '';
|
|
261
|
+
document.body.style.overflow = '';
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Print CSS**: `@page { size: A4 landscape; margin: 0 }` (or `portrait` if overridden). The print stylesheet has two paths:
|
|
267
|
+
|
|
268
|
+
1. **Snapshot path (primary)** — when a `.print-img` has a non-empty `src`, the rule `.page:has(.print-img[src]:not([src=""])) > *:not(.print-img) { display: none }` hides everything else and the snapshot fills the page at `z-index: 9999`. Folios are baked into the snapshot.
|
|
269
|
+
2. **Live-DOM fallback** — if a snapshot is missing or empty, the page prints the live DOM. The folio is forced visible by overriding `.folio` with `z-index: 100`, `bottom: 6mm`, and **dropping `backdrop-filter`** (Chromium's PDF engine strips it, making the pill effectively invisible against dark imagery). The pill background falls back to a solid `rgba(252, 251, 248, 0.95)` for light pages and `rgba(6, 68, 68, 0.78)` for `.folio-dark` pages. The same `z-index` lift is applied to `.back-foot`.
|
|
270
|
+
|
|
271
|
+
**Why both paths matter**: users sometimes hit Cmd+P before snapshots are captured, or the snapshot pipeline fails silently. The fallback guarantees the folio still prints. The `.print-img` elements use `opacity: 0; width: 1px; height: 1px` on screen (not `display: none` — that prevents image loading in some browsers).
|
|
272
|
+
|
|
273
|
+
## PDF deliverable
|
|
274
|
+
|
|
275
|
+
The brochure is shipped as both `brochure.html` and `<property_slug>-brochure.pdf`. The PDF lives alongside `brochure.html` in `<property_dir>/brochure/output/`.
|
|
276
|
+
|
|
277
|
+
### Two-stage generation
|
|
278
|
+
|
|
279
|
+
Browser print-to-PDF is the source of truth, but Chromium re-rasterises CSS background-images at print DPI, producing 30–70 MB outputs. A second pass through Ghostscript at 300dpi recompresses without visible loss.
|
|
280
|
+
|
|
281
|
+
1. **Stage 1 — Chromium print-to-PDF** via Playwright `browser_run_code`:
|
|
282
|
+
- Wait for `networkidle` so all images have loaded
|
|
283
|
+
- Hide `.download-bar`
|
|
284
|
+
- Call `page.emulateMedia({ media: 'print' })`
|
|
285
|
+
- `page.pdf({ path, format: 'A4', landscape: true, printBackground: true, preferCSSPageSize: true, margin: 0 })` for the default landscape folio. Drop `landscape: true` if the brochure has been swapped to portrait.
|
|
286
|
+
2. **Stage 2 — Ghostscript recompression**:
|
|
287
|
+
```
|
|
288
|
+
gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.5 -dPDFSETTINGS=/printer \
|
|
289
|
+
-dNOPAUSE -dQUIET -dBATCH -dDetectDuplicateImages=true \
|
|
290
|
+
-sOutputFile=<final>.pdf <stage1>.pdf
|
|
291
|
+
```
|
|
292
|
+
3. Discard the stage-1 PDF.
|
|
293
|
+
|
|
294
|
+
A typical folio ships at 3–6 MB after recompression. Anything over 10 MB suggests an over-sized image source slipped through optimisation.
|
|
295
|
+
|
|
296
|
+
### When to (re)generate
|
|
297
|
+
|
|
298
|
+
Generate the PDF after every approved layout change. Do not let the PDF drift behind the HTML.
|
|
299
|
+
|
|
300
|
+
## Scope
|
|
301
|
+
|
|
302
|
+
**Included**: HTML brochure, image organisation **and per-slot optimisation**, AI hero prompt, all print snapshots, **PDF deliverable** (Chromium print-to-PDF + Ghostscript recompression), Material Information compliance, EPC chart, disclaimers.
|
|
303
|
+
|
|
304
|
+
**Not included**: Professional photography, actual EPC assessment, legal verification of Material Information values, agent CRM integration, print shop preparation (CMYK, bleed marks), executing the AI hero image prompt.
|
|
305
|
+
|
|
306
|
+
**Depends on**: Playwright MCP for screenshots and PDF emit, local HTTP server for browser preview, A4 print skill for print constraints, `cwebp` (libwebp) for image encoding, `gs` (Ghostscript) for PDF recompression.
|