/* Copyright © 2025 RPGScience. All rights reserved. No permission is granted to use, copy, modify, or distribute this file. */

/* Reuse before you add. The house rule for this site's CSS is at the top of
   tokens.css, and it applies to this file above all others. */

/* The pieces the app is built from: the page frame, the header and menus, the
   generator sidebar, buttons, form fields, dialogs, the map surface, popups and
   the account panel. Needs tokens.css before it.

   Anything a second panel could want should name a token rather than a value of
   its own. Raw colours are still fine for genuine one-offs: see-through
   overlays, the dark tooltip and debug palettes, gradients and the map
   highlight animations, none of which the light-surface tokens can express. */

.page-container { display: flex; gap: var(--spacing-lg); margin: 0; padding-top: 60px; /* Space for fixed header */ }
.container { display: flex; gap: var(--spacing-lg); width: 100%; }
.map-container,
.dungeon-map-container {
    contain: strict;
    position: relative;
    /* Responsive scaling: fit to available space while maintaining 1.5:1 aspect ratio */
    /* Right gap: 330px + 5px = 335px, Bottom gap: 70px + 5px = 75px */
    width: min(calc(100vw - 335px), calc((100vh - 75px) * 1.5));
    height: calc(min(calc(100vw - 335px), calc((100vh - 75px) * 1.5)) / 1.5);
    /* Minimum and maximum size constraints */
    min-width: 300px;
    min-height: 200px;
    max-width: 2400px;  /* 2x original size limit */
    max-height: 1600px; /* 2x original size limit */
    background-color: var(--border-color);
    padding: 0;
    overflow: hidden;
    margin-left: 320px; /* Increased margin for wider sidebar */
    margin-top: 16px;
    box-shadow: var(--worldmap-shadow);
    min-width: 600px;
    min-height: 400px;
    /* Stop double-click and drag from selecting/highlighting the map text. */
    -webkit-user-select: none;
    user-select: none;
}
.map-container:focus,
.dungeon-map-container:focus { outline: none; box-shadow: none; }

/* Ensure SVG layer fills the container */
#svgMapLayer {
    width: 100% !important;
    height: 100% !important;
}

/* Boot placeholder: shown on first paint until world generation starts */
.map-boot-placeholder {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 15px;
    background-color: var(--border-color);
    color: var(--text-secondary);
}
.map-boot-placeholder-text {
    margin: 0;
    font-size: 13px;
}
.map-boot-spinner {
    width: 35px;
    height: 35px;
    border: 4px solid rgba(0, 0, 0, 0.12);
    border-top-color: var(--accent-primary, #4a90e2);
    border-radius: 50%;
    animation: map-boot-spin 1s linear infinite;
}
@keyframes map-boot-spin {
    to { transform: rotate(360deg); }
}

/* Generator map stage: shown while a glade (or similar) finishes render + tree bake */
.generator-map-loading {
    position: absolute;
    inset: 0;
    z-index: 5;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 15px;
    background: rgba(26, 26, 26, 0.55);
    color: #f0f0f0;
    pointer-events: auto;
}
.generator-map-loading .map-boot-placeholder-text {
    color: #f0f0f0;
}

/* Loading modal: centred over map box (world generation and large exports) */
#loading-modal {
    position: absolute;
    inset: 0;
    z-index: 10;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(26, 26, 26, 0.8);
    pointer-events: auto;
}

/* Ensure player container fills the map container */
.player-container {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none; /* Allow clicks to pass through to underlying elements */
}

/* Ensure main world / dungeon map SVG scales to fill the container, but not nested SVGs */
.map-container svg:not(svg svg),
.dungeon-map-container svg:not(svg svg) {
    width: 100%;
    height: 100%;
}

