diff --git a/gaehsnitz/static/suff/favicon.svg b/gaehsnitz/static/suff/favicon.svg new file mode 100644 index 0000000..db09544 --- /dev/null +++ b/gaehsnitz/static/suff/favicon.svg @@ -0,0 +1,3 @@ + + 🍺 + diff --git a/gaehsnitz/static/suff/style.css b/gaehsnitz/static/suff/style.css new file mode 100644 index 0000000..fa430d4 --- /dev/null +++ b/gaehsnitz/static/suff/style.css @@ -0,0 +1,379 @@ +*, *::before, *::after { + box-sizing: border-box; +} + +html, body, h1, h2, p, form, label, input, button { + margin: 0; + padding: 0; + border: none; +} + +html, body { + width: 100%; + min-height: 100%; + font-size: 16px; + font-family: system-ui, -apple-system, sans-serif; +} + +body { + background-color: #161616; + color: #EEEEEE; + padding: 24px 16px 48px; + display: flex; + flex-direction: column; + align-items: center; +} + +main { + width: 100%; + max-width: 480px; + display: flex; + flex-direction: column; + gap: 18px; +} + +.site-name { + text-align: center; + color: #885522; + font-size: 0.95rem; + letter-spacing: 0.05em; + margin-bottom: -4px; +} + +h1 { + font-size: 2.4rem; + font-weight: bold; + color: #EE9933; + text-shadow: 0 0 16px #CC6611; + text-align: center; + margin-bottom: 8px; +} + +h2 { + font-size: 1.3rem; + font-weight: normal; + color: #FFCC77; +} + +p { + line-height: 1.5rem; + color: #DDDDDD; +} + +a { + color: #EE9933; + text-decoration: none; +} + +a:hover, a:focus { + color: #EEEEEE; +} + +.error { + color: #EE6622; + font-weight: bold; +} + +form { + display: flex; + flex-direction: column; + gap: 20px; +} + +label { + display: flex; + flex-direction: column; + gap: 6px; + color: #FFCC77; + font-size: 0.95rem; +} + +input[type="text"] { + background-color: rgba(80, 40, 10, 0.4); + color: #EEEEEE; + border: 2px solid #885522; + border-radius: 6px; + padding: 14px 12px; + font-size: 1.1rem; + width: 100%; +} + +input[type="text"]:focus { + outline: none; + border-color: #EE9933; +} + +button { + background-color: #EE9933; + color: #161616; + font-size: 1.1rem; + font-weight: bold; + padding: 14px 20px; + border-radius: 6px; + cursor: pointer; + width: 100%; + min-height: 52px; +} + +button:hover, button:focus { + background-color: #FFCC77; +} + +button { + transition: transform 80ms ease, background-color 100ms ease; +} + +button:active { + transform: scale(0.96); +} + +.muted { + font-size: 0.9rem; + color: #AAAAAA; + text-align: center; +} + +.muted-left { + font-size: 0.9rem; + color: #AAAAAA; +} + +h3 { + font-size: 1.1rem; + font-weight: normal; + color: #FFCC77; + margin-bottom: 10px; +} + +section { + display: flex; + flex-direction: column; + gap: 10px; +} + +.total-box { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 18px; + background-color: rgba(80, 40, 10, 0.4); + border: 2px solid #885522; + border-radius: 8px; +} + +.total-label { + color: #FFCC77; + font-size: 0.95rem; +} + +.total-value { + color: #EE9933; + font-size: 2rem; + font-weight: bold; + text-shadow: 0 0 12px #CC6611; +} + +.drink-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 12px; +} + +.drink-grid form { + margin: 0; +} + +.drink-btn { + width: 100%; + aspect-ratio: 4 / 3; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 4px; + padding: 12px 8px; + line-height: 1.2; +} + +.drink-plus { + font-size: 1.6rem; + font-weight: bold; +} + +.drink-name { + font-size: 1rem; + font-weight: bold; + text-align: center; + word-break: break-word; +} + +.drink-price { + font-size: 0.85rem; + font-weight: normal; + opacity: 0.8; +} + +.history { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 2px; +} + +.history li { + display: grid; + grid-template-columns: auto 1fr auto; + gap: 10px; + padding: 10px 12px; + background-color: rgba(80, 40, 10, 0.2); + border-radius: 4px; + font-size: 0.95rem; +} + +.history li:nth-child(odd) { + background-color: rgba(80, 40, 10, 0.35); +} + +.hist-when { + color: #FFCC77; + white-space: nowrap; +} + +.hist-price { + color: #DDDDDD; + white-space: nowrap; +} + +.btn-secondary { + background-color: transparent; + color: #885522; + border: 2px solid #885522; +} + +.btn-secondary:hover, .btn-secondary:focus { + background-color: rgba(80, 40, 10, 0.4); + color: #FFCC77; +} + +.logout-form { + margin-top: 24px; +} + +.toast { + background-color: rgba(238, 153, 51, 0.15); + border: 2px solid #EE9933; + color: #FFCC77; + padding: 12px 16px; + border-radius: 6px; + text-align: center; + font-weight: bold; + overflow: hidden; + max-height: 200px; + animation: toast-in 200ms ease-out, toast-collapse 800ms ease-in-out 5s forwards; +} + +@keyframes toast-in { + from { opacity: 0; transform: translateY(-8px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes toast-collapse { + 0% { + opacity: 1; + max-height: 200px; + padding-top: 12px; + padding-bottom: 12px; + margin-bottom: 0; + border-width: 2px; + } + 40% { + opacity: 0; + max-height: 200px; + padding-top: 12px; + padding-bottom: 12px; + border-width: 2px; + } + 100% { + opacity: 0; + max-height: 0; + padding-top: 0; + padding-bottom: 0; + margin-bottom: calc(-1 * var(--gap, 18px)); + border-width: 0; + } +} + +.day-group { + margin-bottom: 14px; +} + +.day-heading { + font-size: 0.95rem; + font-weight: bold; + color: #EE9933; + margin: 0 0 6px 4px; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.empty-state { + text-align: center; + padding: 24px 12px; + background-color: rgba(80, 40, 10, 0.15); + border: 1px dashed #885522; + border-radius: 8px; + display: flex; + flex-direction: column; + gap: 8px; +} + +.empty-emoji { + font-size: 2.5rem; + margin: 0 0 8px; + line-height: 1; +} + +.empty-state p { + margin: 0; +} + +.link-row { + display: flex; + flex-direction: column; + gap: 12px; + margin-top: 8px; +} + +.link-btn { + display: block; + text-align: center; + background-color: #EE9933; + color: #161616; + font-weight: bold; + padding: 14px 20px; + border-radius: 6px; + transition: transform 80ms ease, background-color 100ms ease; +} + +.link-btn:hover, .link-btn:focus { + background-color: #FFCC77; + color: #161616; +} + +.link-btn:active { + transform: scale(0.96); +} + +.link-btn-secondary { + background-color: transparent; + color: #885522; + border: 2px solid #885522; +} + +.link-btn-secondary:hover, .link-btn-secondary:focus { + background-color: rgba(80, 40, 10, 0.4); + color: #FFCC77; +} diff --git a/gaehsnitz/suff.py b/gaehsnitz/suff.py index 000d86c..7fe5a28 100644 --- a/gaehsnitz/suff.py +++ b/gaehsnitz/suff.py @@ -166,12 +166,20 @@ def pin_view(request): def me_view(request): phase = _require_open(request) drinks = Drink.objects.order_by("name") if phase == "booking" else Drink.objects.none() + booked_drink = None + booked_id = request.GET.get("booked") + if booked_id: + try: + booked_drink = Drink.objects.get(pk=int(booked_id)) + except (Drink.DoesNotExist, TypeError, ValueError): + booked_drink = None context = _tab_context(request.user) context.update( { "phase": phase, "drinks": drinks, "current_day": _current_festival_day(), + "booked_drink": booked_drink, } ) return render(request, "suff/me.html", context) @@ -197,7 +205,7 @@ def book_view(request): day=_current_festival_day(), for_free=False, ) - return HttpResponseRedirect(reverse("suff:me")) + return HttpResponseRedirect(f"{reverse('suff:me')}?booked={drink.id}") @require_http_methods(["POST"]) diff --git a/gaehsnitz/templates/suff/base.html b/gaehsnitz/templates/suff/base.html index 7cb03a9..8410adc 100644 --- a/gaehsnitz/templates/suff/base.html +++ b/gaehsnitz/templates/suff/base.html @@ -1,12 +1,18 @@ +{% load static %} Suff - Gähsnitz Open Air + + -

Suff

- {% block content %}{% endblock %} +
+

Gähsnitz Open Air

+

Suff

+ {% block content %}{% endblock %} +
diff --git a/gaehsnitz/templates/suff/me.html b/gaehsnitz/templates/suff/me.html index 625ba1d..738af69 100644 --- a/gaehsnitz/templates/suff/me.html +++ b/gaehsnitz/templates/suff/me.html @@ -7,44 +7,67 @@

Festival vorbei. Buchungen geschlossen, nur noch Anzeige.

{% endif %} -

Deine Rechnung: {{ total|floatformat:2 }} €

- -

Bisher gebucht

-{% if consumption_list %} - -{% else %} -

Noch nichts gebucht.

+{% if booked_drink %} +
+ Gebucht: +1 {{ booked_drink.name }} +
{% endif %} +
+ Deine Rechnung + {{ total|floatformat:2 }} € +
+ {% if phase == "booking" %} -

Neues Getränk buchen

-

Heutiger Tag: {{ current_day }}

- {% for drink in drinks %} -
- {% csrf_token %} - - -
- {% endfor %} +
+

Neues Getränk buchen

+
+ {% for drink in drinks %} +
+ {% csrf_token %} + + +
+ {% endfor %} +
+
{% endif %} -
-
+
+

Bisher gebucht

+ {% if consumption_list %} + {% regroup consumption_list by get_day_display as day_groups %} + {% for group in day_groups %} +
+

{{ group.grouper }}

+
    + {% for c in group.list %} +
  • + {% if c.created_at %}{{ c.created_at|date:"H:i" }}{% else %}—{% endif %} + {{ c.drink.name }} + + {% if c.for_free %}gratis{% else %}{{ c.drink.sale_price_per_bottle|floatformat:2 }} €{% endif %} + +
  • + {% endfor %} +
+
+ {% endfor %} + {% else %} +
+

🍺

+

Noch nichts gebucht.

+ {% if phase == "booking" %}

Tipp dich rein, sobald du was trinkst!

{% endif %} +
+ {% endif %} +
+ + {% csrf_token %} - +
{% endblock %} diff --git a/gaehsnitz/templates/suff/name.html b/gaehsnitz/templates/suff/name.html index 9abca9a..f4ccd00 100644 --- a/gaehsnitz/templates/suff/name.html +++ b/gaehsnitz/templates/suff/name.html @@ -7,12 +7,12 @@ entfernt (z.B. wird aus "Flo Hä!" → "flo-ha"). Merk dir den Namen so, wie er hier nach dem Anlegen angezeigt wird.

-{% if error %}

{{ error }}

{% endif %} +{% if error %}

{{ error }}

{% endif %}
{% csrf_token %}
diff --git a/gaehsnitz/templates/suff/no_pin.html b/gaehsnitz/templates/suff/no_pin.html index 7016642..4dc171e 100644 --- a/gaehsnitz/templates/suff/no_pin.html +++ b/gaehsnitz/templates/suff/no_pin.html @@ -13,9 +13,8 @@

Bitte einen Admin bitten, die PIN ĂĽber das Admin-Panel zu setzen.

-

- ZurĂĽck - · - Admin-Panel -

+ {% endblock %} diff --git a/suff.md b/suff.md index 29bedc8..b1f4175 100644 --- a/suff.md +++ b/suff.md @@ -40,9 +40,25 @@ Self-service drink tab for festival attendees. Lives at `/suff/`. Plain Django, - `gaehsnitz/admin.py` — `SetPinForm` + `set_pin_view` - `gaehsnitz/templates/suff/{base,name,pin,no_pin,me}.html` - `gaehsnitz/templates/admin/gaehsnitz/user/set_pin.html` +- `gaehsnitz/static/suff/{style.css,favicon.svg}` - `gaehsnitz/migrations/0003_consumption_created_at_user_pin.py` - Edits: `gaehsnitzproject/settings.py`, `gaehsnitzproject/urls.py`, `gaehsnitz/models.py` -## Next step +## Frontend -Mobile-first styling. Currently zero CSS. Big tap targets for drink buttons, sticky/large running total, dark-mode friendly for outdoor evening use. Visual feedback after booking, day-grouped history, friendlier empty state, bigger PIN input. Decide whether `/suff/` integrates into the GOA site header or stays a separate microsite. +Mobile-first styled. Dark theme matching GOA palette (`#161616` bg, `#EE9933`/`#FFCC77` amber accents, `#885522` brown borders). Standalone microsite — no nav to main GOA page. + +- Landing/login: GOA subhead + big "Suff" wordmark, `name` and `pin` forms with stacked label/input, large tap targets +- `me` page: 2-col drink button grid (4:3 aspect), stacked +1 / name / price; bordered total box; day-grouped history with zebra rows; emoji empty-state +- Booking confirmation: amber toast, 5s display, then 800ms collapse animation (pure CSS, no JS) +- `:active` scale(0.96) feedback on buttons + link-buttons +- `no_pin.html` link-buttons styled (primary + secondary) +- SVG favicon (🍺) + +## Further ideas + +- Color-code drink buttons (per-drink accent border or bg — Bier amber, Wasser blue, etc.) for fast visual recognition in dim light +- Drink icons/emoji per type +- Style phase pages (`before` / `closed` if non-404) +- PWA manifest for add-to-homescreen +- Donation/free-drink flow if needed (currently admin-only via `for_free`)