:root {
    --color-canvas: #15110d;
    --color-panel: #211a14;
    --color-panel-raised: #2b2118;
    --color-text: #f3e7cc;
    --color-text-muted: #b9aa8c;
    --color-accent: #d0a85a;
    --color-accent-strong: #edc978;
    --color-border: #6f5633;
    --color-danger: #d47a72;
    --color-success: #83a86b;
    /* A critical success is green like an ordinary success and brighter than one, so that
       the recent-rolls panel tells the two tiers apart by colour as well as by their text. */
    --color-critical: #a9dd63;
    /* The two duality dice, named for the sides of the roll rather than for their hues: the
       rules put amber on Hope and violet on Fear. Both are muted enough to sit on a raised
       panel without competing with the accent. */
    --color-die-hope: #e0a94e;
    --color-die-fear: #a58fd8;
    --font-body: ui-sans-serif, system-ui, sans-serif;
    --font-display: ui-serif, Georgia, serif;
    --text-sm: 0.875rem;
    --text-base: 1rem;
    --text-lg: 1.25rem;
    /* A page title inside the world shell, which has less room than a full-width page. */
    --text-md: 1.5rem;
    --text-xl: clamp(1.75rem, 3vw, 2.5rem);
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-6: 1.5rem;
    --space-8: 2rem;
    --space-12: 3rem;
    --radius-sm: 0.25rem;
    --radius-md: 0.5rem;
    --shell-nav-width: 16rem;
    --shell-widget-width: 17rem;
    --shadow-panel: 0 0.5rem 1.5rem rgb(0 0 0 / 0.2);
}

* { box-sizing: border-box; }

/* The `hidden` attribute is mapped to `display: none` only by the UA stylesheet, so any
   author rule setting `display` on an element wins over it regardless of specificity —
   `button { display: inline-block }` below would keep a hidden button visible. The
   attribute needs an author rule of its own to hold. */
[hidden] { display: none !important; }

html {
    background: var(--color-canvas);
    color: var(--color-text);
    font-family: var(--font-body);
    font-size: var(--text-base);
    line-height: 1.5;
    /* The stream's jump links are plain same-page anchors; this is what makes the move
       between its ends readable rather than a cut. */
    scroll-behavior: smooth;
}
/* A reader who asks for reduced motion gets the instant jump the anchor performs by itself. */
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
}

body { margin: 0; min-width: 20rem; }
h1, h2, h3, p { margin: 0; }
h1, h2, h3 { color: var(--color-text); font-family: var(--font-display); line-height: 1.15; }
h1 { font-size: var(--text-xl); }
h2 { font-size: var(--text-lg); }
a { color: var(--color-accent-strong); }
a:hover { color: var(--color-text); }

button, .button {
    appearance: none;
    background: var(--color-accent);
    border: 1px solid var(--color-accent-strong);
    border-radius: var(--radius-sm);
    color: #17110b;
    cursor: pointer;
    display: inline-block;
    font: inherit;
    font-weight: 700;
    padding: var(--space-2) var(--space-4);
    text-align: center;
    text-decoration: none;
}