/* Tooltip styles (mostly unchanged) */
.tooltip { position: relative; display: inline-block; cursor: help; padding: 0; margin: 0; }
.tooltip .tooltiptext {
    visibility: hidden;
    width: max-content;
    max-width: 220px;
    background-color: var(--accent-primary);
    color: var(--text-headings);
    text-align: left;
    border-radius: var(--radius-md, 4px);
    padding: 6px 10px;
    margin: 0;
    position: absolute;
    z-index: 9999;
    bottom: 125%;
    left: 0;
    opacity: 0;
    transition: opacity 0.3s;
    box-shadow: var(--shadow-popup, 0 0 10px rgba(0,0,0,0.5));
    font-size: var(--font-sm, 11px);
    pointer-events: none;
}
.tooltip.tooltip-right .tooltiptext { left: auto; right: 0; text-align: left; }
.tooltip.show .tooltiptext { visibility: visible; opacity: 1; pointer-events: auto; }
/* Fantasy map fonts (for SVG elements only) */
@font-face { font-family: 'Ovo'; src: url('/assets/fonts/Ovo.woff2') format('woff2'); }
@font-face { font-family: 'Lora'; font-style: normal; font-weight: 400; src: url('/assets/fonts/Lora.woff2') format('woff2'); }
@font-face { font-family: 'Lora'; font-style: normal; font-weight: 700; src: url('/assets/fonts/Lora-Bold.woff2') format('woff2'); }
@font-face { font-family: 'MorrisRomanBlack'; src: url('/assets/fonts/MorrisRomanBlack.woff2') format('woff2'); }
@font-face { font-family: 'GoudyMediaeval'; src: url('/assets/fonts/GoudyMediaeval.woff2') format('woff2'); }
@font-face { font-family: 'GoudyMediaevalDemiBold'; src: url('/assets/fonts/GoudyMediaevalDemiBold.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; src: url('/assets/fonts/Inter-Regular.woff2') format('woff2'); }

/* =================================================================== */
/* === NEW THEMATIC STYLES & UI OVERHAUL (Add this to your CSS) === */
/* =================================================================== */

/* 1. NEW HEADER / NAVIGATION
------------------------------------ */
.main-header {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 44px;
    background-color: var(--nav-bg); /* deep navy blue */
    display: flex;
    align-items: center;
    padding: 0 20px;
    z-index: 1100;
    box-shadow: var(--navbar-shadow);
}
.main-nav {
    margin-left: 20px;
}
.main-nav ul {
    list-style: none;
    display: flex;
    gap: 10px;
}
.main-nav a {
    text-decoration: none;
    color: #f6f6f6;
    padding: 4px 10px;
    border-bottom: 2px solid transparent;
    transition: all 0.2s ease-in-out;
}
.main-nav a:hover {
    color: var(--accent-primary);
}
.main-nav a.active {
    color: var(--accent-primary);
    background: rgba(249, 168, 37, 0.15);
    border-radius: 2px;
    border-bottom: none;
}

/* 2. THEMED SIDE PANEL
------------------------------------ */


.panel-side .form-control,
.panel-side .btn,
.panel-side .display-options-grid {
    max-width: 100%;
}


.panel-side {
    /* The dense context. Labels and controls come down a size, buttons keep the
       default so Generate stays easy to hit. Every control inside follows from
       this one line, which is why no rule below restates a font size. */
    --field-font: 11.5px;
    width: 300px;
    left: var(--spacing-lg);
    top: 60px;
    z-index: 100;
    max-height: calc(100vh - 70px);
    box-sizing: border-box;
    overflow-x: hidden;
    overflow-y: auto;
    background: var(--bg-sidebar);
    color: var(--text-primary);

    border: 1px solid var(--border-color);
    padding: 15px;
    /* Scrollbar is hidden everywhere; scrolling still works via wheel,
       touch and keyboard. Avoids the flash of a scrollbar during accordion
       expansion when nested content briefly overflows the panel's max
       height. */
    scrollbar-width: none;            /* Firefox */
    -ms-overflow-style: none;         /* IE/old Edge */
    box-shadow: var(--panel-shadow);
}
.panel-side::-webkit-scrollbar { display: none; } /* Chromium / Safari */
.panel-side h1 {
    font-size: 22px;
    font-weight: 700;
    color: var(--text-headings);
    margin-bottom: 4px;
}
.panel-side h3 {
    color: var(--text-headings);
    margin-top: 12px;
    font-size: 15px;
    font-weight: 500;
}
.panel-side .h3-subtext {
    color: var(--text-byline);
    font-weight: normal;
}


/* 3. ACCORDION (NEW IMPLEMENTATION)
------------------------------------ */
.accordion-item {
    border-bottom: 1px solid var(--border-color);
    margin-bottom: 5px;
    position: relative;
}
.accordion-item.open { z-index: 10; }
.accordion-header {
    padding: 8px 24px 8px 4px;
    background: transparent;
    border: none;
    width: 100%;
    text-align: left;
    cursor: pointer;
    font-size: 16px;
    font-weight: 500;
    color: var(--text-headings);
    position: relative;
    z-index: 0;
    user-select: none;
}
.accordion-header:hover {
    background-color: rgba(157, 107, 83, 0.1);
}
.accordion-header::after {
    content: '';
    position: absolute;
    right: 8px;
    top: 50%;
    width: 5px;
    height: 5px;
    border-right: 2px solid #5f5f5f;
    border-bottom: 2px solid #5f5f5f;
    transform: translateY(-50%) rotate(45deg);
    transition: transform 0.2s ease;
}
.accordion-item.open .accordion-header::after {
    transform: translateY(-50%) rotate(225deg);
}
.accordion-item.open .accordion-header {
    background-color: rgba(226, 226, 230, 0.4);
}
.accordion-content {
    overflow: hidden;
    height: 0;
    transition: height 0.2s ease;
    position: relative;
    z-index: 1; /* ensure tooltips inside can layer above header */
}
.accordion-content-inner {
    padding: 0 5px 15px 5px;
}
.accordion-item.open > .accordion-content {
    height: var(--content-height);
    background-color: rgba(226, 226, 230, 0.4);
}


/* 4. THEMED FORM CONTROLS & BUTTONS
------------------------------------ */
/* Fixed-width label slot used in label + control rows where the label
   needs to align consistently. Drop-in replacement for the inline
   style="width: 75px;" used throughout the dungeon panel. */
.panel-side .form-label-fixed {
    width: 75px;
}
/* Form control that should take the remaining row width. Drop-in
   replacement for the inline style="flex: 1 1 auto; min-width: 0;"
   used throughout the dungeon panel. */
.panel-side .form-control-grow {
    flex: 1 1 auto;
    min-width: 0;
}
.panel-side .form-control:focus {
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 2px rgba(157, 107, 83, 0.2);
}
/* The sidebar sits on a darker face than a dialog does, so the quiet role is
   given a solid colour here instead of an edge. These are the companion buttons
   that sit in a row beside a field, Randomise and Reset and the export grid, so
   they take the field size rather than the full button size. */
.panel-side .btn-secondary {
    --btn-font: var(--field-font);
    background-color: var(--accent-secondary);
    border-color: transparent;
    color: #fff;
}
.panel-side .btn-secondary:hover {
    background-color: var(--accent-secondary-hover);
}
/* The Random buttons on the dungeon, cave and glade panels are companion buttons too,
   sitting in a row between the Seed field and Reset, so they take the field size and
   line up with both. They keep the loud colour because they are the main action, which
   is why the rule above does not already cover them. */
#dg-randomSeedBtn,
#cv-randomSeedBtn,
#gl-randomSeedBtn {
    --btn-font: var(--field-font);
}
.panel-side .display-options-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px 10px;
    margin-bottom: 14px;
}

/* 5. LEGEND & CTA BLOCK
------------------------------------ */
.legend-image-container {
    font-size: 13px;
}
.legend-icon {
    border: 1px solid #e2d8c3;
    border-radius: 2px;
}

/* === BASE LAYOUT & PANEL STYLES === */
.container {
    display: flex;
    gap: var(--spacing-lg);
    width: 100%;
}
.page-container {
    display: flex;
    gap: var(--spacing-lg);
    margin: 0;
    padding-top: var(--header-height); /* Space for fixed header */
}
.panel {
    padding: var(--spacing-lg);
    border-radius: var(--radius-lg);
    overflow-y: auto;
}
.panel-fixed {
    position: fixed;
}




/* === BUTTONS ===
   One box, four roles, one size scale. Anything a person clicks that looks like
   a button gets .btn and one role. Do not write a new button rule: to change the
   size in a context, restate --btn-pad and --btn-font on the container.

   The edge is transparent rather than absent so a button that shows one is the
   same size as a button that does not, and rows of them line up.

   Never paint a button face in --bg-input. That is an input surface and sits one
   shade off --bg-panel, so the button vanishes into the panel. */
.btn {
    display: inline-block;
    background-color: var(--bg-control);
    color: var(--text-primary);
    padding: var(--btn-pad);
    border: 1px solid transparent;
    border-radius: 4px;
    cursor: pointer;
    font-size: var(--btn-font);
    white-space: nowrap;
    transition: background-color 0.2s, border-color 0.2s, color 0.2s;
}
.btn:hover {
    background-color: var(--bg-control-hover);
}
/* The one that finishes the job: Generate, Save, Confirm. White text is a
   deliberate choice for the house style, not an oversight, which is why
   --accent-primary sits a step darker than the brightest yellow. Keep these
   labels bold and never smaller than the button scale. */
.btn-primary {
    background-color: var(--accent-primary);
    color: #fff;
}
.btn-primary:hover {
    background-color: var(--accent-primary-hover);
}
/* The alternative, which must not shout. Face and panel are both light greys,
   so the edge is what gives this one its shape. */
.btn-secondary {
    background-color: var(--bg-control);
    color: var(--text-primary);
    border-color: var(--border-color);
}
.btn-secondary:hover {
    background-color: var(--bg-control-hover);
}
/* Deletes and resets, so it has to read as a warning at a glance. */
.btn-danger {
    background-color: var(--danger);
    color: #fff;
}
.btn-danger:hover {
    background-color: var(--danger-hover);
}
/* Moving between views rather than acting on the map. */
.btn-nav {
    background-color: var(--accent-blue);
    color: #fff;
}
.btn-nav:hover {
    background-color: var(--accent-blue-hover);
}
.btn:disabled {
    background-color: var(--bg-control);
    border-color: var(--border-color);
    color: var(--text-muted);
    cursor: not-allowed;
}

