@import url('https://fonts.googleapis.com/css2?family=Nunito&display=swap');

body {
    padding: 0;
    margin: 0 auto;
    background-color: black;
    color: var(--light);
    user-select: none;
    box-sizing: border-box;
    font-family: 'Nunito', sans-serif;
}

::placeholder {
    color: var(--light);
}


:root {
    --dark: #1B1B1B;
    --less-dark: #333131;
    --averrage:#5e5e5e;
    --light: #a1a1a1;
    /* One plate for every HUD box and row: the friends panel, the leaderboard rows, the readout
       and the chat log all sit on this, so they cannot drift apart one box at a time. */
    --hud-plate: rgba(11, 11, 13, .72);
    --hud-radius: 6px;
}

@keyframes grow {
    0% {
        display: none;
        opacity: 0;
    }
    1% {
        display: flex;
        opacity: .7;
        transform: scale(.7);
    }
    100% {
        opacity: 1;
        transform: scale(1);
    }
}

#view { 
    position: absolute;
    width: 100%;
    height: 100%;
}

/* Freeze Movement on a server that does not pin the body itself: the client parks its own
   cursor on the centre of your cells (Network.aimWorld), so the pointer on screen is not what
   the body is following. Hide it over the arena for as long as that hold is on. Nothing is
   captured and nothing moves for the player - their real mouse keeps travelling and is back
   under their hand the moment F goes again - and the rule takes the canvas only, so the HUD's
   own cursors (and its panels) are untouched. */
body.movement-frozen #view {
    cursor: none;
}

#modals-container {
    display: none;
    justify-content: center;
    align-items: center;
    position: absolute;
    width: 100%;
    height: 100%;
}

.center {
    display: flex;
    align-items: center;
    justify-content: center;
}

.red-text {
    color: red;
}

/* One line of the log, with no plate of its own: the messages sit straight on the box (a
   background per line is what made a busy chat look like a stack of blocks). */
.hud-message-tile {
    width: 100%;
    padding: 1px 2px;
    flex-shrink: 0;
}

/* The log's own text inherits the box's font, exactly like every other HUD box: Nunito, at
   whatever the Chat UI Scale and Global UI Scale options put on .hud-chat (UserInterface.
   applyScales). It used to hard-code 14px bold here, which is why the log read as a different
   font from the rest of the HUD AND why the two scale options never reached the messages - a
   fixed pixel size on the message beats anything the box above it inherits. (The main client's
   chat is the same shape: 13px, regular weight, its tabs the only bold thing in the panel.) */
.hud-message-item {
    font-size: inherit;
    font-weight: 400;
}

.hud-message {
    display: inline;
    color: #ffffff;
}

.main-container {
    position: absolute;
    width: 100%;
    height: 100%;
    display: flex;
    gap: 1em;
    background-color: rgba(0, 0, 0, .4);
    backdrop-filter: blur(5px);
}

.anim {
    animation: grow .1s;
}

/* The lobby card is the ONE .anim box that contains an absolutely positioned child on a desktop:
   .panel-links hangs itself in the overlay's top right out of it (index.html says why the row
   lives inside the card). A transform - which is what `grow` pops with - makes the card the
   CONTAINING BLOCK for that child, so for the 100ms of the animation the links row would be
   measured from the card's own corner and then jump to the overlay's when the transform went.
   The card therefore fades in without the scale. Every other .anim box (the modals) keeps `grow`,
   and nothing reads this class from JavaScript - it is markup only. */
.panel.anim {
    animation: cardIn .1s;
}

@keyframes cardIn {
    0% {
        opacity: 0;
    }
    1% {
        opacity: .7;
    }
    100% {
        opacity: 1;
    }
}

.modal-servers-content {
    width: 100%;
    height: 100%;
    padding: 15px;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    align-items: center;
    gap: 5px;
}

.modal-servers-tile {
    padding: 5px;
    width: 100%;
    background-color: var(--dark);
}

#hud-container {
    position: absolute;
    width: 100%;
    height: 100%;
    overflow: hidden;
    pointer-events: none;
    /* The HUD's one base size, 14px, carried by the box every panel lives in: the friends box,
       the map and its clock inherit it, and the chat, board and readout are given the same
       number times their own scale options by UserInterface.applyScales. Global UI Scale
       multiplies it here, so the panels that inherit follow that option too. */
    font-size: calc(14px * var(--hud-scale, 1));
}

/* Six corners, each a column of boxes. The reference layout: the party top left, the
   leaderboard top right, the chat over the stats bottom left, the clock over the map bottom
   right - and a box moves to another corner with the UI Positions options, stacking in the
   order its corner is filled rather than landing on top of whatever was there. Every box is
   the same size because they are all given the one font size (UserInterface.applyScales). */
.hud-corner {
    position: absolute;
    display: flex;
    flex-direction: column;
    gap: 4px;
    max-width: 100%;
    max-height: 100%;
}

.hud-corner-tl { top: 0; left: 0; align-items: flex-start; }
.hud-corner-tr { top: 0; right: 0; align-items: flex-end; }
.hud-corner-tc { top: 0; left: 50%; transform: translateX(-50%); align-items: center; }
.hud-corner-bl { bottom: 0; left: 0; align-items: flex-start; justify-content: flex-end; }
.hud-corner-bc { bottom: 0; left: 50%; transform: translateX(-50%); align-items: center; justify-content: flex-end; }
.hud-corner-br { bottom: 0; right: 0; align-items: flex-end; justify-content: flex-end; }

.hud-leaderboard {
    width: 200px;
    margin: 15px;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.hud-leaderboard-tile {
    width: 100%;
    padding: 3px 8px;
    border-radius: var(--hud-radius);
    background-color: var(--hud-plate);
    overflow-wrap: break-word;
}

.hud-tile {
    display: flex;
    flex-direction: column;
}

/* A HUD box moves between the six corners when its plate is dragged (UserInterface.
   installHudDrag): the cursor is the whole affordance. Only the plate takes the events -
   the tile around it stays click-through, so the game still gets the clicks at the edge of
   the screen - and while one is moving nothing under the pointer is selectable. */
.hud-draggable {
    pointer-events: auto;
    cursor: grab;
    touch-action: none;
}

body.hud-dragging,
body.hud-dragging * {
    cursor: grabbing !important;
    user-select: none !important;
}

.hud-drag-active {
    opacity: 0.92;
}

/* The stats strip: one line, the way the reference client's readout sits. Its own font size is
   the Stats Scale option (UserInterface.applyScales), so it is set per element. */
.hud-stats-tile {
    align-items: flex-start;
}

.hud-stats {
    margin: 10px;
    border-radius: 6px;
    padding: 4px 10px;
    max-width: 60vw;
    height: min-content;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    background-color: rgba(11, 11, 13, .72);
    color: #f2f2f2;
    letter-spacing: .3px;
}

.hud-stats-line {
    display: flex;
    align-items: center;
    gap: 8px;
    white-space: nowrap;
}

.hud-stats-line b {
    font-weight: 700;
}

/* the wifi mark that closes the stats line */
.hud-stats-wifi {
    width: 17px;
    height: 17px;
    flex: 0 0 17px;
    fill: #f2f2f2;
    opacity: .9;
}

/* The date and time, in a box of its own - it used to be a line drawn over the map, and it now
   drags like every other HUD box (its own posClock), so it wears the same plate as the rest.
   Where the time sits inside the box is the Map tab's Clock alignment option. */
.hud-clock {
    margin: 10px;
    padding: 2px 10px;
    border-radius: var(--hud-radius);
    background-color: var(--hud-plate);
    color: #f2f2f2;
    letter-spacing: .4px;
    text-shadow: 0 1px 2px #000;
    white-space: nowrap;
}

/* The friends box: the same plate, radius and padding as the readout beside it (and the board's
   rows), because it is one of the HUD's boxes like any other - it used to be the only one on the
   opaque --dark with a 5px radius and 4px of padding, which is what read as "a different style". */
.hud-party {
    width: 190px;
    margin: 10px;
    padding: 4px 10px;
    display: flex;
    flex-direction: column;
    gap: 4px;
    border-radius: var(--hud-radius);
    background-color: var(--hud-plate);
}

/* Every box in the HUD reads at one size - the Global UI Scale on the shared base, with the
   chat, board and map carrying their own multipliers - so nothing here sets a size of its own. */
.hud-party-head {
    display: flex;
    align-items: center;
    gap: 4px;
}

.hud-party-title {
    flex: 1 1 auto;
    overflow: hidden;
    white-space: nowrap;
}

.hud-party-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 168px;
    overflow-y: auto;
}

.hud-party-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    padding: 2px 4px;
    border-radius: 3px;
    background-color: var(--less-dark);
}

.hud-party-mass {
    opacity: .6;
}

/* the invite picker: everyone on screen, nearest first */
.modal-party-content {
    width: 100%;
    height: 84%;
    padding: 15px;
    box-sizing: border-box;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.modal-party-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 6px 8px;
    border-radius: 6px;
    background-color: var(--less-dark);
}

.modal-party-name {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

.modal-party-name .hud-party-mass {
    font-size: .78em;
}

.modal-party-actions {
    display: flex;
    gap: 5px;
    flex-shrink: 0;
}

.modal-party-actions .button {
    font-size: .72em;
    padding: 0 8px;
}

.hud-minimap {
    background-color: var(--dark);
    border-radius: 15px;
    margin: 15px;
}

/* The chat input, imitating the main client's own (#chatInputWrap / #chat_textbox): a 460px box
   centred 75% of the way down the page - below the cell being played - faded in by Enter and
   faded out again when it is left or the message is sent. Opacity rather than display, because a
   display:none field cannot be focused: the focus that opens the box needs it to be focusable
   already, and pointer-events keeps it out of the way while it is faded out. */
/* The chat field's own box, and a FORM (the phone's keyboard action key is a submit, and a
   bare input has none). margin is zeroed by hand: a form is a UA block and this box places
   itself entirely from its top/left/transform. */
.hud-chat-bar {
    position: absolute;
    left: 50%;
    top: 75%;
    margin: 0;
    transform: translateX(-50%);
    width: 460px;
    max-width: 80vw;
    opacity: 0;
    pointer-events: none;
    transition: opacity .3s ease-in-out;
}

.hud-chat-bar.open {
    opacity: 1;
    pointer-events: auto;
}

/* By id, the way the main client has it (#chat_textbox): the page's own input[type=text] rule is
   more specific than any class, and its filled grey box is exactly what this field must not be. */
#chat-field {
    width: 100%;
    height: 34px;
    box-sizing: border-box;
    padding: 0;
    border: 0;
    outline: none;
    border-radius: 6px;
    background-color: rgba(0, 0, 0, .45);
    color: #ffffff;
    font-family: inherit;
    font-size: .88em;
    text-indent: 12px;
}

#chat-field:focus {
    outline: none;
    background-color: rgba(0, 0, 0, .6);
}

/* A party invite: a small strip just under that input, not a modal over the arena. An invite is
   not worth covering the game for, and the player who is about to talk is already looking here. */
.hud-party-invite {
    position: absolute;
    left: 50%;
    top: 75%;
    transform: translate(-50%, 44px);
    display: none;
    align-items: center;
    gap: 10px;
    max-width: 80vw;
    box-sizing: border-box;
    padding: 8px 12px;
    border-radius: 8px;
    background-color: rgba(0, 0, 0, .72);
    color: #ffffff;
    font-size: .82em;
    z-index: 6;
    /* #hud-container is pointer-events:none so the cursor reaches the canvas underneath, and this
       strip inherited it - which is why Join and Decline could not be clicked at all. The strip is
       an exception, exactly as the player panel is: its two buttons are the whole point of it. */
    pointer-events: auto;
}

.hud-party-invite.open {
    display: flex;
}

/* The player panel (middle click on a cell): a small menu at the click - the main client's own
   shape. It is deliberately not a modal: reading a player's name must not take the game over. */
.player-panel {
    position: absolute;
    display: none;
    flex-direction: column;
    gap: 4px;
    min-width: 150px;
    max-width: 260px;
    box-sizing: border-box;
    padding: 8px;
    border-radius: 8px;
    background-color: rgba(0, 0, 0, .82);
    color: #ffffff;
    font-size: .82em;
    z-index: 7;
    /* #hud-container is pointer-events:none so the cursor reaches the canvas underneath;
       the panel is the exception - its buttons must be clickable where they sit. */
    pointer-events: auto;
}