button:hover, .button:hover { background: var(--color-accent-strong); color: #17110b; }
button:disabled, button:disabled:hover {
    background: var(--color-canvas);
    border-color: var(--color-text-muted);
    color: var(--color-text-muted);
    cursor: not-allowed;
}
button:focus-visible, a:focus-visible, input:focus-visible, textarea:focus-visible {
    outline: 3px solid var(--color-accent-strong);
    outline-offset: 2px;
}

form { display: grid; gap: var(--space-3); }
label { color: var(--color-text-muted); font-size: var(--text-sm); font-weight: 700; }
input, textarea {
    background: var(--color-canvas);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    color: var(--color-text);
    font: inherit;
    max-width: 100%;
    padding: var(--space-2) var(--space-3);
    width: 100%;
}
textarea { min-height: 9rem; resize: vertical; }
input[type="file"] { padding: var(--space-2); }

.notice {
    background: color-mix(in srgb, var(--color-danger) 18%, var(--color-panel));
    border: 1px solid var(--color-danger);
    border-radius: var(--radius-sm);
    color: var(--color-text);
    padding: var(--space-3) var(--space-4);
}

/* The auto side margins centre these pages, but they also switch off the stretch a grid
   item would otherwise get, leaving the box sized to its own content: the character form
   then narrows as soon as a lightweight NPC hides the stat grid. An explicit width fills
   the column up to the cap instead, so the content no longer sets the page width. */
.landing, .world-picker, .form-page {
    margin: var(--space-12) auto;
    max-width: 42rem;
    padding: 0 var(--space-4);
    width: 100%;
}
.landing, .world-picker, .form-page, .world-overview, .location-page { display: grid; gap: var(--space-6); }
.landing form, .world-card {
    background: var(--color-panel);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-panel);
    padding: var(--space-4);
}
.world-cards { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.world-card { display: grid; gap: var(--space-3); }
.description { overflow-wrap: anywhere; white-space: pre-wrap; color: var(--color-text-muted); }
.muted { color: var(--color-text-muted); }

.world-shell {
    display: grid;
    gap: var(--space-6);
    grid-template-columns: minmax(0, var(--shell-nav-width)) minmax(0, 1fr) minmax(0, var(--shell-widget-width));
    margin: 0 auto;
    max-width: 120rem;
    min-height: 100vh;
    padding: var(--space-6);
}

.shell-nav, .shell-content, .shell-widgets { align-self: start; }
.shell-nav {
    background: var(--color-panel);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-panel);
    display: grid;
    gap: var(--space-6);
    padding: var(--space-4);
}
.shell-nav header, .shell-nav nav, .shell-widgets { display: grid; gap: var(--space-3); }
.shell-nav h1 { font-size: var(--text-lg); }
/* Every link in the nav column is styled the same, whether it is a top-level destination,
   a location in the list or the world's own name: no underline by default, and a bottom
   border that appears on hover. */
.shell-nav a { border-bottom: 1px solid transparent; text-decoration: none; }
.shell-nav a:hover { border-color: var(--color-accent); }
.shell-nav nav > a { padding: var(--space-1) 0; }
.shell-nav ul { display: grid; gap: var(--space-2); list-style: none; margin: 0; padding: 0; }
.shell-nav li a { display: block; overflow-wrap: anywhere; }
.shell-nav .nav-divider { border-top: 1px solid var(--color-border); margin-top: var(--space-2); padding-top: var(--space-4); }
.shell-content { display: grid; gap: var(--space-6); min-width: 0; }

.world-overview, .location-page, .form-page {
    background: var(--color-panel-raised);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-panel);
    padding: var(--space-6);
}
.world-overview section, .location-page { display: grid; gap: var(--space-4); }
.world-card, .shell-widgets section, .reserved-directory, .map-form {
    background: var(--color-panel);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-panel);
    padding: var(--space-4);
}
.world-map, .location-image {
    background: var(--color-canvas);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    height: auto;
    max-width: 100%;
}
.location-page > h1 { font-size: var(--text-md); }
.scene-setting { display: grid; gap: var(--space-4); }
.scene-setting summary { cursor: pointer; width: fit-content; }
.location-image { display: block; margin-inline: auto; max-height: 25rem; object-fit: contain; }
.shell-widgets section { display: grid; gap: var(--space-2); }
.world-date, .eyebrow, .shell-widgets p { color: var(--color-text-muted); }
.eyebrow { font-size: var(--text-sm); letter-spacing: 0.08em; text-transform: uppercase; }

.character-directory, .character-sheet, .character-form fieldset, .composer {
    background: var(--color-panel);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--space-4);
}
.character-directory h3 { color: var(--color-text-muted); font-size: var(--text-sm); margin: 0; }
/* The row of buttons a panel or a form ends with. A form is a grid and stretches every
   child across it, which is right for its inputs and wrong for its buttons: inside this
   row they are flex items and so are sized by their own labels. Only a form that opts in
   by carrying the row gets that — the login and character forms keep their wide buttons. */
.directory-actions, .action-row { display: flex; flex-wrap: wrap; gap: var(--space-3); }
/* A table's seating, which opens its page. The list of who sits there is plain text with a
   control beside each name, and the row under it holds whatever this viewer may do to the
   table itself. */
