Parva Machina
// COMPONENT STANDARD

Shared components

Every game has its own site, its own stack and its own palette. The publisher is the only thing they share — this page both describes the components that make that visible and hands them over, working.

The previews on this page run on the very file you would copy. If a preview looks wrong, the standard is wrong.

// 00 — THE CONTRACT

Token contract

The component knows nothing about the host's tokens. Three variables are required; everything else derives from them. A host writes the mapping once and gets all four components.

Required
  • --pmf-bgthe footer’s ground
  • --pmf-fgprimary ink; the hairlines derive from it
  • --pmf-accentthe game’s signature — not brass
Derived
  • --pmf-fg-mutedcolor-mix(in oklab, fg 72%, bg)
  • --pmf-fg-subtlecolor-mix(in oklab, fg 56%, bg)
  • --pmf-surfacecolor-mix(in oklab, fg 4%, bg)
  • --pmf-line-softfg @ 9%
  • --pmf-linefg @ 13%
  • --pmf-line-strongfg @ 18%
  • --pmf-line-tickfg @ 32%
  • --pmf-radius0
  • --pmf-hover-shadownone
  • --pmf-focus-shadownone
  • --pmf-font-sansinherit
  • --pmf-font-monoui-monospace, …
  • --pmf-scale1
  • --pmf-maxw1180px
Locked
  • --pmf-brass#c8892f
  • .pmf__pm-plate#131211 / #e8e3d8 / radius 0
Host mappingglobals.css
/* Once, in the game's own stylesheet. */
.pmf,
.pmf-topbar,
.pmf-badge,
.pmf-legalbar {
	--pmf-bg: var(--sac-bg);
	--pmf-fg: var(--sac-fg);
	--pmf-accent: var(--sac-success);
	--pmf-radius: var(--sac-radius);
	--pmf-font-sans: var(--sac-display);
	--pmf-font-mono: var(--sac-mono);

	/* Opaque mode: --sac-border is a colour, not an alpha, so all steps are set. */
	--pmf-line-soft: #16211c;
	--pmf-line: var(--sac-border);
	--pmf-line-strong: #2a3b33;
	--pmf-line-tick: #3d5449;

	/* Shadow marks interaction, never presence. */
	--pmf-hover-shadow: var(--sac-glow-green);
	--pmf-focus-shadow: var(--sac-glow-green);
}

Map by reference, never by value. Custom properties resolve at use time, so a reference makes the host's theme variants flow into the footer on their own. A literal freezes it in the default theme — and it looks perfectly fine until someone flips the switch.

// 01 — FOOTER

Footer — 5·3·4

The full statement. The game's identity across five columns, the two menus across three, the publisher across four; copyright and legal links beneath.

Anatomy
≥ 1001px5 · 3 · 4
game
discover · network
Parva Machina
copyright
legal
701 – 1000px6 · 3 · 3 + 12
game
discover
network
Parva Machina
copyright
legal
≤ 700px1
game
discover
network
Parva Machina
copyright
legal
// 02 — TOP BAND

Top band

A hairline strip above the game's own header. Deliberately quiet: it names the publisher and gets out of the way.

Theme
// 03 — BRAND BADGE

Brand badge

One line, for under a hero or beside a title. At this size the mark carries it, not the wordmark.

Theme
// 04 — LEGAL STRIP

Legal strip

The copyright line on its own, for pages the full footer would overwhelm. Identical treatment to the bar inside it.

Theme
© 2026 Parva Machina
// 05 — INSTALLING

Wiring it into a game

Copy the files, map the tokens, build the data from your own i18n and path builders. The standard never constructs a URL.