.player-panel.open {
    display: flex;
}

/* The name and the staff-only id in front of it read white: the panel is a dark plate over
   the arena, and the grey made the one line you opened the panel FOR the faintest thing on it. */
.player-panel-name {
    font-size: .82em;
    color: #ffffff;
    word-break: break-word;
}

/* The buttons hug their own text instead of stretching to the panel's full width - four
   stacked full-width bars made a tall ladder where a menu wants short rows. */
.player-panel-btn {
    padding: 5px 10px;
    border-radius: 6px;
    background-color: var(--averrage);
    text-align: center;
    cursor: pointer;
    align-self: flex-start;
}

/* Merge | Freeze | Kill as one row of three - 3 x 1: three equal columns, a single row. They
   are the three things staff do TO the target, and one line is what keeps the menu from
   growing a ladder as tools are added. */
.player-panel-tools {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 4px;
    align-self: stretch;
}

/* In the grid the cell is the size, not the icon: stretch past the flex-start the text buttons
   use to hug their label, or the three cells would be three different widths. */
.player-panel-tools .player-panel-btn {
    align-self: stretch;
    padding: 4px 0;
    font-size: 1.15em;
    line-height: 1.3;
}

.player-panel-btn:hover {
    background-color: #6f6f6f;
}

/* Freeze's icon cannot spell the state out the way the old "Unfreeze" label did, so a frozen
   target reads as a tinted button (and as "Unfreeze the target" in its title). It keeps the
   same tint on hover: it is a state readout first and a button second. */
.player-panel-btn.on,
.player-panel-btn.on:hover {
    background-color: #3d6fa8;
}

.hud-party-invite-note b {
    color: #f1c40f;
}

.hud-party-invite-btn {
    flex: 0 0 auto;
    padding: 4px 10px;
    border-radius: 6px;
    background-color: var(--averrage);
    cursor: pointer;
}

.hud-party-invite-btn:hover {
    background-color: #6f6f6f;
}

#chat-field::placeholder {
    color: rgba(255, 255, 255, .65);
}


.hud-chat-content {
    width: 100%;
    height: 100%;
    display: flex;
    padding: 10px;
    box-sizing: border-box;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    gap: 5px;
    /* auto, not scroll: the themed bar appears when there is something to scroll, instead of an
       empty groove down the side of every chat box */
    overflow-y: auto;
    overflow-x: hidden;
    pointer-events: all;
}

.hud-chat {
    position: relative;
    border-radius: 15px;
    margin: 15px;
    width: 370px;
    height: 260px;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    background-color: var(--dark);
}

/* The chat box's resize grip: dragging it writes the Chatbox Width and Height options, so the
   drag and the sliders are one number rather than two (UserInterface.makeChatResizable). */
.hud-chat-resize {
    position: absolute;
    right: 3px;
    bottom: 3px;
    width: 16px;
    height: 16px;
    border-right: 2px solid var(--averrage);
    border-bottom: 2px solid var(--averrage);
    border-radius: 0 0 8px 0;
    pointer-events: all;
    cursor: nwse-resize;
    /* the drag is the grip's own gesture: no touch scrolling, no text selection under it */
    touch-action: none;
    user-select: none;
}

.hud-chat-resize:hover {
    border-color: #f2f2f2;
}

.hud-right {
    justify-content: flex-end;
}

.hud-bottom {
    align-items: flex-end;
}

/* The lobby card. A title row, a name + party-tag row, the two primary actions, then the
   rest as an even grid. The old panel put the name field, the tag field, two buttons and the
   skin avatar in one flex row that relied on flex-grow for every width: the tag field (width
   0, no grow) collapsed to an empty box and the name field was squeezed around it. */
.panel {
    width: 460px;
    padding: 18px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    background-color: var(--dark);
    border-radius: 15px;
    box-shadow: 0 18px 50px rgba(0, 0, 0, .55);
}

.panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

.title {
    font-size: 2.6em;
    line-height: 1;
    letter-spacing: .5px;
}

.title-dot {
    color: #56b949;
}

.panel-row {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
}

.panel-actions {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
    width: 100%;
}


.panel-row .button,
.panel-actions .button {
    width: 100%;
}

#name {
    flex: 1 1 auto;
    min-width: 0;
}

/* A party tag is at most six characters, so the field is sized for exactly that. */
#tag {
    flex: 0 0 96px;
    text-transform: uppercase;
    letter-spacing: 2px;
    text-align: center;
}

#play,
#spectate {
    flex: 1 1 0;
    height: 38px;
    font-size: 1.05em;
}

#play {
    flex-grow: 2;
}

.button.primary {
    background-color: #2f7d3f;
    color: #f2fff4;
}

.button.primary:hover {
    background-color: #3d9a51;
}

#skin {
    width: 56px;
    height: 56px !important;
    flex: 0 0 56px;
    border: 2px solid var(--less-dark);
    background-repeat: no-repeat;
    background-size: contain;
    background-position: center;
}

/* `.button` is worn by DIVs everywhere in this client (the admin cards are the ones you can
   copy), and this is what makes that work: a div has no default chrome, so the plate is the
   whole look. A real <button> carrying the same class keeps the browser's own border, font and
   colour unless they are taken away - which is exactly why one of them looked like nothing else
   on the page. Cleared here once, so either element reads the same. */
.button {
    flex-shrink: 0;
    height: 30px;
    background-color: var(--less-dark);
    border: 0;
    border-radius: 5px;
    color: inherit;
    font: inherit;
    transition: background-color .3s ease-in-out;
}

.button:hover {
    cursor: pointer;
    background-color: var(--averrage);
}

.group-x {
    width: 100%;
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
}

.group-y {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
}

input[type=text] {
    min-width: 0;
    height: 34px;
    outline: none;
    border: none;
    padding: 5px;
    color: var(--light);
    box-sizing: border-box;
    background-color: var(--less-dark);
    border-radius: 5px;
    transition: background-color .1s ease-in-out;
    font-size: 1em;
}

input[type=text]:focus {
    background-color: var(--averrage);
}

.modal {
    border-radius: 15px;
    background-color: var(--dark);
    position: absolute;
}

/* The settings panel: a section per group, one switch row per option, instead of the flat
   list of raw setting names it used to render. */
.modal-settings-content {
    width: 100%;
    height: 86%;
    padding: 0 15px 15px 15px;
    box-sizing: border-box;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    gap: 0;
}

/* The tab row: one tab per group of options, and the grouping is the main client's own
   (Gameplay, Cells, Map, UI). */
.settings-tabs {
    display: flex;
    flex-shrink: 0;
    gap: 4px;
    margin-bottom: 10px;
    border-bottom: 1px solid var(--less-dark);
}

.settings-tab {
    flex: 1 1 auto;
    padding: 8px 0;
    text-align: center;
    border-radius: 8px 8px 0 0;
    font-size: .78em;
    letter-spacing: .8px;
    text-transform: uppercase;
    color: var(--averrage);
    cursor: pointer;
    transition: color .15s ease-in-out, background-color .15s ease-in-out;
}

.settings-tab.active {
    color: #ffffff;
    background-color: var(--less-dark);
}

/* Every scroll area is dressed the same way: a thin bar in the client's own grey on a
   transparent track, so it reads as part of the panel instead of the browser's bright white
   stripe over a dark window. One list of selectors, so a new scrolling box joins by being
   added here - the settings panes, the chat log, the friends list, the skin/store grids and
   the party picker. */
.settings-panes,
.hud-chat-content,
.hud-party-list,
.modal-skins-content,
.modal-party-content,
.modal-store-content,
.modal-admin-content {
    scrollbar-width: thin;
    scrollbar-color: var(--averrage) transparent;
}

.settings-panes {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding-right: 10px;
}

.settings-panes::-webkit-scrollbar,
.hud-chat-content::-webkit-scrollbar,
.hud-party-list::-webkit-scrollbar,
.modal-skins-content::-webkit-scrollbar,
.modal-party-content::-webkit-scrollbar,
.modal-store-content::-webkit-scrollbar,
.modal-admin-content::-webkit-scrollbar {
    width: 8px;
}

.settings-panes::-webkit-scrollbar-track,
.hud-chat-content::-webkit-scrollbar-track,
.hud-party-list::-webkit-scrollbar-track,
.modal-skins-content::-webkit-scrollbar-track,
.modal-party-content::-webkit-scrollbar-track,
.modal-store-content::-webkit-scrollbar-track,
.modal-admin-content::-webkit-scrollbar-track {
    background: transparent;
}

.settings-panes::-webkit-scrollbar-thumb,
.hud-chat-content::-webkit-scrollbar-thumb,
.hud-party-list::-webkit-scrollbar-thumb,
.modal-skins-content::-webkit-scrollbar-thumb,
.modal-party-content::-webkit-scrollbar-thumb,
.modal-store-content::-webkit-scrollbar-thumb,
.modal-admin-content::-webkit-scrollbar-thumb {
    border-radius: 4px;
    background-color: var(--averrage);
}

.settings-panes::-webkit-scrollbar-thumb:hover,
.hud-chat-content::-webkit-scrollbar-thumb:hover,
.hud-party-list::-webkit-scrollbar-thumb:hover,
.modal-skins-content::-webkit-scrollbar-thumb:hover,
.modal-party-content::-webkit-scrollbar-thumb:hover,
.modal-store-content::-webkit-scrollbar-thumb:hover,
.modal-admin-content::-webkit-scrollbar-thumb:hover {
    background-color: #6f6f6f;
}

.settings-pane {
    display: none;
    flex-direction: column;
    gap: 6px;
}

.settings-pane.active {
    display: flex;
}

.modal-settings-section {
    margin-top: 6px;
    font-size: .72em;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--averrage);
}

.modal-settings-tile {
    width: 100%;
    display: flex;
    align-items: center;
    box-sizing: border-box;
    justify-content: space-between;
    gap: 10px;
    padding: 8px 10px;
    border-radius: 6px;
    background-color: var(--less-dark);
}

/* The Reset row that ends every pane: a settings tile's shape, but it is a button rather than an
   option - which is why it is not a .modal-settings-tile (that class means "one option, one
   control", and the panel's own row count is built from it). */
.settings-reset-tile {
    width: 100%;
    display: flex;
    align-items: center;
    box-sizing: border-box;
    justify-content: space-between;
    gap: 10px;
    padding: 8px 10px;
    margin-top: 10px;
    border-top: 1px solid var(--averrage);
    border-radius: 6px;
    background-color: var(--less-dark);
}

