Compare commits

...

110 Commits

Author SHA1 Message Date
nik8
8f4b5d581d Merge branch '54-filter-fur-library' into 'master'
Resolve "Filter für library"

Closes #54

See merge request enrichment-2024/qivip!21
2025-03-22 11:59:25 +00:00
nik8
809d857e94 Swiper_package 2025-03-22 12:58:59 +01:00
Frank Poetzsch-Heffter
98142c91f3 Pfad für tailwind-Dateien angepasst 2025-03-22 11:36:25 +00:00
nik8
93b2675b9e Zwischenstand 2025-03-22 12:21:39 +01:00
nik8
157daae740 struktur_library 2025-03-20 21:34:17 +01:00
nik8
f6459965e0 Suchfunktion_update 2025-03-18 17:22:42 +01:00
nik8
a3331543f2 Filter_library 2025-03-17 18:28:12 +01:00
nik8
9d71f2bbf3 Merge branch '50-in-der-handy-ansicht-ist-qivip-zu-dicht-an-dem-text-darunter-home-seite' into 'master'
Resolve "In der Handy Ansicht ist "qivip" zu dicht an dem Text darunter (Home Seite)"

Closes #50

See merge request enrichment-2024/qivip!20
2025-03-17 12:28:07 +00:00
nik8
8cceecf1bd Abstand_qivip_slogan 2025-03-17 13:27:18 +01:00
nik8
c330c4bc70 Merge branch '49-beim-speichern-eines-neuen-quiz-richtig-weiterfuhrung-nicht-das-man-zweilmal-auf-speichern' into 'master'
Resolve "Beim Speichern eines neuen Quiz richtig weiterführung, nicht das man zweilmal auf speichern drücken muss..."

Closes #49

See merge request enrichment-2024/qivip!19
2025-03-17 12:14:18 +00:00
nik8
26bf2a2c55 unnötige_Zeile_entfernt 2025-03-17 13:13:47 +01:00
nik8
86958c6d16 Weiterleitung_nach_Quiz_erstellen_gefixt 2025-03-17 13:12:04 +01:00
nik8
83a3d1584a Merge branch '47-weiterleitung-nach-registrierung-fixen' into 'master'
Resolve "Weiterleitung nach Registrierung fixen"

Closes #47

See merge request enrichment-2024/qivip!18
2025-03-17 11:41:31 +00:00
nik8
a07c66ad1a Weiterleitung_Registrierung_gefixt_autom_Login_nach_Regestrierung 2025-03-17 12:39:20 +01:00
nik8
956e69b56f Moderieren_link_zu_library 2025-03-16 18:17:16 +01:00
nik8
526e486fd3 Text in Box 2025-03-16 17:53:48 +01:00
juhnsa
b149cbcfee Components verfuegbar machen 2025-03-16 17:49:49 +01:00
nik8
12c5cec7a1 Merge branch '51-im-impressum-einen-kasten-machen-schoner' into 'master'
Resolve "Im Impressum einen Kasten machen (schöner)"

Closes #51

See merge request enrichment-2024/qivip!17
2025-03-16 16:41:59 +00:00
nik8
e0bfda0dee Impressum_Datenschutzerklärung_ansprechend 2025-03-16 17:41:11 +01:00
nik8
23675bc69e Merge branch '36-verwalten-mit-konto-mit-der-bibliothek-verbinden' into 'master'
Resolve "Verwalten mit Erstellbutton ersetzen und Verknüpfung herstellen"

Closes #36

See merge request enrichment-2024/qivip!16
2025-03-16 16:04:05 +00:00
nik8
94659791e6 fix#36 2025-03-16 17:03:26 +01:00
nik8
17cc0af02d Merge branch '40-offentliche-bibliothek' into 'master'
Resolve "Öffentliche Bibliothek"

Closes #40

See merge request enrichment-2024/qivip!14
2025-03-16 15:30:41 +00:00
nik8
f28878e1b0 small_changes_Datenbank 2025-03-16 16:27:50 +01:00
nik8
2a8b0b813c öffentliche_Library_und_Datenbankchanges 2025-03-16 14:51:27 +01:00
Juhn-Vitus Saß
f18ed83f9d Merge branch '41-quiz-id-updaten-wenn-participant-ein-anderes-quiz-beitritt' into 'master'
Resolve "Quiz ID updaten wenn Participant einem anderen Quiz beitritt"

Closes #41

See merge request enrichment-2024/qivip!13
2025-03-16 12:18:35 +00:00
juhnsa
d1cca38595 Quiz Titel anzeigen 2025-03-16 11:54:00 +01:00
juhnsa
1f1344522f Quiz Instanz von Teilnehmer updaten, wenn dieser einem anderen Quiz beitritt 2025-03-16 09:55:36 +01:00
Juhn-Vitus Saß
7cad46063e Merge branch '31-projektordner-core-umbenennen-in-z-b-django-2' into 'master'
Resolve "Projektordner "core" umbenennen in z.B. Django"

Closes #31

See merge request enrichment-2024/qivip!12
2025-03-16 08:23:23 +00:00
juhnsa
55750bcf1f Django Projektordner von 'core' in ''django' umbenennen 2025-03-15 21:48:35 +01:00
Juhn-Vitus Saß
230944cbcd Merge branch '35-seite-fur-namen-und-avatar-auswahl-erstellen' into 'master'
Resolve "Seite für Namen und Avatar Auswahl erstellen"

Closes #35

See merge request enrichment-2024/qivip!11
2025-03-15 20:16:28 +00:00
Juhn-Vitus Saß
2f47ff8523 Merge branch '34-der-code-soll-nur-aus-zahlen-bestehen' into 'master'
Resolve "Der Code soll nur aus Zahlen bestehen!"

Closes #34

See merge request enrichment-2024/qivip!10
2025-03-15 20:15:57 +00:00
juhnsa
3ef7d61da6 Teilnehmlogik grundlegend fertiggestellt 2025-03-15 20:30:49 +01:00
juhnsa
7fe29c5bd4 Warnmeldung beheben 2025-03-15 19:41:13 +01:00
Juhn-Vitus Saß
5775e53bb8 Fix template name 2025-03-15 16:30:31 +01:00
Juhn-Vitus Saß
57682d4607 Fix routing issue 2025-03-15 16:30:09 +01:00
ben8
915552ac4a fix#37 2025-03-15 16:18:28 +01:00
861e89ae4d Merge branch '35-seite-fur-namen-und-avatar-auswahl-erstellen' of edugit.org:enrichment-2024/qivip into 35-seite-fur-namen-und-avatar-auswahl-erstellen 2025-03-15 16:13:44 +01:00
97fd326561 Create initialize_participant.html 2025-03-15 16:11:35 +01:00
Juhn-Vitus Saß
34a9ab6a43 View erstellen 2025-03-15 16:10:34 +01:00
ben8
4c597473b7 Merge branch 'master' of edugit.org:enrichment-2024/qivip 2025-03-15 15:53:19 +01:00
ben8
8119f202d3 design_changes 2025-03-15 15:53:16 +01:00
6beccc772a Add initialize_participant.html 2025-03-15 15:50:41 +01:00
Frank Poetzsch-Heffter
4797a74111 almost 6 digits 2025-03-15 15:42:21 +01:00
Juhn-Vitus Saß
30b5cd9731 Fix Button 2025-03-15 15:31:52 +01:00
Juhn-Vitus Saß
6b83f26fc0 Merge branch '21-host-und-spiel-app-erstellen-und-grundlegende-templates-erstellen' into 'master'
Resolve "Host und Spiel App erstellen und Grundlegende Templates erstellen"

Closes #21

See merge request enrichment-2024/qivip!9
2025-03-15 14:26:45 +00:00
Juhn-Vitus Saß
346ac69046 Join Mechanik erstellen 2025-03-15 15:25:39 +01:00
Juhn-Vitus Saß
838eaab9be Fix Code generation 2025-03-15 14:59:53 +01:00
Juhn-Vitus Saß
eba34fa2d4 Merge branch '21-host-und-spiel-app-erstellen-und-grundlegende-templates-erstellen' of edugit.org:enrichment-2024/qivip into 21-host-und-spiel-app-erstellen-und-grundlegende-templates-erstellen 2025-03-15 14:53:00 +01:00
Juhn-Vitus Saß
9f51def959 Fix Admin 2025-03-15 14:52:58 +01:00
c8bf38dd36 Structure Installed Apps 2025-03-15 14:51:36 +01:00
Juhn-Vitus Saß
7120dcd317 DB Model für Quiz Spiele 2025-03-15 14:50:57 +01:00
ben8
da419533e9 Merge branch 'master' of edugit.org:enrichment-2024/qivip 2025-03-15 14:14:43 +01:00
ben8
bd0aa651af Teilnhame_verbunden 2025-03-15 14:14:40 +01:00
Juhn-Vitus Saß
d1b11a0c51 Update Gitignore 2025-03-15 13:46:34 +01:00
Juhn-Vitus Saß
5dc849f551 Update Gitignore 2025-03-15 13:45:49 +01:00
35617470c4 Merge branch '21-teilnehmer-seite-vorberiten-mit-eingabefeld-fur-websockets' into 'master'
Resolve "Host und Spiel App erstellen und Grundlegende Templates erstellen"

Closes #21

See merge request enrichment-2024/qivip!6
2025-03-15 10:57:49 +00:00
Juhn-Vitus Saß
b6991c2cbd Update REQUIREMENTS 2025-03-15 11:31:25 +01:00
ben8
a0e31d77f2 Merge branch 'master' of edugit.org:enrichment-2024/qivip 2025-03-15 11:19:15 +01:00
ben8
434d1f63e3 Struktur 2025-03-15 11:19:12 +01:00
nik8
e3bd3c8579 Update Design 2025-03-15 11:00:29 +01:00
nik8
7e66868a7d Löschenformular_blaue_Border 2025-03-14 18:32:29 +01:00
nik8
b306321565 Design_form_fürs_Bearbeiten 2025-03-14 18:25:35 +01:00
nik8
3a099945b5 #fix_28 2025-03-14 18:18:19 +01:00
nik8
9f6c1cc33c #fix_29_Links 2025-03-14 17:31:56 +01:00
juhnsa
13cbaaa6dc Templates hinzufuegen; Spiel beitreten seite stylen 2025-03-12 20:16:00 +01:00
juhnsa
46634b1e1c Initiale Play App erstellen und integrieren 2025-03-12 19:16:08 +01:00
Juhn-Vitus Saß
3508b8e341 Merge branch '5-formular-fur-quizfragen-erstellen-uberarbeiten' into 'master'
Resolve "Formular für Quizfragen erstellen / überarbeiten"