Data schemapmf-types.ts
/*
 * pmf — the data contract, v1.
 *
 * ONE object. The hardest constraint on it, and the one everything else falls
 * out of: `PmFooterData` is 100% JSON-serializable. Anything that is not — an
 * SVG node — travels through a named slot instead, never through the object.
 *
 * That constraint is what lets the same shape be produced by a react-i18next
 * call, by MDX frontmatter, or by a build script reading remote JSON. It is
 * also what lets the reference DOM be generated from a fixture file, which is
 * how three renderers are held to one output.
 *
 * ── Resolved strings only ───────────────────────────────────────────────────
 * Every human-readable field is a resolved string. No translation keys, no
 * `t()` inside the component, no i18n dependency of any kind.
 *
 * This is the only shape that works across the network: SilkWard and SysAdmin
 * Crisis resolve at runtime against a loaded react-i18next namespace, while
 * Regnarium resolves at build time against a content tree. A key-based schema
 * would force one of them to invent a dictionary it does not have.
 *
 * ── The standard never builds a URL ─────────────────────────────────────────
 * Every `href` arrives ready. This is `path.*` from src/i18n/index.ts applied
 * across repositories: each site's own path builder stays the single source of
 * truth and the footer is purely a consumer.
 *
 * It is also what makes the route divergence a non-problem. The footer never
 * learns that SilkWard and Regnarium use `[locale]` while SysAdmin Crisis uses
 * `[lang]`, or that two sites put legal pages at the root while the third
 * nests them under `legal/`.
 */

export interface PmFooterData {
	version: 1;
	/** BCP-47. Used for `hreflang` on cross-origin links and for `dir` — never for copy. */
	locale: string;
	dir?: 'ltr' | 'rtl';

	game: GameIdentity;
	socials: SocialLink[];
	stores: StoreLinks;
	discover: MenuColumn;
	network: NetworkColumn;
	studio: StudioBlock;
	legal: FooterLink[];
	/** Fully resolved, year already interpolated at build. See the note in README.md. */
	copyright: string;
	a11y: A11yStrings;
}

export interface GameIdentity {
	/**
	 * Must match an id in the network registry.
	 *
	 * This is checked as a BUILD ERROR, not a warning — see `checkNetwork()` in
	 * README.md. A misspelt id makes the self-exclusion filter match nothing, so
	 * the game links to itself in its own footer, with a link that works
	 * perfectly and therefore never gets reported.
	 */
	id: string;
	name: string;
	/** The game's own home — where the wordmark links. */
	href: string;
	/** Two or three sentences. Resolved. */
	description: string;
	wordmark: Wordmark;
}

/**
 * Three variants, and the choice between them is not stylistic.
 *
 * - `text`   — SilkWard and SysAdmin Crisis have no logo asset. This IS the logo.
 * - `img`    — a same-origin path, legal under `img-src 'self'`. The right form
 *              for Regnarium's `brand/lockup-yatay.svg`: cacheable, no markup
 *              weight. `width`/`height` are mandatory, against layout shift.
 * - `slot`   — metadata only; the `<svg>` arrives through the `game-logo` slot.
 *              Use this and only this when the mark must inherit `currentColor`.
 *              The data object stays serializable because the node never enters it.
 */
export type Wordmark =
	| { kind: 'text' }
	| { kind: 'img'; src: string; width: number; height: number; alt: string }
	| { kind: 'slot'; width: number; height: number; title: string };

/**
 * Icons are hand-drawn on the 26px grid at 1.2px stroke — the same convention as
 * `src/components/schematic/PrincipleIcon.astro`. They ship as an inline sprite
 * emitted by the component itself.
 *
 * Not an icon font, not a CDN package, and deliberately NOT an external
 * `<use href="/sprite.svg#id">`: external `use` is subject to fetch rules, and
 * every site in this network runs `default-src 'self'`.
 */
export type SocialPlatform =
	| 'x'
	| 'bluesky'
	| 'mastodon'
	| 'discord'
	| 'github'
	| 'youtube'
	| 'itch'
	| 'reddit'
	| 'steam'
	| 'linkedin'
	| 'email'
	| 'rss';

export interface SocialLink {
	platform: SocialPlatform;
	href: string;
	/** Resolved, becomes the accessible name — "SilkWard on Bluesky", not "Bluesky". */
	label: string;
	/** Defaults to `me noopener`. */
	rel?: string;
}

/**
 * Either store may be absent and the row collapses; both absent and the block is
 * omitted with no reserved gap.
 *
 * Badges resolve to `${basePath}appstore-${badgeLocale ?? locale}.svg` and
 * `${basePath}googleplay-${badgeLocale ?? locale}.svg`. Both vendors publish
 * localised artwork, and under `img-src 'self'` every locale you use must be
 * committed locally.
 *
 * `badgeLocale` is separate from `locale` because Apple and Google use different
 * locale code sets, and neither matches your route params.
 */