.table-seating { display: grid; gap: var(--space-2); }
.table-members { display: grid; gap: var(--space-2); list-style: none; margin: 0; padding: 0; }
.table-members li { align-items: baseline; display: flex; gap: var(--space-3); }
.table-actions { align-items: start; display: flex; flex-wrap: wrap; gap: var(--space-3); }
.character-table-wrap, .invite-table-wrap { overflow-x: auto; }
.character-table, .invite-table { border-collapse: collapse; width: 100%; }
.character-table th, .character-table td, .invite-table th, .invite-table td {
    border-bottom: 1px solid var(--color-border);
    padding: var(--space-2);
    text-align: left;
    vertical-align: middle;
}
.character-table th, .invite-table th { color: var(--color-text-muted); font-size: var(--text-sm); }
.character-cell { align-items: center; display: flex; gap: var(--space-3); }
.directory-portrait {
    border-radius: var(--radius-sm);
    flex: 0 0 44px;
    height: 44px;
    object-fit: cover;
    width: 44px;
}
.stacked-pair { display: grid; gap: var(--space-1); }
.character-summary { align-items: flex-start; display: flex; gap: var(--space-3); margin-top: var(--space-3); }
.character-widget img { aspect-ratio: 1; border-radius: var(--radius-sm); flex: 0 0 4rem; object-fit: cover; width: 4rem; }
.character-sheet { display: grid; gap: var(--space-6); }
.character-sheet header { display: flex; flex-wrap: wrap; gap: var(--space-4); }
.character-sheet .portrait { border-radius: var(--radius-md); height: 12rem; object-fit: cover; width: 12rem; }
.portrait-placeholder { align-content: center; background: var(--color-canvas); color: var(--color-text-muted); text-align: center; }
.stats, .stat-grid { display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr)); }
/* An NPC form with the stats box unchecked hides the grid; a PC form has no such checkbox,
   so its grid is always shown. */
form:has(input[name="has_stats"]:not(:checked)) .stat-grid { display: none; }
.stats { margin: 0; }
.stats div { background: var(--color-canvas); border-radius: var(--radius-sm); padding: var(--space-3); }
.stats dd { font-size: var(--text-lg); margin: 0; }
.status { color: var(--color-accent-strong); text-transform: capitalize; }

/* Text for a screen reader and nothing else: each pip is a square with no writing on it, so
   the button carries what clicking it will do where only a reader that needs it will find it.
   The size and the clip are what keep it out of the layout without hiding it from the
   accessibility tree, which `display: none` would do. */
.visually-hidden {
    clip-path: inset(50%);
    height: 1px;
    overflow: hidden;
    position: absolute;
    white-space: nowrap;
    width: 1px;
}
/* One pool: its name and count above, its pips below. The row is the unit htmx swaps, so it
   is what the write answers with and nothing outside it moves. */
