Branding & the website
Branding is app-owned: the engine ships none, because it has no app to ship. What it does do is make one set of source images serve everything: the app icon, the splash, the web assets, and the game's public website. Nothing is authored twice.
Author the marks in any vector tool and export the PNG sources below; every platform-specific size is generated from them.
Replacing the placeholder artwork
A scaffolded game ships with the EigenInteractive mark and seed colour, so it looks deliberate before you have drawn anything. Swapping in your own is these seven steps, in this order, because the later ones consume what the earlier ones write:
-
Replace the sources in
app/assets/icon/:icon.pngandicon_foreground.pngat 1024 × 1024, andsplash.png/splash_dark.pngif your splash mark differs. See the app icon for what the foreground has to keep clear of. -
Match the colours in
app/pubspec.yaml:flutter_launcher_icons.adaptive_icon_backgroundand everyflutter_native_splashcolour. These cannot read Dart, so nothing keeps them in step with your theme but you. -
Regenerate, from
app/:dart run flutter_launcher_iconsdart run flutter_native_splash:create -
Set the seed in
app/lib/main.dart,Branding(seedColor: …), and, if you have configured asite, itsprimaryColorinserver/src/index.ts. Material 3 rebuilds both schemes from the seed, so this one value is the app's whole palette. -
Draw the share card:
app/web/og-image.pngat 1200 × 630, and fill in the OG tags inapp/web/index.html. It is the only hand-made file in the pipeline. -
Build the web bundle, from the repository root:
pnpm run build:webThis is the step that carries the regenerated icons into the Worker's
public/, which is where the download page and the manifest read them from. Skip it and the website keeps showing the mark it had before. -
Commit the generated files. Launcher icons, splash drawables and web icons are generated once and committed, not rebuilt on each build.
Everything below is the detail behind those steps.
The app icon
Two 1024 × 1024 PNGs in assets/icon/. They are build-time inputs, so they are
not declared under flutter: assets:.
| File | Notes |
|---|---|
icon.png | Full square icon, artwork edge-to-edge, opaque. Used for iOS, macOS, web and the legacy Android icon. iOS rejects alpha, so set remove_alpha_ios: true if the source has any. |
icon_foreground.png | Adaptive-icon foreground: the mark alone on transparent, inside the inner ~66%. Android masks it to a circle or squircle and parallaxes it, so anything near the edge is cropped. Also reused as the splash image. |
dart run flutter_launcher_icons writes the Android mipmaps and adaptive XML,
the iOS/macOS appiconsets, and the web favicon and icons plus the icons array
in manifest.json. It never touches web/index.html, and it does not
generate the notification icon.
The splash
flutter_native_splash: is a top-level pubspec key, not nested under
flutter:. Reusing icon_foreground.png as the splash image keeps the splash
mark and the home-screen icon the same file. Regenerate with
dart run flutter_native_splash:create after any config or asset change.
Two things to know:
- On Android 12+ the
image:key is ignored entirely. The platform builds the splash from the adaptive launcher icon, so theandroid_12:block only sets colours. And-v31is a minimum-version qualifier: that block covers API 31 and everything after, not just Android 12. - Colours cannot read Dart.
color/color_darkmust be kept in sync by hand with the theme surfaces derived fromBranding.seedColor; changing the seed means editing them and regenerating.
For a splash mark that differs from the launcher icon, add
assets/splash/logo.png (plus logo_dark.png) at 1152 × 1152 with artwork
inside the inner 640 px, because the outer ring is cropped by Android 12's circular
mask.
The app's web build
A fresh Flutter app ships template values that fail silently: <title> is the
project name, the description is "A new Flutter project.", and manifest.json
carries Flutter's default #0175C2. Replace all of them.
Flutter's web template also has no Open Graph tags, so a pasted link renders
as a bare URL. Add og:* and twitter:* to <head>, with og:image an
absolute URL at 1200 × 630 (web/og-image.png). A relative og:image is
the usual reason a preview renders blank, since scrapers do not resolve them.
Keep text centred; some clients crop to a square. Verify with the Facebook
Sharing Debugger after deploying, and re-scrape after changes, since both it and Slack
cache hard.
The game's website
The Worker's site block generates the rest of the game's public web presence,
and it consumes exactly the files above, with no second icon set and no
extra artwork:
| Route | What it is |
|---|---|
GET / | Landing page: app icon, name, tagline, screenshots, store buttons |
GET /terms, /privacy, /delete-account | The legal documents |
GET /sitemap.xml, GET /robots.txt | Crawler directives |
GET /site.webmanifest | Web app manifest |
site: {
tagline: "A hidden-information battle of wits for two players.",
primaryColor: "#1a237e",
screenshots: ["1.png", "2.png"], // under public/screenshots/
operator: {
name: "Your Company Ltd",
jurisdiction: "India",
contactEmail: "hello@example.com",
effectiveDate: "1 July 2026",
},
},
The point is that you get a complete, indexable, store-compliant site by configuration. The alternative is every game hand-rolling the same four pages and getting the store requirements subtly wrong.
Absolute URLs in canonical links, OG tags and the sitemap are built from the
request origin, so there is no domain to configure. To keep one canonical
host, disable the workers.dev route in production. Store buttons come from your
deepLink block, so store URLs are configured once. The /download page emits
SoftwareApplication JSON-LD with applicationCategory: "GameApplication".
Every page ends in a footer carrying your copyright, the three legal links, and
a credit line, Built with EigenInteractive, where only the name is a link.
Set madeByCredit to your own string, or to null to drop it:
site: { /* … */ madeByCredit: null },
A custom credit that still names EigenInteractive keeps the link on that word; one that does not renders as plain text. Every link the engine puts on these pages (legal, store buttons, the credit) opens in a new tab, so a visitor mid-download does not lose the page.
Before site is configured
/download serves without it, so a game has a working web/native handoff from
the first deploy. What it does not have is the legal half: those routes are
not mounted, so the footer carries only the credit until you fill in operator.
The page waits for a Flutter web build to reach public/ before it offers a
"Play on the web" button, because / would otherwise bounce straight back to
/download.
Icons are a separate question, and the answer is the same on every page the
engine renders: the browser tab, the download page's logo, and the manifest.
Until your game has icons of its own, all three use the EigenInteractive
mark, served by the Worker and drawn in your primaryColor, exactly as the
Flutter shell defaults to the EigenInteractive seed and credit. It is a
placeholder, not a default. The moment favicon.png exists in public/ the
engine links your icons everywhere and stops serving its own.
That check looks at the icons themselves rather than at the web build, so a
game with no web build still gets its own. pnpm run build:web is the usual
way they arrive; an Android-only game can copy the same files out of app/web/
into public/ and get the identical result.
A game with no web build and no store URLs in deepLink has nothing to offer
at all, and the page says Coming soon. rather than trailing off after the
tagline. That is the state a freshly scaffolded game is in, so seeing it on your
first wrangler dev is correct, not a misconfiguration.
The web asset handoff
The root scaffold's build:web command writes Flutter's complete release
bundle directly into the Worker's public/ directory. The engine's default
paths match the filenames flutter_launcher_icons emits:
| The app generates | Worker asset path | Used for |
|---|---|---|
web/favicon.png | favicon.png | Browser tab |
web/icons/Icon-192.png | icons/Icon-192.png | Manifest, apple-touch-icon |
web/icons/Icon-512.png | icons/Icon-512.png | Manifest |
web/icons/Icon-maskable-192.png | icons/Icon-maskable-192.png | Manifest (maskable) |
web/icons/Icon-maskable-512.png | icons/Icon-maskable-512.png | Manifest (maskable) |
web/og-image.png | og-image.png | Landing-page share card |
Screenshots go under public/screenshots/. og-image.png is the only hand-made
file in the whole pipeline, and the app's own share card already asks for it.
Legal documents
All three default to templates the engine ships. They take your operator block
as typed props, so there are no placeholders to fill in and nothing to keep
in sync, and a mistyped field is a compile error. They describe only what the
engine itself collects: accounts, display names, optional avatars, game
history, ratings, friend connections, push tokens and crash diagnostics.
They are a starting template, not legal advice, and you are the one on the hook for what they say. If you add analytics, advertising, payments, or any other processing, you must edit them. Two lines in particular assume things about your app: the privacy policy's "Diagnostics" bullet assumes crash reporting, and the delete-account steps describe the reference Flutter shell's Settings screen.
To supply your own prose, pass an HTML fragment, body content only, since the engine supplies the shell, styling and footer:
// wrangler.jsonc: lets you import .html files as strings
"rules": [{ "type": "Text", "globs": ["**/*.html"], "fallthrough": true }]
import terms from "./legal/terms.html";
// …
site: { /* … */ legal: { terms } },
Your fragment is inserted as-is, so write your own values into it directly.
The scaffold reserves legal and /download paths with run_worker_first, so
Flutter's SPA fallback cannot shadow them. To replace generated legal prose,
use the typed site.legal fragments above.
Checklist
-
assets/icon/icon.png+icon_foreground.pngat 1024 × 1024, foreground inside the inner ~66% -
flutter_launcher_icons:adaptive background matches the brand → regenerate -
flutter_native_splash:colours match the theme → regenerate - (optional)
ic_notification.xmldeclared to overrideeigen_firebase's default silhouette; notifications work without it -
web/index.html: real title, description and OG/Twitter tags, absoluteog:image;web/og-image.pngat 1200 × 630 -
web/manifest.json: realname,short_name,description,background_color/theme_color -
pnpm run build:webplaces the complete Flutter bundle in Worker assets -
site.operatorfilled in, and the three legal documents actually read - App Links
<intent-filter>carries anandroid:pathPrefixfor both/joinand/game; see Deep links