export interface StoreLinks {
	ios?: { href: string; badgeLocale?: string };
	android?: { href: string; badgeLocale?: string };
	/** Defaults to `/standard/`. */
	basePath?: string;
}

export interface MenuColumn {
	/** Column heading. Resolved. */
	label: string;
	links: FooterLink[];
}

export interface FooterLink {
	href: string;
	label: string;
	external?: boolean;
	rel?: string;
	/** Renders `aria-current="page"`. */
	current?: boolean;
}

export interface NetworkColumn {
	label: string;
	items: NetworkItem[];
	/** Resolved marker for an unreleased sibling. Required if any item is `soon`. */
	soonLabel?: string;
}

export interface NetworkItem {
	id: string;
	name: string;
	href: string;
	/**
	 * The sibling game's own signature colour, rendered as a 6px dot — the one
	 * place a foreign hue is allowed inside the footer.
	 */
	signature?: string;
	/** `soon` renders a muted mono marker instead of a live link treatment. */
	status?: 'live' | 'soon';
}

/**
 * Almost all constant, so almost all of it is a package export rather than host
 * data — see `PM_STUDIO` in `pmf-data.ts`. Only the blurb and the two labels are
 * per-locale, and therefore only they live here.
 */
export interface StudioBlock {
	/** One or two sentences. Resolved. */
	blurb: string;
	/** Resolved label for the link to the Parva Machina about page. */
	aboutLabel: string;
	/** Resolved eyebrow above the lockup. Defaults to `// PARVA MACHINA`. */
	eyebrow?: string;
	/**
	 * Which Parva Machina locale to link to.
	 *
	 * Parva Machina ships `tr` and `en`; a game may carry locales it does not.
	 * Resolve with `pmLocaleFor()`, which maps by primary subtag and falls back
	 * to `en` — never link a visitor to a 404 to preserve a locale prefix.
	 */
	pmLocale?: string;
}

export interface A11yStrings {
	footerLabel: string;
	discoverLabel: string;
	networkLabel: string;
	legalLabel: string;
	socialsLabel: string;
	pmSocialsLabel: string;
}
SourceSiteFooter.tsx
import { PmFooter } from '@/standard/PmFooter';
import { assertKnownGame, selectNetwork } from '@/standard/pmf-data';
import registry from '@/standard/network.json';

assertKnownGame(registry, 'sysadmin');

export function SiteFooter({ lang }: { lang: string }) {
	const { t } = useTranslation();

	return (
		<PmFooter
			data={{
				version: 1,
				locale: lang,
				game: {
					id: 'sysadmin',
					name: 'SysAdmin Crisis',
					href: localePath(lang, ''),
					description: t('site.footer.pitch'),
					wordmark: { kind: 'text' },
				},
				socials: [{ platform: 'github', href: '…', label: t('site.social.github') }],
				stores: {},
				discover: {
					label: t('site.footer.discover'),
					links: [
						{ href: localePath(lang, 'about'), label: t('site.nav.about') },
						{ href: localePath(lang, 'contact'), label: t('site.nav.contact') },
					],
				},
				network: {
					label: t('site.footer.network'),
					soonLabel: t('site.footer.soon'),
					items: selectNetwork(registry, 'sysadmin'),
				},
				studio: {
					blurb: t('site.footer.studio'),
					aboutLabel: t('site.footer.studio_about'),
				},
				legal: LEGAL_LINKS.map((l) => ({
					href: localePath(lang, l.path),
					label: t(l.key),
				})),
				copyright: t('site.footer.rights', { year: BUILD_YEAR }),
				a11y: { /* … */ },
			}}
		/>
	);
}
pmf.csssrc/standard/pmf.css
/*
 * pmf — Parva Machina brand footer standard
 * v1.0.0
 *
 * ONE file. Plain CSS, zero dependencies, zero build step. Copy it verbatim
 * into a game site and map the token contract; nothing else is required.
 *
 * This file is the standard. The `/components` page on parvamachina.com does
 * not re-express it in Tailwind — it imports THIS file and prints THIS file's
 * source. If the preview looks wrong, the standard is wrong. Drift is
 * structurally impossible, which is the only reason a standard survives four
 * repositories and one maintainer.
 *
 * ── The contract ────────────────────────────────────────────────────────────
 * Three variables are required. Everything else derives from them:
 *
 *     .pmf { --pmf-bg: …; --pmf-fg: …; --pmf-accent: …; }
 *
 * Map them BY REFERENCE, never by value:
 *
 *     .pmf { --pmf-accent: var(--sac-success); }   ← correct
 *     .pmf { --pmf-accent: #5ef08a; }              ← wrong
 *
 * Custom properties resolve at use time, so a reference makes every one of the
 * host's theme variants (`data-theme`, `data-contrast`, `data-font`) flow into
 * the footer for free. A literal freezes the footer in the default theme — and
 * it looks perfectly fine until someone flips the switch, which is why this is
 * the first thing written down.
 *
 * ── Namespace ───────────────────────────────────────────────────────────────
 * `pmf-`, not `pm-`: parvamachina.com already owns `.pm-nisan`, `.pm-cetvel`
 * and `.pm-izgara`, and SysAdmin Crisis owns `sac-`. Every selector here is a
 * single class, unlayered, and nothing outside `.pmf` is ever targeted.
 */

