XL landscape polish: btn-primary sizing, tray from right, footer bg, layout fixes
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

- .btn-xl removed; .btn-primary absorbs 4rem sizing (same as PICK SIGS/PICK ROLES)
- Landscape navbar .btn-primary: 3rem → 4rem to match base; XL stays 4rem (consistent)
- _button-pad.scss XL: base .btn ×1.2 (2.4rem); .btn-xl block deleted
- _tray.scss XL (≥1800px): portrait-style tray (slides from right, z-95)
- tray.js: _isLandscape() returns false at ≥1800px; portrait code paths run throughout
- Footer sidebar: background-color added so opaque footer masks tray sliding behind it
- Copyright .footer-container: bottom → top in landscape sidebar
- #id_room_menu: right: 2.5rem override in _room.scss XL block (cascade fix)
- navbar-text XL: 0.65rem × 1.2 = 0.78rem
- All landscape media queries: max-width: 1440px cutoff removed (already done prior)
- btn-xl class stripped from all 5 templates; test_navbar.py assertion updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-04-06 03:02:37 -04:00
parent 6654785f25
commit 0bcc7567bb
13 changed files with 121 additions and 84 deletions

View File

@@ -40,6 +40,9 @@ var Tray = (function () {
function _isLandscape() { function _isLandscape() {
if (_landscapeOverride !== null) return _landscapeOverride; if (_landscapeOverride !== null) return _landscapeOverride;
// ≥1800px uses portrait-style tray (slides from right) to match the
// doubled sidebar widths and XL tray CSS reset.
if (window.innerWidth >= 1800) return false;
return window.innerWidth > window.innerHeight; return window.innerWidth > window.innerHeight;
} }
@@ -94,12 +97,10 @@ var Tray = (function () {
// Closed: tray hidden above viewport, handle visible at y=0. // Closed: tray hidden above viewport, handle visible at y=0.
_maxTop = -(gearBtnTop - handleH); _maxTop = -(gearBtnTop - handleH);
} else { } else {
// Portrait: slide on X axis. // Portrait (and XL landscape, which uses portrait-style tray):
// Wrap width is pinned to viewportW (JS) so its right edge only // Wrap width = viewportW so the closed right edge reaches the viewport boundary.
// reaches the viewport boundary when left = 0 (fully open). // The footer sidebar (z-100) is above the wrap (z-95) and has a solid background,
// This mirrors landscape: the open edge appears only at the last moment. // so the handle parks behind it and slides out from under it when opened.
// Open: left = 0 → wrap right = viewportW exactly.
// Closed: left = viewportW - handleW → tray fully off-screen right.
var handleW = _btn.offsetWidth || 48; var handleW = _btn.offsetWidth || 48;
if (_wrap) _wrap.style.width = window.innerWidth + 'px'; if (_wrap) _wrap.style.width = window.innerWidth + 'px';
_minLeft = 0; _minLeft = 0;
@@ -313,7 +314,7 @@ var Tray = (function () {
} }
} else { } else {
if (_tray) _tray.style.display = 'none'; if (_tray) _tray.style.display = 'none';
if (_wrap) { _wrap.style.top = ''; _wrap.style.height = ''; } if (_wrap) { _wrap.style.top = ''; _wrap.style.height = ''; _wrap.style.width = ''; }
_computeBounds(); _computeBounds();
_applyVerticalBounds(); _applyVerticalBounds();
_computeCellSize(); _computeCellSize();
@@ -342,8 +343,8 @@ var Tray = (function () {
if (_wrap) _wrap.style.top = _maxTop + 'px'; if (_wrap) _wrap.style.top = _maxTop + 'px';
_computeCellSize(); _computeCellSize();
} else { } else {
// Clear landscape's inline top so portrait CSS applies. // Clear landscape's inline top/height/width so portrait CSS applies.
if (_wrap) _wrap.style.top = ''; if (_wrap) { _wrap.style.top = ''; _wrap.style.width = ''; }
_applyVerticalBounds(); _applyVerticalBounds();
_computeCellSize(); // wrap has correct height after _applyVerticalBounds _computeCellSize(); // wrap has correct height after _applyVerticalBounds
_computeBounds(); _computeBounds();

View File

@@ -113,7 +113,7 @@ class NavbarByeTest(FunctionalTest):
class NavbarContGameTest(FunctionalTest): class NavbarContGameTest(FunctionalTest):
""" """
When the authenticated user has at least one room with a game event the When the authenticated user has at least one room with a game event the
CONT GAME btn-primary btn-xl appears in the navbar and navigates to that CONT GAME btn-primary appears in the navbar and navigates to that
room on confirmation. Its tooltip must also appear below the button. room on confirmation. Its tooltip must also appear below the button.
""" """
@@ -139,7 +139,6 @@ class NavbarContGameTest(FunctionalTest):
) )
btn = self.browser.find_element(By.ID, "id_cont_game") btn = self.browser.find_element(By.ID, "id_cont_game")
self.assertIn("btn-primary", btn.get_attribute("class")) self.assertIn("btn-primary", btn.get_attribute("class"))
self.assertIn("btn-xl", btn.get_attribute("class"))
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
# T6 — CONT GAME tooltip appears below btn # # T6 — CONT GAME tooltip appears below btn #

View File

@@ -120,7 +120,7 @@
.room-page, .room-page,
.billboard-page { .billboard-page {
> .gear-btn { > .gear-btn {
right: 0.5rem; right: 1rem;
bottom: 3.95rem; // same gap above kit btn as portrait; no page-specific overrides needed bottom: 3.95rem; // same gap above kit btn as portrait; no page-specific overrides needed
top: auto; top: auto;
} }
@@ -132,7 +132,7 @@
#id_wallet_applet_menu, #id_wallet_applet_menu,
#id_room_menu, #id_room_menu,
#id_billboard_applet_menu { #id_billboard_applet_menu {
right: 0.5rem; right: 1rem;
bottom: 6.6rem; bottom: 6.6rem;
top: auto; top: auto;
} }
@@ -153,7 +153,7 @@
#id_game_kit_menu, #id_game_kit_menu,
#id_wallet_applet_menu, #id_wallet_applet_menu,
#id_room_menu, #id_room_menu,
#id_billboard_applet_menu { right: 2rem; } #id_billboard_applet_menu { right: 2.5rem; }
} }
// ── Applet box visual shell (reusable outside the grid) ──── // ── Applet box visual shell (reusable outside the grid) ────

View File

@@ -194,7 +194,7 @@ body {
} }
@media (orientation: landscape) { @media (orientation: landscape) {
$sidebar-w: 4rem; $sidebar-w: 5rem;
// ── Sidebar layout: navbar ← left, footer → right ──────────────────────────── // ── Sidebar layout: navbar ← left, footer → right ────────────────────────────
body { body {
@@ -266,11 +266,10 @@ body {
} }
.btn-primary { .btn-primary {
width: 3rem; width: 4rem;
height: 3rem; height: 4rem;
font-size: 0.75rem; font-size: 0.875rem;
border-width: 0.125rem; border-width: 0.21rem;
// margin-left: 0.75rem;
} }
// Login form: offset from fixed sidebars in landscape // Login form: offset from fixed sidebars in landscape
@@ -328,6 +327,7 @@ body {
align-items: center; align-items: center;
border-top: none; border-top: none;
border-left: 0.1rem solid rgba(var(--secUser), 0.3); border-left: 0.1rem solid rgba(var(--secUser), 0.3);
background-color: rgba(var(--priUser), 1); // opaque: masks tray sliding behind it
padding: 1rem 0; padding: 1rem 0;
gap: 0; gap: 0;
z-index: 100; z-index: 100;
@@ -348,11 +348,11 @@ body {
.footer-container { .footer-container {
position: absolute; position: absolute;
bottom: 0.75rem; top: 0.75rem;
text-align: center; text-align: center;
font-size: 0.55rem; font-size: 1rem;
line-height: 1.4; line-height: 1.4;
color: rgba(var(--secUser), 0.5); color: rgba(var(--secUser), 1);
br { display: block; } br { display: block; }
} }
@@ -372,13 +372,8 @@ body {
} }
.navbar-brand h1 { font-size: 2.4rem; } .navbar-brand h1 { font-size: 2.4rem; }
.navbar-text { font-size: 1.3rem; } .navbar-text { font-size: 0.78rem; } // 0.65rem × 1.2
.btn-primary { width: 4rem; height: 4rem; font-size: 0.875rem; }
.btn-primary {
width: 5rem;
height: 5rem;
font-size: 1.125rem;
}
.input-group { .input-group {
left: $sidebar-xl; left: $sidebar-xl;
@@ -393,8 +388,8 @@ body {
// h2 page title: portrait-style — centred and full-size on a wide canvas // h2 page title: portrait-style — centred and full-size on a wide canvas
body .container .row .col-lg-6 h2 { body .container .row .col-lg-6 h2 {
font-size: 2rem; font-size: 4rem;
letter-spacing: 0.33em; letter-spacing: 1em;
text-align: center; text-align: center;
text-align-last: center; text-align-last: center;
} }
@@ -403,7 +398,7 @@ body {
width: $sidebar-xl; width: $sidebar-xl;
#id_footer_nav { #id_footer_nav {
gap: 4rem; gap: 8rem;
a { font-size: 3rem; } a { font-size: 3rem; }
} }
@@ -419,13 +414,6 @@ body {
.navbar-brand h1 { .navbar-brand h1 {
font-size: 1.2rem; font-size: 1.2rem;
} }
.btn-primary {
width: 3rem;
height: 3rem;
font-size: 0.75rem;
border-width: 0.125rem;
}
} }
.row .col-lg-6 h2 { .row .col-lg-6 h2 {
@@ -492,8 +480,8 @@ body {
br { display: none; } br { display: none; }
small { small {
font-size: 0.7rem; font-size: 0.75rem;
opacity: 0.6; opacity: 1;
} }
} }
} }

View File

@@ -25,6 +25,10 @@
} }
&.btn-primary { &.btn-primary {
width: 4rem;
height: 4rem;
font-size: 0.875rem;
border-width: 0.21rem;
color: rgba(var(--quaUser), 1); color: rgba(var(--quaUser), 1);
border-color: rgba(var(--quaUser), 1); border-color: rgba(var(--quaUser), 1);
background-color: rgba(var(--quiUser), 1); background-color: rgba(var(--quiUser), 1);
@@ -34,37 +38,6 @@
0.25rem 0.25rem 0.25rem rgba(var(--quiUser), 0.12) 0.25rem 0.25rem 0.25rem rgba(var(--quiUser), 0.12)
; ;
&:hover {
text-shadow:
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25),
0 0 1rem rgba(var(--quaUser), 1)
;
box-shadow:
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--quaUser), 0.12)
;
}
&:active {
border: 0.18rem solid rgba(var(--quaUser), 1);
text-shadow:
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25),
0 0 0.12rem rgba(var(--quaUser), 1)
;
box-shadow:
-0.1rem -0.1rem 0.12rem rgba(var(--quiUser), 0.25),
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--quaUser), 0.12)
;
}
}
&.btn-xl {
width: 4rem;
height: 4rem;
font-size: 0.875rem;
border-width: 0.21rem;
&:hover { &:hover {
text-shadow: text-shadow:
0.2rem 0.2rem 0.2rem rgba(0, 0, 0, 0.25), 0.2rem 0.2rem 0.2rem rgba(0, 0, 0, 0.25),
@@ -72,7 +45,7 @@
; ;
box-shadow: box-shadow:
0.24rem 0.24rem 0.5rem rgba(0, 0, 0, 0.25), 0.24rem 0.24rem 0.5rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--quaUser), 22) 0 0 0.5rem rgba(var(--quaUser), 0.22)
; ;
} }
@@ -87,7 +60,7 @@
-0.2rem -0.2rem 0.24rem rgba(0, 0, 0, 0.25), -0.2rem -0.2rem 0.24rem rgba(0, 0, 0, 0.25),
0 0 0.5rem rgba(var(--quaUser), 0.22) 0 0 0.5rem rgba(var(--quaUser), 0.22)
; ;
} }
} }
&.btn-abandon { &.btn-abandon {
@@ -300,6 +273,12 @@
} }
} }
@media (orientation: landscape) and (min-width: 1800px) {
width: 2.4rem; // 2rem × 1.2
height: 2.4rem;
font-size: 0.75rem; // 0.63rem × 1.2
}
&.btn-disabled { &.btn-disabled {
cursor: default !important; cursor: default !important;
font-size: 1.2rem; font-size: 1.2rem;

View File

@@ -4,7 +4,7 @@
right: 0.5rem; right: 0.5rem;
@media (orientation: landscape) { @media (orientation: landscape) {
right: 0.5rem; right: 1rem;
bottom: 0.5rem; bottom: 0.5rem;
top: auto; top: auto;
} }

View File

@@ -1101,6 +1101,10 @@ html:has(.sig-backdrop) {
@media (orientation: landscape) and (min-width: 1800px) { @media (orientation: landscape) and (min-width: 1800px) {
// Sig overlay: clear doubled navbar sidebar (8rem instead of 4rem) // Sig overlay: clear doubled navbar sidebar (8rem instead of 4rem)
.sig-overlay { padding-left: 8rem; } .sig-overlay { padding-left: 8rem; }
// Room menu: base right: 0.5rem (same-specificity ID rule) overrides _applets.scss
// XL block because _room.scss is imported later. Re-declare here to win the cascade.
#id_room_menu { right: 2.5rem; }
} }
// ─── Seat tray — see _tray.scss ───────────────────────────────────────────── // ─── Seat tray — see _tray.scss ─────────────────────────────────────────────

View File

@@ -329,10 +329,76 @@ $handle-r: 1rem;
} }
} }
// ── XL landscape: tray spans between doubled 8rem sidebars ───────────────── // ── XL landscape (≥1800px): portrait-style tray — slides in from right ─────
// Overrides all landscape rules above. JS also returns false from _isLandscape()
// at this width so portrait code paths run throughout.
@media (orientation: landscape) and (min-width: 1800px) { @media (orientation: landscape) and (min-width: 1800px) {
#id_tray_wrap { #id_tray_wrap {
left: 8rem; flex-direction: row;
right: 8rem; top: 0;
bottom: 0;
left: auto; // JS controls left; width set to innerWidth so wrap fills viewport
right: auto;
height: auto;
width: auto;
z-index: 95; // below footer/nav sidebars (z-100) — opaque footer masks the tray
transition: left 0.35s cubic-bezier(0.4, 0, 0.2, 1);
&.wobble { animation: tray-wobble 0.45s ease; }
&.snap { animation: tray-snap 0.30s ease; }
}
#id_tray_handle {
width: $handle-exposed; // 48px portrait strip
height: auto;
}
#id_tray_grip {
top: 50%;
bottom: auto;
left: calc(#{$handle-exposed} / 2 - 0.125rem);
transform: translateY(-50%);
width: $handle-rect-w; // 10000px extends leftward
height: $handle-rect-h; // 72px
}
#id_tray {
border-left: 2.5rem solid rgba(var(--quaUser), 1);
border-top: 2.5rem solid rgba(var(--quaUser), 1);
border-bottom: 2.5rem solid rgba(var(--quaUser), 1);
border-right: none;
margin-left: 0.5rem;
margin-bottom: 0;
width: auto;
max-width: none;
align-self: auto;
flex: 1;
height: auto;
overflow: hidden;
box-shadow:
-0.25rem 0 0.5rem rgba(0, 0, 0, 0.55),
inset 0 0 0 0.3rem rgba(var(--quiUser), 0.45),
inset 0.6rem 0 1.5rem -0.5rem rgba(0, 0, 0, 1),
inset 0.6rem 0 1.5rem -0.5rem rgba(var(--quiUser), 0.5),
inset 0 0.6rem 1.5rem -0.5rem rgba(0, 0, 0, 1),
inset 0 0.6rem 1.5rem -0.5rem rgba(var(--quiUser), 0.5),
inset 0 -0.6rem 1.5rem -0.5rem rgba(0, 0, 0, 1),
inset 0 -0.6rem 1.5rem -0.5rem rgba(var(--quiUser), 0.5)
;
}
#id_tray_grid {
grid-template-columns: none;
grid-template-rows: repeat(8, var(--tray-cell-size, 48px));
grid-auto-flow: column;
grid-auto-columns: var(--tray-cell-size, 48px);
grid-auto-rows: auto;
position: static;
}
.tray-cell {
border-top: none;
border-right: 2px dotted rgba(var(--priUser), 0.35);
border-bottom: 2px dotted rgba(var(--priUser), 0.35);
} }
} }