/* === PAGE CTAS ===
   The large call to action on the landing, gallery, FAQ and pricing pages. Same
   two faces as .btn, at page scale: .hub-cta rides inside a generator card and
   follows the card's hover, .gal-cta is a link that stands on its own.

   Quiet face by default, which is what a hub card wants at rest. A .gal-cta is
   solid unless it is marked quiet, since a page has one main action. */
.hub-cta,
.gal-cta {
    display: inline-block;
    padding: 10px 22px;
    font-size: var(--font-lg);
    font-weight: 700;
    text-align: center;
    text-decoration: none;
    color: var(--text-primary);
    background: var(--bg-control);
    border: 1px solid var(--border-color);
    transition: background-color 0.15s, color 0.15s, border-color 0.15s;
}

/* White on yellow, matching .btn-primary. These are already bold and 15px. */
.hub-cta-solid,
.gal-cta:not(.gal-cta-quiet),
.hub-card:hover .hub-cta {
    color: #fff;
    background: var(--accent-primary);
    border-color: var(--accent-primary);
}

/* === FORM CONTROLS === */
.form-control {
    background-color: var(--bg-control);
    border: 1px solid var(--border-color);
    border-radius: 4px;
    padding: var(--field-pad);
    font-size: var(--field-font);
    color: var(--text-primary);

    /* No bottom margin. A field is often centred in a row next to a button, and a
       margin on one side only would lift the field half a pixel and make the button
       look taller than it is. Stacked fields get their spacing from the row instead. */
    width: auto;
    outline: none;
    transition: border-color 0.2s;
}
.form-control:focus {
    border-color: var(--accent-primary);
    background-color: var(--bg-control-hover);
}
.form-control-number {
    width: 80px;
}
.form-control-text {
    width: 140px;
}
.form-control-select {
    min-width: 60px;
    padding-right: 18px;
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
}
/* Nothing sets a size on body, so without a size of its own a label falls back
   to the browser's 16px and towers over its own control. */
.form-label,
.checkbox-label,
.h3-subtext {
    color: var(--text-primary);
    font-size: var(--field-font);
}
.form-label {
    margin-right: 4px;
}
.form-checkbox {
    accent-color: var(--accent-primary);
    vertical-align: middle;
    margin-right: 4px;
}

/* A field stacked as label, control, then hint. Use this outside the sidebar,
   where .control-group puts the label beside the control instead. The control
   fills the width on its own, since a flex column stretches its children. */
.form-group {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.form-group label {
    font-weight: 600;
    color: var(--text-primary);
    font-size: var(--field-font);
}

/* The row of buttons that closes a form. */
.form-actions {
    display: flex;
    gap: 12px;
    justify-content: flex-end;
}

.form-actions-centre {
    justify-content: center;
}

/* === SELECT BOX OPTION STYLING === */
select option {
    background-color: white;
    color: black;
}

select option:checked,
select option[selected] {
    background-color: #f8f9fa !important; /* Very light grey for selected items */
    color: black;
}

/* === CONTROL GROUPS === */
/* A row of controls, usually a label with a field and sometimes a button beside it.
   The row owns the space beneath it, so the controls inside need no margins of
   their own and every row is separated by the same amount. */
.control-group {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-bottom: 7px;
}

#export-format-options label:has(:disabled) {
    color: #b0b0b0;
}

#export-format-options label.tooltip {
    cursor: default;
}

#export-format-options label.tooltip:has(#export-format-webp, #export-format-png)::after {
    content: '?';
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 11px;
    height: 11px;
    margin-left: 2px;
    border: 1px solid #909090;
    border-radius: 50%;
    color: #909090;
    font-size: 8px;
    font-weight: 700;
    line-height: 1;
    vertical-align: super;
}

/* === HELP TEXT === */
.help-text {
    color: var(--text-secondary);
    font-size: var(--field-font);
    line-height: 1.5;
}

/* === LEGEND === */
.legend-image-container {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4px 24px;
    margin-top: 8px;
}
.legend-image-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 2px;
}
.legend-icon {
    width: 40px;
    height: 30px;
    margin-right: 6px;
    border: 1px solid #8a5e48;
    border-radius: 2px;
}

/* Inter for the whole interface. Text inherits it from body; the four form
   elements do not inherit font by default, so they are named. */
body,
button,
input,
select,
textarea {
    font-family: 'Inter', sans-serif;
}

.main-nav a {
    font-size: 13px;
}

.auth-buttons {
    display: flex;
    gap: 10px;
    margin-left: auto;
    align-items: center;
}

/* Hamburger Menu Button */
.hamburger-btn {
    display: none;
    flex-direction: column;
    justify-content: space-around;
    width: 30px;
    height: 24px;
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 0;
    margin-left: auto;
    z-index: 1200;
}

.hamburger-line {
    width: 100%;
    height: 3px;
    background-color: #fff;
    border-radius: 2px;
    transition: all 0.3s ease;
}

.hamburger-btn.active .hamburger-line:nth-child(1) {
    transform: rotate(45deg) translate(6px, 6px);
}

.hamburger-btn.active .hamburger-line:nth-child(2) {
    opacity: 0;
}

.hamburger-btn.active .hamburger-line:nth-child(3) {
    transform: rotate(-45deg) translate(7px, -7px);
}

/* Mobile Menu Overlay */
.mobile-menu-overlay {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.5);
    z-index: 1150;
    opacity: 0;
    transition: opacity 0.3s ease;
}

.mobile-menu-overlay.active {
    display: block;
    opacity: 1;
}

/* Mobile Menu */
.mobile-menu {
    display: none;
    position: fixed;
    top: 0;
    right: -300px;
    width: 280px;
    height: 100%;
    background: var(--nav-bg);
    z-index: 1200;
    padding: 60px 20px 20px 20px;
    overflow-y: auto;
    transition: right 0.3s ease;
    box-shadow: -2px 0 10px rgba(0, 0, 0, 0.3);
}

.mobile-menu.active {
    right: 0;
}

.mobile-menu-list {
    list-style: none;
    padding: 0;
    margin: 0 0 30px 0;
}

.mobile-menu-list li {
    margin: 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}

.mobile-menu-link {
    display: block;
    padding: 16px 10px;
    color: rgba(255, 255, 255, 0.8);
    text-decoration: none;
    font-size: 16px;
    font-weight: 500;
    transition: all 0.2s ease;
}

.mobile-menu-link:hover,
.mobile-menu-link.active {
    color: #f9a825;
    background: rgba(249, 168, 37, 0.1);
}

/* A roomy context: full width entries meant for a thumb. */
.mobile-menu-auth {
    --btn-pad: 10px 20px;
    --btn-font: 14px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding-top: 20px;
    border-top: 1px solid rgba(255, 255, 255, 0.1);
}

