| name | og |
| description | Share-link previews and app identity for apps on *.grok.me: the og:image card in the root head, the SVG favicon, and PWA icons for installable apps. Use when scaffolding, renaming, or restyling the app — and for share / unfurl / OG / Twitter card questions. A custom 1200×630 card from the app's own art is the default — games of every kind (DOM board/word games included), whimsical apps, creative tools, and brand-forward pages; only plain utilities keep the placeholder. By default run the whole brand-asset pass in a parallel background task (the `task` tool) while the app builds. Triggers on "share", "rename", "app name", "OG", "Open Graph", "twitter card", "unfurl", "og:image", "og:type", "x:game:image", "x-banner", "link preview", "social card", "thumbnail", "preview image", "favicon", "app icon", "PWA", "manifest", "installable", "home screen", "SEO", "meta description".
|
| metadata | {"short-description":"Brand assets: og.jpg card, X feed banner, SVG favicon, PWA icons — parallel task by default"} |
| user-invocable | false |
Share cards, favicon, and app icons
A deployed app (https://{name}.grok.me) unfurls with a 1200×630 card, and
every app (preview included) shows a favicon in the browser tab. Both are
wired in the root head() — AGENTS.md § "First scaffold" is the source of
truth for src/routes/__root.tsx. Extend __root.tsx; never replace it
wholesale (auth SSR, redesign, another skill's excerpt). Dropping og:image
or the favicon link ships a broken card / blank tab icon, and npm run dev /
build will not catch it.
og:type for games (X game-card presentation)
When the app is a game of any kind (Canvas/WebGL and DOM board/word/
puzzle/quiz games), set this meta tag in the root head() always — it
does not need VITE_PUBLIC_HOSTNAME and must not be gated on ogImage:
{ property: "og:type", content: "x:game" },
Why this exists: X (Twitter) uses og:type="x:game" when unfurling
*.grok.me links to present the card as a game rather than a generic
website. This is a product contract with X's card pipeline — keep the tag,
and do not "correct" it to website during refactors. Non-games should omit
og:type or use website (scraper default).
Why og:type content x:game (and not the alternatives)
This will live forever in every shipped game; pick the stable channel:
| Option | Verdict |
|---|
<meta property="og:type" content="x:game"> | Chosen. Stays on the standard OG content-type property; value is a namespaced type (x:game) so it cannot be confused with a future global OGP game type or bare website. X's card pipeline keys off this exact value to present the unfurl as a game. Do not shorten to bare game. |
og:type="game" (no x:) | Rejected. Looks like a global OGP type that does not exist on ogp.me; the product contract is the namespaced x:game string. |
twitter:card | Wrong layer for the game signal. Layout only (summary_large_image). It lives in the first-scaffold __root.tsx and PWA chrome (AGENTS.md) so every app gets it — do not treat this skill as the place that adds it. Never use it as the game type. |
Separate x:type / x:card meta properties | Do not invent. Extra properties double surface area (agents forget one of two tags). Namespacing inside og:type's content (x:game) is enough. |
Example root meta slice for a game (with a custom card once published):
meta: [
{ charSet: "utf-8" },
{ name: "viewport", content: "width=device-width, initial-scale=1" },
{ title: APP_NAME },
{ property: "og:type", content: "x:game" },
...(ogImage
? [
{ property: "og:image", content: ogImage },
{ property: "og:image:width", content: "1200" },
{ property: "og:image:height", content: "630" },
]
: []),
...(xBanner
? [
{ property: "x:game:image", content: xBanner },
{ property: "x:game:image:width", content: "1200" },
{ property: "x:game:image:height", content: "264" },
]
: []),
],
browser-smoke.mjs / brand-check.mjs emit a BRAND WARNING when a
canvas app is missing og:type="x:game" or (once the custom link card is
wired) missing x:game:image / public/x-banner.jpg — treat that as not
done, same as a missing custom card.
Parallelize the brand-asset pass (default)
The identity bundle — public/og.jpg, public/favicon.svg, PWA icons when
installable — needs only the app's name, theme, and palette, and writes only
public/ plus a few head() / manifest lines. Start it as a background
task with the task tool early in the build (AGENTS.md § "Parallel work"),
right after the name and palette are settled: give the task this skill and
sole ownership of public/ brand assets, keep building the app, and
integrate its head() edits at the end (wait_tasks before the final
verify). Image generation is pure waiting — a build that runs the card pass
inline at the end serializes minutes of model latency behind an already
finished app.
Pass the task this skill plus the 16:9 canvas contract below, so the card it
generates crops to 1200×630 without clipping the title.
A default, not a hard rule: stay sequential when the user is art-directing
the card, when the app art the card should reuse doesn't exist yet, or when
the task tool isn't available.
Which card an app gets
Default: generate a custom card. Any app with a face of its own gets one
built from its own art (below):
- Games — every kind and rendering tech. Canvas and WebGL, but equally
DOM-rendered board, card, word, puzzle, and quiz games. A tic-tac-toe grid
made of divs is still a game and still ships a custom card.
- Whimsical, playful, and toy apps — generators, virtual pets, screensavers.
- Creative tools — drawing, music, photo, design, anything whose output is
visual or expressive.
- Content- and brand-forward apps — landing pages, portfolios,
storefronts, anything presenting an identity to visitors.
When in doubt, make the custom card — a themed card is the better unfurl
for anything a user would show someone.
-
Plain utility apps only (converters, invoice/CRUD trackers, internal
dashboards, minimal notes/admin tools — apps whose face is the data, not a
theme) → keep the default og.grok.me card:
https://og.grok.me/v1/card.png?host={VITE_PUBLIC_HOSTNAME}&title={APP_NAME}
Optional theme colour: append &color={RRGGBB} (6-digit hex, no # — e.g.
FF4D2E). The service paints it as the card background under the platform
art, and flips the title and the "Built with Grok" subtitle dark when the
colour is light. Pick the app's primary/brand colour; omit it to keep the
default charcoal card.
plus og:image:width / og:image:height of 1200 / 630. When the user
renames the app, update APP_NAME — it is both the document title and the
painted card title.
Either way: live preview has no VITE_PUBLIC_HOSTNAME, so emit no
og:image at all (text-only unfurl is expected). On publish the platform
injects the hostname — do not write a .env for it. Card pixels update on
the next deploy (the URL is baked into HTML at build time).
Custom card: generate public/og.jpg
For prompt-craft, composition, and blind read-back verification, follow the
imagine and game-asset-core skills — this skill owns the card-specific
contract only (size, lockup, wiring).
-
Set the canvas with aspect_ratio: "16:9". The call looks like
{ "prompt": "…", "aspect_ratio": "16:9" } — ratio words in the prompt do
not set the canvas. At 2mp, 16:9 renders 1792×1008, so the normalize
below cover-crops to 1200×630 trimming only ~3% vertically, which a
centered title survives. A narrower canvas is what kills titles: from 3:2
the same crop takes ~21% vertically, straight through the lockup.
Paths:
- Default — one call with
aspect_ratio: "16:9": imagine_text_to_image
with the art + baked title. Check the output dimensions via
read_file / Pillow on the returned file_path; if the ratio missed, reframe or use
the API path.
- Reframe if needed: pass the prior
file_path into imagine_image_to_image
with aspect_ratio: "16:9" and a prompt like "extend the scenery
left and right into a wider frame; keep the title lettering and
central subject exactly as they are".
- Optional — true 2:1 via the xAI Images API:
POST https://api.x.ai/v1/images/generations with "aspect_ratio": "2:1"
and response_format: "b64_json" using the injected XAI_API_KEY
(see the xai-api skill). 2mp 2:1 is 1984×992; normalize then trims
only ~2.4% per side and nothing vertical.
Build the prompt from the app's theme, palette, and characters. If the
app already has a key generated asset (hero sprite, title scene), pass
its file_path into imagine_image_to_image so the card matches in-game art —
same 16:9 + check-the-output rule applies.
Last resort only (no imagine_text_to_image and no xAI Images API): stay
on whatever canvas you have and keep the entire title block inside the
middle half of the frame height, with the crop-clipping check in
step 6 as the gate.
-
Bake the title in like a game cover. Store-page covers (Stardew Valley,
Cuphead) lead with a short stylized logo-type title. Put the exact app name
in quotes in the prompt; 1–3 strong words; optional short tagline under the
title in smaller lettering (exact words in quotes).
- Stack multi-word titles into a two-line lockup ("SKY" over "STRIKE").
- Center the block both ways with generous margins — avoid "upper third"
/ percentage placement (models hug the edge).
If neither imagine_text_to_image nor the xAI Images API is available,
fall back to the og.grok.me card — never ship a missing or broken
og:image URL.
Regenerate the card when the app's visual identity materially changes (theme
overhaul, new hero art) — and on rename: the title is baked into the pixels,
so update APP_NAME and regenerate the card with the new name. (A titleless
fallback card survives a rename without regeneration.)
X feed card: public/x-banner.jpg (x:game:image, 50:11)
Games also ship a second, much wider card that X uses in the feed (the
link-preview og.jpg is still required — this does not replace it). Write
public/x-banner.jpg at exactly 50:11 (1200×264 — 1200 is the standard
max card width on the web, same as og.jpg) and declare it with the
namespaced property — do not invent x:game:og or overload og:image:
const xBanner = host ? `https://${host}/x-banner.jpg` : undefined;
...(xBanner
? [
{ property: "x:game:image", content: xBanner },
{ property: "x:game:image:width", content: "1200" },
{ property: "x:game:image:height", content: "264" },
]
: []),
Same no-host guard as og:image: live preview has no hostname, so emit no
x:game:image at all. Same JPEG / size discipline (keep it well under
600 KB). Keep x:game:image:width / x:game:image:height at 1200 / 264
the same way og:image keeps 1200 / 630. Generate the art at 50:11
(aspect_ratio: "50:11" on imagine_text_to_image). If the tool schema
does not offer 50:11, generate at the widest offered ratio and cover-crop
— then the vertical crop is severe (16:9 → 50:11 keeps only the middle ~39%
of the height), so the lockup must sit inside that middle band. Either way,
normalize:
ffmpeg -y -i banner-raw.jpg \
-vf "scale=1200:264:force_original_aspect_ratio=increase,crop=1200:264" \
-q:v 4 public/x-banner.jpg
Left-align the content. Unlike the centered og.jpg lockup, compose the
banner with the title lockup in the left half — vertically centered, with
a comfortable left margin — and let the scenery/characters extend to the
right, so the card still reads when feed layouts crop or overlay its right
side. Never edge-hug: a 50:11 frame decapitates edge-hugging lettering even
faster than the 1200×630 card. Reuse the link-cover art when you can
(reframe the same scene wider rather than inventing a second identity).
Verify the 1200×264 JPEG the same way as og.jpg (dimensions + under
600 KB + no clipped title, lockup left of center).
Add the tags to the game meta slice next to og:image — gated on the same
hostname check — only at this point, once public/x-banner.jpg exists.
The first scaffold ships without them; declaring the tag before the file
exists publishes a 404ing image URL.
Favicon: hand-author public/favicon.svg
Every app gets one, and it works in live preview immediately (no host needed).
-
Write the SVG by hand — never imagine_text_to_image. It must stay crisp at 16px:
one bold glyph or shape, flat fills from the app's design tokens, a square
viewBox, a handful of elements at most. For whimsical apps an emoji-text
SVG is a fine quick win:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100" rx="20" fill="#1a1b26"/>
<text x="50" y="50" font-size="62" text-anchor="middle"
dominant-baseline="central">🥕</text>
</svg>
-
Wire it in the root head():
links: [
{ rel: "icon", type: "image/svg+xml", href: "/favicon.svg" },
{ rel: "stylesheet", href: appCss },
],
-
Verify it renders non-blank and legible small (browser tab in a preview
screenshot, or read back a rasterized copy). Update it when the app's theme
or name changes meaning — it is part of the app's identity, not a set-and-forget.
PWA icons: only for installable apps
When the app ships a web manifest (the user asked for installable / PWA /
home-screen behavior — do not invent a manifest just to have icons), add
raster icons derived from the favicon artwork so the identity stays
consistent:
public/icon-192.png and public/icon-512.png — the favicon's glyph on
its tile, rasterized at size. Playwright (baked into the sandbox) can
screenshot the served /favicon.svg at a 192/512 viewport; or redraw the
same mark as a flat PNG. Keep it bold and flat — no photographic detail.
- A maskable variant (
"purpose": "maskable") needs the glyph inside the
center ~80% safe zone so launcher shapes don't clip it.
- Wire the manifest
icons array plus theme_color / background_color
from the app's design tokens, and read the 192 back to confirm it stays
legible.
Not supported
No /api/og route, no runtime image renderer, no per-route cards, no runtime
og:* mutation. The card is one static site-wide image chosen at build time.
If you add robots.txt, never blanket Disallow: / — crawlers must fetch /
to read the meta tags.