# The footer contract

Identical on every site in the family, and present on every page of each site. It is the
only part of a demo that sells directly, so it is a contract rather than a design choice.

Three parts, in this order: the fictional-business disclaimer, the social icons, the
attribution.

## 1. Attribution

```astro
A demo by <a href={attribution.agency.url} target="_blank" rel="noopener">AlphaBlue</a
><span aria-hidden="true" class="px-1.5 text-ink-soft">·</span>Creative direction by <a
  href="https://soft.pw" target="_blank" rel="noopener noreferrer">Luke</a>
```

Note the two `rel` values differ, and the AlphaBlue href comes from the data file rather
than being written inline. Both are deliberate; see the two bullets below.

- **One line, joined by a middot. Never break it with `<br />`.** Two forced lines, left
  aligned, leave the credit stranded against a wide empty footer on any desktop screen — it
  stops reading as a signature and starts reading as leftover markup. Two sites in this
  family shipped that way before anyone looked at a footer on a large display. The one-line
  form is also exactly what the share card sets, so the page and the card now agree.
- **Place it opposite the disclaimer, not beneath it.** From `lg` up the bottom bar is a
  single row — disclaimer left, credit right-aligned; below `lg` it stacks and both go
  left. Split at `lg`, not `sm`: at 640px the two columns are each too narrow for a
  paragraph. And do not put `shrink-0` on the credit — it is a long string, and pinning a
  long string's width is the overflow bug this family keeps rediscovering.
- **The names are the links.** Do not also print the bare URL in parentheses — it is
  redundant once the name is a link, and it looks like a footnote rather than a credit.
- `target="_blank"` on both. **`rel="noopener"` on the AlphaBlue links, `rel="noopener
  noreferrer"` on genuinely third-party ones.** The two tokens do different jobs and get
  pasted together out of habit: `noopener` is the security control, stopping the opened
  page from touching `window.opener`, and it always belongs. `noreferrer` hides *you* from
  a destination you do not control — but the pitch funnel is our own destination, and
  suppressing the referrer there deletes the only attribution signal that survives when
  the destination's analytics does not log query strings. Cloudflare Web Analytics, for
  one, deliberately logs none.
- **Tag every outbound AlphaBlue link with UTM parameters**, so a lead can be traced to
  the demo that produced it without depending on the referrer at all:

  ```
  ?utm_source=<demo-slug>&utm_medium=demo&utm_campaign=alphablue-demos&utm_content=<element>
  ```

  `utm_source` is the demo itself, so it reads straight out of a source/medium report with
  no campaign drill-down. `utm_content` separates the banner at the top of every page from
  the credit at the bottom and from the pitch dialog — a distinction the referrer cannot
  carry, since cross-origin requests send the origin only. Build them through one helper in
  the site's data file rather than by hand at each call site, and annotate the calls
  `/* @__PURE__ */` so a client bundle importing anything else from that module can still
  tree-shake them away.
- Styled quietly — muted body colour on the dark footer surface, underlined — but never
  hidden, never image-baked, never removed.
- **The same credit goes on every share card**, as plain text in the card's footer strip:
  `A demo by AlphaBlue · Creative direction by Luke`. The page footer reaches people who
  scrolled; the card reaches the far larger number who only watched a link go past in a
  chat. Baking the credit into an image is wrong on the page and exactly right there — the
  card is an image either way. See [`share-cards.md`](share-cards.md).

Confirm the two URLs with the user on the first site; they are stable across the family.

## 2. Social icons

Every icon opens the same dialog. **None of them links to a profile**, because the
accounts do not exist — a dead link or, worse, a link to some real stranger's page, is the
fastest way to make a demo look careless.

```
This is a demo site made by AlphaBlue.
Like it? Get yours.        → https://start.alphablueth.com  (new tab)
```

**Choosing the set.** Default is Facebook, Instagram, TikTok, YouTube — offer it, let the
user pick per site, and say what you think fits.

**Plausibility beats completeness.** Ask what a business of this size, in this trade, in
this market would actually run. A platform the business would obviously never maintain
reads as decoration and quietly weakens everything around it; a missing one costs
nothing. Local messaging apps often matter more than global networks, and a
photography-led trade earns Instagram in a way a service trade does not. Ask; do not
assume the default.

**Draw the icons in the site's own icon language** — same stroke weight, same size, same
hover colour as every other icon on the site — rather than pasting solid brand marks in
their official colours. The footer must still look like it belongs to the site.

Minimum 44px touch target. Each icon needs an `aria-label` that says what it does
(`"Facebook — about this demo"`), because it does not do what its shape implies.

## 3. The dialog, in CSS only

The footer is global, so a scripted modal means JavaScript on every route. `:target`
gives the same behaviour with none:

```astro
<a href="#demo-cta" aria-label="Facebook — about this demo">…icon…</a>

<div id="demo-cta" class="demo-cta" role="dialog" aria-labelledby="demo-cta-title">
  <a href="#demo-cta-closed" class="demo-cta__scrim" aria-label="Close" tabindex="-1"></a>
  <div class="demo-cta__panel">…title, body, CTA, Close…</div>
</div>

<style>
  .demo-cta { display: none; position: fixed; inset: 0; z-index: 60;
              align-items: center; justify-content: center; padding: 1rem; }
  .demo-cta:target { display: flex; }
  .demo-cta__scrim { position: absolute; inset: 0; background: rgb(<ink-950> / 0.62); }
  .demo-cta__panel { position: relative; width: 100%; max-width: 26rem; }
</style>
```

Close links point at `#demo-cta-closed`, an id that deliberately does not exist: `:target`
stops matching so the dialog hides, and because nothing matches, the browser does not
scroll anywhere.

Known limitation, worth stating rather than hiding: this is a disclosure, not a true
modal — there is no focus trap and Escape does not close it. That is an accepted trade
for keeping every content page at zero JavaScript. If a site ever genuinely needs a
trapped modal, change the JS budget in `requirements.md` first.

## 4. Verifying it

Do not eyeball this. Read it back:

```js
// every value must equal a token already used elsewhere on the site
getComputedStyle(panel).backgroundColor   // === sand-50
getComputedStyle(panel).borderTopColor    // === the site card border
getComputedStyle(panel).boxShadow         // === 'none'  (borders, not shadows)
getComputedStyle(ctaButton).backgroundColor // === the site's primary button
```

Then confirm, in the built HTML rather than the dev server:

- the attribution renders on **every** route, with both `target="_blank"`
- the JS budget is unchanged — count `<script>` tags per built page
- no social icon resolves to an external profile URL
