# presentation/renderers/site.renderer.ts

> 112 lines of code and 26 definitions.

Tree: Site tree
Language: typescript
Layer: product
Canonical: https://banes-lab.com/anatomy/tree#file-presentation-renderers-site-renderer-ts
Source text: https://banes-lab.com/assets/sources/source.30a4287e9fcde75fdc9bcd6bd82d4dcffee9e9a59045529ca6ecde874bde25e8.generated.txt

## Definitions

- `renderClosing` (lexical_declaration, line 59)
- `closingTerm` (lexical_declaration, line 27)
- `closingBody` (lexical_declaration, line 51)
- `linkRow` (lexical_declaration, line 79)
- `renderBadges` (lexical_declaration, line 94)
- `renderSiteFooter` (lexical_declaration, line 119, exported)
- `copyrightText` (lexical_declaration, line 115, exported)
- `stackedBody` (lexical_declaration, line 32)
- `renderClosings` (lexical_declaration, line 65)
- `profileLink` (lexical_declaration, line 72)
- `platformLinks` (lexical_declaration, line 90)
- `renderLicense` (lexical_declaration, line 104)
- `routeBody` (lexical_declaration, line 39)
- `CLOSING_STEP` (lexical_declaration, line 24)
- `FOOTER_LINK_CLASS` (lexical_declaration, line 25)
- `code` (lexical_declaration, line 28)
- `linked` (lexical_declaration, line 40)
- `body` (lexical_declaration, line 52)
- `{ closing, tail }` (lexical_declaration, line 60)
- `heading` (lexical_declaration, line 61)
- `joined` (lexical_declaration, line 80)
- `PLATFORM_PAGES` (lexical_declaration, line 84)
- `images` (lexical_declaration, line 98)
- `terms` (lexical_declaration, line 105)
- `notice` (lexical_declaration, line 120, exported)
- `closings` (lexical_declaration, line 121, exported)

## Uses