View File

@@ -41,7 +41,7 @@
</div> </div>
{% endif %} {% endif %}
<button type="submit" class="btn btn-primary btn-xl">Share</button> <button type="submit" class="btn btn-primary">Share</button>
</form> </form>
<small>Note shared with: <small>Note shared with:
{% for user in note.shared_with.all %} {% for user in note.shared_with.all %}

View File

@@ -53,7 +53,7 @@
{% if room.gate_status == 'OPEN' %} {% if room.gate_status == 'OPEN' %}
<form method="POST" action="{% url 'epic:pick_roles' room.id %}" style="display:contents"> <form method="POST" action="{% url 'epic:pick_roles' room.id %}" style="display:contents">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="launch-game-btn btn btn-primary btn-xl">PICK ROLES</button> <button type="submit" class="launch-game-btn btn btn-primary">PICK ROLES</button>
</form> </form>
{% endif %} {% endif %}
</div> </div>

View File

@@ -8,7 +8,7 @@
<form method="POST" action="{% url 'epic:confirm_token' room.id %}"> <form method="POST" action="{% url 'epic:confirm_token' room.id %}">
{% csrf_token %} {% csrf_token %}
{% if is_last_slot %} {% if is_last_slot %}
<button type="submit" class="btn btn-primary btn-xl">PICK ROLES</button> <button type="submit" class="btn btn-primary">PICK ROLES</button>
{% else %} {% else %}
<button type="submit" class="btn btn-confirm">OK</button> <button type="submit" class="btn btn-confirm">OK</button>
{% endif %} {% endif %}

View File

@@ -14,7 +14,7 @@
<div id="id_pick_sigs_wrap"{% if starter_roles|length < 6 %} style="display:none"{% endif %}> <div id="id_pick_sigs_wrap"{% if starter_roles|length < 6 %} style="display:none"{% endif %}>
<form method="POST" action="{% url 'epic:pick_sigs' room.id %}"> <form method="POST" action="{% url 'epic:pick_sigs' room.id %}">
{% csrf_token %} {% csrf_token %}
<button id="id_pick_sigs_btn" type="submit" class="btn btn-primary btn-xl">PICK<br>SIGS</button> <button id="id_pick_sigs_btn" type="submit" class="btn btn-primary">PICK<br>SIGS</button>
</form> </form>
</div> </div>
{% endif %} {% endif %}

View File

@@ -24,7 +24,7 @@
{% if navbar_recent_room_url %} {% if navbar_recent_room_url %}
<button <button
id="id_cont_game" id="id_cont_game"
class="btn btn-primary btn-xl" class="btn btn-primary"
type="button" type="button"
data-confirm="Continue game?" data-confirm="Continue game?"
data-href="{{ navbar_recent_room_url }}" data-href="{{ navbar_recent_room_url }}"