.modal-settings-label {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.modal-settings-hint {
    font-size: .72em;
    color: var(--averrage);
}

/* a switch, so a boolean does not need a checkbox and a label to be told apart */
.switch {
    position: relative;
    flex: 0 0 46px;
    width: 46px;
    height: 24px;
    border-radius: 12px;
    background-color: var(--averrage);
    cursor: pointer;
    transition: background-color .2s ease-in-out;
}

.switch.on {
    background-color: #2f7d3f;
}

.switch::after {
    content: "";
    position: absolute;
    top: 3px;
    left: 3px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background-color: #f2f2f2;
    transition: transform .2s ease-in-out;
}

.switch.on::after {
    transform: translateX(22px);
}

/* A mode select, for the options that are one of a list rather than on/off (Skins, Names, Mass,
   the frame cap) - the main client's All / Tag / Self / Not self / Off rows. */
.settings-select {
    flex: 0 0 130px;
    width: 130px;
    height: 30px;
    padding: 0 8px;
    border: 1px solid var(--averrage);
    border-radius: 8px;
    background-color: var(--dark);
    color: var(--light);
    font-family: inherit;
    font-size: .82em;
    cursor: pointer;
}

.settings-select:focus {
    outline: none;
    border-color: #f1c40f;
}

/* The Config tab: the whole settings blob as text, so it can be copied out or pasted back. */
.config-text {
    width: 100%;
    height: 150px;
    box-sizing: border-box;
    padding: 8px;
    border: 1px solid var(--averrage);
    border-radius: 6px;
    background-color: var(--dark);
    color: var(--light);
    font-family: monospace;
    font-size: .78em;
    line-height: 1.35;
    resize: none;
}

/* The Config tab's two buttons (and anything else the panel needs to be clicked rather than
   flipped). */
.settings-button {
    flex: 0 0 auto;
    padding: 7px 16px;
    border-radius: 8px;
    background-color: var(--averrage);
    color: #f2f2f2;
    font-size: .78em;
    letter-spacing: .5px;
    cursor: pointer;
    transition: background-color .15s ease-in-out;
}

.settings-button:hover {
    background-color: #6f6f6f;
}

/* A colour swatch, for the options that are a colour rather than on/off or a number. The
   input's own chrome is replaced so it reads as one more setting control. */
.settings-color {
    flex: 0 0 46px;
    width: 46px;
    height: 26px;
    padding: 0;
    border: 1px solid var(--averrage);
    border-radius: 6px;
    background: transparent;
    cursor: pointer;
}

.settings-color::-webkit-color-swatch-wrapper { padding: 2px; }
.settings-color::-webkit-color-swatch { border: 0; border-radius: 4px; }
.settings-color::-moz-color-swatch { border: 0; border-radius: 4px; }

/* Suggested colours beside a colour input (the Map tab's Grid color): one click sets the picker,
   so a player does not have to hunt for a grey that reads against their own arena. */
.settings-color-wrap {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 6px;
}

.settings-color-swatch {
    flex: 0 0 16px;
    width: 16px;
    height: 16px;
    border-radius: 4px;
    border: 1px solid var(--averrage);
    cursor: pointer;
}

.settings-color-swatch:hover {
    border-color: #f1c40f;
}

/* A slider and the number it is on. The number is a field: typing into it sets the option, and
   the slider follows - the main client prints the same readout beside every range row. */
.settings-range-wrap {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 8px;
}

.settings-value {
    flex: 0 0 46px;
    width: 46px;
    height: 24px;
    box-sizing: border-box;
    padding: 0 4px;
    border: 1px solid var(--averrage);
    border-radius: 6px;
    background-color: var(--dark);
    color: var(--light);
    font-family: inherit;
    font-size: .78em;
    text-align: center;
}

.settings-value:focus {
    outline: none;
    border-color: #f1c40f;
    color: #f2f2f2;
}

.settings-range {
    flex: 0 0 130px;
    width: 130px;
    height: 24px;
    margin: 0;
    padding: 0;
    -webkit-appearance: none;
    appearance: none;
    background: transparent;
    cursor: pointer;
}

.settings-range::-webkit-slider-runnable-track {
    height: 6px;
    border-radius: 3px;
    background-color: var(--averrage);
}

.settings-range::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 16px;
    height: 16px;
    margin-top: -5px;
    border-radius: 50%;
    background-color: #f2f2f2;
}

.settings-range::-moz-range-track {
    height: 6px;
    border-radius: 3px;
    background-color: var(--averrage);
}

.settings-range::-moz-range-thumb {
    width: 16px;
    height: 16px;
    border: 0;
    border-radius: 50%;
    background-color: #f2f2f2;
}

.modal-header {
    width: 100%;
    padding: 5px;
    border-radius: 15px 15px 0px 0px;
    box-sizing: border-box;
    display: flex;
    justify-content: flex-end;
    align-items: center;
}

.modal-close {
    width: 30px;
    height: 30px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 2em;
    color: var(--light);
    cursor: pointer;
}

.modal-skins-content {
    width: 100%;
    height: 80%;
    padding: 15px;
    box-sizing: border-box;
    overflow-y: auto;
    overflow-x: hidden;
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: flex-start;
    gap: 15px;
}

.modal-skin-tile {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    height: 180px;
}

.modal-background {
    width: 100%;
    height: 100%;
    position: absolute;
    backdrop-filter: blur(5px);
    background-color: rgba(0, 0, 0, .4);
}

.round {
    border-radius: 50%;
}

/* skin picker: the list is the main client's whole folder (1400+ skins), so it is searched
   and paged instead of dumped into one enormous grid. The toolbar holds the search box and
   the "remove skin" button. */
.modal-skins-toolbar {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 10px;
}

#skin-search {
    flex: 1;
}

.modal-skins-grid {
    width: 100%;
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: flex-start;
    gap: 12px;
}

.modal-skins-grid .modal-skin-tile {
    width: 110px;
    height: auto;
    gap: 6px;
    align-items: center;
}

.modal-skins-grid .modal-skin-tile .round {
    width: 110px;
    height: 110px;
    background-color: var(--less-dark);
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
}

.modal-skins-grid .modal-skin-tile .button {
    max-width: 110px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: .72em;
}

#skin-more {
    width: 100%;
}

.hud-leaderboard-mass {
    float: right;
    opacity: .55;
    padding-left: 6px;
}

/* The phone badge: 📱 after the nick of a player whose connection is a phone's (the marker the
   server writes into the board's name). A phone's own screen has no room for a second word, so
   the glyph stays small and sits tight against the name - the VIP pill is the loud one. */
.hud-leaderboard-phone {
    margin-left: 3px;
    font-size: .85em;
    opacity: .9;
}

/* The leaderboard's one badge: VIP, and only on a row that carries the flag. The colours are the
   main client's (.lbVip), the shape follows this client's rounded tiles. */
.hud-leaderboard-vip {
    margin-right: 5px;
    padding: 0 4px;
    border-radius: 4px;
    background-color: #f1c40f;
    color: #111;
    font-size: .82em;
    font-weight: 700;
    letter-spacing: 1px;
}

/* The minimap is a second canvas sitting on top of the play area. Without this it swallows
   the mouse as the cursor passes over it, and the cell stops following mid-drive. */
.hud-minimap {
    pointer-events: none;
}

/* ---------- account strip, store and admin (all served by the main web server) ---------- */

.account-strip {
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: 5px;
    padding: 8px;
    box-sizing: border-box;
    border-radius: 8px;
    background-color: var(--dark);
    text-align: center;
}

/* ---------- the menu's own furniture: the guest note, the Free coins line, and the three
   texts at the top right ----------
   The account lines are centred (the strip's own children stay full width, so the XP bar
   still fills its row), the Free coins line sits under the buttons beside the guest
   note, and the top-right texts are the leaderboard / party link / admin doors drawn as
   plain white type - `--font` is the body face every other piece of this UI uses. The Free
   coins line is one line of type for a signed-in player: "Free 20 coin/hour" is the whole of
   it, the wait follows it while the hour is not up, and it wears no plate at all. */
.panel-free-coins {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 2px 0 4px;
    box-sizing: border-box;
    font-size: .82em;
    color: var(--averrage);
}

/* The middle segment of the line: plain type, no plate, no border, no padding - the same
   treatment the top-right texts get, so the whole row reads as one line of words. */
.panel-free-coins-btn {
    padding: 0;
    border: 0;
    background: none;
    color: #fff;
    font: inherit;
    font-weight: 700;
    cursor: pointer;
}

.panel-free-coins-btn:hover {
    color: #fff;
    text-decoration: underline;
}

.panel-free-coins-btn.disabled {
    color: inherit;
    font-weight: 400;
    cursor: default;
}

.panel-free-coins-btn.disabled:hover {
    color: inherit;
    text-decoration: none;
}

.panel-links {
    position: absolute;
    top: 18px;
    right: 26px;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 6px;
    z-index: 2;
}

.panel-link {
    font-size: 1em;
    font-weight: 700;
    letter-spacing: .3px;
    color: #ffffff;
    cursor: pointer;
    text-shadow: 0 1px 3px rgba(0, 0, 0, .85);
    user-select: none;
}

.panel-link:hover {
    text-decoration: underline;
}

/* The two controls the phone's links row carries and a desktop's does not: the leaderboard
   (with the map behind it) and the fullscreen, which moved out of the in-game corner and into
   this row (mobile.css draws them there, index.html says why). They are the touch layer's, so
   they are hidden everywhere else - a desktop steers with keys, has the browser's own F11 for
   the screen and reads its boxes from the box itself. */
.panel-mobile-control {
    display: none;
}

.account-line {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    font-size: .82em;
}

.account-dim {
    color: var(--averrage);
}

.account-badge {
    font-size: .66em;
    letter-spacing: 1px;
    padding: 1px 5px;
    border-radius: 4px;
    background-color: #f2506e;
    color: #fff;
}

.account-level {
    flex-shrink: 0;
}

.account-xp {
    flex-grow: 1;
    height: 6px;
    border-radius: 3px;
    overflow: hidden;
    background-color: var(--less-dark);
}

.account-xp-fill {
    height: 100%;
    background-color: #56b949;
}

.account-strip .button {
    width: 100%;
}

.modal-store-content,
.modal-admin-content {
    width: 100%;
    height: 88%;
    padding: 15px;
    box-sizing: border-box;
    overflow-y: auto;
    overflow-x: hidden;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.store-balance {
    font-size: 1em;
}

.modal-note {
    padding: 8px 10px;
    border-radius: 6px;
    background-color: #4d1f27;
    font-size: .82em;
}

/* The store's toast - Cigar2's own .storeToast (store.css): a small dark pill along the bottom
   of the panel that says what just happened and fades out by itself. It replaced the full-width
   red bar that used to sit under the header and shove the whole shelf down for every "Started.",
   "Saved." and "Bought." */
.store-toast {
    position: absolute;
    left: 50%;
    bottom: 12px;
    transform: translate(-50%, 10px);
    max-width: 80%;
    padding: 8px 14px;
    border-radius: 18px;
    background-color: #333333;
    color: #ffffff;
    font-size: .78em;
    font-weight: 700;
    text-align: center;
    opacity: 0;
    pointer-events: none;
    transition: opacity .18s ease, transform .18s ease;
}

.store-toast.show {
    opacity: 1;
    transform: translate(-50%, 0);
}

.store-toast.error {
    background-color: #d9534f;
}

/* The arena's own toast - the store toast's shape, lifted out of the panel: a one-line pill over
   the play area for answers that belong to an action rather than to the conversation (the life's
   XP, an invite the server refused). pointer-events keeps it from eating a click mid-fight. */
#hud-toast {
    position: fixed;
    left: 50%;
    top: 12%;
    transform: translate(-50%, -8px);
    max-width: 60%;
    padding: 8px 16px;
    border-radius: 18px;
    background-color: #333333;
    color: #ffffff;
    font-size: .85em;
    font-weight: 700;
    text-align: center;
    opacity: 0;
    pointer-events: none;
    z-index: 60;
    transition: opacity .18s ease, transform .18s ease;
}

#hud-toast.show {
    opacity: 1;
    transform: translate(-50%, 0);
}

.modal-note {
    margin: 15px;
}

.store-section-title {
    margin: 14px 0 8px 0;
    font-size: .82em;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--averrage);
}

/* A section heading with an action of its own on the right - the Running now strip's Cancel,
   which is only drawn while a booster is actually running. */
.store-section-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

.store-section-head .store-section-title {
    margin-bottom: 8px;
}

.store-section-action {
    flex: 0 0 auto;
    padding: 4px 12px;
    border-radius: 6px;
    background-color: #8c3b36;
    color: #ffffff;
    font-size: .78em;
    letter-spacing: .5px;
    cursor: pointer;
}

.store-section-action:hover {
    background-color: #a34640;
}

/* The confirm the Cancel button asks with: stopping a booster early is not refunded, so it is a
   deliberate second click rather than one stray one. */
.store-confirm {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 10px 6px;
    text-align: center;
}

.store-confirm-title {
    font-size: 1em;
}

.store-confirm-note {
    font-size: .82em;
    color: var(--averrage);
}

.store-confirm-actions {
    display: flex;
    justify-content: center;
    gap: 10px;
}

/* The drawer's own shelves, one per kind (XP Boost / Mass Boost / Minion Plan - the main
   client's renderOwnedBoosterMenu sections), each with the count it holds. */
.store-boost-shelf + .store-boost-shelf {
    margin-top: 12px;
}

.store-boost-shelf-title {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 6px;
    font-size: .72em;
    letter-spacing: .6px;
    text-transform: uppercase;
    color: var(--averrage);
}

.store-boost-shelf-title span {
    color: var(--light);
    opacity: .7;
}

