$gate-node: 64px; $gate-gap: 36px; $gate-line: 2px; .room-page { position: relative; display: flex; align-items: center; justify-content: center; flex: 1; min-height: 0; overflow: hidden; } #id_room_menu { position: fixed; bottom: 6.6rem; right: 0.5rem; z-index: 314; background-color: rgba(var(--priUser), 0.95); border: 0.15rem solid rgba(var(--secUser), 1); box-shadow: 0 0 0.5rem rgba(var(--secUser), 0.75), 0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25) ; border-radius: 0.75rem; padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem; } // Scroll-lock when gate is open. Uses html (not body) to avoid CSS overflow // propagation quirk on Linux headless Firefox where body overflow:hidden can // disrupt pointer events on position:fixed descendants. // NOTE: may be superfluous — root cause of CI kit-btn failures turned out to be // game-kit.js missing from git (was in gitignored STATIC_ROOT only). html:has(.gate-overlay) { overflow: hidden; } .gate-overlay { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(4px); z-index: 100; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; // Prevents backdrop from intercepting clicks on position:fixed elements // (e.g. #id_kit_btn) in Linux headless Firefox. // NOTE: may be superfluous — see html:has comment above. pointer-events: none; } .gate-modal { display: flex; flex-direction: column; align-items: center; pointer-events: auto; padding: 2rem; border: 0.1rem solid rgba(var(--terUser), 0.5); border-radius: 1rem; background-color: rgba(var(--priUser), 1); .gate-header { text-align: center; h1 { font-size: 2rem; color: rgba(var(--secUser), 0.6); margin-bottom: 1rem; text-align: justify; text-align-last: center; text-justify: inter-character; text-transform: uppercase; text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.125), // highlight (up-left) -0.125rem -0.125rem 0 rgba(0, 0, 0, 0.8) // shadow (down-right) ; span { color: rgba(var(--quaUser), 0.6); } margin: 0 0 0.5rem; } .gate-status-wrap { display: flex; justify-content: center; align-items: baseline; opacity: 0.5; font-size: 0.75em; text-transform: uppercase; letter-spacing: 0.15em; margin-bottom: 1rem; .status-dots { display: inline-flex; span { display: inline-block; width: 0.5em; text-align: center; } } } } .token-slot { position: relative; display: flex; flex-direction: row; border: 2px solid rgba(var(--terUser), 0.7); border-radius: 0.4rem; background: rgba(0, 0, 0, 0.35); min-width: 180px; &.locked { opacity: 0.3; pointer-events: none; } &.ready { border-color: rgba(var(--terUser), 1); button.token-rails { box-shadow: 0 0 0.6rem rgba(var(--terUser), 0.6), 0 0 1.6rem rgba(var(--terUser), 0.25) ; .rail { background: rgba(var(--terUser), 1); } } } &.pending, &.claimed { box-shadow: 0 0 0.6rem rgba(var(--terUser), 0.5), 0 0 1.4rem rgba(var(--terUser), 0.2), ; .token-return-btn { text-shadow: 0 0 0.5rem rgba(var(--terUser), 0.8); } &:hover { border-color: rgba(var(--terUser), 1); background: rgba(0, 0, 0, 0.55); box-shadow: 0 0 0.8rem rgba(var(--terUser), 0.75), 0 0 2rem rgba(var(--terUser), 0.35), ; } } .token-rails, button.token-rails { display: flex; flex-direction: row; align-items: stretch; padding: 0.6rem 0.45rem; gap: 0.2rem; border-right: 1px solid rgba(var(--terUser), 0.35); .rail { display: block; width: 2px; background: rgba(var(--terUser), 0.55); border-radius: 1px; } } button.token-rails { background: transparent; border: none; outline: none; border-right: 1px solid rgba(var(--terUser), 0.35); cursor: pointer; border-radius: 0.3rem 0 0 0.3rem; &:hover { background: rgba(var(--terUser), 0.1); .rail { background: rgba(var(--terUser), 1); } } } .token-return-btn { position: absolute; inset: 0; background: transparent; border: none; outline: none; cursor: pointer; border-radius: inherit; } .token-panel { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 0.45rem 0.75rem; gap: 0.15rem; .token-denomination { font-size: 1.5em; font-weight: bold; color: rgba(var(--terUser), 1); line-height: 1; } .token-insert-label, .token-insert-btn { &::before { content: '← '; } font-size: 0.6em; text-transform: uppercase; letter-spacing: 0.08em; text-align: center; line-height: 1.3; } .token-return-label { font-size: 0.55em; text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.5; line-height: 1.3; text-align: center; } } } .gate-slots { display: flex; flex-direction: row; align-items: center; gap: $gate-gap; .gate-slot { position: relative; width: $gate-node; height: $gate-node; border-radius: 50%; border: $gate-line solid rgba(var(--terUser), 1); display: flex; flex-direction: column; align-items: center; justify-content: center; flex-shrink: 0; &.filled, &.reserved { background: rgba(var(--terUser), 0.2); } &.filled:hover, &.reserved:hover { box-shadow: -0.1rem -0.1rem 1rem rgba(var(--ninUser), 1), -0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 1), 0.05rem 0.05rem 0.5rem rgba(0, 0, 0, 1), ; } .slot-number { font-size: 0.7em; opacity: 0.5; } .slot-gamer { display: none; } form { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; } // CARTE drop-target circle — matches .reserved appearance &:has(.drop-token-btn) { background: rgba(var(--terUser), 0.2); &:hover { box-shadow: -0.1rem -0.1rem 1rem rgba(var(--ninUser), 1), -0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 1), 0.05rem 0.05rem 0.5rem rgba(0, 0, 0, 1), ; } } } } } // Narrow viewport — scale down, 2×3 slot grid (portrait mobile + narrow desktop) @media (max-width: 700px) { .gate-modal { padding: 1.25rem 1.5rem; .gate-header { h1 { font-size: 1.5rem; } .gate-status-wrap { margin-bottom: 0.5rem; } } .token-slot { min-width: 150px; } .gate-slots { display: grid; grid-template-columns: repeat(3, 52px); grid-template-rows: repeat(2, 52px); gap: 24px; .gate-slot { width: 52px; height: 52px; &:nth-child(1) { grid-column: 1; grid-row: 1; } &:nth-child(2) { grid-column: 2; grid-row: 1; } &:nth-child(3) { grid-column: 3; grid-row: 1; } &:nth-child(4) { grid-column: 1; grid-row: 2; } &:nth-child(5) { grid-column: 2; grid-row: 2; } &:nth-child(6) { grid-column: 3; grid-row: 2; } } } } } // ─── Room shell layout ───────────────────────────────────────────────────── .room-shell { display: flex; flex-direction: row; align-items: stretch; gap: 2rem; width: 100%; max-height: 80vh; } // ─── Table hex + seat positions ──────────────────────────────────────────── // // .table-hex: regular pointy-top hexagon. // clip-path polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%) // on a 160×185 container gives equal-length sides (height = width × 2/√3). // // Seats use absolute positioning from the .room-table centre. // $seat-r = 130px — radius to seat centroid // $seat-r-x = round(130px × sin60°) = 113px — horizontal component // $seat-r-y = round(130px × cos60°) = 65px — vertical component // // Clockwise from top: slots 1→2→3→4→5→6. $seat-r: 130px; $seat-r-x: round($seat-r * 0.866); // 113px $seat-r-y: round($seat-r * 0.5); // 65px .room-table { flex: 2; position: relative; display: flex; align-items: center; justify-content: center; min-height: 300px; } // Hex border: clip-path clips CSS borders, so the ring is a wrapper with the // same hex polygon at a slightly larger size. 0.25rem each side — subtle only. .table-hex-border { width: calc(160px + 0.5rem); height: calc(185px + 0.5rem); clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%); background: rgba(var(--quaUser), 1); filter: drop-shadow(0 0 6px rgba(var(--quaUser), 0.5)); display: flex; align-items: center; justify-content: center; } .table-hex { width: 160px; height: 185px; clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%); // Six gradients — one per hex face — each perpendicular to that face so the // shadows follow the hex geometry rather than the rectangular bounding box. // CSS angle convention: 0°=up, 90°=right. Shadow goes FROM face INWARD. // Left face → 90° Right face → 270° // Top-left face → 150° Top-right face → 210° // Bottom-left face → 30° Bottom-right face→ 330° background: linear-gradient(90deg, rgba(0, 0, 0, 0.2) 0%, transparent 15%), linear-gradient(90deg, rgba(var(--quaUser), 0.1) 0%, transparent 15%), linear-gradient(270deg, rgba(0, 0, 0, 0.2) 0%, transparent 15%), linear-gradient(270deg, rgba(var(--quaUser), 0.1) 0%, transparent 15%), linear-gradient(210deg, rgba(0, 0, 0, 0.2) 0%, transparent 15%), linear-gradient(210deg, rgba(var(--quaUser), 0.1) 0%, transparent 15%), linear-gradient(150deg, rgba(0, 0, 0, 0.2) 0%, transparent 15%), linear-gradient(150deg, rgba(var(--quaUser), 0.1) 0%, transparent 15%), linear-gradient(30deg, rgba(0, 0, 0, 0.2) 0%, transparent 15%), linear-gradient(30deg, rgba(var(--quaUser), 0.1) 0%, transparent 15%), linear-gradient(330deg, rgba(0, 0, 0, 0.2) 0%, transparent 15%), linear-gradient(330deg, rgba(var(--quaUser), 0.1) 0%, transparent 15%), rgba(var(--duoUser), 1); display: flex; align-items: center; justify-content: center; } .table-center { display: flex; align-items: center; justify-content: center; } .room-inventory { flex: 1; display: flex; flex-direction: column; gap: 1rem; min-height: 0; overflow-y: auto; scrollbar-width: thin; scrollbar-color: rgba(var(--terUser), 0.3) transparent; } .table-seat { position: absolute; display: flex; flex-direction: column; align-items: center; gap: 0.25rem; // Centre the element on its anchor point transform: translate(-50%, -50%); // Clockwise from top — slot drop order during ROLE_SELECT &[data-slot="1"] { left: 50%; top: calc(50% - #{$seat-r}); } &[data-slot="2"] { left: calc(50% + #{$seat-r-x}); top: calc(50% - #{$seat-r-y}); } &[data-slot="3"] { left: calc(50% + #{$seat-r-x}); top: calc(50% + #{$seat-r-y}); } &[data-slot="4"] { left: 50%; top: calc(50% + #{$seat-r}); } &[data-slot="5"] { left: calc(50% - #{$seat-r-x}); top: calc(50% + #{$seat-r-y}); } &[data-slot="6"] { left: calc(50% - #{$seat-r-x}); top: calc(50% - #{$seat-r-y}); } .seat-portrait { width: 36px; height: 36px; border-radius: 50%; border: 2px solid rgba(var(--terUser), 1); display: flex; align-items: center; justify-content: center; font-size: 0.75rem; opacity: 0.6; } .seat-label { font-size: 0.65rem; opacity: 0.5; max-width: 80px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } // Arc of mini cards — visible only on the currently active seat .seat-card-arc { display: none; position: absolute; width: 18px; height: 26px; border-radius: 2px; border: 1px solid rgba(var(--terUser), 0.7); background: rgba(var(--quaUser), 0.9); // Three fanned cards stacked behind the portrait &::before, &::after { content: ""; position: absolute; inset: 0; border-radius: inherit; border: inherit; background: inherit; } &::before { transform: rotate(-18deg) translate(-4px, 2px); } &::after { transform: rotate( 18deg) translate( 4px, 2px); } } &.active .seat-portrait { opacity: 1; border-color: rgba(var(--secUser), 1); box-shadow: 0 0 0.5rem rgba(var(--ninUser), 0.5); } &.active .seat-card-arc { display: block; transform: translateY(-28px); // float above the portrait } } // ─── Card stack ──────────────────────────────────────────────────────────── .card-stack { width: 60px; height: 90px; display: flex; align-items: center; justify-content: center; border-radius: 6px; border: 1px solid rgba(var(--secUser), 1); background: rgba(var(--terUser), 1); cursor: default; transition: box-shadow 0.2s ease; &[data-state="eligible"] { cursor: pointer; border-color: rgba(var(--terUser), 1); box-shadow: 0 0 0.6rem rgba(var(--ninUser), 0.6), 0 0 1.6rem rgba(var(--secUser), 0.25); } &[data-state="ineligible"] { opacity: 0.4; cursor: not-allowed; } } // ─── Role select modal ───────────────────────────────────────────────────── .role-select-backdrop { position: fixed; inset: 0; z-index: 100; display: flex; justify-content: center; align-items: center; background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(4px); cursor: pointer; } #id_role_select { display: flex; gap: 1rem; pointer-events: none; @media (max-width: 600px) { display: grid; grid-template-columns: repeat(3, 80px); grid-template-rows: repeat(2, 120px); gap: 0.75rem; } } // ─── Card component ──────────────────────────────────────────────────────── $card-w: 80px; $card-h: 120px; .card { width: $card-w; height: $card-h; border-radius: 6px; cursor: pointer; pointer-events: auto; position: relative; perspective: 600px; .card-back, .card-front { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; border-radius: inherit; border: 2px solid rgba(var(--terUser), 1); background: rgba(var(--quiUser), 1); backface-visibility: hidden; -webkit-backface-visibility: hidden; transition: transform 0.35s ease; } .card-back { transform: rotateY(0deg); font-size: 1.5rem; color: rgba(var(--quaUser), 1); background: rgba(var(--quiUser), 1); border: 1px solid rgba(var(--terUser), 1); } .card-front { transform: rotateY(180deg); padding: 0.5rem; text-align: center; .card-role-name { font-size: 0.75rem; color: rgba(var(--quaUser), 1); text-transform: uppercase; letter-spacing: 0.05em; } } &.flipped, &.face-up { .card-back { transform: rotateY(-180deg); } .card-front { transform: rotateY(0deg); } } } // ─── Inventory role card hand ─────────────────────────────────────────────── // // Cards are stacked vertically: only a $strip-height peek of each card below // the first is visible by default, showing the role name at the top of the // card face. Hovering any card slides it right to pop it clear of the stack. $inv-card-w: 100px; $inv-card-h: 150px; $inv-strip: 30px; // visible height of each stacked card after the first #id_inv_role_card { display: flex; flex-direction: column; .card { width: $inv-card-w; height: $inv-card-h; position: relative; z-index: 1; flex-shrink: 0; transition: transform 0.2s ease; // Every card after the first overlaps the one above it & + .card { margin-top: -($inv-card-h - $inv-strip); } // Role name pinned to the top of the face so it reads in the strip .card-front { justify-content: flex-start; padding-top: 0.4rem; } // Pop the hovered card to the right, above siblings &:hover { transform: translateX(1.5rem); z-index: 10; } } } // ─── Partner indicator ───────────────────────────────────────────────────── .partner-indicator { margin-top: 0.5rem; font-size: 0.75rem; opacity: 0.6; text-align: center; } // Landscape mobile — aggressively scale down to fit short viewport @media (orientation: landscape) and (max-width: 1440px) { .gate-modal { padding: 0.6rem 1.25rem; .gate-header { h1 { font-size: 1rem; margin: 0 0 0.25rem; } .gate-status-wrap { font-size: 0.65em; margin-bottom: 0.35rem; } } .token-slot { min-width: 130px; .token-rails, button.token-rails { padding: 0.4rem 0.35rem; } .token-panel { padding: 0.3rem 0.5rem; .token-denomination { font-size: 1.1em; } } } .gate-slots { gap: 14px; .gate-slot { width: 40px; height: 40px; .slot-number { font-size: 0.6em; } } } .form-container { h3 { font-size: 0.85rem; margin: 0.25rem 0; } form { gap: 0.35rem; } .form-control-lg { --_pad-v: 0.4rem; font-size: 0.9rem; } } } } // ─── Significator deck (SIG_SELECT phase) ────────────────────────────────── // When the sig deck is present, switch room-page from centred to column layout .room-page:has(#id_sig_deck) { flex-direction: column; align-items: stretch; justify-content: flex-start; gap: 1rem; .room-shell { max-height: 50vh; } } #id_sig_deck { display: flex; flex-wrap: wrap; gap: 0.4rem; padding: 0.75rem; overflow-y: auto; align-content: flex-start; max-height: 45vh; scrollbar-width: thin; scrollbar-color: rgba(var(--terUser), 0.3) transparent; } .sig-card { width: 70px; height: 108px; border-radius: 0.4rem; background: rgba(var(--priUser), 1); border: 0.1rem solid rgba(var(--secUser), 0.4); display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 0.25rem; cursor: pointer; transition: transform 0.15s, border-color 0.15s; position: relative; &:hover { border-color: rgba(var(--secUser), 1); transform: translateY(-2px); box-shadow: 0 0 0.5rem rgba(var(--secUser), 0.3); } // Bottom corner is redundant at this size .fan-card-corner--br { display: none; } // Top corner — override game-kit's 1.5rem defaults with deeper nesting .fan-card-corner--tl { .fan-corner-rank { font-size: 0.65rem; padding: 0; } i { font-size: 0.55rem; } } // Face — deeper nesting to beat game-kit specificity .fan-card-face { padding: 0.25rem 0.2rem; gap: 0.1rem; .fan-card-name-group { font-size: 0.38rem; } .fan-card-name { font-size: 0.5rem; } .fan-card-arcana { font-size: 0.35rem; } } } // ─── Seat tray — see _tray.scss ─────────────────────────────────────────────