.
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
<div class="toptrump-card">
|
<div class="toptrump-card">
|
||||||
<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 %}');">
|
<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 class="card-hero-img" src="{{ card.image.url }}" alt="{{ card.name }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="title-band">{{ card.name }}</div>
|
<div class="title-band">{{ card.name }}</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
{# 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 card.background and card.background.image %}{{ card.background.image.url }}{% elif 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 class="card-hero-img" src="{{ card.image.url }}" alt="{{ card.name }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="title-band">{{ card.name }}</div>
|
<div class="title-band">{{ card.name }}</div>
|
||||||
|
|||||||
@@ -64,10 +64,41 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ form.background.label }}</label>
|
<label class="label">{{ form.background.label }}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<div class="select is-fullwidth">{{ form.background }}</div>
|
<div style="display:none">{{ form.background }}</div>
|
||||||
|
<div class="background-picker">
|
||||||
|
<div class="bg-options">
|
||||||
|
<div class="bg-option" data-bg-id="" role="button" tabindex="0">Use site default</div>
|
||||||
|
{% for bg in backgrounds %}
|
||||||
|
<div class="bg-option{% if form.instance.background and form.instance.background.pk == bg.pk %} selected{% endif %}" data-bg-id="{{ bg.pk }}" title="{{ bg.title|default:'Background' }}">
|
||||||
|
<img src="{{ bg.image.url }}" alt="{{ bg.title }}">
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
<p class="help">Optional: choose a background image to use for this card.</p>
|
<p class="help">Optional: choose a background image to use for this card.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
const script = document.currentScript;
|
||||||
|
const root = script && script.parentElement ? script.parentElement : document;
|
||||||
|
const picker = root.querySelector('.background-picker');
|
||||||
|
if(!picker) return;
|
||||||
|
const select = root.querySelector('select[name="background"]');
|
||||||
|
const options = picker.querySelectorAll('.bg-option');
|
||||||
|
function clearSelected(){ options.forEach(o=>o.classList.remove('selected')); }
|
||||||
|
options.forEach(function(opt){
|
||||||
|
opt.addEventListener('click', function(){
|
||||||
|
const id = opt.getAttribute('data-bg-id');
|
||||||
|
clearSelected();
|
||||||
|
opt.classList.add('selected');
|
||||||
|
if(select){ select.value = id || '';
|
||||||
|
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
<script>
|
<script>
|
||||||
(function(){
|
(function(){
|
||||||
// Attach to file inputs within this form fragment and update the .file-name text
|
// Attach to file inputs within this form fragment and update the .file-name text
|
||||||
|
|||||||
@@ -34,6 +34,30 @@
|
|||||||
/* Top Trumps card styles */
|
/* Top Trumps card styles */
|
||||||
.toptrump-card{max-width:320px;border-radius:10px;overflow:hidden;background:var(--card-bg);color:var(--text);box-shadow:0 6px 18px rgba(0,0,0,0.12);border:1px solid rgba(0,0,0,0.06)}
|
.toptrump-card{max-width:320px;border-radius:10px;overflow:hidden;background:var(--card-bg);color:var(--text);box-shadow:0 6px 18px rgba(0,0,0,0.12);border:1px solid rgba(0,0,0,0.06)}
|
||||||
.toptrump-card .hero-image{height:180px;background-size:cover;background-position:center}
|
.toptrump-card .hero-image{height:180px;background-size:cover;background-position:center}
|
||||||
|
/* Hero image: make the dinosaur image larger, cover the banner and remove shadow */
|
||||||
|
.toptrump-card .hero-image img.card-hero-img{
|
||||||
|
display:block;
|
||||||
|
margin:8px auto 0;
|
||||||
|
width:92%;
|
||||||
|
height:140px;
|
||||||
|
object-fit:cover;
|
||||||
|
border-radius:6px;
|
||||||
|
box-shadow:none;
|
||||||
|
}
|
||||||
|
/* Star rating visuals (app-level) */
|
||||||
|
.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 }
|
||||||
|
/* Background picker thumbnails (app-level) */
|
||||||
|
.background-picker{margin-top:0.5rem}
|
||||||
|
.background-picker .bg-options{display:flex;gap:0.5rem;flex-wrap:wrap}
|
||||||
|
.background-picker .bg-option{width:64px;height:48px;border:1px solid rgba(0,0,0,0.06);display:flex;align-items:center;justify-content:center;cursor:pointer;background:#fff;color:#222;padding:4px;border-radius:4px}
|
||||||
|
.background-picker .bg-option img{width:100%;height:100%;object-fit:cover;display:block;border-radius:2px}
|
||||||
|
.background-picker .bg-option.selected{outline:2px solid var(--accent);box-shadow:0 2px 6px rgba(0,0,0,0.08)}
|
||||||
|
.background-picker .bg-option[role="button"]{font-size:0.8rem;padding:6px 8px}
|
||||||
.toptrump-card .title-band{background:var(--accent);color:#072027;padding:0.6rem 0.75rem;text-align:center;font-weight:700;font-size:1.05rem}
|
.toptrump-card .title-band{background:var(--accent);color:#072027;padding:0.6rem 0.75rem;text-align:center;font-weight:700;font-size:1.05rem}
|
||||||
.toptrump-card .card-body{padding:0.75rem}
|
.toptrump-card .card-body{padding:0.75rem}
|
||||||
.toptrump-card .stat-row{display:flex;align-items:center;gap:0.75rem;padding:0.25rem 0}
|
.toptrump-card .stat-row{display:flex;align-items:center;gap:0.75rem;padding:0.25rem 0}
|
||||||
|
|||||||
+7
-5
@@ -20,6 +20,7 @@ def card_list(request):
|
|||||||
|
|
||||||
def card_create(request):
|
def card_create(request):
|
||||||
background = BackgroundImage.get_active() if BackgroundImage.objects.exists() else None
|
background = BackgroundImage.get_active() if BackgroundImage.objects.exists() else None
|
||||||
|
backgrounds = BackgroundImage.objects.all()
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = DinosaurForm(request.POST, request.FILES)
|
form = DinosaurForm(request.POST, request.FILES)
|
||||||
@@ -28,25 +29,26 @@ def card_create(request):
|
|||||||
if is_htmx(request):
|
if is_htmx(request):
|
||||||
card_html = render_to_string('cards/_card_item.html', {'card': card, 'background': background}, request=request)
|
card_html = render_to_string('cards/_card_item.html', {'card': card, 'background': background}, request=request)
|
||||||
# Also return an out-of-band fragment to replace/clear the form container
|
# Also return an out-of-band fragment to replace/clear the form container
|
||||||
form_html = render_to_string('cards/_form.html', {'form': DinosaurForm()}, request=request)
|
form_html = render_to_string('cards/_form.html', {'form': DinosaurForm(), 'background': background, 'backgrounds': backgrounds}, request=request)
|
||||||
oob = f'<div id="htmx-form" hx-swap-oob="true">{form_html}</div>'
|
oob = f'<div id="htmx-form" hx-swap-oob="true">{form_html}</div>'
|
||||||
return HttpResponse(card_html + oob)
|
return HttpResponse(card_html + oob)
|
||||||
return redirect('cards:list')
|
return redirect('cards:list')
|
||||||
else:
|
else:
|
||||||
if is_htmx(request):
|
if is_htmx(request):
|
||||||
# Return the form as an out-of-band fragment so it updates the form container
|
# Return the form as an out-of-band fragment so it updates the form container
|
||||||
form_html = render_to_string('cards/_form.html', {'form': form}, request=request)
|
form_html = render_to_string('cards/_form.html', {'form': form, 'background': background, 'backgrounds': backgrounds}, request=request)
|
||||||
oob = f'<div id="htmx-form" hx-swap-oob="true">{form_html}</div>'
|
oob = f'<div id="htmx-form" hx-swap-oob="true">{form_html}</div>'
|
||||||
return HttpResponse(oob)
|
return HttpResponse(oob)
|
||||||
else:
|
else:
|
||||||
form = DinosaurForm()
|
form = DinosaurForm()
|
||||||
|
|
||||||
return render(request, 'cards/_form.html', {'form': form, 'background': background})
|
return render(request, 'cards/_form.html', {'form': form, 'background': background, 'backgrounds': backgrounds})
|
||||||
|
|
||||||
|
|
||||||
def card_edit(request, pk):
|
def card_edit(request, pk):
|
||||||
card = get_object_or_404(Dinosaur, pk=pk)
|
card = get_object_or_404(Dinosaur, pk=pk)
|
||||||
background = BackgroundImage.get_active() if BackgroundImage.objects.exists() else None
|
background = BackgroundImage.get_active() if BackgroundImage.objects.exists() else None
|
||||||
|
backgrounds = BackgroundImage.objects.all()
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = DinosaurForm(request.POST, request.FILES, instance=card)
|
form = DinosaurForm(request.POST, request.FILES, instance=card)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@@ -59,13 +61,13 @@ def card_edit(request, pk):
|
|||||||
return redirect('cards:list')
|
return redirect('cards:list')
|
||||||
else:
|
else:
|
||||||
if is_htmx(request):
|
if is_htmx(request):
|
||||||
form_html = render_to_string('cards/_form.html', {'form': form, 'card': card}, request=request)
|
form_html = render_to_string('cards/_form.html', {'form': form, 'card': card, 'background': background, 'backgrounds': backgrounds}, request=request)
|
||||||
oob = f'<div id="htmx-form" hx-swap-oob="true">{form_html}</div>'
|
oob = f'<div id="htmx-form" hx-swap-oob="true">{form_html}</div>'
|
||||||
return HttpResponse(oob)
|
return HttpResponse(oob)
|
||||||
else:
|
else:
|
||||||
form = DinosaurForm(instance=card)
|
form = DinosaurForm(instance=card)
|
||||||
|
|
||||||
return render(request, 'cards/_form.html', {'form': form, 'card': card, 'background': background})
|
return render(request, 'cards/_form.html', {'form': form, 'card': card, 'background': background, 'backgrounds': backgrounds})
|
||||||
|
|
||||||
|
|
||||||
def card_delete(request, pk):
|
def card_delete(request, pk):
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -2,7 +2,7 @@
|
|||||||
<div class="toptrump-card">
|
<div class="toptrump-card">
|
||||||
<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 %}');">
|
<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 class="card-hero-img" src="{{ card.image.url }}" alt="{{ card.name }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="title-band">{{ card.name }}</div>
|
<div class="title-band">{{ card.name }}</div>
|
||||||
|
|||||||
@@ -63,10 +63,42 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ form.background.label }}</label>
|
<label class="label">{{ form.background.label }}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<div class="select is-fullwidth">{{ form.background }}</div>
|
<div style="display:none">{{ form.background }}</div>
|
||||||
|
<div class="background-picker">
|
||||||
|
<div class="bg-options">
|
||||||
|
<div class="bg-option" data-bg-id="" role="button" tabindex="0">Use site default</div>
|
||||||
|
{% for bg in backgrounds %}
|
||||||
|
<div class="bg-option{% if form.instance.background and form.instance.background.pk == bg.pk %} selected{% endif %}" data-bg-id="{{ bg.pk }}" title="{{ bg.title|default:'Background' }}">
|
||||||
|
<img src="{{ bg.image.url }}" alt="{{ bg.title }}">
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
<p class="help">Optional: choose a background image to use for this card.</p>
|
<p class="help">Optional: choose a background image to use for this card.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
const script = document.currentScript;
|
||||||
|
const root = script && script.parentElement ? script.parentElement : document;
|
||||||
|
const picker = root.querySelector('.background-picker');
|
||||||
|
if(!picker) return;
|
||||||
|
const select = root.querySelector('select[name="background"]');
|
||||||
|
const options = picker.querySelectorAll('.bg-option');
|
||||||
|
function clearSelected(){ options.forEach(o=>o.classList.remove('selected')); }
|
||||||
|
options.forEach(function(opt){
|
||||||
|
opt.addEventListener('click', function(){
|
||||||
|
const id = opt.getAttribute('data-bg-id');
|
||||||
|
clearSelected();
|
||||||
|
opt.classList.add('selected');
|
||||||
|
if(select){ select.value = id || '';
|
||||||
|
// dispatch change for any listeners
|
||||||
|
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
<script>
|
<script>
|
||||||
(function(){
|
(function(){
|
||||||
const script = document.currentScript;
|
const script = document.currentScript;
|
||||||
|
|||||||
@@ -34,6 +34,23 @@
|
|||||||
/* Top Trumps card styles */
|
/* Top Trumps card styles */
|
||||||
.toptrump-card{max-width:320px;border-radius:10px;overflow:hidden;background:var(--card-bg);color:var(--text);box-shadow:0 6px 18px rgba(0,0,0,0.12);border:1px solid rgba(0,0,0,0.06)}
|
.toptrump-card{max-width:320px;border-radius:10px;overflow:hidden;background:var(--card-bg);color:var(--text);box-shadow:0 6px 18px rgba(0,0,0,0.12);border:1px solid rgba(0,0,0,0.06)}
|
||||||
.toptrump-card .hero-image{height:180px;background-size:cover;background-position:center}
|
.toptrump-card .hero-image{height:180px;background-size:cover;background-position:center}
|
||||||
|
/* Hero image: make the dinosaur image larger, cover the banner and remove shadow */
|
||||||
|
.toptrump-card .hero-image img.card-hero-img{
|
||||||
|
display:block;
|
||||||
|
margin:8px auto 0;
|
||||||
|
width:92%;
|
||||||
|
height:140px;
|
||||||
|
object-fit:cover;
|
||||||
|
border-radius:6px;
|
||||||
|
box-shadow:none;
|
||||||
|
}
|
||||||
|
/* Background picker thumbnails */
|
||||||
|
.background-picker{margin-top:0.5rem}
|
||||||
|
.background-picker .bg-options{display:flex;gap:0.5rem;flex-wrap:wrap}
|
||||||
|
.background-picker .bg-option{width:64px;height:48px;border:1px solid rgba(0,0,0,0.06);display:flex;align-items:center;justify-content:center;cursor:pointer;background:#fff;color:#222;padding:4px;border-radius:4px}
|
||||||
|
.background-picker .bg-option img{max-width:100%;max-height:100%;display:block;border-radius:2px}
|
||||||
|
.background-picker .bg-option.selected{outline:2px solid var(--accent);box-shadow:0 2px 6px rgba(0,0,0,0.08)}
|
||||||
|
.background-picker .bg-option[role="button"]{font-size:0.8rem;padding:6px 8px}
|
||||||
.toptrump-card .title-band{background:var(--accent);color:#072027;padding:0.6rem 0.75rem;text-align:center;font-weight:700;font-size:1.05rem}
|
.toptrump-card .title-band{background:var(--accent);color:#072027;padding:0.6rem 0.75rem;text-align:center;font-weight:700;font-size:1.05rem}
|
||||||
.toptrump-card .card-body{padding:0.75rem}
|
.toptrump-card .card-body{padding:0.75rem}
|
||||||
.toptrump-card .stat-row{display:flex;align-items:center;gap:0.75rem;padding:0.25rem 0}
|
.toptrump-card .stat-row{display:flex;align-items:center;gap:0.75rem;padding:0.25rem 0}
|
||||||
|
|||||||
Reference in New Issue
Block a user