Index

mmo-app

1 symbol · generated 5 Oct 2026

element

mmo-app

mmo-app.jhs:1

The application shell. One element, three templates, chosen by the host's class: <mmo-app class="admin">, class="landing" or class="blog".

<mmo-app template></mmo-app>
Extends
CustomShadowElement
The `load(config)` contract, the menu, breadcrumb, tab, notification, modal and panel api and the `MMO.app` entry point are the ones projects already use; the shell around them is built on the `.scs` design system, and on three templates instead of one fixed layout. One change, in 3.1.0: a message (`notify`, `alert`, `confirm`, `ask`, `error`, `showModal`, `showNotification`, `showPanel`) is text unless it is a node, where it used to be html -- markup a caller wrote goes through `MMO.core.dom.html()`.

Index

templatethe template to use (admin, landing, blog).
template accessorThe active template.
constructor()
init()
initPanelResize()Let the side panel be dragged wider or narrower by its left edge.
resizePanelByKey(event)Resize the panel from the keyboard: left and right by 16px, 4px with shift, Home back to its default width.
setPanelWidth(width)
fitPanel()Give the panel the width its shown panel asks for (showPanel's width), unless the user chose one by dragging its edge.
resetPanelWidth()Put the panel back to the width the stylesheet gives it.
updateRegions()Reflect which optional regions the page filled, as host classes: a hero, a page header or a rail that no slot fills is removed from the layout instead of holding empty space.
load(config)Load the application configuration, the object the theme writes into MMO.config.
addBreadcrumb(label, url, index)Add a breadcrumb, closest to the root first.
clearTabs()Remove every page tab.
getTabs()Every page tab currently in the strip.
addTab(label, url, icon, order)Add a page tab.
scrollTabs(direction)Scroll the tab strip.
updateTabsOverflow()Show the tab scrollers only when the strip actually overflows.
getMenu(url)Find a rail entry by the url it points at.
setActiveMenu(url)Mark a menu entry, and every accordion above it, as active.
revealMenu(node)Scroll a rail entry into view, in the rail's own scroller.
addMenu(group, label, url, icon, order)Add a menu entry, creating the accordion hierarchy its url implies.
bindAccordionToggle(accordion)Keep an accordion's open state across pages, and keep its title a link.
toggleMenu()Collapse or expand the rail.
shortcut(event)Global shortcuts: alt+/ opens the palette, escape closes what is open.
initCommands()The commands the shell always has: what its own buttons do, when the template has those buttons.
addCommand(label, run, icon, keywords)Add a command to the palette, listed under « Commandes ».
addSearchSource(source)Add a remote source to the palette: its results are listed under their own heading, after the pages and the commands.
openPalette(query)Open the command palette, on the whole menu or already searching.
paletteBackdrop(event)Close the command palette when the click is on the veil around it, not in its box.
closePalette()Close the command palette.
filterPalette(query)Filter the palette on what has been typed, one group per category: the text of the page itself first (searchPage), then the pages of the menu, the commands, and each remote source (addSea…
searchPage(query, limit)Find a text in the page: accents and case do not count.
indexPage()Read the text of the page once, when the palette opens, so a search is a lookup rather than a walk through the whole document at every key.
pageMatch(entry, at, length)One match in the page, as a palette item: the words around it, the match marked.
revealPageMatch(node, at, length)Scroll to a match in the page, and select it: the selection is the browser's own highlight, and nothing is written into the page.
fold(text) staticFold a text for searching: lower case, accents dropped -- one character for one character, so an offset in the folded text is the same offset in the original.
searchRemote(source, query)Ask a remote source, and read its answer as palette items.
appendPaletteGroup(parent, title, items)Add a group of results to the palette.
updatePaletteEmpty(query)Keep one result selected, and say so when there is none left -- once no source is still searching.
paletteKey(event)Move through the results, across the groups, and follow the selected one.
showPanel(title, body, options)Open a panel in the side drawer, in a tab of its own.
createPanelTab(panel)the tab of a panel: its title, which shows it, and a button closing it
selectPanel(id)Show an open panel.
updatePanel(id, changes)Change an open panel: the title of its tab, or its key (showPanel).
onPanelTabsKey(event)the tab strip from the keyboard: arrows go from tab to tab, Delete closes one
closePanel(value, id)Close a panel, resolving its promise.
closeAllPanels(value)Close every panel.
showNotification(css, content)Show a notification.
notify(content, title, button, butIcon)Show a notification with a title and a dismiss button.
showModal(css, content)Show a modal dialog.
answer(node, value)Wire a dialog's buttons to its result: any <button value="..."> closes the dialog with that value, and the promise resolves with it -- false when the dialog is dismissed (escape, a click outsid…
ask(title, content)Ask for a value: one input, and the answer.
confirm(title, content)Ask a yes/no question.
alert(title, content)Say something, with one button to dismiss it.
error(title, content)The same, in the error tone.
describeError(error)An error as a user reads it: what happened, in French; the server's message when it is meant for a user (the framework's own name what they are about in [brackets], an id or a path is no such thi…
toggleUserCard()Toggle the account card.
toggleConsole()Toggle the developer drawer.
showConsoleTab(name)Show one pane of the developer drawer, and open the drawer if it was closed.
loadServerInfo(reload)Fill the server pane with what the server is made of: php, its extensions and every package the framework included, as AdminController::routeServerInfo answers them.
toggleTOC()Toggle the table of contents.
updateConsoleTags(console)Update the warning and error counters of the status bar.
gotoTop(event)Scroll back to the top of whichever container actually scrolls.
blockScreen(active)Block the screen while something runs.
setThemeMode(scheme)Apply a colour scheme.
toggleDarkMode()Switch between the dark and light schemes.
(default)the page itself
logothe mark at the top of the rail
menurail entries written by hand, next to the ones MMO.config.menus adds
side-footthe foot of the rail, on the row the scheme switch ends
user-infothe account line next to the account button, at the end of the top bar
user-card-bodywhat the account card shows when it is opened
brandthe brand in the top bar, for the landing and blog templates
navlinks in the top bar
actionsbuttons on the right of the top bar
headeranything else on the top bar, after the actions
herothe band above the page, for a landing
page-titlethe page header's title
page-actionsthe page header's buttons
navigationa strip between the header and the content (filters, a sub-navigation)
asidethe rail on the right of the content, for an article
footerthe footer, for the landing and blog templates
panel-titleextra content at the end of the side panel's tab strip (its tabs are built by showPanel)
panelthe body of the side panel
statusthe status bar, left of the log button
logsthe lines the server log tab of the developer drawer shows
notificationsthe notifications stacked in the corner
screen-blockerwhat covers the page while it is blocked (blockScreen)
.adminthe application shell: rail, breadcrumb, page tabs, side panel, status bar
.landingthe marketing shell: sticky translucent header, hero, sections, large footer
.blogthe editorial shell: header, content column, sticky rail, footer
.menu-collapsedthe rail is reduced to icons
.menu-openthe rail is open, on a small screen
.has-tabsthe page has tabs: a small screen gives them a row of their own under the top bar
.panel-openthe side panel is open
.console-openthe developer drawer (server log, server information) is open
.toc-openthe table of contents is open
.palette-openthe command palette is open, over the page
.has-herothe page filled the hero region, so the shell makes room for it
.has-user-cardthe page filled the account card, so it gets a body above the logout link

Attributes1

template

mmo-app.jhs

the template to use (admin, landing, blog). Defaults to the host's class.

template

Properties1

template

accessormmo-app.jhs:1413

The active template.

get template() set template(value)
Type
string

Methods64

initPanelResize()

mmo-app.jhs:1314

Let the side panel be dragged wider or narrower by its left edge.

initPanelResize()`MMO.inputs.Drag` in `size-x` mode resizes by the left edge, which is the one the panel has: it grows towards the content. The panel's own `max-width` caps it, and 240px is the point below which its header no longer fits.
returnsvoid

resizePanelByKey(event)

mmo-app.jhs:1348

Resize the panel from the keyboard: left and right by 16px, 4px with shift, Home back to its default width.

resizePanelByKey(event)
eventKeyboardEvent
returnsvoid

setPanelWidth(width)

mmo-app.jhs:1368
setPanelWidth(width)
widthnumber- in pixels
returnsvoid

fitPanel()

mmo-app.jhs:1379

Give the panel the width its shown panel asks for (showPanel's width), unless the user chose one by dragging its edge. The panel's own max-width still caps it.

fitPanel()
returnsvoid

resetPanelWidth()

mmo-app.jhs:1387

Put the panel back to the width the stylesheet gives it. @return {void}

resetPanelWidth()

updateRegions()

mmo-app.jhs:1397

Reflect which optional regions the page filled, as host classes: a hero, a page header or a rail that no slot fills is removed from the layout instead of holding empty space.

updateRegions()

load(config)

mmo-app.jhs:1435

Load the application configuration, the object the theme writes into MMO.config.

load(config)A link's label is html: it is the application's own config (a page's `index.json`, what a controller adds to the document), like every other region -- a controller naming a link after data escapes it.
configobject- breadcrumbs, navigations, menus and logs

addBreadcrumb(label, url, index)

mmo-app.jhs:1482

Add a breadcrumb, closest to the root first.

addBreadcrumb(label, url, index = 0)
labelstring- html, from the application's config (load)
urlstring
index = 0number- the depth of this crumb
returnsHTMLAnchorElement

getTabs()

mmo-app.jhs:1498

Every page tab currently in the strip.

getTabs()
returnsNodeListthe page tabs

addTab(label, url, icon, order)

mmo-app.jhs:1510

Add a page tab.

addTab(label, url, icon = null, order = 0)
labelstring- html, from the application's config (load)
urlstring
icon = null?string
order = 0number
returnsHTMLAnchorElement

scrollTabs(direction)

mmo-app.jhs:1526

Scroll the tab strip.

scrollTabs(direction)
directionnumber- -1 left, 1 right

updateTabsOverflow()

mmo-app.jhs:1532

Show the tab scrollers only when the strip actually overflows.

updateTabsOverflow()

getMenu(url)

mmo-app.jhs:1549

Find a rail entry by the url it points at.

getMenu(url)
urlstring
returns?HTMLElementthe menu entry for this url

setActiveMenu(url)

mmo-app.jhs:1562

Mark a menu entry, and every accordion above it, as active.

setActiveMenu(url)A marked entry nobody can see is not a mark: the folders it sits under are opened, and the rail is scrolled to it -- inside the rail only, so the page itself does not move.
urlstring

revealMenu(node)

mmo-app.jhs:1591

Scroll a rail entry into view, in the rail's own scroller.

revealMenu(node)
nodeHTMLElement

addMenu(group, label, url, icon, order)

mmo-app.jhs:1612

Add a menu entry, creating the accordion hierarchy its url implies.

addMenu(group, label, url, icon, order = 0)
groupstring- the section of the rail this entry belongs to, html (load)
labelstring- html, from the application's config (load)
urlstring
iconstring
order = 0number

bindAccordionToggle(accordion)

mmo-app.jhs:1695

Keep an accordion's open state across pages, and keep its title a link.

bindAccordionToggle(accordion)
accordionHTMLElement

toggleMenu()

mmo-app.jhs:1720

Collapse or expand the rail. On a small screen it opens as a drawer instead.

toggleMenu()

shortcut(event)

mmo-app.jhs:1741

Global shortcuts: alt+/ opens the palette, escape closes what is open.

shortcut(event)`/` is read from the key *and* from its position: on a qwerty keyboard alt+/ is the `Slash` key, but a mac turns it into `÷`, and an azerty keyboard has `/` on shift.
eventKeyboardEvent

initCommands()

mmo-app.jhs:1758

The commands the shell always has: what its own buttons do, when the template has those buttons.

initCommands()

addCommand(label, run, icon, keywords)

mmo-app.jhs:1775

Add a command to the palette, listed under « Commandes ».

addCommand(label, run, icon = null, keywords = '')
labelstring- text
runFunction- called when the command is picked, once the palette is closed
icon = null?string- an <mmo-icon> name
keywords = ''string- other words the command is found by
returnslabel: string, run: Function, icon: ?string, keywords: string} the command
MMO.app.addCommand('Vider le cache', () => clearCache(), 'trash', 'cache clear')

addSearchSource(source)

mmo-app.jhs:1791

Add a remote source to the palette: its results are listed under their own heading, after the pages and the commands. The server is asked url?q=…&limit=… (plus params) and answers {results: [{url, label | shortName | name, kind, hint | namespace, icon}]}, or the same wrapped in data. The server usually declares its sources in MMO.config.searches (load).

addSearchSource(source)
source
returnsObjectthe source
MMO.app.addSearchSource({name: 'documentation', label: 'Documentation', url: MMO.url('doc/search')})

openPalette(query)

mmo-app.jhs:1802

Open the command palette, on the whole menu or already searching.

openPalette(query = '')
query = ''string- what the palette starts with
MMO.app.openPalette('Route')

paletteBackdrop(event)

mmo-app.jhs:1814

Close the command palette when the click is on the veil around it, not in its box.

paletteBackdrop(event)
eventMouseEvent

filterPalette(query)

mmo-app.jhs:1834

Filter the palette on what has been typed, one group per category: the text of the page itself first (searchPage), then the pages of the menu, the commands, and each remote source (addSearchSource), which only answers once typing pauses.

filterPalette(query)A page is matched on its label *and* its url, so it is found by what it is called or by where it lives.
querystring

searchPage(query, limit)

mmo-app.jhs:1903

Find a text in the page: accents and case do not count. Each match is listed with its context and the heading it sits under; picking it scrolls to it and selects it (revealPageMatch).

searchPage(query, limit = 20)
querystring
limit = 20number
returnsArray<Object>palette items

indexPage()

mmo-app.jhs:1925

Read the text of the page once, when the palette opens, so a search is a lookup rather than a walk through the whole document at every key. The page is what the default slot and the page's own regions hold -- never the menu, the panels or the notifications -- open shadow roots included, and only what is visible.

indexPage()
returnsArray<{node: Text, text: string, heading: string>} each text node, folded (fold), with the heading it sits under

pageMatch(entry, at, length)

mmo-app.jhs:1967

One match in the page, as a palette item: the words around it, the match marked.

pageMatch(entry, at, length)
entry
atnumber- where the match starts in the text node
lengthnumber
returnsObject

revealPageMatch(node, at, length)

mmo-app.jhs:1991

Scroll to a match in the page, and select it: the selection is the browser's own highlight, and nothing is written into the page.

revealPageMatch(node, at, length)
nodeText
atnumber
lengthnumber

fold(text)

staticmmo-app.jhs:2012

Fold a text for searching: lower case, accents dropped -- one character for one character, so an offset in the folded text is the same offset in the original.

static fold(text)
textstring
returnsstring

searchRemote(source, query)

mmo-app.jhs:2024

Ask a remote source, and read its answer as palette items.

searchRemote(source, query)
source
querystring
returnsPromise<Array<Object>>

appendPaletteGroup(parent, title, items)

mmo-app.jhs:2044

Add a group of results to the palette.

appendPaletteGroup(parent, title, items)
parentHTMLElement
titlestring- the category
items

updatePaletteEmpty(query)

mmo-app.jhs:2079

Keep one result selected, and say so when there is none left -- once no source is still searching.

updatePaletteEmpty(query)
querystring

paletteKey(event)

mmo-app.jhs:2096

Move through the results, across the groups, and follow the selected one.

paletteKey(event)
eventKeyboardEvent

showPanel(title, body, options)

mmo-app.jhs:2146

Open a panel in the side drawer, in a tab of its own.

showPanel(title, body, options = {})Several panels can be open at once: each has a tab, which shows it, and a close button. Opening a panel shows it. Given a `key`, a panel already open with the same key is shown instead of opening a second one (one tab per record, say). The promise it returns is the panel's result: `closePanel(value)` resolves it with `value`, which is how an edit form in a panel reports what the user did (`MMO.elements.Entity`). A panel closed by its tab's button resolves with `null`. The title and the body are text unless they are nodes, like every message of the shell (`showModal`).
See
selectPanel, updatePanel).
titlestring- text
body?(Node | string | MMO.core.dom.Html)- a node, or text; markup you wrote goes through MMO.core.dom.html()
options = {}object- key: what identifies the panel, to show it again rather than open it twice; width: the width it wants, in pixels, while it is shown (an edit form needing room), unless the user dragged the panel to a width of their own
returnsPromiseresolved with the value the panel is closed with. promise.node is the panel's body element, for a caller that has to reach into it, and promise.id its id (closePanel,
MMO.app.showPanel(entity.title, form)MMO.app.showPanel('#12 Fix the login', form, {key: 'ticket-12'})

createPanelTab(panel)

mmo-app.jhs:2174

the tab of a panel: its title, which shows it, and a button closing it

createPanelTab(panel)
panel

selectPanel(id)

mmo-app.jhs:2198

Show an open panel.

selectPanel(id)
idstring- the panel's id (promise.id of showPanel)
returnsvoid

updatePanel(id, changes)

mmo-app.jhs:2218

Change an open panel: the title of its tab, or its key (showPanel).

updatePanel(id, changes = {})
idstring- the panel's id
changes = {}object- title (text) and/or key
returnsvoid

onPanelTabsKey(event)

mmo-app.jhs:2232

the tab strip from the keyboard: arrows go from tab to tab, Delete closes one

onPanelTabsKey(event)
event

closePanel(value, id)

mmo-app.jhs:2259

Close a panel, resolving its promise. The panel shown is the one closed unless another is named: a button inside a panel closes its own (the one shown, since it was clicked).

closePanel(value = null, id = null)
value = null*- what showPanel's promise resolves with
id = null?string- the panel to close, the one shown by default

closeAllPanels(value)

mmo-app.jhs:2280

Close every panel.

closeAllPanels(value = null)
value = null*- what each panel's promise resolves with

showNotification(css, content)

mmo-app.jhs:2296

Show a notification.

showNotification(css, ...content)Like every message of the shell (`showModal`), its content is text unless it is a node: what a caller got from a user or an api shows as it was typed. Markup it wrote itself goes through `MMO.core.dom.html()`.
cssstring- a tone class: success, warning, error, info
...content...*- its children (MMO.core.dom.appendChildren); one with a slot goes to that part of the card, title or footer (mmo-notification)
returnsHTMLElement
MMO.app.showNotification('success', MMO.core.dom.createElement('span', {slot: 'title'}, 'Saved'), file.name)

notify(content, title, button, butIcon)

mmo-app.jhs:2316

Show a notification with a title and a dismiss button.

notify(content, title, button = 'ok', butIcon = 'eye')
content*- the body: text, a node, or MMO.core.dom.html() (showNotification)
titlestring- text
button = 'ok'string- the dismiss label, text
butIcon = 'eye'string- the dismiss icon
returnsHTMLElement
MMO.app.notify(file.name + ' is saved.', 'Saved')

showModal(css, content)

mmo-app.jhs:2340

Show a modal dialog.

showModal(css, ...content)Its content is text unless it is a node: a name, an error's message, anything a caller got from a user or an api shows as it was typed and runs nothing -- the same goes for `ask`, `confirm`, `alert`,
See
error, showNotification, notify and showPanel. Markup the caller wrote itself goes through MMO.core.dom.html().
cssstring
...content...*- its children, MMO.core.dom.appendChildren
returnsHTMLDialogElement
MMO.app.showModal('info', MMO.core.dom.createElement('h2', {}, file.name), MMO.core.dom.html('<p>is <b>saved</b></p>'))

answer(node, value)

mmo-app.jhs:2356

Wire a dialog's buttons to its result: any <button value="..."> closes the dialog with that value, and the promise resolves with it -- false when the dialog is dismissed (escape, a click outside), so a caller can always tell "no" from "not answered".

answer(node, value)
nodeHTMLDialogElement
valuefunction(HTMLDialogElement, string):*- what to resolve with, from the button's value
returnsPromise

ask(title, content)

mmo-app.jhs:2385

Ask for a value: one input, and the answer.

ask(title, ...content)
titlestring- text
...content...*- shown above the input: text unless it is a node (showModal)
returnsPromise<string | false>what was typed, false when cancelled
MMO.app.ask('Rename « ' + folder.name + ' »', 'Its new name:')

confirm(title, content)

mmo-app.jhs:2408

Ask a yes/no question.

confirm(title, ...content)
titlestring- text
...content...*- text unless it is a node (showModal)
returnsPromise<boolean>
MMO.app.confirm('Delete « ' + file.name + ' »?', 'It cannot be recovered.')

alert(title, content)

mmo-app.jhs:2423

Say something, with one button to dismiss it.

alert(title, ...content)
titlestring- text
...content...*- text unless it is a node (showModal)
returnsPromise<boolean>true when it was acknowledged

error(title, content)

mmo-app.jhs:2444

The same, in the error tone.

error(title, ...content)An Error, as the title or in the content, is told in words a user reads (`describeError`): what happened, the server's own message when it is one, and the technical detail (url, status, stack) folded underneath. One error is one window: shown again -- `MMO.fetch*` shows it, then the caller who caught it says what it was doing -- it replaces the window it was in.
titlestring | Error- text, or an Error (then titled Une erreur est survenue)
...content...*- text unless it is a node (showModal); an Error is described
returnsPromise<boolean>
MMO.app.error(file.name, error.message)MMO.fetchFile(url).catch(e => MMO.app.error('Le document n\'a pas pu être généré', e))

describeError(error)

mmo-app.jhs:2473

An error as a user reads it: what happened, in French; the server's message when it is meant for a user (the framework's own name what they are about in [brackets], an id or a path is no such thing); the rest -- url, status, message, stack -- folded under « Détail technique ».

describeError(error)
errorError
returnsHTMLElement[]its paragraph and its folded detail
MMO.app.describeError(new MMO.RequestError(response, 'https://x/a.json (403): Forbidden')) // « Vous n'avez pas les droits... »

showConsoleTab(name)

mmo-app.jhs:2527

Show one pane of the developer drawer, and open the drawer if it was closed. The server pane is fetched the first time it is asked for, not on every page load: it is a tool, not a page region.

showConsoleTab(name)
namestring- logs or server

loadServerInfo(reload)

mmo-app.jhs:2542

Fill the server pane with what the server is made of: php, its extensions and every package the framework included, as AdminController::routeServerInfo answers them.

loadServerInfo(reload = false)
reload = falseboolean- fetch again even if the pane was already filled
returnsPromise

updateConsoleTags(console)

mmo-app.jhs:2584

Update the warning and error counters of the status bar.

updateConsoleTags(console)
consoleHTMLElement

gotoTop(event)

mmo-app.jhs:2593

Scroll back to the top of whichever container actually scrolls.

gotoTop(event = null)
event = null?Event

blockScreen(active)

mmo-app.jhs:2603

Block the screen while something runs.

blockScreen(active = true)
active = trueboolean

setThemeMode(scheme)

mmo-app.jhs:2611

Apply a colour scheme.

setThemeMode(scheme = 'default')
scheme = 'default'string- dark, light or default to follow the system

toggleDarkMode()

mmo-app.jhs:2628

Switch between the dark and light schemes.

toggleDarkMode()

Slots22

menu

mmo-app.jhs

rail entries written by hand, next to the ones MMO.config.menus adds

<slot name="menu">

side-foot

mmo-app.jhs

the foot of the rail, on the row the scheme switch ends

<slot name="side-foot">

user-info

mmo-app.jhs

the account line next to the account button, at the end of the top bar

<slot name="user-info">

user-card-body

mmo-app.jhs

what the account card shows when it is opened

<slot name="user-card-body">

brand

mmo-app.jhs

the brand in the top bar, for the landing and blog templates

<slot name="brand">

actions

mmo-app.jhs

buttons on the right of the top bar

<slot name="actions">

header

mmo-app.jhs

anything else on the top bar, after the actions

<slot name="header">

hero

mmo-app.jhs

the band above the page, for a landing

<slot name="hero">

page-title

mmo-app.jhs

the page header's title

<slot name="page-title">

page-actions

mmo-app.jhs

the page header's buttons

<slot name="page-actions">

navigation

mmo-app.jhs

a strip between the header and the content (filters, a sub-navigation)

<slot name="navigation">

aside

mmo-app.jhs

the rail on the right of the content, for an article

<slot name="aside">

panel-title

mmo-app.jhs

extra content at the end of the side panel's tab strip (its tabs are built by showPanel)

<slot name="panel-title">

panel

mmo-app.jhs

the body of the side panel

<slot name="panel">

status

mmo-app.jhs

the status bar, left of the log button

<slot name="status">

logs

mmo-app.jhs

the lines the server log tab of the developer drawer shows

<slot name="logs">

notifications

mmo-app.jhs

the notifications stacked in the corner

<slot name="notifications">

screen-blocker

mmo-app.jhs

what covers the page while it is blocked (blockScreen)

<slot name="screen-blocker">

CSS hooks12

.admin

mmo-app.jhs

the application shell: rail, breadcrumb, page tabs, side panel, status bar

.admin

.landing

mmo-app.jhs

the marketing shell: sticky translucent header, hero, sections, large footer

.landing

.blog

mmo-app.jhs

the editorial shell: header, content column, sticky rail, footer

.blog

.menu-collapsed

mmo-app.jhs

the rail is reduced to icons

.menu-collapsed

.menu-open

mmo-app.jhs

the rail is open, on a small screen

.menu-open

.has-tabs

mmo-app.jhs

the page has tabs: a small screen gives them a row of their own under the top bar

.has-tabs

.panel-open

mmo-app.jhs

the side panel is open

.panel-open

.console-open

mmo-app.jhs

the developer drawer (server log, server information) is open

.console-open

.toc-open

mmo-app.jhs

the table of contents is open

.toc-open

.palette-open

mmo-app.jhs

the command palette is open, over the page

.palette-open

.has-hero

mmo-app.jhs

the page filled the hero region, so the shell makes room for it

.has-hero

.has-user-card

mmo-app.jhs

the page filled the account card, so it gets a body above the logout link

.has-user-card