.pool-tracks { display: grid; gap: var(--space-3); }
.pool-track { display: grid; gap: var(--space-1); }
.pool-summary { align-items: baseline; display: flex; gap: var(--space-2); margin: 0; }
.pool-name { font-family: var(--font-display); }
.pool-count { color: var(--color-text-muted); font-size: var(--text-sm); }
/* Each pip is its own form, so the row lays the forms out rather than the buttons. */
.pips { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.pips form { display: contents; }
/* Every pip is a symbol rather than a box: a heart for HP, a shield for Armor, a spark for
   Hope, a bolt for Stress, a skull for Doom and a sword for a progress track. The pip is the
   icon alone, sized to the square the boxes used to have. One path per symbol serves both
   states, which is why the fill and the stroke are set here and not in the drawing: a marked
   pip is the silhouette filled in and an unmarked one the same outline, so what a pip says
   rests on shape, with the colour only reinforcing it. `evenodd` is what makes the skull's
   eye sockets holes in the marked silhouette; every other symbol is a single contour, which
   the rule leaves alone. */
.pip {
    background: none;
    border: 0;
    color: var(--color-text-muted);
    display: block;
    font-size: 1.25rem;
    line-height: 0;
    padding: 0;
}
.pip svg {
    fill: none;
    fill-rule: evenodd;
    stroke: currentColor;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-width: 1.7;
}
/* A marked pip is the accent, which is the one colour this app spends on what is in play.
   Doom is the exception: what the world is owed is drawn in the danger colour. */
.pip.filled { color: var(--color-accent-strong); }
.pip.filled svg { fill: currentColor; }
button.pip:hover { color: var(--color-accent-strong); }
.doom-track .pip.filled { color: var(--color-danger); }

/* The clock in the shell's widget column, marked out from the panels around it because it is
   the one thing there that is written to rather than read. */
.doom-widget .doom-track { margin-top: var(--space-2); }

/* What the group is fighting through, above the stream that narrates it. Tracks sit side by
   side: two share a row of the content column, and a third wraps under them. */
.track-list { display: grid; gap: var(--space-3); }
.track-grid {
    display: grid;
    gap: var(--space-3);
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}
.track-grid:empty { display: none; }
.progress-track {
    background: var(--color-panel-raised);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    display: grid;
    gap: var(--space-2);
    justify-items: start;
    padding: var(--space-3);
}

.visibility-badge {
    align-self: start;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
    padding: 0 var(--space-2);
}
.visibility-badge.shared { color: var(--color-success); }
.visibility-badge.private { color: var(--color-text-muted); }
.action-row form { display: inline; }

.post-stream { display: grid; gap: var(--space-4); }
.post {
    background: var(--color-panel);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    display: grid;
    gap: var(--space-2);
    padding: var(--space-4);
}
.post-header { align-items: center; display: flex; gap: var(--space-3); }
.post-portrait { border-radius: var(--radius-sm); flex: 0 0 44px; height: 44px; object-fit: cover; width: 44px; }
.post-byline { align-items: baseline; display: flex; flex-wrap: wrap; gap: var(--space-2); }
/* Sized close to body text so it fits the byline, and bold so the actor's name is still
   the part that stands out in it. */
.post-byline .sheet-link {
    font-family: var(--font-display);
    font-size: var(--text-base);
    font-weight: 700;
}
/* .post-source names where a post in a merged stream was written, and reads as a byline
   detail rather than as the post's own heading. */
.post-time, .post-author, .post-edited, .post-source { color: var(--color-text-muted); font-size: var(--text-sm); }
.post-body { margin: 0; }
/* Wide enough for the composer this also carries: a post runs to 4000 characters, so a
   narrower textarea would cost more in writing comfort than the width saves. Content
   taller than the viewport scrolls inside the dialog rather than out of it. */
.sheet-popover {
    background: var(--color-panel-raised);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    color: var(--color-text);
    max-height: 85vh;
    max-width: 54rem;
    overflow: auto;
    padding: var(--space-4);
    width: 92vw;
}
/* A sheet fragment is one panel that spaces its own children; a post form is several
   sibling elements that do not, and paragraphs have no margin of their own here. */
.popover-body { display: grid; gap: var(--space-3); }
.sheet-popover::backdrop { background: rgb(0 0 0 / 0.6); }
.post-controls { display: flex; gap: var(--space-3); font-size: var(--text-sm); }
/* The actor picker shows a single name, so it is sized to its content rather than to the
   form column: `justify-self` is what stops a grid item from stretching. */
.composer select { justify-self: start; max-width: min(100%, 22rem); }
/* The remaining-characters hint. post-stream.js unhides it for the last stretch of the
   limit and adds `danger` closer to it; the muted colour comes from the class the server
   renders alongside, and the two-class selector is what lets `danger` win over it. */
.composer-count, .composer-hint { font-size: var(--text-sm); margin: 0; }
.composer-hint { overflow-wrap: anywhere; }
.composer-count.danger { color: var(--color-danger); }
/* The button that opens the fuller roll examples and the chip a roll's label is set in are
   the same button: the type of the text around them, a thin outline and no fill, so neither
   reads as a badge. `popovertarget` is the whole mechanism in both, so each works with
   scripting off and takes focus like any other button. Only how each sits in its line differs,
   which is what the two rules below carry. */
.hint-more, .roll-chip {
    background: none;
    border: 1px solid var(--color-border);
    color: inherit;
    font: inherit;
    padding: 0 var(--space-2);
}
.hint-more:hover, .roll-chip:hover {
    background: none;
    border-color: var(--color-accent);
    color: var(--color-accent-strong);
}
/* The hint's button follows the sentence it was written after. */
.hint-more { margin-inline-start: var(--space-2); }
/* The examples themselves. The card takes the same box as a roll's breakdown (see
   `.roll-card` below) but no anchor name, so it opens centred in the top layer; Esc and a
   click outside close it. Each example is the markup to type with a line on what it shows. */
.hint-examples { display: grid; gap: var(--space-3); list-style: none; margin: 0; padding: 0; }
.hint-examples li { display: grid; font-size: var(--text-sm); gap: var(--space-1); }
.hint-examples code { color: var(--color-text); overflow-wrap: anywhere; }
.hint-examples span { color: var(--color-text-muted); }
.stream-pages, .stream-ends {
    align-items: baseline;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
}
.stream-ends { justify-content: space-between; }
/* Quieter than a page link: it moves the reader inside the page they are already on. */
.stream-jump { color: var(--color-text-muted); font-size: var(--text-sm); text-decoration: none; }
/* The switcher and the scene's own controls sit above the stream, each sized by its
   content rather than stretched across the column, so a one-field form takes a single row
   rather than stacking a full-width input and button the way the world and character forms
   do. */
.scene-switcher { align-items: baseline; display: flex; flex-wrap: wrap; gap: var(--space-3); }
.scene-switcher select { max-width: min(100%, 26rem); }
.scene-form summary, .scene-state { cursor: pointer; width: fit-content; }
.scene-form form { display: grid; gap: var(--space-2); margin-top: var(--space-3); }
/* The two scene forms sit side by side. `align-items: start` keeps the collapsed one at
   the top of the row while the other is expanded, instead of stretching it. */
.scene-actions { align-items: start; display: flex; flex-wrap: wrap; gap: var(--space-3); }
.cast-list { display: grid; gap: var(--space-2); list-style: none; margin: 0; padding: 0; }
.cast-list li { align-items: center; display: flex; gap: var(--space-2); }
.cast-portrait { border-radius: var(--radius-sm); flex: 0 0 32px; height: 32px; object-fit: cover; width: 32px; }
/* A roll stands inside the prose it was written in, so the chip is inline; the body around
   it is `pre-wrap`, which the chip and the card both opt out of, so a label's own line
   breaks and space runs render the same in either. Each tier sets one tint, which the
   card's verdict and the panel's line read. */
.roll-critical { --roll-tint: var(--color-critical); }
.roll-success-hope { --roll-tint: var(--color-success); }
.roll-success-fear { --roll-tint: var(--color-accent); }
.roll-failure-hope { --roll-tint: var(--color-text-muted); }
.roll-failure-fear { --roll-tint: var(--color-danger); }
/* A reaction is judged outside the Hope and Fear economy, so its three tiers take the plain
   readings of the same tints: the critical keeps the one criticals are known by, and success
   and failure take the palette's green and red with nothing between them. */
.roll-reaction-critical { --roll-tint: var(--color-critical); }
.roll-reaction-success { --roll-tint: var(--color-success); }
.roll-reaction-failure { --roll-tint: var(--color-danger); }
/* The wrapper only groups a chip with its own card; it must not become a box of its own in
   the middle of the prose. */
.roll { display: inline; }
/* The chip is the roll's label set in the reader's prose, so a post reads as a post and
   everything the roll consisted of stays in the card behind it. Its padding is inline only
   (see the shared rule above), which keeps the outline off the text without growing the line
   box; the margin keeps two chips written back to back from painting one border against the
   other. */
.roll-chip { display: inline-block; margin-inline: var(--space-1); white-space: normal; }
/* The card is a popover, so it is drawn in the top layer: however long the breakdown, it
   cannot stretch a post or push the stream around. Its laid-out state is the default here
   because a browser without the Popover API ignores both the `popover` attribute and the
   `popovertarget` on the chip: the button opens nothing, so the card is left standing in the
   flow under it as a breakdown the reader can still read. */
.roll-card, .hint-card {
    background: var(--color-panel-raised);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-panel);
    color: var(--color-text);
    display: grid;
    gap: var(--space-2);
    max-width: min(26rem, 92vw);
    padding: var(--space-4);
    white-space: normal;
}
/* A closed popover must not lay itself out, so a card is hidden until it is opened. The rule
   states the closed state rather than the open one on purpose, and is the whole feature guard:
   a browser that does not know `:popover-open` cannot parse the selector, and an invalid
   selector makes the browser drop the rule it belongs to. Such a browser is exactly the one
   that ignores the `popover` attribute and the chip's `popovertarget` as well, so dropping
   this rule leaves the card laid out in the flow under the chip, which is the fallback. */