Closes #5

See merge request enrichment-2024/qivip!5
2025-03-12 18:08:48 +00:00
juhnsa
039e3ea0cb Vergrößere Textfelder in Quiz-Fragen-Formularen
- Ändere Eingabefelder zu Textareas für bessere Benutzerfreundlichkeit
- Fragen-Textfeld auf 4 Zeilen vergrößert
- Antwort-Optionen auf 2 Zeilen vergrößert
- Behebe Probleme mit der Anzeige bestehender Fragen
2025-03-12 19:08:08 +01:00
90d7a3ea94 Changed 'static/homepage/t-style.css' to 'static/css/t-style.css' 2025-03-08 19:37:00 +01:00
c348734ff4 Merge branch '26-fix_fragen_erstellen-bearbeiten' into 'master'
Resolve "Fix_Fragen_Erstellen/Bearbeiten"

Closes #26

See merge request enrichment-2024/qivip!4
2025-03-08 17:30:32 +00:00
35da688b1e Fix Create Questins 2025-03-08 18:30:14 +01:00
Juhn-Vitus Saß
089abfc598 Update README 2025-03-08 18:20:21 +01:00
f732d8305e Merge branch '19-project_template_ordner' into 'master'
Resolve "Project_Ordner"

Closes #19

See merge request enrichment-2024/qivip!3
2025-03-08 15:23:30 +00:00
ben8
7a4caf7191 margin verändert quiz library 2025-03-08 16:21:51 +01:00
213b4cfd77 New structure 2025-03-08 16:21:40 +01:00
ben8
1f55ad8247 margin bei quiz library 2025-03-08 16:19:49 +01:00
ben8
53ffe3822a Merge branch 'master' of edugit.org:enrichment-2024/qivip 2025-03-08 16:18:16 +01:00
ben8
71d8deab5b Rahmen bei Quiz library 2025-03-08 16:18:14 +01:00
Frank Poetzsch-Heffter
2173d7f928 Readme ergänzt durch Issue - Merge Request- Merge 2025-03-08 14:56:33 +00:00
ben8
eddab4a298 text color white 2025-03-08 15:56:02 +01:00
Frank Poetzsch-Heffter
2ea2c03c67 t-style.css entferent
Merge branch 'fix_20_t-style_entfernen'
2025-03-08 15:41:13 +01:00
Frank Poetzsch-Heffter
0775710102 t-style.css entfernt 2025-03-08 15:36:42 +01:00
Juhn-Vitus Saß
ba77d72091 Merge branch 'master' of edugit.org:enrichment-2024/qivip 2025-03-08 15:36:41 +01:00
Juhn-Vitus Saß
b09eb2c7d6 Fix #5 2025-03-08 15:34:39 +01:00
ben8
47beb3a8e7 Merge branch 'master' of edugit.org:enrichment-2024/qivip 2025-03-08 15:33:28 +01:00
ben8
0eaac8c2f0 Merge branch 'master' of edugit.org:enrichment-2024/qivip 2025-03-08 15:28:15 +01:00
266cf4dbba Merge branch 'master' of edugit.org:enrichment-2024/qivip 2025-03-08 15:16:19 +01:00
5a63584a17 Blue logout button 2025-03-08 15:16:17 +01:00
nik8
37ffbebc67 Registrierfehler behoben 2025-03-08 15:14:07 +01:00
9fdbaf7b23 Remove junk 'accounts/static' 2025-03-08 15:05:32 +01:00
ben8
adb208a56a Register gefixt 2025-03-08 15:01:48 +01:00
e09410d38a Fixed Style 2025-03-08 14:59:24 +01:00
ben8
9e510fa090 Merge branch 'master' of edugit.org:enrichment-2024/qivip 2025-03-08 14:52:31 +01:00
ben8
c0ade602b5 feature #13 2025-03-08 14:52:27 +01:00
9e3317274f Merge branch 'master' of edugit.org:enrichment-2024/qivip 2025-03-08 14:48:56 +01:00
16f489c91b Footer Funktionen 2025-03-08 14:48:54 +01:00
Ben
fa260ee4ff Merge branch 'master' of edugit.org:enrichment-2024/qivip 2025-03-08 14:44:36 +01:00
Ben
cc6ace3798 Quiz Detail Seite verschönern 2025-03-08 14:36:38 +01:00
Ben
4d213db5c9 Quiz Detail Seite verschönern 2025-03-08 14:33:58 +01:00
ben8
44822b9a11 Merge branch 'master' of edugit.org:enrichment-2024/qivip 2025-03-08 13:46:52 +01:00
ben8
7a59998cff fix #7 2025-03-08 13:46:48 +01:00
nik8
83335fd07a mini Fabänderung 2025-03-08 13:44:20 +01:00
7113cc197f Add favicon 2025-03-08 13:14:59 +01:00
d8fcfeb0c0 Add favicon 2025-03-08 13:13:02 +01:00
ben8
31cc63250c Merge branch 'master' of edugit.org:enrichment-2024/qivip 2025-03-08 13:04:03 +01:00
ben8
53c2a13923 feature #4 2025-03-08 13:03:18 +01:00
4afeb40dca Merge branch '8-weiterleitung-zur-anmeldeseite-fixen' into 'master'
Resolve "Weiterleitung zur Anmeldeseite fixen"

Closes #8

See merge request enrichment-2024/qivip!2
2025-03-08 11:39:44 +00:00
27274582c5 Weiterleitung Bibliothek 2025-03-08 12:39:05 +01:00
7f4b2f2e87 Merge branch 'fix_2_Button_'Moderieren'' into 'master'
Replace button 'Eröffnen' with 'Moderieren'

See merge request enrichment-2024/qivip!1
2025-03-08 10:54:21 +00:00
d1ef6e0dd3 Replace button 'Eröffnen' with 'Moderieren' 2025-03-08 11:47:46 +01:00
125 changed files with 2046 additions and 1462 deletions

4
.gitignore vendored
View File

@@ -2,3 +2,7 @@ __pycache__
.venv
db.sqlite3
tailwindcss.exe
t-style.css
dump.rdb
media
node_modules

View File

