> ## Documentation Index
> Fetch the complete documentation index at: https://developer.jobmojito.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Portal pages

> Import a whole portal site — pages in every language, the menu, the site footer and the theme CSS — as one JSON document.

## Portal pages import

Generates the documents imported by **Configuration → Interview portal** (or **Consumer portal**) **→ Import** in the admin.

A portal site is a set of **pages**. Each page has an address (its **slug**), an optional place in the menu, and per language a title, an SEO description and an ordered list of **blocks** — the same blocks the visual page editor builds. A site can also carry **theme CSS** that restyles the whole portal.

One import can create a single page or rebuild a complete careers site, for example a copy of an existing corporate careers website with its sub-pages.

<Note>
  This import writes pages to the database **as drafts** straight away (unlike the three configuration documents, which only fill a form). Nothing reaches candidates until the page is published — either with the **Publish after import** switch in the import dialog, or page by page afterwards.
</Note>

## Documents

Two document shapes. A file may hold one document or a JSON array of them; the dialog also takes several files at once.

### A whole site

```json theme={null}
{
  "format": "jobmojito.portal-site",
  "version": 1,
  "portal": "interview",
  "theme": { "css": ".jm-site .jm-nav { background: #000033; }" },
  "pages": [
    { "slug": "", "show_in_menu": false, "sort_order": 0, "languages": [ … ] },
    { "slug": "about", "show_in_menu": true, "sort_order": 10, "languages": [ … ] },
    { "slug": "about/our-history", "show_in_menu": true, "sort_order": 11, "languages": [ … ] },
    { "slug": "_footer", "show_in_menu": false, "sort_order": 999, "languages": [ … ] }
  ]
}
```