.store-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.store-card {
    min-width: 150px;
    padding: 8px;
    border-radius: 8px;
    background-color: var(--less-dark);
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.store-card-title {
    font-size: .88em;
}

.store-card-sub {
    font-size: .72em;
    color: var(--averrage);
}

.store-card .button {
    font-size: .82em;
}

/* ---------- the VIP shelf wears the shop's own card box ----------
   The VIP boxes are the potion slot's own box, to the pixel: 182px wide and 186px tall, the same
   14px radius and the same 1px edge, laid in fixed 182px tracks rather than stretched fractional
   ones - a 1fr track is whatever is left over, which is how these ended up a different size from
   the potion shelf beside them. auto-fit collapses the unused tracks, so two boxes sit centred
   instead of being pinned to the left of a row built for four. */
.store-vip-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, 182px);
    justify-content: center;
    gap: 12px;
}

.store-vip-grid .store-card {
    /* border-box, because .store-case-card is: without it the padding and the edge are added on
       top of the height and the box comes out taller than the one it is imitating. */
    box-sizing: border-box;
    /* the "?" help button is pinned to the card's own top-right corner (store-case-help). */
    position: relative;
    width: 182px;
    height: 186px;
    min-height: 186px;
    padding: 14px 10px 10px;
    border: 1px solid var(--less-dark);
    border-radius: 14px;
    justify-content: center;
    gap: 4px;
}

/* ---------- the VIP "?" panel: the reference card ----------
   White sheet, gold heading, the expiry stamp under it, the perks ticked in green,
   then the two status rows (VIP active / VIP + Cosmetics with its day count) and one
   full-width Close. Only THIS modal is dressed white - the shell behind it keeps its
   own colours. */
/* The VIP card wears this client's own shell - the dark modal, its own greys, and the gold
   the store marks VIP with. It was a white card copied from the reference picture, which read
   as a foreign sheet dropped over the game (the panel and its close glyph now just use the
   base .modal rules like every other panel). */
.vip-info {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 2px 22px 20px;
    text-align: center;
}

.vip-info-title {
    font-size: 1.5em;
    font-weight: 700;
    letter-spacing: .06em;
    color: #f1c40f;
}

.vip-info-status {
    font-size: .78em;
    color: var(--light);
    margin-top: -6px;
}

.vip-info-perks {
    display: flex;
    flex-direction: column;
    gap: 6px;
    text-align: left;
}

.vip-info-perk {
    position: relative;
    padding-left: 22px;
    font-size: .8em;
    font-weight: 700;
    line-height: 1.4;
    color: var(--light);
}

.vip-info-perk::before {
    content: "\2713";
    position: absolute;
    left: 2px;
    color: #2ecc71;
    font-weight: 700;
}

.vip-info-states {
    display: flex;
    gap: 8px;
    justify-content: center;
}

.vip-info-chip {
    flex: 1 1 auto;
    box-sizing: border-box;
    padding: 8px 10px;
    border: 1px solid var(--averrage);
    border-radius: 6px;
    background-color: var(--less-dark);
    color: var(--light);
    font-size: .78em;
    font-weight: 700;
    text-align: center;
}

/* the pass that IS running wears the store's VIP gold, filled (the badge's own colours) */
.vip-info-chip.on {
    background-color: #f1c40f;
    border-color: #f1c40f;
    color: #111;
}

/* ...and the cosmetics pass keeps an outline of its own while it runs */
.vip-info-chip.outline {
    background-color: transparent;
    border-color: #f1c40f;
    color: #f1c40f;
}

.vip-info-chip.outline:not(.on) {
    border-color: var(--averrage);
    color: var(--averrage);
}

.vip-info-close {
    width: 100%;
    box-sizing: border-box;
    padding: 9px 0;
    border: none;
    border-radius: 8px;
    background-color: var(--averrage);
    color: #f2f2f2;
    font-size: .82em;
    font-weight: 700;
    letter-spacing: .5px;
    cursor: pointer;
    transition: background-color .15s ease-in-out;
}

.vip-info-close:hover {
    background-color: #6f6f6f;
}

/* ---------- the buy confirmation ----------
   Artwork, the name, the price with its own currency mark, then Cancel and Buy -
   every purchase in the store asks here first (Account.confirmPurchase), the way
   the main client asks before a skin changes hands. */
.store-buy {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    padding: 4px 20px 20px;
    text-align: center;
}

.store-buy-title {
    font-size: 1em;
    font-weight: 700;
}

.store-buy-art {
    width: 84px;
    height: 84px;
    object-fit: contain;
    border-radius: 10px;
    background-color: var(--dark);
}

.store-buy-glyph {
    font-size: 2.4em;
    line-height: 1;
}

.store-buy-price {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    font-size: .95em;
    color: var(--light);
}

.store-buy-price img {
    width: 18px;
    height: 18px;
    object-fit: contain;
}

.store-buy-note {
    font-size: .82em;
    color: var(--averrage);
}

.store-buy-actions {
    display: flex;
    gap: 10px;
    width: 100%;
}

.store-buy-actions .settings-button,
.store-buy-actions .button {
    flex: 1 1 0;
    box-sizing: border-box;
    width: auto;
    text-align: center;
}

/* ---------- the store's shelves ----------
   A wall of categories on the way in, then one category at a time with its own tabs. The shapes
   are the main client's own (its category cards and its Owned / Hidden / All tab strip), drawn in
   this client's rounded dark style. */
.store-categories {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
}

.store-cat {
    min-height: 104px;
    padding: 12px;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 6px;
    border-radius: 10px;
    background-color: var(--less-dark);
    cursor: pointer;
    transition: background-color .15s ease-in-out;
}

.store-cat:hover {
    background-color: var(--averrage);
}

.store-cat-label {
    font-size: 1.05em;
    letter-spacing: .3px;
}

.store-cat-sub {
    font-size: .72em;
    color: var(--light);
    opacity: .75;
}

/* the category header: the way back, the name, the balance */
.store-head {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
}

/* The way back, in the main client's own shape (.storeBack): a round arrow button that is only
   there when there is a shelf to go back from. It used to be a text chip reading "\u2039 Store"
   sitting next to a title that also said "Store", which is the one thing a header must not do - so
   the chip says nothing but the arrow, and it is hidden at the root. */
.store-back {
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--less-dark);
    border-radius: 50%;
    background-color: var(--less-dark);
    font-size: 1.05em;
    line-height: 1;
    cursor: pointer;
}

.store-back:hover {
    border-color: #5865F2;
    background-color: #5865F2;
    color: #fff;
}

/* The shelf's own name, centred the way the main client centres #storeTitle - and it is EMPTY at
   the root store, because the back arrow beside it already says where you are. */
.store-head-title {
    flex: 1 1 auto;
    min-width: 0;
    text-align: center;
    font-size: 1.05em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Pushed to the far right whether or not the title is up. */
.store-head-balance {
    margin-left: auto;
    font-size: .82em;
    color: var(--averrage);
}

/* Owned first, then Buy - the shelf a player looks at most is the one that opens */
.store-tabstrip {
    display: flex;
    gap: 6px;
    flex-shrink: 0;
    border-bottom: 1px solid var(--less-dark);
}

/* The strip shares its width out between the tabs, and a tab never wraps: the skins shop has six
   of them ("ANIMATED (51)" broke onto two lines and made the others look like they had grown). */
.store-tab {
    flex: 1 1 auto;
    min-width: 0;
    padding: 7px 6px;
    text-align: center;
    border-radius: 8px 8px 0 0;
    font-size: .78em;
    letter-spacing: .6px;
    text-transform: uppercase;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--averrage);
    cursor: pointer;
    transition: color .15s ease-in-out, background-color .15s ease-in-out;
}

.store-tab.active {
    color: #ffffff;
    background-color: var(--less-dark);
}

/* the shelf scrolls under the header, so the tabs and the balance never leave the screen */
.store-body {
    flex: 1 1 auto;
    min-height: 0;
    /* The padding is what keeps the first row of a shelf off the tab strip: the shelf scrolls,
       and without it a scrolled card ran right up into the header. */
    padding: 6px 4px 0 2px;
    overflow-y: auto;
}

/* The store's content box is only a frame: it does not scroll itself (the .store-body above is
   the scroll area), so the header and the tabs stay put instead of riding out of the panel, and
   it is what the toast is positioned against. */
.modal-store-content {
    position: relative;
    overflow: hidden;
}

/* an owned skin: its own artwork, its name, and Equip. (No fixed width: the card below is the
   one that decides the shape, and a leftover 132px here left a gap in every grid track.) */
.store-skin-card {
    padding: 8px;
    box-sizing: border-box;
    border-radius: 8px;
    background-color: var(--less-dark);
    display: flex;
    flex-direction: column;
    gap: 6px;
    align-items: center;
}

.store-skin-art {
    width: 116px;
    height: 116px;
    border-radius: 8px;
    background-color: var(--dark);
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
}

.store-empty,
.store-foot {
    font-size: .78em;
    color: var(--averrage);
}

.store-foot {
    margin-top: 8px;
}

.modal-admin-toolbar {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 10px;
}

.modal-admin-toolbar input {
    flex: 1;
}

/* The toolbar's own button (Custom skins, with the queue's count): a plain `.button` plate hugs
   its text because the class only fixes the height, so this gives it the sides it needs. */
.modal-admin-toolbar .button {
    padding: 0 12px;
    white-space: nowrap;
}

.modal-admin-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

/* One row of the custom-skin moderation queue: the artwork, who it belongs to and its state on
   the left, the decisions on the right. The picture is the same circle the game draws a skin in,
   so the reviewer sees what a player would. */
.adm-custom-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px;
    border-radius: 8px;
    background-color: var(--dark);
}

.adm-custom-pic {
    width: 56px;
    height: 56px;
    flex: 0 0 56px;
    border-radius: 50%;
    background-color: var(--averrage);
    object-fit: contain;
}

.adm-custom-text {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.adm-custom-title {
    font-size: .82em;
    font-weight: 700;
    color: var(--light);
}

.adm-custom-sub {
    font-size: .7em;
    color: #bbb;
    overflow-wrap: anywhere;
}

.adm-custom-actions {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.adm-custom-actions .button {
    padding: 4px 8px;
    font-size: .68em;
}

.button.danger {
    background-color: #7a2233;
}

.button.danger:hover {
    background-color: #a12c43;
}

.hud-message-party {
    color: #56b949;
}

/* The server's AFK countdown: the same slot as the invite strip, one line further down, so it is
   a message under the box the player is looking at rather than a row of the stats readout. It
   puts itself away - the server saying 0, a stale-packet watchdog, or the player moving (see
   UserInterface.onServerInfo / hideStaleAfk). */
.hud-afk {
    position: absolute;
    left: 50%;
    top: 75%;
    transform: translate(-50%, 84px);
    display: none;
    box-sizing: border-box;
    max-width: 80vw;
    padding: 6px 12px;
    border-radius: 8px;
    background-color: rgba(0, 0, 0, .72);
    color: #e8b84b;
    font-size: .82em;
    z-index: 5;
}

/* the all-time board (from /auth/top) - rank, name, level, best */
.modal-top-content {
    width: 100%;
    height: 100%;
    padding: 15px;
    box-sizing: border-box;
    overflow-y: auto;
}

.modal-top-head,
.modal-top-row {
    display: grid;
    /* #, Player, Level, Best, Games, Eaten, Time - the main client's own seven columns on
       its Top players table, so the numbers line up the same way. */
    grid-template-columns: 30px 1fr 50px 64px 64px 72px 76px;
    gap: 8px;
    align-items: center;
    padding: 5px 8px;
}

.modal-top-head {
    font-size: .72em;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--averrage);
}

.modal-top-row {
    border-radius: 5px;
    background-color: var(--less-dark);
    margin-bottom: 4px;
}

.modal-top-rank {
    color: var(--averrage);
}

.modal-top-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.modal-top-level,
.modal-top-best,
.modal-top-games,
.modal-top-eaten,
.modal-top-time {
    text-align: right;
}

/* the profile modal (from /auth/me) */
/* The account's own card, in the store's Owned > Profile shelf: it is the modal's own card, so
   the shelf only has to stop it stretching across a 780px panel - the heights it was built for
   ("100%" of a 470px modal, and its own scrolling) belong to the popup, not to a shelf. */
.store-profile {
    width: 360px;
    max-width: 100%;
    margin: 0 auto;
}

.store-profile .modal-profile-content {
    height: auto;
    min-height: 420px;
    max-height: 100%;
}

/* The profile shelf's Free coins row: a label, the claim button and its own note on one
   line under the XP bar. The button wears the store's own .button look, so this only has
   to size it and say when it cannot be used (the hour is not up). */
.store-free-coins {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--averrage);
}