@@ -6,6 +6,7 @@
2. [Starten des Servers](#starten-des-servers)
3. [Superuser erstellen](#superuser-erstellen)
4. [Tailwind-Nutzung](#tailwind-nutzung)
5. [Issue - Merge Request - Merge](#issue-mergerequest-merge)
## Initialisierung des Projekts
@@ -83,7 +84,11 @@ npm install tailwindcss @tailwindcss/cli
### Datei generieren
```sh
# Bei Verwendung der Binärdatei:
tailwindcss -i <INPUT-DATEI> -o <OUTPUT-DATEI> --watch --minify
# Bei Verwendung von NPM:
npx @tailwindcss/cli -i <INPUT-DATEI> -o <OUTPUT-DATEI> --watch --minify
```
- INPUT-DATEI: CSS Datei, welche die Einbindung und Konfiguration von Tailwind enthält
@@ -94,5 +99,19 @@ tailwindcss -i <INPUT-DATEI> -o <OUTPUT-DATEI> --watch --minify
**Beispiel:**
```sh
tailwindcss -i core/homepage/static/homepage/t-input.css -o core/homepage/static/homepage/t-style.css --watch --minify
# unter Linux mit Tailwind Binärdatei:
tailwindcss -i static/css/t-input.css -o static/css/t-style.css --watch --minify
# unter Windows mit NPM (in bash mit '/' statt '\'):
npx @tailwindcss/cli -i .\static\css\t-input.css -o .\static\css\t-style.css --watch --minify
```
## Issue - Merge Request - Merge
Hier der vereinbarte Arbeitsablauf:
1. Issue erstellen - das geht am einfachsten über die Webseite. Der Titel beschreibt kurz und klar, was angestrebt wird.
2. Merge Request anlegen - dadurch wird der Bransch automatisch angelegt. Dieser kann mit `git pull` geholt und mit `git checkout <branch>` bearbeitet werden.
3. Nach dem Hochladen kann der Merge in den Master durchgeführt werden.

View File

@@ -1,21 +0,0 @@
/*color vars*/
:root {
--main_bg: #1e1e1e;
--main_text_color: #d3d3d3;
--main_color: #3399cc;
--hover_main_color: #3399ccb0;
}
body {
background-color: var(--main_bg);
color: var(--main_text_color);
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}

View File

@@ -1,128 +0,0 @@
.register_link {
color:var(--main_text_color);
background-color: #2e2e2e;
}
.register_button {
color:var(--main_text_color);
background-color: #2e2e2e;
border-radius: 5px;
border:none;
margin-top: 10px;
}
.login-container {
background-color: #2e2e2e;
padding: 30px;
border-radius: 15px;
width: 400px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.5);
text-align: center;
border: 2px solid var(--main_color);
}
.login-header {
color: var(--main_text_color);
font-size: 24px;
margin-bottom: 20px;
}
.input-group {
width: 350px;
margin-bottom: 15px;
display: flex;
flex-direction: column;
align-items: center;
padding-left: 25px;
padding-right: 25px;
}
.input-group input {
width: 100%;
padding: 10px;
border: 1px solid #555;
border-radius: 15px;
background-color: #4a4a4a;
color: var(--main_text_color);
outline: none;
transition: transform 0.2s ease;
}
.input-group input:focus {
transform: scale(1.06);
border-color: var(--main_color);
}
.login-button {
width: 50%;
padding: 13px;
background-color: var(--main_color);
color: var(--main_text_color);
border: none;
border-radius: 15px;
cursor: pointer;
transition: background-color 0.3s ease;
font-size: 20px;
font-weight: bold;
margin-top: 10px;
transition: transform 0.2s ease;
}
.login-button:hover {
background-color: var(--hover_main_color);
transform: scale(1.06);
}
.login-logo {
width: 400px;
height: auto;
margin-bottom: 20px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 10px;
}
.login-logo-mobile {
width: 150px;
}
.login-container {
text-align: center;
}
/* MOBILE DEVICES */
@media (max-width: 768px) {
.login-container {
width: 250px;
padding: 10px;
}
.input-group {
width: 200px;
}
.login-logo {
display:none;
}
.login-button {
height: auto;
}
.login-form {
padding-bottom: 12px;
}
}
/* DESKTOP DEVICES*/
@media (min-width: 768px) {
.login-logo-mobile {
display: none;
}
}

View File

@@ -1,16 +0,0 @@
{% load static %}
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="{% static 'homepage/t-style.css' %}">
</head>
<body>
{% include 'homepage/partials/_nav.html' %}
{% block content %}
{% endblock %}
{% include 'homepage/partials/_footer.html' %}
</body>
</html>

View File

@@ -1,11 +0,0 @@
{% extends 'accounts/base.html'%}
{% block title %}Home{% endblock %}
{% block content %}
<div class="grid place-content-center h-120">
<h1 class="text-center m-4 text-6xl">Moin moin!</h1>
<form action="{% url 'accounts:logout' %}" method="post">
{% csrf_token %}
<button class="bg-red-600 text-white p-3 rounded-full font-black" type="submit">Abmelden</button>
</form>
</div>
{% endblock %}

View File

@@ -1,12 +0,0 @@
<nav class="flex justify-between bg-blue-600 h-12 px-4 sm:px-6 lg:px-8 rounded-lg m-2 shadow-md">
<div class="flex items-center">
<h2 class="text-xl font-bold text-white hover:scale-110 transition duration-200"><a href="#">qivip</a></h2>
</div>
<div class="flex items-center">
<ul class="flex space-x-4 qp-nav-list">
<li><a href="#">Start</a></li>
<li><a href="#">Bibliothek</a></li>
<li><a href="{% url 'login' %}">Konto</a></li> <!--Hier ist der Link erstmal auf das alte Login-Formular gesetzt! Zum Testen!-->
</ul>
</div>
</nav>

View File

@@ -1,997 +0,0 @@
/*! tailwindcss v4.0.9 | MIT License | https://tailwindcss.com */
@layer theme, base, components, utilities;
@layer theme {
:root, :host {
--font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
monospace;
--color-red-500: oklch(0.637 0.237 25.331);
--color-red-600: oklch(0.577 0.245 27.325);
--color-yellow-500: oklch(0.795 0.184 86.047);
--color-green-500: oklch(0.723 0.219 149.579);
--color-blue-100: oklch(0.932 0.032 255.585);
--color-blue-400: oklch(0.707 0.165 254.624);
--color-blue-500: oklch(0.623 0.214 259.815);
--color-blue-600: oklch(0.546 0.245 262.881);
--color-indigo-500: oklch(0.585 0.233 277.117);
--color-indigo-600: oklch(0.511 0.262 276.966);
--color-purple-500: oklch(0.627 0.265 303.9);
--color-gray-200: oklch(0.928 0.006 264.531);
--color-gray-500: oklch(0.551 0.027 264.364);
--color-gray-600: oklch(0.446 0.03 256.802);
--color-gray-900: oklch(0.21 0.034 264.665);
--color-stone-900: oklch(0.216 0.006 56.043);
--color-white: #fff;
--spacing: 0.25rem;
--breakpoint-lg: 64rem;
--container-md: 28rem;
--text-sm: 0.875rem;
--text-sm--line-height: calc(1.25 / 0.875);
--text-lg: 1.125rem;
--text-lg--line-height: calc(1.75 / 1.125);
--text-xl: 1.25rem;
--text-xl--line-height: calc(1.75 / 1.25);
--text-2xl: 1.5rem;
--text-2xl--line-height: calc(2 / 1.5);
--text-6xl: 3.75rem;
--text-6xl--line-height: 1;
--text-8xl: 6rem;
--text-8xl--line-height: 1;
--font-weight-extralight: 200;
--font-weight-light: 300;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--font-weight-extrabold: 800;
--font-weight-black: 900;
--radius-sm: 0.25rem;
--radius-md: 0.375rem;
--radius-lg: 0.5rem;
--radius-xl: 0.75rem;
--default-transition-duration: 150ms;
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
--default-font-family: var(--font-sans);
--default-font-feature-settings: var(--font-sans--font-feature-settings);
--default-font-variation-settings: var(--font-sans--font-variation-settings);
--default-mono-font-family: var(--font-mono);
--default-mono-font-feature-settings: var(--font-mono--font-feature-settings);
--default-mono-font-variation-settings: var(--font-mono--font-variation-settings);
}
}
@layer base {
*, ::after, ::before, ::backdrop, ::file-selector-button {
box-sizing: border-box;
margin: 0;
padding: 0;
border: 0 solid;
}
html, :host {
line-height: 1.5;
-webkit-text-size-adjust: 100%;
tab-size: 4;
font-family: var( --default-font-family, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji' );
font-feature-settings: var(--default-font-feature-settings, normal);
font-variation-settings: var(--default-font-variation-settings, normal);
-webkit-tap-highlight-color: transparent;
}
body {
line-height: inherit;
}
hr {
height: 0;
color: inherit;
border-top-width: 1px;
}
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
h1, h2, h3, h4, h5, h6 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
-webkit-text-decoration: inherit;
text-decoration: inherit;
}
b, strong {
font-weight: bolder;
}
code, kbd, samp, pre {
font-family: var( --default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace );
font-feature-settings: var(--default-mono-font-feature-settings, normal);
font-variation-settings: var(--default-mono-font-variation-settings, normal);
font-size: 1em;
}
small {
font-size: 80%;
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
table {
text-indent: 0;
border-color: inherit;
border-collapse: collapse;
}
:-moz-focusring {
outline: auto;
}
progress {
vertical-align: baseline;
}
summary {
display: list-item;
}
ol, ul, menu {
list-style: none;
}
img, svg, video, canvas, audio, iframe, embed, object {
display: block;
vertical-align: middle;
}
img, video {
max-width: 100%;
height: auto;
}
button, input, select, optgroup, textarea, ::file-selector-button {
font: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
color: inherit;
border-radius: 0;
background-color: transparent;
opacity: 1;
}
:where(select:is([multiple], [size])) optgroup {
font-weight: bolder;
}
:where(select:is([multiple], [size])) optgroup option {
padding-inline-start: 20px;
}
::file-selector-button {
margin-inline-end: 4px;
}
::placeholder {
opacity: 1;
color: color-mix(in oklab, currentColor 50%, transparent);
}
textarea {
resize: vertical;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-date-and-time-value {
min-height: 1lh;
text-align: inherit;
}
::-webkit-datetime-edit {
display: inline-flex;
}
::-webkit-datetime-edit-fields-wrapper {
padding: 0;
}
::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {
padding-block: 0;
}
:-moz-ui-invalid {
box-shadow: none;
}
button, input:where([type='button'], [type='reset'], [type='submit']), ::file-selector-button {
appearance: button;
}
::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
height: auto;
}
[hidden]:where(:not([hidden='until-found'])) {
display: none !important;
}
}
@layer utilities {
.absolute {
position: absolute;
}
.static {
position: static;
}
.top-0 {
top: calc(var(--spacing) * 0);
}
.right-0 {
right: calc(var(--spacing) * 0);
}
.bottom-0 {
bottom: calc(var(--spacing) * 0);
}
.-z-1 {
z-index: calc(1 * -1);
}
.-z-50 {
z-index: calc(50 * -1);
}
.z-40 {
z-index: 40;
}
.m-0 {
margin: calc(var(--spacing) * 0);
}
.m-0\.75 {
margin: calc(var(--spacing) * 0.75);
}
.m-2 {
margin: calc(var(--spacing) * 2);
}
.m-4 {
margin: calc(var(--spacing) * 4);
}
.mx-auto {
margin-inline: auto;
}
.mt-1 {
margin-top: calc(var(--spacing) * 1);
}
.mt-2 {
margin-top: calc(var(--spacing) * 2);
}
.mt-3 {
margin-top: calc(var(--spacing) * 3);
}
.mt-4 {
margin-top: calc(var(--spacing) * 4);
}
.mt-8 {
margin-top: calc(var(--spacing) * 8);
}
.mr-0\.75 {
margin-right: calc(var(--spacing) * 0.75);
}
.mb-6 {
margin-bottom: calc(var(--spacing) * 6);
}
.ml-0\.75 {
margin-left: calc(var(--spacing) * 0.75);
}
.block {
display: block;
}
.flex {
display: flex;
}
.grid {
display: grid;
}
.hidden {
display: none;
}
.h-12 {
height: calc(var(--spacing) * 12);
}
.h-20 {
height: calc(var(--spacing) * 20);
}
.h-120 {
height: calc(var(--spacing) * 120);
}
.h-150 {
height: calc(var(--spacing) * 150);
}
.h-full {
height: 100%;
}
.w-full {
width: 100%;
}
.max-w-screen-lg {
max-width: var(--breakpoint-lg);
}
.grid-cols-1 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.place-content-center {
place-content: center;
}
.place-items-stretch {
place-items: stretch;
}
.items-center {
align-items: center;
}
.justify-around {
justify-content: space-around;
}
.justify-between {
justify-content: space-between;
}
.justify-center {
justify-content: center;
}
.justify-end {
justify-content: flex-end;
}
.gap-2 {
gap: calc(var(--spacing) * 2);
}
.gap-4 {
gap: calc(var(--spacing) * 4);
}
.space-y-4 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));
margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)));
}
}
.space-x-2 {
:where(& > :not(:last-child)) {
--tw-space-x-reverse: 0;
margin-inline-start: calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));
margin-inline-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)));
}
}
.space-x-4 {
:where(& > :not(:last-child)) {
--tw-space-x-reverse: 0;
margin-inline-start: calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));
margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)));
}
}
.overflow-hidden {
overflow: hidden;
}
.overflow-x-auto {
overflow-x: auto;
}
.rounded-full {
border-radius: calc(infinity * 1px);
}
.rounded-lg {
border-radius: var(--radius-lg);
}
.rounded-md {
border-radius: var(--radius-md);
}
.rounded-sm {
border-radius: var(--radius-sm);
}
.rounded-xl {
border-radius: var(--radius-xl);
}
.border-2 {
border-style: var(--tw-border-style);
border-width: 2px;
}
.border-3 {
border-style: var(--tw-border-style);
border-width: 3px;
}
.border-4 {
border-style: var(--tw-border-style);
border-width: 4px;
}
.border-blue-100 {
border-color: var(--color-blue-100);
}
.border-blue-600 {
border-color: var(--color-blue-600);
}
.border-gray-500 {
border-color: var(--color-gray-500);
}
.bg-blue-100 {
background-color: var(--color-blue-100);
}
.bg-blue-400 {
background-color: var(--color-blue-400);
}
.bg-blue-500 {
background-color: var(--color-blue-500);
}
.bg-blue-600 {
background-color: var(--color-blue-600);
}
.bg-gray-200 {
background-color: var(--color-gray-200);
}
.bg-gray-500 {
background-color: var(--color-gray-500);
}
.bg-green-500 {
background-color: var(--color-green-500);
}
.bg-indigo-500 {
background-color: var(--color-indigo-500);
}
.bg-purple-500 {
background-color: var(--color-purple-500);
}
.bg-red-500 {
background-color: var(--color-red-500);
}
.bg-red-600 {
background-color: var(--color-red-600);
}
.bg-white {
background-color: var(--color-white);
}
.bg-yellow-500 {
background-color: var(--color-yellow-500);
}
.object-cover {
object-fit: cover;
}
.p-0\.75 {
padding: calc(var(--spacing) * 0.75);
}
.p-2 {
padding: calc(var(--spacing) * 2);
}
.p-3 {
padding: calc(var(--spacing) * 3);
}
.p-4 {
padding: calc(var(--spacing) * 4);
}
.px-1 {
padding-inline: calc(var(--spacing) * 1);
}
.px-4 {
padding-inline: calc(var(--spacing) * 4);
}
.py-2 {
padding-block: calc(var(--spacing) * 2);
}
.pb-4 {
padding-bottom: calc(var(--spacing) * 4);
}
.text-center {
text-align: center;
}
.text-2xl {
font-size: var(--text-2xl);
line-height: var(--tw-leading, var(--text-2xl--line-height));
}
.text-6xl {
font-size: var(--text-6xl);
line-height: var(--tw-leading, var(--text-6xl--line-height));
}
.text-lg {
font-size: var(--text-lg);
line-height: var(--tw-leading, var(--text-lg--line-height));
}
.text-sm {
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
}
.text-xl {
font-size: var(--text-xl);
line-height: var(--tw-leading, var(--text-xl--line-height));
}
.text-\[clamp\(0\.3rem\,5vw\,1rem\)\] {
font-size: clamp(0.3rem, 5vw, 1rem);
}
.text-\[clamp\(0\.6rem\,5vw\,1rem\)\] {
font-size: clamp(0.6rem, 5vw, 1rem);
}
.text-\[clamp\(0\.55rem\,5vw\,1\.5rem\)\] {
font-size: clamp(0.55rem, 5vw, 1.5rem);
}
.text-\[clamp\(0\.75rem\,5vw\,1\.5rem\)\] {
font-size: clamp(0.75rem, 5vw, 1.5rem);
}
.text-\[clamp\(0\.75rem\,5vw\,1\.25rem\)\] {
font-size: clamp(0.75rem, 5vw, 1.25rem);
}
.font-black {
--tw-font-weight: var(--font-weight-black);
font-weight: var(--font-weight-black);
}
.font-bold {
--tw-font-weight: var(--font-weight-bold);
font-weight: var(--font-weight-bold);
}
.font-extralight {
--tw-font-weight: var(--font-weight-extralight);
font-weight: var(--font-weight-extralight);
}
.font-light {
--tw-font-weight: var(--font-weight-light);
font-weight: var(--font-weight-light);
}
.font-semibold {
--tw-font-weight: var(--font-weight-semibold);
font-weight: var(--font-weight-semibold);
}
.break-words {
overflow-wrap: break-word;
}
.text-blue-600 {
color: var(--color-blue-600);
}
.text-gray-600 {
color: var(--color-gray-600);
}
.text-red-600 {
color: var(--color-red-600);
}
.text-stone-900 {
color: var(--color-stone-900);
}
.text-white {
color: var(--color-white);
}
.italic {
font-style: italic;
}
.shadow-\[0_2px_2px_rgba\(0\,0\,0\,0\.1\)\] {
--tw-shadow: 0 2px 2px var(--tw-shadow-color, rgba(0,0,0,0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.shadow-md {
--tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.filter {
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
}
.transition {
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.duration-200 {
--tw-duration: 200ms;
transition-duration: 200ms;
}
.hover\:scale-105 {
&:hover {
@media (hover: hover) {
--tw-scale-x: 105%;
--tw-scale-y: 105%;
--tw-scale-z: 105%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
}
}
.hover\:scale-110 {
&:hover {
@media (hover: hover) {
--tw-scale-x: 110%;
--tw-scale-y: 110%;
--tw-scale-z: 110%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
}
}
.focus\:scale-105 {
&:focus {
--tw-scale-x: 105%;
--tw-scale-y: 105%;
--tw-scale-z: 105%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
}
.sm\:grid-cols-3 {
@media (width >= 40rem) {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
.sm\:px-6 {
@media (width >= 40rem) {
padding-inline: calc(var(--spacing) * 6);
}
}
.sm\:text-2xl {
@media (width >= 40rem) {
font-size: var(--text-2xl);
line-height: var(--tw-leading, var(--text-2xl--line-height));
}
}
.md\:mb-8 {
@media (width >= 48rem) {
margin-bottom: calc(var(--spacing) * 8);
}
}
.md\:h-screen {
@media (width >= 48rem) {
height: 100vh;
}
}
.md\:grid-cols-2 {
@media (width >= 48rem) {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.md\:text-8xl {
@media (width >= 48rem) {
font-size: var(--text-8xl);
line-height: var(--tw-leading, var(--text-8xl--line-height));
}
}
.lg\:grid-cols-3 {
@media (width >= 64rem) {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
.lg\:px-8 {
@media (width >= 64rem) {
padding-inline: calc(var(--spacing) * 8);
}
}
.dark\:bg-gray-900 {
@media (prefers-color-scheme: dark) {
background-color: var(--color-gray-900);
}
}
}
nav div ul.qp-nav-list li {
color: var(--color-white);
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 200ms;
transition-duration: 200ms;
&:hover {
@media (hover: hover) {
--tw-scale-x: 110%;
--tw-scale-y: 110%;
--tw-scale-z: 110%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
}
&:hover {
@media (hover: hover) {
border-bottom-style: var(--tw-border-style);
border-bottom-width: 2px;
}
}
&:hover {
@media (hover: hover) {
border-color: var(--color-white);
}
}
}
a.qp-a-button {
border-radius: var(--radius-md);
padding: calc(var(--spacing) * 6);
--tw-font-weight: var(--font-weight-extrabold);
font-weight: var(--font-weight-extrabold);
--tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 300ms;
transition-duration: 300ms;
&:hover {
@media (hover: hover) {
--tw-scale-x: 105%;
--tw-scale-y: 105%;
--tw-scale-z: 105%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
}
}
.register input {
border-radius: calc(infinity * 1px);
background-color: var(--color-gray-200);
padding: calc(var(--spacing) * 3);
--tw-font-weight: var(--font-weight-black);
font-weight: var(--font-weight-black);
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 200ms;
transition-duration: 200ms;
&:focus {
--tw-scale-x: 105%;
--tw-scale-y: 105%;
--tw-scale-z: 105%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
}
.input-group {
margin-inline: auto;
display: flex;
max-width: var(--container-md);
flex-direction: column;
gap: calc(var(--spacing) * 4);
border-radius: var(--radius-xl);
background-color: var(--color-white);
padding: calc(var(--spacing) * 6);
--tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.input-group form {
display: flex;
flex-direction: column;
gap: calc(var(--spacing) * 4);
}
.input-group form p {
display: flex;
flex-direction: column;
gap: calc(var(--spacing) * 2);
}
.input-group form p label {
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
--tw-font-weight: var(--font-weight-semibold);
font-weight: var(--font-weight-semibold);
color: var(--color-gray-900);
}
.input-group form p input {
border-radius: calc(infinity * 1px);
background-color: var(--color-gray-200);
padding: calc(var(--spacing) * 3);
--tw-font-weight: var(--font-weight-black);
font-weight: var(--font-weight-black);
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 200ms;
transition-duration: 200ms;
&:focus {
--tw-scale-x: 105%;
--tw-scale-y: 105%;
--tw-scale-z: 105%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
&:focus {
background-color: var(--color-blue-100);
}
&:focus {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
&:focus {
--tw-ring-color: var(--color-blue-400);
}
&:focus {
--tw-outline-style: none;
outline-style: none;
}
}
.input-group form p select {
border-radius: var(--radius-md);
background-color: var(--color-gray-200);
padding: calc(var(--spacing) * 3);
--tw-font-weight: var(--font-weight-black);
font-weight: var(--font-weight-black);
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 200ms;
transition-duration: 200ms;
&:focus {
--tw-scale-x: 105%;
--tw-scale-y: 105%;
--tw-scale-z: 105%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
&:focus {
background-color: var(--color-blue-100);
}
&:focus {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
&:focus {
--tw-ring-color: var(--color-blue-400);
}
&:focus {
--tw-outline-style: none;
outline-style: none;
}
}
.input-group form p textarea {
min-height: 100px;
border-radius: var(--radius-xl);
background-color: var(--color-gray-200);
padding: calc(var(--spacing) * 3);
--tw-font-weight: var(--font-weight-black);
font-weight: var(--font-weight-black);
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 200ms;
transition-duration: 200ms;
&:focus {
--tw-scale-x: 105%;
--tw-scale-y: 105%;
--tw-scale-z: 105%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
&:focus {
background-color: var(--color-blue-100);
}
&:focus {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
&:focus {
--tw-ring-color: var(--color-blue-400);
}
&:focus {
--tw-outline-style: none;
outline-style: none;
}
}
.input-group form p button {
border-radius: calc(infinity * 1px);
background-color: var(--color-indigo-500);
padding: calc(var(--spacing) * 3);
--tw-font-weight: var(--font-weight-black);
font-weight: var(--font-weight-black);
color: var(--color-white);
--tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 200ms;
transition-duration: 200ms;
&:hover {
@media (hover: hover) {
background-color: var(--color-indigo-600);
}
}
&:hover {
@media (hover: hover) {
--tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
}
&:focus {
--tw-scale-x: 105%;
--tw-scale-y: 105%;
--tw-scale-z: 105%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
}
@property --tw-space-y-reverse {
syntax: "*";
inherits: false;
initial-value: 0;
}
@property --tw-space-x-reverse {
syntax: "*";
inherits: false;
initial-value: 0;
}
@property --tw-border-style {
syntax: "*";
inherits: false;
initial-value: solid;
}
@property --tw-font-weight {
syntax: "*";
inherits: false;
}
@property --tw-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-inset-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-inset-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-ring-color {
syntax: "*";
inherits: false;
}
@property --tw-ring-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-inset-ring-color {
syntax: "*";
inherits: false;
}
@property --tw-inset-ring-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-ring-inset {
syntax: "*";
inherits: false;
}
@property --tw-ring-offset-width {
syntax: "<length>";
inherits: false;
initial-value: 0px;
}
@property --tw-ring-offset-color {
syntax: "*";
inherits: false;
initial-value: #fff;
}
@property --tw-ring-offset-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-blur {
syntax: "*";
inherits: false;
}
@property --tw-brightness {
syntax: "*";
inherits: false;
}
@property --tw-contrast {
syntax: "*";
inherits: false;
}
@property --tw-grayscale {
syntax: "*";
inherits: false;
}
@property --tw-hue-rotate {
syntax: "*";
inherits: false;
}
@property --tw-invert {
syntax: "*";
inherits: false;
}
@property --tw-opacity {
syntax: "*";
inherits: false;
}
@property --tw-saturate {
syntax: "*";
inherits: false;
}
@property --tw-sepia {
syntax: "*";
inherits: false;
}
@property --tw-drop-shadow {
syntax: "*";
inherits: false;
}
@property --tw-duration {
syntax: "*";
inherits: false;
}
@property --tw-scale-x {
syntax: "*";
inherits: false;
initial-value: 1;
}
@property --tw-scale-y {
syntax: "*";
inherits: false;
initial-value: 1;
}
@property --tw-scale-z {
syntax: "*";
inherits: false;
initial-value: 1;
}

View File

@@ -1,15 +0,0 @@
{% load static %}
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'homepage/t-style.css' %}">
<title>qivip</title>
</head>
<body>
{% include 'homepage/partials/_nav.html' %}
{% block content%}{% endblock %}
{% include 'homepage/partials/_footer.html' %}
</body>
</html>

View File

@@ -1,15 +0,0 @@
{% extends 'homepage/base.html' %}
{% block content %}
<div class="grid place-content-center md:h-screen h-150 w-full -z-50 top-0 p-4">
<div class="text-center">
<h2 class="font-bold md:text-8xl text-6xl md:mb-8 text-blue-600">qivip</h2>
<h3 class="italic font-extralight sm:text-2xl text-md">Interaktives Lernen neu definiert.</h3>
</div>
<div class="grid sm:grid-cols-3 place-items-stretch text-center mt-8 gap-4 p-4 border-3 bg-blue-100 border-blue-100 rounded-md">
<a class="qp-a-button bg-green-500 text-white" href="#">Teilnehmen</a>
<a class="qp-a-button bg-indigo-500 text-white" href="#">Eröffnen</a>
<a class="qp-a-button bg-purple-500 text-white" href="#">Verwalten</a>
</div>
</div>
{% endblock %}

View File

@@ -1,7 +0,0 @@
<div class="grid place-content-center gap-2 w-full mt-4">
<div class="flex space-x-2 pb-4">
<p class="text-sm font-extralight"><a href="#">Impressum</a></p>
<p class="text-sm font-extralight"><a href="#">Datenschutz</a></p>
<p class="text-sm font-extralight"><a href="#">Repository</a></p>
</div>
</div>

View File

@@ -1,15 +0,0 @@
<nav class="flex justify-between bg-blue-600 h-12 px-4 sm:px-6 lg:px-8 rounded-lg m-2 shadow-md">
<div class="flex items-center">
<h2 class="text-xl font-bold text-white hover:scale-110 transition duration-200"><a href="{% url 'homepage:home' %}">qivip</a></h2>
</div>
<div class="flex items-center">
<ul class="flex space-x-4 qp-nav-list">
<li><a href="{% url 'library:overview_quiz' %}">Bibliothek</a></li>
{% if user.is_authenticated %}
<li><a href="{% url 'accounts:home' %}">Konto</a></li>
{% else %}
<li><a href="{% url 'accounts:login' %}">Anmelden</a></li>
{% endif %}
</ul>
</div>
</nav>

View File

@@ -1,4 +0,0 @@
from django.shortcuts import render, redirect
def home(request):
return render(request, 'homepage/home.html')

View File

@@ -1,12 +0,0 @@
from django import forms
from .models import QivipQuiz, QivipQuestion
class QuizForm(forms.ModelForm):
class Meta:
model = QivipQuiz
fields = ['name', 'description', 'status', 'category', 'tags']
class QuestionForm(forms.ModelForm):
class Meta:
model = QivipQuestion
fields = ['quiz_id', 'data']

View File

@@ -1,17 +0,0 @@
{% load static %}
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'homepage/t-style.css' %}">
<title>qivip</title>
</head>
<body>
{% include 'homepage/partials/_nav.html' %}
<div class="max-w-screen-lg mx-auto">
{% block content%}{% endblock %}
</div>
{% include 'homepage/partials/_footer.html' %}
</body>
</html>

View File

@@ -1,11 +0,0 @@
{% extends 'library/base.html' %}
{% block content %}
<h1>Löschen</h1>
<p>Soll das Quiz "{{ object.name }}" wirklich gelöscht werden?</p>
<form method="post">
{% csrf_token %}
<button type="submit">Löschen</button>
<a href="{% url 'library:overview_quiz' %}">Abbrechen</a>
</form>
{% endblock %}

View File

@@ -1,19 +0,0 @@
{% extends 'library/base.html' %}
{% block content %}
<h1>{{ quiz.name }}</h1>
<p>{{ quiz.description }}</p>
<a href="{% url 'library:edit_quiz' quiz.id %}">Bearbeiten</a>
<a href="{% url 'library:delete_quiz' quiz.id %}">Löschen</a>
<h2>Fragen</h2>
<ul>
<a href="{% url 'library:new_question' %}">Neue Frage erstellen</a>
{% for question in questions %}
<li>{{ question.data }}</li>
<a href="{% url 'library:edit_question' question.id %}">Bearbeiten</a>
<a href="{% url 'library:delete_question' question.id %}">Löschen</a>
{% endfor %}
</ul>
{% endblock %}

View File

@@ -1,13 +0,0 @@
{% extends 'library/base.html' %}
{% block content %}
<h1>Formular</h1>
<h2>{{ form.name.value }}</h2>
<div class="input-group">
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Speichern</button>
</form>
</div>
{% endblock %}

View File

@@ -1,19 +0,0 @@
{% extends 'library/base.html' %}
{% block content %}
<h1>Übersicht</h1>
<div class="flex justify-end">
<a href="{% url 'library:new_quiz' %}" class="bg-blue-500 text-white px-4 py-2 rounded-md">Neues Quiz erstellen</a>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{% for quiz in quizzes %}
<div class="bg-white rounded-lg p-4 shadow-md">
<h2 class="text-lg font-bold"><a href="{% url 'library:detail_quiz' quiz.id %}">{{ quiz.name }}</a></h2>
<p class="text-sm text-gray-600">{{ quiz.description }}</p>
<a href="#">Spiel starten</a>
<a href="{% url 'library:edit_quiz' quiz.id %}">Bearbeiten</a>
<a href="{% url 'library:delete_quiz' quiz.id %}">Löschen</a>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@@ -1,99 +0,0 @@
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from .models import QivipQuiz, QivipQuestion
from .forms import QuizForm, QuestionForm
# Übersicht aller Quizze
@login_required
def overview_quiz(request):
quizzes = QivipQuiz.objects.filter(user_id=request.user)
return render(request, 'library/overview_quiz.html', {'quizzes': quizzes})
# Neues Quiz erstellen
@login_required
def new_quiz(request):
if request.method == 'POST':
form = QuizForm(request.POST)
if form.is_valid():
quiz = form.save(commit=False)
quiz.user_id = request.user
quiz.save()
form.save_m2m() # Speichert die Many-to-Many Beziehungen (Tags)
return redirect('library:edit_quiz', pk=quiz.pk)
else:
form = QuizForm()
return render(request, 'library/form.html', {'form': form})
# Quiz bearbeiten
@login_required
def edit_quiz(request, pk):
quiz = get_object_or_404(QivipQuiz, pk=pk, user_id=request.user)
if request.method == 'POST':
form = QuizForm(request.POST, instance=quiz)
if form.is_valid():
form.save()
return redirect('library:overview_quiz')
else:
form = QuizForm(instance=quiz)
return render(request, 'library/form.html', {'form': form})
# Quiz löschen
@login_required
def delete_quiz(request, pk):
quiz = get_object_or_404(QivipQuiz, pk=pk, user_id=request.user)
if request.method == 'POST':
quiz.delete()
return redirect('library:overview_quiz')
return render(request, 'library/delete_confirmation.html', {'object': quiz})
# Quiz anzeigen
@login_required
def detail_quiz(request, pk):
quiz = get_object_or_404(QivipQuiz, pk=pk, user_id=request.user)
questions = QivipQuestion.objects.filter(quiz_id=quiz)
return render(request, 'library/detail_quiz.html', {'quiz': quiz, 'questions': questions})
# Übersicht aller Fragen
@login_required
def question_list(request):
questions = QivipQuestion.objects.filter(quiz_id__user_id=request.user)
return render(request, 'library/question_list.html', {'questions': questions})
# Neue Frage erstellen
@login_required
def new_question(request):
if request.method == 'POST':
form = QuestionForm(request.POST)
if form.is_valid():
question = form.save(commit=False)
# Prüfe ob der User Zugriff auf das Quiz hat
if question.quiz_id.user_id != request.user:
return redirect('library:question_list')
question.save()
return redirect('library:edit_question', pk=question.pk)
else:
form = QuestionForm()
return render(request, 'library/form.html', {'form': form})
# Frage bearbeiten
@login_required
def edit_question(request, pk):
question = get_object_or_404(QivipQuestion, pk=pk, quiz_id__user_id=request.user)
if request.method == 'POST':
form = QuestionForm(request.POST, instance=question)
if form.is_valid():
form.save()
return redirect('library:edit_question', pk=pk)
else:
form = QuestionForm(instance=question)
return render(request, 'library/form.html', {'form': form})
# Frage löschen
@login_required
def delete_question(request, pk):
question = get_object_or_404(QivipQuestion, pk=pk, quiz_id__user_id=request.user)
if request.method == 'POST':
question.delete()
return redirect('library:detail_quiz', pk=question.quiz_id.pk)
return render(request, 'library/delete_confirmation.html', {'object': question})

View File

@@ -1,5 +1,6 @@
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.contrib.auth import login, authenticate
from .forms import RegisterForm
# Create your views here.
@@ -11,8 +12,9 @@ def register(response):
if response.method == "POST":
form = RegisterForm(response.POST)
if form.is_valid():
form.save()
return redirect("home")
user=form.save()
login(response, user) #automatischer Login nach Registrierung
return redirect("accounts:home")
else:
form = RegisterForm()

View File

@@ -35,6 +35,7 @@ INSTALLED_APPS = [
'homepage.apps.HomepageConfig',
'components.apps.ComponentsConfig',
'accounts.apps.AccountsConfig',
'play',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
@@ -58,7 +59,7 @@ ROOT_URLCONF = 'core.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@@ -120,11 +121,15 @@ USE_TZ = True
# https://docs.djangoproject.com/en/5.1/howto/static-files/
STATIC_URL = '/static/'
BASE_DIR='static' #von ben8 hinzugefügt
STATICFILES_DIRS = [BASE_DIR / 'static']
# Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
LOGIN_URL = '/login/'
LOGIN_URL = '/account/login/'
import os
MEDIA_URL = '/media/' # URL, um auf Medien-Dateien zuzugreifen
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # Speicherort für hochgeladene Dateien

View File

@@ -21,6 +21,7 @@ urlpatterns = [
path('admin/', admin.site.urls),
path('account/', include('accounts.urls')),
path('', include('homepage.urls')),
path('play/', include('components.urls')),
path('play/', include('play.urls')),
path('library/', include('library.urls')),
path('components/', include('components.urls'))
]

View File

@@ -5,4 +5,6 @@ app_name = 'homepage' # Verhindert Konflikt mit anderen Apps
urlpatterns = [
path('', views.home, name="home"),
path('impress', views.impress, name="impress"),
path('privacy', views.privacy, name="privacy"),
]

9
django/homepage/views.py Normal file
View File

@@ -0,0 +1,9 @@
from django.shortcuts import render, redirect
def home(request):
return render(request, 'homepage/home.html')
def impress(request):
return render(request, 'homepage/impress.html')
def privacy(request):
return render(request, 'homepage/privacy.html')

31
django/library/forms.py Normal file
View File

@@ -0,0 +1,31 @@
from django import forms
from .models import QivipQuiz, QivipQuestion
class QuizForm(forms.ModelForm):
class Meta:
model = QivipQuiz
fields = ['name', 'description','image', 'status', 'category', 'tags',"difficulty"]
class QuestionForm(forms.ModelForm):
class Meta:
model = QivipQuestion
fields = ['quiz_id', 'data']
from django import forms
class QuizFilterForm(forms.Form):
search = forms.CharField(required=False, label="Suche", widget=forms.TextInput(attrs={
'class': 'flex flex-grow rounded-lg p-2' ,'placeholder': 'Suche ...',
}))
min_amout_questions = forms.IntegerField(required=False, min_value=1, label="Minimale Anzahl an Fragen", widget=forms.NumberInput(attrs={
'class': 'border-2 border-gray-300 rounded-lg p-2 w-full'
}))
max_amout_questions = forms.IntegerField(required=False, min_value=1, label="Maximale Anzahl an Fragen", widget=forms.NumberInput(attrs={
'class': 'border-2 border-gray-300 rounded-lg p-2 w-full'
}))
user = forms.CharField(required=False, label="von User", widget=forms.TextInput(attrs={
'class': 'border-2 border-gray-300 rounded-lg p-2 w-full'
}))

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.3 on 2025-03-08 13:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='status',
field=models.CharField(choices=[('public', 'Öffentlich'), ('hidden', 'Versteckt'), ('private', 'Privat')], default='private', max_length=10),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.3 on 2025-03-08 13:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0002_alter_qivipquiz_status'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='status',
field=models.CharField(choices=[('public', 'Öffentlich'), ('hidden', 'Versteckt'), ('private', 'Privat')], default='Privat', max_length=10),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.3 on 2025-03-08 13:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0003_alter_qivipquiz_status'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='status',
field=models.CharField(choices=[('public', 'Öffentlich'), ('hidden', 'Versteckt'), ('private', 'Privat')], default='private', max_length=10),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.3 on 2025-03-08 13:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0004_alter_qivipquiz_status'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='status',
field=models.CharField(choices=[('public', 'Öffentlich'), ('hidden', 'Versteckt'), ('private', 'Privat')], default='public', max_length=10),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.3 on 2025-03-08 13:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0005_alter_qivipquiz_status'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='description',
field=models.TextField(blank=True, default='In dem Quiz geht es um ...', null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.3 on 2025-03-08 13:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0006_alter_qivipquiz_description'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='description',
field=models.TextField(blank=True, default='Das Quiz ist ein ... Quiz. In dem Quiz geht es um ...', null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.3 on 2025-03-08 13:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0007_alter_qivipquiz_description'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='description',
field=models.TextField(blank=True, default='Das Quiz ist ein ... Quiz. \n In dem Quiz geht es um ...', null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.3 on 2025-03-08 13:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0008_alter_qivipquiz_description'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='description',
field=models.TextField(blank=True, default='Das Quiz ist ein ... Quiz. \nIn dem Quiz geht es um ...', null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.3 on 2025-03-08 13:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0009_alter_qivipquiz_description'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='description',
field=models.TextField(blank=True, default='In dem Quiz geht es um ...', null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.3 on 2025-03-08 13:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0010_alter_qivipquiz_description'),
]
operations = [
migrations.AddField(
model_name='qivipquiz',
name='difficulty',
field=models.CharField(choices=[('public', 'Öffentlich'), ('hidden', 'Versteckt'), ('private', 'Privat')], default='public', max_length=10),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.3 on 2025-03-08 13:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0011_qivipquiz_difficulty'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='difficulty',
field=models.CharField(choices=[('public', 'Öffentlich'), ('hidden', 'Versteckt'), ('private', 'Privat')], default='not defined', max_length=10),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.3 on 2025-03-08 13:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0012_alter_qivipquiz_difficulty'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='difficulty',
field=models.CharField(choices=[('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('not defined', 'nicht gesetzt')], default='not defined', max_length=11),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.3 on 2025-03-08 13:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0013_alter_qivipquiz_difficulty'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='difficulty',
field=models.CharField(choices=[('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('not defined', 'nicht gesetzt')], default='not defined', help_text='1: niedrigste Schwierigkeit, 2: höchste Schwierigkeit', max_length=11),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.3 on 2025-03-08 13:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0014_alter_qivipquiz_difficulty'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='difficulty',
field=models.CharField(choices=[('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('not defined', 'nicht gesetzt')], default='not defined', help_text='1: niedrigste Schwierigkeit und 5: höchste Schwierigkeit', max_length=11),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-03-16 12:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0015_alter_qivipquiz_difficulty'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='status',
field=models.CharField(choices=[('öffentlich', 'Öffentlich'), ('versteckt', 'Versteckt'), ('privat', 'Privat')], default='öffentlich', max_length=10),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-03-16 12:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0016_alter_qivipquiz_status'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='difficulty',
field=models.CharField(choices=[('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('nicht gesetzt', 'nicht gesetzt')], default='nicht gesetzt', help_text='1: niedrigste Schwierigkeit und 5: höchste Schwierigkeit', max_length=13),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-03-16 13:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0017_alter_qivipquiz_difficulty'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='description',
field=models.TextField(blank=True, default='In dem Quiz geht es um ...', max_length=150, null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-03-16 13:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0018_alter_qivipquiz_description'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='description',
field=models.TextField(default='In dem Quiz geht es um ...', max_length=255, null=True),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.1.7 on 2025-03-16 13:08
import library.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0019_alter_qivipquiz_description'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='description',
field=models.TextField(default='In dem Quiz geht es um ...', max_length=255, validators=[library.models.QivipQuiz.validate_description]),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-03-16 14:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0020_alter_qivipquiz_description'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='name',
field=models.CharField(max_length=100),
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 5.1.7 on 2025-03-16 14:35
import library.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0021_alter_qivipquiz_name'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='description',
field=models.TextField(default='In dem Quiz geht es um ...', max_length=200, validators=[library.models.QivipQuiz.validate_description]),
),
migrations.AlterField(
model_name='qivipquiz',
name='name',
field=models.CharField(max_length=75),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-03-21 17:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0022_alter_qivipquiz_description_alter_qivipquiz_name'),
]
operations = [
migrations.AddField(
model_name='qivipquiz',
name='image',
field=models.ImageField(blank=True, null=True, upload_to='quiz_images/'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-03-22 10:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0023_qivipquiz_image'),
]
operations = [
migrations.AlterField(
model_name='qivipquiz',
name='image',
field=models.ImageField(blank=True, max_length=256, null=True, upload_to='quiz_images/'),
),
]

View File

@@ -2,23 +2,39 @@ from django.db import models
from django.contrib.auth.models import User
from django.utils.text import slugify
import uuid
from django.core.exceptions import ValidationError
class QivipQuiz(models.Model):
STATUS_VALUES = {
"public": "Öffentlich",
"hidden": "Versteckt",
"private": "Privat",
"öffentlich": "Öffentlich",
"versteckt": "Versteckt",
"privat": "Privat",
}
DIFFICULTY_VALUES = {
"1": "1",
"2": "2",
"3": "3",
"4": "4",
"5": "5",
"nicht gesetzt":"nicht gesetzt",
}
def validate_description(value):
if value.strip() == "In dem Quiz geht es um ...":
raise ValidationError("Bitte gib eine aussagekräftige Beschreibung ein.")
image = models.ImageField(max_length=256, upload_to='quiz_images/', blank=True, null=True) # Bild speichern in media/quiz_images/
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name='quiz')
creation_date = models.DateTimeField(auto_now_add=True)
update_date = models.DateTimeField(auto_now=True)
status = models.CharField(max_length=10, choices=STATUS_VALUES.items())
status = models.CharField(max_length=10, choices=list(STATUS_VALUES.items()), default="öffentlich")
category = models.ForeignKey('QuizCategory', related_name='quiz', on_delete=models.CASCADE)
tags = models.ManyToManyField('Tag', blank=True)
name = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
name = models.CharField(max_length=75)
description = models.TextField(validators=[validate_description],max_length=200,blank=False, default="In dem Quiz geht es um ...")
difficulty= models.CharField(max_length=13, choices=list(DIFFICULTY_VALUES.items()), default="nicht gesetzt", help_text="1: niedrigste Schwierigkeit und 5: höchste Schwierigkeit")
def __str__(self):
return self.name

View File

@@ -1,6 +1,8 @@
from django.urls import path
from . import views
from django.conf import settings
from . import views
from django.conf.urls.static import static
app_name = 'library'
urlpatterns = [
@@ -12,4 +14,6 @@ urlpatterns = [
path('question/new/', views.new_question, name='new_question'),
path('question/edit/<int:pk>/', views.edit_question, name='edit_question'),
path('question/delete/<int:pk>/', views.delete_question, name='delete_question'),
]
]# Nur für die Entwicklungsumgebung
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

388
django/library/views.py Normal file
View File

@@ -0,0 +1,388 @@
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.urls import reverse
from django.contrib import messages
from .models import QivipQuiz, QivipQuestion
from .forms import QuizForm, QuestionForm
import json
from django.core.paginator import Paginator
from .models import QivipQuiz
from .forms import QuizFilterForm
from django.db.models import Count
from django.contrib.auth.models import User
# Übersicht aller Quizze
def overview_quiz(request):
#Filter
form = QuizFilterForm(request.GET)
try:
filter_my_quizzes = QivipQuiz.objects.filter(user_id=request.user).annotate(max_amout_questions=Count('question'))
except:
filter_my_quizzes = QivipQuiz.objects.none()
filter_other_quizzes = QivipQuiz.objects.annotate(max_amout_questions=Count('question'))
if form.is_valid():
search = form.cleaned_data.get('search')
username= form.cleaned_data.get('user')
max_amout_questions= form.cleaned_data.get('max_amout_questions')
min_amout_questions= form.cleaned_data.get('min_amout_questions')
if search: # Suche nach Namen
filter_other_quizzes = filter_other_quizzes.filter(name__icontains=search)
filter_my_quizzes = filter_my_quizzes.filter(name__icontains=search)
if min_amout_questions:
filter_other_quizzes = filter_other_quizzes.filter(max_amout_questions__gte=min_amout_questions)
try:
filter_my_quizzes =filter_my_quizzes.filter(max_amout_questions__gte=min_amout_questions)
except:
filter_my_quizzes = QivipQuiz.objects.none()
if max_amout_questions:
filter_other_quizzes = filter_other_quizzes.filter(max_amout_questions__lte=max_amout_questions)
try:
filter_my_quizzes =filter_my_quizzes.filter(max_amout_questions__lte=max_amout_questions)
except:
filter_my_quizzes = QivipQuiz.objects.none()
if username:
try:
user = User.objects.get(username=username) # Benutzer anhand des Namens holen
filter_other_quizzes = filter_other_quizzes.filter(user_id=user.id)
filter_my_quizzes = filter_my_quizzes.filter(user_id=user.id) # Nach der user_id filtern
except User.DoesNotExist:
filter_other_quizzes = QivipQuiz.objects.none() # Falls User nicht existiert, leere Query zurückgeben
filter_my_quizzes = QivipQuiz.objects.none()
try:
filter_other_quizzes=filter_other_quizzes.exclude(user_id=request.user)
except:
filter_other_quizzes=filter_other_quizzes
# Anzahl der Quiz pro Seite für Swiper
context = {
'show_search': True,
'filter_my_quizzes': filter_my_quizzes,
'filter_other_quizzes': filter_other_quizzes,
'form': form,
}
return render(request, 'library/overview_quiz.html', context)
try:
filter_my_quizzes = QivipQuiz.objects.filter(user_id=request.user)
filter_other_quizzes = QivipQuiz.objects.filter(status='öffentlich').exclude(user_id=request.user).filter(question__isnull=False).distinct()
context = {
'show_search': True,
'filter_my_quizzes': filter_my_quizzes,
'filter_other_quizzes': filter_other_quizzes,
'form': form,
}
return render(request, 'library/overview_quiz.html', context)
except:
filter_other_quizzes = QivipQuiz.objects.filter(status='öffentlich').filter(question__isnull=False).distinct()
context = {
'show_search': True,
'filter_my_quizzes': None,
'filter_other_quizzes': filter_other_quizzes,
'form': form,
}
return render(request, 'library/overview_quiz.html', context)
# Neues Quiz erstellen
@login_required
def new_quiz(request):
if request.method == 'POST':
form = QuizForm(request.POST, request.FILES)
if form.is_valid():
quiz = form.save(commit=False)
quiz.user_id = request.user
quiz.save()
form.save_m2m() # Speichert die Many-to-Many Beziehungen (Tags)
return redirect('library:detail_quiz', pk=quiz.pk)
else:
form = QuizForm()
return render(request, 'library/form.html', {'form': form})
# Quiz bearbeiten
@login_required
def edit_quiz(request, pk):
quiz = get_object_or_404(QivipQuiz, pk=pk, user_id=request.user)
if request.method == 'POST':
form = QuizForm(request.POST, instance=quiz, files=request.FILES)
if form.is_valid():
form.save()
return redirect('library:detail_quiz', pk=quiz.pk)
else:
form = QuizForm(instance=quiz)
return render(request, 'library/form.html', {'form': form})
# Quiz löschen
@login_required
def delete_quiz(request, pk):
quiz = get_object_or_404(QivipQuiz, pk=pk, user_id=request.user)
if request.method == 'POST':
quiz.delete()
return redirect('library:overview_quiz')
return render(request, 'library/delete_confirmation.html', {'object': quiz})
# Quiz anzeigen
@login_required
def detail_quiz(request, pk):
quiz = get_object_or_404(QivipQuiz, pk=pk, user_id=request.user)
questions = QivipQuestion.objects.filter(quiz_id=quiz)
# Parse JSON data for each question
for question in questions:
if question.data:
question.data = json.loads(question.data)
context = {
'quiz': quiz,
'questions': questions
}
return render(request, 'library/detail_quiz.html', context)
# Übersicht aller Fragen
@login_required
def question_list(request):
questions = QivipQuestion.objects.filter(quiz_id__user_id=request.user)
# Parse JSON data for each question
for question in questions:
if question.data:
question.data = json.loads(question.data)
return render(request, 'library/question_list.html', {'questions': questions})
# Neue Frage erstellen
@login_required
def new_question(request):
question_type = request.GET.get('type')
quiz_id = request.GET.get('quiz_id')
if not quiz_id:
messages.error(request, 'Quiz ID muss angegeben werden.')
return redirect('library:overview_quiz')
try:
quiz = QivipQuiz.objects.get(pk=quiz_id, user_id=request.user)
except QivipQuiz.DoesNotExist:
messages.error(request, 'Quiz nicht gefunden oder keine Berechtigung.')
return redirect('library:overview_quiz')
if question_type not in ['multiple_choice', 'true_false']:
base_url = reverse('library:new_question')
return redirect(f'{base_url}?type=multiple_choice&quiz_id={quiz_id}')
if request.method == 'POST':
question_text = request.POST.get('question', '')
# Handle different question types
if question_type == 'true_false':
correct_answer = request.POST.get('correct_answer') == 'true'
json_data = {
'type': question_type,
'question': question_text,
'options': [
{'value': 'Wahr', 'is_correct': correct_answer},
{'value': 'Falsch', 'is_correct': not correct_answer}
]
}
elif question_type == 'multiple_choice':
options = []
i = 1
# Limit to maximum 6 options
while request.POST.get(f'option_{i}') and i <= 6:
is_correct = request.POST.get(f'correct_{i}') == '1'
options.append({
'order': i,
'value': request.POST.get(f'option_{i}'),
'is_correct': is_correct
})
i += 1
# Validate minimum 2 options
if len(options) < 2:
messages.error(request, 'Mindestens zwei Optionen müssen angegeben werden.')
return redirect(request.get_full_path())
json_data = {
'type': question_type,
'question': question_text,
'options': options
}
question = QivipQuestion()
question.data = json.dumps(json_data)
question.quiz_id = quiz
question.save()
return redirect('library:detail_quiz', pk=quiz.pk)
else:
# Initialize empty question data for new questions
if question_type == 'true_false':
question_data = {
'type': question_type,
'question': '',
'options': [
{'value': 'Wahr', 'is_correct': True},
{'value': 'Falsch', 'is_correct': False}
]
}
elif question_type == 'multiple_choice':
question_data = {
'type': question_type,
'question': '',
'options': [
{'value': '', 'is_correct': False},
{'value': '', 'is_correct': False}
]
}
template_name = f'library/question/question_{question_type}.html'
context = {
'question': question_data,
'quiz_id': quiz_id,
'quiz': quiz
}
return render(request, template_name, context)
# Frage bearbeiten
@login_required
def edit_question(request, pk):
question = get_object_or_404(QivipQuestion, pk=pk, quiz_id__user_id=request.user)
if question.quiz_id.user_id != request.user:
return redirect('library:question_list')
# Parse the existing JSON data
try:
question_data = json.loads(question.data) if question.data else {}
question_type = question_data.get('type', 'multiple_choice')
# For true/false questions, get the correct answer from the options
if question_type == 'true_false':
# Default to true if no options exist
correct_answer = True
if question_data.get('options'):
correct_answer = question_data['options'][0].get('is_correct', True)
question_data = {
'type': question_type,
'question': question_data.get('question', ''),
'correct_answer': correct_answer, # Add this for template compatibility
'options': [
{'value': 'Wahr', 'is_correct': correct_answer},
{'value': 'Falsch', 'is_correct': not correct_answer}
]
}
else:
# For multiple choice questions
question_data.setdefault('type', question_type)
question_data.setdefault('question', '')
question_data.setdefault('options', [])
except (json.JSONDecodeError, AttributeError):
# Handle invalid JSON or None data
question_type = question_data.get('type', 'multiple_choice')
if question_type == 'true_false':
question_data = {
'type': question_type,
'question': '',
'correct_answer': True, # Default to true for new questions
'options': [
{'value': 'Wahr', 'is_correct': True},
{'value': 'Falsch', 'is_correct': False}
]
}
else:
question_data = {
'type': question_type,
'question': '',
'options': []
}
if request.method == 'POST':
question_text = request.POST.get('question', '')
# Handle different question types
if question_type == 'true_false':
correct_answer = request.POST.get('correct_answer') == 'true'
json_data = {
'type': question_type,
'question': question_text,
'options': [
{'value': 'Wahr', 'is_correct': correct_answer},
{'value': 'Falsch', 'is_correct': not correct_answer}
]
}
elif question_type == 'multiple_choice':
options = []
i = 1
# Limit to maximum 6 options
while request.POST.get(f'option_{i}') and i <= 6:
options.append({
'order': i,
'value': request.POST.get(f'option_{i}'),
'is_correct': request.POST.get(f'correct_{i}') == '1'
})
i += 1
# Validate minimum 2 options
if len(options) < 2:
messages.error(request, 'Mindestens zwei Optionen müssen angegeben werden.')
return redirect(request.get_full_path())
# Validate maximum 6 options
if len(options) > 6:
messages.error(request, 'Maximal 6 Optionen sind erlaubt.')
return redirect(request.get_full_path())
json_data = {
'type': question_type,
'question': question_text,
'options': options
}
question.data = json.dumps(json_data)
question.save()
return redirect('library:detail_quiz', pk=question.quiz_id.pk)
else:
template_name = f'library/question/question_{question_type}.html'
context = {
'question': {'data': question_data}, # Wrap in data structure expected by template
'quiz_id': question.quiz_id.pk,
'quiz': question.quiz_id
}
return render(request, template_name, context)
# Frage löschen
@login_required
def delete_question(request, pk):
question = get_object_or_404(QivipQuestion, pk=pk, quiz_id__user_id=request.user)
# Parse JSON data for question
if question.data:
question.data = json.loads(question.data)
if request.method == 'POST':
question.delete()
return redirect('library:detail_quiz', pk=question.quiz_id.pk)
return render(request, 'library/delete_confirmation.html', {'object': question})

35
django/package-lock.json generated Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "django",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "django",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"swiper": "^11.2.6"
}
},
"node_modules/swiper": {
"version": "11.2.6",
"resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.6.tgz",
"integrity": "sha512-8aXpYKtjy3DjcbzZfz+/OX/GhcU5h+looA6PbAzHMZT6ESSycSp9nAjPCenczgJyslV+rUGse64LMGpWE3PX9Q==",
"funding": [
{
"type": "patreon",
"url": "https://www.patreon.com/swiperjs"
},
{
"type": "open_collective",
"url": "http://opencollective.com/swiper"
}
],
"license": "MIT",
"engines": {
"node": ">= 4.7.0"
}
}
}
}

15
django/package.json Normal file
View File

@@ -0,0 +1,15 @@
{
"name": "django",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"swiper": "^11.2.6"
}
}

0
django/play/__init__.py Normal file
View File

6
django/play/admin.py Normal file
View File

@@ -0,0 +1,6 @@
from django.contrib import admin
from .models import QuizGame, QuizGameParticipant
# Register your models here.
admin.site.register(QuizGame)
admin.site.register(QuizGameParticipant)

6
django/play/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class PlayConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'play'

View File

@@ -0,0 +1,37 @@
# Generated by Django 5.1.7 on 2025-03-15 13:48
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('library', '0015_alter_qivipquiz_difficulty'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='QuizGame',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('join_code', models.CharField(max_length=6, unique=True)),
('host_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='host_user', to=settings.AUTH_USER_MODEL)),
('quiz_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='library.qivipquiz')),
],
),
migrations.CreateModel(
name='QuizGameParticipant',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('display_name', models.CharField(max_length=15, verbose_name='Anzeigename')),
('score', models.IntegerField(max_length=10, verbose_name='Punkte')),
('avatar', models.CharField(max_length=200)),
('quiz_game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='participant', to='play.quizgame')),
],
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-03-15 13:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('play', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='quizgame',
name='join_code',
field=models.CharField(blank=True, max_length=6, unique=True),
),
]

View File

@@ -0,0 +1,29 @@
# Generated by Django 5.1.7 on 2025-03-15 19:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('play', '0002_alter_quizgame_join_code'),
]
operations = [
migrations.AddField(
model_name='quizgameparticipant',
name='participant_id',
field=models.CharField(default=1, max_length=200, unique=True),
preserve_default=False,
),
migrations.AlterField(
model_name='quizgameparticipant',
name='avatar',
field=models.CharField(blank=True, default='', max_length=200),
),
migrations.AlterField(
model_name='quizgameparticipant',
name='score',
field=models.IntegerField(default=0, verbose_name='Punkte'),
),
]

View File

40
django/play/models.py Normal file
View File

@@ -0,0 +1,40 @@
from django.db import models
from django.contrib.auth.models import User
from library.models import QivipQuiz
from django.http import response
import random, string
# Create your models here.
class QuizGame(models.Model):
host_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='host_user')
join_code = models.CharField(max_length=6, unique=True, blank=True)
quiz_id = models.ForeignKey(QivipQuiz, on_delete=models.CASCADE)
def save(self, *args, **kwargs):
if not self.join_code:
self.join_code = self.generate_unique_code()
super().save(*args, **kwargs)
def generate_unique_code(self):
for i in range(10):
new_code = ''.join(random.choices(string.digits, k=6))
if not QuizGame.objects.filter(join_code=new_code).exists():
return new_code
class QuizGameParticipant(models.Model):
participant_id = models.CharField(max_length=200, unique=True)
display_name = models.CharField(verbose_name="Anzeigename", max_length=15)
quiz_game = models.ForeignKey(QuizGame, on_delete=models.CASCADE, related_name="participant")
score = models.IntegerField(verbose_name="Punkte", default=0)
avatar = models.CharField(max_length=200, blank=True, default="")
def save(self, *args, **kwargs):
if not self.participant_id:
self.participant_id = self.generate_unique_id()
super().save(*args, **kwargs)
def generate_unique_id(self):
for i in range(10):
new_id = ''.join(random.choices(string.digits + string.ascii_lowercase, k=60))
if not QuizGameParticipant.objects.filter(participant_id=new_id).exists():
return new_id

3
django/play/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

10
django/play/urls.py Normal file
View File

@@ -0,0 +1,10 @@
from django.urls import path
from . import views
app_name = 'play'
urlpatterns = [
path('lobby/<str:join_code>', views.lobby, name='lobby'),
path('lobby/<str:join_code>/participate', views.create_participant, name='create_participant'),
path('join', views.join_game, name='join_game'),
]

60
django/play/views.py Normal file
View File

@@ -0,0 +1,60 @@
from django.shortcuts import render
from django.shortcuts import render, redirect, get_object_or_404, reverse
from play.models import QuizGame, QuizGameParticipant
from library.models import QivipQuiz
from django.http import HttpResponseRedirect
# Create your views here.
def lobby(request, join_code):
if not "participant_id" in request.COOKIES:
return redirect('play:create_participant', join_code=join_code)
participant_id = request.COOKIES['participant_id']
quiz_game = get_object_or_404(QuizGame, join_code=join_code)
quiz = get_object_or_404(QivipQuiz, pk=quiz_game.quiz_id.id)
participant = get_object_or_404(QuizGameParticipant, participant_id=participant_id)
if not participant.quiz_game.join_code == join_code:
participant.quiz_game = quiz_game
participant.save()
context = {
'debug': "test",
'participant': participant,
'quiz': quiz,
}
return render(request, 'play/lobby.html', context=context)
def create_participant(request, join_code):
if "participant_id" in request.COOKIES: # Umleiten, falls Teilnehmer bereits erstellt
return redirect('play:lobby', join_code=join_code)
if request.method == 'POST':
display_name = request.POST.get('display_name')
join_code = request.POST.get('join_code')
try:
quiz = QuizGame.objects.get(join_code=join_code)
participant = QuizGameParticipant()
participant.display_name = display_name
participant.quiz_game = quiz
participant.save()
response = HttpResponseRedirect(reverse('play:lobby', kwargs={'join_code': join_code}))
response.set_cookie('participant_id', participant.participant_id, max_age=3600)
return response
except QuizGame.DoesNotExist:
# TODO: Fehlermeldung fuer nicht-existierendes Quiz
pass
return render(request, 'play/initialize_participant.html', {'join_code': join_code})
def join_game(request):
if request.method == 'POST':
join_code = request.POST.get('game_code')
try:
quiz = QuizGame.objects.get(join_code=join_code)
return redirect('play:lobby', join_code=join_code)
except QuizGame.DoesNotExist:
# TODO: Mit message eine Fehlermeldung weitergeben (und im entsprechenden Template Designen)
pass
return render(request, 'play/join_game.html')

View File

@@ -1,5 +1,9 @@
@import 'tailwindcss';
qp-container {
@apply max-w-xl w-full mx-auto;
}
nav div ul.qp-nav-list li {
@apply text-white hover:scale-110 hover:border-b-2 hover:border-white transition duration-200;
}
@@ -7,7 +11,10 @@ nav div ul.qp-nav-list li {
a.qp-a-button {
@apply p-6 rounded-md font-extrabold hover:scale-105 transition duration-300 shadow-md;
}
.register input{@apply p-3 rounded-full bg-gray-200 focus:scale-105 transition duration-200 font-black;}
a.qp-a-button-small {
@apply p-1 rounded-md hover:scale-105 transition duration-300 shadow-md;
}
.register input{@apply p-3 rounded-full bg-gray-200 focus:scale-105 transition duration-200 font-black w-full;}
.input-group {
@apply flex flex-col gap-4 max-w-md mx-auto p-6 bg-white rounded-xl shadow-lg;

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More