| Field       | Type                          | Notes                                                                                       |
| ----------- | ----------------------------- | ------------------------------------------------------------------------------------------- |
| `format`    | `"jobmojito.portal-site"`     | Required.                                                                                   |
| `version`   | `1`                           | A higher version is imported as far as this editor understands it, with a warning.          |
| `portal`    | `"interview"` \| `"consumer"` | Which portal the site was made for. Importing into the other one works but shows a warning. |
| `theme.css` | string                        | Theme CSS for the portal — see [Theme CSS](#theme-css). Omit to keep the current theme.     |
| `pages`     | Page\[]                       | See [Page](#page).                                                                          |

### One page

```json theme={null}
{
  "format": "jobmojito.portal-page",
  "version": 1,
  "page": {
    "slug": "careers/culinary",
    "show_in_menu": false,
    "languages": [
      {
        "mojito_language_code": "en",
        "title": "Culinary",
        "description": "Culinary jobs at sea and ashore.",
        "blocks": [
          { "id": "hero", "type": "hero", "layout": "background", "title": "Culinary", "imageUrl": "https://cdn.example.org/culinary.jpg", "showSearch": true },
          { "id": "jobs", "type": "jobs", "title": "Culinary jobs", "tags": ["culinary"], "showSearch": true, "emptyText": "No culinary jobs are open right now." }
        ]
      }
    ]
  }
}
```

## What an import does

* **Matched by slug.** A page whose slug already exists is **replaced** (all its languages, its menu flag and, when given, its sort order). Any other page is **created**. Pages that are not in the document are left alone — an import never deletes a page.
* **Same slug twice** — the last one in the document (or the last file) wins, with a warning.
* **Draft or live.** Everything is saved as a draft. With **Publish after import** switched on, pages and the theme are published in the same step.
* **Theme.** When the document has `theme.css`, it replaces the portal's theme draft (and is published with the switch on).
* **Checked before writing.** The dialog lists every page it will create (`+`) or replace (`↻`), with its languages and block count, and any errors and warnings. Errors drop the affected page; warnings drop only the part named (a block, a language).
* **Lenient about extra keys.** Unknown fields are ignored and unknown block types skipped with a warning, so a document written for a newer editor still imports what this one understands.

## Page

| Field          | Type        | Notes                                                                                                  |
| -------------- | ----------- | ------------------------------------------------------------------------------------------------------ |
| `slug`         | string      | Required. The page address — see [Slugs](#slugs).                                                      |
| `show_in_menu` | boolean     | Default `false`. See [Menu](#menu).                                                                    |
| `sort_order`   | integer     | Menu and list order, ascending. Omitted: an existing page keeps its order, a new page goes to the end. |
| `languages`    | Language\[] | At least one. One entry per portal language; a language listed twice keeps the first.                  |

**Language entry**

| Field                  | Type     | Notes                                                                                                                   |
| ---------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------- |
| `mojito_language_code` | string   | Required. Platform language code: `en`, `de`, `sk`, `es`, … The portal shows the entry matching the visitor's language. |
| `title`                | string   | Page title: browser tab, menu label, search results.                                                                    |
| `description`          | string   | SEO description. Empty falls back to the portal's default.                                                              |
| `blocks`               | Block\[] | The page content, top to bottom. See [Blocks](#blocks).                                                                 |

## Slugs

| Slug                    | Page                                                                                                     |
| ----------------------- | -------------------------------------------------------------------------------------------------------- |
| `""`                    | The portal home page (`/`).                                                                              |
| `about`, `life-onboard` | A page at `/about`, `/life-onboard`. Lower-case letters, digits and dashes.                              |
| `about/our-history`     | A sub-page at `/about/our-history`. Its parent `about` should exist.                                     |
| `_footer`               | The **site footer**, shown on every page above the platform's legal footer. Holds one `footer` block.    |
| `_jobs`                 | Built-in: blocks shown above the job list at `/jobs` (interview portal).                                 |
| `_blog`                 | Built-in: blocks shown above the blog at `/blog` (while blogs are switched on).                          |
| `_support`              | Built-in: the support page at `/support`.                                                                |
| `_tracking`             | Built-in: the message on `/application-tracking` (interview portal, signed-in candidates).               |
| `_completion`           | Built-in: the message after an interview, `/interview/success` (interview portal, signed-in candidates). |
| `_signed_in_home`       | Built-in: blocks at the top of `/home` (coaching portal, signed-in learners).                            |

A custom slug is refused when:

* it is not lower-case words joined by dashes, with `/` between levels (`^[a-z0-9]+(-[a-z0-9]+)*(/[a-z0-9]+(-[a-z0-9]+)*)*$`), or longer than 200 characters;
* its first segment is an address the portal already uses: `jobs`, `blog`, `support`, `interview`, `position`, `login`, `profile`, `calendar`, `application-tracking`, `privacy_policy`, `terms_of_service`, `cookie_policy`, `home`, `catalogue`, `sessions`, `resume`, `pricing`, `faq` and a few more;
* its first segment is a language code (`de/…` is the German version of a page, not a page called "de").

Built-in slugs (starting with `_`) and the home page cannot be moved or renamed; importing them only replaces their content.

## Menu

* A top-level page with `show_in_menu: true` gets a link in the portal header, labelled with its title in the visitor's language.
* Its direct sub-pages with `show_in_menu: true` form that link's dropdown. Deeper levels never appear in the menu — link to them from blocks.
* `sort_order` orders both levels.
* Built-in pages that can be in the menu (`_jobs`, `_blog`, `_support`, `_tracking`, `_signed_in_home`) follow their own `show_in_menu` once published.

## Blocks

Every block is an object with a `type` and a stable `id` (unique within its language; generated when missing or duplicated). All blocks also accept:

| Field       | Values                               | Notes                                                                  |
| ----------- | ------------------------------------ | ---------------------------------------------------------------------- |
| `enabled`   | boolean                              | Only `false` hides the block.                                          |
| `tone`      | `default`, `muted`, `dark`, `accent` | Section background. A theme restyles each tone.                        |
| `anchor`    | `[a-z0-9-]*`                         | Id of the section, for links such as `/about#values`.                  |
| `className` | letters, digits, dashes, spaces      | Extra classes on the section wrapper, for the [theme CSS](#theme-css). |

Fields marked *markdown* use the restricted renderer described in [Format content with Markdown](/cookbooks/format-content-with-markdown). Images and videos must be public https URLs; the importer uploads nothing.

### Content blocks

| type           | Fields                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `hero`         | `kicker`, `title` (markdown; `_word_` = accent), `body` (markdown), `ctaLabel`, `ctaHref`, `imageUrl`, `layout` (`split` image beside the copy \| `background` image behind it), `align` (`left`\|`center`, background layout), `height` (`medium`\|`tall` = full screen, background layout), `videoUrl` (looping .mp4/.webm over the image, background layout), `showSearch` (job search box, submits to `/jobs`), `searchPlaceholder`, `searchButtonLabel` |
| `intro`        | `body` (markdown), `imageUrl` (floated beside the text)                                                                                                                                                                                                                                                                                                                                                                                                      |
| `rich-text`    | `body` (markdown), `width` (`narrow`\|`wide`), `boxed` (on a card), `textStyle` (`headline` large centred text \| `body` plain text)                                                                                                                                                                                                                                                                                                                         |
| `media-text`   | `title`, `body` (markdown), `imageUrl`, `imageSide` (`left`\|`right`), `ctaLabel`, `ctaHref`                                                                                                                                                                                                                                                                                                                                                                 |
| `cards`        | `title`, `body` (markdown intro), `style` (`overlay` title over a photo \| `stacked` photo above text \| `tile` text-only link tiles), `columns` (2–4), `ratio` (`portrait`\|`landscape`\|`square`), `showTitles` (`false` hides titles on overlay cards whose photo carries a logo), `layout` (`grid`\|`slider` paged sideways), `items[]` of `{ title, body, imageUrl, href, linkLabel }`                                                                  |
| `feature-grid` | `title`, `columns` (2–5), `imageStyle` (`cover`\|`icon`), `items[]` of `{ title, body, imageUrl, href, linkLabel }`                                                                                                                                                                                                                                                                                                                                          |
| `stats`        | `title`, `items[]` of `{ value, label }`                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `quote`        | `quote`, `author`, `role`, `imageUrl`, `layout` (`centered`\|`photo` portrait across the section with the quote on a card), `title` (card heading, photo layout)                                                                                                                                                                                                                                                                                             |
| `video`        | `title`, `body`, `url` (YouTube, Vimeo or https .mp4/.webm), `imageUrl` (poster for a file)                                                                                                                                                                                                                                                                                                                                                                  |
| `tabs`         | `title`, `body`, `layout` (`pills`\|`vertical`), `items[]` of `{ label, title, body, imageUrl, steps }` — `steps` is a list of job titles drawn as a career ladder                                                                                                                                                                                                                                                                                           |
| `timeline`     | `title`, `body`, `layout` (`vertical`\|`horizontal`\|`cards`), `items[]` of `{ label, title, body, imageUrl }` — `label` is the date or year                                                                                                                                                                                                                                                                                                                 |
| `logo-row`     | `title`, `layout` (`row`\|`slider`), `logos[]` of `{ imageUrl, alt }`                                                                                                                                                                                                                                                                                                                                                                                        |
| `faq`          | `title`, `items[]` of `{ question, answer (markdown) }`                                                                                                                                                                                                                                                                                                                                                                                                      |
| `cta`          | `title`, `body`, `ctaLabel`, `ctaHref`, `imageUrl` (background photo), `align` (`center`\|`left`)                                                                                                                                                                                                                                                                                                                                                            |
| `divider`      | `style` (`line`\|`space`), `spacing` (`sm`\|`md`\|`lg`), `width` (`narrow`\|`wide`\|`full`)                                                                                                                                                                                                                                                                                                                                                                  |

### Live blocks

These render data from the portal itself.

| type            | Portal    | Fields                                                                                                                                                                                                                                                                                                                      |
| --------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `jobs`          | interview | The open positions. `title`, `tags` (job tags from the position editor; a job matches one of them, case-insensitive), `query` (comma-separated keywords matched in title, description or location), `limit` (1–100), `showSearch`, `showTagFilter` (chips to narrow by tag), `allLabel`, `emptyText`, `ctaLabel`, `ctaHref` |
| `job-openings`  | interview | Job cards of the classic home page. `discoverId` (discovery interview id — never invent)                                                                                                                                                                                                                                    |
| `public-avatar` | interview | The talking avatar. `avatarId` (never invent)                                                                                                                                                                                                                                                                               |
| `easy-apply`    | interview | "How it works" cards of the AI interview. No fields.                                                                                                                                                                                                                                                                        |
| `blogs`         | both      | Latest posts. `title`, `body`, `tag` (a blog tag; empty = all), `limit` (1–24), `layout` (`grid`\|`list`), `showExcerpt`, `showDate`, `ctaLabel`, `ctaHref`                                                                                                                                                                 |
| `pricing`       | coaching  | The configured pricing table. `title`                                                                                                                                                                                                                                                                                       |
| `footer`        | both      | Site footer, on the `_footer` page. `logoUrl`, `logoAlt`, `columns[]` of `{ title, links[] of { label, href } }`, `topLinks[]` and `bottomLinks[]` of `{ label, href }`, `note` (markdown, e.g. copyright). Leave `topLinks`, `bottomLinks` and `note` out to drop those rows.                                              |

Links (`href`, `ctaHref`) are portal paths (`/jobs`, `/about/our-history`, `/about#values`) or full https URLs.

## Theme CSS

`theme.css` restyles the whole portal on top of the platform styles. It is saved per portal (**Theme CSS** in the pages list) and applies to every page, the header and the footer — and on the interview screens, to the header and footer only, so the interview itself keeps its own look.

Write every rule under `.jm-site`, the wrapper of the portal and of the editor canvas:

| Hook                                                       | Element                                            |
| ---------------------------------------------------------- | -------------------------------------------------- |
| `.jm-site`                                                 | The portal root. Set fonts and CSS variables here. |
| `.jm-nav`                                                  | Header and menu.                                   |
| `.jm-footer`, `.jm-site-footer`                            | Footer area and the `_footer` page's footer block. |
| `.jm-block--<type>`                                        | Every block of a type, e.g. `.jm-block--hero`.     |
| `.jm-tone--<tone>`                                         | Sections of a tone, e.g. `.jm-tone--dark`.         |
| `.jm-card--overlay`, `.jm-card--stacked`, `.jm-card--tile` | Cards by style.                                    |
| `.jm-button`, `.jm-tabs__tab`, `.jm-job`, …                | Buttons, tabs, job rows.                           |
| your `className`                                           | Any class you put on a block.                      |

Use `var(--accent)` for the brand colour instead of a hard-coded value, so [Branding](/cookbooks/portal-customisation/branding) stays the source of truth. The tone variables `--jm-tone-muted-bg`, `--jm-tone-dark-bg`, `--jm-tone-dark-fg`, `--jm-tone-accent-bg` and `--jm-tone-accent-fg` recolour the sections without touching the blocks. `@import` of Google Fonts and `@font-face` are allowed.

If the header is dark, upload a light logo under **Branding → Logo on dark backgrounds** and show it from the theme:

```css theme={null}
.jm-site .jm-nav .jm-logo--default:has(+ .jm-logo--dark) { display: none; }
.jm-site .jm-nav .jm-logo--dark { display: block; }
```

## Rules

* One language entry per portal language, with the same blocks in each (translated).
* Keep block `id`s stable between imports of the same page; they are how the editor tracks blocks.
* Never invent ids (`avatarId`, `discoverId`) or links to pages the document does not create.
* A page shown in the menu needs a short `title` — it is the menu label.
* Put the site footer on `_footer`, not at the bottom of every page.
* Import unpublished first, check the pages in the editor, then publish.