.store-free-coins-label {
    font-size: .85em;
    color: var(--light);
}

.store-free-coins-btn {
    font-size: .8em;
    padding: 4px 10px;
}

.store-free-coins-btn.disabled {
    opacity: .55;
    cursor: default;
}

.store-free-coins-note {
    font-size: .78em;
    color: var(--light);
    opacity: .8;
}

.modal-profile-content {
    width: 100%;
    height: 100%;
    padding: 15px;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    overflow-y: auto;
}

.modal-profile-avatar {
    width: 64px;
    height: 64px;
    border-radius: 50%;
    object-fit: cover;
    background-color: var(--less-dark);
}

.modal-profile-name {
    font-size: 1.05em;
    font-weight: 700;
}

.modal-profile-level {
    color: var(--averrage);
}

.modal-profile-content .account-xp {
    width: 80%;
}

.modal-profile-rows {
    width: 100%;
    margin-top: 8px;
}

.modal-profile-row {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    padding: 5px 8px;
    margin-bottom: 4px;
    border-radius: 5px;
    background-color: var(--less-dark);
}

.modal-profile-row span {
    color: var(--averrage);
}

/* the chat channels - "All" and "Party", the main client's own two. Two small tabs pinned to
   the log's top-left rather than two stretched halves, and the strip itself has no plate of its
   own: the only background in the box is the log's. */
.hud-chat-tabs {
    width: 100%;
    display: flex;
    flex-shrink: 0;
    justify-content: flex-start;
    align-items: center;
    gap: 6px;
    padding: 7px 8px 4px 8px;
    box-sizing: border-box;
}

.hud-chat-tab {
    flex: 0 0 auto;
    padding: 3px 11px;
    border-radius: 7px;
    font-size: .82em;
    letter-spacing: .3px;
    color: var(--light);
    cursor: pointer;
    pointer-events: all;
    transition: color .15s ease-in-out, background-color .15s ease-in-out;
}

.hud-chat-tab:hover {
    color: #ffffff;
}

.hud-chat-tab.active {
    color: #ffffff;
    background-color: var(--averrage);
}

/* the tabs take their own row, so the log flexes under them rather than overflowing */
.hud-chat-tabs + .hud-chat-content {
    height: auto;
    flex: 1;
    min-height: 0;
}

/* a party line shown on the Global tab - readable, but plainly not global chat */
.hud-message-ghost {
    opacity: .55;
}

.hud-chat-unread {
    display: inline-block;
    min-width: 15px;
    padding: 0 4px;
    margin-left: 4px;
    border-radius: 8px;
    background-color: #b4453f;
    color: #ffffff;
    font-size: .82em;
}

.hud-chat-unread:empty {
    display: none;
}

/* (The invite modal's own .party-invite block is gone: an invite is the small strip under the
   chat input now - .hud-party-invite above. Nothing renders a .party-invite any more.) */

/* the line under the lobby buttons: "Solving a bot check...", connection notes */
.lobby-status {
    min-height: 14px;
    font-size: .78em;
    color: var(--averrage);
    text-align: center;
}

/* the anti-bot step-up (see UserInterface.solveCaptcha) */
.captcha-overlay {
    position: fixed;
    inset: 0;
    z-index: 10;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: rgba(0, 0, 0, .6);
    backdrop-filter: blur(4px);
}

.captcha-box {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    padding: 20px;
    min-width: 260px;
    border-radius: 15px;
    background-color: var(--dark);
}

.captcha-title {
    font-size: 1.05em;
}

.captcha-note {
    font-size: .78em;
    color: var(--averrage);
    text-align: center;
}

.captcha-box .button {
    width: 100%;
}

/* a skin this account has to buy (or level into) before it can be worn */
.modal-skin-tile.locked .round {
    opacity: .35;
}

.modal-skin-tile.locked .button {
    background-color: #43303a;
}/* ---------- the store, drawn in the main client's own shape ----------
   The category wall, the skins shop with its four-per-page pager and its Cells panel, the potion
   cards and their odds popup, the cosmetics controls, the admin card and the lobby's brew slots.
   Everything here is this client's palette (--dark / --less-dark / --averrage), never the main
   client's, so the two stores look like two clients rather than one copy of the other. */
.store-categories {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px;
    align-content: start;
}

.store-tile {
    padding: 14px 12px;
    border: 1px solid var(--less-dark);
    border-radius: 12px;
    background-color: var(--less-dark);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    text-align: center;
    cursor: pointer;
    transition: transform .12s ease, border-color .12s ease, box-shadow .12s ease;
}

.store-tile:hover {
    transform: translateY(-2px);
    border-color: #5865F2;
    box-shadow: 0 6px 16px rgba(0, 0, 0, .3);
}

.store-tile-icon {
    width: 64px;
    height: 64px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.store-tile-img {
    width: 46px;
    height: 46px;
    object-fit: contain;
}

.store-tile-round {
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background-color: var(--dark);
    border: 1px solid var(--averrage);
    padding: 3px;
    box-sizing: border-box;
}

.store-tile-swatch {
    width: 46px;
    height: 46px;
    border-radius: 50%;
    background-color: var(--dark);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .12);
}

.store-tile-label {
    font-size: 1rem;
    letter-spacing: .3px;
}

.store-tile-sub {
    font-size: .72em;
    color: var(--light);
    opacity: .8;
}

/* ---------- the skins shop: the Cells panel, the grid, the pager ---------- */
.store-skin-split {
    display: flex;
    gap: 14px;
    align-items: flex-start;
}

.store-skin-split-list {
    flex: 1 1 auto;
    min-width: 0;
}

.store-skin-slots {
    flex: 0 0 126px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    position: sticky;
    top: 0;
}

.store-skin-slots-title {
    font-size: .66em;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .6px;
    color: var(--averrage);
}

.store-skin-slot {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px;
    border: 1px solid var(--less-dark);
    border-radius: 10px;
    background-color: var(--dark);
    color: var(--light);
    font: inherit;
    font-size: .78em;
    text-align: left;
    cursor: pointer;
    transition: border-color .16s ease, background-color .16s ease;
}

.store-skin-slot:hover {
    border-color: var(--averrage);
}

.store-skin-slot.active {
    border-color: #5865F2;
    background-color: var(--less-dark);
    color: #fff;
}

.store-skin-slot-pic {
    flex: 0 0 34px;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background-color: var(--less-dark);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}

.store-skin-slot-pic img {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

.store-skin-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px;
}

.store-skin-grid-owned {
    grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
    gap: 10px;
}

.store-skin-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    min-height: 190px;
    padding: 12px 10px 10px;
    border: 1px solid var(--less-dark);
    border-radius: 14px;
    background-color: var(--less-dark);
    text-align: center;
    cursor: pointer;
    transition: transform .16s ease, border-color .16s ease, box-shadow .16s ease;
}

.store-skin-card:hover {
    transform: translateY(-2px);
    border-color: #5865F2;
    box-shadow: 0 6px 14px -6px rgba(0, 0, 0, .55);
}

.store-skin-card.selected {
    border-color: #43b581;
}

.store-skin-card.locked img {
    opacity: .35;
}

/* The artwork is a full-bleed square with the cell in the middle of it, so the preview is
   clipped to the circle the game draws it in rather than shown as a square tile. */
.store-skin-card img {
    width: 110px;
    height: 110px;
    object-fit: contain;
    border-radius: 50%;
    background-color: var(--dark);
}

.store-skin-name {
    max-width: 100%;
    font-size: .82em;
    font-weight: 700;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The card's bottom line: a price ("100 DNA", "1,200 coins") or a state ("tap to equip"). It is
   the card's own width and it wraps, so a long price cannot spill over the tiles beside it. */
.store-skin-price {
    width: 100%;
    margin-top: auto;
    padding: 7px 8px;
    border-radius: 8px;
    box-sizing: border-box;
    background-color: #1a9e4b;
    color: #fff;
    font-size: .72em;
    line-height: 1.2;
    flex-wrap: wrap;
    text-align: center;
    overflow-wrap: anywhere;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
}

.store-skin-card.locked .store-skin-price {
    background-color: var(--averrage);
    color: #ddd;
}

.store-skin-card.owned .store-skin-price {
    background-color: var(--dark);
    color: var(--light);
}

.store-skin-card.selected .store-skin-price {
    background-color: #2f7d4f;
    color: #fff;
}

/* The Custom shelf (the player's own artwork, by URL): the slot a click will fill carries the
   "picked" outline, a filled slot carries the Replace control (same DNA price as filling it),
   and the shelf's one spend button sits under the grid at full width. */
.store-skin-card.picked {
    border-color: #f0b90b;
}

.store-custom-replace {
    width: 100%;
    margin-top: 6px;
    padding: 5px 8px;
    border: 0;
    border-radius: 8px;
    background-color: #3a3a3a;
    color: var(--light);
    font-size: .68em;
    font-weight: 700;
    cursor: pointer;
}

.store-custom-replace:hover {
    background-color: #4a4a4a;
}

.store-custom-save {
    width: 100%;
    margin: 10px 0 4px;
    padding: 9px 10px;
    border: 0;
    border-radius: 8px;
    background-color: #1a9e4b;
    color: #fff;
    font-size: .8em;
    font-weight: 700;
    cursor: pointer;
}

.store-custom-save:hover {
    background-color: #21b357;
}

.store-art-icon {
    width: 14px;
    height: 14px;
    object-fit: contain;
}

/* The DNA (or coin) mark on a price line is an icon, not the card's preview. The preview rule is
   `.store-skin-card img` - a class plus a type, so it outranked `.store-art-icon` and drew the
   price's DNA at 110px in a round dark tile, which is what swallowed the Animated shelf's cards. */
.store-skin-price img {
    width: 16px;
    height: 16px;
    border-radius: 0;
    background: none;
    object-fit: contain;
}

.store-skin-groups {
    min-width: 0;
}

.store-skin-group {
    margin-bottom: 12px;
}

/* A shelf head, the main client's own (store.css .storeSkinGroupHead): a plain row with a rule
   under it and the count pushed to the right, rather than a filled grey bar. */
.store-skin-group-head {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 2px;
    border: 0;
    border-bottom: 1px solid var(--less-dark);
    border-radius: 0;
    background-color: transparent;
    color: var(--light);
    font: inherit;
    font-size: .78em;
    font-weight: 700;
    letter-spacing: .4px;
    text-transform: uppercase;
    text-align: left;
    cursor: pointer;
    transition: color .14s ease, border-color .14s ease;
}

.store-skin-group-head:hover {
    color: #5865F2;
    border-color: #5865F2;
}

.store-skin-group-arrow {
    width: 12px;
}

.store-skin-group-label {
    flex: 1 1 auto;
}

.store-skin-group-count {
    margin-left: auto;
    color: var(--averrage);
    font-weight: 400;
}

.store-group-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-bottom: 10px;
}

.store-group-chip {
    border: 1px solid var(--less-dark);
    border-radius: 999px;
    background-color: var(--less-dark);
    color: var(--light);
    font: inherit;
    font-size: .72em;
    padding: 4px 10px;
    cursor: pointer;
}

.store-group-chip:hover {
    border-color: #5865F2;
}

.store-group-chip.active {
    background-color: #5865F2;
    border-color: #5865F2;
    color: #fff;
}

.store-search-row {
    padding: 0 0 10px 0;
}

.store-search {
    width: 100%;
    box-sizing: border-box;
    padding: 7px 10px;
    border: 1px solid var(--less-dark);
    border-radius: 6px;
    background-color: var(--dark);
    color: var(--light);
    font: inherit;
    outline: none;
}

.store-search:focus {
    border-color: #5865F2;
}

.store-pager-wrap {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 6px;
}

.store-pager-wrap:not(.paged) .store-pager-side {
    visibility: hidden;
}

.store-pager-side {
    width: 34px;
    height: 150px;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: var(--averrage);
    font-size: 28px;
    line-height: 1;
    cursor: pointer;
    transition: color .14s ease, background-color .14s ease;
}

.store-pager-side:hover:not(:disabled) {
    color: #5865F2;
    background-color: var(--less-dark);
}

.store-pager-side:disabled {
    opacity: .3;
    cursor: default;
}

