ui/mapbrowser.razor.scss
// Map select — a fullscreen page over the lobby (2026-10-05): the maps down the left under the title and the two tabs,
// the one you're pointing at far right. Black Ops' "Mission select" for the layout; the lobby's look for the rest
// (Inter, strips with a left rule, the gold accent the config and map modals already used).
//
// ⛔ CLASS NAMES THE LOBBY DOESN'T USE. This page renders INSIDE the lobby's root, so every `.lobby .x` rule in
// LobbyMenu.razor.scss reaches any element of ours called `.x`. The first cut called its columns `.left` and `.right`
// and its backdrop `.bg` and `.scrim`: the lobby's 0.42 scrim won over ours (0.82), so the art showed through bright,
// and its `.right { margin-left: auto; width: 330px }` pulled the picture about. Keep to names that aren't in that file.
//
// ⚠️ Rendered inside the lobby's root, which is `pointer-events: none`, so this has to opt back
// in. A panel that declares neither is silently un-hit-testable in s&box — that was the lobby's
// own bug, and ConfigBrowser carries the same note.
//
// ⚠️ SIZES IN THE 1080-HIGH UI SPACE, like the lobby's.
$gold: #ffd479;
.mapselect
{
font-family: Inter;
position: absolute;
top: 0px; left: 0px; right: 0px; bottom: 0px;
width: 100%;
height: 100%;
pointer-events: all;
// ⚠️ PLAIN BLACK FOR NOW (the user: *"i dont want to see the map background / for now make it black, i will get an
// image later"*). The image goes here as `background-image` with `background-size: cover`; nothing else changes.
background-color: #000;
.page
{
position: absolute;
top: 0px; left: 0px; right: 0px; bottom: 0px;
width: 100%;
height: 100%;
flex-direction: row;
// The lobby's `.content` padding, so the title lands where the brand was.
padding: 56px 64px;
}
// ── the left column: title, tabs, names ─────────────────────────────────
.select
{
flex-direction: column;
width: 380px;
flex-shrink: 0;
}
// The brand's size and weight: this page's name stands where the lobby's did.
.title
{
font-size: 44px;
font-weight: 600;
color: #fff;
text-transform: uppercase;
letter-spacing: 2px;
}
.tabs
{
flex-direction: row;
flex-shrink: 0;
margin-top: 8px;
// The underline carries the selection, not a filled pill: two pills read as two buttons rather than as one
// thing with two states.
.tab
{
padding: 8px 0px;
margin-right: 28px;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 1.5px;
color: #6f747c;
border-bottom: 2px solid rgba( 0, 0, 0, 0 );
cursor: pointer;
&:hover { color: #c9cdd4; }
&.on
{
color: #fff;
border-bottom: 2px solid $gold;
}
}
}
// ⚠️ THE CHOSEN MAP, WHERE THE TABS STAND ON THE MAPS (the gamemode step, 2026-10-05): drawn as the one tab that is on.
.modeof
{
flex-shrink: 0;
align-self: flex-start;
margin-top: 8px;
padding: 8px 0px;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 1.5px;
color: #fff;
border-bottom: 2px solid $gold;
}
.tabhint
{
flex-shrink: 0;
margin-top: 10px;
margin-bottom: 20px;
font-size: 13px;
color: #7d828a;
}
.list
{
flex-direction: column;
flex-grow: 1;
flex-shrink: 1;
// ⛔ THE LIST SCROLLS; ITS ROWS DO NOT SHRINK. Survival has 19 maps, more than the column's height holds at
// this row size. With the flex default every row would absorb the difference and their names would print on
// top of one another, which is what the lobby's own column did before it got the same rule.
overflow-y: scroll;
.empty
{
padding: 20px 4px;
font-size: 14px;
color: #6b7078;
}
// ⚠️ SOLID, NOT TRANSLUCENT (the user: *"the map selection should not be transparent"*). Shaped like the
// lobby's `.opt` rows, a strip with a left rule, in opaque greys.
.row
{
flex-direction: row;
align-items: center;
flex-shrink: 0;
padding: 10px 16px;
margin-bottom: 4px;
background-color: #111317;
border-left: 2px solid #2a2e36;
cursor: pointer;
.name
{
flex-grow: 1;
font-size: 17px;
color: #b9bec6;
}
.badge
{
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1.2px;
color: $gold;
}
&:hover
{
background-color: #191c22;
.name { color: #fff; }
}
// ⚠️ "ON" IS THE PREVIEWED MAP, which stays lit when the cursor leaves the list, so the picture on the right
// always says whose it is.
&.on
{
background-color: #1f232a;
border-left: 2px solid $gold;
.name { color: #fff; }
}
}
}
.back
{
flex-shrink: 0;
align-self: flex-start;
margin-top: 18px;
padding: 10px 18px;
font-size: 15px;
color: #a3a8b0;
background-color: #111317;
border-left: 2px solid #2a2e36;
cursor: pointer;
&:hover
{
color: #fff;
background-color: #191c22;
border-left: 2px solid #5a5f68;
}
}
// ── the map you're pointing at, far right ───────────────────────────────
// ⚠️ PUSHED TO THE RIGHT EDGE (`margin-left: auto`), at the card's own size (the user: *"the map image should go a
// lot more to the right, be smaller, and the name should be a lot bigger and the description bigger too"*).
.detail
{
flex-direction: column;
flex-shrink: 0;
width: 640px;
margin-left: auto;
justify-content: center;
align-items: flex-start;
}
.dname
{
font-size: 60px;
font-weight: 700;
color: #fff;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 16px;
}
// ⚠️ 640 × 360, the cards' own resolution, so the picture is drawn 1:1 at 1080p rather than stretched.
.dart
{
width: 640px;
height: 360px;
flex-shrink: 0;
background-size: cover;
background-position: center;
background-color: #111317;
border: 1px solid #2a2e36;
align-items: center;
justify-content: center;
.noimg
{
font-size: 120px;
font-weight: 600;
color: #2a2e36;
}
}
// ⚠️ THE GAMEMODE POINTED AT, UNDER THE MAP'S PICTURE (the gamemode step, 2026-10-05). The map's name stays the big one.
.dmode
{
margin-top: 18px;
font-size: 30px;
font-weight: 600;
color: $gold;
text-transform: uppercase;
letter-spacing: 1px;
}
.ddesc
{
width: 640px;
margin-top: 18px;
font-size: 22px;
color: #c9cdd4;
}
.dmeta
{
margin-top: 14px;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 1.5px;
color: #6f747c;
&.loaded { color: $gold; }
// ⚠️ The lobby's own "can't ready" red (`.opt.disabled .opt-desc`): the same warning, said before the click.
&.unset { color: #d97a7a; }
}
// ── loading ─────────────────────────────────────────────────────────────
//
// ⛔ NOT AN OVERLAY. The page's ordinary content while a map loads, so it is plain flexbox with nothing to
// mis-measure — and it blocks interaction by construction, because the tabs and rows are simply not rendered.
.loadpage
{
position: absolute;
top: 0px; left: 0px; right: 0px; bottom: 0px;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
}
.loadpane
{
flex-direction: column;
align-items: center;
width: 520px;
.loadtitle
{
font-size: 26px;
font-weight: 600;
color: #fff;
text-align: center;
}
.loadstatus
{
margin-top: 8px;
font-size: 14px;
color: #7d828a;
}
.loaddetail
{
margin-top: 4px;
font-size: 12px;
color: #5c6169;
font-family: Consolas;
}
.loadbar
{
width: 100%;
height: 4px;
margin-top: 22px;
border-radius: 2px;
background-color: #1f232a;
overflow: hidden;
// ⛔ A TRAVELLING STRIPE, NOT A GROWING FILL. There is no download-progress API for a
// package mount, so a fill would be an animation pretending to be a measurement — and
// the moment it sat at 80% for a minute it would read as a hang.
.loadfill
{
width: 34%;
height: 100%;
border-radius: 2px;
background-color: $gold;
animation-name: mapload;
animation-duration: 1.1s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
}
}
}
}
// ⚠️ AT FILE SCOPE, NOT NESTED. s&box's SCSS does not hoist a @keyframes out of a selector block —
// nested, the rule compiles and the animation simply never runs, which looks like a frozen bar.
@keyframes mapload
{
0% { margin-left: -34%; }
100% { margin-left: 100%; }
}