ui/toolpanel.razor.scss
// Tool settings — the column that sits to the RIGHT of the Q menu.
//
// Rendered as a CHILD of DevMenu's root, so it inherits that root's
// `pointer-events: none` and has to opt back in itself. Declaring neither makes
// a panel silently un-hit-testable in s&box — that was the lobby bug.
//
// ⚠️ ROW LAYOUT IS TWO LINES, NOT ONE. The first version crammed label, hint
// and a − value + control onto a single 340px line; the hint squeezed the label,
// the value had nowhere to go, and every edit was a click on a 24px target.
// Label and field share the top line, the hint gets its own underneath.
.toolpanel
{
pointer-events: all;
// ⚠️ Positioned from the CENTRE, not from the screen edge. Both this and the
// dev menu are centred by the shared root, so the offset is half the Q
// menu's 820px width plus a gutter. Anchoring to the right edge instead
// would drift apart from it at every other resolution.
position: absolute;
left: 50%;
margin-left: 426px;
// Anchored at the top; height follows the content for short lists.
top: 8%;
width: 380px;
flex-direction: column;
background-color: rgba( 14, 16, 20, 0.97 );
border: 1px solid rgba( 255, 255, 255, 0.12 );
border-radius: 6px;
overflow: hidden;
// ⚠️ `bottom` AS WELL AS `top` — a DEFINITE height, not a max-height.
//
// DevMenu's frame is a flex CHILD, so its `max-height: 80%` gets applied by
// flex layout and its body shrinks and scrolls. This panel is
// position: absolute — out of flex flow — so nothing sized it, the
// percentage max-height never clamped, and the rows just ran off the bottom
// of the screen with no overflow for scrolling to act on.
//
// Only applied once the list is long enough to need it, so a two-row tool
// does not get a full-height frame with nothing in it.
&.tall { bottom: 8%; }
.titlebar
{
flex-direction: row;
align-items: center;
padding: 12px 16px;
background-color: rgba( 255, 255, 255, 0.05 );
border-bottom: 1px solid rgba( 255, 255, 255, 0.08 );
.title
{
font-size: 15px;
color: #fff;
flex-grow: 1;
}
.close
{
font-size: 15px;
color: rgba( 255, 255, 255, 0.35 );
padding: 2px 6px;
border-radius: 4px;
cursor: pointer;
&:hover { color: #fff; background-color: rgba( 255, 255, 255, 0.10 ); }
}
}
.rows
{
flex-direction: column;
padding: 4px 0px;
// Take everything between the titlebar and the footer, and scroll what
// does not fit. flex-shrink is stated rather than assumed — without a
// shrinkable, bounded box there is nothing for overflow to act on.
flex-grow: 1;
flex-shrink: 1;
overflow-y: scroll;
}
// The titlebar and footer must NOT be squeezed to make room for the rows —
// otherwise a long list eats the close button.
.titlebar, .foot { flex-shrink: 0; }
.row
{
// ⛔ `flex-shrink: 0` OR A BOUNDED PANEL CRAMS INSTEAD OF SCROLLING, AND THAT IS WHAT
// "everything is trying to fit on screen at once and nothing is understandable" WAS.
// `.rows` is a flex column with a DEFINITE height once `.tall` is on, and a flex child
// here defaults to shrinkable — the two lines above exist precisely because it does. So
// the rows were being squeezed to fit the box rather than overflowing it: labels, fields
// and the option chips all compressed, and `overflow-y: scroll` had nothing to scroll
// because nothing ever overflowed.
//
// ⚠️ THIS IS WHAT MAKES "SCROLLABLE" MEAN WHAT IT SOUNDS LIKE. Every row keeps the height
// it would have had with no bound at all — the panel looks exactly as it did before it
// was given one — and the surplus runs off the bottom of `.rows`, which is the thing the
// scrollbar acts on. Rows shrinking and rows scrolling are alternatives, not partners.
flex-shrink: 0;
flex-direction: column;
padding: 9px 16px;
border-bottom: 1px solid rgba( 255, 255, 255, 0.05 );
.head
{
flex-direction: row;
align-items: center;
}
.label
{
flex-grow: 1;
font-size: 14px;
color: rgba( 255, 255, 255, 0.90 );
}
// Hint on its own line, dim. It is reference, not part of the control.
.hint
{
font-size: 11px;
color: rgba( 255, 255, 255, 0.30 );
margin-top: 3px;
}
}
// ── the controls ────────────────────────────────────────────────────────
.field
{
flex-direction: row;
align-items: center;
}
.entry
{
width: 108px;
height: 28px;
padding: 0px 8px;
font-size: 14px;
color: #ffd479;
background-color: rgba( 0, 0, 0, 0.45 );
border: 1px solid rgba( 255, 255, 255, 0.12 );
border-radius: 4px;
&:hover { border-color: rgba( 255, 255, 255, 0.28 ); }
&:focus { border-color: #ffd479; color: #fff; }
}
.nudge
{
flex-direction: row;
margin-left: 6px;
.step
{
width: 26px;
height: 28px;
align-items: center;
justify-content: center;
font-size: 15px;
color: rgba( 255, 255, 255, 0.50 );
background-color: rgba( 255, 255, 255, 0.06 );
cursor: pointer;
&:first-child { border-radius: 4px 0px 0px 4px; margin-right: 1px; }
&:last-child { border-radius: 0px 4px 4px 0px; }
&:hover { background-color: rgba( 255, 255, 255, 0.18 ); color: #fff; }
}
}
// A toggle is a state, so it reads as one — not as a number you nudge.
.toggle
{
width: 74px;
height: 28px;
align-items: center;
justify-content: center;
font-size: 13px;
color: rgba( 255, 255, 255, 0.45 );
background-color: rgba( 255, 255, 255, 0.06 );
border: 1px solid rgba( 255, 255, 255, 0.10 );
border-radius: 4px;
cursor: pointer;
&:hover { border-color: rgba( 255, 255, 255, 0.30 ); }
&.on
{
color: #0e1014;
background-color: #7ad17a;
border-color: #7ad17a;
}
}
// Pick-one chips. Same visual language as .toggle — both answer "which
// state is this in?", so they should not look like different kinds of
// control.
//
// ⚠️ WRAPS, and the row is allowed to grow. A weapon list is as long as the
// pack is; forcing it onto one line would clip the options you cannot see
// into ones you cannot reach.
.swatches
{
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-end;
gap: 4px;
max-width: 240px;
.swatch
{
width: 22px;
height: 22px;
border-radius: 3px;
// A hairline so a white swatch still has an edge against the panel.
border: 1px solid rgba( 0, 0, 0, 0.45 );
cursor: pointer;
&:hover { transform: scale( 1.12 ); }
// ⚠️ RINGED, NOT TINTED. On a row of colours a "selected" highlight would be
// one more colour competing with the twelve on show; an outset ring reads as
// selection without changing what the swatch is showing.
&.on
{
border: 2px solid #ffffff;
box-shadow: 0px 0px 0px 2px rgba( 0, 0, 0, 0.6 );
}
}
}
.options
{
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-end;
gap: 4px;
max-width: 240px;
.opt
{
padding: 3px 8px;
font-size: 12px;
color: rgba( 255, 255, 255, 0.55 );
background-color: rgba( 255, 255, 255, 0.06 );
border: 1px solid rgba( 255, 255, 255, 0.10 );
border-radius: 4px;
cursor: pointer;
&:hover { border-color: rgba( 255, 255, 255, 0.30 ); color: #fff; }
&.on
{
color: #0e1014;
background-color: #4fc9a8; // teal — the Player-assisting colour
border-color: #4fc9a8;
}
}
}
.info
{
font-size: 13px;
color: rgba( 255, 255, 255, 0.40 );
}
.foot
{
padding: 9px 16px;
font-size: 11px;
color: rgba( 255, 255, 255, 0.28 );
border-top: 1px solid rgba( 255, 255, 255, 0.08 );
}
}