/* The potion history's own pager: its chevrons are drawn over the outer cards' own margins
   instead of beside them. Laid out in the row they cost 80px of the card row (two 34px chevrons
   and two gaps), which made a history card 159px wide next to the shop's own 177px one - the two
   side-by-side shelves did not draw the same card. Overlaid, the grid gets the whole shelf width.
   A card's own 10px padding and its centred artwork keep the chevrons clear of everything on it,
   and the hover highlight is dropped because it would paint a rectangle over a card. */
.store-pager-history {
    grid-template-columns: 1fr;
}

.store-pager-history > .store-case-history-grid,
.store-pager-history > .store-pager-side {
    grid-area: 1 / 1;
}

.store-pager-history > .store-pager-side {
    z-index: 1;
    align-self: center;
}

.store-pager-history > .store-pager-side-left {
    justify-self: start;
}

.store-pager-history > .store-pager-side-right {
    justify-self: end;
}

.store-pager-history > .store-pager-side:hover:not(:disabled) {
    background-color: transparent;
}

.store-pager-nav {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 6px;
    margin: 12px 0 4px;
}

.store-pager-btn {
    min-width: 30px;
    height: 30px;
    padding: 0 8px;
    border: 1px solid var(--less-dark);
    border-radius: 8px;
    background-color: var(--less-dark);
    color: var(--light);
    font: inherit;
    font-size: .78em;
    line-height: 1;
    cursor: pointer;
}

.store-pager-btn:hover:not(:disabled) {
    border-color: #5865F2;
    color: #fff;
}

.store-pager-btn:disabled {
    opacity: .5;
    cursor: default;
}

.store-pager-page.active {
    background-color: #5865F2;
    border-color: #5865F2;
    color: #fff;
    font-weight: 700;
}

.store-pager-gap {
    color: var(--averrage);
    padding: 0 2px;
}

/* ---------- potions: the cards, the brew clock and the history ---------- */
.store-banner {
    padding: 8px 10px;
    margin-bottom: 10px;
    border-radius: 6px;
    background-color: var(--less-dark);
    font-size: .78em;
    color: var(--light);
}

/* One card, three shelves. A potion wears the shop's own card wherever it is drawn - on the
   shelf, on its own brew slot under Owned, and in the history - so the same potion never changes
   size when it moves from one shelf to the next. The column rule is the shop's own, and it is
   auto-fill rather than auto-fit on purpose: auto-fit collapses the tracks nothing landed in, so
   three brew slots would stretch into three fat columns beside a shop card of the shop's own
   width - which is exactly the mismatch this fixes. */
.store-case-grid,
.potion-slots-grid,
.store-case-history-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 12px;
}

/* ...and one card box. 186px is the shop card's own height (a label, the artwork and one button),
   pinned on all three so a card carrying two shorter lines is not a different card beside it. */
.store-case-card,
.potion-slot-card,
.store-case-history {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    min-height: 186px;
    box-sizing: border-box;
    padding: 14px 10px 10px;
    border: 1px solid var(--less-dark);
    border-radius: 14px;
    background-color: var(--less-dark);
    text-align: center;
}

.store-case-help {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 22px;
    height: 22px;
    border: 1px solid var(--averrage);
    border-radius: 50%;
    background-color: var(--dark);
    color: var(--light);
    font: inherit;
    font-size: .72em;
    line-height: 1;
    cursor: pointer;
}

.store-case-help:hover {
    border-color: #5865F2;
    color: #fff;
}

.store-case-label {
    font-size: .82em;
    font-weight: 700;
    text-align: center;
}

