renamed the Popes/0-card trumps from Earthman deck (feat. new apps.epic migrations to reseed); fixes to card deck horizontal scroll speed, game_kit.html, to make scrolling feel more natural
This commit is contained in:
18
src/apps/epic/migrations/0018_alter_tarotcard_suit.py
Normal file
18
src/apps/epic/migrations/0018_alter_tarotcard_suit.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 6.0 on 2026-04-01 17:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('epic', '0017_tableseat_significator_fk'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tarotcard',
|
||||||
|
name='suit',
|
||||||
|
field=models.CharField(blank=True, choices=[('WANDS', 'Wands'), ('CUPS', 'Cups'), ('SWORDS', 'Swords'), ('PENTACLES', 'Pentacles')], max_length=10, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
"""
|
||||||
|
Data migration: rename The Schiz (card 0) and the five Pope cards (cards 1–5)
|
||||||
|
in the Earthman deck.
|
||||||
|
|
||||||
|
0: "The Schiz" → "The Nomad"
|
||||||
|
1: "Pope 1: Chancellor" → "Pope 1: The Schizo"
|
||||||
|
2: "Pope 2: President" → "Pope 2: The Despot"
|
||||||
|
3: "Pope 3: Tsar" → "Pope 3: The Capitalist"
|
||||||
|
4: "Pope 4: Chairman" → "Pope 4: The Fascist"
|
||||||
|
5: "Pope 5: Emperor" → "Pope 5: The War Machine"
|
||||||
|
"""
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
NEW_NAMES = {
|
||||||
|
0: ("The Nomad", "the-nomad"),
|
||||||
|
1: ("Pope 1: The Schizo", "pope-1-the-schizo"),
|
||||||
|
2: ("Pope 2: The Despot", "pope-2-the-despot"),
|
||||||
|
3: ("Pope 3: The Capitalist", "pope-3-the-capitalist"),
|
||||||
|
4: ("Pope 4: The Fascist", "pope-4-the-fascist"),
|
||||||
|
5: ("Pope 5: The War Machine","pope-5-the-war-machine"),
|
||||||
|
}
|
||||||
|
|
||||||
|
OLD_NAMES = {
|
||||||
|
0: ("The Schiz", "the-schiz"),
|
||||||
|
1: ("Pope 1: Chancellor", "pope-1-chancellor"),
|
||||||
|
2: ("Pope 2: President", "pope-2-president"),
|
||||||
|
3: ("Pope 3: Tsar", "pope-3-tsar"),
|
||||||
|
4: ("Pope 4: Chairman", "pope-4-chairman"),
|
||||||
|
5: ("Pope 5: Emperor", "pope-5-emperor"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def rename_forward(apps, schema_editor):
|
||||||
|
TarotCard = apps.get_model("epic", "TarotCard")
|
||||||
|
DeckVariant = apps.get_model("epic", "DeckVariant")
|
||||||
|
|
||||||
|
earthman = DeckVariant.objects.filter(slug="earthman").first()
|
||||||
|
if not earthman:
|
||||||
|
return
|
||||||
|
|
||||||
|
for number, (new_name, new_slug) in NEW_NAMES.items():
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MAJOR", number=number
|
||||||
|
).update(name=new_name, slug=new_slug)
|
||||||
|
|
||||||
|
|
||||||
|
def rename_reverse(apps, schema_editor):
|
||||||
|
TarotCard = apps.get_model("epic", "TarotCard")
|
||||||
|
DeckVariant = apps.get_model("epic", "DeckVariant")
|
||||||
|
|
||||||
|
earthman = DeckVariant.objects.filter(slug="earthman").first()
|
||||||
|
if not earthman:
|
||||||
|
return
|
||||||
|
|
||||||
|
for number, (old_name, old_slug) in OLD_NAMES.items():
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MAJOR", number=number
|
||||||
|
).update(name=old_name, slug=old_slug)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("epic", "0018_alter_tarotcard_suit"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(rename_forward, reverse_code=rename_reverse),
|
||||||
|
]
|
||||||
@@ -213,13 +213,11 @@ class TarotCard(models.Model):
|
|||||||
CUPS = "CUPS"
|
CUPS = "CUPS"
|
||||||
SWORDS = "SWORDS"
|
SWORDS = "SWORDS"
|
||||||
PENTACLES = "PENTACLES" # Fiorentine 4th suit
|
PENTACLES = "PENTACLES" # Fiorentine 4th suit
|
||||||
COINS = "COINS" # Earthman 4th suit (Ossum / Stone)
|
|
||||||
SUIT_CHOICES = [
|
SUIT_CHOICES = [
|
||||||
(WANDS, "Wands"),
|
(WANDS, "Wands"),
|
||||||
(CUPS, "Cups"),
|
(CUPS, "Cups"),
|
||||||
(SWORDS, "Swords"),
|
(SWORDS, "Swords"),
|
||||||
(PENTACLES, "Pentacles"),
|
(PENTACLES, "Pentacles"),
|
||||||
(COINS, "Coins"),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
deck_variant = models.ForeignKey(
|
deck_variant = models.ForeignKey(
|
||||||
@@ -282,7 +280,6 @@ class TarotCard(models.Model):
|
|||||||
self.WANDS: 'fa-wand-sparkles',
|
self.WANDS: 'fa-wand-sparkles',
|
||||||
self.CUPS: 'fa-trophy',
|
self.CUPS: 'fa-trophy',
|
||||||
self.SWORDS: 'fa-gun',
|
self.SWORDS: 'fa-gun',
|
||||||
self.COINS: 'fa-star',
|
|
||||||
self.PENTACLES: 'fa-star',
|
self.PENTACLES: 'fa-star',
|
||||||
}.get(self.suit, '')
|
}.get(self.suit, '')
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ function initGameKitPage() {
|
|||||||
fanContent.innerHTML = html;
|
fanContent.innerHTML = html;
|
||||||
cards = Array.from(fanContent.querySelectorAll('.fan-card'));
|
cards = Array.from(fanContent.querySelectorAll('.fan-card'));
|
||||||
if (currentIndex >= cards.length) currentIndex = 0;
|
if (currentIndex >= cards.length) currentIndex = 0;
|
||||||
|
cards.forEach(function(c) {
|
||||||
|
c.style.transition = 'transform 0.18s ease-out, opacity 0.18s ease-out';
|
||||||
|
});
|
||||||
updateFan();
|
updateFan();
|
||||||
dialog.showModal();
|
dialog.showModal();
|
||||||
});
|
});
|
||||||
@@ -84,6 +87,21 @@ function initGameKitPage() {
|
|||||||
updateFan();
|
updateFan();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step through multiple cards one at a time so intermediate cards are visible
|
||||||
|
var _navTimer = null;
|
||||||
|
function navigateAnimated(steps) {
|
||||||
|
if (!cards.length || steps === 0) return;
|
||||||
|
clearTimeout(_navTimer);
|
||||||
|
var sign = steps > 0 ? 1 : -1;
|
||||||
|
var remaining = Math.abs(steps);
|
||||||
|
function tick() {
|
||||||
|
navigate(sign);
|
||||||
|
remaining--;
|
||||||
|
if (remaining > 0) _navTimer = setTimeout(tick, 60);
|
||||||
|
}
|
||||||
|
tick();
|
||||||
|
}
|
||||||
|
|
||||||
// Click on the dark backdrop (the dialog or fan-wrap itself, not on any card child) closes
|
// Click on the dark backdrop (the dialog or fan-wrap itself, not on any card child) closes
|
||||||
var fanWrap = dialog.querySelector('.tarot-fan-wrap');
|
var fanWrap = dialog.querySelector('.tarot-fan-wrap');
|
||||||
dialog.addEventListener('click', function(e) {
|
dialog.addEventListener('click', function(e) {
|
||||||
@@ -96,16 +114,46 @@ function initGameKitPage() {
|
|||||||
if (e.key === 'ArrowLeft') navigate(-1);
|
if (e.key === 'ArrowLeft') navigate(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mousewheel navigation — throttled so each detent advances one card
|
// Mousewheel navigation — accumulate delta, cap at 3 cards per event so fast
|
||||||
var lastWheel = 0;
|
// spins don't overshoot; CSS transitions handle the visual smoothness.
|
||||||
|
var wheelAccum = 0;
|
||||||
|
var wheelDecayTimer = null;
|
||||||
|
var WHEEL_STEP = 150;
|
||||||
dialog.addEventListener('wheel', function(e) {
|
dialog.addEventListener('wheel', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var now = Date.now();
|
clearTimeout(wheelDecayTimer);
|
||||||
if (now - lastWheel < 150) return;
|
wheelAccum += e.deltaY;
|
||||||
lastWheel = now;
|
var steps = Math.trunc(wheelAccum / WHEEL_STEP);
|
||||||
navigate(e.deltaY > 0 ? 1 : -1);
|
if (steps !== 0) {
|
||||||
|
steps = Math.sign(steps) * Math.min(Math.abs(steps), 3);
|
||||||
|
wheelAccum -= steps * WHEEL_STEP;
|
||||||
|
navigate(steps);
|
||||||
|
}
|
||||||
|
wheelDecayTimer = setTimeout(function() { wheelAccum = 0; }, 200);
|
||||||
}, { passive: false });
|
}, { passive: false });
|
||||||
|
|
||||||
|
// Touch/swipe navigation — uses navigateAnimated so intermediate cards are visible
|
||||||
|
var touchStartX = 0;
|
||||||
|
var touchStartY = 0;
|
||||||
|
var touchStartTime = 0;
|
||||||
|
dialog.addEventListener('touchstart', function(e) {
|
||||||
|
touchStartX = e.touches[0].clientX;
|
||||||
|
touchStartY = e.touches[0].clientY;
|
||||||
|
touchStartTime = Date.now();
|
||||||
|
}, { passive: true });
|
||||||
|
dialog.addEventListener('touchend', function(e) {
|
||||||
|
var dx = e.changedTouches[0].clientX - touchStartX;
|
||||||
|
var dy = e.changedTouches[0].clientY - touchStartY;
|
||||||
|
if (Math.abs(dy) > Math.abs(dx)) return; // vertical swipe — ignore
|
||||||
|
if (Math.abs(dx) < 60) return; // dead zone — raise to 40–60 for more deliberate swipe required
|
||||||
|
var elapsed = Math.max(1, Date.now() - touchStartTime);
|
||||||
|
var velocity = Math.abs(dx) / elapsed; // px/ms
|
||||||
|
var steps = velocity > 0.8 // flick threshold — raise (e.g. 0.6) so more swipes use drag formula
|
||||||
|
? Math.max(1, Math.round(velocity * 4)) // flick multiplier — lower (e.g. 4–5) to reduce cards per fast flick
|
||||||
|
: Math.round(Math.abs(dx) / 150); // slow-drag divisor — raise (e.g. 120–150) for fewer cards per short drag
|
||||||
|
navigateAnimated(dx < 0 ? steps : -steps);
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
prevBtn.addEventListener('click', function() { navigate(-1); });
|
prevBtn.addEventListener('click', function() { navigate(-1); });
|
||||||
nextBtn.addEventListener('click', function() { navigate(1); });
|
nextBtn.addEventListener('click', function() { navigate(1); });
|
||||||
|
|
||||||
|
|||||||
@@ -227,6 +227,14 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
perspective: 900px;
|
perspective: 900px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
&:hover, &.active {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tarot-fan {
|
.tarot-fan {
|
||||||
|
|||||||
Reference in New Issue
Block a user