seat tray: tray.js, SCSS, FTs, Jasmine specs

- new apps.epic.static tray.js: IIFE with drag-open/click-close/wobble
  behaviour; document-level pointermove+mouseup listeners; reset() for
  Jasmine afterEach; try/catch around setPointerCapture for synthetic events
- _room.scss: #id_tray_wrap fixed-right flex container; #id_tray_handle +
  #id_tray_grip (box-shadow frame, transparent inner window, border-radius
  clip); #id_tray_btn grab cursor; #id_tray bevel box-shadows, margin-left
  gap, height removed (align-items:stretch handles it); tray-wobble keyframes
- _applets.scss + _game-kit.scss: z-index raised (312-318) for primacy over
  tray (310)
- room.html: #id_tray_wrap + children markup; tray.js script tag
- FTs test_room_tray: 5 tests (T1-T5); _simulate_drag via execute_script
  pointer events (replaces unreliable ActionChains drag); wobble asserts on
  #id_tray_wrap not btn
- static_src/tests/TraySpec.js + SpecRunner.html: Jasmine unit tests for
  all tray.js branches

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-03-28 18:52:46 -04:00
parent 30ea0fad9d
commit b35c9b483e
22 changed files with 16193 additions and 18 deletions

View File

@@ -93,7 +93,7 @@
position: fixed;
bottom: 4.2rem;
right: 0.5rem;
z-index: 202;
z-index: 314;
}
}
@@ -104,7 +104,7 @@
position: fixed;
bottom: 6.6rem;
right: 1rem;
z-index: 201;
z-index: 312;
}
// In landscape: shift gear btn and applet menus left of the footer right sidebar

View File

