Spare/rarity filters, owned-only suggestions, and a Game night tab
Library filters: - "Only spare copies" quick filter (Library): cards with more copies owned than the decks' main boards use, the opposite of the ⚠ over-assignment warning. Rows show "N spare" while it's on. - Rarity toggles (C/U/R/M) in the shared criteria panel, so Library, Card Search and the set browser all get them. Any printing counts, since the library tracks cards rather than printings; within a set it's that set's printing. Arena-only printings don't count for a card that also exists on paper, so a digital re-rarity can't make a paper common read as rare. Suggestions: - "From my collection" toggle (remembered) narrows candidates to owned cards before scoring and paging, so "Suggest more" keeps finding them. - Each suggestion reports copies other decks already use, shown as "N spare" / "all in other decks". suggest_cards now takes a SuggestOptions struct instead of a bare `unusual` flag. Game tab: a life counter for 2-6 players with tap/hold ±1/±10, a running change bubble, poison and per-opponent commander damage (which also comes off life), out-of-the-game markers, dice and a coin, and a random first player. A seat's deck puts its commander's art behind the life total. On phones the board fills the screen with the far row turned to face across the table, and asks to keep the screen awake. The game is per-device scratch state in localStorage, not synced. Plain filter chips now show an accent background when active; before, "Unusual picks" only changed its text colour.
This commit is contained in:
380
src/styles.css
380
src/styles.css
@@ -1413,6 +1413,9 @@ li:focus-within .row-actions {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
.filter-chip.active {
|
||||
/* Plain on/off chips ("Unusual picks"); the category chips below swap in
|
||||
their own colour. */
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border-color: transparent;
|
||||
}
|
||||
@@ -1481,6 +1484,13 @@ li:focus-within .row-actions {
|
||||
opacity: 1;
|
||||
box-shadow: 0 0 0 2px var(--accent-hover);
|
||||
}
|
||||
/* Rarity toggles borrow the colour pips' on/off treatment, tinted like the
|
||||
expansion-symbol colours printed on the cards themselves. */
|
||||
/* Common's black symbol needs a ring to show at all on the dark panel. */
|
||||
.pip.rarity-pip.common { background: #2b2a2e; color: #e6e1d8; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); }
|
||||
.pip.rarity-pip.uncommon { background: linear-gradient(135deg, #c9d3dc, #7f8c98); color: #1d232a; }
|
||||
.pip.rarity-pip.rare { background: linear-gradient(135deg, #e6c66e, #a77f2a); color: #2b2210; }
|
||||
.pip.rarity-pip.mythic { background: linear-gradient(135deg, #f08a3c, #c2401f); color: #fff; }
|
||||
|
||||
/* ============================================================
|
||||
Tables
|
||||
@@ -2919,3 +2929,373 @@ tbody tr:hover {
|
||||
font-variant-numeric: tabular-nums;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Game night (life counter)
|
||||
============================================================ */
|
||||
|
||||
.game-setup {
|
||||
max-width: 720px;
|
||||
}
|
||||
.game-resume {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.game-setup-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
.game-seats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.game-seat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.game-seat-color {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex-shrink: 0;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
.game-seat-name,
|
||||
.game-seat-deck {
|
||||
flex: 1 1 120px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.game-board {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-height: 420px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.game-row {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
.game-player {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-lg);
|
||||
border: 2px solid var(--player-color);
|
||||
background-color: var(--bg-panel);
|
||||
background-color: color-mix(in srgb, var(--player-color) 28%, var(--bg-panel));
|
||||
background-size: cover;
|
||||
background-position: center 30%;
|
||||
color: var(--text-strong);
|
||||
/* Lets the life total size itself to the panel (cq units) — a 6-player
|
||||
phone layout has panels a third the size of a 2-player one. */
|
||||
container-type: size;
|
||||
}
|
||||
/* Darkens commander art enough for the numbers to read over it. */
|
||||
.game-player::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.55));
|
||||
pointer-events: none;
|
||||
}
|
||||
.game-player.rotated {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.game-player.first {
|
||||
box-shadow: 0 0 0 3px var(--warn), 0 0 24px rgba(224, 163, 74, 0.45);
|
||||
}
|
||||
/* Dims the panel rather than filtering it, which would also grey out the
|
||||
"out" reason and the first-player highlight that need to stay legible. */
|
||||
.game-player.out::before {
|
||||
background: rgba(12, 11, 10, 0.78);
|
||||
}
|
||||
.game-player.out .game-life {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.game-life-half {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
width: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
font-size: 28px;
|
||||
font-weight: 300;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
.game-life-half:hover {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
.game-life-half:active {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
.game-life-half.minus {
|
||||
left: 0;
|
||||
justify-content: flex-start;
|
||||
padding-left: 8%;
|
||||
}
|
||||
.game-life-half.plus {
|
||||
right: 0;
|
||||
justify-content: flex-end;
|
||||
padding-right: 8%;
|
||||
}
|
||||
|
||||
.game-player-head {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
text-shadow: 0 1px 6px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
.game-player-name {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.game-player-deck {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 11px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.game-life {
|
||||
position: relative;
|
||||
font-size: 64px;
|
||||
font-size: min(34cqw, 44cqh);
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-shadow: 0 2px 12px rgba(0, 0, 0, 0.6);
|
||||
pointer-events: none;
|
||||
}
|
||||
.game-delta {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 100%;
|
||||
margin-left: 6px;
|
||||
padding: 1px 7px;
|
||||
border-radius: var(--radius-pill);
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
font-size: 16px;
|
||||
font-size: max(13px, min(8cqw, 11cqh));
|
||||
font-weight: 700;
|
||||
text-shadow: none;
|
||||
}
|
||||
.game-delta.up {
|
||||
color: #86e0a4;
|
||||
}
|
||||
.game-delta.down {
|
||||
color: #ff9a86;
|
||||
}
|
||||
.game-out-label {
|
||||
position: relative;
|
||||
margin-top: 6px;
|
||||
padding: 2px 10px;
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--danger);
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
pointer-events: none;
|
||||
}
|
||||
.game-player-foot {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
/* Taps between the badges still reach the −/+ halves underneath. */
|
||||
pointer-events: none;
|
||||
}
|
||||
.game-player-foot > button {
|
||||
pointer-events: auto;
|
||||
}
|
||||
.game-badge {
|
||||
padding: 3px 9px;
|
||||
border-radius: var(--radius-pill);
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: var(--text-strong);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.game-badge-btn:hover {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
.game-badge.first {
|
||||
border-color: transparent;
|
||||
background: var(--warn);
|
||||
color: #2b1d05;
|
||||
}
|
||||
|
||||
.game-strip {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.game-hub {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--border-soft);
|
||||
background: var(--bg-elev);
|
||||
color: var(--text-strong);
|
||||
}
|
||||
.game-hub:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.game-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.game-menu button {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.game-counters {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.game-counter-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
.game-counter-heading {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.game-dot {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.game-stepper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.game-stepper button {
|
||||
min-width: 34px;
|
||||
justify-content: center;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.game-stepper-value {
|
||||
min-width: 34px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.game-dice-buttons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 6px;
|
||||
}
|
||||
.game-dice-buttons button {
|
||||
justify-content: center;
|
||||
}
|
||||
.game-dice-result {
|
||||
min-height: 96px;
|
||||
margin: 12px 0 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.game-dice-latest {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
animation: game-dice-pop 0.22s ease-out;
|
||||
}
|
||||
.game-dice-value {
|
||||
font-size: 52px;
|
||||
font-weight: 800;
|
||||
line-height: 1.05;
|
||||
color: var(--text-strong);
|
||||
}
|
||||
.game-dice-history {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
@keyframes game-dice-pop {
|
||||
from {
|
||||
transform: scale(0.6);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* On a phone the table takes the whole screen — topbar and bottom nav
|
||||
included; the hub menu's "Leave the table" (or back) is the way out. */
|
||||
@media (max-width: 720px) {
|
||||
.game-board {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 300;
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
padding: calc(env(safe-area-inset-top) + 6px) 6px calc(env(safe-area-inset-bottom) + 6px);
|
||||
background: var(--bg-app);
|
||||
}
|
||||
.game-life-half {
|
||||
font-size: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user