mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-09-21 20:40:47 +02:00
web(ui): move theme controls to sidebar bottom, tighten Test Hand fan arc, set desktop to 280x392; mobile banner cleanup; bump version to 2.2.10 and update compose APP_VERSION; cache-bust CSS
This commit is contained in:
parent
07a92eb47f
commit
f28f8e6b4f
9 changed files with 188 additions and 81 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -27,6 +27,18 @@ This format follows Keep a Changelog principles and aims for Semantic Versioning
|
|||
- (placeholder) – no current unreleased land alternatives bugs logged
|
||||
- Step 5 card grid scroll flicker at bottom: added overscroll containment and skip virtualization for small (<80 items) grids to prevent upward jump when reaching end
|
||||
|
||||
## [2.2.10] - 2025-09-11
|
||||
|
||||
### Changed
|
||||
- Web UI: Test Hand uses a default fanned layout on desktop with tightened arc and 40% overlap; outer cards sit lower for a full-arc look
|
||||
- Desktop Test Hand card size set to 280×392; responsive sizes refined at common breakpoints
|
||||
- Theme controls moved from the top banner to the bottom of the left sidebar; sidebar made a flex column with the theme block anchored at the bottom
|
||||
- Mobile banner simplified to show only Menu, title; spacing and gaps tuned to prevent overflow and wrapping
|
||||
|
||||
### Fixed
|
||||
- Prevented mobile banner overflow by hiding non-essential items and relocating theme controls
|
||||
- Ensured desktop sizing wins over previous inline styles by using global CSS overrides; cards no longer shrink due to flex
|
||||
|
||||
## [2.2.9] - 2025-09-10
|
||||
|
||||
### Added
|
||||
|
|
|
@ -52,6 +52,3 @@
|
|||
- Deterministic toggle for land alternative randomization (e.g., `LAND_ALTS_DETERMINISTIC=1`).
|
||||
- Unit tests focusing on edge-case mono-color filtering and theme weighting bounds.
|
||||
- Potential adaptive virtualization row-height measurement per column for further smoothness (currently fixed estimate works acceptably).
|
||||
|
||||
---
|
||||
Generated template ready for tagging release `${VERSION}` (update actual version number in CI/CD pipeline or tagging script).
|
||||
|
|
|
@ -83,7 +83,12 @@ body {
|
|||
/* Top banner */
|
||||
.top-banner{ position:sticky; top:0; z-index:10; background: var(--surface-banner); color: var(--surface-banner-text); border-bottom:1px solid var(--border); }
|
||||
.top-banner{ min-height: var(--banner-h); }
|
||||
.top-banner .top-inner{ margin:0; padding:.5rem 0; display:grid; grid-template-columns: var(--sidebar-w) 1fr; align-items:center; }
|
||||
.top-banner .top-inner{ margin:0; padding:.5rem 0; display:grid; grid-template-columns: var(--sidebar-w) 1fr; align-items:center; width:100%; box-sizing:border-box; }
|
||||
.top-banner .top-inner > div{ min-width:0; }
|
||||
@media (max-width: 1100px){
|
||||
.top-banner .top-inner{ grid-auto-rows:auto; }
|
||||
.top-banner .top-inner select{ max-width:140px; }
|
||||
}
|
||||
.top-banner h1{ font-size: 1.1rem; margin:0; padding-left: 1rem; }
|
||||
.banner-status{ color: var(--muted); font-size:.9rem; text-align:left; padding-left: 1.5rem; padding-right: 1.5rem; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%; min-height:1.2em; }
|
||||
.banner-status.busy{ color:#fbbf24; }
|
||||
|
@ -105,6 +110,8 @@ body {
|
|||
width: var(--sidebar-w);
|
||||
z-index: 9; /* below the banner (z=10) */
|
||||
box-shadow: 2px 0 10px rgba(0,0,0,.18);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.content{ padding: 1.25rem 1.5rem; grid-column: 2; min-width: 0; }
|
||||
|
||||
|
@ -120,13 +127,22 @@ body.nav-collapsed .top-banner .top-inner{ padding-left: .5rem; padding-right: .
|
|||
/* Mobile tweaks */
|
||||
@media (max-width: 900px){
|
||||
:root{ --sidebar-w: 240px; }
|
||||
.top-banner .top-inner{ grid-template-columns: 1fr; row-gap: .35rem; padding:.4rem .5rem; }
|
||||
.top-banner .top-inner{ grid-template-columns: 1fr; row-gap: .35rem; padding:.4rem 15px !important; }
|
||||
.banner-status{ padding-left: .5rem; }
|
||||
.layout{ grid-template-columns: 0 1fr; }
|
||||
.sidebar{ transform: translateX(-100%); visibility: hidden; }
|
||||
body:not(.nav-collapsed) .layout{ grid-template-columns: var(--sidebar-w) 1fr; }
|
||||
body:not(.nav-collapsed) .sidebar{ transform: translateX(0); visibility: visible; }
|
||||
.content{ padding: .9rem .6rem; max-width: 100vw; box-sizing: border-box; overflow-x: hidden; }
|
||||
.top-banner{ box-shadow:0 2px 6px rgba(0,0,0,.4); }
|
||||
/* Spacing tweaks: tighter left, larger gaps between visible items */
|
||||
.top-banner .top-inner > div{ gap: 25px !important; }
|
||||
.top-banner .top-inner > div:first-child{ padding-left: 0 !important; }
|
||||
/* Mobile: show only Menu, Title, and Theme selector */
|
||||
#btn-open-permalink{ display:none !important; }
|
||||
#banner-status{ display:none !important; }
|
||||
#health-dot{ display:none !important; }
|
||||
.top-banner #theme-reset{ display:none !important; }
|
||||
}
|
||||
|
||||
/* Additional mobile spacing for bottom floating controls */
|
||||
|
@ -149,6 +165,14 @@ body.nav-collapsed .top-banner .top-inner{ padding-left: .5rem; padding-right: .
|
|||
.nav a{ color: var(--surface-sidebar-text); text-decoration:none; padding:.4rem .5rem; border-radius:6px; border:1px solid transparent; }
|
||||
.nav a:hover{ background: color-mix(in srgb, var(--surface-sidebar) 85%, var(--surface-sidebar-text) 15%); border-color: var(--border); }
|
||||
|
||||
/* Sidebar theme controls anchored at bottom */
|
||||
.sidebar .nav { flex: 1 1 auto; }
|
||||
.sidebar-theme { margin-top: auto; padding-top: .75rem; border-top: 1px solid var(--border); }
|
||||
.sidebar-theme-label { display:block; color: var(--surface-sidebar-text); font-size: 12px; opacity:.8; margin: 0 0 .35rem .1rem; }
|
||||
.sidebar-theme-row { display:flex; align-items:center; gap:.5rem; }
|
||||
.sidebar-theme-row select { background: var(--panel); color: var(--text); border:1px solid var(--border); border-radius:6px; padding:.3rem .4rem; }
|
||||
.sidebar-theme-row .btn-ghost { background: transparent; color: var(--surface-sidebar-text); border:1px solid var(--border); }
|
||||
|
||||
/* Simple two-column layout for inspect panel */
|
||||
.two-col { display: grid; grid-template-columns: 1fr 320px; gap: 1rem; align-items: start; }
|
||||
.two-col .grow { min-width: 0; }
|
||||
|
@ -392,63 +416,50 @@ img.lqip.loaded { filter: blur(0); opacity: 1; }
|
|||
width: 100% !important;
|
||||
max-width: 100vw !important;
|
||||
}
|
||||
|
||||
|
||||
/* Test hand responsive adjustments */
|
||||
#test-hand{ --card-w: 170px !important; --card-h: 238px !important; --overlap: .5 !important; }
|
||||
|
||||
/* Modal & form layout fixes (original block retained inside media query) */
|
||||
/* Fix modal layout on mobile */
|
||||
.modal {
|
||||
padding: 10px !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 100% !important;
|
||||
max-width: calc(100vw - 20px) !important;
|
||||
box-sizing: border-box !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
/* Force single column for include/exclude grid */
|
||||
.include-exclude-grid {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 1rem !important;
|
||||
}
|
||||
|
||||
.include-exclude-grid { display: flex !important; flex-direction: column !important; gap: 1rem !important; }
|
||||
/* Fix basics grid */
|
||||
.basics-grid {
|
||||
grid-template-columns: 1fr !important;
|
||||
gap: 1rem !important;
|
||||
}
|
||||
|
||||
.basics-grid { grid-template-columns: 1fr !important; gap: 1rem !important; }
|
||||
/* Ensure all inputs and textareas fit properly */
|
||||
.modal input,
|
||||
.modal textarea,
|
||||
.modal select {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
min-width: 0 !important;
|
||||
}
|
||||
|
||||
.modal select { width: 100% !important; max-width: 100% !important; box-sizing: border-box !important; min-width: 0 !important; }
|
||||
/* Fix chips containers */
|
||||
.modal [id$="_chips_container"] {
|
||||
max-width: 100% !important;
|
||||
overflow-x: hidden !important;
|
||||
word-wrap: break-word !important;
|
||||
}
|
||||
|
||||
.modal [id$="_chips_container"] { max-width: 100% !important; overflow-x: hidden !important; word-wrap: break-word !important; }
|
||||
/* Ensure fieldsets don't overflow */
|
||||
.modal fieldset {
|
||||
max-width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
.modal fieldset { max-width: 100% !important; box-sizing: border-box !important; overflow-x: hidden !important; }
|
||||
/* Fix any inline styles that might cause overflow */
|
||||
.modal fieldset > div,
|
||||
.modal fieldset > div > div {
|
||||
max-width: 100% !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
.modal fieldset > div > div { max-width: 100% !important; overflow-x: hidden !important; }
|
||||
}
|
||||
|
||||
@media (max-width: 640px){
|
||||
#test-hand{ --card-w: 150px !important; --card-h: 210px !important; }
|
||||
/* Generic stack shrink */
|
||||
.stack-wrap:not(#test-hand){ --card-w: 150px; --card-h: 210px; }
|
||||
}
|
||||
|
||||
@media (max-width: 560px){
|
||||
#test-hand{ --card-w: 140px !important; --card-h: 196px !important; padding-bottom:.75rem; }
|
||||
#test-hand .stack-grid{ display:flex !important; gap:.5rem; grid-template-columns:none !important; overflow-x:auto; padding-bottom:.25rem; }
|
||||
#test-hand .stack-card{ flex:0 0 auto; }
|
||||
.stack-wrap:not(#test-hand){ --card-w: 140px; --card-h: 196px; }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
|
@ -508,3 +519,8 @@ img.lqip.loaded { filter: blur(0); opacity: 1; }
|
|||
display: none !important; /* Hide separators on mobile */
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop sizing for Test Hand */
|
||||
@media (min-width: 900px) {
|
||||
#test-hand { --card-w: 280px !important; --card-h: 392px !important; }
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
}catch(_){ }
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="/static/styles.css?v=20250902-3" />
|
||||
<link rel="stylesheet" href="/static/styles.css?v=20250911-1" />
|
||||
<!-- Performance hints -->
|
||||
<link rel="preconnect" href="https://api.scryfall.com" crossorigin>
|
||||
<link rel="dns-prefetch" href="https://api.scryfall.com">
|
||||
|
@ -54,23 +54,9 @@
|
|||
<div style="display:flex; align-items:center; gap:.5rem">
|
||||
<span id="health-dot" class="health-dot" title="Health"></span>
|
||||
<div id="banner-status" class="banner-status">{% block banner_subtitle %}{% endblock %}</div>
|
||||
<button type="button" class="btn" title="Open a saved permalink"
|
||||
<button type="button" id="btn-open-permalink" class="btn" title="Open a saved permalink"
|
||||
onclick="(function(){try{var token = prompt('Paste a /build/from?state=... URL or token:'); if(!token) return; var m = token.match(/state=([^&]+)/); var t = m? m[1] : token.trim(); if(!t) return; window.location.href = '/build/from?state=' + encodeURIComponent(t); }catch(_){}})()">Open Permalink…</button>
|
||||
{% if enable_themes %}
|
||||
<label style="margin:0 .5rem; align-items:flex-start; margin-left:auto">
|
||||
<span class="muted" style="font-size:11px">Theme</span>
|
||||
<select id="theme-select" aria-label="Theme selector">
|
||||
<option value="system">System</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="high-contrast">High contrast</option>
|
||||
<option value="cb-friendly">Color-blind</option>
|
||||
</select>
|
||||
</label>
|
||||
<button type="button" id="theme-reset" class="btn" title="Reset theme preference" style="background: transparent; color: var(--surface-banner-text); border:1px solid var(--border);">
|
||||
Reset
|
||||
</button>
|
||||
{% endif %}
|
||||
{# Theme controls moved to sidebar #}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
@ -95,6 +81,21 @@
|
|||
{% if show_diagnostics %}<a href="/diagnostics">Diagnostics</a>{% endif %}
|
||||
{% if show_logs %}<a href="/logs">Logs</a>{% endif %}
|
||||
</nav>
|
||||
{% if enable_themes %}
|
||||
<div class="sidebar-theme" role="group" aria-label="Theme">
|
||||
<label class="sidebar-theme-label" for="theme-select">Theme</label>
|
||||
<div class="sidebar-theme-row">
|
||||
<select id="theme-select" aria-label="Theme selector">
|
||||
<option value="system">System</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="high-contrast">High contrast</option>
|
||||
<option value="cb-friendly">Color-blind</option>
|
||||
</select>
|
||||
<button type="button" id="theme-reset" class="btn btn-ghost" title="Reset theme preference">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</aside>
|
||||
<main class="content" data-error-surface>
|
||||
{% block content %}{% endblock %}
|
||||
|
|
|
@ -8,11 +8,13 @@
|
|||
<div class="muted">Commander: <strong data-card-name="{{ commander }}">{{ commander }}</strong>{% if tags and tags|length %} • Themes: {{ tags|join(', ') }}{% endif %}</div>
|
||||
<div class="muted">This view mirrors the end-of-build summary. Use the buttons to download the CSV/TXT exports.</div>
|
||||
|
||||
<div style="display:grid; grid-template-columns: 360px 1fr; gap: 1rem; align-items:start; margin-top: .75rem;">
|
||||
<div>
|
||||
<div class="two-col two-col-left-rail" style="margin-top:.75rem;">
|
||||
<aside class="card-preview">
|
||||
{% if commander %}
|
||||
<img src="https://api.scryfall.com/cards/named?fuzzy={{ commander|urlencode }}&format=image&version=normal" alt="{{ commander }}" data-card-name="{{ commander }}" style="width:320px; height:auto; border-radius:8px; border:1px solid var(--border); box-shadow: 0 6px 18px rgba(0,0,0,.55);" />
|
||||
<div class="muted" style="margin-top:.25rem;">Commander: <span data-card-name="{{ commander }}">{{ commander }}</span></div>
|
||||
<a href="https://scryfall.com/search?q={{ commander|urlencode }}" target="_blank" rel="noopener">
|
||||
<img src="https://api.scryfall.com/cards/named?fuzzy={{ commander|urlencode }}&format=image&version=normal" alt="{{ commander }} card image" data-card-name="{{ commander }}" width="320" />
|
||||
</a>
|
||||
<div class="muted" style="margin-top:.25rem;">Commander: <span data-card-name="{{ commander }}">{{ commander }}</span></div>
|
||||
{% endif %}
|
||||
<div style="margin-top:.75rem; display:flex; gap:.35rem; flex-wrap:wrap;">
|
||||
{% if csv_path %}
|
||||
|
@ -27,13 +29,13 @@
|
|||
<button type="submit">Download TXT</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<a href="/decks/compare?A={{ name|urlencode }}" class="btn" role="button" title="Compare this deck with another">Compare…</a>
|
||||
<a href="/decks/compare?A={{ name|urlencode }}" class="btn" role="button" title="Compare this deck with another">Compare…</a>
|
||||
<form method="get" action="/decks" style="display:inline; margin:0;">
|
||||
<button type="submit">Back to Finished Decks</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
</aside>
|
||||
<div class="grow">
|
||||
{% if summary %}
|
||||
{% if owned_set %}
|
||||
{% set ns = namespace(owned=0, total=0) %}
|
||||
|
|
|
@ -338,12 +338,13 @@
|
|||
|
||||
<!-- Test Hand (7 random cards; duplicates allowed only for basic lands) -->
|
||||
<section style="margin-top:1rem;">
|
||||
<h5>Test Hand</h5>
|
||||
<div style="display:flex; gap:.5rem; align-items:center; margin-bottom:.5rem;">
|
||||
<h5 style="margin:0 0 .35rem 0; display:flex; align-items:center; gap:.75rem; flex-wrap:wrap;">Test Hand
|
||||
<span class="muted" style="font-size:12px; font-weight:400;">Draw 7 at random (no repeats except for basic lands).</span>
|
||||
</h5>
|
||||
<div style="display:flex; gap:.6rem; align-items:center; flex-wrap:wrap; margin-bottom:.5rem;">
|
||||
<button type="button" id="btn-new-hand">New Hand</button>
|
||||
<span class="muted" style="font-size:12px;">Draw 7 at random (no repeats except for basic lands).</span>
|
||||
</div>
|
||||
<div class="stack-wrap" id="test-hand" style="--card-w: 240px; --card-h: 336px; --overlap: .55; --cols: 7;">
|
||||
<div class="stack-wrap hand-row-overlap" id="test-hand">
|
||||
<div class="stack-grid" id="test-hand-grid"></div>
|
||||
</div>
|
||||
<script>
|
||||
|
@ -415,13 +416,30 @@
|
|||
var grid = document.getElementById('test-hand-grid');
|
||||
if (!grid) return;
|
||||
grid.innerHTML = '';
|
||||
hand.forEach(function(name){
|
||||
var host = document.getElementById('test-hand');
|
||||
if (host){ host.style.setProperty('--mid', (hand.length ? (hand.length - 1)/2 : 0)); host.style.setProperty('--count', hand.length); }
|
||||
hand.forEach(function(name, idx){
|
||||
if (!name) return;
|
||||
var div = document.createElement('div');
|
||||
div.className = 'stack-card';
|
||||
if (GC_SET && GC_SET.has(name)) {
|
||||
div.className += ' game-changer';
|
||||
}
|
||||
div.style.setProperty('--i', idx);
|
||||
var mid = (hand.length - 1) / 2;
|
||||
var diff = Math.abs(idx - mid);
|
||||
var peakRaise = 22; // px raise at center (accentuate arc)
|
||||
var dropPer = 5; // linear component per distance step
|
||||
// Strengthen curve so the very outer cards sit lower
|
||||
var outerExtra = 24; // quadratic downward px strongest at edges
|
||||
var edgeBias = 8; // cubic bias for far edges
|
||||
var norm = (mid > 0 ? diff / mid : 0); // 0..1
|
||||
var curve = norm * norm * outerExtra; // quadratic easing
|
||||
var curve3 = norm * norm * norm * edgeBias; // cubic accentuation
|
||||
var y = (diff * dropPer) + curve + curve3 - peakRaise; // center negative (raised), edges positive (lower)
|
||||
// Minor smoothing so second-from-edge isn't too low
|
||||
if (mid >= 2 && Math.abs(diff - (mid - 1)) < 0.001) { y += 2; }
|
||||
div.style.setProperty('--ty', y + 'px');
|
||||
div.innerHTML = (
|
||||
'<img src="https://api.scryfall.com/cards/named?fuzzy=' + encodeURIComponent(name) + '&format=image&version=normal" alt="' + name + '" data-card-name="' + name + '" />'
|
||||
);
|
||||
|
@ -431,9 +449,73 @@
|
|||
function newHand(){ var deck = collectDeck(); render(drawHand(deck)); }
|
||||
var btn = document.getElementById('btn-new-hand');
|
||||
if (btn) btn.addEventListener('click', newHand);
|
||||
// Fan effect — desktop default (>=900px, hover-capable pointer)
|
||||
var handEl = document.getElementById('test-hand');
|
||||
(function(){
|
||||
if(!handEl) return;
|
||||
var onEnter = function(){ handEl.classList.add('fan'); };
|
||||
var onLeave = function(){ handEl.classList.remove('fan'); };
|
||||
var mq = window.matchMedia('(any-hover: hover) and (pointer: fine) and (min-width: 900px)');
|
||||
function attach(){ handEl.addEventListener('mouseenter', onEnter); handEl.addEventListener('mouseleave', onLeave); }
|
||||
function detach(){ handEl.removeEventListener('mouseenter', onEnter); handEl.removeEventListener('mouseleave', onLeave); }
|
||||
// Desktop: fan is default; Mobile/tablet: no fan
|
||||
function apply(){
|
||||
if (mq.matches) {
|
||||
detach();
|
||||
handEl.classList.add('fan');
|
||||
} else {
|
||||
detach();
|
||||
handEl.classList.remove('fan');
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (typeof mq.addEventListener === 'function') mq.addEventListener('change', apply);
|
||||
else if (typeof mq.addListener === 'function') mq.addListener(apply);
|
||||
} catch(_) {}
|
||||
apply();
|
||||
})();
|
||||
newHand();
|
||||
})();
|
||||
</script>
|
||||
<style>
|
||||
/* Base overlapping hand: 160px cards (same as deck thumbnails) */
|
||||
#test-hand.hand-row-overlap{ padding-bottom:.9rem; --fan-gap:28px; --card-w:160px; --card-h:224px; }
|
||||
#test-hand.hand-row-overlap .stack-grid{ display:flex !important; gap:0; overflow-x:auto; scrollbar-width:thin; }
|
||||
#test-hand.hand-row-overlap .stack-card{ width:var(--card-w); height:var(--card-h); transition: transform .25s ease, margin-left .25s ease, width .25s ease, height .25s ease; flex: 0 0 auto; }
|
||||
/* Dynamic overlap: show ~30% of next card */
|
||||
#test-hand.hand-row-overlap .stack-card + .stack-card{ margin-left: calc(var(--card-w) * -0.7); }
|
||||
#test-hand.hand-row-overlap .stack-card img{ width:var(--card-w); height:var(--card-h); display:block; }
|
||||
#test-hand.hand-row-overlap .stack-card:hover{ z-index:999; transform:translateY(-4px); }
|
||||
/* Desktop sizing for Test Hand */
|
||||
@media (min-width:900px){
|
||||
#test-hand.hand-row-overlap{ --card-w:280px; --card-h:392px; --fan-gap:40px; }
|
||||
#test-hand.hand-row-overlap .stack-card + .stack-card{ margin-left: calc(var(--card-w) * -0.7); }
|
||||
#test-hand.hand-row-overlap.fan{ --card-w:280px; --card-h:392px; }
|
||||
}
|
||||
/* Hover fan-out: spread cards horizontally, enlarge if not already large */
|
||||
#test-hand.hand-row-overlap.fan{ --fan-overlap:0.40; --fan-gap:0px; }
|
||||
#test-hand.hand-row-overlap.fan .stack-card + .stack-card{ margin-left:0; }
|
||||
#test-hand.hand-row-overlap.fan .stack-grid{ justify-content:center; overflow:visible; padding-left:0; }
|
||||
/* Fan transform now applies a 40% overlap (visible width ~60%) while keeping center aligned */
|
||||
#test-hand.hand-row-overlap.fan .stack-card{ position:relative; transform: translateX(calc((var(--i) - var(--mid)) * (var(--fan-gap) - (var(--card-w) * var(--fan-overlap))))) translateY(var(--ty,0px)) rotate(calc((var(--i) - var(--mid)) * 4deg)); }
|
||||
/* Responsive adjustments */
|
||||
@media (max-width:900px){
|
||||
#test-hand.hand-row-overlap.fan{ --card-w:240px; --card-h:336px; --fan-overlap:0.40; --fan-gap:0px; }
|
||||
}
|
||||
@media (max-width:640px){
|
||||
#test-hand.hand-row-overlap{ --card-w:150px; --card-h:210px; }
|
||||
#test-hand.hand-row-overlap.fan{ --card-w:200px; --card-h:280px; --fan-overlap:0.40; --fan-gap:0px; }
|
||||
}
|
||||
@media (min-width:640px) and (max-width:899px){
|
||||
#test-hand.hand-row-overlap{ --card-w:160px; --card-h:224px; }
|
||||
}
|
||||
@media (max-width:480px){
|
||||
#test-hand.hand-row-overlap{ --card-w:140px; --card-h:196px; }
|
||||
#test-hand.hand-row-overlap .stack-card + .stack-card{ margin-left: calc(var(--card-w) * -0.65); }
|
||||
#test-hand.hand-row-overlap.fan{ --card-w:180px; --card-h:252px; --fan-overlap:0.40; --fan-gap:0px; }
|
||||
#test-hand.hand-row-overlap.fan .stack-grid{ padding-left:0; }
|
||||
}
|
||||
</style>
|
||||
</section>
|
||||
<style>
|
||||
.chart-tooltip { position: fixed; pointer-events: none; background: #0f1115; color: #e5e7eb; border: 1px solid var(--border); padding: .4rem .55rem; border-radius: 6px; font-size: 12px; line-height: 1.3; white-space: pre-line; z-index: 9999; display: none; box-shadow: 0 4px 16px rgba(0,0,0,.4); }
|
||||
|
|
|
@ -27,12 +27,9 @@ services:
|
|||
WEB_AUTO_SETUP: "1" # 1=auto-run setup/tagging when needed
|
||||
WEB_AUTO_REFRESH_DAYS: "7" # Refresh cards.csv if older than N days; 0=never
|
||||
WEB_TAG_PARALLEL: "1" # 1=parallelize tagging
|
||||
WEB_TAG_WORKERS: "4" # Worker count when parallel tagging
|
||||
WEB_TAG_WORKERS: "4" # Worker count when parallel tagging
|
||||
|
||||
# Compliance/exports
|
||||
WEB_AUTO_ENFORCE: "0" # 1=auto-apply bracket enforcement and re-export
|
||||
APP_VERSION: "v2.2.9" # Optional label shown in footer
|
||||
# WEB_CUSTOM_EXPORT_BASE: "" # Optional custom export basename
|
||||
|
||||
|
||||
# Misc land tuning (utility land selection – Step 7)
|
||||
# MISC_LAND_DEBUG: "1" # 1=write misc land debug CSVs (post-filter, candidates); off by default unless SHOW_DIAGNOSTICS=1
|
||||
|
|
|
@ -30,10 +30,10 @@ services:
|
|||
WEB_TAG_PARALLEL: "1"
|
||||
WEB_TAG_WORKERS: "4"
|
||||
|
||||
# Compliance/exports
|
||||
WEB_AUTO_ENFORCE: "0"
|
||||
APP_VERSION: "v2.2.9"
|
||||
# WEB_CUSTOM_EXPORT_BASE: ""
|
||||
# Compliance/exports
|
||||
WEB_AUTO_ENFORCE: "0"
|
||||
APP_VERSION: "v2.2.10"
|
||||
# WEB_CUSTOM_EXPORT_BASE: ""
|
||||
|
||||
# Misc land tuning (utility land selection – Step 7)
|
||||
# MISC_LAND_DEBUG: "1" # 1=write misc land debug CSVs (post-filter, candidates); off unless SHOW_DIAGNOSTICS=1
|
||||
|
|
|
@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|||
|
||||
[project]
|
||||
name = "mtg-deckbuilder"
|
||||
version = "2.2.9"
|
||||
version = "2.2.10"
|
||||
description = "A command-line tool for building and analyzing Magic: The Gathering decks"
|
||||
readme = "README.md"
|
||||
license = {file = "LICENSE"}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue