ui/perfhud.razor.scss
// PERF PANEL — a dev tool, so it reads as one.
//
// ⛔ `pointer-events: all`, NOT `auto`. s&box's CSS subset does not know the standard `auto`
// keyword, so the first version set it on every button and was silently ignored — the panel drew
// correctly and nothing in it could be clicked. TradeTableTuner.razor.scss is the working
// precedent and it uses `all`.
//
// ⛔ AND IT IS ONLY ON WHEN THE MENU IS OPEN. A panel in the top-right corner that permanently
// takes the mouse would eat clicks over its own area while you play — including shooting. So the
// readout passes them through and `.live` (set by MenuOpen) is what claims them.
//
// ⚠️ REAL WIDTH, REAL FONT SIZES. The first version let the box size to its content with 9-11px
// text, which came out unreadable on a laptop. The tuner's 460px / 13px is the scale that works
// here; this is a little wider because the owner buttons wrap.
.perfhud
{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
font-family: Inter;
// ⛔ NOT `display: none`. A zero-opacity panel keeps its layout, so the rows do not reflow on
// the frame it opens — the same reason Scoreboard and the tuner do it this way.
&.hidden
{
opacity: 0;
pointer-events: none;
}
.box
{
position: absolute;
top: 20px;
right: 20px;
flex-direction: column;
width: 400px;
padding: 14px 16px 16px;
gap: 4px;
// A cap plus scrolling, because the owner list grows with the config — a canyon config has
// a dozen rows and an uncapped panel would run off the bottom of the screen.
max-height: 86%;
overflow: scroll;
background-color: rgba( 8, 10, 14, 0.92 );
border: 1px solid rgba( 255, 255, 255, 0.12 );
border-radius: 4px;
font-size: 15px;
color: rgba( 235, 240, 248, 0.92 );
// ⚠️ ONLY the open menu takes the mouse — see the note at the top.
&.live { pointer-events: all; }
}
.fps
{
flex-direction: row;
align-items: center;
gap: 6px;
margin-bottom: 6px;
padding-bottom: 10px;
border-bottom: 1px solid rgba( 255, 255, 255, 0.12 );
font-size: 38px;
font-weight: 700;
&.good { color: #7ddc8a; }
&.ok { color: #e8c46a; }
&.bad { color: #ec6a6a; }
.unit
{
font-size: 15px;
font-weight: 400;
color: rgba( 235, 240, 248, 0.5 );
}
.spacer { flex-grow: 1; }
.tog
{
font-size: 20px;
font-weight: 400;
color: rgba( 235, 240, 248, 0.6 );
padding: 2px 10px;
cursor: pointer;
&:hover { color: #fff; }
}
}
.line
{
flex-direction: row;
align-items: center;
flex-shrink: 0;
gap: 6px;
height: 22px;
&.dim { color: rgba( 235, 240, 248, 0.55 ); }
&.rec
{
margin-top: 6px;
color: #ec6a6a;
font-weight: 600;
}
.k
{
width: 52px;
font-size: 13px;
color: rgba( 235, 240, 248, 0.45 );
}
.v
{
width: 108px;
&.warn { color: #e8c46a; }
&.wide { width: 250px; }
}
}
// ── the menu ─────────────────────────────────────────────────────────────
.menu
{
flex-direction: column;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid rgba( 255, 255, 255, 0.12 );
gap: 5px;
}
.head
{
flex-direction: row;
align-items: center;
gap: 8px;
margin-top: 8px;
font-size: 12px;
font-weight: 600;
letter-spacing: 1.4px;
color: rgba( 235, 240, 248, 0.5 );
.hint
{
font-size: 11px;
font-weight: 400;
letter-spacing: 0;
color: rgba( 235, 240, 248, 0.32 );
}
}
.btns
{
flex-direction: row;
flex-wrap: wrap;
gap: 5px;
}
.btn
{
// ⚠️ flex-shrink 0. It defaults to 1 here, and a wrapped row of buttons squashes into
// unreadable slivers instead of wrapping — the trap the map browser's rows hit too.
flex-shrink: 0;
align-items: center;
justify-content: center;
height: 30px;
padding: 0 12px;
font-size: 13px;
cursor: pointer;
background-color: rgba( 255, 255, 255, 0.08 );
border: 1px solid rgba( 255, 255, 255, 0.12 );
border-radius: 3px;
color: rgba( 235, 240, 248, 0.88 );
&:hover
{
background-color: rgba( 255, 255, 255, 0.18 );
color: #fff;
}
// A live state, not a pressed look — these are toggles, and the panel is the only place
// that says which way they currently sit.
&.on
{
background-color: rgba( 125, 220, 138, 0.18 );
border-color: rgba( 125, 220, 138, 0.5 );
color: #a8e8b2;
}
&.owner
{
flex-direction: row;
gap: 7px;
// OFF is the loud state on purpose: a hidden owner is a scene that no longer matches
// the map, and forgetting one is how a later measurement comes out wrong.
&.off
{
background-color: rgba( 236, 106, 106, 0.18 );
border-color: rgba( 236, 106, 106, 0.55 );
color: #f0a0a0;
}
.cnt
{
font-size: 11px;
color: rgba( 235, 240, 248, 0.45 );
}
}
&.mark
{
background-color: rgba( 232, 196, 106, 0.14 );
border-color: rgba( 232, 196, 106, 0.34 );
color: #e8c46a;
}
}
.foot
{
flex-direction: row;
gap: 5px;
margin-top: 12px;
padding-top: 10px;
border-top: 1px solid rgba( 255, 255, 255, 0.12 );
}
}