/* body.dock is the STATIONARY full-surface DRAG HANDLE — it carries `drag: drag` (so RmlUi emits dragstart/drag/dragend that our bind_drag reads) but it is NEVER transformed. This is load-bearing: RmlUi projects a drag event's mouse_x/mouse_y into the DRAGGED element's transformed coordinate frame, so if the dragged element itself were translated by `slide` (as it was), the reported drag x would feed back into `slide` and ping-pong between the finger position and ~dock_width every frame (the flicker). Keeping the drag handle at identity transform makes the reported coordinate the raw finger position. The MOVING visual is div.panel below — a child that is NOT the drag element, so its transform never enters the drag projection. body itself paints nothing (the gradient lives on the panel); it is just the input-capture + drag-origin layer. */ body.dock { width: 100%; height: 100%; drag: drag; } /* div.panel is the VALUE-DRIVEN moving visual: data-style-transform binds translateX(slide px) (closed = -dock_width, open = 0). The gradient, padding, flex layout and scroll live here, not on body. */ div.panel { width: 100%; height: 100%; /* Use the SHADER-based linear-gradient (not the legacy horizontal-gradient, which renders via interpolated vertex colours and shows hard 8-bit alpha banding over light backgrounds). linear-gradient is computed per-pixel in our GL3 fragment shader, which also applies ~1 LSB TPDF dither to break the alpha-ramp banding as it fades to transparent. */ decorator: linear-gradient( to right, #000000ff, #00000000 ); padding: 8dp; font-family: Noto Sans; display: flex; flex-direction: column; align-items: center; overflow-x: hidden; overflow-y: auto; } /* DECLARATIVE EASING CARRIER for the C++ animator (d1 fix). This `transition` is NOT animated by RmlUi: RmlUi only starts a CSS transition on a class/definition change, and `slide` is driven through the inline data-style-transform above, which RmlUi never animates. Instead the C++ SlideAnimator READS this transition (duration + tween) via UiSurface::transition_timing("panel", "transform") and drives slide_px_ itself, frame by frame — so editing the duration/tween here hot-reloads the on-seat feel with no recompile. The element id="panel" is what transition_timing looks the element up by. NOTE: RmlUi 6.2 has NO bare `linear` keyword — use a named tween; `cubic-in-out` is the authored ease here. */ #panel { transition: transform 0.36s cubic-in-out; } /* Hide both scrollbars (the rail scrolls, but no visible scrollbar). RmlUi instances scrollbarvertical/scrollbarhorizontal elements; width/height 0 hides them (verified pattern: vendored Samples effects_style.rcss / invader.rcss). */ div.panel scrollbarvertical { width: 0dp; } div.panel scrollbarhorizontal { height: 0dp; } /* The slot WRAPPER. margin:auto on this single flex item vertically centers the card stack when it fits, and collapses to 0 free-space (top stays reachable, no clipping) when the cards overflow — the correct flex-center+overflow pattern (justify-content:center would strand/clip the top on overflow). flex-shrink:0 so the stack keeps its natural height and the container scrolls. */ div.rail { display: flex; flex-direction: column; align-items: center; flex-shrink: 0; margin: auto 0; } @keyframes slot-enter { from { opacity: 0; transform: translateX(-12dp) scale(0.72); } to { opacity: 1; transform: translateX(0px) scale(1.0); } } div.slot { display: block; position: relative; width: 224dp; height: 140dp; min-height: 140dp; margin-bottom: 8dp; background-color: #2e2e32ff; border-radius: 10dp; overflow: hidden; transform-origin: 0% 0%; animation: slot-enter 0.16s cubic-out 1 normal; } div.slot div.thumb { /* OVERSCAN the slot by 2dp on every side: RmlUi resolves the inset/percent box anchored top-left, leaving the thumb ~2px short on the RIGHT so the #2e2e32 slot placeholder peeked through there (diagnosed real-seat: the sliver sampled as the placeholder, not the thumb). A negative inset makes the thumb a few dp LARGER than the slot; the slot's rounded overflow:hidden clips the overscan to the rounded card, so every edge is fully covered with no placeholder peek and the corners stay rounded. cover/center keeps the slightly-larger box fully covered + centered. */ display: block; position: absolute; left: -2dp; top: -2dp; right: -2dp; bottom: -2dp; } div.slot span.title { /* Title overlay INTENTIONALLY NOT RENDERED (user decision): the card is thumbnail-only for now. display:none keeps the {{ row.title }} binding + the bind_list_string("slots","title", …) getter LIVE and compiling so the overlay can be re-enabled later by restoring `display: block`. The scrim squared off the thumbnail's rounded bottom corners + protruded on the right; hiding it lets all four corners round cleanly. */ display: none; position: absolute; left: 0; right: 0; bottom: 0; padding: 4dp 6dp; background-color: #00000099; color: #f2f2f2ff; font-size: 13dp; text-align: center; }