ui/tilttuner.razor.scss
// TILT TUNER — a dev panel, so it reads as one.
//
// ⚠️ MODELLED ON RecoilTuner.razor.scss, NOT SHARING IT. Razor components scope their own styles;
// a shared sheet would make either tuner's tweak silently change the other. That file already
// makes the same argument about its own copy of SoulBoxTuner's.
//
// ⚠️ COOL GREY, DELIBERATELY PLAIN. It sits over the game while the player watches a gun kick — a
// styled panel would compete with the thing being judged, which is the whole job here.

.tilt {
    position: absolute;
    width: 100%;
    height: 100%;
    pointer-events: none;
    align-items: flex-start;
    justify-content: flex-start;

    font-family: Inter;

    // ⛔ NOT `display: none`. A zero-opacity panel keeps its layout, so the rows do not reflow on
    // the frame it opens.
    &.hidden {
        opacity: 0;
        pointer-events: none;
    }
}

.panel {
    // ⛔ ABSOLUTE, BECAUSE `left`/`top` MEAN NOTHING OTHERWISE. The drag writes those two
    // properties every frame; in the flow layout this panel started in they would have been
    // parsed, stored and ignored, which looks exactly like the drag code never running.
    //
    // ⚠️ THE STARTING CORNER IS THE OLD PADDING. It sat at x=600 to clear the 520px-wide
    // `nz_recoil` panel at x=40, so both tuners can be open at once — the lean is judged against
    // the kick that caused it. Now it is a starting point rather than a fixed home.
    position: absolute;
    left: 600px;
    top: 60px;

    flex-direction: column;
    width: 500px;
    background-color: rgba(8, 10, 12, 0.92);
    border: 1px solid rgba(255, 255, 255, 0.10);
    border-radius: 4px;
    padding: 14px 16px 16px;

    // ⚠️ The ROOT is pointer-events:none so the game keeps the mouse everywhere else; only the
    // panel itself takes clicks, or the sliders cannot be dragged.
    pointer-events: all;
}

.head {
    // ⚠️ A MOVE CURSOR, because a bar that drags and does not say so is a bar nobody tries.
    cursor: move;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 10px;
    padding-bottom: 8px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.12);

    .title {
        font-size: 13px;
        font-weight: 600;
        letter-spacing: 1.6px;
        color: rgba(228, 234, 240, 1);
    }

    .close {
        font-size: 20px;
        color: rgba(150, 160, 172, 1);
        padding: 0 4px;
        cursor: pointer;

        // ⚠️ ABOVE THE GRIP, so the button keeps its hover and its click. The grip already
        // stops short of it; this is the belt to that braces.
        z-index: 2;

        &:hover { color: rgba(235, 240, 245, 1); }
    }

    // ⛔ THE DRAG SURFACE, COVERING THE BAR EXCEPT ITS RIGHT EDGE. It is a bare overlay rather
    // than a wrapper because the razor generator will not compile children inside a custom Panel.
    //
    // ⚠️ 88% LEAVES ~56px FOR THE CLOSE BUTTON. Any wider and the × becomes a drag grip that
    // happens to close the window when you do not move; any narrower and the title bar has a dead
    // strip in the middle of it.
    .grip {
        position: absolute;
        left: 0;
        top: 0;
        width: 88%;
        height: 100%;
        z-index: 1;
        cursor: move;
    }
}

.warn {
    font-size: 12.5px;
    color: rgba(226, 180, 110, 1);
    margin-bottom: 10px;
}

// ⚠️ THE GROUP HEADINGS CARRY WHAT THE SLIDER MOVES, not just its name. "Damp" means nothing on
// its own; "a rolled model tips the sight picture itself" is why the slider exists.
.group {
    font-size: 11px;
    letter-spacing: 1.1px;
    color: rgba(128, 140, 154, 1);
    margin: 12px 0 4px;
    padding-bottom: 4px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

.row {
    flex-direction: row;
    align-items: center;
    height: 30px;

    .label {
        width: 58px;
        font-size: 13px;
        color: rgba(160, 170, 182, 1);
    }

    .s {
        flex-grow: 1;
        margin: 0 10px;
    }

    // ⚠️ Tabular figures and a fixed width, so the number does not shove the slider sideways as it
    // ticks through 9 -> 10 -> 100. Wider than the recoil tuner's because "instant" lives here.
    .val {
        width: 66px;
        justify-content: flex-end;
        text-align: right;
        font-size: 13px;
        color: rgba(224, 230, 236, 1);
        font-variant-numeric: tabular-nums;
    }
}

// The delivered figure. Monospace because it is data, green because it is the answer rather than
// the input — the sliders above are what you set, this is what the gun does.
.out {
    margin-top: 10px;
    padding: 8px 10px;
    background-color: rgba(0, 0, 0, 0.45);
    border: 1px solid rgba(255, 255, 255, 0.07);
    border-radius: 3px;
    font-family: Consolas;
    font-size: 12px;
    color: rgba(150, 210, 170, 1);
    white-space: nowrap;

    // The second line is character rather than magnitude — how it stops, where it turns, whether
    // the softness outruns the fire rate. Warm, so the two are not read as one number each.
    &.climb {
        margin-top: 4px;
        color: rgba(200, 190, 140, 1);
    }

    // The fleet line is about OTHER weapons than the one in your hands, so it is cooler than
    // both — a different subject, not a third number about this gun.
    &.fleet {
        margin-top: 4px;
        color: rgba(150, 180, 215, 1);
    }
}

.buttons {
    flex-direction: row;
    margin-top: 12px;
}

.btn {
    padding: 7px 14px;
    margin-right: 8px;
    background-color: rgba(255, 255, 255, 0.07);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 3px;
    font-size: 12.5px;
    letter-spacing: 0.6px;
    color: rgba(224, 230, 236, 1);
    cursor: pointer;

    &:hover { background-color: rgba(255, 255, 255, 0.13); }

    &.copy {
        background-color: rgba(70, 130, 100, 0.35);
        border-color: rgba(120, 200, 160, 0.35);
    }
}