diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..1d953f4 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use nix diff --git a/.gitignore b/.gitignore index 983599a..d74ec92 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ db.sqlite3 dist/ .DS_Store .python-version +.direnv diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 875403e..d0b56e0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,13 @@ repos: -- repo: https://github.com/psf/black - rev: 24.3.0 - hooks: - - id: black +# disable due to incomaptible formatting between +# black and ruff +# TODO: replace with ruff when it works on NixOS +# - repo: https://github.com/psf/black +# rev: 24.8.0 +# hooks: +# - id: black - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort name: isort (python) @@ -12,4 +15,6 @@ repos: rev: v1.34.0 hooks: - id: djlint-reformat-django + args: ["--ignore", "H011"] - id: djlint-django + args: ["--ignore", "H011"] diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 0d70e04..743b006 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,8 +1,11 @@ { "recommendations": [ - "ms-python.black-formatter", + "charliermarsh.ruff", "ms-python.python", "ms-python.vscode-pylance", "ms-python.debugpy", + "batisteo.vscode-django", + "bradlc.vscode-tailwindcss", + "EditorConfig.EditorConfig" ] -} \ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json index fc1a347..3118271 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,7 +6,28 @@ "python.testing.pytestEnabled": true, "python.analysis.typeCheckingMode": "strict", "[python]": { - "editor.defaultFormatter": "ms-python.black-formatter", - "editor.formatOnSave": true + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + }, }, + "ruff.path": ["/nix/store/jaibb3v0rrnlw5ib54qqq3452yhp1xcb-ruff-0.5.7/bin/ruff"], + "tailwind-fold.supportedLanguages": [ + "html", + "typescriptreact", + "javascriptreact", + "typescript", + "javascript", + "vue-html", + "vue", + "php", + "markdown", + "coffeescript", + "svelte", + "astro", + "erb", + "django-html" + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index d52a5ce..fe89cc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Add stats for dropped purchases, monthly playtimes * Allow deleting purchases * Add all-time stats +* Manage purchases ## Improved * mark refunded purchases red on game overview diff --git a/common/input.css b/common/input.css index 113775c..75e4442 100644 --- a/common/input.css +++ b/common/input.css @@ -70,9 +70,15 @@ form label { } @layer utilities { + .min-w-20char { + min-width: 20ch; + } .max-w-20char { max-width: 20ch; } + .min-w-30char { + min-width: 30ch; + } .max-w-30char { max-width: 30ch; } @@ -120,14 +126,6 @@ textarea:disabled { @apply mx-1; } -th { - @apply text-right; -} - -th label { - @apply mr-4; -} - .basic-button-container { @apply flex space-x-2 justify-center; } @@ -170,4 +168,4 @@ th label { @apply inline-block truncate max-w-20char transition-all group-hover:absolute group-hover:max-w-none group-hover:-top-8 group-hover:-left-6 group-hover:min-w-60 group-hover:px-6 group-hover:py-3.5 group-hover:bg-purple-600 group-hover:rounded-sm group-hover:outline-dashed group-hover:outline-purple-400 group-hover:outline-4; } -} */ \ No newline at end of file +} */ diff --git a/common/time.py b/common/time.py index a51f2ce..8e2d384 100644 --- a/common/time.py +++ b/common/time.py @@ -12,7 +12,7 @@ def _safe_timedelta(duration: timedelta | int | None): def format_duration( - duration: timedelta | int | None, format_string: str = "%H hours" + duration: timedelta | int | float | None, format_string: str = "%H hours" ) -> str: """ Format timedelta into the specified format_string. diff --git a/common/utils.py b/common/utils.py index ed82eb6..6a5ec38 100644 --- a/common/utils.py +++ b/common/utils.py @@ -1,3 +1,32 @@ +from random import choices +from string import ascii_lowercase +from typing import Any + +from django.template.loader import render_to_string +from django.utils.safestring import mark_safe + + +def Popover( + wrapped_content: str, + popover_content: str = "", +) -> str: + id = randomid() + if popover_content == "": + popover_content = wrapped_content + content = f"{wrapped_content}" + result = mark_safe( + str(content) + + render_to_string( + "cotton/popover.html", + { + "id": id, + "slot": popover_content, + }, + ) + ) + return result + + def safe_division(numerator: int | float, denominator: int | float) -> int | float: """ Divides without triggering division by zero exception. @@ -9,7 +38,7 @@ def safe_division(numerator: int | float, denominator: int | float) -> int | flo return 0 -def safe_getattr(obj, attr_chain, default=None): +def safe_getattr(obj: object, attr_chain: str, default: Any | None = None) -> object: """ Safely get the nested attribute from an object. @@ -28,3 +57,24 @@ def safe_getattr(obj, attr_chain, default=None): except AttributeError: return default return obj + + +def truncate(input_string: str, length: int = 30, ellipsis: str = "…") -> str: + return ( + (f"{input_string[:length-len(ellipsis)]}{ellipsis}") + if len(input_string) > 30 + else input_string + ) + + +def truncate_with_popover(input_string: str) -> str: + if (truncated := truncate(input_string)) != input_string: + print(f"Not the same after: {truncated=}") + return Popover(wrapped_content=truncated, popover_content=input_string) + else: + print("Strings are the same!") + return input_string + + +def randomid(seed: str = "", length: int = 10) -> str: + return seed + "".join(choices(ascii_lowercase, k=length)) diff --git a/games/forms.py b/games/forms.py index 1104d54..979d990 100644 --- a/games/forms.py +++ b/games/forms.py @@ -1,7 +1,7 @@ from django import forms from django.urls import reverse -from common.utils import safe_getattr +from common.utils import safe_getattr from games.models import Device, Edition, Game, Platform, Purchase, Session custom_date_widget = forms.DateInput(attrs={"type": "date"}) diff --git a/games/migrations/0035_alter_session_device.py b/games/migrations/0035_alter_session_device.py new file mode 100644 index 0000000..2f02601 --- /dev/null +++ b/games/migrations/0035_alter_session_device.py @@ -0,0 +1,25 @@ +# Generated by Django 5.1 on 2024-08-11 15:50 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("games", "0034_purchase_date_dropped_purchase_infinite"), + ] + + operations = [ + migrations.AlterField( + model_name="session", + name="device", + field=models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_DEFAULT, + to="games.device", + ), + ), + ] diff --git a/games/migrations/0036_alter_edition_platform.py b/games/migrations/0036_alter_edition_platform.py new file mode 100644 index 0000000..e101e0e --- /dev/null +++ b/games/migrations/0036_alter_edition_platform.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1 on 2024-08-11 16:48 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('games', '0035_alter_session_device'), + ] + + operations = [ + migrations.AlterField( + model_name='edition', + name='platform', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='games.platform'), + ), + ] diff --git a/games/models.py b/games/models.py index 3f5f3e5..15d6dd1 100644 --- a/games/models.py +++ b/games/models.py @@ -2,7 +2,7 @@ from datetime import timedelta from django.core.exceptions import ValidationError from django.db import models -from django.db.models import F, Manager, Sum +from django.db.models import F, Sum from django.utils import timezone from common.time import format_duration @@ -15,6 +15,18 @@ class Game(models.Model): wikidata = models.CharField(max_length=50, null=True, blank=True, default=None) created_at = models.DateTimeField(auto_now_add=True) + session_average: float | int | timedelta | None + session_count: int | None + + def __str__(self): + return self.name + + +class Platform(models.Model): + name = models.CharField(max_length=255) + group = models.CharField(max_length=255, null=True, blank=True, default=None) + created_at = models.DateTimeField(auto_now_add=True) + def __str__(self): return self.name @@ -23,11 +35,11 @@ class Edition(models.Model): class Meta: unique_together = [["name", "platform", "year_released"]] - game = models.ForeignKey("Game", on_delete=models.CASCADE) + game = models.ForeignKey(Game, on_delete=models.CASCADE) name = models.CharField(max_length=255) sort_name = models.CharField(max_length=255, null=True, blank=True, default=None) platform = models.ForeignKey( - "Platform", on_delete=models.CASCADE, null=True, blank=True, default=None + Platform, on_delete=models.SET_DEFAULT, null=True, blank=True, default=None ) year_released = models.IntegerField(null=True, blank=True, default=None) wikidata = models.CharField(max_length=50, null=True, blank=True, default=None) @@ -83,9 +95,9 @@ class Purchase(models.Model): objects = PurchaseQueryset().as_manager() - edition = models.ForeignKey("Edition", on_delete=models.CASCADE) + edition = models.ForeignKey(Edition, on_delete=models.CASCADE) platform = models.ForeignKey( - "Platform", on_delete=models.CASCADE, default=None, null=True, blank=True + Platform, on_delete=models.CASCADE, default=None, null=True, blank=True ) date_purchased = models.DateField() date_refunded = models.DateField(blank=True, null=True) @@ -100,7 +112,7 @@ class Purchase(models.Model): type = models.CharField(max_length=255, choices=TYPES, default=GAME) name = models.CharField(max_length=255, default="", null=True, blank=True) related_purchase = models.ForeignKey( - "Purchase", + "self", on_delete=models.SET_NULL, default=None, null=True, @@ -135,15 +147,6 @@ class Purchase(models.Model): super().save(*args, **kwargs) -class Platform(models.Model): - name = models.CharField(max_length=255) - group = models.CharField(max_length=255, null=True, blank=True, default=None) - created_at = models.DateTimeField(auto_now_add=True) - - def __str__(self): - return self.name - - class SessionQuerySet(models.QuerySet): def total_duration_formatted(self): return format_duration(self.total_duration_unformatted()) @@ -172,14 +175,14 @@ class Session(models.Model): class Meta: get_latest_by = "timestamp_start" - purchase = models.ForeignKey("Purchase", on_delete=models.CASCADE) + purchase = models.ForeignKey(Purchase, on_delete=models.CASCADE) timestamp_start = models.DateTimeField() timestamp_end = models.DateTimeField(blank=True, null=True) duration_manual = models.DurationField(blank=True, null=True, default=timedelta(0)) duration_calculated = models.DurationField(blank=True, null=True) device = models.ForeignKey( "Device", - on_delete=models.CASCADE, + on_delete=models.SET_DEFAULT, null=True, blank=True, default=None, @@ -220,7 +223,7 @@ class Session(models.Model): def duration_sum(self) -> str: return Session.objects.all().total_duration_formatted() - def save(self, *args, **kwargs): + def save(self, *args, **kwargs) -> None: if self.timestamp_start != None and self.timestamp_end != None: self.duration_calculated = self.timestamp_end - self.timestamp_start else: diff --git a/games/static/base.css b/games/static/base.css index 7adf3d9..ba8af4f 100644 --- a/games/static/base.css +++ b/games/static/base.css @@ -1246,6 +1246,18 @@ input:checked + .toggle-bg { } } +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + .visible { visibility: visible; } @@ -1370,10 +1382,18 @@ input:checked + .toggle-bg { margin-bottom: 0.75rem; } +.mb-4 { + margin-bottom: 1rem; +} + .mb-8 { margin-bottom: 2rem; } +.me-2 { + margin-inline-end: 0.5rem; +} + .ml-1 { margin-left: 0.25rem; } @@ -1382,6 +1402,18 @@ input:checked + .toggle-bg { margin-right: 1rem; } +.ms-0 { + margin-inline-start: 0px; +} + +.ms-2 { + margin-inline-start: 0.5rem; +} + +.ms-2\.5 { + margin-inline-start: 0.625rem; +} + .mt-2 { margin-top: 0.5rem; } @@ -1390,6 +1422,14 @@ input:checked + .toggle-bg { margin-top: 1rem; } +.mb-5 { + margin-bottom: 1.25rem; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + .block { display: block; } @@ -1427,12 +1467,16 @@ input:checked + .toggle-bg { height: 1.5rem; } +.h-10 { + height: 2.5rem; +} + .h-12 { height: 3rem; } -.h-24 { - height: 6rem; +.h-2\.5 { + height: 0.625rem; } .h-3 { @@ -1451,6 +1495,10 @@ input:checked + .toggle-bg { height: 1.5rem; } +.h-8 { + height: 2rem; +} + .h-9 { height: 2.25rem; } @@ -1463,6 +1511,14 @@ input:checked + .toggle-bg { width: 50%; } +.w-10 { + width: 2.5rem; +} + +.w-2\.5 { + width: 0.625rem; +} + .w-24 { width: 6rem; } @@ -1471,6 +1527,10 @@ input:checked + .toggle-bg { width: 1rem; } +.w-44 { + width: 11rem; +} + .w-5 { width: 1.25rem; } @@ -1487,10 +1547,6 @@ input:checked + .toggle-bg { width: 1.75rem; } -.w-auto { - width: auto; -} - .w-full { width: 100%; } @@ -1503,6 +1559,10 @@ input:checked + .toggle-bg { max-width: 1024px; } +.max-w-screen-xl { + max-width: 1280px; +} + .max-w-sm { max-width: 24rem; } @@ -1639,6 +1699,12 @@ input:checked + .toggle-bg { gap: 1.25rem; } +.-space-x-px > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(-1px * var(--tw-space-x-reverse)); + margin-left: calc(-1px * calc(1 - var(--tw-space-x-reverse))); +} + .space-x-1 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(0.25rem * var(--tw-space-x-reverse)); @@ -1651,6 +1717,23 @@ input:checked + .toggle-bg { margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); } +.space-x-3 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.75rem * var(--tw-space-x-reverse)); + margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse))); +} + +.divide-y > :not([hidden]) ~ :not([hidden]) { + --tw-divide-y-reverse: 0; + border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(1px * var(--tw-divide-y-reverse)); +} + +.divide-gray-100 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(243 244 246 / var(--tw-divide-opacity)); +} + .self-center { align-self: center; } @@ -1659,6 +1742,10 @@ input:checked + .toggle-bg { overflow: hidden; } +.overflow-x-auto { + overflow-x: auto; +} + .truncate { overflow: hidden; text-overflow: ellipsis; @@ -1673,6 +1760,10 @@ input:checked + .toggle-bg { text-wrap: wrap; } +.rounded { + border-radius: 0.25rem; +} + .rounded-full { border-radius: 9999px; } @@ -1717,6 +1808,10 @@ input:checked + .toggle-bg { border-width: 0px; } +.border-b { + border-bottom-width: 1px; +} + .border-blue-600 { --tw-border-opacity: 1; border-color: rgb(28 100 242 / var(--tw-border-opacity)); @@ -1747,6 +1842,11 @@ input:checked + .toggle-bg { border-color: rgb(220 215 254 / var(--tw-border-opacity)); } +.bg-blue-100 { + --tw-bg-opacity: 1; + background-color: rgb(225 239 254 / var(--tw-bg-opacity)); +} + .bg-blue-700 { --tw-bg-opacity: 1; background-color: rgb(26 86 219 / var(--tw-bg-opacity)); @@ -1762,6 +1862,16 @@ input:checked + .toggle-bg { background-color: rgb(229 231 235 / var(--tw-bg-opacity)); } +.bg-gray-400 { + --tw-bg-opacity: 1; + background-color: rgb(156 163 175 / var(--tw-bg-opacity)); +} + +.bg-gray-50 { + --tw-bg-opacity: 1; + background-color: rgb(249 250 251 / var(--tw-bg-opacity)); +} + .bg-gray-800 { --tw-bg-opacity: 1; background-color: rgb(31 41 55 / var(--tw-bg-opacity)); @@ -1794,6 +1904,10 @@ input:checked + .toggle-bg { padding: 0.25rem; } +.p-2 { + padding: 0.5rem; +} + .p-2\.5 { padding: 0.625rem; } @@ -1807,6 +1921,11 @@ input:checked + .toggle-bg { padding-right: 0.5rem; } +.px-2\.5 { + padding-left: 0.625rem; + padding-right: 0.625rem; +} + .px-3 { padding-left: 0.75rem; padding-right: 0.75rem; @@ -1822,6 +1941,16 @@ input:checked + .toggle-bg { padding-right: 1.25rem; } +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.py-0\.5 { + padding-top: 0.125rem; + padding-bottom: 0.125rem; +} + .py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; @@ -1842,20 +1971,9 @@ input:checked + .toggle-bg { padding-bottom: 0.75rem; } -.pb-16 { - padding-bottom: 4rem; -} - -.pl-3 { - padding-left: 0.75rem; -} - -.pr-4 { - padding-right: 1rem; -} - -.pt-1 { - padding-top: 0.25rem; +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; } .pt-2 { @@ -1866,10 +1984,18 @@ input:checked + .toggle-bg { padding-top: 2rem; } +.text-left { + text-align: left; +} + .text-center { text-align: center; } +.text-right { + text-align: right; +} + .align-top { vertical-align: top; } @@ -1935,14 +2061,26 @@ input:checked + .toggle-bg { font-weight: 700; } +.font-extrabold { + font-weight: 800; +} + .font-medium { font-weight: 500; } +.font-normal { + font-weight: 400; +} + .font-semibold { font-weight: 600; } +.uppercase { + text-transform: uppercase; +} + .leading-6 { line-height: 1.5rem; } @@ -1951,11 +2089,33 @@ input:checked + .toggle-bg { line-height: 2.25rem; } +.leading-none { + line-height: 1; +} + +.leading-tight { + line-height: 1.25; +} + +.tracking-tight { + letter-spacing: -0.025em; +} + .text-blue-600 { --tw-text-opacity: 1; color: rgb(28 100 242 / var(--tw-text-opacity)); } +.text-blue-800 { + --tw-text-opacity: 1; + color: rgb(30 66 159 / var(--tw-text-opacity)); +} + +.text-gray-300 { + --tw-text-opacity: 1; + color: rgb(209 213 219 / var(--tw-text-opacity)); +} + .text-gray-400 { --tw-text-opacity: 1; color: rgb(156 163 175 / var(--tw-text-opacity)); @@ -2256,14 +2416,6 @@ textarea:disabled:is(.dark *) { margin-right: 0.25rem; } -th { - text-align: right; -} - -th label { - margin-right: 1rem; -} - .basic-button-container { display: flex; justify-content: center; @@ -2366,11 +2518,26 @@ th label { } } */ +.odd\:bg-white:nth-child(odd) { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.even\:bg-gray-50:nth-child(even) { + --tw-bg-opacity: 1; + background-color: rgb(249 250 251 / var(--tw-bg-opacity)); +} + .hover\:border-gray-300:hover { --tw-border-opacity: 1; border-color: rgb(209 213 219 / var(--tw-border-opacity)); } +.hover\:border-green-600:hover { + --tw-border-opacity: 1; + border-color: rgb(5 122 85 / var(--tw-border-opacity)); +} + .hover\:bg-blue-800:hover { --tw-bg-opacity: 1; background-color: rgb(30 66 159 / var(--tw-bg-opacity)); @@ -2381,9 +2548,14 @@ th label { background-color: rgb(243 244 246 / var(--tw-bg-opacity)); } -.hover\:bg-gray-400:hover { +.hover\:bg-gray-50:hover { --tw-bg-opacity: 1; - background-color: rgb(156 163 175 / var(--tw-bg-opacity)); + background-color: rgb(249 250 251 / var(--tw-bg-opacity)); +} + +.hover\:bg-green-500:hover { + --tw-bg-opacity: 1; + background-color: rgb(14 159 110 / var(--tw-bg-opacity)); } .hover\:bg-green-700:hover { @@ -2396,6 +2568,11 @@ th label { background-color: rgb(253 232 232 / var(--tw-bg-opacity)); } +.hover\:bg-red-500:hover { + --tw-bg-opacity: 1; + background-color: rgb(240 82 82 / var(--tw-bg-opacity)); +} + .hover\:bg-violet-700:hover { --tw-bg-opacity: 1; background-color: rgb(109 40 217 / var(--tw-bg-opacity)); @@ -2421,13 +2598,19 @@ th label { color: rgb(75 85 99 / var(--tw-text-opacity)); } +.hover\:text-gray-700:hover { + --tw-text-opacity: 1; + color: rgb(55 65 81 / var(--tw-text-opacity)); +} + .hover\:text-gray-900:hover { --tw-text-opacity: 1; color: rgb(17 24 39 / var(--tw-text-opacity)); } -.hover\:underline:hover { - text-decoration-line: underline; +.hover\:text-white:hover { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); } .focus\:z-10:focus { @@ -2476,6 +2659,11 @@ th label { --tw-ring-color: rgb(14 159 110 / var(--tw-ring-opacity)); } +.focus\:ring-green-700:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(4 108 78 / var(--tw-ring-opacity)); +} + .focus\:ring-violet-500:focus { --tw-ring-opacity: 1; --tw-ring-color: rgb(139 92 246 / var(--tw-ring-opacity)); @@ -2505,10 +2693,6 @@ th label { top: -2rem; } -.group:hover .group-hover\:block { - display: block; -} - .group:hover .group-hover\:min-w-60 { min-width: 15rem; } @@ -2557,6 +2741,11 @@ th label { outline-color: #AC94FA; } +.dark\:divide-gray-600:is(.dark *) > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(75 85 99 / var(--tw-divide-opacity)); +} + .dark\:border-blue-500:is(.dark *) { --tw-border-opacity: 1; border-color: rgb(63 131 248 / var(--tw-border-opacity)); @@ -2581,6 +2770,11 @@ th label { border-color: transparent; } +.dark\:bg-blue-200:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(195 221 253 / var(--tw-bg-opacity)); +} + .dark\:bg-blue-600:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(28 100 242 / var(--tw-bg-opacity)); @@ -2624,6 +2818,16 @@ th label { color: rgb(63 131 248 / var(--tw-text-opacity)); } +.dark\:text-blue-800:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(30 66 159 / var(--tw-text-opacity)); +} + +.dark\:text-gray-200:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(229 231 235 / var(--tw-text-opacity)); +} + .dark\:text-gray-400:is(.dark *) { --tw-text-opacity: 1; color: rgb(156 163 175 / var(--tw-text-opacity)); @@ -2634,6 +2838,11 @@ th label { color: rgb(107 114 128 / var(--tw-text-opacity)); } +.dark\:text-gray-600:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity)); +} + .dark\:text-slate-400:is(.dark *) { --tw-text-opacity: 1; color: rgb(148 163 184 / var(--tw-text-opacity)); @@ -2654,6 +2863,26 @@ th label { color: rgb(255 255 255 / var(--tw-text-opacity)); } +.odd\:dark\:bg-gray-900:is(.dark *):nth-child(odd) { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)); +} + +.even\:dark\:bg-gray-800:is(.dark *):nth-child(even) { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +} + +.dark\:hover\:border-green-700:hover:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(4 108 78 / var(--tw-border-opacity)); +} + +.dark\:hover\:border-red-700:hover:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(200 30 30 / var(--tw-border-opacity)); +} + .dark\:hover\:bg-blue-700:hover:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(26 86 219 / var(--tw-bg-opacity)); @@ -2674,6 +2903,11 @@ th label { background-color: rgb(31 41 55 / var(--tw-bg-opacity)); } +.dark\:hover\:bg-green-600:hover:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(5 122 85 / var(--tw-bg-opacity)); +} + .dark\:hover\:bg-red-700:hover:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(200 30 30 / var(--tw-bg-opacity)); @@ -2704,11 +2938,22 @@ th label { --tw-ring-color: rgb(63 131 248 / var(--tw-ring-opacity)); } -@media (min-width: 640px) { - .sm\:inline { - display: inline; - } +.dark\:focus\:ring-blue-800:focus:is(.dark *) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(30 66 159 / var(--tw-ring-opacity)); +} +.dark\:focus\:ring-gray-600:focus:is(.dark *) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(75 85 99 / var(--tw-ring-opacity)); +} + +.dark\:focus\:ring-green-500:focus:is(.dark *) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(14 159 110 / var(--tw-ring-opacity)); +} + +@media (min-width: 640px) { .sm\:table-cell { display: table-cell; } @@ -2717,19 +2962,23 @@ th label { max-width: 28rem; } + .sm\:max-w-screen-sm { + max-width: 640px; + } + .sm\:max-w-xl { max-width: 36rem; } + .sm\:rounded-lg { + border-radius: 0.5rem; + } + .sm\:px-4 { padding-left: 1rem; padding-right: 1rem; } - .sm\:pl-12 { - padding-left: 3rem; - } - .sm\:pl-2 { padding-left: 0.5rem; } @@ -2738,28 +2987,67 @@ th label { padding-left: 1rem; } - .sm\:pl-6 { - padding-left: 1.5rem; - } - .sm\:decoration-2 { text-decoration-thickness: 2px; } } @media (min-width: 768px) { + .md\:mb-0 { + margin-bottom: 0px; + } + + .md\:mt-0 { + margin-top: 0px; + } + .md\:block { display: block; } + .md\:inline { + display: inline; + } + + .md\:hidden { + display: none; + } + .md\:w-auto { width: auto; } + .md\:max-w-screen-md { + max-width: 768px; + } + .md\:flex-row { flex-direction: row; } + .md\:space-x-8 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(2rem * var(--tw-space-x-reverse)); + margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse))); + } + + .md\:border-0 { + border-width: 0px; + } + + .md\:bg-transparent { + background-color: transparent; + } + + .md\:bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + } + + .md\:p-0 { + padding: 0px; + } + .md\:px-6 { padding-left: 1.5rem; padding-right: 1.5rem; @@ -2769,6 +3057,48 @@ th label { padding-top: 0.5rem; padding-bottom: 0.5rem; } + + .md\:text-5xl { + font-size: 3rem; + line-height: 1; + } + + .md\:text-blue-700 { + --tw-text-opacity: 1; + color: rgb(26 86 219 / var(--tw-text-opacity)); + } + + .md\:hover\:bg-transparent:hover { + background-color: transparent; + } + + .md\:hover\:text-blue-700:hover { + --tw-text-opacity: 1; + color: rgb(26 86 219 / var(--tw-text-opacity)); + } + + .md\:dark\:bg-gray-900:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)); + } + + .md\:dark\:bg-transparent:is(.dark *) { + background-color: transparent; + } + + .md\:dark\:text-blue-500:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(63 131 248 / var(--tw-text-opacity)); + } + + .md\:dark\:hover\:bg-transparent:hover:is(.dark *) { + background-color: transparent; + } + + .md\:dark\:hover\:text-blue-500:hover:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(63 131 248 / var(--tw-text-opacity)); + } } @media (min-width: 1024px) { @@ -2783,6 +3113,17 @@ th label { .lg\:max-w-lg { max-width: 32rem; } + + .lg\:text-6xl { + font-size: 3.75rem; + line-height: 1; + } +} + +@media (min-width: 1536px) { + .\32xl\:max-w-screen-2xl { + max-width: 1536px; + } } .rtl\:rotate-180:where([dir="rtl"], [dir="rtl"] *) { @@ -2792,4 +3133,22 @@ th label { .rtl\:space-x-reverse:where([dir="rtl"], [dir="rtl"] *) > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 1; -} \ No newline at end of file +} + +.rtl\:text-left:where([dir="rtl"], [dir="rtl"] *) { + text-align: left; +} + +.rtl\:text-right:where([dir="rtl"], [dir="rtl"] *) { + text-align: right; +} + +.\[\&\:first-of-type_button\]\:rounded-s-lg:first-of-type button { + border-start-start-radius: 0.5rem; + border-end-start-radius: 0.5rem; +} + +.\[\&\:last-of-type_button\]\:rounded-e-lg:last-of-type button { + border-start-end-radius: 0.5rem; + border-end-end-radius: 0.5rem; +} diff --git a/games/templates/add_purchase.html b/games/templates/add_purchase.html index 5a09d96..2fa0ec8 100644 --- a/games/templates/add_purchase.html +++ b/games/templates/add_purchase.html @@ -26,7 +26,9 @@