@@ -9,7 +9,7 @@
top: auto;
}
z-index: 305;
z-index: 318;
font-size: 1.75rem;
cursor: pointer;
color: rgba(var(--secUser), 1);
@@ -42,14 +42,14 @@
border: none;
border-top: 0.1rem solid rgba(var(--terUser), 0.3);
background: rgba(var(--priUser), 0.97);
z-index: 204;
z-index: 316;
overflow: hidden;
@media (orientation: landscape) and (max-width: 1440px) {
$sidebar-w: 4rem;
// left: $sidebar-w;
right: $sidebar-w;
z-index: 301;
z-index: 316;
}
// Closed state
max-height: 0;
@@ -112,6 +112,7 @@
font-size: 1.5rem;
cursor: pointer;
padding: 0 0.125rem;
color: rgba(var(--terUser), 1);
}
.kit-bag-placeholder {
@@ -281,11 +282,11 @@
flex-direction: column;
gap: 0.5rem;
.fan-card-number { font-size: 0.65rem; opacity: 0.5; }
.fan-card-name-group { font-size: 0.65rem; opacity: 0.5; margin: 0; text-transform: uppercase; letter-spacing: 0.08em; }
.fan-card-name { font-size: 0.95rem; font-weight: bold; margin: 0; }
.fan-card-arcana { font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.1em; opacity: 0.6; }
.fan-card-correspondence { font-size: 0.6rem; opacity: 0.45; font-style: italic; }
.fan-card-number { font-size: 0.65rem; }
.fan-card-name-group { font-size: 0.65rem; margin: 0; text-transform: uppercase; letter-spacing: 0.08em; color: rgba(var(--secUser), 1); }
.fan-card-name { font-size: 0.95rem; font-weight: bold; margin: 0; color: rgba(var(--terUser), 1); }
.fan-card-arcana { font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.1em; color: rgba(var(--secUser), 1); }
.fan-card-correspondence { font-size: 0.6rem; font-style: italic; color: rgba(var(--secUser), 0.5); }
}
.fan-nav {

View File

@@ -17,7 +17,7 @@ $gate-line: 2px;
position: fixed;
bottom: 6.6rem;
right: 0.5rem;
z-index: 202;
z-index: 314;
background-color: rgba(var(--priUser), 0.95);
border: 0.15rem solid rgba(var(--secUser), 1);
box-shadow:
@@ -747,3 +747,138 @@ $inv-strip: 30px; // visible height of each stacked card after the first
.fan-card-arcana { font-size: 0.35rem; }
}
}
// ─── Seat tray ──────────────────────────────────────────────────────────────
//
// Structure:
// #id_tray_wrap — fixed right edge, flex row, slides on :has(.open)
// #id_tray_handle — $handle-exposed wide; contains grip + button
// #id_tray_grip — position:absolute; ::before/::after = concentric rects
// #id_tray_btn — circle button (z-index:1, paints above grip)
// #id_tray — 280px panel; covers grip's rightward extension when open
//
// Closed: wrap translateX($tray-w) → only button circle visible at right edge.
// Open: translateX(0) → full tray panel slides in; grip rects visible as handle.
$tray-w: 280px;
$handle-rect-w: 10000px;
$handle-rect-h: 72px;
$handle-exposed: 48px;
$handle-r: 1rem;
#id_tray_wrap {
position: fixed;
// left set by JS: closed = vw - handle; open = 0
// top/bottom set by JS from nav/footer measurements
top: 0;
right: 0;
bottom: 0;
z-index: 310;
pointer-events: none;
display: flex;
flex-direction: row;
align-items: stretch;
transition: left 0.35s cubic-bezier(0.4, 0, 0.2, 1);
&.tray-dragging { transition: none; }
&.wobble { animation: tray-wobble 0.45s ease; }
}
#id_tray_handle {
flex-shrink: 0;
position: relative;
width: $handle-exposed;
display: flex;
align-items: center;
justify-content: center;
}
#id_tray_grip {
position: absolute;
top: 50%;
left: calc(#{$handle-exposed} / 2 - 0.125rem);
transform: translateY(-50%);
width: $handle-rect-w;
height: $handle-rect-h;
pointer-events: none;
// Border + overflow:hidden on the grip itself clips ::before's shadow with correct radius
border-radius: $handle-r;
border: 0.15rem solid rgba(var(--secUser), 1);
overflow: hidden;
// Inset inner window: box-shadow spills outward to fill the opaque frame area,
// clipped to grip's rounded edge by overflow:hidden. background:transparent = see-through hole.
&::before {
content: '';
position: absolute;
inset: 0.4rem;
border-radius: calc(#{$handle-r} - 0.35rem);
border: 0.15rem solid rgba(var(--secUser), 1);
background: transparent;
box-shadow: 0 0 0 200px rgba(var(--priUser), 1);
}
&::after {
content: none;
}
}
#id_tray_btn {
pointer-events: auto;
position: relative;
z-index: 1; // above #id_tray_grip
width: 3rem;
height: 3rem;
border-radius: 50%;
background-color: rgba(var(--priUser), 1);
border: 0.15rem solid rgba(var(--secUser), 1);
cursor: grab;
display: inline-flex;
align-items: center;
justify-content: center;
&.active {
color: rgba(var(--quaUser), 1);
border-color: rgba(var(--quaUser), 1);
}
i {
font-size: 1.75rem;
color: rgba(var(--secUser), 1);
pointer-events: none;
}
&:active { cursor: grabbing; }
&.open { cursor: pointer; }
}
@keyframes tray-wobble {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-8px); }
40% { transform: translateX(6px); }
60% { transform: translateX(-5px); }
80% { transform: translateX(3px); }
}
#id_tray {
flex: 1;
min-width: 0;
margin-left: 0.5rem; // small gap so tray appears slightly off-screen on drag start
pointer-events: auto;
position: relative;
z-index: 1; // above #id_tray_grip pseudo-elements
background: rgba(var(--secUser), 1);
border-left:2.5rem solid rgba(var(--terUser), 1);
border-top: 2.5rem solid rgba(var(--terUser), 1);
border-bottom: 2.5rem solid rgba(var(--terUser), 1);
box-shadow:
-0.25rem 0 0.5rem rgba(0, 0, 0, 0.75),
inset 0 0 0 0.12rem rgba(255, 255, 255, 0.12), // bright bevel ring at wall edge
inset 0.6rem 0 1.5rem -0.5rem rgba(0, 0, 0, 0.45), // left wall depth
inset 0 0.6rem 1.5rem -0.5rem rgba(0, 0, 0, 0.3), // top wall depth
inset 0 -0.6rem 1.5rem -0.5rem rgba(0, 0, 0, 0.3) // bottom wall depth
;
overflow-y: auto;
// scrollbar-width: thin;
// scrollbar-color: rgba(var(--terUser), 0.3) transparent;
}