:where(.pmf, .pmf-topbar, .pmf-badge, .pmf-legalbar) {
	/* ── Tier 1: required ───────────────────────────────────────────────── */
	--pmf-bg: #131211;
	--pmf-fg: #e8e3d8;
	--pmf-accent: #c8892f;

	/* ── Tier 2: derived, all overridable ───────────────────────────────── */
	--pmf-fg-muted: color-mix(in oklab, var(--pmf-fg) 72%, var(--pmf-bg));
	--pmf-fg-subtle: color-mix(in oklab, var(--pmf-fg) 56%, var(--pmf-bg));
	--pmf-surface: color-mix(in oklab, var(--pmf-fg) 4%, var(--pmf-bg));
	--pmf-accent-contrast: var(--pmf-bg);

	/*
	 * The hairline ramp. parvamachina.com carries eight steps because that page
	 * has eight kinds of surface; a footer has three — inner dividers, block
	 * separators, cell edges.
	 *
	 * ALPHA MODE (default): the ramp is one ink plus three scalars. With the
	 * default ink and alphas, `--pmf-line` resolves to exactly
	 * rgb(232 227 216 / 0.13) — the STANDARD hairline in global.css, byte for
	 * byte. The default state of this file IS the Parva Machina design.
	 *
	 * OPAQUE MODE (opt-in): a host whose border token is an opaque colour —
	 * SysAdmin Crisis's `--sac-border: #1e2b25` — must set all THREE steps
	 * explicitly. Do NOT derive them by mixing toward the ground: mixing a
	 * translucent colour with an opaque one RAISES alpha, so `line-soft` comes
	 * out heavier than `line`. That reads as "almost right", which is worse
	 * than broken. Setting fewer than three steps is a spec violation.
	 */
	--pmf-line-ink: var(--pmf-fg);
	--pmf-line-a-soft: 0.09;
	--pmf-line-a: 0.13;
	--pmf-line-a-strong: 0.18;

	--pmf-line-soft: color-mix(in srgb, var(--pmf-line-ink) calc(var(--pmf-line-a-soft) * 100%), transparent);
	--pmf-line: color-mix(in srgb, var(--pmf-line-ink) calc(var(--pmf-line-a) * 100%), transparent);
	--pmf-line-strong: color-mix(in srgb, var(--pmf-line-ink) calc(var(--pmf-line-a-strong) * 100%), transparent);
	/* The registration-mark / hover-edge step. Opaque mode must set this too. */
	--pmf-line-tick: color-mix(in srgb, var(--pmf-line-ink) 32%, transparent);

	/*
	 * Brass. LOCKED — not part of the host mapping, not overridable.
	 *
	 * It governs exactly four things, all inside the Parva Machina column: the
	 * mark's frame and centre dot, the mono eyebrow, the about-link underline,
	 * and the network heading tick. A publisher's mark that recolours per host
	 * is not a mark, it is decoration.
	 *
	 * It is also QUARANTINED to that column, so it reads as a colophon stamped
	 * onto the page rather than as a second accent leaking through the footer.
	 * Contrast is fine where it matters: #c8892f on #07090b is 6.72:1.
	 */
	--pmf-brass: #c8892f;

	/* ── Shape ──────────────────────────────────────────────────────────── */
	/*
	 * Radius is INHERITED — a host with rounded corners keeps them, on the four
	 * elements listed at the bottom of this file. It never reaches hairline
	 * rules, grid gaps, or store badges (see `.pmf__store img`).
	 */
	--pmf-radius: 0;

	/*
	 * Shadow is REFUSED at rest, on every host. Depth here is line weight and
	 * ground tone. A glowing footer competes with the game above it, and every
	 * glow a host owns is tuned for a different surface. The two hooks below
	 * are interaction-only: shadow marks interaction, never presence.
	 */
	--pmf-hover-shadow: none;
	--pmf-focus-shadow: none;

	/* ── Type island ────────────────────────────────────────────────────── */
	/*
	 * `.pmf` sets its own font sizes. It cannot use the `.t-*` ramp, which
	 * exists only on parvamachina.com. Every step routes through `--pmf-scale`
	 * so a host's font-size preference reaches all six at once.
	 */
	--pmf-font-sans: inherit;
	--pmf-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
	--pmf-scale: 1;

	--pmf-t-wordmark: calc(14px * var(--pmf-scale));
	--pmf-t-body: calc(14.5px * var(--pmf-scale));
	--pmf-t-link: calc(14px * var(--pmf-scale));
	--pmf-t-label: calc(11px * var(--pmf-scale));

… + 866

PmFooter.tsxsrc/standard/PmFooter.tsx
/*
 * pmf — the React reference implementation.
 *
 * For the three Next.js game sites. Server-component safe: no hooks, no state,
 * no effects, no `'use client'`. It renders in the RSC pass and ships zero
 * JavaScript, which matters on sites whose whole claim is that they make no
 * requests after the first load.
 *
 * ── Installing ──────────────────────────────────────────────────────────────
 *   1. Copy this file, `pmf.css`, `pmf-types.ts`, `pmf-data.ts`, `pmf-icons.ts`
 *      into the game's own tree.
 *   2. `import './pmf.css'` once, in the root layout.
 *   3. Write the token mapping — see README.md §2.
 *   4. Build `PmFooterData` from your own i18n and path builders.
 *
 * ── The one rule that will bite ─────────────────────────────────────────────
 * This file is a MIRROR of `pmf-render.ts`, which is normative. If you change
 * the markup here, change it there, and run `npm run standard:check` on
 * parvamachina.com — it diffs the two and fails on any difference. Fixing a bug
 * in one renderer and not the other is how a standard quietly stops being one.
 */
import type { JSX } from 'react';
import type { FooterLink, PmFooterData, SocialLink } from './pmf-types';
import { PMF_ARROW, PMF_MARK, PM_STUDIO, pmLocaleFor } from './pmf-data';
import { PMF_ICONS } from './pmf-icons';

export interface PmFooterProps {
	data: PmFooterData;
	/**
	 * The inline `<svg>` for a `wordmark.kind === 'slot'` logo — the only case
	 * where the mark must inherit `currentColor`. Everything else goes through
	 * the data object.
	 */
	gameLogo?: JSX.Element;
}

function Sprite({ data }: { data: PmFooterData }) {
	const ids = [
		...new Set([
			...data.socials.map((social) => social.platform),
			...PM_STUDIO.socials.map((social) => social.platform),
		]),
	];
	return (
		<svg className="pmf__sprite" aria-hidden="true">
			{ids.map((id) => (
				<symbol
					key={id}
					id={`pmf-i-${id}`}
					viewBox="0 0 26 26"
					fill="none"
					stroke="currentColor"
					strokeWidth={1.2}
					dangerouslySetInnerHTML={{ __html: PMF_ICONS[id] }}
				/>
			))}
		</svg>
	);
}

function Socials({ socials, label }: { socials: readonly SocialLink[]; label: string }) {
	if (socials.length === 0) return null;
	return (
		<ul className="pmf__socials" role="list" aria-label={label}>
			{socials.map((social) => (
				<li key={social.href}>
					<a
						className="pmf__social"
						href={social.href}
						rel={social.rel ?? 'me noopener'}
						aria-label={social.label}
					>
						<svg aria-hidden="true">
							<use href={`#pmf-i-${social.platform}`} />
						</svg>
					</a>
				</li>
			))}
		</ul>
	);
}

function LinkItems({ links }: { links: FooterLink[] }) {
	return (
		<>
			{links.map((link) => (
				<li key={link.href}>
					<a
						href={link.href}
						rel={link.rel ?? (link.external ? 'noopener' : undefined)}
						aria-current={link.current ? 'page' : undefined}
					>
						{link.label}
					</a>
				</li>
			))}
		</>
	);
}

function Wordmark({ data, gameLogo }: { data: PmFooterData; gameLogo?: JSX.Element }) {
	const mark = data.game.wordmark;
	if (mark.kind === 'img') {
		return (
			<img
				className="pmf__logo"
				src={mark.src}
				alt={mark.alt}
				width={mark.width}
				height={mark.height}
			/>
		);
	}
	if (mark.kind === 'slot' && gameLogo) return gameLogo;
	return <span className="pmf__wordmark">{data.game.name}</span>;
}

function Stores({ data }: { data: PmFooterData }) {
	const { ios, android } = data.stores;
	if (!ios && !android) return null;

… + 174

READMEsrc/standard/README.md
# pmf — Parva Machina brand components

Four components that make the publisher visible on a game's own site, in a form
that survives four repositories, three stacks and three unrelated palettes.

| Class | What | Where |
| --- | --- | --- |
| `.pmf` | the 5·3·4 footer and its copyright bar | game landing page, bottom |
| `.pmf-topbar` | a hairline publisher strip | above the game's own header |
| `.pmf-badge` | one-line signature | under a hero, beside a title |
| `.pmf-legalbar` | the copyright line alone | where the full footer is too heavy |

## 1. Copy

```
pmf.css          the standard — every visual decision is here
pmf-types.ts     the data contract
pmf-data.ts      constants and the three helpers
pmf-icons.ts     the hand-drawn social set
pmf-render.ts    the normative renderer (framework-free)
PmFooter.tsx     the React mirror, for the Next.js sites
```

Copy all six. Then `import './pmf.css'` once, in the root layout.

## 2. Map the tokens

Three variables are required; everything else derives. Write the mapping once:

```css
.pmf,
.pmf-topbar,
.pmf-badge,
.pmf-legalbar {
	--pmf-bg: var(--your-ground);
	--pmf-fg: var(--your-ink);
	--pmf-accent: var(--your-signature);
}
```

**By reference, never by value.** Custom properties resolve at use time, so a
reference makes your theme variants — `data-theme`, `data-contrast`,
`data-font` — reach the footer with no extra rules. A literal freezes it in the
default theme, and it looks perfectly fine until someone flips the switch.

If your border token is an **opaque colour** rather than an alpha, set all four
hairline steps explicitly: `--pmf-line-soft`, `--pmf-line`, `--pmf-line-strong`,
`--pmf-line-tick`. Do not derive them by mixing toward the ground — mixing a
translucent colour with an opaque one raises alpha, so `line-soft` comes out
heavier than `line`.

## 3. Build the data

`PmFooterData` takes **resolved strings**, never translation keys, and every
`href` arrives ready. The standard never constructs a URL: your own path builder
stays the single source of truth, which is why route shapes can differ across
the network without the footer ever learning about it.

## 4. Wire the network

```bash
node scripts/sync-network.mjs   # prebuild
```

The script fetches `https://parvamachina.com/network.json`, writes a gitignored
generated copy, and leaves the committed `network.fallback.json` in place as the
offline path. A failed fetch is a warning, never a failed release.

`assertKnownGame(registry, ownId)` is the one hard failure in the standard, and
it is deliberate: a misspelt id makes self-exclusion match nothing, so the game
links to itself in its own footer with a link that works perfectly — and is
therefore never reported. A stale sibling tagline is invisible; a self-link is
an embarrassment.

## 5. Store badges

Commit Apple's and Google's official artwork per locale to your own `public/`
directory — `img-src 'self'` rules out hot-linking. Name them
`appstore-<locale>.svg` and `googleplay-<locale>.svg`.

**Render them untouched.** No radius, no filter, no opacity, no recolour. Apple's
guidelines mandate a minimum size and clear space; Google's mandate no
modification. `--pmf-radius` deliberately does not reach the image, because
inheriting a host radius onto an Apple badge is exactly the accident the rule
exists to prevent.

## 6. What is yours, and what is not

**Yours.** Every token in the contract. Which components you use. Which links go
in either menu. Whether the store row exists at all.

… + 11