diff --git a/.env-template b/.env-template new file mode 100644 index 0000000..49e9c15 --- /dev/null +++ b/.env-template @@ -0,0 +1,16 @@ +DJANGO_SECRET_KEY='' + +DEBUG='True' +ALLOWED_HOSTS=[] + +DB_NAME='' +DB_USER='' +DB_PASSWORD='' +DB_HOST='localhost' +DB_PORT=5432 + +EMAIL_HOST='' +EMAIL_PORT= +EMAIL_USE_TLS='True' +EMAIL_HOST_USER='' +EMAIL_HOST_PASSWORD='' \ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..d40de53 --- /dev/null +++ b/README.rst @@ -0,0 +1,8 @@ +For Cod sake do not use this project as is! +=========================================== + +I'm making this public on the off chance someone, at an early stage of learning Django, comes across it and finds something useful in the code. This project is designed specifically for my needs and is not intended to be portable to other people without considerable restructuring effort. Besides this, my coding skills are basic to say the least and as soon as the learner viewing the project improves their knowledge they'll be able to work out all the many errors I've made. + +I will decide on a license and add it in due course. + +Calum. \ No newline at end of file diff --git a/articles/__init__.py b/articles/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/articles/admin.py b/articles/admin.py new file mode 100644 index 0000000..ddbbde0 --- /dev/null +++ b/articles/admin.py @@ -0,0 +1,22 @@ +import tagulous.admin +from django.contrib import admin +from markdownx.admin import MarkdownxModelAdmin + +from .models import Article, Category + + +@admin.register(Category) +class CategoryAdmin(admin.ModelAdmin): + list_display = ['title', 'slug'] + prepopulated_fields = {'slug': ('title',)} + + +class ArticleAdmin(MarkdownxModelAdmin): + list_display = ['title', 'subtitle', 'category', 'tags', 'created', 'updated', 'is_published', 'is_featured'] + list_filter = ['is_published', 'is_featured', 'category', 'tags'] + ordering = ['is_published', '-created'] + prepopulated_fields = {'slug': ('title',)} + list_display_links = ['title', 'subtitle'] + + +tagulous.admin.register(Article, ArticleAdmin) diff --git a/articles/apps.py b/articles/apps.py new file mode 100644 index 0000000..166ff9c --- /dev/null +++ b/articles/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ArticlesConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'articles' diff --git a/articles/migrations/0001_initial.py b/articles/migrations/0001_initial.py new file mode 100644 index 0000000..c08bf10 --- /dev/null +++ b/articles/migrations/0001_initial.py @@ -0,0 +1,72 @@ +# Generated by Django 5.1.6 on 2025-02-16 23:10 + +import django.db.models.deletion +import markdownx.models +import tagulous.models.fields +import tagulous.models.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=40, unique=True)), + ('slug', models.SlugField(max_length=40, unique=True)), + ('introduction', models.TextField(blank=True)), + ], + options={ + 'ordering': ['title'], + }, + ), + migrations.CreateModel( + name='Tagulous_Article_tags', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, unique=True)), + ('slug', models.SlugField()), + ('count', + models.IntegerField(default=0, help_text='Internal counter of how many times this tag is in use')), + ('protected', + models.BooleanField(default=False, help_text='Will not be deleted when the count reaches 0')), + ], + options={ + 'ordering': ('name',), + 'abstract': False, + 'unique_together': {('slug',)}, + }, + bases=(tagulous.models.models.BaseTagModel, models.Model), + ), + migrations.CreateModel( + name='Article', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200)), + ('subtitle', models.CharField(blank=True, max_length=200, null=True)), + ('slug', models.SlugField(max_length=200, unique=True)), + ('introduction', models.TextField(blank=True)), + ('body', markdownx.models.MarkdownxField()), + ('summary', markdownx.models.MarkdownxField(blank=True)), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now_add=True)), + ('is_published', models.BooleanField(default=False)), + ('is_updated', models.BooleanField(default=False)), + ('is_featured', models.BooleanField(default=False)), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='articles', + to='articles.category')), + ('tags', tagulous.models.fields.TagField(_set_tag_meta=True, force_lowercase=True, + help_text='Enter a comma-separated tag string', + to='articles.tagulous_article_tags')), + ], + options={ + 'ordering': ['-created'], + }, + ), + ] diff --git a/articles/migrations/__init__.py b/articles/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/articles/models.py b/articles/models.py new file mode 100644 index 0000000..618c3b2 --- /dev/null +++ b/articles/models.py @@ -0,0 +1,66 @@ +from datetime import datetime + +from django.db import models +from django.urls import reverse_lazy +from django.utils.text import slugify +from markdownx.models import MarkdownxField +from markdownx.utils import markdownify +from tagulous.models import TagField + + +class Category(models.Model): + title = models.CharField(max_length=40, unique=True) + slug = models.SlugField(max_length=40, unique=True) + introduction = models.TextField(blank=True) + + def save(self, force_insert=False, force_update=False, using=None, update_fields=None): + if not self.slug: + self.slug = slugify(self.title) + return super(Category, self).save() + + def get_absolute_url(self): + return reverse_lazy('articles:list_category', args=[self.slug]) + + def __str__(self): + return self.title + + class Meta: + ordering = ['title'] + + +class Article(models.Model): + title = models.CharField(max_length=200) + subtitle = models.CharField(max_length=200, blank=True, null=True) + slug = models.SlugField(max_length=200, unique=True) + category = models.ForeignKey(Category, on_delete=models.PROTECT, related_name='articles') + introduction = models.TextField(blank=True) + body = MarkdownxField() + summary = MarkdownxField(blank=True) + # FIXME: tags with spaces do not match any articles when clicked on. + tags = TagField(force_lowercase=True, + get_absolute_url=lambda tag: reverse_lazy('articles:list_tag', kwargs={'tag': tag.slug})) + created = models.DateTimeField(auto_now_add=True) + updated = models.DateTimeField(auto_now_add=True) + is_published = models.BooleanField(default=False) + is_updated = models.BooleanField(default=False) + is_featured = models.BooleanField(default=False) + + def save(self, force_insert=False, force_update=False, using=None, update_fields=None): + if not self.slug: + self.slug = slugify(self.title) + if self.is_updated: + self.updated = datetime.now() + self.is_updated = False + return super(Article, self).save() + + def get_absolute_url(self): + return reverse_lazy('articles:detail', args=[self.category.slug, self.slug]) + + def body_as_markdown(self): + return markdownify(self.body) + + def __str__(self): + return self.title + + class Meta: + ordering = ['-created'] diff --git a/articles/templates/articles/article_detail.html b/articles/templates/articles/article_detail.html new file mode 100644 index 0000000..b08ac52 --- /dev/null +++ b/articles/templates/articles/article_detail.html @@ -0,0 +1,28 @@ +{% extends 'base.html' %} + +{% block content %} +
+

