Add a club event-background photo, shown in black & white on event heroes

New Club.event_background, uploaded from the identity page. Individual
events have no photo of their own (an established, deliberate scope
decision for this build), so this is the one club-wide stand-in for the
photo the design canvas's own hero mockups call for -- shown under the
same dark gradient the hero already used, filtered to grayscale so it
never fights the club's own brand colours. Falls back to the existing
plain dark background when nothing's uploaded.

Applied to both event hero treatments: M2's own event-detail screen (the
"big black thing" this was reported against) and Home's "next up" card.
The image and gradient are separate absolutely-positioned layers behind
a z-10 content wrapper, not a filter on the card itself, so the
grayscale treatment never touches the text/buttons drawn on top of it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
2026-08-21 18:11:02 +02:00
parent c7887637ff
commit 9e581370e4
10 changed files with 177 additions and 43 deletions

View File

@@ -905,11 +905,12 @@ class ClubSettingsForm(forms.ModelForm):
class Meta:
model = Club
fields = ["name", "legal_name", "legal_address", "legal_zip_code", "legal_city", "contact_email", "website", "logo", "primary_color", "secondary_color"]
fields = ["name", "legal_name", "legal_address", "legal_zip_code", "legal_city", "contact_email", "website", "logo", "event_background", "primary_color", "secondary_color"]
widgets = {
"primary_color": forms.TextInput(attrs={"placeholder": "#1e40af"}),
"secondary_color": forms.TextInput(attrs={"placeholder": "#be185d"}),
"logo": forms.ClearableFileInput(attrs={"accept": "image/png,image/jpeg,image/gif,image/webp,image/svg+xml"}),
"event_background": forms.ClearableFileInput(attrs={"accept": "image/png,image/jpeg,image/gif,image/webp"}),
}

View File

@@ -134,6 +134,25 @@
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<div class="card-title">{% lucide "image" size=16 %} {% trans "Event background" %}</div>
<p class="text-sm text-muted">{% trans "A generic background photo for event screens in the mobile app -- individual events don't have a photo of their own, so this is the one club-wide stand-in. Shown in black & white under a dark gradient so text always stays readable. Without one, a plain dark background is used instead." %}</p>
<div class="mt-3 flex items-end gap-4">
<div>
<div class="mb-1.5 font-display text-[11px] font-bold tracking-[.12em] text-muted uppercase">{% trans "Preview" %}</div>
<div class="relative flex h-24 w-24 items-end overflow-hidden rounded-box bg-ink">
{% if club.event_background %}
<img class="absolute inset-0 h-full w-full object-cover grayscale" src="{{ club.event_background.url }}" alt="">
{% endif %}
<div class="absolute inset-0" style="background: linear-gradient(180deg, rgba(11,18,32,.15) 0%, rgba(11,18,32,.75) 55%, rgba(11,18,32,.95) 100%)"></div>
</div>
</div>
<div class="flex-1">{% form_field form.event_background %}</div>
</div>
</div>
</div>
</form>
<div class="xl:sticky xl:top-4 xl:self-start">

View File

@@ -4885,6 +4885,29 @@ class ClubSettingsPreviewTests(ManagementTestBase):
self.assertEqual(self.club.legal_zip_code, "9000")
self.assertEqual(self.club.legal_city, "Ghent")
def test_the_event_background_can_be_uploaded(self):
response = self.club_post(
"club_settings",
{
"name": "Ajax United",
"legal_name": "",
"contact_email": "",
"website": "",
"primary_color": "",
"secondary_color": "",
"event_background": make_image_file("event-bg.png"),
},
)
self.assertRedirects(response, reverse("management:club_settings"))
self.club.refresh_from_db()
self.assertTrue(self.club.event_background)
def test_the_event_background_field_is_editable(self):
response = self.club_get("club_settings")
self.assertContains(response, 'name="event_background"')
class ClubSettingsDocumentTabTests(ManagementTestBase):
"""The Email and PDF tabs on the Club identity page -- every branded