All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Replace hardcoded id-based section lookup with declarative rules: - data-show-category="games|books|films" on sections - data-hide-status="unending" on individual fields JS now has a single updateVisibility() that evaluates attributes. Adding new conditions only requires touching HTML, not JS. Also hides Progress and Total Hours for unending items. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
278 lines
8.6 KiB
HTML
278 lines
8.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{{ action }} Item — Backlogger</title>
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
background: #0f172a;
|
|
color: #e2e8f0;
|
|
min-height: 100vh;
|
|
}
|
|
a { text-decoration: none; color: inherit; }
|
|
|
|
.site-header {
|
|
background: #0a0f1e;
|
|
border-bottom: 1px solid #1e293b;
|
|
padding: 0.75rem 2rem;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
.site-header .brand { color: #38bdf8; font-weight: 600; font-size: 0.95rem; }
|
|
.site-header nav a { color: #64748b; font-size: 0.85rem; }
|
|
.site-header nav a:hover { color: #e2e8f0; }
|
|
|
|
.container {
|
|
max-width: 520px;
|
|
margin: 3rem auto;
|
|
padding: 0 1.5rem;
|
|
}
|
|
|
|
.card {
|
|
background: #0a0f1e;
|
|
border: 1px solid #1e293b;
|
|
border-radius: 12px;
|
|
padding: 2rem;
|
|
}
|
|
h1 { font-size: 1.35rem; margin-bottom: 1.75rem; letter-spacing: -0.02em; }
|
|
|
|
.field { margin-bottom: 1.25rem; }
|
|
.field label {
|
|
display: block;
|
|
font-size: 0.8rem;
|
|
color: #94a3b8;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.07em;
|
|
margin-bottom: 0.4rem;
|
|
}
|
|
.field input[type="text"],
|
|
.field input[type="number"],
|
|
.field select {
|
|
width: 100%;
|
|
background: #1e293b;
|
|
border: 1px solid #334155;
|
|
border-radius: 6px;
|
|
color: #e2e8f0;
|
|
padding: 0.55rem 0.75rem;
|
|
font-size: 0.9rem;
|
|
}
|
|
.field input:focus,
|
|
.field select:focus { outline: none; border-color: #38bdf8; }
|
|
|
|
.field input[type="range"] {
|
|
width: 100%;
|
|
accent-color: #38bdf8;
|
|
cursor: pointer;
|
|
margin-top: 0.2rem;
|
|
}
|
|
.progress-label {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 0.2rem;
|
|
}
|
|
.progress-val { color: #38bdf8; font-variant-numeric: tabular-nums; font-size: 0.9rem; }
|
|
|
|
.checkbox-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
.checkbox-row input[type="checkbox"] { accent-color: #38bdf8; width: 1rem; height: 1rem; cursor: pointer; }
|
|
.checkbox-row label {
|
|
font-size: 0.9rem;
|
|
color: #e2e8f0;
|
|
text-transform: none;
|
|
letter-spacing: 0;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.section-divider {
|
|
border: none;
|
|
border-top: 1px solid #1e293b;
|
|
margin: 1.5rem 0 1.25rem;
|
|
}
|
|
.section-title {
|
|
font-size: 0.72rem;
|
|
color: #64748b;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.optional { color: #475569; font-size: 0.7rem; margin-left: 0.3rem; text-transform: none; letter-spacing: 0; }
|
|
|
|
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
|
|
|
.actions {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-top: 2rem;
|
|
}
|
|
.btn {
|
|
display: inline-block;
|
|
padding: 0.5rem 1.25rem;
|
|
border-radius: 6px;
|
|
font-size: 0.875rem;
|
|
cursor: pointer;
|
|
border: none;
|
|
}
|
|
.btn-primary { background: #38bdf8; color: #0f172a; font-weight: 600; }
|
|
.btn-primary:hover { opacity: 0.88; }
|
|
.btn-ghost { background: transparent; color: #64748b; border: 1px solid #334155; text-decoration: none; padding: 0.5rem 1.25rem; }
|
|
.btn-ghost:hover { color: #e2e8f0; }
|
|
|
|
.errorlist { list-style: none; color: #f87171; font-size: 0.8rem; margin-top: 0.3rem; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header class="site-header">
|
|
<a class="brand" href="/">k-boris.tech</a>
|
|
<nav>
|
|
<a href="{% url 'backlogger:list' %}">← Back to backlogger</a>
|
|
</nav>
|
|
</header>
|
|
|
|
<div class="container">
|
|
<div class="card">
|
|
<h1>{{ action }} item</h1>
|
|
|
|
<form method="post">
|
|
{% csrf_token %}
|
|
|
|
<div class="field">
|
|
<label for="{{ form.category.id_for_label }}">Category</label>
|
|
{{ form.category }}
|
|
{{ form.category.errors }}
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="{{ form.name.id_for_label }}">Name</label>
|
|
{{ form.name }}
|
|
{{ form.name.errors }}
|
|
</div>
|
|
|
|
<div class="field" data-hide-status="unending">
|
|
<div class="progress-label">
|
|
<label for="{{ form.progress_percent.id_for_label }}">Progress</label>
|
|
<span class="progress-val" id="progress-display">{{ form.progress_percent.value|default:0|floatformat:0 }}%</span>
|
|
</div>
|
|
{{ form.progress_percent }}
|
|
{{ form.progress_percent.errors }}
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="checkbox-row">
|
|
{{ form.favorite }}
|
|
<label for="{{ form.favorite.id_for_label }}">Mark as favorite</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Games fields -->
|
|
<div class="cat-section" data-show-category="games">
|
|
<hr class="section-divider">
|
|
<div class="section-title">Games</div>
|
|
<div class="two-col">
|
|
<div class="field">
|
|
<label>Hours played</label>
|
|
{{ form.hours_played }}
|
|
{{ form.hours_played.errors }}
|
|
</div>
|
|
<div class="field" data-hide-status="unending">
|
|
<label>Total hours <span class="optional">optional</span></label>
|
|
{{ form.total_hours }}
|
|
{{ form.total_hours.errors }}
|
|
{% if item.hltb_main or item.hltb_extra or item.hltb_complete %}
|
|
<div style="margin-top:0.4rem;font-size:0.72rem;color:#475569;line-height:1.6">
|
|
HowLongToBeat:
|
|
{% if item.hltb_main %}<span style="color:#64748b">Main {{ item.hltb_main|floatformat:0 }}h</span>{% endif %}
|
|
{% if item.hltb_extra %}<span style="color:#64748b"> · +Extra {{ item.hltb_extra|floatformat:0 }}h</span>{% endif %}
|
|
{% if item.hltb_complete %}<span style="color:#64748b"> · 100% {{ item.hltb_complete|floatformat:0 }}h</span>{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Books fields -->
|
|
<div class="cat-section" data-show-category="books">
|
|
<hr class="section-divider">
|
|
<div class="section-title">Books</div>
|
|
<div class="two-col">
|
|
<div class="field">
|
|
<label>Pages read</label>
|
|
{{ form.pages_read }}
|
|
{{ form.pages_read.errors }}
|
|
</div>
|
|
<div class="field">
|
|
<label>Total pages <span class="optional">optional</span></label>
|
|
{{ form.total_pages }}
|
|
{{ form.total_pages.errors }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Films fields -->
|
|
<div class="cat-section" data-show-category="films">
|
|
<hr class="section-divider">
|
|
<div class="section-title">Films</div>
|
|
<div class="two-col">
|
|
<div class="field" style="display:flex; align-items:center; padding-top:1.5rem;">
|
|
<div class="checkbox-row">
|
|
{{ form.watched }}
|
|
<label for="{{ form.watched.id_for_label }}">Watched</label>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<label>Duration (min) <span class="optional">optional</span></label>
|
|
{{ form.duration_minutes }}
|
|
{{ form.duration_minutes.errors }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="actions">
|
|
<a href="{% url 'backlogger:list' %}" class="btn btn-ghost">Cancel</a>
|
|
<button type="submit" class="btn btn-primary">Save</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const catSelect = document.getElementById('{{ form.category.id_for_label }}');
|
|
const progressRange = document.getElementById('{{ form.progress_percent.id_for_label }}');
|
|
const progressDisplay = document.getElementById('progress-display');
|
|
const itemStatus = '{{ item.status|default:"active" }}';
|
|
|
|
function updateVisibility() {
|
|
const cat = catSelect.value;
|
|
|
|
// Show only the section matching the selected category
|
|
document.querySelectorAll('[data-show-category]').forEach(el => {
|
|
el.style.display = el.dataset.showCategory === cat ? 'block' : 'none';
|
|
});
|
|
|
|
// Hide fields that are not applicable for the current status
|
|
document.querySelectorAll('[data-hide-status]').forEach(el => {
|
|
el.style.display = el.dataset.hideStatus === itemStatus ? 'none' : '';
|
|
});
|
|
}
|
|
|
|
catSelect.addEventListener('change', updateVisibility);
|
|
updateVisibility();
|
|
|
|
progressRange.addEventListener('input', function() {
|
|
progressDisplay.textContent = Math.round(this.value) + '%';
|
|
});
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|