.roll-card:not(:popover-open), .hint-card:not(:popover-open) { display: none; }
.roll-card-name { font-family: var(--font-display); font-weight: 700; }
.roll-card-tier { color: var(--roll-tint); font-weight: 700; }
/* A critical card holds the dice and this line and nothing else, so the verdict is set in
   capitals there. The casing is presentation only: the tier's own label stays as it is
   written, which is what the rolls panel and everything else read. The compound selector is
   what keeps the panel's one-line entry out of it. */
.roll-card-tier.roll-critical, .roll-card-tier.roll-reaction-critical { text-transform: uppercase; }
/* The word marking a reaction sits in the verdict's line but is not part of the verdict, so
   it is set back to the body weight and the muted colour while the tier keeps the tint. */
.roll-card-kind { color: var(--color-text-muted); font-weight: 400; }
.roll-breakdown { display: grid; font-size: var(--text-sm); gap: var(--space-1); list-style: none; margin: 0; padding: 0; }
.roll-breakdown li { display: flex; gap: var(--space-4); justify-content: space-between; }
/* A die's row carries its icon and its name. Only the icon is tinted: the words stay in the
   card's own text colour, so the die is named at full contrast whatever the tint does. */
.roll-die { align-items: center; display: inline-flex; gap: var(--space-2); }
.roll-die-hope { color: var(--color-die-hope); display: inline-flex; }
.roll-die-fear { color: var(--color-die-fear); display: inline-flex; }
.roll-breakdown .roll-total { border-top: 1px solid var(--color-border); font-weight: 700; padding-top: var(--space-1); }
/* Where anchor positioning exists the card opens against the chip that opened it. Every
   chip carries the same anchor name, so the wrapper confines each name to its own roll —
   without that scope the name resolves across the whole stream and a card lands beside
   somebody else's chip. Both properties are required here because a browser with the name
   but not the scope would do exactly that; without either the card stays centred in the
   viewport, which the top layer makes safe. */
