.
This commit is contained in:
+6
-5
@@ -7,7 +7,7 @@ class DinosaurForm(forms.ModelForm):
|
|||||||
model = Dinosaur
|
model = Dinosaur
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'era', 'diet', 'description', 'image',
|
'name', 'era', 'diet', 'description', 'image',
|
||||||
'speed', 'weight', 'intelligence', 'height',
|
'background', 'speed', 'weight', 'intelligence', 'height',
|
||||||
'facts',
|
'facts',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
@@ -16,10 +16,11 @@ class DinosaurForm(forms.ModelForm):
|
|||||||
'diet': forms.Select(attrs={'class': 'input'}),
|
'diet': forms.Select(attrs={'class': 'input'}),
|
||||||
'description': forms.Textarea(attrs={'class': 'textarea', 'rows': 3}),
|
'description': forms.Textarea(attrs={'class': 'textarea', 'rows': 3}),
|
||||||
'image': forms.ClearableFileInput(attrs={'class': 'file-input'}),
|
'image': forms.ClearableFileInput(attrs={'class': 'file-input'}),
|
||||||
'speed': forms.NumberInput(attrs={'class': 'input', 'min': 0, 'max': 100}),
|
'background': forms.Select(attrs={'class': 'input'}),
|
||||||
'weight': forms.NumberInput(attrs={'class': 'input', 'min': 0, 'max': 100}),
|
'speed': forms.NumberInput(attrs={'class': 'input', 'min': 0, 'max': int(Dinosaur.SPEED_MAX_KMH), 'placeholder': 'km/h'}),
|
||||||
'intelligence': forms.NumberInput(attrs={'class': 'input', 'min': 0, 'max': 100}),
|
'weight': forms.NumberInput(attrs={'class': 'input', 'min': 0, 'max': int(Dinosaur.WEIGHT_MAX_KG), 'placeholder': 'kg'}),
|
||||||
'height': forms.NumberInput(attrs={'class': 'input', 'min': 0, 'max': 100}),
|
'intelligence': forms.NumberInput(attrs={'class': 'input', 'min': 0, 'max': 100, 'placeholder': '0-100 score'}),
|
||||||
|
'height': forms.NumberInput(attrs={'class': 'input', 'min': 0, 'max': int(Dinosaur.HEIGHT_MAX_M), 'step': '0.1', 'placeholder': 'metres'}),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add facts field widget
|
# Add facts field widget
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def forwards(apps, schema_editor):
|
||||||
|
Dinosaur = apps.get_model('cards', 'Dinosaur')
|
||||||
|
# Constants must match those used in models at conversion time
|
||||||
|
SPEED_MAX_KMH = 60.0
|
||||||
|
WEIGHT_MAX_KG = 15000.0
|
||||||
|
HEIGHT_MAX_M = 12.0
|
||||||
|
|
||||||
|
for obj in Dinosaur.objects.all():
|
||||||
|
try:
|
||||||
|
# previous values were 0-100 percentages
|
||||||
|
old_speed = getattr(obj, 'speed', None)
|
||||||
|
old_weight = getattr(obj, 'weight', None)
|
||||||
|
old_height = getattr(obj, 'height', None)
|
||||||
|
if old_speed is not None:
|
||||||
|
new_speed = int(round(float(old_speed) / 100.0 * SPEED_MAX_KMH))
|
||||||
|
obj.speed = new_speed
|
||||||
|
if old_weight is not None:
|
||||||
|
new_weight = int(round(float(old_weight) / 100.0 * WEIGHT_MAX_KG))
|
||||||
|
obj.weight = new_weight
|
||||||
|
if old_height is not None:
|
||||||
|
new_height = round(float(old_height) / 100.0 * HEIGHT_MAX_M, 1)
|
||||||
|
# assign as string to avoid Decimal conversion issues
|
||||||
|
obj.height = new_height
|
||||||
|
obj.save(update_fields=['speed', 'weight', 'height'])
|
||||||
|
except Exception:
|
||||||
|
# skip errors during migration for safety; admins can fix manually
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
def reverse(apps, schema_editor):
|
||||||
|
# Not reversible automatically
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cards', '0003_dinosaur_facts'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='dinosaur',
|
||||||
|
name='speed',
|
||||||
|
field=models.PositiveIntegerField(default=30, help_text='Speed in km/h'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='dinosaur',
|
||||||
|
name='weight',
|
||||||
|
field=models.PositiveIntegerField(default=1000, help_text='Weight in kg'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='dinosaur',
|
||||||
|
name='height',
|
||||||
|
field=models.DecimalField(default=1.5, max_digits=5, decimal_places=1, help_text='Height in metres'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(forwards, reverse_code=migrations.RunPython.noop),
|
||||||
|
]
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cards', '0004_convert_stats_to_units'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='dinosaur',
|
||||||
|
name='background',
|
||||||
|
field=models.ForeignKey(blank=True, help_text='Optional per-card background image', null=True, on_delete=models.SET_NULL, related_name='dinosaurs', to='cards.backgroundimage'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 6.0 on 2025-12-05 10:23
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cards', '0004_convert_stats_to_units'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='dinosaur',
|
||||||
|
name='intelligence',
|
||||||
|
field=models.PositiveSmallIntegerField(default=50, help_text='Score 0-100'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Generated by Django 6.0 on 2025-12-05 10:39
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cards', '0005_add_background_fk'),
|
||||||
|
('cards', '0005_alter_dinosaur_intelligence'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
+137
-5
@@ -19,12 +19,24 @@ class Dinosaur(models.Model):
|
|||||||
diet = models.CharField(max_length=32, choices=DIET_CHOICES, default='herbivore')
|
diet = models.CharField(max_length=32, choices=DIET_CHOICES, default='herbivore')
|
||||||
description = models.TextField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
image = models.ImageField(upload_to='dinosaurs/', blank=True, null=True)
|
image = models.ImageField(upload_to='dinosaurs/', blank=True, null=True)
|
||||||
|
background = models.ForeignKey(
|
||||||
|
'cards.BackgroundImage',
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='dinosaurs',
|
||||||
|
help_text='Optional per-card background image'
|
||||||
|
)
|
||||||
|
|
||||||
# Top-trump style stats (0-100)
|
# Stats stored in real-world units now:
|
||||||
speed = models.PositiveSmallIntegerField(default=50)
|
# - `speed` in km/h
|
||||||
weight = models.PositiveSmallIntegerField(default=50)
|
# - `weight` in kg
|
||||||
intelligence = models.PositiveSmallIntegerField(default=50)
|
# - `height` in metres
|
||||||
height = models.PositiveSmallIntegerField(default=50)
|
# - `intelligence` remains a 0-100 score
|
||||||
|
speed = models.PositiveIntegerField(default=30, help_text='Speed in km/h')
|
||||||
|
weight = models.PositiveIntegerField(default=1000, help_text='Weight in kg')
|
||||||
|
intelligence = models.PositiveSmallIntegerField(default=50, help_text='Score 0-100')
|
||||||
|
height = models.DecimalField(max_digits=5, decimal_places=1, default=1.5, help_text='Height in metres')
|
||||||
facts = models.TextField(blank=True, help_text='One fact per line; shown as bullet points on the card')
|
facts = models.TextField(blank=True, help_text='One fact per line; shown as bullet points on the card')
|
||||||
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
@@ -42,6 +54,126 @@ class Dinosaur(models.Model):
|
|||||||
return []
|
return []
|
||||||
return [f.strip() for f in self.facts.splitlines() if f.strip()]
|
return [f.strip() for f in self.facts.splitlines() if f.strip()]
|
||||||
|
|
||||||
|
# -- Unit helpers / display properties -------------------------------------------------
|
||||||
|
# These map the 0-100 top-trump style values to human-friendly units. The numeric
|
||||||
|
# mappings are conservative defaults and can be adjusted if you want different ranges.
|
||||||
|
# Ranges used to convert between stored unit values and the 0-100 bar/star scale.
|
||||||
|
SPEED_MAX_KMH = 60.0
|
||||||
|
WEIGHT_MAX_KG = 15000.0
|
||||||
|
HEIGHT_MAX_M = 12.0
|
||||||
|
|
||||||
|
# Display helpers (use stored unit values directly)
|
||||||
|
@property
|
||||||
|
def speed_display(self):
|
||||||
|
return f"{self.speed} km/h"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def weight_display(self):
|
||||||
|
kg = int(self.weight)
|
||||||
|
if kg >= 1000:
|
||||||
|
tonnes = round(kg / 1000.0, 1)
|
||||||
|
return f"{tonnes} t"
|
||||||
|
return f"{kg} kg"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height_display(self):
|
||||||
|
# height is a DecimalField; format to one decimal place
|
||||||
|
return f"{float(self.height):.1f} m"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def intelligence_display(self):
|
||||||
|
return f"{self.intelligence}%"
|
||||||
|
|
||||||
|
# Percent helpers: convert stored units back to a 0-100 scale for bars/stars
|
||||||
|
def speed_percent(self):
|
||||||
|
return min(100, int(round(self.speed / float(self.SPEED_MAX_KMH) * 100)))
|
||||||
|
|
||||||
|
def weight_percent(self):
|
||||||
|
return min(100, int(round(self.weight / float(self.WEIGHT_MAX_KG) * 100)))
|
||||||
|
|
||||||
|
def height_percent(self):
|
||||||
|
return min(100, int(round(float(self.height) / float(self.HEIGHT_MAX_M) * 100)))
|
||||||
|
|
||||||
|
def intelligence_percent(self):
|
||||||
|
return min(100, int(self.intelligence or 0))
|
||||||
|
|
||||||
|
# -- Star rating helpers -------------------------------------------------------------
|
||||||
|
def _star_rating_from_percent(self, percent):
|
||||||
|
"""Convert a 0-100 percent into a 0-5 star score in 0.5 increments."""
|
||||||
|
if percent is None:
|
||||||
|
return 0.0
|
||||||
|
val = float(percent) * 0.05 # scale 0-100 -> 0-5
|
||||||
|
# round to nearest 0.5
|
||||||
|
return round(val * 2) / 2.0
|
||||||
|
|
||||||
|
def _stars_string(self, percent):
|
||||||
|
"""Return a simple star string like '★★★★½' and trailing empty stars '☆'."""
|
||||||
|
stars = self._star_rating_from_percent(percent)
|
||||||
|
full = int(stars)
|
||||||
|
half = 1 if (stars - full) >= 0.5 else 0
|
||||||
|
empty = 5 - full - half
|
||||||
|
return "★" * full + ("½" if half else "") + "☆" * empty
|
||||||
|
|
||||||
|
def _star_types_from_percent(self, percent):
|
||||||
|
"""Return a list of five items: 'full', 'half', or 'empty'."""
|
||||||
|
stars = self._star_rating_from_percent(percent)
|
||||||
|
full = int(stars)
|
||||||
|
half = 1 if (stars - full) >= 0.5 else 0
|
||||||
|
types = ['full'] * full
|
||||||
|
if half:
|
||||||
|
types.append('half')
|
||||||
|
types.extend(['empty'] * (5 - len(types)))
|
||||||
|
return types
|
||||||
|
|
||||||
|
# Expose star strings and original values for templates
|
||||||
|
@property
|
||||||
|
def speed_stars(self):
|
||||||
|
return self._stars_string(self.speed_percent())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_star_types(self):
|
||||||
|
return self._star_types_from_percent(self.speed_percent())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_orig(self):
|
||||||
|
return self.speed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def weight_stars(self):
|
||||||
|
return self._stars_string(self.weight_percent())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def weight_star_types(self):
|
||||||
|
return self._star_types_from_percent(self.weight_percent())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def weight_orig(self):
|
||||||
|
return self.weight
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height_stars(self):
|
||||||
|
return self._stars_string(self.height_percent())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height_star_types(self):
|
||||||
|
return self._star_types_from_percent(self.height_percent())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height_orig(self):
|
||||||
|
return self.height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def intelligence_stars(self):
|
||||||
|
return self._stars_string(self.intelligence_percent())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def intelligence_star_types(self):
|
||||||
|
return self._star_types_from_percent(self.intelligence_percent())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def intelligence_orig(self):
|
||||||
|
return self.intelligence
|
||||||
|
|
||||||
|
|
||||||
class BackgroundImage(models.Model):
|
class BackgroundImage(models.Model):
|
||||||
"""Background images used behind card renders.
|
"""Background images used behind card renders.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<article class="card-item" id="card-{{ card.pk }}">
|
<article class="card-item" id="card-{{ card.pk }}">
|
||||||
<div class="toptrump-card">
|
<div class="toptrump-card">
|
||||||
<div class="hero-image" style="background-image: url('{% if background and background.image %}{{ background.image.url }}{% endif %}');">
|
<div class="hero-image" style="background-image: url('{% if card.background and card.background.image %}{{ card.background.image.url }}{% elif background and background.image %}{{ background.image.url }}{% endif %}');">
|
||||||
{% if card.image %}
|
{% if card.image %}
|
||||||
<img src="{{ card.image.url }}" alt="{{ card.name }}" style="display:block;margin:16px auto 0; max-width:72%; height:auto; object-fit:cover; box-shadow:0 4px 12px rgba(0,0,0,0.3); border-radius:6px;">
|
<img src="{{ card.image.url }}" alt="{{ card.name }}" style="display:block;margin:16px auto 0; max-width:72%; height:auto; object-fit:cover; box-shadow:0 4px 12px rgba(0,0,0,0.3); border-radius:6px;">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -16,9 +16,38 @@
|
|||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div style="margin-top:0.5rem">
|
<div style="margin-top:0.5rem">
|
||||||
<div class="stat-row"><div class="stat-label">Speed</div><div class="stat-bar"><div class="stat-fill" style="width:{{ card.speed }}%"></div></div></div>
|
<div class="stat-row">
|
||||||
<div class="stat-row"><div class="stat-label">Weight</div><div class="stat-bar"><div class="stat-fill" style="width:{{ card.weight }}%"></div></div></div>
|
<div class="stat-label">Speed <small class="is-size-7">(km/h)</small></div>
|
||||||
<div class="stat-row"><div class="stat-label">Intelligence</div><div class="stat-bar"><div class="stat-fill" style="width:{{ card.intelligence }}%"></div></div></div>
|
<div class="stat-bar"><div class="stat-fill" style="width:{{ card.speed_percent }}%"></div></div>
|
||||||
|
<div class="stat-value" style="margin-left:8px;font-weight:600">
|
||||||
|
<div>{{ card.speed_display }}</div>
|
||||||
|
<div class="is-size-7" style="font-weight:500">
|
||||||
|
{% for t in card.speed_star_types %}<span class="star {{ t }}">{% if t == 'full' %}★{% else %}☆{% endif %}</span>{% endfor %}
|
||||||
|
<span style="margin-left:6px">(orig: {{ card.speed_orig }})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-row">
|
||||||
|
<div class="stat-label">Weight <small class="is-size-7">(kg)</small></div>
|
||||||
|
<div class="stat-bar"><div class="stat-fill" style="width:{{ card.weight_percent }}%"></div></div>
|
||||||
|
<div class="stat-value" style="margin-left:8px;font-weight:600">
|
||||||
|
<div>{{ card.weight_display }}</div>
|
||||||
|
<div class="is-size-7" style="font-weight:500">
|
||||||
|
{% for t in card.weight_star_types %}<span class="star {{ t }}">{% if t == 'full' %}★{% else %}☆{% endif %}</span>{% endfor %}
|
||||||
|
<span style="margin-left:6px">(orig: {{ card.weight_orig }})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-row">
|
||||||
|
<div class="stat-label">Intelligence <small class="is-size-7">(score)</small></div>
|
||||||
|
<div class="stat-bar"><div class="stat-fill" style="width:{{ card.intelligence_percent }}%"></div></div>
|
||||||
|
<div class="stat-value" style="margin-left:8px;font-weight:600">
|
||||||
|
<div class="is-size-7" style="font-weight:600">
|
||||||
|
{% for t in card.intelligence_star_types %}<span class="star {{ t }}">{% if t == 'full' %}★{% else %}☆{% endif %}</span>{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="is-size-7" style="font-weight:500">(orig: {{ card.intelligence_orig }})</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons" style="margin-top:0.6rem">
|
<div class="buttons" style="margin-top:0.6rem">
|
||||||
<button class="button is-small is-info" hx-get="{% url 'cards:edit' card.pk %}" hx-target="#htmx-form" hx-swap="innerHTML">Edit</button>
|
<button class="button is-small is-info" hx-get="{% url 'cards:edit' card.pk %}" hx-target="#htmx-form" hx-swap="innerHTML">Edit</button>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<div style="display:flex; justify-content:center;">
|
<div style="display:flex; justify-content:center;">
|
||||||
<div class="toptrump-card">
|
<div class="toptrump-card">
|
||||||
{# Hero / image banner using background image if available #}
|
{# Hero / image banner using background image if available #}
|
||||||
<div class="hero-image" style="background-image: url('{% if background and background.image %}{{ background.image.url }}{% endif %}');">
|
<div class="hero-image" style="background-image: url('{% if card.background and card.background.image %}{{ card.background.image.url }}{% elif background and background.image %}{{ background.image.url }}{% endif %}');">
|
||||||
{% if card.image %}
|
{% if card.image %}
|
||||||
<img src="{{ card.image.url }}" alt="{{ card.name }}" style="position:relative; display:block; margin:18px auto 0; max-width:78%; height:auto; object-fit:contain; box-shadow:0 6px 18px rgba(0,0,0,0.35); border-radius:6px;">
|
<img src="{{ card.image.url }}" alt="{{ card.name }}" style="position:relative; display:block; margin:18px auto 0; max-width:78%; height:auto; object-fit:contain; box-shadow:0 6px 18px rgba(0,0,0,0.35); border-radius:6px;">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -29,10 +29,38 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div style="margin-top:0.8rem">
|
<div style="margin-top:0.8rem">
|
||||||
<div class="stat-row"><div class="stat-label">Speed</div><div class="stat-bar"><div class="stat-fill" style="width:{{ card.speed }}%"></div></div><div style="width:36px;text-align:right;font-weight:700">{{ card.speed }}</div></div>
|
<div class="stat-row">
|
||||||
<div class="stat-row"><div class="stat-label">Weight</div><div class="stat-bar"><div class="stat-fill" style="width:{{ card.weight }}%"></div></div><div style="width:36px;text-align:right;font-weight:700">{{ card.weight }}</div></div>
|
<div class="stat-label">Speed <small class="is-size-7">(km/h)</small></div>
|
||||||
<div class="stat-row"><div class="stat-label">Intelligence</div><div class="stat-bar"><div class="stat-fill" style="width:{{ card.intelligence }}%"></div></div><div style="width:36px;text-align:right;font-weight:700">{{ card.intelligence }}</div></div>
|
<div class="stat-bar"><div class="stat-fill" style="width:{{ card.speed_percent }}%"></div></div>
|
||||||
<div class="stat-row"><div class="stat-label">Height</div><div class="stat-bar"><div class="stat-fill" style="width:{{ card.height }}%"></div></div><div style="width:36px;text-align:right;font-weight:700">{{ card.height }}</div></div>
|
<div class="stat-value" style="margin-left:8px;font-weight:700">
|
||||||
|
<div>{{ card.speed_display }}</div>
|
||||||
|
<div class="is-size-7" style="font-weight:500">{% for t in card.speed_star_types %}<span class="star {{ t }}">{% if t == 'full' %}★{% else %}☆{% endif %}</span>{% endfor %} <span style="margin-left:6px">(orig: {{ card.speed_orig }})</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-row">
|
||||||
|
<div class="stat-label">Weight <small class="is-size-7">(kg)</small></div>
|
||||||
|
<div class="stat-bar"><div class="stat-fill" style="width:{{ card.weight_percent }}%"></div></div>
|
||||||
|
<div class="stat-value" style="margin-left:8px;font-weight:700">
|
||||||
|
<div>{{ card.weight_display }}</div>
|
||||||
|
<div class="is-size-7" style="font-weight:500">{% for t in card.weight_star_types %}<span class="star {{ t }}">{% if t == 'full' %}★{% else %}☆{% endif %}</span>{% endfor %} <span style="margin-left:6px">(orig: {{ card.weight_orig }})</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-row">
|
||||||
|
<div class="stat-label">Intelligence <small class="is-size-7">(score)</small></div>
|
||||||
|
<div class="stat-bar"><div class="stat-fill" style="width:{{ card.intelligence_percent }}%"></div></div>
|
||||||
|
<div class="stat-value" style="margin-left:8px;font-weight:700">
|
||||||
|
<div class="is-size-7" style="font-weight:600">{% for t in card.intelligence_star_types %}<span class="star {{ t }}">{% if t == 'full' %}★{% else %}☆{% endif %}</span>{% endfor %}</div>
|
||||||
|
<div class="is-size-7" style="font-weight:500">(orig: {{ card.intelligence_orig }})</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-row">
|
||||||
|
<div class="stat-label">Height <small class="is-size-7">(m)</small></div>
|
||||||
|
<div class="stat-bar"><div class="stat-fill" style="width:{{ card.height_percent }}%"></div></div>
|
||||||
|
<div class="stat-value" style="margin-left:8px;font-weight:700">
|
||||||
|
<div>{{ card.height_display }}</div>
|
||||||
|
<div class="is-size-7" style="font-weight:500">{% for t in card.height_star_types %}<span class="star {{ t }}">{% if t == 'full' %}★{% else %}☆{% endif %}</span>{% endfor %} <span style="margin-left:6px">(orig: {{ card.height_orig }})</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
BIN
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 5.1 MiB |
@@ -1,6 +1,6 @@
|
|||||||
<article class="card-item" id="card-{{ card.pk }}">
|
<article class="card-item" id="card-{{ card.pk }}">
|
||||||
<div class="toptrump-card">
|
<div class="toptrump-card">
|
||||||
<div class="hero-image" style="background-image: url('{% if background and background.image %}{{ background.image.url }}{% endif %}');">
|
<div class="hero-image" style="background-image: url('{% if card.background and card.background.image %}{{ card.background.image.url }}{% elif background and background.image %}{{ background.image.url }}{% endif %}');">
|
||||||
{% if card.image %}
|
{% if card.image %}
|
||||||
<img src="{{ card.image.url }}" alt="{{ card.name }}" style="display:block;margin:16px auto 0; max-width:72%; height:auto; object-fit:cover; box-shadow:0 4px 12px rgba(0,0,0,0.3); border-radius:6px;">
|
<img src="{{ card.image.url }}" alt="{{ card.name }}" style="display:block;margin:16px auto 0; max-width:72%; height:auto; object-fit:cover; box-shadow:0 4px 12px rgba(0,0,0,0.3); border-radius:6px;">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -16,9 +16,49 @@
|
|||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div style="margin-top:0.5rem">
|
<div style="margin-top:0.5rem">
|
||||||
<div class="stat-row"><div class="stat-label">Speed</div><div class="stat-bar"><div class="stat-fill" style="width:{{ card.speed }}%"></div></div></div>
|
<div class="stat-row">
|
||||||
<div class="stat-row"><div class="stat-label">Weight</div><div class="stat-bar"><div class="stat-fill" style="width:{{ card.weight }}%"></div></div></div>
|
<div class="stat-label">Speed <small class="is-size-7">(km/h)</small></div>
|
||||||
<div class="stat-row"><div class="stat-label">Intelligence</div><div class="stat-bar"><div class="stat-fill" style="width:{{ card.intelligence }}%"></div></div></div>
|
<div class="stat-bar"><div class="stat-fill" style="width:{{ card.speed_percent }}%"></div></div>
|
||||||
|
<div class="stat-value" style="margin-left:8px;font-weight:600">
|
||||||
|
<div>{{ card.speed_display }}</div>
|
||||||
|
<div class="is-size-7" style="font-weight:500">
|
||||||
|
{% for t in card.speed_star_types %}<span class="star {{ t }}">{% if t == 'full' %}★{% else %}☆{% endif %}</span>{% endfor %}
|
||||||
|
<span style="margin-left:6px">(orig: {{ card.speed_orig }})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-row">
|
||||||
|
<div class="stat-label">Weight <small class="is-size-7">(kg)</small></div>
|
||||||
|
<div class="stat-bar"><div class="stat-fill" style="width:{{ card.weight_percent }}%"></div></div>
|
||||||
|
<div class="stat-value" style="margin-left:8px;font-weight:600">
|
||||||
|
<div>{{ card.weight_display }}</div>
|
||||||
|
<div class="is-size-7" style="font-weight:500">
|
||||||
|
{% for t in card.weight_star_types %}<span class="star {{ t }}">{% if t == 'full' %}★{% else %}☆{% endif %}</span>{% endfor %}
|
||||||
|
<span style="margin-left:6px">(orig: {{ card.weight_orig }})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-row">
|
||||||
|
<div class="stat-label">Height <small class="is-size-7">(m)</small></div>
|
||||||
|
<div class="stat-bar"><div class="stat-fill" style="width:{{ card.height_percent }}%"></div></div>
|
||||||
|
<div class="stat-value" style="margin-left:8px;font-weight:600">
|
||||||
|
<div>{{ card.height_display }}</div>
|
||||||
|
<div class="is-size-7" style="font-weight:500">
|
||||||
|
{% for t in card.height_star_types %}<span class="star {{ t }}">{% if t == 'full' %}★{% else %}☆{% endif %}</span>{% endfor %}
|
||||||
|
<span style="margin-left:6px">(orig: {{ card.height_orig }})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-row">
|
||||||
|
<div class="stat-label">Intelligence <small class="is-size-7">(score)</small></div>
|
||||||
|
<div class="stat-bar"><div class="stat-fill" style="width:{{ card.intelligence_percent }}%"></div></div>
|
||||||
|
<div class="stat-value" style="margin-left:8px;font-weight:600">
|
||||||
|
<div class="is-size-7" style="font-weight:600">
|
||||||
|
{% for t in card.intelligence_star_types %}<span class="star {{ t }}">{% if t == 'full' %}★{% else %}☆{% endif %}</span>{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="is-size-7" style="font-weight:500">(orig: {{ card.intelligence_orig }})</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons" style="margin-top:0.6rem">
|
<div class="buttons" style="margin-top:0.6rem">
|
||||||
<button class="button is-small is-info" hx-get="{% url 'cards:edit' card.pk %}" hx-target="#htmx-form" hx-swap="innerHTML">Edit</button>
|
<button class="button is-small is-info" hx-get="{% url 'cards:edit' card.pk %}" hx-target="#htmx-form" hx-swap="innerHTML">Edit</button>
|
||||||
|
|||||||
@@ -86,19 +86,47 @@
|
|||||||
<div class="field-body">
|
<div class="field-body">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ form.speed.label }}</label>
|
<label class="label">{{ form.speed.label }}</label>
|
||||||
<div class="control">{{ form.speed }}</div>
|
<div class="control">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<div class="control is-expanded">{{ form.speed }}</div>
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-static">km/h</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ form.weight.label }}</label>
|
<label class="label">{{ form.weight.label }}</label>
|
||||||
<div class="control">{{ form.weight }}</div>
|
<div class="control">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<div class="control is-expanded">{{ form.weight }}</div>
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-static">kg</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ form.intelligence.label }}</label>
|
<label class="label">{{ form.intelligence.label }}</label>
|
||||||
<div class="control">{{ form.intelligence }}</div>
|
<div class="control">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<div class="control is-expanded">{{ form.intelligence }}</div>
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-static">score</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ form.height.label }}</label>
|
<label class="label">{{ form.height.label }}</label>
|
||||||
<div class="control">{{ form.height }}</div>
|
<div class="control">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<div class="control is-expanded">{{ form.height }}</div>
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-static">m</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,6 +41,13 @@
|
|||||||
.toptrump-card .stat-bar{flex:1;height:12px;background:rgba(255,255,255,0.06);border-radius:8px;overflow:hidden}
|
.toptrump-card .stat-bar{flex:1;height:12px;background:rgba(255,255,255,0.06);border-radius:8px;overflow:hidden}
|
||||||
.toptrump-card .stat-fill{height:100%;background:linear-gradient(90deg,var(--accent),#ffdd57);}
|
.toptrump-card .stat-fill{height:100%;background:linear-gradient(90deg,var(--accent),#ffdd57);}
|
||||||
.toptrump-card .fact-list{margin-top:0.5rem;padding-left:1rem}
|
.toptrump-card .fact-list{margin-top:0.5rem;padding-left:1rem}
|
||||||
|
/* Star rating visuals */
|
||||||
|
.star { font-size:0.95rem; display:inline-block; line-height:1; margin-left:2px; }
|
||||||
|
.star.full { color:#ffdd57; }
|
||||||
|
.star.empty { color:rgba(255,255,255,0.25); }
|
||||||
|
.star.half { color:rgba(255,255,255,0.25); position:relative; display:inline-block; }
|
||||||
|
.star.half::before { content: '★'; color:#ffdd57; position:absolute; left:0; top:0; width:50%; overflow:hidden; display:inline-block; }
|
||||||
|
.stat-value { text-align:right; min-width:6.5rem }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Reference in New Issue
Block a user