---
name: property-socials
description: Use when generating Open Graph / social-share images for a property page built by the real-estate brochure pipeline (estate-agents/<agent>/properties/<slug>/output/web/page.html). Produces landscape, square, and portrait JPGs that match the brochure's brand palette and typography, pulling copy from property.json and hero images already curated for that property. Triggers include "og images for <property>", "social collateral for <listing>", "Instagram tiles for that brochure", or any request to create share-card imagery for a published realagent.chat property page.
---

# Property OG / Social Images

The brochure pipeline already produces a styled property page at
`estate-agents/<agent>/properties/<slug>/output/web/page.html` with curated hero
images under `images/`, branding tokens in `:root` of the page CSS, and
structured metadata in the property's `property.json`. This skill renders
share-ready images by overlaying brand-consistent typography on those hero
shots — it does not re-design anything.

## Inputs the skill consumes (never invents)

- **Palette and fonts** — read from `:root` in `output/web/page.html`. Use the
  exact `--paper / --ink / --gold` tokens and Google Font imports already on
  that page. Do not pick new colours.
- **Copy** — `property.json` is canonical for address, price, qualifier,
  bedrooms/baths/receptions, agent name + phone. The page's
  `<meta name="description">` is the sub-headline. The property's display
  name (`property.json -> property_name`, or the first address line if the
  property has no distinct name) is the headline.
- **Hero images** — `images/<slug>-hero-1-main.webp`, `hero-2-kitchen.webp`,
  `hero-3-garden.webp` are the curated set. Use them by name; do not pick
  numbered shots from the raw set unless the curated heroes are missing.

## Output contract

This skill owns the **full** social-collateral deliverable. Whether invoked directly or through `make-brochure`'s `+socials` add-on, the same files land on disk — no orchestrator-side gallery emission, no split contract.

```
output/web/
  socials/
    og-landscape-<hero>.jpg     # 1200×628  Twitter / LinkedIn / Facebook / OG
    og-square-<hero>.jpg        # 1200×1200 Instagram feed
    og-portrait-<hero>.jpg      # 1080×1350 Instagram portrait, mobile
  socials.html                  # gallery page — the third HTML endpoint in the bundle
  social-posts.md               # paste-ready captions, also linked from socials.html
  og.html                       # template that renders the tiles, kept for re-runs
```

`<hero>` is `main`, `kitchen`, or `garden`. At minimum produce `og-landscape-main.jpg`; offer the full nine-variant set (landscape + square + portrait × main/kitchen/garden) when the user wants social collateral, not just a meta image.

**Gallery page (`socials.html`)** sits at the root of the web bundle, alongside `brochure.html` and `index.html`. It previews each tile at its actual dimensions with download links, the suggested copy from `social-posts.md` inline next to each tile, and the platform's `<meta>` snippet ready to paste into a page `<head>`. Brand it from the same DESIGN.md tokens used for the brochure — same `:root` palette, same display family, same accent.

