Canvas Design System
Main Site Tokens

App Footer

How an app page ends, in two weights: the 24px stone rail, or the app frame's beta bar — both in place of the marketing footer.

When to Use

Use when: Never, by hand — and that's the point. Any page under one of the paths in WS_APP_PATHS gets this automatically, whichever way it reaches the footer. Call ws_app_footer() directly only to force the rail on a page outside those paths.
Don't use when: You're ending a content or marketing page. Those close on the Footer composite — the Level Up grid and the newsletter CTA are how those pages earn their keep, and stripping them costs signups.

Variants

band (default) is the 24px rail: one height, one colour, no content. A tool page should end quietly, and anything configurable there would be an invitation to put marketing back.

<?= ws_app_footer() ?>

full is the consolidated shell's frame bar, from the app wireframes: a 56px --gray-500 band carrying the wordmark, the 2.0 Beta pill and the build version (read live from the changelog's newest entry) on the left, and Send feedback / Resources / Company on the right. An Install the app action sits hidden before them, revealed only when Chrome fires beforeinstallprompt — the installed app is the one window a browser draws without a URL row, so the bar offers it exactly when the browser permits (Safari and Firefox never fire the event and never see the button). Send feedback opens a ws_modal wired to api/contact.php; Resources and Company open small link panels above their buttons, carrying the same links as the marketing footer's columns. The launcher has no footer entry point — the rail's More button is its one way in. The composite's automatic routing calls with no arguments, so the shell picks this variant through $GLOBALS['ws_app_footer_options'] before the footer runs. Full-bleed tool steps take no footer at all (DESIGN.md §12.7).

// app/index.php — before includes/footer.php runs
$GLOBALS['ws_app_footer_options'] = ['variant' => 'full'];

How a page gets it

Every route to a footer — ws_footer(), a direct components/footer.php include, or the legacy includes/footer.php — passes through the composite template. That template runs one test and delegates, so no page needs per-page wiring and no app can drift.

// includes/components/footer.php
if (ws_is_app_page()) {
    include __DIR__ . '/app-footer.php';
    return;
}

ws_is_app_page() matches the first segment of the request path against WS_APP_PATHS, which mirrors the app inventory in PRODUCT.md §5:

PhasePaths
Learnlibrary, academy, tips, podcasts
Planplanner, agenda-check, toolkits, myworkshopr
Facilitatefacilitator, sidekick, intervention
Reflectdna, synthesize, coach
Otherstrategist, admin

Adding an app means adding its path there and nothing else. Don't add a marketing page that happens to live under an app directory.

States

StateBehavior
First renderDefines WS_GLOBAL_FOOTER_RENDERED, emits the token link, the style block, the rail, and the toast/auth functional core.
Subsequent rendersThe shared guard returns early. A page gets one footer of one kind — whichever composite reaches the guard first wins.
Short pageThe rail sits in normal flow, so on a page shorter than the viewport it lands under the content rather than at the window bottom. It does not stick.
Fixed-viewport pageNot rendered. The Planner builder, facilitator/app.php and synthesize/app.php are 100vh workspaces — 24px more would buy a scrollbar on a canvas designed not to scroll, and the rail would sit below the fold. Those pages end at </body>.

Options

The helper takes no arguments. If you find yourself wanting one, the page probably wants the full Footer instead.

Accessibility

ConcernBehavior
LandmarkCarries aria-hidden="true". The rail holds no content, and an empty contentinfo landmark is noise a screen-reader user has to check and discard.
ContrastNot applicable — there is no text. --gray-400 is documented as decorative-only for exactly this reason; never put essential text on it.
FocusNothing focusable, so it adds no tab stops between the last control on the page and the end of the document.

Tokens

TokenValueUse
--gray-400#A8A29EThe rail's fill — the only colour in the component

The component emits platform-tokens.css itself, because app pages such as the Planner and Facilitator run their own CSS stacks and don't all load the shared sheet. Browsers dedupe the identical href. See Token Reference for current values.

CSS Classes

ClassPurpose
.ws-app-footerThe rail. 24px tall, flex: none so a flex page layout can't compress it.

Files

FilePurpose
includes/components/helpers.phpws_app_footer(), ws_is_app_page(), and the WS_APP_PATHS list
includes/components/app-footer.phpThe template, the shared render-once guard, and the bundled style
includes/components/footer.phpThe composite that delegates here on an app page

Live render

The real component follows, full-bleed. The stone band below this line is the whole thing.