.mobile-menu-auth .btn {
    width: 100%;
    text-align: center;
}
/* The outline button role, for the two dark surfaces: the header bar and the
   mobile menu. Size comes from the button scale like every other role, so the
   same markup is small in the header and thumb-sized in the menu. */
.btn-login {
    border: 1px solid rgba(249,168,37,0.7);
    background: transparent;
    color: rgba(255,255,255,0.8);
}
.btn-login:hover {
    background: rgba(249,168,37,0.15);
    color: #fff;
    border-color: rgba(249,168,37,0.9);
}

.display-options-grid > label {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    cursor: pointer;
}

/* Removed redundant Arial font override - Inter is applied globally */

/* The padding only exists to give the logo a bigger tap target, filling the
   44px header. The matching negative margins cancel it, so the logo still sits
   where it always has and the header keeps its height. */
.header-logo-link {
    display: flex;
    align-items: center;
    padding: 4px 12px;
    margin: -4px -12px;
}

.header-logo-img {
    margin-left: -10px;
}

/* === App Title Lockup === */
.app-title {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    margin: 10px 0 4px 0;
    line-height: 1.1;
}
.app-badge {
    position: absolute;
    top: -5px;
    right: -7px;
    padding: 3px 18px;
    font-size: 7.6px;
    font-weight: 700;
    letter-spacing: 0.2px;
    color: white;
    background: #5a6c7d;
    transform: rotate(45deg) translate(30%, -65%);
    transform-origin: center;
    white-space: nowrap;
}

.fixed-panel-stack {
    position: absolute;
    top: 20px;
    right: 20px;
    z-index: 1200;
    display: flex;
    flex-direction: column;
    gap: 12px;
    width: 260px;
    pointer-events: auto;
}

.panel-debug, .panel-progress {
    background: #232323;
    color: #8fd6ff;
    box-shadow: 0 3px 8px rgba(0,0,0,0.2);
    border-radius: 4px;
    padding: 12px 10px 10px 10px;
    margin: 0 0 4px 0;
    margin-bottom: 10px;
    /* Removed Arial override - using global Inter font */
    font-size: 10px;
    text-align: left;
}
.generation-progress-container {
    display: flex;
    flex-direction: column;
    gap: 0px;
}
.progress-item {
    font-size: 9px;
    line-height: 1.2;
    padding: 4px 8px;
    background: #333;
    border-radius: 3px;
    margin-bottom: 0px;
    margin-top: 0px;
    color: #e0e0e0;
}
.progress-item-name {
    font-weight: bold;
    color: #bde0ff;
}
.progress-item-time {
    color: #3ecf4c;
    float: right;
}
.progress-item-count {
    display: inline;
    color: #b0b0b0;
    margin-left: 8px;
    margin-top: 0;
    margin-bottom: 0px;
    font-size: 9px;
}

/* Removed duplicate .heading-main - defined in responsive.css */



/* Default: NO blue glow on editable elements */
[data-editable="true"] {
    filter: none !important;
    cursor: default;
    overflow: none;
}

/* Label editing mode ONLY: Blue outline on editable elements */
.label-edit-mode [data-editable="true"] {
    filter: url(#editable-outline) !important;
    cursor: pointer !important;
    overflow: visible !important;
}

/* Only for labels explicitly marked as not visible */
.label-edit-mode [data-editable="true"][data-visible="false"] {
    opacity: 0.6 !important;
    stroke-opacity: 1 !important;
}

.label-edit-mode [data-editable="true"]:hover {
    filter: url(#editable-outline-hover) !important;
}

/* Grab cursor for the currently selected draggable element */
.label-edit-mode [data-editable="true"].re-grab-cursor {
    cursor: grab !important;
}

/* Special handling for transparent rect elements - make them barely visible for feMorphology */
.label-edit-mode rect[data-editable="true"] {
    stroke: rgba(255, 255, 255, 1) !important;
    stroke-width: 0.8 !important;
}

.label-edit-mode rect[data-editable="true"]:hover {
    stroke: rgba(255, 255, 255, 1) !important;
    stroke-width: 0.8 !important;
}

.player-container {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none; /* Only SVG inside should handle events */
    z-index: 20000;
}
#worldmap-svg {
    pointer-events: none;
}

.editor-preview-layer {
    position: absolute;
    inset: 0;
    pointer-events: none;
    overflow: hidden;
    z-index: 2;
}
.editor-preview-layer > * {
    position: absolute;
    left: 0;
    top: 0;
    transform-origin: 0 0;
    will-change: transform;
}
.editor-preview-layer svg {
    overflow: visible;
}
.editor-preview-layer img {
    display: block;
}

/* Minimap viewport ripple: HTML overlay, 3 pulses then idle (see pulseMinimapViewportRipple). */
.detail-popup-minimap { position: relative; }
.minimap-viewport-ripple {
    position: absolute;
    pointer-events: none;
    border: 3px solid #fff;
    box-sizing: border-box;
    transform-origin: center;
}
@keyframes minimap-viewport-ripple-pulse {
    from { transform: scale(1); opacity: 1; }
    to { transform: scale(2.5); opacity: 0; }
}

/* Popup Detail Shadow Reduction for Region Editor (shadow is on left column only, not minimap) */
#detail-popup.region-editor-shadow-reduced #detail-popup-content {
    box-shadow: 0px 12px 32px 0 rgba(0,0,0,0.3), 0 2px 6px 0 rgba(0,0,0,0.18);
}

/* POI click affordance in the popup detail map.
   The rect with id "popup-poi-icon-tooltip-*" is the invisible hit area
   that sits on top of each POI icon (see popupPOIs.js). Styling it on
   :hover gives a clear "this is clickable" cue without altering the
   rendered POI icons. Hover state never fires during the SVG-to-raster
   export (the serialised SVG is loaded into an <img>), so the exported
   image is identical to the unhovered map. */
#popup-detail-svg [id^="popup-poi-icon-tooltip-"] {
    cursor: pointer;
    transition: fill 0.12s ease, stroke 0.12s ease;
}
#popup-detail-svg [id^="popup-poi-icon-tooltip-"]:hover {
    fill: rgba(255, 215, 0, 0.18);
    stroke: rgba(255, 215, 0, 0.95);
    stroke-width: 2.5;
}

/* POI label click affordance. The label <text> is wrapped in a <g> by
   popupLabels.js for POI labels only (not settlements). The <rect> sibling
   inside that group is transparent at rest, so the static SVG and raster
   export are unchanged, and gets the same golden halo as the POI icon on
   hover. The pointer-events override lets the transparent rect catch hover
   anywhere in the bbox, not only on the painted text glyphs. */