.store-case-icon {
    height: 96px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.store-case-art {
    width: 84px;
    height: 84px;
    object-fit: contain;
}

.store-case-owned {
    font-size: .72em;
    color: var(--light);
}

.store-case-brew {
    width: 100%;
    box-sizing: border-box;
    padding: 7px 8px;
    border-radius: 8px;
    background-color: var(--dark);
    color: var(--light);
    font-size: .72em;
    text-align: center;
}

.store-case-btn {
    width: 100%;
    box-sizing: border-box;
    font-size: .78em;
}

.store-case-pill {
    margin-left: 6px;
    padding: 1px 6px;
    border-radius: 6px;
    background-color: #4d1f27;
    font-size: .82em;
    text-transform: lowercase;
}

/* Nothing may hang outside a history card: the artwork used to stick out of the top of it. The
   four lines sit a little tighter than the shop card's three (the main client's own
   .storeCaseHistoryCard gap is 6px) so four of them still fit the one card height, and the two
   stamp lines close up by a hair so the card lands under it rather than over it. */
.store-case-history {
    gap: 6px;
    overflow: hidden;
}

.store-case-history.unopened {
    border-color: #7a5c1e;
}

/* The slot is as tall as the artwork the shop draws (84px), so a history potion sits inside its
   card at the shop's own size - 54px used to clip 30px off a picture that is exactly this
   tall. */
.store-case-history-icon {
    height: 84px;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
}

.store-case-history-label {
    font-size: .78em;
    font-weight: 700;
}

.store-case-history-loot,
.store-case-history-times {
    font-size: .66em;
    color: var(--light);
    line-height: 1.35;
}

/* The two stamps are the one place a line-height is tightened: two printed dates at 1.35 made a
   history card 194px against the shop card's own 186px. */
.store-case-history-times {
    line-height: 1.2;
}

.store-potion-info {
    padding: 15px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    height: 86%;
    overflow-y: auto;
}

.store-potion-info-head {
    display: flex;
    align-items: center;
    gap: 10px;
}

.store-potion-info-art {
    width: 48px;
    height: 48px;
    object-fit: contain;
}

.store-potion-info-title {
    font-size: 1.05em;
}

.store-potion-rows {
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.store-potion-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 8px;
    border-radius: 6px;
    background-color: var(--less-dark);
    font-size: .78em;
}

.store-potion-row img {
    width: 18px;
    height: 18px;
    object-fit: contain;
}

.store-potion-row span {
    flex: 1 1 auto;
}

.store-potion-row.guaranteed b {
    color: #56b949;
}

/* ---------- cosmetics: the option row and the Name Modifications controls ---------- */
.store-cosmetic-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* ---------- one card per cosmetic: a titled card, with its controls on their own plates ----------
   The shelf used to be one flat slab of --less-dark with unlabelled colour bars stretched
   across it, which is what made it read as a mess. */
.store-cosmetic-card {
    padding: 12px 14px;
    border: 1px solid var(--less-dark);
    border-radius: 12px;
    background-color: var(--less-dark);
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* The card's own title bar: the label, its description under it, and a rule that separates
   the heading from the controls it belongs to. */
.store-cosmetic-card .store-cosmetic-head {
    border-bottom: 1px solid var(--dark);
    padding-bottom: 8px;
}

.store-cosmetic-head {
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.store-cosmetic-opts {
    display: flex;
    gap: 8px;
}

.store-opt {
    flex: 1 1 0;
    padding: 9px 8px;
    border: 1px solid var(--averrage);
    border-radius: 10px;
    background-color: var(--dark);
    color: var(--light);
    font: inherit;
    font-size: .82em;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
}

.store-opt b {
    font-size: 1em;
}

.store-opt span {
    font-size: .78em;
    color: var(--averrage);
}

.store-opt:hover {
    border-color: #5865F2;
}

.store-opt.active {
    border-color: #43b581;
    background-color: #22402d;
    color: #fff;
}

.store-glow-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border: 1px solid var(--dark);
    border-radius: 10px;
    background-color: var(--dark);
}

.store-glow-label {
    flex: 0 0 48px;
    font-size: .78em;
    color: var(--light);
}

.store-glow-range {
    flex: 1 1 auto;
    accent-color: #5865F2;
}

.store-glow-value {
    flex: 0 0 auto;
    min-width: 30px;
    padding: 2px 8px;
    border-radius: 8px;
    background-color: var(--less-dark);
    color: var(--light);
    text-align: center;
    font-size: .78em;
}

/* The three name colours as three tiles: label above, swatch under it, each on its own
   plate. They used to be colour inputs stretched to fill the row with their labels sitting
   on top of the bars. The row itself spans all three columns of a grid, so the tiles are
   the same width whatever the modal is doing. */
.store-glow-colors {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 10px;
    padding: 8px 10px;
}

.store-glow-color {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
    padding: 8px;
    border: 1px solid var(--less-dark);
    border-radius: 10px;
    background-color: var(--dark);
    font-size: .74em;
    color: var(--light);
    text-align: center;
}

.store-glow-color input[type="color"] {
    display: block;
    width: 100%;
    height: 30px;
    padding: 0;
    border: 1px solid var(--averrage);
    border-radius: 8px;
    background: var(--dark);
    cursor: pointer;
}

/* ---------- the Hotkeys tab ---------- */
.hotkey-key {
    min-width: 74px;
    text-align: center;
    font-weight: 700;
    letter-spacing: .5px;
}

.hotkey-key.listening {
    border-color: #5865F2;
    color: #fff;
}

/* ---------- the admin card ----------
   One rule for the row (the old stylesheet carried two .admin-row blocks and a dead
   .admin-row-actions block, which fought over padding and layout). The row is a button:
   avatar, name, the record line that grows, and the level chip pinned right. */
.admin-row {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 9px;
    border: 0;
    border-radius: 8px;
    background-color: var(--less-dark);
    color: var(--light);
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: background-color .15s ease-in-out;
}

.admin-row:hover {
    background-color: var(--averrage);
}

.admin-row:focus-visible {
    outline: 2px solid #5865F2;
    outline-offset: 1px;
}

.admin-row-name {
    flex: 0 1 auto;
    font-size: .82em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.admin-row-sub {
    flex: 1 1 auto;
    min-width: 0;
    font-size: .72em;
    color: var(--averrage);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.admin-row:hover .admin-row-sub {
    color: var(--light);
}

.adm-avatar {
    width: 26px;
    height: 26px;
    flex: 0 0 26px;
    border-radius: 50%;
    background-color: var(--dark);
    object-fit: cover;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: .78em;
    color: var(--light);
}

.adm-avatar.big {
    width: 48px;
    height: 48px;
    flex-basis: 48px;
    font-size: 1.05em;
}

.adm-level {
    flex: 0 0 auto;
    padding: 2px 7px;
    border-radius: 6px;
    background-color: var(--dark);
    font-size: .66em;
    letter-spacing: .5px;
    color: var(--light);
}

.adm-card {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px;
    border-radius: 10px;
    background-color: var(--less-dark);
}

.adm-card-main {
    flex: 1 1 auto;
    min-width: 0;
}

.adm-card-name {
    font-size: 1em;
    font-weight: 700;
}

.adm-card-id {
    font-size: .72em;
    color: var(--averrage);
    overflow: hidden;
    text-overflow: ellipsis;
}

.adm-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 8px;
}

.adm-stat {
    padding: 8px 10px;
    border-radius: 8px;
    background-color: var(--less-dark);
    display: flex;
    flex-direction: column;
    gap: 3px;
    font-size: .72em;
    color: var(--light);
}

.adm-stat b {
    font-size: 1.25em;
    color: #fff;
}

.adm-fields {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 8px;
}

.adm-field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

/* The label is a caption over its box (uppercase grey, the settings panel's own), not the
   field's own text size - the old rule shrank the whole field to .72em, input included. */
.adm-field span {
    font-size: .66em;
    letter-spacing: .6px;
    text-transform: uppercase;
    color: var(--averrage);
}

/* One chrome for every text box on the card: the settings controls' own visible grey edge
   and gold focus, instead of a .less-dark border on a .less-dark panel that read as a
   borderless grey hole with nothing to say what it was for. */
.adm-field input,
.adm-input {
    box-sizing: border-box;
    height: 30px;
    padding: 0 8px;
    border: 1px solid var(--averrage);
    border-radius: 8px;
    background-color: var(--dark);
    color: var(--light);
    font: inherit;
    font-size: .82em;
    outline: none;
    transition: border-color .15s ease-in-out, color .15s ease-in-out;
}

.adm-field input {
    width: 100%;
}

.adm-field input:focus,
.adm-input:focus {
    border-color: #f1c40f;
    color: #f2f2f2;
}

.adm-field input::placeholder,
.adm-input::placeholder {
    color: var(--averrage);
    opacity: 1;
}

.adm-inline {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.adm-inline .button {
    font-size: .78em;
    padding: 0 10px;
}

.adm-input {
    width: 88px;
}

/* The booster grant row's three captions: the inputs are prefilled (2 / 24 / 25), and a
   placeholder only ever shows on an EMPTY field - so without these the row was three bare
   numbers beside a dropdown. */
.adm-lbl {
    font-size: .78em;
    color: var(--averrage);
    letter-spacing: .3px;
    white-space: nowrap;
}

.adm-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.adm-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 9px;
    border-radius: 999px;
    background-color: var(--less-dark);
    font-size: .72em;
    color: var(--light);
}

.adm-chip-x {
    border: 0;
    background: transparent;
    color: var(--averrage);
    font: inherit;
    font-size: 1em;
    line-height: 1;
    cursor: pointer;
}

.adm-chip-x:hover {
    color: #f2506e;
}

.adm-skin-preview {
    width: 40px;
    height: 40px;
    object-fit: contain;
    border: 1px solid var(--less-dark);
    border-radius: 8px;
    background-color: var(--dark);
}

/* ---------- the brew slots: one per potion held, inside the store's Owned shelf ----------
   The slots draw the shop's own card (see .store-case-card above): the same column, the same box
   and the same 84px artwork, so the Owned shelf and the Premium shelf beside it read as one set.
   The wrapper is the grid cell the card fills - that is what keeps an empty slot exactly the size
   of the one beside it holding a potion. */
/* The slots are a centred flex row of shop-card-sized boxes. The shared grid up top lays
   four tracks in a shop this wide, so a row of three slots sat in the first three and the
   fourth stayed empty - a left-aligned row in a centred card. 182px is one track of that
   shelf grid, so a brew slot is exactly the size of the potion card beside it. */
.potion-slots-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    width: 100%;
}

.potion-slots-grid {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 12px;
    width: 100%;
}

.potion-slot {
    display: flex;
    flex: 0 1 182px;
    min-width: 150px;
}

.potion-slot-card {
    /* The shop card is solid; a slot that holds nothing says so with a dashed edge. */
    border-style: dashed;
    border-color: var(--averrage);
    flex: 1 1 auto;
}

.potion-slot-card.filled {
    border-style: solid;
    border-color: var(--less-dark);
}

.potion-slot-icon {
    height: 96px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.potion-slot-art {
    width: 84px;
    height: 84px;
    object-fit: contain;
}

/* The name and the countdown on one line, the shop card's own "one line under the artwork". */
.potion-slot-line {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 6px;
    max-width: 100%;
}

.potion-slot-name {
    min-width: 0;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--light);
    font-size: .82em;
}

.potion-slot-timer {
    flex: 0 0 auto;
    color: var(--light);
    font-size: .72em;
}

.potion-slot-action {
    width: 100%;
    box-sizing: border-box;
    padding: 6px 8px;
    font-size: .78em;
    border-radius: 6px;
    background-color: #22303a;
    color: var(--light);
    text-align: center;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
}

.potion-slot-action:hover {
    background-color: #2c4150;
    color: #fff;
}

.potion-slot-action.open {
    background-color: #2f7d4f;
    color: #fff;
}

/* A slot whose brew the client has no stamp for yet: it is asking the server, and it must not
   look like a button - it is not one (see Account.potionSlotHTML). */
.potion-slot-action.pending {
    background-color: var(--less-dark);
    color: var(--averrage);
    cursor: default;
}

.potion-slot-action.pending:hover {
    background-color: var(--less-dark);
    color: var(--averrage);
}

.potion-slot-dna {
    width: 12px;
    height: 12px;
    object-fit: contain;
}

.potion-slot-plus {
    font-size: 1.4em;
    line-height: 1;
    color: var(--averrage);
}

.potion-slot-empty {
    color: var(--averrage);
}

.potion-slot-get {
    /* Its own line under the slots, centred by the wrapper, and only as wide as its own text
       plus its padding - it used to span the whole shelf as a full-width bar. */
    padding: 6px 26px;
    border: 1px solid var(--less-dark);
    border-radius: 8px;
    background-color: var(--less-dark);
    color: var(--light);
    font: inherit;
    font-size: .78em;
    cursor: pointer;
}

.potion-slot-get:hover {
    border-color: #5865F2;
    color: #fff;
}

.button.disabled {
    opacity: .45;
}

/* ---------- the category tiles' own artwork ----------
   The Cosmetics tile is a cell - the catalogue's Saturn, clipped to the circle, with a nickname
   over it - and the name sits LOW in it, which is the look its Name Position cosmetic sells.
   The VIP tile wears the membership badge itself, the gold pill from web/index.html #vipBadge. */
.store-tile-cell {
    /* block, not the span's own inline box: an inline element has no height for the absolute
       name under it to be measured against. */
    position: relative;
    display: block;
    width: 58px;
    height: 58px;
}

.store-tile-cell-img {
    width: 58px;
    height: 58px;
    border-radius: 50%;
    object-fit: contain;
    background-color: var(--dark);
}

.store-tile-cell-name {
    position: absolute;
    left: 50%;
    bottom: 1px;
    transform: translateX(-50%);
    max-width: 58px;
    font-size: .66em;
    font-weight: 700;
    line-height: 1;
    color: #fff;
    text-shadow: 0 0 3px #000, 0 0 2px #000, 1px 1px 0 #000, -1px -1px 0 #000;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    pointer-events: none;
}

.vip-badge {
    display: inline-block;
    border-radius: 999px;
    font-weight: 700;
    letter-spacing: .08em;
    color: #3a2a00;
    background: linear-gradient(135deg, #d4a017, #f5d76e);
    box-shadow: 0 1px 4px rgba(0, 0, 0, .25);
}

/* The membership pass carries the badge its own colour, the way the membership and the
   cosmetics pass are two different things on the VIP shelf. */
.vip-badge-plus {
    color: #241a3d;
    background: linear-gradient(135deg, #8b5cf6, #c4b5fd);
}

/* The badge on a VIP card sits above its title, centred in the card's own column rather than
   hanging off its left edge, and reads a size up from the leaderboard's chip: on this card it is
the headline, not an aside beside a name. */
.store-card-badge {
    display: flex;
    justify-content: center;
}

.store-card-badge .vip-badge {
    padding: 3px 13px;
    font-size: 1.05em;
}

.store-tile-vip {
    padding: 5px 15px;
    font-size: 1em;
}

/* ---------- boosters: the main client's block (store.css .storeBoost*) ----------
   The artwork centred, the caption under it, then a price + duration button per option - two
   blocks across, four for the compact own/minion grids. */
.store-boost-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
}

.store-boost-block {
    border: 1px solid var(--less-dark);
    border-radius: 10px;
    background-color: var(--less-dark);
    padding: 16px 12px 14px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    position: relative; /* the stack count badge's anchor */
}

/* Stacked identical boosters: how many of this exact plan are in the drawer (inventoryHTML
   groups them into one card). */
.store-boost-count {
    position: absolute;
    top: 4px;
    right: 6px;
    min-width: 20px;
    padding: 1px 6px;
    border-radius: 10px;
    background-color: var(--averrage);
    color: #111;
    font-size: .72em;
    font-weight: 700;
    line-height: 1.4;
    text-align: center;
}

.store-boost-icon {
    width: 112px;
    height: 112px;
    object-fit: contain;
}

.store-boost-caption {
    font-size: 1rem;
    font-weight: 700;
    text-align: center;
}

.store-boost-btns {
    width: 100%;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    justify-content: center;
}

.store-boost-btns .store-opt {
    flex: 1 1 calc(50% - 6px);
    min-width: 0;
    padding: 8px 6px;
    text-align: center;
}

.store-boost-btns .store-opt span {
    display: block;
    font-size: .72em;
    color: var(--averrage);
}

/* The second line of an owned block: "Active for <t>", "2h pack" or blank. Always drawn, so a
   card never changes height when a plan starts or stops. */
.store-boost-state {
    min-height: 13px;
    font-size: .72em;
    line-height: 1.2;
    text-align: center;
    color: var(--averrage);
}

.store-boost-block .store-opt {
    width: 100%;
}

/* The four minion packs on one screen: small badges, compact price buttons. */
.store-minion-grid {
    gap: 8px;
}

.store-minion-grid .store-boost-block {
    padding: 8px 6px 6px;
}

.store-minion-grid .store-boost-icon {
    width: 56px;
    height: 56px;
}

/* Owned and running boosters: four across, so a drawer of them fits in one row. */
.store-owned-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 6px;
}

.store-owned-grid .store-boost-block {
    padding: 8px 4px 6px;
    border-radius: 8px;
}

.store-owned-grid .store-boost-icon {
    width: 56px;
    height: 56px;
}

.store-owned-grid .store-boost-caption {
    max-width: 100%;
    font-size: .72em;
    line-height: 1.15;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.store-owned-grid .store-opt {
    font-size: .66em;
    padding: 3px 6px;
}

/* ---------- coins and DNA: the main client's price table (store.css .storeDnaRow) ----------
   base · + · bonus% · = · total + icon · $price · Buy, one bordered row per tier. */
.store-dna-grid {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.store-dna-row {
    display: grid;
    grid-template-columns: 92px 16px 84px 16px 1fr 90px 100px;
    align-items: center;
    gap: 8px;
    background-color: var(--less-dark);
    border: 1px solid var(--less-dark);
    border-radius: 12px;
    padding: 10px 14px;
    transition: border-color .14s ease;
}

.store-dna-row:hover {
    border-color: #5865F2;
}

.store-dna-base {
    font-weight: 700;
    color: var(--light);
}

.store-dna-plus,
.store-dna-eq {
    color: var(--averrage);
    text-align: center;
}

.store-dna-bonus {
    padding: 5px 0;
    border-radius: 999px;
    background-color: #ffb300;
    color: #4a3700;
    text-align: center;
    font-weight: 700;
}

.store-dna-total {
    display: flex;
    align-items: center;
    gap: 6px;
    font-weight: 700;
    color: #fff;
}

.store-dna-total img {
    width: 18px;
    height: 18px;
    object-fit: contain;
}

/* The purse's marks, in the main client's own shape (.storePurseImg: 15px square, sitting 3px
   into the line): at 18px with no vertical-align the coin stood on the text's baseline, so its
   bottom was five pixels above the digits' and the balance read as floating. */
.store-purse-img {
    width: 15px;
    height: 15px;
    object-fit: contain;
    vertical-align: -3px;
}

.store-dna-price {
    text-align: right;
    color: var(--averrage);
    font-variant-numeric: tabular-nums;
}

.store-dna-buy {
    width: 100%;
    padding: 7px 0;
    border: 1px solid var(--averrage);
    border-radius: 8px;
    background-color: var(--dark);
    color: var(--light);
    font: inherit;
    font-size: .78em;
    font-weight: 700;
    cursor: pointer;
    transition: filter .14s ease, border-color .14s ease;
}

.store-dna-buy:hover {
    border-color: #5865F2;
    color: #fff;
}

/* ---------- opening a potion (Cigar2's #potionOpenModal, store.css) ----------
   The potion on the left with its artwork, the rewards landing on the right one row at a time,
   and a Collect button under them. The rows and the button ride a per-row animation-delay, so
   the loot arrives one after another rather than all at once. */
@keyframes potionRowIn {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: translateY(0); }
}

/* A server line in the chat log: the gear that marks the server talking (the main client's
   own badge), dimmer than a player's name so the line reads as a notice rather than as
   somebody's message. */
.hud-message-server {
    font-weight: 700;
    opacity: .75;
}

.potion-open {
    width: 100%;
    /* 84%, not 100%: the modal's own header sits above the content, so a content box of exactly
       the panel's height hangs its bottom edge out of the panel (the store's own content uses
       88% for the same reason). */
    height: 84%;
    box-sizing: border-box;
    padding: 4px 20px 20px;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.potion-open-title {
    font-size: 1.05em;
    font-weight: 700;
    text-align: center;
}

.potion-open-body {
    flex: 1 1 auto;
    min-height: 0;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
}

.potion-open-left {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
}

.potion-open-art {
    width: 96px;
    height: 96px;
    object-fit: contain;
}

.potion-open-name {
    font-size: .88em;
    font-weight: 700;
}

.potion-open-reveal {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 14px;
    border-radius: 10px;
    background-color: var(--dark);
    overflow-y: auto;
}

.potion-opening {
    color: var(--light);
}

.potion-reveal-row {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 6px;
    text-align: center;
    font-size: .82em;
    font-weight: 700;
    animation: potionRowIn 320ms cubic-bezier(.2, .7, .3, 1) both;
}

.potion-reveal-icon {
    width: 20px;
    height: 20px;
    object-fit: contain;
    flex: 0 0 auto;
}

.potion-reveal-glyph {
    flex: 0 0 auto;
    line-height: 1;
}

.potion-open-collect {
    margin-top: 4px;
    padding: 8px 22px;
    border: 0;
    border-radius: 8px;
    background-color: #5865F2;
    color: #fff;
    font: inherit;
    font-weight: 700;
    cursor: pointer;
    animation: potionRowIn 320ms cubic-bezier(.2, .7, .3, 1) both;
}

.potion-open-collect:hover {
    filter: brightness(1.08);
}

/* ---------- the profile card (Cigar2's #profileModal, topPlayers.css) ----------
   Avatar, name, a level pill, the stat rows as one bordered table, then the XP bar with its
   "into / need" line under it - a card, not a stack of pills. */
.modal-profile-level {
    padding: 2px 10px;
    border-radius: 999px;
    background-color: #5865F2;
    color: #fff;
    font-size: .82em;
    font-weight: 700;
}

.modal-profile-table {
    width: 100%;
    margin: 10px 0 12px;
    border-collapse: collapse;
}

.modal-profile-table td {
    padding: 5px 6px;
    font-size: .82em;
    border-bottom: 1px solid var(--less-dark);
}

.modal-profile-table td:first-child {
    text-align: left;
    color: var(--averrage);
}

.modal-profile-table td:last-child {
    text-align: right;
    font-weight: 700;
    color: #fff;
}

.modal-profile-xp {
    width: 100%;
}

.modal-profile-xp-track {
    height: 12px;
    border-radius: 6px;
    background-color: var(--less-dark);
    overflow: hidden;
}

.modal-profile-xp-fill {
    height: 100%;
    border-radius: 6px;
    background: linear-gradient(90deg, #5865F2, #7289DA);
}

.modal-profile-xp-label {
    margin-top: 4px;
    font-size: .72em;
    color: var(--averrage);
    text-align: center;
}

/* ---------- the one type weight scale ----------
   Every panel used to read at one weight: a card's own name sat at 400, exactly like its sub-line
   under it, and the tab strip, the buttons and the prices had no emphasis at all - which is what
   "the fonts are not consistent" was really about. The main client sets these apart by weight
   (#storeTitle 17px/700, .storeTab 12px/700, .storeTileLabel 700, .caseSlotOpenNow 700,
   .storeCaseHistoryLabel 600), so this is that arrangement in one place rather than a weight
   sprinkled rule by rule. The sizes themselves come from the one em ladder above
   (.66 tiny / .72 meta / .78 sub / .82 label / .88 value / 1 body / 1.05 title). */
.store-head-title,
.store-card-title,
.store-tile-label,
.store-skin-name,
.store-case-label,
.potion-slot-name,
.store-case-history-label,
.store-boost-shelf-title,
.store-section-title,
.store-boost-caption,
.modal-profile-name,
.modal-profile-level,
.title,
.store-tab,
.settings-tab,
.hud-chat-tab,
.button,
.settings-button,
.store-case-btn,
.store-case-brew,
.potion-slot-action,
.store-pager-btn,
.store-skin-price,
.store-section-action,
.store-toast {
    font-weight: 700;
}

/* The half-titles: a label over a value, a shelf's own name, the countdown under a name. */
.modal-settings-label,
.modal-settings-section,
.store-boost-state,
.potion-slot-timer,
.store-skin-group-head {
    font-weight: 600;
}

/* ...and the hint under a setting is a note, not a half-title, so it keeps the body weight
   (it lives inside the label, which is why it has to say so itself). */
.modal-settings-hint {
    font-weight: 400;
}

/* ---------- the shop editor (the owner alone) ----------
   One page wearing the store's own skin: the shelf strip, the shelf being edited, and the artwork
   sitting on disk that is not in the shop. The rows are flex and they wrap, because the panel is
   fixed-width and a nine-column table of it would either clip the fields or squeeze the number
   inputs to nothing. Nothing here is styled outside the store's own palette. */
.admin-shop {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 2px 2px 8px;
}

.admin-shop-section {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.admin-shop-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding-bottom: 5px;
    border-bottom: 1px solid var(--less-dark);
    font-size: .82em;
    letter-spacing: .4px;
    text-transform: uppercase;
    color: var(--light);
}

.admin-shop-head span {
    font-size: .85em;
    letter-spacing: 0;
    text-transform: none;
    opacity: .6;
}

/* What the section is for, said once, on the right of its own rule. */
.admin-shop-head i {
    margin-left: auto;
    font-style: normal;
    font-size: .92em;
    letter-spacing: 0;
    text-transform: none;
    text-align: right;
    opacity: .55;
}

.admin-shop-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 7px;
    padding: 6px 8px;
    border: 1px solid var(--less-dark);
    border-radius: 5px;
    background-color: var(--dark);
}

.admin-shop-shelfrow {
    background-color: rgba(255, 255, 255, .015);
}

/* Adding a shelf is not an edit of anything yet, so its row is dashed. */
.admin-shop-new {
    border-style: dashed;
    background: none;
}

.admin-shop-input {
    min-width: 0;
    padding: 5px 7px;
    font: inherit;
    font-size: .88em;
    color: inherit;
    background-color: #191919;
    border: 1px solid var(--less-dark);
    border-radius: 4px;
}

.admin-shop-input:focus {
    outline: none;
    border-color: #5865F2;
}

.admin-shop-wide {
    flex: 1 1 140px;
}

.admin-shop-num {
    width: 66px;
    flex: 0 0 auto;
}

.admin-shop-field {
    display: flex;
    flex-direction: column;
    gap: 2px;
    font-size: .66em;
    letter-spacing: .3px;
    text-transform: uppercase;
    color: var(--light);
    opacity: .85;
}

.admin-shop-btn {
    flex: 0 0 auto;
    padding: 5px 9px;
    font: inherit;
    font-size: .8em;
    color: var(--light);
    background-color: var(--less-dark);
    border: 1px solid var(--less-dark);
    border-radius: 4px;
    cursor: pointer;
}

.admin-shop-btn:hover {
    color: #fff;
    border-color: var(--averrage);
}

.admin-shop-btn.primary {
    color: #fff;
    background-color: #5865F2;
    border-color: #5865F2;
}

.admin-shop-btn.danger {
    color: #ff9a9a;
}

/* The second click: a removal arms its own button and says so, because nothing here can undo it. */
.admin-shop-btn.danger:hover,
.admin-shop-btn.armed,
.admin-shop-btn.armed:hover {
    color: #fff;
    background-color: #7a2222;
    border-color: #7a2222;
}

.admin-shop-btn.warn {
    color: #ffd479;
}

.admin-shop-shelf {
    flex: 0 0 auto;
    padding: 5px 11px;
    font: inherit;
    font-size: .82em;
    color: var(--light);
    background-color: var(--less-dark);
    border: 1px solid transparent;
    border-radius: 999px;
    cursor: pointer;
}

.admin-shop-shelf b {
    margin-left: 3px;
    font-weight: normal;
    opacity: .55;
}

.admin-shop-shelf.active {
    color: #fff;
    background-color: #5865F2;
}

.admin-shop-shelf.hidden-shelf {
    border-color: var(--light);
    border-style: dashed;
    opacity: .55;
}

.admin-shop-art {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    object-fit: contain;
}

/* The three row actions travel together, so a narrow panel wraps them as one group instead of
   leaving "Remove" stranded on a line of its own. */
.admin-shop-actions {
    display: flex;
    gap: 6px;
    margin-left: auto;
}

.admin-shop-name {
    flex: 1 1 110px;
    min-width: 0;
    display: flex;
    flex-direction: column;
    line-height: 1.25;
}

.admin-shop-name b {
    font-weight: normal;
    font-size: .92em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.admin-shop-name i {
    font-style: normal;
    font-size: .72em;
    color: var(--light);
    opacity: .6;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.admin-shop-count {
    font-size: .74em;
    color: var(--light);
    opacity: .7;
}

/* The editor's own shelf strip: which shelf the rows below are editing. */
.admin-shop-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.admin-shop-filter {
    display: flex;
    align-items: center;
    gap: 8px;
}

.admin-shop-filter .admin-shop-input {
    flex: 1 1 auto;
}

.admin-shop-unlisted {
    opacity: .85;
}

.admin-shop-pager {
    margin-top: 2px;
}

.admin-shop-note {
    padding: 8px 2px;
    font-size: .84em;
    color: var(--light);
    opacity: .7;
}

/* The owner's door into the editor, on the store's own header - drawn only where it means
   something (the Owner account, on the skins shelf). */
.store-head-edit {
    flex: 0 0 auto;
    padding: 5px 10px;
    font-size: .78em;
    white-space: nowrap;
    color: var(--light);
    background-color: var(--less-dark);
    border: 1px solid var(--less-dark);
    border-radius: 4px;
    cursor: pointer;
}

.store-head-edit:hover {
    color: #fff;
    border-color: var(--averrage);
}

.store-head-edit.active {
    color: #fff;
    background-color: #5865F2;
    border-color: #5865F2;
}

/* ---------- the sign-in / account door ----------
   The username + password form (Account.openSignIn) and the account's own rows on the Owned >
   Profile shelf (Account.accountDoorHTML). Both are drawn inside the ordinary modal stack, so
   only the inside of the panel needs dressing: the panel, its close glyph and its backdrop are
   the shared ones (ModalSystem). */
.signin-box {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 100%;
    box-sizing: border-box;
    padding: 0 15px 15px 15px;
}

.signin-title {
    font-size: .95em;
    color: #fff;
    text-align: center;
}

.signin-tabs {
    display: flex;
    gap: 6px;
    background-color: #111;
    border-radius: 8px;
    padding: 3px;
}

.signin-tab {
    flex: 1;
    text-align: center;
    padding: 7px 4px;
    border-radius: 6px;
    font-size: .84em;
    color: var(--light);
    cursor: pointer;
}

.signin-tab:hover {
    color: #fff;
}

.signin-tab.on {
    background-color: var(--less-dark);
    color: #fff;
}

.signin-input {
    width: 100%;
    box-sizing: border-box;
    padding: 9px 10px;
    border: 1px solid var(--less-dark);
    border-radius: 7px;
    background-color: #111;
    color: var(--light);
    font-family: inherit;
    font-size: .85em;
    outline: none;
    /* A login field is the one place a player must be able to select what they typed. */
    user-select: text;
}

.signin-input:focus {
    border-color: #5865F2;
}

.signin-note {
    min-height: 1.1em;
    font-size: .78em;
    line-height: 1.3;
    color: var(--light);
    text-align: center;
    word-break: break-word;
}

.signin-note.bad {
    color: #ff8a8a;
}

.signin-or {
    position: relative;
    text-align: center;
    font-size: .74em;
    color: var(--averrage);
}

.signin-or::before,
.signin-or::after {
    content: "";
    position: absolute;
    top: 50%;
    width: 42%;
    height: 1px;
    background-color: var(--less-dark);
}

.signin-or::before { left: 0; }
.signin-or::after { right: 0; }

.signin-or span {
    position: relative;
    padding: 0 8px;
    background-color: var(--dark);
}

.signin-fine {
    font-size: .72em;
    line-height: 1.35;
    color: var(--averrage);
    text-align: center;
}

/* The guest strip's own door: one line of type that opens the panel, so a player who has never
   signed in is not left guessing that the lobby's skin button is where accounts live. */
.account-signin {
    color: #fff;
    cursor: pointer;
    text-decoration: underline;
}

.account-signin:hover {
    color: #5865F2;
}

/* The account's own rows on the Profile shelf: Discord, the username + password door, and the
   sign-out - one row each, with the single action that changes it on the right. */
.account-door {
    width: 100%;
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px solid var(--less-dark);
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.account-door-title {
    font-size: .8em;
    color: #fff;
}

.account-door-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 7px 9px;
    border-radius: 7px;
    background-color: #111;
    font-size: .78em;
}

.account-door-label {
    color: var(--averrage);
}

.account-door-state {
    flex: 1;
    text-align: right;
    color: var(--light);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.account-door-state.linked {
    color: #7ddc7d;
}

.account-door-btn {
    color: #5865F2;
    cursor: pointer;
    white-space: nowrap;
}

.account-door-btn:hover {
    color: #fff;
}