- [core/factories/element.factory.ts](https://banes-lab.com/source/tree/core/factories/element.factory.ts.md)
- [presentation/components/link.component.ts](https://banes-lab.com/source/tree/presentation/components/link.component.ts.md)
- [presentation/renderers/element.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/element.renderer.ts.md)

## Used by

- [presentation/renderers/page.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/page.renderer.ts.md)
- [presentation/widgets/site.widget.ts](https://banes-lab.com/source/tree/presentation/widgets/site.widget.ts.md)

## Source

```typescript
import {
    CLOSING_SEPARATOR,
    CONTACT_CLOSING,
    INFORMATION_TITLE,
    LINK_SEPARATOR,
    MODEL_CLOSING,
    PRIVACY_TITLE,
    TERMS_TITLE,
    USE_CLOSING,
} from "#configuration/strings/page.strings";
import { COPYRIGHT_PREFIX, COPYRIGHT_SUFFIX } from "#configuration/strings/company.strings";
import type { Closing, License } from "#types/site.types";
import { INFORMATION_PAGE, PRIVACY_PAGE, TERMS_PAGE } from "#core/ids/page.ids";
import { createExternalLink, createLink, createPageLink } from "#presentation/components/link.component";
import type { ClosingCell } from "#types/page.types";
import { FADE_IN } from "#configuration/constants/element.constants";
import { ICON_BASE_CLASS } from "#configuration/icons/element.icons";
import type { IconLink } from "#types/link.types";
import { PROFILE_LINKS } from "#configuration/icons/company.icons";
import { SPACE } from "#configuration/constants/document.constants";
import { animated } from "#presentation/renderers/element.renderer";
import { createElement } from "#core/factories/element.factory";

const CLOSING_STEP = 60;
const FOOTER_LINK_CLASS = "footer-link";

const closingTerm = function closingTerm(term: string, linked: boolean): HTMLElement {
    const code = createElement("code", { text: term });
    return linked ? createExternalLink(term, "closing-route", [code]) : code;
};

const stackedBody = function stackedBody(closing: Closing): HTMLElement {
    return createElement("div", {
        children: closing.terms.map((term) => createElement("span", { className: "closing-line", text: term })),
        className: "closing-stack",
    });
};

const routeBody = function routeBody(closing: Closing): HTMLElement {
    const linked = closing.linked === true;
    const body = createElement("p");
    for (const [index, term] of closing.terms.entries()) {
        if (index > 0) {
            body.append(CLOSING_SEPARATOR);
        }
        body.append(term.includes(SPACE) ? term : closingTerm(term, linked));
    }
    return body;
};

const closingBody = function closingBody(closing: Closing): readonly HTMLElement[] {
    const body = closing.stacked === true ? stackedBody(closing) : routeBody(closing);
    if (closing.note === undefined) {
        return [body];
    }
    return [createElement("p", { className: "closing-note", text: closing.note }), body];
};

const renderClosing = function renderClosing(cell: ClosingCell, delay: number): HTMLElement {
    const { closing, tail } = cell;
    const heading = createElement("h3", { children: [createElement("i", { className: closing.icon }), closing.title] });
    return animated("div", { children: [heading, ...closingBody(closing), ...tail] }, FADE_IN, delay);
};

const renderClosings = function renderClosings(cells: readonly ClosingCell[]): HTMLElement {
    return createElement("section", {
        children: cells.map((cell, index) => renderClosing(cell, index * CLOSING_STEP)),
        className: "site-closing",
    });
};

const profileLink = function profileLink(link: IconLink): HTMLElement {
    return createExternalLink(link.href, `${FOOTER_LINK_CLASS} profile-link`, [
        createElement("i", { className: `${ICON_BASE_CLASS} ${link.icon}` }),
        link.title,
    ]);
};

const linkRow = function linkRow(links: readonly HTMLElement[]): HTMLElement {
    const joined = links.flatMap((link, index) => (index === 0 ? [link] : [LINK_SEPARATOR, link]));
    return createElement("p", { children: joined, className: "link-row" });
};

const PLATFORM_PAGES: readonly (readonly [string, string])[] = [
    [TERMS_PAGE, TERMS_TITLE],
    [PRIVACY_PAGE, PRIVACY_TITLE],
    [INFORMATION_PAGE, INFORMATION_TITLE],
];

const platformLinks = function platformLinks(): HTMLElement {
    return linkRow(PLATFORM_PAGES.map(([page, title]) => createPageLink(page, FOOTER_LINK_CLASS, [title])));
};

const renderBadges = function renderBadges(license: License): readonly HTMLElement[] {
    if (license.badges.length === 0) {
        return [];
    }
    const images = license.badges.map((badge) =>
        createElement("img", { attributes: { alt: badge.alt, src: badge.source }, className: "license-badge" }),
    );
    return [createExternalLink(license.href, "license-badges", images)];
};

const renderLicense = function renderLicense(license: License): HTMLElement {
    const terms = createElement("p", { className: "site-license" });
    terms.append(
        license.lead,
        createLink(license.href, FOOTER_LINK_CLASS, [license.name]),
        SPACE,
        ...renderBadges(license),
    );
    return terms;
};

export const copyrightText = function copyrightText(): string {
    return COPYRIGHT_PREFIX + String(new Date().getFullYear()) + COPYRIGHT_SUFFIX;
};

export const renderSiteFooter = function renderSiteFooter(license?: License): Element {
    const notice = license === undefined ? [] : [renderLicense(license)];
    const closings = renderClosings([
        { closing: MODEL_CLOSING, tail: [] },
        { closing: USE_CLOSING, tail: [platformLinks(), ...notice] },
        { closing: CONTACT_CLOSING, tail: [linkRow(PROFILE_LINKS.map(profileLink))] },
    ]);
    return createElement("footer", { children: [closings], className: "site-footer" });
};
```