@supports (anchor-name: --roll-chip) and (anchor-scope: --roll-chip) {
    .roll { anchor-scope: --roll-chip; }
    .roll-chip { anchor-name: --roll-chip; }
    .roll-card {
        margin: var(--space-1) 0 0 0;
        position-anchor: --roll-chip;
        position-area: bottom span-right;
        position-try-fallbacks: flip-block, flip-inline;
    }
}

/* One line per roll, in the narrow widget column: the actor, what was rolled, and how it
   landed, each on its own row so a long name or label wraps instead of squeezing the tier. */
.roll-log { display: grid; gap: var(--space-3); list-style: none; margin: 0; padding: 0; }
.roll-log li { display: grid; gap: 0; }
.roll-log-name { font-family: var(--font-display); }
.roll-log-tier { color: var(--roll-tint); font-size: var(--text-sm); }

.scene-header { display: grid; gap: var(--space-1); }
.scene-header p { align-items: baseline; display: flex; gap: var(--space-2); }
/* The group playing the scene, marked like the closed badge beside it so the header's two
   pieces of standing information read as one row. */
.scene-table, .scene-closed {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    color: var(--color-text-muted);
    font-size: var(--text-sm);
    padding: 0 var(--space-2);
}

@media (max-width: 62rem) {
    .world-shell { grid-template-columns: minmax(12rem, var(--shell-nav-width)) minmax(0, 1fr); }
    .shell-widgets { grid-column: 1 / -1; grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 44rem) {
    .world-shell { grid-template-columns: 1fr; padding: var(--space-4); }
    .shell-widgets { grid-column: auto; grid-template-columns: 1fr; }
    .landing, .world-picker, .form-page { margin: var(--space-8) auto; }
}