{{ article.title }}

+ {% if article.subtitle %} +
{{ article.subtitle }}
+ {% endif %} +

Added on the {{ article.created | date:'jS \o\f F, Y' }} + {% if article.created.date != article.updated.date %} + and updated on the {{ article.updated | date:'jS \o\f F, Y' }} + {% endif %} +

+
+ {% if article.introduction %} +

{{ article.introduction|linebreaksbr }}

+
+ {% endif %} +
+ {{ article.body_as_markdown|safe }} +
+
+
+ Posted in the {{ article.category }} category +{# {% include 'articles/includes/display_tags.html' %}#} +
+
+{% endblock %} \ No newline at end of file diff --git a/articles/templates/articles/article_list.html b/articles/templates/articles/article_list.html new file mode 100644 index 0000000..aac3fc5 --- /dev/null +++ b/articles/templates/articles/article_list.html @@ -0,0 +1,52 @@ +{% extends 'base.html' %} + +{% block content %} +
+ {% if object_list %} + {% if category %} +

{{ category }} Articles

+ {% if category.introduction %} +
+

{{ category.introduction }}

+
+ {% endif %} + {% elif tag %} +

Articles for tag: {{ tag }}

+ {% else %} +

Recent Articles

+ {% endif %} +
+
+ {% for article in object_list %} +
+ {{ article.title }} + {% if article.subtitle %} +
{{ article.subtitle }}
+ {% endif %} + {% if article.introduction %} +
+

{{ article.introduction|linebreaksbr }}

+ {% endif %} +
+
+ + {{ article.category }} + • {{ article.created | date:'jS \o\f F, Y' }} + + {% include 'articles/includes/display_tags.html' %} +
+
+ {% endfor %} +
+ {% else %} +
+

Oops!

+
It looks like there are no articles for that {% if category %}category{% elif tag %}tag{% endif %} yet.
+

(sorry not sorry)

+
+ {% endif %} + {% if page_obj.has_previous or page_obj.has_next %} + {% include 'pagination.html' %} + {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/articles/templates/articles/category_list.html b/articles/templates/articles/category_list.html new file mode 100644 index 0000000..b657696 --- /dev/null +++ b/articles/templates/articles/category_list.html @@ -0,0 +1,21 @@ +{% extends 'base.html' %} + +{% block content %} +
+ {% if object_list %} +

Category List

+
+ + {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/articles/templates/articles/includes/display_tags.html b/articles/templates/articles/includes/display_tags.html new file mode 100644 index 0000000..c7246d3 --- /dev/null +++ b/articles/templates/articles/includes/display_tags.html @@ -0,0 +1,3 @@ +{% for tag in article.tags.all %} + {{ tag }}{% if tag != article.tags.last %},{% endif %} +{% endfor %} \ No newline at end of file diff --git a/articles/templates/articles/tag_list.html b/articles/templates/articles/tag_list.html new file mode 100644 index 0000000..ca23c3e --- /dev/null +++ b/articles/templates/articles/tag_list.html @@ -0,0 +1,17 @@ +{% extends 'base.html' %} + +{% block content %} +
+ {% if object_list %} +

List of Tags

+
+ + {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/articles/tests.py b/articles/tests.py new file mode 100644 index 0000000..a39b155 --- /dev/null +++ b/articles/tests.py @@ -0,0 +1 @@ +# Create your tests here. diff --git a/articles/urls.py b/articles/urls.py new file mode 100644 index 0000000..e279bf8 --- /dev/null +++ b/articles/urls.py @@ -0,0 +1,14 @@ +from django.urls import path + +from . import views + +app_name = 'articles' + +urlpatterns = [ + path('', views.ArticleList.as_view(), name='list'), + path('categories/', views.CategoryList.as_view(), name='categories'), + path('categories//', views.ArticleList.as_view(), name='list_category'), + path('tags/', views.TagList.as_view(), name='tags'), + path('tags//', views.ArticleList.as_view(), name='list_tag'), + path('//', views.ArticleDetail.as_view(), name='detail'), +] diff --git a/articles/views.py b/articles/views.py new file mode 100644 index 0000000..a988861 --- /dev/null +++ b/articles/views.py @@ -0,0 +1,42 @@ +from django.shortcuts import get_object_or_404 +from django.views.generic import DetailView, ListView + +from .models import Article, Category + + +class ArticleList(ListView): + category = None + tag = None + paginate_by = 9 + + def get_queryset(self): + category_slug = self.kwargs.get('category', None) + self.tag = self.kwargs.get('tag', None) + if category_slug: + self.category = get_object_or_404(Category, slug=category_slug) + return Article.objects.filter(category=self.category).filter(is_published=True) + elif self.tag: + return Article.objects.filter(tags=self.tag).filter(is_published=True) + else: + return Article.objects.filter(is_published=True) + + def get_context_data(self, *, object_list=None, **kwargs): + context = super(ArticleList, self).get_context_data(**kwargs) + context['category'] = self.category + context['tag'] = self.tag + return context + + +class ArticleDetail(DetailView): + model = Article + + +class CategoryList(ListView): + model = Category + + +class TagList(ListView): + template_name = 'articles/tag_list.html' + + def get_queryset(self): + return Article.tags.tag_model.objects.all() diff --git a/config/settings.py b/config/settings.py index 6cfe6a0..be82070 100644 --- a/config/settings.py +++ b/config/settings.py @@ -9,24 +9,26 @@ https://docs.djangoproject.com/en/5.1/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/5.1/ref/settings/ """ - +import os from pathlib import Path +from dotenv import load_dotenv + +load_dotenv() + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-e7m1a6dwy1^-^zp^*_(ql^c8y5!^0$kf4jwarw^3ny5b(d^t1r' +SECRET_KEY = os.getenv('DJANGO_SECRET_KEY') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = [] +DEBUG = os.getenv('DEBUG') +ALLOWED_HOSTS = eval(os.getenv('ALLOWED_HOSTS')) # Application definition @@ -37,6 +39,11 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django.contrib.sites', + 'django.contrib.flatpages', + 'core.apps.CoreConfig', + 'articles.apps.ArticlesConfig', + 'markdownx', ] MIDDLEWARE = [ @@ -54,8 +61,7 @@ ROOT_URLCONF = 'config.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [BASE_DIR / 'templates'] - , + 'DIRS': [BASE_DIR / 'templates'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -70,18 +76,20 @@ TEMPLATES = [ WSGI_APPLICATION = 'config.wsgi.application' - # Database # https://docs.djangoproject.com/en/5.1/ref/settings/#databases DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.getenv('DB_NAME'), + 'USER': os.getenv('DB_USER'), + 'PASSWORD': os.getenv('DB_PASSWORD'), + 'HOST': os.getenv('DB_HOST'), + 'PORT': os.getenv('DB_PORT'), } } - # Password validation # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators @@ -100,11 +108,10 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] - # Internationalization # https://docs.djangoproject.com/en/5.1/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = 'en-gb' TIME_ZONE = 'UTC' @@ -112,11 +119,42 @@ USE_I18N = True USE_TZ = True - # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.1/howto/static-files/ STATIC_URL = 'static/' +STATIC_ROOT = BASE_DIR / 'static/' +MEDIA_URL = 'media/' +MEDIA_ROOT = BASE_DIR / 'media/' + +# Site id to allow flatpages to function correctly +SITE_ID = 1 + +# User model and authentication +# LOGIN_REDIRECT_URL = '' +# LOGOUT_REDIRECT_URL = '' + +# Email backend configuration +# DEFAULT_FROM_EMAIL = 'calum@drulum.com' +# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +# EMAIL_HOST = os.getenv('EMAIL_HOST') +# EMAIL_PORT = os.getenv('EMAIL_PORT') +# EMAIL_USE_TLS = (os.getenv('EMAIL_USE_TLS') == 'True') +# EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER') +# EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD') + +# Tagulous config +SERIALIZATION_MODULES = { + 'xml': 'tagulous.serializers.xml_serializer', + 'json': 'tagulous.serializers.json', + 'python': 'tagulous.serializers.python', + 'yaml': 'tagulous.serializers.pyyaml', +} + +# Markdownx config +MARKDOWNX_MARKDOWN_EXTENSIONS = [ + 'markdown.extensions.extra', +] # Default primary key field type # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field diff --git a/config/urls.py b/config/urls.py index 10a0e72..1f70949 100644 --- a/config/urls.py +++ b/config/urls.py @@ -15,8 +15,11 @@ Including another URLconf 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), + path('', include('core.urls', namespace='core')), + path('articles/', include('articles.urls', namespace='articles')), + path('markdownx/', include('markdownx.urls')), ] diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/admin.py b/core/admin.py new file mode 100644 index 0000000..846f6b4 --- /dev/null +++ b/core/admin.py @@ -0,0 +1 @@ +# Register your models here. diff --git a/core/apps.py b/core/apps.py new file mode 100644 index 0000000..8115ae6 --- /dev/null +++ b/core/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'core' diff --git a/core/migrations/__init__.py b/core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/models.py b/core/models.py new file mode 100644 index 0000000..6b20219 --- /dev/null +++ b/core/models.py @@ -0,0 +1 @@ +# Create your models here. diff --git a/core/static/css/base.css b/core/static/css/base.css new file mode 100644 index 0000000..4a7fbda --- /dev/null +++ b/core/static/css/base.css @@ -0,0 +1,1840 @@ +/* +! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +*/ + +html { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font family by default. +2. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-feature-settings: inherit; + /* 1 */ + font-variation-settings: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden] { + display: none; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +.prose { + color: var(--tw-prose-body); + max-width: 65ch; +} + +.prose :where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.25em; + margin-bottom: 1.25em; +} + +.prose :where([class~="lead"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-lead); + font-size: 1.25em; + line-height: 1.6; + margin-top: 1.2em; + margin-bottom: 1.2em; +} + +.prose :where(a):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-links); + text-decoration: underline; + font-weight: 500; +} + +.prose :where(strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-bold); + font-weight: 600; +} + +.prose :where(a strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; +} + +.prose :where(blockquote strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; +} + +.prose :where(thead th strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; +} + +.prose :where(ol):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: decimal; + margin-top: 1.25em; + margin-bottom: 1.25em; + padding-left: 1.625em; +} + +.prose :where(ol[type="A"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: upper-alpha; +} + +.prose :where(ol[type="a"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: lower-alpha; +} + +.prose :where(ol[type="A" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: upper-alpha; +} + +.prose :where(ol[type="a" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: lower-alpha; +} + +.prose :where(ol[type="I"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: upper-roman; +} + +.prose :where(ol[type="i"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: lower-roman; +} + +.prose :where(ol[type="I" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: upper-roman; +} + +.prose :where(ol[type="i" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: lower-roman; +} + +.prose :where(ol[type="1"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: decimal; +} + +.prose :where(ul):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: disc; + margin-top: 1.25em; + margin-bottom: 1.25em; + padding-left: 1.625em; +} + +.prose :where(ol > li):not(:where([class~="not-prose"],[class~="not-prose"] *))::marker { + font-weight: 400; + color: var(--tw-prose-counters); +} + +.prose :where(ul > li):not(:where([class~="not-prose"],[class~="not-prose"] *))::marker { + color: var(--tw-prose-bullets); +} + +.prose :where(dt):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + margin-top: 1.25em; +} + +.prose :where(hr):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + border-color: var(--tw-prose-hr); + border-top-width: 1px; + margin-top: 3em; + margin-bottom: 3em; +} + +.prose :where(blockquote):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-weight: 500; + font-style: italic; + color: var(--tw-prose-quotes); + border-left-width: 0.25rem; + border-left-color: var(--tw-prose-quote-borders); + quotes: "\201C""\201D""\2018""\2019"; + margin-top: 1.6em; + margin-bottom: 1.6em; + padding-left: 1em; +} + +.prose :where(blockquote p:first-of-type):not(:where([class~="not-prose"],[class~="not-prose"] *))::before { + content: open-quote; +} + +.prose :where(blockquote p:last-of-type):not(:where([class~="not-prose"],[class~="not-prose"] *))::after { + content: close-quote; +} + +.prose :where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 800; + font-size: 2.25em; + margin-top: 0; + margin-bottom: 0.8888889em; + line-height: 1.1111111; +} + +.prose :where(h1 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-weight: 900; + color: inherit; +} + +.prose :where(h2):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 700; + font-size: 1.5em; + margin-top: 2em; + margin-bottom: 1em; + line-height: 1.3333333; +} + +.prose :where(h2 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-weight: 800; + color: inherit; +} + +.prose :where(h3):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + font-size: 1.25em; + margin-top: 1.6em; + margin-bottom: 0.6em; + line-height: 1.6; +} + +.prose :where(h3 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-weight: 700; + color: inherit; +} + +.prose :where(h4):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + margin-top: 1.5em; + margin-bottom: 0.5em; + line-height: 1.5; +} + +.prose :where(h4 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-weight: 700; + color: inherit; +} + +.prose :where(img):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; +} + +.prose :where(picture):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + display: block; + margin-top: 2em; + margin-bottom: 2em; +} + +.prose :where(kbd):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-weight: 500; + font-family: inherit; + color: var(--tw-prose-kbd); + box-shadow: 0 0 0 1px rgb(var(--tw-prose-kbd-shadows) / 10%), 0 3px 0 rgb(var(--tw-prose-kbd-shadows) / 10%); + font-size: 0.875em; + border-radius: 0.3125rem; + padding-top: 0.1875em; + padding-right: 0.375em; + padding-bottom: 0.1875em; + padding-left: 0.375em; +} + +.prose :where(code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-code); + font-weight: 600; + font-size: 0.875em; +} + +.prose :where(code):not(:where([class~="not-prose"],[class~="not-prose"] *))::before { + content: "`"; +} + +.prose :where(code):not(:where([class~="not-prose"],[class~="not-prose"] *))::after { + content: "`"; +} + +.prose :where(a code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; +} + +.prose :where(h1 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; +} + +.prose :where(h2 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; + font-size: 0.875em; +} + +.prose :where(h3 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; + font-size: 0.9em; +} + +.prose :where(h4 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; +} + +.prose :where(blockquote code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; +} + +.prose :where(thead th code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; +} + +.prose :where(pre):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-pre-code); + background-color: var(--tw-prose-pre-bg); + overflow-x: auto; + font-weight: 400; + font-size: 0.875em; + line-height: 1.7142857; + margin-top: 1.7142857em; + margin-bottom: 1.7142857em; + border-radius: 0.375rem; + padding-top: 0.8571429em; + padding-right: 1.1428571em; + padding-bottom: 0.8571429em; + padding-left: 1.1428571em; +} + +.prose :where(pre code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + background-color: transparent; + border-width: 0; + border-radius: 0; + padding: 0; + font-weight: inherit; + color: inherit; + font-size: inherit; + font-family: inherit; + line-height: inherit; +} + +.prose :where(pre code):not(:where([class~="not-prose"],[class~="not-prose"] *))::before { + content: none; +} + +.prose :where(pre code):not(:where([class~="not-prose"],[class~="not-prose"] *))::after { + content: none; +} + +.prose :where(table):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + width: 100%; + table-layout: auto; + text-align: left; + margin-top: 2em; + margin-bottom: 2em; + font-size: 0.875em; + line-height: 1.7142857; +} + +.prose :where(thead):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + border-bottom-width: 1px; + border-bottom-color: var(--tw-prose-th-borders); +} + +.prose :where(thead th):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + vertical-align: bottom; + padding-right: 0.5714286em; + padding-bottom: 0.5714286em; + padding-left: 0.5714286em; +} + +.prose :where(tbody tr):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + border-bottom-width: 1px; + border-bottom-color: var(--tw-prose-td-borders); +} + +.prose :where(tbody tr:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + border-bottom-width: 0; +} + +.prose :where(tbody td):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + vertical-align: baseline; +} + +.prose :where(tfoot):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + border-top-width: 1px; + border-top-color: var(--tw-prose-th-borders); +} + +.prose :where(tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + vertical-align: top; +} + +.prose :where(figure > *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + margin-bottom: 0; +} + +.prose :where(figcaption):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-captions); + font-size: 0.875em; + line-height: 1.4285714; + margin-top: 0.8571429em; +} + +.prose { + --tw-prose-body: #374151; + --tw-prose-headings: #111827; + --tw-prose-lead: #4b5563; + --tw-prose-links: #111827; + --tw-prose-bold: #111827; + --tw-prose-counters: #6b7280; + --tw-prose-bullets: #d1d5db; + --tw-prose-hr: #e5e7eb; + --tw-prose-quotes: #111827; + --tw-prose-quote-borders: #e5e7eb; + --tw-prose-captions: #6b7280; + --tw-prose-kbd: #111827; + --tw-prose-kbd-shadows: 17 24 39; + --tw-prose-code: #111827; + --tw-prose-pre-code: #e5e7eb; + --tw-prose-pre-bg: #1f2937; + --tw-prose-th-borders: #d1d5db; + --tw-prose-td-borders: #e5e7eb; + --tw-prose-invert-body: #d1d5db; + --tw-prose-invert-headings: #fff; + --tw-prose-invert-lead: #9ca3af; + --tw-prose-invert-links: #fff; + --tw-prose-invert-bold: #fff; + --tw-prose-invert-counters: #9ca3af; + --tw-prose-invert-bullets: #4b5563; + --tw-prose-invert-hr: #374151; + --tw-prose-invert-quotes: #f3f4f6; + --tw-prose-invert-quote-borders: #374151; + --tw-prose-invert-captions: #9ca3af; + --tw-prose-invert-kbd: #fff; + --tw-prose-invert-kbd-shadows: 255 255 255; + --tw-prose-invert-code: #fff; + --tw-prose-invert-pre-code: #d1d5db; + --tw-prose-invert-pre-bg: rgb(0 0 0 / 50%); + --tw-prose-invert-th-borders: #4b5563; + --tw-prose-invert-td-borders: #374151; + font-size: 1rem; + line-height: 1.75; +} + +.prose :where(picture > img):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + margin-bottom: 0; +} + +.prose :where(video):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; +} + +.prose :where(li):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + +.prose :where(ol > li):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-left: 0.375em; +} + +.prose :where(ul > li):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-left: 0.375em; +} + +.prose :where(.prose > ul > li p):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0.75em; + margin-bottom: 0.75em; +} + +.prose :where(.prose > ul > li > *:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.25em; +} + +.prose :where(.prose > ul > li > *:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-bottom: 1.25em; +} + +.prose :where(.prose > ol > li > *:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.25em; +} + +.prose :where(.prose > ol > li > *:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-bottom: 1.25em; +} + +.prose :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0.75em; + margin-bottom: 0.75em; +} + +.prose :where(dl):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.25em; + margin-bottom: 1.25em; +} + +.prose :where(dd):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0.5em; + padding-left: 1.625em; +} + +.prose :where(hr + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(h2 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(h3 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(h4 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(thead th:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-left: 0; +} + +.prose :where(thead th:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-right: 0; +} + +.prose :where(tbody td, tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-top: 0.5714286em; + padding-right: 0.5714286em; + padding-bottom: 0.5714286em; + padding-left: 0.5714286em; +} + +.prose :where(tbody td:first-child, tfoot td:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-left: 0; +} + +.prose :where(tbody td:last-child, tfoot td:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-right: 0; +} + +.prose :where(figure):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; +} + +.prose :where(.prose > :first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(.prose > :last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-bottom: 0; +} + +.static { + position: static; +} + +.row-span-3 { + grid-row: span 3 / span 3; +} + +.m-8 { + margin: 2rem; +} + +.mx-8 { + margin-left: 2rem; + margin-right: 2rem; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.my-4 { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.my-8 { + margin-top: 2rem; + margin-bottom: 2rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.mt-4 { + margin-top: 1rem; +} + +.block { + display: block; +} + +.flex { + display: flex; +} + +.grid { + display: grid; +} + +.max-w-2xl { + max-width: 42rem; +} + +.max-w-3xl { + max-width: 48rem; +} + +.max-w-5xl { + max-width: 64rem; +} + +.max-w-none { + max-width: none; +} + +.break-inside-avoid-column { + -moz-column-break-inside: avoid; + break-inside: avoid-column; +} + +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + +.content-center { + align-content: center; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.gap-12 { + gap: 3rem; +} + +.gap-x-2 { + -moz-column-gap: 0.5rem; + column-gap: 0.5rem; +} + +.gap-x-4 { + -moz-column-gap: 1rem; + column-gap: 1rem; +} + +.bg-blue-900 { + --tw-bg-opacity: 1; + background-color: rgb(30 58 138 / var(--tw-bg-opacity)); +} + +.p-8 { + padding: 2rem; +} + +.px-8 { + padding-left: 2rem; + padding-right: 2rem; +} + +.pb-8 { + padding-bottom: 2rem; +} + +.text-center { + text-align: center; +} + +.text-justify { + text-align: justify; +} + +.font-mono { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +.font-sans { + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +} + +.font-serif { + font-family: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} + +.text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; +} + +.text-6xl { + font-size: 3.75rem; + line-height: 1; +} + +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} + +.font-bold { + font-weight: 700; +} + +.font-extrabold { + font-weight: 800; +} + +.font-medium { + font-weight: 500; +} + +.font-semibold { + font-weight: 600; +} + +.uppercase { + text-transform: uppercase; +} + +.italic { + font-style: italic; +} + +.text-blue-600 { + --tw-text-opacity: 1; + color: rgb(37 99 235 / var(--tw-text-opacity)); +} + +.text-cyan-400 { + --tw-text-opacity: 1; + color: rgb(34 211 238 / var(--tw-text-opacity)); +} + +.text-neutral-400 { + --tw-text-opacity: 1; + color: rgb(163 163 163 / var(--tw-text-opacity)); +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +@media (min-width: 768px) { + .md\:prose-lg { + font-size: 1.125rem; + line-height: 1.7777778; + } + + .md\:prose-lg :where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.3333333em; + margin-bottom: 1.3333333em; + } + + .md\:prose-lg :where([class~="lead"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 1.2222222em; + line-height: 1.4545455; + margin-top: 1.0909091em; + margin-bottom: 1.0909091em; + } + + .md\:prose-lg :where(blockquote):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.6666667em; + margin-bottom: 1.6666667em; + padding-left: 1em; + } + + .md\:prose-lg :where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 2.6666667em; + margin-top: 0; + margin-bottom: 0.8333333em; + line-height: 1; + } + + .md\:prose-lg :where(h2):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 1.6666667em; + margin-top: 1.8666667em; + margin-bottom: 1.0666667em; + line-height: 1.3333333; + } + + .md\:prose-lg :where(h3):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 1.3333333em; + margin-top: 1.6666667em; + margin-bottom: 0.6666667em; + line-height: 1.5; + } + + .md\:prose-lg :where(h4):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.7777778em; + margin-bottom: 0.4444444em; + line-height: 1.5555556; + } + + .md\:prose-lg :where(img):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.7777778em; + margin-bottom: 1.7777778em; + } + + .md\:prose-lg :where(picture):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.7777778em; + margin-bottom: 1.7777778em; + } + + .md\:prose-lg :where(picture > img):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + margin-bottom: 0; + } + + .md\:prose-lg :where(video):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.7777778em; + margin-bottom: 1.7777778em; + } + + .md\:prose-lg :where(kbd):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 0.8888889em; + border-radius: 0.3125rem; + padding-top: 0.2222222em; + padding-right: 0.4444444em; + padding-bottom: 0.2222222em; + padding-left: 0.4444444em; + } + + .md\:prose-lg :where(code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 0.8888889em; + } + + .md\:prose-lg :where(h2 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 0.8666667em; + } + + .md\:prose-lg :where(h3 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 0.875em; + } + + .md\:prose-lg :where(pre):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 0.8888889em; + line-height: 1.75; + margin-top: 2em; + margin-bottom: 2em; + border-radius: 0.375rem; + padding-top: 1em; + padding-right: 1.5em; + padding-bottom: 1em; + padding-left: 1.5em; + } + + .md\:prose-lg :where(ol):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.3333333em; + margin-bottom: 1.3333333em; + padding-left: 1.5555556em; + } + + .md\:prose-lg :where(ul):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.3333333em; + margin-bottom: 1.3333333em; + padding-left: 1.5555556em; + } + + .md\:prose-lg :where(li):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0.6666667em; + margin-bottom: 0.6666667em; + } + + .md\:prose-lg :where(ol > li):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-left: 0.4444444em; + } + + .md\:prose-lg :where(ul > li):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-left: 0.4444444em; + } + + .md\:prose-lg :where(.md\:prose-lg > ul > li p):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0.8888889em; + margin-bottom: 0.8888889em; + } + + .md\:prose-lg :where(.md\:prose-lg > ul > li > *:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.3333333em; + } + + .md\:prose-lg :where(.md\:prose-lg > ul > li > *:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-bottom: 1.3333333em; + } + + .md\:prose-lg :where(.md\:prose-lg > ol > li > *:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.3333333em; + } + + .md\:prose-lg :where(.md\:prose-lg > ol > li > *:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-bottom: 1.3333333em; + } + + .md\:prose-lg :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0.8888889em; + margin-bottom: 0.8888889em; + } + + .md\:prose-lg :where(dl):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.3333333em; + margin-bottom: 1.3333333em; + } + + .md\:prose-lg :where(dt):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.3333333em; + } + + .md\:prose-lg :where(dd):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0.6666667em; + padding-left: 1.5555556em; + } + + .md\:prose-lg :where(hr):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 3.1111111em; + margin-bottom: 3.1111111em; + } + + .md\:prose-lg :where(hr + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + } + + .md\:prose-lg :where(h2 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + } + + .md\:prose-lg :where(h3 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + } + + .md\:prose-lg :where(h4 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + } + + .md\:prose-lg :where(table):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 0.8888889em; + line-height: 1.5; + } + + .md\:prose-lg :where(thead th):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-right: 0.75em; + padding-bottom: 0.75em; + padding-left: 0.75em; + } + + .md\:prose-lg :where(thead th:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-left: 0; + } + + .md\:prose-lg :where(thead th:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-right: 0; + } + + .md\:prose-lg :where(tbody td, tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-top: 0.75em; + padding-right: 0.75em; + padding-bottom: 0.75em; + padding-left: 0.75em; + } + + .md\:prose-lg :where(tbody td:first-child, tfoot td:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-left: 0; + } + + .md\:prose-lg :where(tbody td:last-child, tfoot td:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-right: 0; + } + + .md\:prose-lg :where(figure):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.7777778em; + margin-bottom: 1.7777778em; + } + + .md\:prose-lg :where(figure > *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + margin-bottom: 0; + } + + .md\:prose-lg :where(figcaption):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 0.8888889em; + line-height: 1.5; + margin-top: 1em; + } + + .md\:prose-lg :where(.md\:prose-lg > :first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + } + + .md\:prose-lg :where(.md\:prose-lg > :last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-bottom: 0; + } +} + +@media (min-width: 1024px) { + .lg\:prose-xl { + font-size: 1.25rem; + line-height: 1.8; + } + + .lg\:prose-xl :where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.2em; + margin-bottom: 1.2em; + } + + .lg\:prose-xl :where([class~="lead"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 1.2em; + line-height: 1.5; + margin-top: 1em; + margin-bottom: 1em; + } + + .lg\:prose-xl :where(blockquote):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.6em; + margin-bottom: 1.6em; + padding-left: 1.0666667em; + } + + .lg\:prose-xl :where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 2.8em; + margin-top: 0; + margin-bottom: 0.8571429em; + line-height: 1; + } + + .lg\:prose-xl :where(h2):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 1.8em; + margin-top: 1.5555556em; + margin-bottom: 0.8888889em; + line-height: 1.1111111; + } + + .lg\:prose-xl :where(h3):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 1.5em; + margin-top: 1.6em; + margin-bottom: 0.6666667em; + line-height: 1.3333333; + } + + .lg\:prose-xl :where(h4):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.8em; + margin-bottom: 0.6em; + line-height: 1.6; + } + + .lg\:prose-xl :where(img):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; + } + + .lg\:prose-xl :where(picture):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; + } + + .lg\:prose-xl :where(picture > img):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + margin-bottom: 0; + } + + .lg\:prose-xl :where(video):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; + } + + .lg\:prose-xl :where(kbd):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 0.9em; + border-radius: 0.3125rem; + padding-top: 0.25em; + padding-right: 0.4em; + padding-bottom: 0.25em; + padding-left: 0.4em; + } + + .lg\:prose-xl :where(code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 0.9em; + } + + .lg\:prose-xl :where(h2 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 0.8611111em; + } + + .lg\:prose-xl :where(h3 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 0.9em; + } + + .lg\:prose-xl :where(pre):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 0.9em; + line-height: 1.7777778; + margin-top: 2em; + margin-bottom: 2em; + border-radius: 0.5rem; + padding-top: 1.1111111em; + padding-right: 1.3333333em; + padding-bottom: 1.1111111em; + padding-left: 1.3333333em; + } + + .lg\:prose-xl :where(ol):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.2em; + margin-bottom: 1.2em; + padding-left: 1.6em; + } + + .lg\:prose-xl :where(ul):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.2em; + margin-bottom: 1.2em; + padding-left: 1.6em; + } + + .lg\:prose-xl :where(li):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0.6em; + margin-bottom: 0.6em; + } + + .lg\:prose-xl :where(ol > li):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-left: 0.4em; + } + + .lg\:prose-xl :where(ul > li):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-left: 0.4em; + } + + .lg\:prose-xl :where(.lg\:prose-xl > ul > li p):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0.8em; + margin-bottom: 0.8em; + } + + .lg\:prose-xl :where(.lg\:prose-xl > ul > li > *:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.2em; + } + + .lg\:prose-xl :where(.lg\:prose-xl > ul > li > *:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-bottom: 1.2em; + } + + .lg\:prose-xl :where(.lg\:prose-xl > ol > li > *:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.2em; + } + + .lg\:prose-xl :where(.lg\:prose-xl > ol > li > *:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-bottom: 1.2em; + } + + .lg\:prose-xl :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0.8em; + margin-bottom: 0.8em; + } + + .lg\:prose-xl :where(dl):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.2em; + margin-bottom: 1.2em; + } + + .lg\:prose-xl :where(dt):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.2em; + } + + .lg\:prose-xl :where(dd):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0.6em; + padding-left: 1.6em; + } + + .lg\:prose-xl :where(hr):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 2.8em; + margin-bottom: 2.8em; + } + + .lg\:prose-xl :where(hr + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + } + + .lg\:prose-xl :where(h2 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + } + + .lg\:prose-xl :where(h3 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + } + + .lg\:prose-xl :where(h4 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + } + + .lg\:prose-xl :where(table):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 0.9em; + line-height: 1.5555556; + } + + .lg\:prose-xl :where(thead th):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-right: 0.6666667em; + padding-bottom: 0.8888889em; + padding-left: 0.6666667em; + } + + .lg\:prose-xl :where(thead th:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-left: 0; + } + + .lg\:prose-xl :where(thead th:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-right: 0; + } + + .lg\:prose-xl :where(tbody td, tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-top: 0.8888889em; + padding-right: 0.6666667em; + padding-bottom: 0.8888889em; + padding-left: 0.6666667em; + } + + .lg\:prose-xl :where(tbody td:first-child, tfoot td:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-left: 0; + } + + .lg\:prose-xl :where(tbody td:last-child, tfoot td:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-right: 0; + } + + .lg\:prose-xl :where(figure):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; + } + + .lg\:prose-xl :where(figure > *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + margin-bottom: 0; + } + + .lg\:prose-xl :where(figcaption):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-size: 0.9em; + line-height: 1.5555556; + margin-top: 1em; + } + + .lg\:prose-xl :where(.lg\:prose-xl > :first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + } + + .lg\:prose-xl :where(.lg\:prose-xl > :last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-bottom: 0; + } +} + +.hover\:text-red-400:hover { + --tw-text-opacity: 1; + color: rgb(248 113 113 / var(--tw-text-opacity)); +} + +.prose-img\:rounded-2xl :is(:where(img):not(:where([class~="not-prose"],[class~="not-prose"] *))) { + border-radius: 1rem; +} + +@media (min-width: 640px) { + .sm\:w-1\/2 { + width: 50%; + } + + .sm\:text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; + } +} + +@media (min-width: 768px) { + .md\:row-span-2 { + grid-row: span 2 / span 2; + } + + .md\:row-span-3 { + grid-row: span 3 / span 3; + } + + .md\:grid { + display: grid; + } + + .md\:grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + + .md\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .md\:grid-cols-\[400px_1fr\] { + grid-template-columns: 400px 1fr; + } + + .md\:items-end { + align-items: flex-end; + } + + .md\:justify-end { + justify-content: flex-end; + } + + .md\:gap-x-4 { + -moz-column-gap: 1rem; + column-gap: 1rem; + } +} + +@media (min-width: 1024px) { + .lg\:w-1\/3 { + width: 33.333333%; + } + + .lg\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .lg\:text-6xl { + font-size: 3.75rem; + line-height: 1; + } +} + +@media (min-width: 1280px) { + .xl\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } +} \ No newline at end of file diff --git a/core/templates/base.html b/core/templates/base.html new file mode 100644 index 0000000..1c10008 --- /dev/null +++ b/core/templates/base.html @@ -0,0 +1,18 @@ +{% load static %} + + + + + + + {% block title %}Drulum{% endblock %} + + + +{% block page %} + {% block header %}{% include 'header.html' %}{% endblock %} + {% block content %}{% endblock %} + {% block footer %}{% include 'footer.html' %}{% endblock %} +{% endblock %} + + \ No newline at end of file diff --git a/core/templates/core/homepage.html b/core/templates/core/homepage.html new file mode 100644 index 0000000..650c9a9 --- /dev/null +++ b/core/templates/core/homepage.html @@ -0,0 +1,46 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+

Oh my word, this is basic!

+

I got bored and decided to develop this site from scratch, again. See the About This Website page if + you're at all interested in what I'm doing and why. The short version is that I'll be developing + features for the website as I create content for it. In other words, it's going to take a long time + before it's pretty.

+

Longer term this site will present my own photographs and photography + tuition services along with articles on photography, philosophy, politics, health & mental health, and + whatever else I feel like.

+
+
+ {% if featured_articles %} +

Featured Articles

+
+
+ {% for article in featured_articles %} +
+ {{ article.title }} + {% if article.subtitle %} +
{{ article.subtitle }}
+ {% endif %} + {% if article.introduction %} +
+

{{ article.introduction|linebreaksbr }}

+ {% endif %} +
+
+ + {{ article.category }} + • {{ article.created | date:'jS \o\f F, Y' }} + + {% include 'articles/includes/display_tags.html' %} +
+
+ {% endfor %} +
+ {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/core/templates/flatpages/default.html b/core/templates/flatpages/default.html new file mode 100644 index 0000000..2adce53 --- /dev/null +++ b/core/templates/flatpages/default.html @@ -0,0 +1,8 @@ +{% extends 'base.html' %} + +{% block content %} +

{{ flatpage.title }}

+
+ {{ flatpage.content }} +
+{% endblock %} \ No newline at end of file diff --git a/core/templates/footer.html b/core/templates/footer.html new file mode 100644 index 0000000..ac40002 --- /dev/null +++ b/core/templates/footer.html @@ -0,0 +1,3 @@ +
+

All content © Calum Andrew Morrell

+
\ No newline at end of file diff --git a/core/templates/header.html b/core/templates/header.html new file mode 100644 index 0000000..fccb66a --- /dev/null +++ b/core/templates/header.html @@ -0,0 +1,32 @@ +
+
+

the digital home of

+

Calum Andrew Morrell

+ {#

Photography · Writing · Audio · Video

#} +
+
+ + +
+
\ No newline at end of file diff --git a/core/templates/pagination.html b/core/templates/pagination.html new file mode 100644 index 0000000..0ff2d34 --- /dev/null +++ b/core/templates/pagination.html @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/core/tests.py b/core/tests.py new file mode 100644 index 0000000..a39b155 --- /dev/null +++ b/core/tests.py @@ -0,0 +1 @@ +# Create your tests here. diff --git a/core/urls.py b/core/urls.py new file mode 100644 index 0000000..138c8b8 --- /dev/null +++ b/core/urls.py @@ -0,0 +1,13 @@ +from django.contrib.flatpages.views import flatpage +from django.urls import path + +from . import views + +app_name = 'core' + +urlpatterns = [ + path('', views.Homepage.as_view(), name='homepage'), + path('about/me/', flatpage, {'url': '/about/me/'}, name='about-me'), + path('about/website/', flatpage, {'url': '/about/website/'}, name='about-website'), + path('about/contact/', flatpage, {'url': '/about/contact/'}, name='contact-me'), +] diff --git a/core/views.py b/core/views.py new file mode 100644 index 0000000..d59612b --- /dev/null +++ b/core/views.py @@ -0,0 +1,14 @@ +from django.views.generic import TemplateView + +from articles.models import Article + + +class Homepage(TemplateView): + template_name = 'core/homepage.html' + + def get_context_data(self, **kwargs): + context = super(Homepage, self).get_context_data() + context['featured_articles'] = Article.objects.filter(is_featured=True) + return context + +# TODO: place a random website link on the homepage diff --git a/pyproject.toml b/pyproject.toml index d1349d1..4a03122 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,13 @@ [project] name = "drulum" version = "0.1.0" -description = "Add your description here" +description = "This is the Django project for drulum.com" requires-python = ">=3.13" dependencies = [ "django>=5.1.6", + "django-markdownx>=4.0.7", + "django-tagulous>=2.1.0", + "gunicorn>=23.0.0", + "psycopg>=3.2.4", + "python-dotenv>=1.0.1", ] diff --git a/uv.lock b/uv.lock index 4446a3c..e4d8817 100644 --- a/uv.lock +++ b/uv.lock @@ -1,4 +1,5 @@ version = 1 +revision = 1 requires-python = ">=3.13" [[package]] @@ -24,16 +25,132 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/75/6f/d2c216d00975e2604b10940937b0ba6b2c2d9b3cc0cc633e414ae3f14b2e/Django-5.1.6-py3-none-any.whl", hash = "sha256:8d203400bc2952fbfb287c2bbda630297d654920c72a73cc82a9ad7926feaad5", size = 8277066 }, ] +[[package]] +name = "django-markdownx" +version = "4.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "markdown" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/b9/e33e16c721ca429c42cdf52f5f52f78023a576eb4f01294385446a3adaee/django-markdownx-4.0.7.tar.gz", hash = "sha256:38aa331c2ca0bee218b77f462361b5393e4727962bc6021939c09048363cb6ea", size = 35697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/0e/1413c3228ef262df351094621e0e1c550a68a7960c23719198b41a6097bc/django_markdownx-4.0.7-py2.py3-none-any.whl", hash = "sha256:c1975ae3053481d4c111abd38997a5b5bb89235a1e3215f995d835942925fe7b", size = 44136 }, +] + +[[package]] +name = "django-tagulous" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/97/f9a157716e3ce00b9d0ae596d9452bc6bb993066337a885a94c96ac7169c/django_tagulous-2.1.0.tar.gz", hash = "sha256:f629b54ad720052092785b0dce056dc6a68c7b63f8126075af9c25848b250bfd", size = 291575 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/7b/14c1c0a8e5378ccebe906676084ada7c7679ad76659a44cb5a55413ba5bc/django_tagulous-2.1.0-py3-none-any.whl", hash = "sha256:5ebba5a51f049f6df5f9d2a30eef431c0bf7cd35758aa0a42fc3351be4d239cd", size = 286294 }, +] + [[package]] name = "drulum" version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "django" }, + { name = "django-markdownx" }, + { name = "django-tagulous" }, + { name = "gunicorn" }, + { name = "psycopg" }, + { name = "python-dotenv" }, ] [package.metadata] -requires-dist = [{ name = "django", specifier = ">=5.1.6" }] +requires-dist = [ + { name = "django", specifier = ">=5.1.6" }, + { name = "django-markdownx", specifier = ">=4.0.7" }, + { name = "django-tagulous", specifier = ">=2.1.0" }, + { name = "gunicorn", specifier = ">=23.0.0" }, + { name = "psycopg", specifier = ">=3.2.4" }, + { name = "python-dotenv", specifier = ">=1.0.1" }, +] + +[[package]] +name = "gunicorn" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029 }, +] + +[[package]] +name = "markdown" +version = "3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, +] + +[[package]] +name = "pillow" +version = "11.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20", size = 46742715 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/31/9ca79cafdce364fd5c980cd3416c20ce1bebd235b470d262f9d24d810184/pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc", size = 3226640 }, + { url = "https://files.pythonhosted.org/packages/ac/0f/ff07ad45a1f172a497aa393b13a9d81a32e1477ef0e869d030e3c1532521/pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0", size = 3101437 }, + { url = "https://files.pythonhosted.org/packages/08/2f/9906fca87a68d29ec4530be1f893149e0cb64a86d1f9f70a7cfcdfe8ae44/pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1", size = 4326605 }, + { url = "https://files.pythonhosted.org/packages/b0/0f/f3547ee15b145bc5c8b336401b2d4c9d9da67da9dcb572d7c0d4103d2c69/pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec", size = 4411173 }, + { url = "https://files.pythonhosted.org/packages/b1/df/bf8176aa5db515c5de584c5e00df9bab0713548fd780c82a86cba2c2fedb/pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5", size = 4369145 }, + { url = "https://files.pythonhosted.org/packages/de/7c/7433122d1cfadc740f577cb55526fdc39129a648ac65ce64db2eb7209277/pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114", size = 4496340 }, + { url = "https://files.pythonhosted.org/packages/25/46/dd94b93ca6bd555588835f2504bd90c00d5438fe131cf01cfa0c5131a19d/pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352", size = 4296906 }, + { url = "https://files.pythonhosted.org/packages/a8/28/2f9d32014dfc7753e586db9add35b8a41b7a3b46540e965cb6d6bc607bd2/pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3", size = 4431759 }, + { url = "https://files.pythonhosted.org/packages/33/48/19c2cbe7403870fbe8b7737d19eb013f46299cdfe4501573367f6396c775/pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9", size = 2291657 }, + { url = "https://files.pythonhosted.org/packages/3b/ad/285c556747d34c399f332ba7c1a595ba245796ef3e22eae190f5364bb62b/pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c", size = 2626304 }, + { url = "https://files.pythonhosted.org/packages/e5/7b/ef35a71163bf36db06e9c8729608f78dedf032fc8313d19bd4be5c2588f3/pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65", size = 2375117 }, + { url = "https://files.pythonhosted.org/packages/79/30/77f54228401e84d6791354888549b45824ab0ffde659bafa67956303a09f/pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861", size = 3230060 }, + { url = "https://files.pythonhosted.org/packages/ce/b1/56723b74b07dd64c1010fee011951ea9c35a43d8020acd03111f14298225/pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081", size = 3106192 }, + { url = "https://files.pythonhosted.org/packages/e1/cd/7bf7180e08f80a4dcc6b4c3a0aa9e0b0ae57168562726a05dc8aa8fa66b0/pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c", size = 4446805 }, + { url = "https://files.pythonhosted.org/packages/97/42/87c856ea30c8ed97e8efbe672b58c8304dee0573f8c7cab62ae9e31db6ae/pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547", size = 4530623 }, + { url = "https://files.pythonhosted.org/packages/ff/41/026879e90c84a88e33fb00cc6bd915ac2743c67e87a18f80270dfe3c2041/pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab", size = 4465191 }, + { url = "https://files.pythonhosted.org/packages/e5/fb/a7960e838bc5df57a2ce23183bfd2290d97c33028b96bde332a9057834d3/pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9", size = 2295494 }, + { url = "https://files.pythonhosted.org/packages/d7/6c/6ec83ee2f6f0fda8d4cf89045c6be4b0373ebfc363ba8538f8c999f63fcd/pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe", size = 2631595 }, + { url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651 }, +] + +[[package]] +name = "psycopg" +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/f2/954b1467b3e2ca5945b83b5e320268be1f4df486c3e8ffc90f4e4b707979/psycopg-3.2.4.tar.gz", hash = "sha256:f26f1346d6bf1ef5f5ef1714dd405c67fb365cfd1c6cea07de1792747b167b92", size = 156109 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/49/15114d5f7ee68983f4e1a24d47e75334568960352a07c6f0e796e912685d/psycopg-3.2.4-py3-none-any.whl", hash = "sha256:43665368ccd48180744cab26b74332f46b63b7e06e8ce0775547a3533883d381", size = 198716 }, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, +] [[package]] name = "sqlparse"