# presentation/views/profile.view.ts

> 137 lines of code and 25 definitions.

Tree: Bane's Lab Site
Language: typescript
Layer: product
Canonical: https://banes-lab.com/anatomy/tree#file-presentation-views-profile-view-ts
Source text: https://banes-lab.com/source/tree/presentation/views/profile.view.ts.txt

Listed in [presentation/views](https://banes-lab.com/api/source/tree/presentation/views.md), after [presentation/views/privacy.view.ts](https://banes-lab.com/source/tree/presentation/views/privacy.view.ts.md) and before [presentation/views/search.view.ts](https://banes-lab.com/source/tree/presentation/views/search.view.ts.md).

## Definitions

- `profileFactCell` (lexical_declaration, line 85)
- `profileHero` (lexical_declaration, line 68)
- `profileFactList` (lexical_declaration, line 100)
- `profilePortrait` (lexical_declaration, line 60)
- `profileContacts` (lexical_declaration, line 44)
- `profileTopic` (lexical_declaration, line 107)
- `renderAbout` (lexical_declaration, line 129)
- `profileHeadline` (lexical_declaration, line 36)
- `DELAY_HEADLINE` (lexical_declaration, line 27)
- `DELAY_PROFILE_LEAD` (lexical_declaration, line 28)
- `DELAY_CONTACTS` (lexical_declaration, line 29)
- `DELAY_PORTRAIT` (lexical_declaration, line 30)
- `DELAY_FACTS` (lexical_declaration, line 31)
- `DELAY_TOPICS` (lexical_declaration, line 32)
- `HEADLINE_WORD_STEP` (lexical_declaration, line 33)
- `PROFILE_STEP` (lexical_declaration, line 34)
- `words` (lexical_declaration, line 37)
- `image` (lexical_declaration, line 61)
- `column` (lexical_declaration, line 69)
- `glow` (lexical_declaration, line 78)
- `[lead]` (lexical_declaration, line 108)
- `icon` (lexical_declaration, line 109)
- `heading` (lexical_declaration, line 114)
- `topics` (lexical_declaration, line 130)
- `PAGE` (lexical_declaration, line 140, exported)

## Contained in

- [presentation/views](https://banes-lab.com/anatomy/tree/folder-presentation-views.md)

## 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)
- [presentation/renderers/text.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/text.renderer.ts.md)

## Linked from

- [core/factories](https://banes-lab.com/anatomy/tree/folder-core-factories.md)
- [presentation/components](https://banes-lab.com/anatomy/tree/folder-presentation-components.md)
- [presentation/renderers](https://banes-lab.com/anatomy/tree/folder-presentation-renderers.md)
- [presentation/views](https://banes-lab.com/anatomy/tree/folder-presentation-views.md)

## Source

```typescript
import { ABOUT_DESCRIPTION, ABOUT_TITLE } from "#configuration/strings/page.strings";
import {
    ABOUT_META,
    ABOUT_SECTIONS,
    EMAIL_ACTION,
    GITHUB_ACTION,
    LINKEDIN_ACTION,
    PORTRAIT_ALT,
    PROFILE_EYEBROW,
    PROFILE_FACTS,
    PROFILE_LEAD,
    PROFILE_THESIS,
} from "#configuration/strings/profile.strings";
import { AUTHOR_GITHUB, AUTHOR_MAIL, AUTHOR_PROFILE } from "#core/assets/link.assets";
import { FADE_IN, FADE_IN_UP, FADE_IN_UP_LARGE } from "#configuration/constants/element.constants";
import { ABOUT_PAGE } from "#core/ids/page.ids";
import type { PageDefinition } from "#types/page.types";
import type { ProfileFact } from "#types/profile.types";
import { SITE_PORTRAIT } from "#core/assets/image.assets";
import { SPACE } from "#configuration/constants/document.constants";
import type { Section } from "#types/document.types";
import { animated } from "#presentation/renderers/element.renderer";
import { createElement } from "#core/factories/element.factory";
import { createExternalLink } from "#presentation/components/link.component";
import { fillMarkup } from "#presentation/renderers/text.renderer";

const DELAY_HEADLINE = 90;
const DELAY_PROFILE_LEAD = 240;
const DELAY_CONTACTS = 300;
const DELAY_PORTRAIT = 120;
const DELAY_FACTS = 60;
const DELAY_TOPICS = 80;
const HEADLINE_WORD_STEP = 60;
const PROFILE_STEP = 60;

const profileHeadline = function profileHeadline(): HTMLElement {
    const words = PROFILE_THESIS.split(SPACE).flatMap((word, index) => [
        animated("span", { className: "word", text: word }, FADE_IN_UP, DELAY_HEADLINE + index * HEADLINE_WORD_STEP),
        SPACE,
    ]);
    return createElement("h1", { children: words, className: "home-thesis" });
};

const profileContacts = function profileContacts(): HTMLElement {
    return animated(
        "div",
        {
            children: [
                createExternalLink(AUTHOR_MAIL, "btn", [EMAIL_ACTION]),
                createExternalLink(AUTHOR_GITHUB, "btn ghost", [GITHUB_ACTION]),
                createExternalLink(AUTHOR_PROFILE, "go", [LINKEDIN_ACTION]),
            ],
            className: "home-actions",
        },
        FADE_IN,
        DELAY_CONTACTS,
    );
};

const profilePortrait = function profilePortrait(): HTMLElement {
    const image = createElement("img", {
        attributes: { alt: PORTRAIT_ALT, src: SITE_PORTRAIT },
        className: "profile-image",
    });
    return animated("div", { children: [image], className: "profile-portrait" }, FADE_IN_UP_LARGE, DELAY_PORTRAIT);
};

const profileHero = function profileHero(): HTMLElement {
    const column = createElement("div", {
        children: [
            animated("p", { className: "home-eyebrow", text: PROFILE_EYEBROW }, FADE_IN, 0),
            profileHeadline(),
            animated("p", { className: "home-lead", text: PROFILE_LEAD }, FADE_IN, DELAY_PROFILE_LEAD),
            profileContacts(),
        ],
        className: "home-intro",
    });
    const glow = createElement("div", { className: "home-glow" });
    return createElement("section", {
        children: [glow, column, profilePortrait()],
        className: "home-hero profile-hero",
    });
};

const profileFactCell = function profileFactCell(entry: ProfileFact, delay: number): HTMLElement {
    return animated(
        "div",
        {
            children: [
                createElement("dt", { text: entry.label }),
                createElement("dd", { text: entry.value }),
            ],
            className: "profile-fact",
        },
        FADE_IN_UP,
        delay,
    );
};

const profileFactList = function profileFactList(): HTMLElement {
    return createElement("dl", {
        children: PROFILE_FACTS.map((entry, index) => profileFactCell(entry, DELAY_FACTS + index * PROFILE_STEP)),
        className: "profile-facts",
    });
};

const profileTopic = function profileTopic(section: Section, delay: number): HTMLElement {
    const [lead] = section.subsections;
    const icon = createElement("span", {
        attributes: { "aria-hidden": "true" },
        children: [createElement("i", { className: section.icon })],
        className: "page-icon",
    });
    const heading = createElement("h2", {
        children: [section.title, createElement("small", { text: lead?.title ?? "" })],
    });
    return animated(
        "article",
        {
            attributes: { id: section.id },
            children: [icon, heading, fillMarkup(createElement("p"), lead?.content ?? "")],
            className: "home-row profile-topic",
        },
        FADE_IN_UP,
        delay,
    );
};

const renderAbout = function renderAbout(): Element {
    const topics = createElement("div", {
        children: ABOUT_SECTIONS.map((section, index) => profileTopic(section, DELAY_TOPICS + index * PROFILE_STEP)),
        className: "home-rows profile-topics",
    });
    return createElement("div", {
        children: [profileHero(), profileFactList(), topics],
        className: "home-content profile-content",
    });
};

export const PAGE: PageDefinition = {
    content: { kind: "document", meta: ABOUT_META, sections: ABOUT_SECTIONS },
    description: ABOUT_DESCRIPTION,
    id: ABOUT_PAGE,
    profile: true,
    render: renderAbout,
    title: ABOUT_TITLE,
};
```