**Captions (`social-posts.md`)** are written one section per image, ready to paste. Every caption is generated through the `plainly` skill's
register: no em-dashes, no inflation vocabulary (*discover*, *unlock*,
*nestled*, *stunning*, *boasts*, *bespoke* where it isn't literally bespoke),
no antithetical "it's not X, it's Y", no rhetorical openers (*Imagine…*,
*Picture this…*). Read each draft aloud; if it sounds like an estate-agent
blurb-bot rather than a person, rewrite it.

Per-platform shape (these are caps, not minimums):

- **Landscape post (Twitter / X)**: under 280 characters. One sentence of
  scene, one of specs, one of agent + phone. Link as last token.
- **Landscape post (LinkedIn / Facebook)**: three short paragraphs. Opening
  line that names the property and locality. A middle paragraph of concrete
  features taken from `property.json`'s `key_features`. A closing paragraph
  with price, agent name, phone, listing URL.
- **Square (Instagram feed)**: property name + locality on line one, two or
  three lines of concrete features, price, agent + phone, "link in bio", four
  to six hashtags drawn from locality + property type + agency.
- **Portrait (Instagram portrait / Reels cover)**: lead with what is visible
  in *that specific hero* (exterior, kitchen, or garden), not the generic
  property summary. Three lines maximum. Price + agent line at the bottom.

Copy sources, in order: `property.json` for facts (address, price, beds, baths,
acreage, key features); the page's `<meta name="description">` for tone of the
hero sub-line; the brochure folder display name for the headline. Do not invent
features that are not in those sources.

## How rendering works

A single `og.html` template lives at `output/web/og.html` alongside `page.html`
so it can use relative `images/` paths. It accepts `?format=landscape|square|
portrait` and `?hero=main|kitchen|garden`, applies the matching CSS class with
explicit min/max width and height (no relative units), and sets the background
hero image from the param. Playwright screenshots the URL at exactly the
container's pixel size.

Critical layout rules:

- The stage div fixes its own width and height with `width / minWidth / maxWidth`
  (and the height triple). Without max constraints the viewport bleeds.
- Hero fills the stage with `background-size: cover; background-position: center`
  so the same template works across all three aspect ratios.
- A vertical gradient scrim (transparent → ink 0.85) sits between hero and copy.
  On bright daytime shots the scrim's bottom stop must reach ≥ 0.85 or the
  eyebrow/sub copy loses contrast.
- Copy block sits in the bottom-left: eyebrow (locality, gold), headline
  (display serif), sub (italic body serif), thin rule, specs row, footer line
  with `realagent.chat / <agent>` left and `<agent name> · <phone>` right.

## Generation flow

1. Confirm `output/web/og.html` is in sync with the page's `:root` palette and
   `property.json` copy. If the property page has been re-themed since the last
   render, update the template before screenshotting.
2. Start a static server in `output/web/` on any free port:
   `python3 -m http.server <port> &`. Hold the PID so it can be killed at the
   end. Relative `images/` paths require a real HTTP origin; `file://` won't
   render webfonts either.
3. On a fresh machine, `npx -y playwright install chromium` once. The first
   `playwright screenshot` invocation otherwise fails with "Executable doesn't
   exist".
4. Screenshot each variant. Pass `--wait-for-timeout=2500` so Google Fonts
   land before capture. Always quote URLs that contain `&`.
5. Stop the server. Verify each JPG with `Read` so the user sees the rendered
   collateral inline — file sizes alone aren't proof of a good render.

## Failure modes seen on first use

- **Filename mangling.** A bash loop using `for v in "landscape main" …; set --
  $v` collapses to filenames like `og-landscape main-.jpg` (space, then dash).
  Use an explicit per-variant call or an array; never rely on positional
  splitting for two-word tuples.
- **6 KB screenshots.** A screenshot under ~20 KB is a blank page. Common
  causes: server not running, wrong port, Chromium not installed, or `og.html`
  threw a JS error. Open the URL in a browser to confirm before re-running.
- **Eyebrow on a light surface.** The garden / pool hero washes the eyebrow
  out. Either deepen the scrim's bottom stop or move the eyebrow above the
  hero crop on that variant. Don't ship a low-contrast tile.
- **Treating this as a Next.js task.** The brochure site is static HTML. The
  upstream `og-images` skill assumes `app/og/page.tsx`; ignore that scaffold
  and render against the static page directly.
- **Captions that sound like AI marketing.** "Discover this stunning home
  nestled in the heart of Essex" is the failure mode. Strip *discover*,
  *unlock*, *nestled*, *stunning*, *boasts*, *exquisite*, *charming*, every
  em-dash, every exclamation mark used to fake enthusiasm. The aloud test from
  the `plainly` skill applies: read the caption out; if you would not say it
  in conversation to a buyer, rewrite it.

## Out of scope

- The brochure page itself (owned by `real-estate-brochure:property-brochure`).
- Property metadata extraction (owned by
  `real-estate-brochure:property-extract`).
- Wiring `<meta property="og:image">` into `page.html`. Surface the snippet for
  the operator; the page is regenerated by a separate skill and edits there
  belong in that pipeline, not this one.
- **JSON-LD / schema.org structured data on `page.html`.** Emitted upstream by
  `property-brochure` via the `aeo-emit-jsonld` MCP tool — the
  `{{ jsonld_script_block }}` token lives in the brochure's `references/index.html`
  and is filled at brochure-render time. `property-socials` does not produce any
  indexable HTML page (`socials.html` is operator-only collateral; `og.html` is a
  screenshot template), so there is nothing to inline here.