#popup-detail-svg g.popup-poi-label,
#popup-detail-svg g.popup-poi-label * {
    cursor: pointer;
}
#popup-detail-svg g.popup-poi-label .popup-poi-label-hit {
    pointer-events: all;
    transition: fill 0.12s ease, stroke 0.12s ease;
}
#popup-detail-svg g.popup-poi-label:hover .popup-poi-label-hit {
    fill: rgba(255, 215, 0, 0.18);
    stroke: rgba(255, 215, 0, 0.95);
    stroke-width: 2.5;
}
/* Region edit mode suppresses the label hover halo and the pulse
   animation, matching the POI icon behaviour. The icon achieves this
   for free via its data-editable="true" attribute being caught by the
   editor's !important hover rules; the label hit-rect deliberately
   has no data-editable (we don't want the editor's white outline on
   it), so we suppress the gold halo explicitly here. */
#detail-popup.label-edit-mode #popup-detail-svg g.popup-poi-label:hover .popup-poi-label-hit,
#detail-popup.label-edit-mode #popup-detail-svg.show-poi-hints g.popup-poi-label .popup-poi-label-hit {
    fill: transparent;
    stroke: transparent;
    animation: none;
}
/* Pulse animation applied to every POI hit-rect while the first-session
   click hint is visible, so users can see every clickable POI at a
   glance. Fades fill and stroke from invisible to gold and back. The
   class is added to the live SVG only and removed when the hint is
   dismissed, so the raster export is never affected. The per-element
   :hover rule above is more specific than this one for the hovered
   POI, so hovering still locks the gold halo on a single target while
   the others keep pulsing. */
@keyframes poi-hint-pulse {
    0%, 100% { fill: rgba(255, 215, 0, 0);    stroke: rgba(255, 215, 0, 0); }
    50%      { fill: rgba(255, 215, 0, 0.30); stroke: rgba(255, 215, 0, 1.0); }
}
#popup-detail-svg.show-poi-hints [id^="popup-poi-icon-tooltip-"],
#popup-detail-svg.show-poi-hints g.popup-poi-label .popup-poi-label-hit {
    stroke-width: 2.5;
    animation: poi-hint-pulse 1.4s ease-in-out infinite;
}

/* Session hint banner. Built by js/ui/sessionHint.js.
   Appears centred over a map the first time something is opened each browser
   session: the region popup ("Click a Point of Interest...") and a battlemap
   opened from a point of interest. The flags are in-memory, so a reload
   re-arms them. Lives outside the SVG so the raster export is unaffected.

   Wears nearly the same surface as the walkthrough card in tutorial.css, since
   the two are the only onboarding notices in the app and a reader meets them
   within moments of each other. The card has a yellow title bar naming the
   tutorial, because it takes over the screen without being asked. This one is
   a single question that can be ignored, so the accent stays a quiet stripe.
   Kept as its own rule rather than sharing the card's class, because the card
   is fixed and placed from script while this sits inside whatever it was
   given. */
.session-hint {
    position: absolute;
    top: 25%;
    left: 50%;
    transform: translate(-50%, -50%);
    /* Without this the banner stretches to whatever room the map gives it, so
       a longer message runs out into one very wide line. Same measure as the
       walkthrough card, which is the other notice a reader meets. */
    max-width: 300px;
    background-color: var(--bg-panel);
    color: var(--text-primary);
    padding: var(--spacing-xl);
    border: 1px solid var(--border-color);
    border-top: 3px solid var(--accent-primary);
    border-radius: 4px;
    box-shadow: var(--shadow-popup, 0 0 10px rgba(0,0,0,0.5));
    font-family: 'Inter', sans-serif;
    font-size: var(--font-lg, 14px);
    font-weight: 500;
    text-align: center;
    cursor: pointer;
    user-select: none;
    transition: opacity 0.4s ease;
    /* #detail-popup is positioned with a z-index of its own, so this stacks
       against the popup's contents rather than against the world map. Kept
       well clear of them all so the banner is never buried by the minimap or
       the editor overlay. */
    z-index: 20001;
}
/* showSessionHint takes any message string, so a \n in one becomes a real
   line break. The battlemap hint relies on this to keep its two sentences
   apart. */
.session-hint-message {
    white-space: pre-line;
}
.session-hint-action {
    margin-top: var(--spacing-lg);
}
/* Faded dark text worked on the old orange fill. On the panel it needs a
   real muted colour instead, the same one the rest of the app uses.
   The dismiss line is a button so the keyboard can reach it, so it also has
   to be stripped back to looking like the plain text it used to be. */
.session-hint-note,
.session-hint-dismiss {
    display: block;
    width: 100%;
    margin-top: var(--spacing-md);
    padding: 0;
    background: none;
    border: none;
    font-family: inherit;
    font-size: var(--font-sm, 10px);
    font-weight: 400;
    color: var(--text-secondary);
}
.session-hint-dismiss {
    cursor: pointer;
}

/* ============================================================================
   MODAL OVERLAY - Proper blocking modals at document root
   ============================================================================ */
.modal-overlay {
    position: fixed !important;
    top: 0 !important;
    left: 0 !important;
    width: 100vw !important;
    height: 100vh !important;
    z-index: 50000 !important;
    pointer-events: auto !important;
    background: rgba(0, 0, 0, 0.6);
}

.modal-overlay.hidden {
    display: none !important;
}

/* Ensure modal content blocks all interactions */
.modal-overlay:not(.hidden) {
    display: flex !important;
    align-items: center;
    justify-content: center;
}

/* ============================================================================
   DIALOG
   The shell shared by every panel that floats over the map: My Worlds, Save
   World, the Codex, the delete warning, and the sign-in and account modals. A
   dialog is a flat panel with a header bar above a body. Size is the only thing
   a panel sets for itself.
   ============================================================================ */
.dialog {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--bg-panel);
    border: 1px solid var(--border-color);
    box-shadow: var(--shadow-popup);
}

/* Title on the left, close button hard right. */
.dialog-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 4px 24px;
    border-bottom: 1px solid var(--border-color);
    background: var(--bg-input);
}

.dialog-header h1,
.dialog-header h2,
.dialog-header h3 {
    margin: 0;
    font-size: 20px;
    font-weight: 600;
    color: var(--text-primary);
}

.dialog-close {
    background: none;
    border: none;
    font-size: 28px;
    line-height: 1;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 0 4px;
    border-radius: 4px;
    transition: background-color 0.2s, color 0.2s;
}

.dialog-close:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
}

/* A dialog with no header bar, for the ones that are read top to bottom: sign
   in, account settings and the export warning. The gap does the spacing, so no
   child needs a margin, and a child that is hidden leaves no hole behind. */
.dialog-card {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 32px;
    position: relative;
    max-height: 90vh;
    overflow-y: auto;
}

.dialog-card h2 {
    margin: 0;
    font-size: 22px;
    font-weight: 700;
    color: var(--text-headings);
}

.dialog-card ul {
    margin: 0;
    padding-left: 20px;
}

/* The line that introduces the block under it. */
.dialog-card-lead {
    font-size: var(--font-lg);
    font-weight: 600;
    color: var(--text-headings);
}

/* The export dialog: wider than the sign-in card, with roomier sides. */
#export-confirm-panel {
    min-width: 400px;
    max-width: 520px;
    padding: 32px 48px;
}

.export-spec-time {
    color: #b02300;
    font-weight: bold;
    white-space: pre-line;
}

.export-region-list {
    margin-left: 10px;
    line-height: 1.4;
    white-space: pre-line;
}

/* === PROGRESS BAR ===
   A dark track with a green fill whose width is set from script. Used by the
   generator's loading overlay and by the export dialog. */
.progress-bar-track {
    width: 100%;
    height: 18px;
    background: #222;
    border-radius: 8px;
}

.progress-bar-fill {
    height: 100%;
    width: 0%;
    background: linear-gradient(90deg, #3ecf4c 0%, #2e8b57 100%);
    border-radius: 8px;
}

.progress-bar-label {
    margin-bottom: 8px;
    font-size: var(--font-lg);
    color: var(--text-headings);
    text-align: center;
}

/* The card shown while a world is generated or an export runs. */
.loading-card {
    min-width: 240px;
    text-align: center;
}

/* While a world is generated the step line becomes a running list, so it reads
   better small and left aligned. Exports show a single percentage instead. */
.progress-bar-log {
    font-size: var(--font-sm);
    color: var(--text-secondary);
    text-align: left;
}

/* The tally of what is being drawn, listed under the message. */
.loading-feature-list {
    font-size: var(--font-sm);
    color: var(--text-secondary);
    line-height: 1.4;
    margin-left: 10px;
    white-space: pre-line;
}

/* Amber notice inside a dialog, for a choice with a cost attached. */
.dialog-warning {
    padding: 12px;
    font-weight: 500;
    color: #b45309;
    background: rgba(245, 158, 11, 0.1);
    border: 1px solid rgba(245, 158, 11, 0.3);
}

/* ============================================================================
   DENSITY SLIDERS - Settlement and POI Controls
   ============================================================================ */

/* Main slider styling matching region editor */
input.density-slider {
    -webkit-appearance: none;
    appearance: none;
    height: 8px;
    border-radius: 8px;
    background: var(--bg-control);
    border: 1px solid var(--border-color);
    outline: none;
    accent-color: #4B5563;
}

/* WebKit slider track */
input.density-slider::-webkit-slider-runnable-track {
    height: 8px;
    border-radius: 8px;
    background: var(--bg-control);
    border: 1px solid var(--border-color);
}

/* WebKit slider thumb */
input.density-slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background: #F8F8F8;
    border: 1.5px solid #8B95A3;
    margin-top: -4px;
}

/* Firefox slider thumb */
input.density-slider::-moz-range-thumb {
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background: #F8F8F8;
    border: 2px solid #7B8593;
}

/* Firefox slider track */
input.density-slider::-moz-range-track {
    height: 8px;
    border-radius: 8px;
    background: var(--bg-control);
    border: 1px solid var(--border-color);
}

/* Firefox slider progress */
input.density-slider::-moz-range-progress {
    background-color: #4B5563;
    height: 8px;
    border-radius: 8px;
}

/* ====================================
   CONTENT OVERLAY SYSTEM
   ==================================== */

/* Content Overlay System */
.content-overlay {
    position: fixed;
    top: 44px; /* Below header */
    left: 0;
    right: 0;
    bottom: 0;
    background: #f5f7fa;
    z-index: 1050;
    overflow-y: auto;
    opacity: 1;
    transition: opacity 0.3s ease;
    /* The overlay is a fixed panel with its own scrollbar, so an in-page anchor
       scrolls this, not the window. The offset stops a sticky section switcher
       covering the heading it jumped to, so it has to match the switcher's
       height. Pages with no anchors of their own are unaffected. */
    scroll-behavior: smooth;
    scroll-padding-top: 60px;
}

@media (prefers-reduced-motion: reduce) {
    .content-overlay {
        scroll-behavior: auto;
    }
}

/* The overlay is given focus when it opens so Page Up, Page Down, Home, End
   and the space bar scroll it. It is not a control, so it shows no ring. */
.content-overlay:focus {
    outline: none;
}

/* An overlay page (Gallery, FAQ, Pricing) covers the base view, but the base
   view is still in the document and still as tall as it ever was. Without
   this the window keeps a scrollbar of its own, so the reader sees two and
   the keyboard drives the wrong one. Set by navigationManager. */
body.overlay-open {
    overflow: hidden;
}

.content-overlay.hidden {
    display: none;
    opacity: 0;
}

.overlay-content {
    padding: 0;
    min-height: 100%;
}

/* ============================================
   AUTH MODAL STYLES
   ============================================ */

/* A .dialog-card, so only the width is left to say. */
.auth-modal-content {
    width: 100%;
    max-width: 400px;
}

.auth-modal-close {
    position: absolute;
    top: 12px;
    right: 12px;
    font-size: 24px;
}

.auth-modal-header {
    text-align: center;
}

.auth-modal-logo {
    height: 60px;
    margin-bottom: 16px;
}

.auth-modal-header h2 {
    margin: 0 0 8px 0;
    font-size: 24px;
    color: var(--text-headings);
}

.auth-modal-subtitle {
    margin: 0;
    color: var(--text-secondary);
    font-size: 14px;
}

.auth-modal-body {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* The roomiest button on the site, and a .btn like every other. It only restates
   the scale and goes full width, so the face still comes from a role. */
.auth-btn {
    --btn-pad: 12px 16px;
    --btn-font: 15px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    width: 100%;
    border-radius: 8px;
    font-weight: 500;
}

/* White rather than the secondary grey, because Google asks for its button to
   be left alone. */
.auth-btn-google {
    background: #fff;
    color: #333;
    border-color: #ddd;
}

.auth-btn-google:hover {
    background: #f8f8f8;
    border-color: #ccc;
}

.auth-btn-icon {
    width: 20px;
    height: 20px;
}

.auth-divider {
    display: flex;
    align-items: center;
    gap: 16px;
    color: var(--text-secondary);
    font-size: 13px;
}

.auth-divider::before,
.auth-divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--border-color);
}

/* The roomiest form on the site: one field, and the first thing a new visitor
   touches. The fields themselves are the shared .form-group and .form-control. */
.auth-form {
    --field-pad: 12px 14px;
    --field-font: 15px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* Code Entry Section */
.auth-code-section {
    text-align: center;
}

.auth-code-info {
    margin: 0 0 8px 0;
    color: var(--text-secondary);
    font-size: 14px;
}

.auth-code-info strong {
    color: var(--text-primary);
}

.auth-code-label {
    display: block;
    font-size: 18px;
    font-weight: 600;
    color: var(--text-headings);
    margin: 26px 0 12px;
}

.auth-code-input {
    display: block;
    width: 220px;
    height: 56px;
    margin: 0 auto 16px;
    font-size: 28px;
    font-weight: 600;
    font-family: 'SF Mono', Monaco, Consolas, monospace;
    letter-spacing: 8px;
    text-align: center;
    padding: 0 8px;
    border: 2px solid var(--border-color);
    border-radius: 8px;
    background: var(--bg-input);
    color: var(--text-primary);
    caret-color: transparent; /* Hide cursor - cleaner look for OTP */
    transition: border-color 0.2s, box-shadow 0.2s;
}

.auth-code-input:focus {
    outline: none;
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 3px rgba(var(--accent-primary-rgb), 0.2);
}

.auth-code-input::placeholder {
    color: var(--text-muted);
    opacity: 0.4;
}

.auth-code-actions {
    display: flex;
    justify-content: center;
    gap: 8px;
    margin-top: 12px;
    color: var(--text-muted);
}

.auth-btn-link {
    background: none;
    border: none;
    color: var(--text-secondary);
    font-size: 13px;
    cursor: pointer;
    padding: 4px 8px;
}

.auth-btn-link:hover {
    color: var(--text-primary);
    text-decoration: underline;
}

.auth-consent {
    padding-top: 8px;
}

.auth-checkbox-label {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    font-size: 13px;
    color: var(--text-secondary);
    cursor: pointer;
}

.auth-checkbox-label input {
    margin-top: 2px;
}

.auth-modal-footer {
    margin-top: 24px;
    padding-top: 16px;
    border-top: 1px solid var(--border-color);
    text-align: center;
}

.auth-terms {
    margin: 0;
    font-size: 12px;
    color: var(--text-muted);
}

/* User Menu (when logged in) */
.user-menu {
    position: relative;
}

.user-menu-trigger {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    background: var(--bg-control);
    border: 1px solid var(--border-color);
    border-radius: 6px;
    cursor: pointer;
    font-size: 13px;
    color: var(--text-primary);
}

.user-menu-trigger:hover {
    background: var(--bg-control-hover);
}

.user-menu-dropdown {
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 8px;
    background: var(--bg-panel);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    min-width: 200px;
    z-index: 1000;
}

.user-menu-dropdown.hidden {
    display: none;
}

.user-menu-header {
    padding: 12px 16px;
    border-bottom: 1px solid var(--border-color);
}

.user-menu-email {
    font-size: 13px;
    color: var(--text-primary);
    word-break: break-all;
}

.user-menu-plan {
    font-size: 11px;
    color: var(--text-secondary);
    margin-top: 4px;
}

.user-menu-item {
    display: block;
    width: 100%;
    padding: 10px 16px;
    text-align: left;
    background: none;
    border: none;
    font-size: 13px;
    color: var(--text-primary);
    cursor: pointer;
}

.user-menu-item:hover {
    background: var(--bg-control);
}

.user-menu-item.logout {
    color: #dc2626;
    border-top: 1px solid var(--border-color);
}

/* Signed-in entries in the mobile slide-in menu, which has no dropdown of its
   own. Colours come from the dark menu panel, not the light user dropdown. */
.mobile-user-menu {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.mobile-user-email {
    font-size: 13px;
    color: #fff;
    word-break: break-all;
}

.mobile-user-plan {
    font-size: 11px;
    color: rgba(255, 255, 255, 0.6);
    margin-bottom: 8px;
}

/* These entries are buttons rather than links, so only the button chrome is
   reset. The size and weight still come from .mobile-menu-link above. */
.mobile-user-menu .mobile-menu-link {
    background: none;
    border: none;
    width: 100%;
    text-align: left;
    cursor: pointer;
    font-family: inherit;
}

.mobile-user-menu .mobile-menu-link.logout {
    color: #f87171;
}

/* ============================================
   ACCOUNT SETTINGS MODAL
   All of it is borrowed: the shell and heading from the sign-in modal, the email
   and plan lines from the user menu, the fields from .form-group and
   .form-control, the hints from .help-text, the buttons from .btn. The two rules
   below are the panel's own, and neither names a colour or a size.
   ============================================ */

/* One group of settings under one heading. */
.account-section {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* A button here acts on the section above it, so it should not stretch. One
   click arms the reset and swaps it to .btn-danger, the next one does it. */
.account-section .btn {
    align-self: flex-start;
}

/* ====================================
   DUNGEON VIEW
   Sibling of #world-view inside .container. One of the two is shown at a
   time; the inactive one carries display:none + inert so paint, layout,
   and hit-testing all stop on it. Reuses .panel-side for the sidebar so
   the dungeon controls match the world controls pixel-for-pixel.
   ==================================== */

/* The dungeon sidebar inherits all .panel-side rules; the
   .dungeon-panel-side class is reserved for future tweaks. */

/* Back to World is a .btn with the navigation role, the same blue as Save World.
   The two sit together at the foot of the generator panels. */

/* Dungeon map container shares all sizing/positioning with .map-container
   above (see the combined `.map-container, .dungeon-map-container` rule)
   so both maps respond identically to viewport changes. */

/* Large close button anchored to the top-right of the dungeon map. The host
   re-attaches this button after every renderMapSvg call (svgMapRender wipes
   container.innerHTML on each render). */
.dungeon-map-close {
    position: absolute;
    top: 12px;
    right: 12px;
    width: 48px;
    height: 48px;
    background: rgba(255, 255, 255, 0.85);
    border: 1px solid var(--border-color);
    border-radius: 50%;
    color: var(--text-headings);
    font-size: 36px;
    line-height: 1;
    cursor: pointer;
    box-shadow: var(--panel-shadow);
    z-index: 10;
}
.dungeon-map-close:hover {
    background: #fff;
}

/* ----------- Dungeon sidebar controls (scoped to .dungeon-panel-side)
   Carried over from js/dungeon/styles.css so the host sidebar matches
   the standalone harness without those rules leaking to the world UI. */

/* The generator headings show the map's own name, which changes on every re-roll
   and on every keystroke in the Title field. Names run from about 11 to 40
   characters, so the heading kept flipping between one and two lines and shoving
   every control below it up and down. Clicking Random twice in the same spot then
   missed the button the second time. Two lines are always reserved and never
   exceeded, so the controls stay put.

   The two jobs are split over two elements. The heading reserves the height and
   centres whatever it holds, so a one-line name sits in the middle of the space
   rather than against the top. The span inside does the line limit, in the
   -webkit-box form browsers have supported for years, which cannot also centre
   its own text. The world panel keeps its own heading untouched. */
.dungeon-panel-side .app-title,
.cave-panel-side .app-title {
    display: flex;
    flex-direction: column;
    /* The base rule centres items, which would shrink the span to the width of a
       short name and clip long ones early. */
    align-items: stretch;
    justify-content: center;
    /* Two lines at the heading's own line-height of 1.1. */
    min-height: 2.2em;
}

.dungeon-panel-side .app-title > span,
.cave-panel-side .app-title > span {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
}

.dungeon-panel-side .slider-label {
    width: 85px;
    flex-shrink: 0;
}

.dungeon-panel-side .control-group > .density-slider {
    flex: 1 1 auto;
    min-width: 0;
}

.dungeon-panel-side .range-value {
    font-size: 11.5px;
    color: var(--text-primary);
    min-width: 2.35rem;
    text-align: left;
}

/* Dungeon export grid: centred labels; lock sits after text via ::after. */
.export-row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 6px;
}

.export-row .btn {
    width: 100%;
    text-align: center;
}

.export-row .btn.export-locked {
    position: relative;
}

.export-row .btn.export-locked[data-export-label='JPG'],
.export-row .btn.export-locked[data-export-label='PNG'],
.export-row .btn.export-locked[data-export-label='SVG'],
.export-row .btn.export-locked[data-export-label='VTT'] {
    --export-label-half: 1.5ch;
}

.export-row .btn.export-locked[data-export-label='WebP'],
.export-row .btn.export-locked[data-export-label='JSON'] {
    --export-label-half: 2ch;
}

.export-row .btn.export-locked::after {
    content: '\00a0\00a0\1F512';
    position: absolute;
    top: 50%;
    left: calc(50% + var(--export-label-half, 1.5ch));
    transform: translateY(-50%);
    font-size: 0.9em;
    line-height: 1;
    pointer-events: none;
}

.export-upgrade-label {
    display: block;
    text-align: center;
    font-size: 0.62rem;
    font-weight: 600;
    color: #22c55e;
    margin-top: 2px;
    cursor: pointer;
}

.export-upgrade-label[hidden] {
    display: none;
}

.dungeon-panel-side .classic-details,
.cave-panel-side .classic-details {
    width: 100%;
    margin: 6px 0 0;
}

.dungeon-panel-side .classic-details summary,
.cave-panel-side .classic-details summary {
    cursor: pointer;
    user-select: none;
    list-style: none;
    padding: 2px 0;
}

.dungeon-panel-side .classic-details summary::-webkit-details-marker,
.cave-panel-side .classic-details summary::-webkit-details-marker {
    display: none;
}

.dungeon-panel-side .classic-details summary::before,
.cave-panel-side .classic-details summary::before {
    content: '\25b8 ';
}

.dungeon-panel-side .classic-details[open] > summary::before,
.cave-panel-side .classic-details[open] > summary::before {
    content: '\25be ';
}

/* Collapsible subsection titles at h3 weight (cave generator, colour overrides). */
.dungeon-panel-side .classic-details:has(> summary.details-section-heading),
.cave-panel-side .classic-details:has(> summary.details-section-heading) {
    margin-top: 12px;
}

.dungeon-panel-side .classic-details summary.details-section-heading,
.cave-panel-side .classic-details summary.details-section-heading {
    font-size: 14px;
    font-weight: 500;
    color: var(--text-headings);
    padding: 0 0 4px;
}

.dungeon-panel-side .advanced-grid {
    display: grid;
    grid-template-columns: 4.5rem minmax(0, 1fr) 2.35rem;
    gap: 3px 4px;
    align-items: center;
    padding: 4px 0;
}

.dungeon-panel-side .advanced-grid label {
    font-size: 11px;
    color: var(--text-primary);
    margin: 0;
    white-space: nowrap;
}

.dungeon-panel-side .advanced-grid .density-slider {
    min-width: 0;
    width: 100%;
}

.dungeon-panel-side .colour-swatch-grid,
.cave-panel-side .colour-swatch-grid {
    display: grid;
    grid-template-columns: 1fr auto 1fr auto;
    gap: 4px 8px;
    align-items: center;
    padding: 6px 0 2px;
}

.cave-panel-side .colour-swatch-grid.colour-swatch-stack {
    grid-template-columns: 1fr auto;
}

/* Display Options: Map / Colours / Trees subsections. */
.cave-panel-side #gl-renderControls > .classic-details:first-child {
    margin-top: 0;
}

.cave-panel-side .glade-display-map-row {
    margin-top: 4px;
    margin-bottom: 2px;
}

.cave-panel-side .glade-display-inline-label {
    flex-shrink: 0;
    font-size: 11px;
    color: var(--text-primary);
    margin: 0;
    padding-left: 10px;
    min-width: 3.25rem;
    cursor: pointer;
}

.cave-panel-side .glade-display-map-toggles {
    margin-top: 4px;
    padding-left: 10px;
}

.cave-panel-side .glade-tree-controls {
    margin-top: 4px;
}

.cave-panel-side .glade-tree-slot {
    margin-bottom: 10px;
}

.cave-panel-side .glade-tree-slot:last-child {
    margin-bottom: 2px;
}

/* Tighten the select → Blossoms pairing without affecting Clearing/Water spacing. */
.cave-panel-side .glade-tree-slot > .colour-swatch-grid {
    padding-top: 2px;
    padding-bottom: 0;
}

.cave-panel-side .glade-tree-slot > .colour-swatch-grid:first-child {
    padding-top: 4px;
}

.cave-panel-side .glade-tree-row-main {
    display: grid;
    grid-template-columns: 3.25rem 1fr;
    align-items: center;
    gap: 4px;
    min-width: 0;
}

/* Match Clearing/Water label inset and type size so Tree 1–3 line up with them. */
.cave-panel-side .glade-tree-row-main > .glade-tree-slot-label {
    font-size: 11px;
    color: var(--text-primary);
    margin: 0;
    padding-left: 10px;
    cursor: pointer;
}

/* Keeps Blossoms checkboxes under the species selects (same first column as Tree 1–3). */
.cave-panel-side .glade-tree-row-main > .glade-tree-slot-spacer {
    min-height: 1px;
}

.cave-panel-side .glade-tree-row-main > .glade-tree-blossom-label {
    font-size: 11px;
    color: var(--text-primary);
    margin: 0;
    padding-left: 0; /* override colour-swatch-grid label inset so the checkbox sits under the select */
    cursor: pointer;
}

.dungeon-panel-side .colour-swatch-grid label,
.cave-panel-side .colour-swatch-grid label {
    font-size: 11px;
    color: var(--text-primary);
    margin: 0;
    padding-left: 10px;
    cursor: pointer;
}

.dungeon-panel-side .colour-swatch-grid input[type="color"],
.cave-panel-side .colour-swatch-grid input[type="color"] {
    width: 36px;
    height: 24px;
    padding: 0;
    border: 1px solid var(--border-color);
    border-radius: 3px;
    cursor: pointer;
    background: var(--bg-control);
}

.dungeon-panel-side .colour-swatch-grid input[type="color"]::-webkit-color-swatch-wrapper,
.cave-panel-side .colour-swatch-grid input[type="color"]::-webkit-color-swatch-wrapper {
    padding: 2px;
}

.dungeon-panel-side .colour-swatch-grid input[type="color"]::-webkit-color-swatch,
.cave-panel-side .colour-swatch-grid input[type="color"]::-webkit-color-swatch {
    border-radius: 2px;
    border: none;
}

.dungeon-panel-side .debug-section {
    margin-top: 12px;
    padding-top: 10px;
}

.dungeon-panel-side .timing-list {
    margin: 6px 0 0;
}

.dungeon-panel-side .timing-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 1rem;
    font-size: 11.5px;
    color: var(--text-primary);
}

.dungeon-panel-side .timing-row + .timing-row {
    margin-top: 3px;
}

.dungeon-panel-side .timing-row dt {
    margin: 0;
    font-weight: 400;
}

.dungeon-panel-side .timing-row dd {
    margin: 0;
    font-variant-numeric: tabular-nums;
}

