diff options
Diffstat (limited to 'pydis_site')
32 files changed, 1261 insertions, 354 deletions
diff --git a/pydis_site/apps/api/models/bot/metricity.py b/pydis_site/apps/api/models/bot/metricity.py index 25b42fa2..cae630f1 100644 --- a/pydis_site/apps/api/models/bot/metricity.py +++ b/pydis_site/apps/api/models/bot/metricity.py @@ -1,5 +1,12 @@ from django.db import connections +BLOCK_INTERVAL = 10 * 60 # 10 minute blocks + +EXCLUDE_CHANNELS = [ + "267659945086812160", # Bot commands + "607247579608121354" # SeasonalBot commands +] + class NotFound(Exception): """Raised when an entity cannot be found.""" @@ -21,7 +28,8 @@ class Metricity: def user(self, user_id: str) -> dict: """Query a user's data.""" - columns = ["verified_at"] + # TODO: Swap this back to some sort of verified at date + columns = ["joined_at"] query = f"SELECT {','.join(columns)} FROM users WHERE id = '%s'" self.cursor.execute(query, [user_id]) values = self.cursor.fetchone() @@ -33,7 +41,48 @@ class Metricity: def total_messages(self, user_id: str) -> int: """Query total number of messages for a user.""" - self.cursor.execute("SELECT COUNT(*) FROM messages WHERE author_id = '%s'", [user_id]) + self.cursor.execute( + """ + SELECT + COUNT(*) + FROM messages + WHERE + author_id = '%s' + AND NOT is_deleted + AND NOT %s::varchar[] @> ARRAY[channel_id] + """, + [user_id, EXCLUDE_CHANNELS] + ) + values = self.cursor.fetchone() + + if not values: + raise NotFound() + + return values[0] + + def total_message_blocks(self, user_id: str) -> int: + """ + Query number of 10 minute blocks during which the user has been active. + + This metric prevents users from spamming to achieve the message total threshold. + """ + self.cursor.execute( + """ + SELECT + COUNT(*) + FROM ( + SELECT + (floor((extract('epoch' from created_at) / %s )) * %s) AS interval + FROM messages + WHERE + author_id='%s' + AND NOT is_deleted + AND NOT %s::varchar[] @> ARRAY[channel_id] + GROUP BY interval + ) block_query; + """, + [BLOCK_INTERVAL, BLOCK_INTERVAL, user_id, EXCLUDE_CHANNELS] + ) values = self.cursor.fetchone() if not values: diff --git a/pydis_site/apps/api/tests/test_infractions.py b/pydis_site/apps/api/tests/test_infractions.py index 93ef8171..82b497aa 100644 --- a/pydis_site/apps/api/tests/test_infractions.py +++ b/pydis_site/apps/api/tests/test_infractions.py @@ -512,6 +512,36 @@ class CreationTests(APISubdomainTestCase): ) +class InfractionDeletionTests(APISubdomainTestCase): + @classmethod + def setUpTestData(cls): + cls.user = User.objects.create( + id=9876, + name='Unknown user', + discriminator=9876, + ) + + cls.warning = Infraction.objects.create( + user_id=cls.user.id, + actor_id=cls.user.id, + type='warning', + active=False + ) + + def test_delete_unknown_infraction_returns_404(self): + url = reverse('bot:infraction-detail', args=('something',), host='api') + response = self.client.delete(url) + + self.assertEqual(response.status_code, 404) + + def test_delete_known_infraction_returns_204(self): + url = reverse('bot:infraction-detail', args=(self.warning.id,), host='api') + response = self.client.delete(url) + + self.assertEqual(response.status_code, 204) + self.assertRaises(Infraction.DoesNotExist, Infraction.objects.get, id=self.warning.id) + + class ExpandedTests(APISubdomainTestCase): @classmethod def setUpTestData(cls): diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py index 72ffcb3c..69bbfefc 100644 --- a/pydis_site/apps/api/tests/test_users.py +++ b/pydis_site/apps/api/tests/test_users.py @@ -407,9 +407,10 @@ class UserMetricityTests(APISubdomainTestCase): def test_get_metricity_data(self): # Given - verified_at = "foo" + joined_at = "foo" total_messages = 1 - self.mock_metricity_user(verified_at, total_messages) + total_blocks = 1 + self.mock_metricity_user(joined_at, total_messages, total_blocks) # When url = reverse('bot:user-metricity-data', args=[0], host='api') @@ -418,9 +419,10 @@ class UserMetricityTests(APISubdomainTestCase): # Then self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), { - "verified_at": verified_at, + "joined_at": joined_at, "total_messages": total_messages, "voice_banned": False, + "activity_blocks": total_blocks }) def test_no_metricity_user(self): @@ -440,7 +442,7 @@ class UserMetricityTests(APISubdomainTestCase): {'exception': ObjectDoesNotExist, 'voice_banned': False}, ] - self.mock_metricity_user("foo", 1) + self.mock_metricity_user("foo", 1, 1) for case in cases: with self.subTest(exception=case['exception'], voice_banned=case['voice_banned']): @@ -453,13 +455,14 @@ class UserMetricityTests(APISubdomainTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.json()["voice_banned"], case["voice_banned"]) - def mock_metricity_user(self, verified_at, total_messages): + def mock_metricity_user(self, joined_at, total_messages, total_blocks): patcher = patch("pydis_site.apps.api.viewsets.bot.user.Metricity") self.metricity = patcher.start() self.addCleanup(patcher.stop) self.metricity = self.metricity.return_value.__enter__.return_value - self.metricity.user.return_value = dict(verified_at=verified_at) + self.metricity.user.return_value = dict(joined_at=joined_at) self.metricity.total_messages.return_value = total_messages + self.metricity.total_message_blocks.return_value = total_blocks def mock_no_metricity_user(self): patcher = patch("pydis_site.apps.api.viewsets.bot.user.Metricity") @@ -468,3 +471,4 @@ class UserMetricityTests(APISubdomainTestCase): self.metricity = self.metricity.return_value.__enter__.return_value self.metricity.user.side_effect = NotFound() self.metricity.total_messages.side_effect = NotFound() + self.metricity.total_message_blocks.side_effect = NotFound() diff --git a/pydis_site/apps/api/viewsets/bot/infraction.py b/pydis_site/apps/api/viewsets/bot/infraction.py index edec0a1e..423e806e 100644 --- a/pydis_site/apps/api/viewsets/bot/infraction.py +++ b/pydis_site/apps/api/viewsets/bot/infraction.py @@ -5,6 +5,7 @@ from rest_framework.exceptions import ValidationError from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.mixins import ( CreateModelMixin, + DestroyModelMixin, ListModelMixin, RetrieveModelMixin ) @@ -18,7 +19,13 @@ from pydis_site.apps.api.serializers import ( ) -class InfractionViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, GenericViewSet): +class InfractionViewSet( + CreateModelMixin, + RetrieveModelMixin, + ListModelMixin, + GenericViewSet, + DestroyModelMixin +): """ View providing CRUD operations on infractions for Discord users. @@ -108,6 +115,13 @@ class InfractionViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge - 400: if a field in the request body is invalid or disallowed - 404: if an infraction with the given `id` could not be found + ### DELETE /bot/infractions/<id:int> + Delete the infraction with the given `id`. + + #### Status codes + - 204: returned on success + - 404: if a infraction with the given `id` does not exist + ### Expanded routes All routes support expansion of `user` and `actor` in responses. To use an expanded route, append `/expanded` to the end of the route e.g. `GET /bot/infractions/expanded`. diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py index 5205dc97..829e2694 100644 --- a/pydis_site/apps/api/viewsets/bot/user.py +++ b/pydis_site/apps/api/viewsets/bot/user.py @@ -109,8 +109,10 @@ class UserViewSet(ModelViewSet): #### Response format >>> { - ... "verified_at": "2020-10-06T21:54:23.540766", - ... "total_messages": 2 + ... "joined_at": "2020-10-06T21:54:23.540766", + ... "total_messages": 2, + ... "voice_banned": False, + ... "activity_blocks": 1 ...} #### Status codes @@ -255,6 +257,7 @@ class UserViewSet(ModelViewSet): data = metricity.user(user.id) data["total_messages"] = metricity.total_messages(user.id) data["voice_banned"] = voice_banned + data["activity_blocks"] = metricity.total_message_blocks(user.id) return Response(data, status=status.HTTP_200_OK) except NotFound: return Response(dict(detail="User not found in metricity"), diff --git a/pydis_site/apps/home/migrations/0002_auto_now_on_repository_metadata.py b/pydis_site/apps/home/migrations/0002_auto_now_on_repository_metadata.py new file mode 100644 index 00000000..7e78045b --- /dev/null +++ b/pydis_site/apps/home/migrations/0002_auto_now_on_repository_metadata.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.11 on 2020-12-21 22:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('home', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='repositorymetadata', + name='last_updated', + field=models.DateTimeField(auto_now=True, help_text='The date and time this data was last fetched.'), + ), + ] diff --git a/pydis_site/apps/home/models/repository_metadata.py b/pydis_site/apps/home/models/repository_metadata.py index 92d2404d..00a83cd7 100644 --- a/pydis_site/apps/home/models/repository_metadata.py +++ b/pydis_site/apps/home/models/repository_metadata.py @@ -1,32 +1,31 @@ from django.db import models -from django.utils import timezone class RepositoryMetadata(models.Model): """Information about one of our repos fetched from the GitHub API.""" last_updated = models.DateTimeField( - default=timezone.now, - help_text="The date and time this data was last fetched." + help_text="The date and time this data was last fetched.", + auto_now=True, ) repo_name = models.CharField( primary_key=True, max_length=40, - help_text="The full name of the repo, e.g. python-discord/site" + help_text="The full name of the repo, e.g. python-discord/site", ) description = models.CharField( max_length=400, - help_text="The description of the repo." + help_text="The description of the repo.", ) forks = models.IntegerField( - help_text="The number of forks of this repo" + help_text="The number of forks of this repo", ) stargazers = models.IntegerField( - help_text="The number of stargazers for this repo" + help_text="The number of stargazers for this repo", ) language = models.CharField( max_length=20, - help_text="The primary programming language used for this repo." + help_text="The primary programming language used for this repo.", ) def __str__(self): diff --git a/pydis_site/apps/home/tests/mock_github_api_response.json b/pydis_site/apps/home/tests/mock_github_api_response.json index 10be4f99..ddbffed8 100644 --- a/pydis_site/apps/home/tests/mock_github_api_response.json +++ b/pydis_site/apps/home/tests/mock_github_api_response.json @@ -35,7 +35,7 @@ "forks_count": 31 }, { - "full_name": "python-discord/seasonalbot", + "full_name": "python-discord/sir-lancebot", "description": "test", "stargazers_count": 97, "language": "Python", diff --git a/pydis_site/apps/home/tests/test_repodata_helpers.py b/pydis_site/apps/home/tests/test_repodata_helpers.py index 77b1a68d..5634bc9b 100644 --- a/pydis_site/apps/home/tests/test_repodata_helpers.py +++ b/pydis_site/apps/home/tests/test_repodata_helpers.py @@ -123,10 +123,38 @@ class TestRepositoryMetadataHelpers(TestCase): mock_get.return_value.json.return_value = ['garbage'] metadata = self.home_view._get_repo_data() - self.assertEquals(len(metadata), len(self.home_view.repos)) - for item in metadata: - with self.subTest(item=item): - self.assertEqual(item.description, "Not available.") - self.assertEqual(item.forks, 999) - self.assertEqual(item.stargazers, 999) - self.assertEqual(item.language, "Python") + self.assertEquals(len(metadata), 0) + + def test_cleans_up_stale_metadata(self): + """Tests that we clean up stale metadata when we start the HomeView.""" + repo_data = RepositoryMetadata( + repo_name="python-discord/INVALID", + description="testrepo", + forks=42, + stargazers=42, + language="English", + last_updated=timezone.now() - timedelta(seconds=HomeView.repository_cache_ttl + 1), + ) + repo_data.save() + self.home_view.__init__() + cached_repos = RepositoryMetadata.objects.all() + cached_names = [repo.repo_name for repo in cached_repos] + + self.assertNotIn("python-discord/INVALID", cached_names) + + def test_dont_clean_up_unstale_metadata(self): + """Tests that we don't clean up good metadata when we start the HomeView.""" + repo_data = RepositoryMetadata( + repo_name="python-discord/site", + description="testrepo", + forks=42, + stargazers=42, + language="English", + last_updated=timezone.now() - timedelta(seconds=HomeView.repository_cache_ttl + 1), + ) + repo_data.save() + self.home_view.__init__() + cached_repos = RepositoryMetadata.objects.all() + cached_names = [repo.repo_name for repo in cached_repos] + + self.assertIn("python-discord/site", cached_names) diff --git a/pydis_site/apps/home/views/home.py b/pydis_site/apps/home/views/home.py index 09969f1d..e77772fb 100644 --- a/pydis_site/apps/home/views/home.py +++ b/pydis_site/apps/home/views/home.py @@ -1,4 +1,4 @@ -import datetime +import logging from typing import Dict, List import requests @@ -10,11 +10,13 @@ from django.views import View from pydis_site.apps.home.models import RepositoryMetadata +log = logging.getLogger(__name__) + class HomeView(View): """The main landing page for the website.""" - github_api = "https://api.github.com/users/python-discord/repos" + github_api = "https://api.github.com/users/python-discord/repos?per_page=100" repository_cache_ttl = 3600 # Which of our GitHub repos should be displayed on the front page, and in which order? @@ -22,82 +24,98 @@ class HomeView(View): "python-discord/site", "python-discord/bot", "python-discord/snekbox", - "python-discord/seasonalbot", + "python-discord/sir-lancebot", "python-discord/metricity", "python-discord/django-simple-bulma", ] + def __init__(self): + """Clean up stale RepositoryMetadata.""" + RepositoryMetadata.objects.exclude(repo_name__in=self.repos).delete() + def _get_api_data(self) -> Dict[str, Dict[str, str]]: - """Call the GitHub API and get information about our repos.""" - repo_dict: Dict[str, dict] = {repo_name: {} for repo_name in self.repos} + """ + Call the GitHub API and get information about our repos. + + If we're unable to get that info for any reason, return an empty dict. + """ + repo_dict = {} # Fetch the data from the GitHub API api_data: List[dict] = requests.get(self.github_api).json() # Process the API data into our dict for repo in api_data: - full_name = repo["full_name"] - - if full_name in self.repos: - repo_dict[full_name] = { - "full_name": repo["full_name"], - "description": repo["description"], - "language": repo["language"], - "forks_count": repo["forks_count"], - "stargazers_count": repo["stargazers_count"], - } + try: + full_name = repo["full_name"] + + if full_name in self.repos: + repo_dict[full_name] = { + "full_name": repo["full_name"], + "description": repo["description"], + "language": repo["language"], + "forks_count": repo["forks_count"], + "stargazers_count": repo["stargazers_count"], + } + # Something is not right about the API data we got back from GitHub. + except (TypeError, ConnectionError, KeyError) as e: + log.error( + "Unable to parse the GitHub repository metadata from response!", + extra={ + 'api_data': api_data, + 'error': e + } + ) + continue return repo_dict def _get_repo_data(self) -> List[RepositoryMetadata]: """Build a list of RepositoryMetadata objects that we can use to populate the front page.""" - # Try to get site data from the cache - try: - repo_data = RepositoryMetadata.objects.get(repo_name="python-discord/site") + database_repositories = [] - # If the data is stale, we should refresh it. - if (timezone.now() - repo_data.last_updated).seconds > self.repository_cache_ttl: + # First, let's see if we have any metadata cached. + cached_data = RepositoryMetadata.objects.all() - # Try to get new data from the API. If it fails, return the cached data. - try: - api_repositories = self._get_api_data() - except (TypeError, ConnectionError): - return RepositoryMetadata.objects.all() - database_repositories = [] - - # Update or create all RepoData objects in self.repos - for repo_name, api_data in api_repositories.items(): - try: - repo_data = RepositoryMetadata.objects.get(repo_name=repo_name) - repo_data.description = api_data["description"] - repo_data.language = api_data["language"] - repo_data.forks = api_data["forks_count"] - repo_data.stargazers = api_data["stargazers_count"] - except RepositoryMetadata.DoesNotExist: - repo_data = RepositoryMetadata( - repo_name=api_data["full_name"], - description=api_data["description"], - forks=api_data["forks_count"], - stargazers=api_data["stargazers_count"], - language=api_data["language"], - ) - repo_data.save() - database_repositories.append(repo_data) - return database_repositories - - # Otherwise, if the data is fresher than 2 minutes old, we should just return it. - else: - return RepositoryMetadata.objects.all() + # If we don't, we have to create some! + if not cached_data: - # If this is raised, the database has no repodata at all, we will create them all. - except RepositoryMetadata.DoesNotExist: - database_repositories = [] - try: - # Get new data from API - api_repositories = self._get_api_data() + # Try to get new data from the API. If it fails, we'll return an empty list. + # In this case, we simply don't display our projects on the site. + api_repositories = self._get_api_data() + + # Create all the repodata records in the database. + for api_data in api_repositories.values(): + repo_data = RepositoryMetadata( + repo_name=api_data["full_name"], + description=api_data["description"], + forks=api_data["forks_count"], + stargazers=api_data["stargazers_count"], + language=api_data["language"], + ) + + repo_data.save() + database_repositories.append(repo_data) - # Create all the repodata records in the database. - for api_data in api_repositories.values(): + return database_repositories + + # If the data is stale, we should refresh it. + if (timezone.now() - cached_data[0].last_updated).seconds > self.repository_cache_ttl: + # Try to get new data from the API. If it fails, return the cached data. + api_repositories = self._get_api_data() + + if not api_repositories: + return RepositoryMetadata.objects.all() + + # Update or create all RepoData objects in self.repos + for repo_name, api_data in api_repositories.items(): + try: + repo_data = RepositoryMetadata.objects.get(repo_name=repo_name) + repo_data.description = api_data["description"] + repo_data.language = api_data["language"] + repo_data.forks = api_data["forks_count"] + repo_data.stargazers = api_data["stargazers_count"] + except RepositoryMetadata.DoesNotExist: repo_data = RepositoryMetadata( repo_name=api_data["full_name"], description=api_data["description"], @@ -105,23 +123,14 @@ class HomeView(View): stargazers=api_data["stargazers_count"], language=api_data["language"], ) - repo_data.save() - database_repositories.append(repo_data) - except TypeError: - for repo_name in self.repos: - repo_data = RepositoryMetadata( - last_updated=timezone.now() - datetime.timedelta(minutes=50), - repo_name=repo_name, - description="Not available.", - forks=999, - stargazers=999, - language="Python", - ) - repo_data.save() - database_repositories.append(repo_data) - + repo_data.save() + database_repositories.append(repo_data) return database_repositories + # Otherwise, if the data is fresher than 2 minutes old, we should just return it. + else: + return RepositoryMetadata.objects.all() + def get(self, request: WSGIRequest) -> HttpResponse: """Collect repo data and render the homepage view.""" repo_data = self._get_repo_data() diff --git a/pydis_site/constants.py b/pydis_site/constants.py index 0b76694a..c7ab5db0 100644 --- a/pydis_site/constants.py +++ b/pydis_site/constants.py @@ -1,5 +1,3 @@ -import git +import os -# Git SHA -repo = git.Repo(search_parent_directories=True) -GIT_SHA = repo.head.object.hexsha +GIT_SHA = os.environ.get("GIT_SHA", "development") diff --git a/pydis_site/hosts.py b/pydis_site/hosts.py index 898e8cdc..5a837a8b 100644 --- a/pydis_site/hosts.py +++ b/pydis_site/hosts.py @@ -4,7 +4,10 @@ from django_hosts import host, patterns host_patterns = patterns( '', host(r'admin', 'pydis_site.apps.admin.urls', name="admin"), + # External API ingress (over the net) host(r'api', 'pydis_site.apps.api.urls', name='api'), + # Internal API ingress (cluster local) + host(r'pydis-api', 'pydis_site.apps.api.urls', name='internal_api'), host(r'staff', 'pydis_site.apps.staff.urls', name='staff'), host(r'.*', 'pydis_site.apps.home.urls', name=settings.DEFAULT_HOST) ) diff --git a/pydis_site/settings.py b/pydis_site/settings.py index 1ae97b86..300452fa 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -28,14 +28,14 @@ if typing.TYPE_CHECKING: env = environ.Env( DEBUG=(bool, False), - SITE_SENTRY_DSN=(str, "") + SITE_DSN=(str, "") ) sentry_sdk.init( - dsn=env('SITE_SENTRY_DSN'), + dsn=env('SITE_DSN'), integrations=[DjangoIntegration()], send_default_pii=True, - release=f"pydis-site@{GIT_SHA}" + release=f"site@{GIT_SHA}" ) # Build paths inside the project like this: os.path.join(BASE_DIR, ...) @@ -47,21 +47,7 @@ DEBUG = env('DEBUG') # SECURITY WARNING: keep the secret key used in production secret! if DEBUG: - ALLOWED_HOSTS = env.list( - 'ALLOWED_HOSTS', - default=[ - 'pythondiscord.local', - 'api.pythondiscord.local', - 'admin.pythondiscord.local', - 'staff.pythondiscord.local', - '0.0.0.0', # noqa: S104 - 'localhost', - 'web', - 'api.web', - 'admin.web', - 'staff.web' - ] - ) + ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=['*']) SECRET_KEY = "yellow polkadot bikini" # noqa: S105 elif 'CI' in os.environ: @@ -76,10 +62,7 @@ else: 'admin.pythondiscord.com', 'api.pythondiscord.com', 'staff.pythondiscord.com', - 'pydis.com', - 'api.pydis.com', - 'admin.pydis.com', - 'staff.pydis.com', + 'pydis-api.default.svc.cluster.local', ] ) SECRET_KEY = env('SECRET_KEY') @@ -392,6 +375,7 @@ AUTHENTICATION_BACKENDS = ( ACCOUNT_ADAPTER = "pydis_site.utils.account.AccountAdapter" ACCOUNT_EMAIL_REQUIRED = False # Undocumented allauth setting; don't require emails ACCOUNT_EMAIL_VERIFICATION = "none" # No verification required; we don't use emails for anything +ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https" # We use this validator because Allauth won't let us actually supply a list with no validators # in it, and we can't just give it a lambda - that'd be too easy, I suppose. diff --git a/pydis_site/static/css/base/base.css b/pydis_site/static/css/base/base.css index dc7c504d..a1d325f9 100644 --- a/pydis_site/static/css/base/base.css +++ b/pydis_site/static/css/base/base.css @@ -12,42 +12,69 @@ main.site-content { flex: 1; } -div.card.has-equal-height { +.card.has-equal-height { height: 100%; display: flex; flex-direction: column; } -.navbar-item.is-fullsize { - padding: 0; +.navbar { + padding-right: 0.8em; } -.navbar-item.is-fullsize img { - max-height: 4.75rem; +.navbar-item .navbar-link { + padding-left: 1.5em; + padding-right: 2.5em; +} + +.navbar-link:not(.is-arrowless)::after { + right: 1.125em; + margin-top: -0.455em; } .navbar-item.has-no-highlight:hover { background-color: transparent; } -.navbar-item.has-left-margin-1 { - margin-left: 1rem; +#navbar-banner { + background-color: transparent; } -.navbar-item.has-left-margin-2 { - margin-left: 2rem; +#navbar-banner img { + max-height: 3rem; } -.navbar-item.has-left-margin-3 { - margin-left: 3rem; +#discord-btn a { + color: transparent; + background-image: url(../../images/navbar/discord.svg); + background-size: 200%; + background-position: 100% 50%; + background-repeat: no-repeat; + padding-left: 2.5rem; + padding-right: 2.5rem; + background-color: #697ec4ff; + margin-left: 0.5rem; + transition: all 0.2s cubic-bezier(.25,.8,.25,1); + overflow: hidden; } -#navbar-banner { +#discord-btn:hover a { + box-shadow: 0 1px 4px rgba(0,0,0,0.16), 0 1px 6px rgba(0,0,0,0.23); + /*transform: scale(1.03) translate3d(0,0,0);*/ + background-size: 200%; + background-position: 1% 50%; +} + +#discord-btn:hover { background-color: transparent; } -#navbar-banner img { - max-height: 3rem; +#linode-logo { + padding-left: 15px; + background: url(https://www.linode.com/wp-content/uploads/2021/01/Linode-Logo-Black.svg) no-repeat center; + filter: invert(1) grayscale(1); + background-size: 60px; + color: #00000000; } #django-logo { @@ -111,3 +138,17 @@ button.is-size-navbar-menu, a.is-size-navbar-menu { .codehilite-wrap { margin-bottom: 1em; } + +/* 16:9 aspect ratio fixing */ +.force-aspect-container { + position: relative; + padding-bottom: 56.25%; +} + +.force-aspect-content { + top: 0; + left: 0; + width: 100%; + height: 100%; + position: absolute; +} diff --git a/pydis_site/static/css/error_pages.css b/pydis_site/static/css/error_pages.css new file mode 100644 index 00000000..77bb7e2b --- /dev/null +++ b/pydis_site/static/css/error_pages.css @@ -0,0 +1,66 @@ +html { + height: 100%; +} + +body { + background-color: #7289DA; + background-image: url("https://raw.githubusercontent.com/python-discord/branding/master/logos/banner_pattern/banner_pattern.svg"); + background-size: 128px; + font-family: "Hind", "Helvetica", "Arial", sans-serif; + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} + +h1, +p { + color: black; + padding: 0; + margin: 0; + margin-bottom: 10px; +} + +h1 { + margin-bottom: 15px; + font-size: 26px; +} + +p, +li { + line-height: 125%; +} + +a { + color: #7289DA; +} + +ul { + margin-bottom: 0; +} + +li { + margin-top: 10px; +} + +.error-box { + display: flex; + flex-direction: column; + max-width: 512px; + background-color: white; + border-radius: 20px; + overflow: hidden; + box-shadow: 5px 7px 40px rgba(0, 0, 0, 0.432); +} + +.logo-box { + display: flex; + justify-content: center; + height: 80px; + padding: 15px; + background-color: #758ad4; +} + +.content-box { + padding: 25px; +} diff --git a/pydis_site/static/css/home/index.css b/pydis_site/static/css/home/index.css index ba856a8e..58ca8888 100644 --- a/pydis_site/static/css/home/index.css +++ b/pydis_site/static/css/home/index.css @@ -1,87 +1,214 @@ -.discord-banner { - border-radius: 0.5rem; +h1 { + padding-bottom: 0.5em; } -.hero-image { - width: 20rem; - margin: auto; +/* Mobile-only notice banner */ + +#mobile-notice { + margin: 5px; + margin-bottom: -10px!important; } -.hero-body { - padding-top: 1rem; - padding-bottom: 1rem; +/* Wave hero */ + +#wave-hero { + position: relative; + background-color: #7289DA; + color: #fff; + height: 32vw; + min-height: 270px; + max-height: 500px; + overflow-x: hidden; + width: 100%; + padding: 0; } -.section-sp img { - height: 5rem; - margin-right: 2rem; +#wave-hero .container { + z-index: 4; /* keep hero contents above wave animations */ } -.video-container iframe, -.video-container object, -.video-container embed { - width: 100%; - height: calc(92vw * 0.5625); - margin: 8px auto auto auto; +@media screen and (min-width: 769px) and (max-width: 1023px) { + #wave-hero .columns { + margin: 0 1em 0 1em; /* Stop cards touching canvas edges in table-view */ + } +} + +#wave-hero iframe { + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); + transition: all 0.3s cubic-bezier(.25,.8,.25,1); + border-radius: 10px; + margin-top: 1em; + border: none; +} + +#wave-hero iframe:hover { + box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22); +} + +#wave-hero-right img{ + border-radius: 10px; + box-shadow: 0 1px 6px rgba(0,0,0,0.16), 0 1px 6px rgba(0,0,0,0.23); + margin-top: 1em; + text-align: right; +} + +#wave-hero .wave { + background: url(../../images/waves/wave_dark.svg) repeat-x; + position: absolute; + bottom: 0; + width: 6400px; + animation-name: wave; + animation-timing-function: cubic-bezier(.36,.45,.63,.53); + animation-iteration-count: infinite; + transform: translate3d(0,0,0); /* Trigger 3D acceleration for smoother animation */ } -div.card.github-card { +#front-wave { + animation-duration: 60s; + animation-delay: -50s; + opacity: 0.5; + height: 178px; +} + +#back-wave { + animation-duration: 65s; + height: 198px; +} + +#bottom-wave { + animation-duration: 50s; + animation-delay: -10s; + background: url(../../images/waves/wave_white.svg) repeat-x !important; + height: 26px; + z-index: 3; +} + +@keyframes wave { + 0% { + margin-left: 0; + } + 100% { + margin-left: -1600px; + } +} + +/* Showcase */ + +#showcase { + margin: 0 1em; +} + +#showcase .mini-timeline { + height: 3px; + position: relative; + margin: 50px 0 50px 0; + background: linear-gradient(to right, #ffffff00, #666666ff, #ffffff00); + text-align: center; +} + +#showcase .mini-timeline i { + display: inline-block; + vertical-align: middle; + width: 30px; + height: 30px; + border-radius: 50%; + position: relative; + top: -14px; + margin: 0 4% 0 4%; + background-color: #3EB2EF; + color: white; + font-size: 15px; + line-height: 33px; + border:none; + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); + transition: all 0.3s cubic-bezier(.25,.8,.25,1); +} + +#showcase .mini-timeline i:hover { + box-shadow: 0 2px 5px rgba(0,0,0,0.16), 0 2px 5px rgba(0,0,0,0.23); + transform: scale(1.5); +} + +/* Projects */ + +#projects { + padding-top: 0; +} + +#projects .card { box-shadow: none; border: #d1d5da 1px solid; border-radius: 3px; + transition: all 0.2s cubic-bezier(.25,.8,.25,1); + height: 100%; + display: flex; + flex-direction: column; +} + +#projects .card:hover { + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); } -div.repo-headline { +#projects .card-header { + box-shadow: none; font-size: 1.25rem; - margin-bottom: 8px; + padding: 1.5rem 1.5rem 0 1.5rem; } -span.repo-language-dot { - border-radius: 50%; - height: 12px; - width: 12px; - top: 1px; - display: inline-block; - position: relative; +#projects .card-header-icon { + font-size: 1.5rem; + padding: 0 1rem 0 0; } -span.repo-language-dot.python { - background-color: #3572A5; +#projects .card-header-title { + padding: 0; + color: #7289DA; } -span.repo-language-dot.html { - background-color: #e34c26; +#projects .card:hover .card-header-title { + color: #363636; } -span.repo-language-dot.css { - background-color: #563d7c; +#projects .card-content { + padding-top: 8px; + padding-bottom: 1rem; } -span.repo-language-dot.javascript { - background-color: #f1e05a; +#projects .card-footer { + margin-top: auto; + border: none; } -#repo-footer-item { - margin-left: 1.2rem; +#projects .card-footer-item { + border: none; } -#sponsors-hero { - padding-top: 2rem; - padding-bottom: 3rem; +#projects .card-footer-item i { + margin-right: 0.5rem; } -@media screen and (min-width: 1088px) { - .video-container iframe { - height: calc(42vw * 0.5625); - max-height: 371px; - max-width: 660px; - } +#projects .repo-language-dot { + border-radius: 50%; + height: 12px; + width: 12px; + top: -1px; + display: inline-block; + position: relative; } -@media screen and (max-width: 1087px) { - .video-container iframe { - height: calc(92vw * 0.5625); - max-height: none; - max-width: none; - } +#projects .repo-language-dot.python { background-color: #3572A5; } +#projects .repo-language-dot.html { background-color: #e34c26; } +#projects .repo-language-dot.css { background-color: #563d7c; } +#projects .repo-language-dot.javascript { background-color: #f1e05a; } + +/* Sponsors */ + +#sponsors .hero-body { + padding-top: 2rem; + padding-bottom: 3rem; +} + +#sponsors img { + height: 5rem; + margin-right: 2rem; } diff --git a/pydis_site/static/css/home/timeline.css b/pydis_site/static/css/home/timeline.css index 73698c7c..0a4dfbb6 100644 --- a/pydis_site/static/css/home/timeline.css +++ b/pydis_site/static/css/home/timeline.css @@ -61,20 +61,6 @@ button, input, textarea, select { background-color: #576297 !important; } -.video-container { - position: relative; - width: 100%; - height: 0; - padding-bottom: 75%; -} -.video { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; -} - .pydis-logo-banner { background-color: #7289DA !important; border-radius: 10px; @@ -3497,8 +3483,8 @@ mark { align-items: center; -ms-flex-negative: 0; flex-shrink: 0; - width: 40px; - height: 40px; + width: 30px; + height: 30px; border-radius: 50%; box-shadow: 0 0 0 4px hsl(0, 0%, 100%), inset 0 2px 0 rgba(0, 0, 0, 0.08), 0 3px 0 4px rgba(0, 0, 0, 0.05); box-shadow: 0 0 0 4px var(--color-white), inset 0 2px 0 rgba(0, 0, 0, 0.08), 0 3px 0 4px rgba(0, 0, 0, 0.05) @@ -3509,17 +3495,23 @@ mark { color: white; } +@media (max-width: 64rem) { + .cd-timeline__img i { + font-size: 0.9em; + } +} + .cd-timeline__img img { - width: 50px; - height: 50px; + width: 40px; + height: 40px; margin-left: 2px; margin-top: 2px; } @media (max-width: 64rem) { .cd-timeline__img img { - width: 30px; - height: 30px; + width: 20px; + height: 20px; margin-left: 2px; margin-top: 2px; } @@ -3532,7 +3524,7 @@ mark { -ms-flex-order: 1; order: 1; margin-left: calc(5% - 30px); - will-change: transform + will-change: transform; } .cd-timeline__block:nth-child(even) .cd-timeline__img { @@ -3646,6 +3638,14 @@ mark { -webkit-animation-name: cd-bounce-2-inverse; animation-name: cd-bounce-2-inverse } + .cd-timeline__img--bounce-out { + -webkit-animation: cd-bounce-out-1 0.6s; + animation: cd-bounce-out-1 0.6s; + } + .cd-timeline__content--bounce-out { + -webkit-animation: cd-bounce-out-2 0.6s; + animation: cd-bounce-out-2 0.6s; + } } @-webkit-keyframes cd-bounce-1 { @@ -3749,3 +3749,75 @@ mark { transform: translateX(0) } } + +@-webkit-keyframes cd-bounce-out-1 { + 0% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1) + } + + 60% { + -webkit-transform: scale(1.2); + transform: scale(1.2) + } + + 100% { + opacity: 0; + -webkit-transform: scale(0.5); + transform: scale(0.5) + } +} + +@keyframes cd-bounce-out-1 { + 0% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } + + 60% { + -webkit-transform: scale(1.2); + transform: scale(1.2); + } + + 100% { + opacity: 0; + -webkit-transform: scale(0.5); + transform: scale(0.5); + } +} + +@-webkit-keyframes cd-bounce-out-2 { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0) + } + 60% { + -webkit-transform: translateX(20px); + transform: translateX(20px) + } + 100% { + opacity: 0; + -webkit-transform: translateX(-100px); + transform: translateX(-100px) + } +} + +@keyframes cd-bounce-out-2 { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0) + } + 60% { + -webkit-transform: translateX(20px); + transform: translateX(20px) + } + 100% { + opacity: 0; + -webkit-transform: translateX(-100px); + transform: translateX(-100px) + } +} diff --git a/pydis_site/static/images/events/100k.png b/pydis_site/static/images/events/100k.png Binary files differnew file mode 100644 index 00000000..ae024d77 --- /dev/null +++ b/pydis_site/static/images/events/100k.png diff --git a/pydis_site/static/images/frontpage/welcome.jpg b/pydis_site/static/images/frontpage/welcome.jpg Binary files differnew file mode 100644 index 00000000..0eb8f672 --- /dev/null +++ b/pydis_site/static/images/frontpage/welcome.jpg diff --git a/pydis_site/static/images/navbar/discord.svg b/pydis_site/static/images/navbar/discord.svg new file mode 100644 index 00000000..406e3836 --- /dev/null +++ b/pydis_site/static/images/navbar/discord.svg @@ -0,0 +1,165 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="120mm" + height="30mm" + viewBox="0 0 120 30" + version="1.1" + id="svg8" + inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)" + sodipodi:docname="discord.svg"> + <defs + id="defs2"> + <rect + x="75.819944" + y="98.265513" + width="25.123336" + height="7.8844509" + id="rect953" /> + <rect + x="75.819946" + y="98.265511" + width="25.123337" + height="7.8844509" + id="rect953-0" /> + <rect + x="75.819946" + y="98.265511" + width="25.123337" + height="7.8844509" + id="rect968" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="2.8" + inkscape:cx="194.44623" + inkscape:cy="53.152927" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="2560" + inkscape:window-height="1413" + inkscape:window-x="4880" + inkscape:window-y="677" + inkscape:window-maximized="1" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:document-rotation="0" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-52.233408,-75.88169)"> + <rect + style="fill:#ffffff;fill-opacity:1;stroke-width:0.137677;paint-order:stroke fill markers;stop-color:#000000" + id="rect832" + width="61.511906" + height="30" + x="52.23341" + y="75.881691" /> + <g + id="g910" + transform="matrix(0.90000009,0,0,0.90000009,17.445516,9.7980333)"> + <g + id="g850" + transform="matrix(0.06491223,0,0,0.06491223,109.76284,82.07218)"> + <path + class="st0" + d="m 142.8,120.1 c -5.7,0 -10.2,4.9 -10.2,11 0,6.1 4.6,11 10.2,11 5.7,0 10.2,-4.9 10.2,-11 0,-6.1 -4.6,-11 -10.2,-11 z m -36.5,0 c -5.7,0 -10.2,4.9 -10.2,11 0,6.1 4.6,11 10.2,11 5.7,0 10.2,-4.9 10.2,-11 0.1,-6.1 -4.5,-11 -10.2,-11 z" + id="path836" /> + <path + class="st0" + d="m 191.4,36.9 h -134 c -11.3,0 -20.5,9.2 -20.5,20.5 v 134 c 0,11.3 9.2,20.5 20.5,20.5 h 113.4 l -5.3,-18.3 12.8,11.8 12.1,11.1 21.6,18.7 V 57.4 C 211.9,46.1 202.7,36.9 191.4,36.9 Z m -38.6,129.5 c 0,0 -3.6,-4.3 -6.6,-8 13.1,-3.7 18.1,-11.8 18.1,-11.8 -4.1,2.7 -8,4.6 -11.5,5.9 -5,2.1 -9.8,3.4 -14.5,4.3 -9.6,1.8 -18.4,1.3 -25.9,-0.1 -5.7,-1.1 -10.6,-2.6 -14.7,-4.3 -2.3,-0.9 -4.8,-2 -7.3,-3.4 -0.3,-0.2 -0.6,-0.3 -0.9,-0.5 -0.2,-0.1 -0.3,-0.2 -0.4,-0.2 -1.8,-1 -2.8,-1.7 -2.8,-1.7 0,0 4.8,7.9 17.5,11.7 -3,3.8 -6.7,8.2 -6.7,8.2 C 75,165.8 66.6,151.4 66.6,151.4 66.6,119.5 81,93.6 81,93.6 95.4,82.9 109,83.2 109,83.2 l 1,1.2 c -18,5.1 -26.2,13 -26.2,13 0,0 2.2,-1.2 5.9,-2.8 10.7,-4.7 19.2,-5.9 22.7,-6.3 0.6,-0.1 1.1,-0.2 1.7,-0.2 6.1,-0.8 13,-1 20.2,-0.2 9.5,1.1 19.7,3.9 30.1,9.5 0,0 -7.9,-7.5 -24.9,-12.6 l 1.4,-1.6 c 0,0 13.7,-0.3 28,10.4 0,0 14.4,25.9 14.4,57.8 0,-0.1 -8.4,14.3 -30.5,15 z m 151,-86.7 H 270.6 V 117 l 22.1,19.9 v -36.2 h 11.8 c 7.5,0 11.2,3.6 11.2,9.4 v 27.7 c 0,5.8 -3.5,9.7 -11.2,9.7 h -34 v 21.1 h 33.2 c 17.8,0.1 34.5,-8.8 34.5,-29.2 V 109.6 C 338.3,88.8 321.6,79.7 303.8,79.7 Z m 174,59.7 v -30.6 c 0,-11 19.8,-13.5 25.8,-2.5 l 18.3,-7.4 c -7.2,-15.8 -20.3,-20.4 -31.2,-20.4 -17.8,0 -35.4,10.3 -35.4,30.3 v 30.6 c 0,20.2 17.6,30.3 35,30.3 11.2,0 24.6,-5.5 32,-19.9 l -19.6,-9 c -4.8,12.3 -24.9,9.3 -24.9,-1.4 z M 417.3,113 c -6.9,-1.5 -11.5,-4 -11.8,-8.3 0.4,-10.3 16.3,-10.7 25.6,-0.8 l 14.7,-11.3 c -9.2,-11.2 -19.6,-14.2 -30.3,-14.2 -16.3,0 -32.1,9.2 -32.1,26.6 0,16.9 13,26 27.3,28.2 7.3,1 15.4,3.9 15.2,8.9 -0.6,9.5 -20.2,9 -29.1,-1.8 l -14.2,13.3 c 8.3,10.7 19.6,16.1 30.2,16.1 16.3,0 34.4,-9.4 35.1,-26.6 1,-21.7 -14.8,-27.2 -30.6,-30.1 z m -67,55.5 h 22.4 V 79.7 H 350.3 Z M 728,79.7 H 694.8 V 117 l 22.1,19.9 v -36.2 h 11.8 c 7.5,0 11.2,3.6 11.2,9.4 v 27.7 c 0,5.8 -3.5,9.7 -11.2,9.7 h -34 v 21.1 H 728 c 17.8,0.1 34.5,-8.8 34.5,-29.2 V 109.6 C 762.5,88.8 745.8,79.7 728,79.7 Z M 565.1,78.5 c -18.4,0 -36.7,10 -36.7,30.5 v 30.3 c 0,20.3 18.4,30.5 36.9,30.5 18.4,0 36.7,-10.2 36.7,-30.5 V 109 C 602,88.6 583.5,78.5 565.1,78.5 Z m 14.4,60.8 c 0,6.4 -7.2,9.7 -14.3,9.7 -7.2,0 -14.4,-3.1 -14.4,-9.7 V 109 c 0,-6.5 7,-10 14,-10 7.3,0 14.7,3.1 14.7,10 z M 682.4,109 c -0.5,-20.8 -14.7,-29.2 -33,-29.2 h -35.5 v 88.8 h 22.7 v -28.2 h 4 l 20.6,28.2 h 28 L 665,138.1 c 10.7,-3.4 17.4,-12.7 17.4,-29.1 z m -32.6,12 h -13.2 v -20.3 h 13.2 c 14.1,0 14.1,20.3 0,20.3 z" + id="path838" /> + </g> + <path + id="path4789-6" + class="" + d="m 167.72059,90.383029 -3.19204,3.19205 c -0.15408,0.15408 -0.40352,0.15408 -0.55746,0 l -0.37229,-0.37231 c -0.15368,-0.15369 -0.15408,-0.40277 -4.9e-4,-0.55681 l 2.52975,-2.54167 -2.52975,-2.54164 c -0.15329,-0.15408 -0.15309,-0.40312 4.9e-4,-0.55681 l 0.37229,-0.37228 c 0.15408,-0.15408 0.40353,-0.15408 0.55746,0 l 3.19204,3.19201 c 0.15408,0.15407 0.15408,0.40354 0,0.55746 z" + inkscape:connector-curvature="0" + style="fill:#ffffff;fill-opacity:1;stroke-width:0.0164247" /> + </g> + <g + id="g904" + transform="matrix(0.90000009,0,0,0.90000009,10.464254,9.7980333)"> + <g + id="g850-3" + transform="matrix(0.06491223,0,0,0.06491223,52.083661,82.07218)"> + <path + class="st0" + d="m 142.8,120.1 c -5.7,0 -10.2,4.9 -10.2,11 0,6.1 4.6,11 10.2,11 5.7,0 10.2,-4.9 10.2,-11 0,-6.1 -4.6,-11 -10.2,-11 z m -36.5,0 c -5.7,0 -10.2,4.9 -10.2,11 0,6.1 4.6,11 10.2,11 5.7,0 10.2,-4.9 10.2,-11 0.1,-6.1 -4.5,-11 -10.2,-11 z" + id="path836-5" + style="fill:#7289da;fill-opacity:1" /> + <path + class="st0" + d="m 191.4,36.9 h -134 c -11.3,0 -20.5,9.2 -20.5,20.5 v 134 c 0,11.3 9.2,20.5 20.5,20.5 h 113.4 l -5.3,-18.3 12.8,11.8 12.1,11.1 21.6,18.7 V 57.4 C 211.9,46.1 202.7,36.9 191.4,36.9 Z m -38.6,129.5 c 0,0 -3.6,-4.3 -6.6,-8 13.1,-3.7 18.1,-11.8 18.1,-11.8 -4.1,2.7 -8,4.6 -11.5,5.9 -5,2.1 -9.8,3.4 -14.5,4.3 -9.6,1.8 -18.4,1.3 -25.9,-0.1 -5.7,-1.1 -10.6,-2.6 -14.7,-4.3 -2.3,-0.9 -4.8,-2 -7.3,-3.4 -0.3,-0.2 -0.6,-0.3 -0.9,-0.5 -0.2,-0.1 -0.3,-0.2 -0.4,-0.2 -1.8,-1 -2.8,-1.7 -2.8,-1.7 0,0 4.8,7.9 17.5,11.7 -3,3.8 -6.7,8.2 -6.7,8.2 C 75,165.8 66.6,151.4 66.6,151.4 66.6,119.5 81,93.6 81,93.6 95.4,82.9 109,83.2 109,83.2 l 1,1.2 c -18,5.1 -26.2,13 -26.2,13 0,0 2.2,-1.2 5.9,-2.8 10.7,-4.7 19.2,-5.9 22.7,-6.3 0.6,-0.1 1.1,-0.2 1.7,-0.2 6.1,-0.8 13,-1 20.2,-0.2 9.5,1.1 19.7,3.9 30.1,9.5 0,0 -7.9,-7.5 -24.9,-12.6 l 1.4,-1.6 c 0,0 13.7,-0.3 28,10.4 0,0 14.4,25.9 14.4,57.8 0,-0.1 -8.4,14.3 -30.5,15 z" + id="path838-6" + style="fill:#7289da;fill-opacity:1" + sodipodi:nodetypes="sssssccccccscccccccccccccccccccccccccccc" /> + </g> + <path + id="path4789-6-2" + class="" + d="m 107.16039,90.382629 -3.19204,3.19205 c -0.15408,0.15408 -0.40352,0.15408 -0.55746,0 l -0.37229,-0.37231 c -0.15368,-0.15369 -0.15408,-0.40277 -5.3e-4,-0.55681 l 2.52975,-2.54167 -2.52975,-2.54164 c -0.15329,-0.15408 -0.15309,-0.40312 5.3e-4,-0.55681 l 0.37229,-0.37228 c 0.15408,-0.15408 0.40353,-0.15408 0.55746,0 l 3.19204,3.19201 c 0.15408,0.15407 0.15408,0.40354 0,0.55746 z" + inkscape:connector-curvature="0" + style="fill:#7289da;fill-opacity:1;stroke-width:0.0164247" /> + <g + aria-label="JOIN US" + transform="matrix(1.2501707,0,0,1.2501707,-25.160061,-36.966352)" + id="text951" + style="font-style:normal;font-weight:normal;font-size:6.35px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect953-0);fill:#7289da;fill-opacity:1;stroke:none"> + <path + d="m 75.839362,102.56309 c 0.127,0.9525 0.89535,1.3843 1.67005,1.3843 0.85725,0 1.7145,-0.55245 1.7145,-1.53035 v -3.028953 h -2.1463 v 1.028703 h 1.02235 v 2.00025 c 0,0.26035 -0.2667,0.4318 -0.5461,0.4318 -0.2794,0 -0.57785,-0.14605 -0.64135,-0.508 z" + style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-family:'Uni Sans';-inkscape-font-specification:'Uni Sans Heavy';fill:#7289da;fill-opacity:1" + id="path850" /> + <path + d="m 79.795412,102.40434 c 0,1.0287 0.93345,1.54305 1.8669,1.54305 0.93345,0 1.86055,-0.51435 1.86055,-1.54305 v -1.5367 c 0,-1.028703 -0.93345,-1.543053 -1.8669,-1.543053 -0.93345,0 -1.86055,0.508 -1.86055,1.543053 z m 1.13665,-1.5367 c 0,-0.3302 0.3556,-0.508 0.7112,-0.508 0.3683,0 0.74295,0.15875 0.74295,0.508 v 1.5367 c 0,0.32385 -0.36195,0.48895 -0.7239,0.48895 -0.36195,0 -0.73025,-0.15875 -0.73025,-0.48895 z" + style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-family:'Uni Sans';-inkscape-font-specification:'Uni Sans Heavy';fill:#7289da;fill-opacity:1" + id="path852" /> + <path + d="m 85.262755,99.388087 h -1.13665 v 4.495803 h 1.13665 z" + style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-family:'Uni Sans';-inkscape-font-specification:'Uni Sans Heavy';fill:#7289da;fill-opacity:1" + id="path854" /> + <path + d="m 85.973945,103.88389 h 1.13665 v -1.79705 l -0.14605,-0.86995 0.03175,-0.006 0.3937,0.9017 1.016,1.77165 h 1.14935 v -4.495803 h -1.1303 v 2.038353 c 0.0063,0 0.12065,0.7747 0.127,0.7747 l -0.03175,0.006 -0.381,-0.9017 -1.08585,-1.917703 h -1.0795 z" + style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-family:'Uni Sans';-inkscape-font-specification:'Uni Sans Heavy';fill:#7289da;fill-opacity:1" + id="path856" /> + <path + d="m 92.546182,99.388087 h -1.14935 v 2.990853 c -0.0063,2.1082 3.5814,2.1082 3.58775,0 v -2.990853 h -1.14935 v 2.990853 c -0.0064,0.7239 -1.28905,0.7239 -1.28905,0 z" + style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-family:'Uni Sans';-inkscape-font-specification:'Uni Sans Heavy';fill:#7289da;fill-opacity:1" + id="path858" /> + <path + d="m 95.44178,103.13459 c 0.4191,0.53975 0.9906,0.8128 1.53035,0.8128 0.8255,0 1.7399,-0.47625 1.778,-1.3462 0.0508,-1.1049 -0.7493,-1.3843 -1.5494,-1.53035 -0.34925,-0.0762 -0.5842,-0.2032 -0.5969,-0.4191 0.01905,-0.5207 0.8255,-0.53975 1.2954,-0.0381 l 0.74295,-0.5715 c -0.46355,-0.565153 -0.9906,-0.717553 -1.5367,-0.717553 -0.8255,0 -1.6256,0.46355 -1.6256,1.346203 0,0.85725 0.6604,1.31445 1.3843,1.42875 0.3683,0.0508 0.78105,0.19685 0.76835,0.45085 -0.03175,0.4826 -1.02235,0.4572 -1.4732,-0.0889 z" + style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-family:'Uni Sans';-inkscape-font-specification:'Uni Sans Heavy';fill:#7289da;fill-opacity:1" + id="path860" /> + </g> + </g> + </g> + <style + id="style834">.st0{fill:#FFFFFF;}</style> +</svg> diff --git a/pydis_site/static/images/navbar/navbar_discordjoin.svg b/pydis_site/static/images/navbar/navbar_discordjoin.svg deleted file mode 100644 index 75e6b102..00000000 --- a/pydis_site/static/images/navbar/navbar_discordjoin.svg +++ /dev/null @@ -1,81 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="114.70044mm" - height="61.897388mm" - viewBox="0 0 114.70044 61.897388" - version="1.1" - id="svg8" - inkscape:version="0.92.4 5da689c313, 2019-01-14" - sodipodi:docname="discordjoin.svg"> - <defs - id="defs2" /> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.98994949" - inkscape:cx="-404.01729" - inkscape:cy="34.494854" - inkscape:document-units="mm" - inkscape:current-layer="layer1" - showgrid="false" - inkscape:window-width="3440" - inkscape:window-height="1409" - inkscape:window-x="2560" - inkscape:window-y="31" - inkscape:window-maximized="1" - fit-margin-top="0" - fit-margin-left="0" - fit-margin-right="0" - fit-margin-bottom="0" /> - <metadata - id="metadata5"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(-52.233408,-75.88169)"> - <path - style="opacity:1;vector-effect:none;fill:#697ec4;fill-opacity:1;stroke:none;stroke-width:0.81460673;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - d="m 60.360377,75.88169 -8.126969,61.89739 H 166.93385 V 75.88169 Z" - id="rect4758-3" - inkscape:connector-curvature="0" /> - <path - id="path4789-6" - class="" - d="m 157.65213,107.11299 -4.99428,4.9943 c -0.24107,0.24107 -0.63135,0.24107 -0.8722,0 l -0.58249,-0.58252 c -0.24045,-0.24046 -0.24107,-0.63017 -8.3e-4,-0.87119 l 3.95805,-3.9767 -3.95805,-3.97665 c -0.23984,-0.24107 -0.23953,-0.63072 8.3e-4,-0.87118 l 0.58249,-0.58248 c 0.24107,-0.24108 0.63137,-0.24108 0.8722,0 l 4.99428,4.99422 c 0.24107,0.24107 0.24107,0.63138 0,0.8722 z" - inkscape:connector-curvature="0" - style="fill:#ffffff;fill-opacity:1;stroke-width:0.02569815" /> - <image - y="94.290833" - x="67.190086" - id="image4856" - xlink:href="data:image/png;base64,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 " - preserveAspectRatio="none" - height="26.987499" - width="79.375" /> - </g> -</svg> diff --git a/pydis_site/static/images/sponsors/adafruit.png b/pydis_site/static/images/sponsors/adafruit.png Binary files differdeleted file mode 100644 index eb14cf5d..00000000 --- a/pydis_site/static/images/sponsors/adafruit.png +++ /dev/null diff --git a/pydis_site/static/images/sponsors/notion.png b/pydis_site/static/images/sponsors/notion.png Binary files differnew file mode 100644 index 00000000..44ae9244 --- /dev/null +++ b/pydis_site/static/images/sponsors/notion.png diff --git a/pydis_site/static/images/waves/wave_dark.svg b/pydis_site/static/images/waves/wave_dark.svg new file mode 100644 index 00000000..35174c47 --- /dev/null +++ b/pydis_site/static/images/waves/wave_dark.svg @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1600" + height="198" + version="1.1" + id="svg11" + sodipodi:docname="wave.svg" + inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"> + <metadata + id="metadata15"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="2560" + inkscape:window-height="1409" + id="namedview13" + showgrid="false" + inkscape:zoom="1.44625" + inkscape:cx="757.49384" + inkscape:cy="107.38903" + inkscape:window-x="4880" + inkscape:window-y="677" + inkscape:window-maximized="1" + inkscape:current-layer="svg11" /> + <defs + id="defs7"> + <linearGradient + id="a" + x1="50%" + x2="50%" + y1="-10.959%" + y2="100%"> + <stop + stop-color="#57BBC1" + stop-opacity=".25" + offset="0%" + id="stop2" /> + <stop + stop-color="#015871" + offset="100%" + id="stop4" /> + </linearGradient> + </defs> + <path + fill="url(#a)" + fill-rule="evenodd" + d="M.005 121C311 121 409.898-.25 811 0c400 0 500 121 789 121v77H0s.005-48 .005-77z" + transform="matrix(-1 0 0 1 1600 0)" + id="path9" + style="fill:#5b6daf;fill-opacity:1" /> +</svg> diff --git a/pydis_site/static/images/waves/wave_white.svg b/pydis_site/static/images/waves/wave_white.svg new file mode 100644 index 00000000..441dacff --- /dev/null +++ b/pydis_site/static/images/waves/wave_white.svg @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1600" + height="28.745832" + version="1.1" + id="svg11" + sodipodi:docname="wavew.svg" + inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"> + <metadata + id="metadata15"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="2560" + inkscape:window-height="1409" + id="namedview13" + showgrid="false" + inkscape:zoom="1.44625" + inkscape:cx="884.40031" + inkscape:cy="-61.865141" + inkscape:window-x="4880" + inkscape:window-y="677" + inkscape:window-maximized="1" + inkscape:current-layer="svg11" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" /> + <defs + id="defs7"> + <linearGradient + id="a" + x1="0.5" + x2="0.5" + y1="-0.10958999" + y2="1"> + <stop + stop-color="#57BBC1" + stop-opacity=".25" + offset="0%" + id="stop2" /> + <stop + stop-color="#015871" + offset="100%" + id="stop4" /> + </linearGradient> + </defs> + <path + fill="url(#a)" + fill-rule="evenodd" + d="M 1599.995,17.566918 C 1289,17.566918 1190.102,-0.03623696 789,5.6042811e-5 389,5.6042811e-5 289,17.566918 0,17.566918 v 11.178914 h 1600 c 0,0 -0.01,-6.968673 -0.01,-11.178914 z" + id="path9" + style="fill:#ffffff;fill-opacity:1;stroke-width:0.381026" /> +</svg> diff --git a/pydis_site/static/js/timeline/main.js b/pydis_site/static/js/timeline/main.js index a4bf4f31..2ff7df57 100644 --- a/pydis_site/static/js/timeline/main.js +++ b/pydis_site/static/js/timeline/main.js @@ -1,5 +1,5 @@ (function(){ - // Vertical Timeline - by CodyHouse.co + // Vertical Timeline - by CodyHouse.co (modified) function VerticalTimeline( element ) { this.element = element; this.blocks = this.element.getElementsByClassName("cd-timeline__block"); @@ -32,17 +32,36 @@ var self = this; for( var i = 0; i < this.blocks.length; i++) { (function(i){ - if( self.contents[i].classList.contains("cd-timeline__content--hidden") && self.blocks[i].getBoundingClientRect().top <= window.innerHeight*self.offset ) { + if((self.contents[i].classList.contains("cd-timeline__content--hidden") || self.contents[i].classList.contains("cd-timeline__content--bounce-out")) && self.blocks[i].getBoundingClientRect().top <= window.innerHeight*self.offset ) { // add bounce-in animation self.images[i].classList.add("cd-timeline__img--bounce-in"); self.contents[i].classList.add("cd-timeline__content--bounce-in"); self.images[i].classList.remove("cd-timeline__img--hidden"); self.contents[i].classList.remove("cd-timeline__content--hidden"); + self.images[i].classList.remove("cd-timeline__img--bounce-out"); + self.contents[i].classList.remove("cd-timeline__content--bounce-out"); } })(i); } }; + VerticalTimeline.prototype.hideBlocksScroll = function () { + if ( ! "classList" in document.documentElement ) { + return; + } + var self = this; + for( var i = 0; i < this.blocks.length; i++) { + (function(i){ + if(self.contents[i].classList.contains("cd-timeline__content--bounce-in") && self.blocks[i].getBoundingClientRect().top > window.innerHeight*self.offset ) { + self.images[i].classList.remove("cd-timeline__img--bounce-in"); + self.contents[i].classList.remove("cd-timeline__content--bounce-in"); + self.images[i].classList.add("cd-timeline__img--bounce-out"); + self.contents[i].classList.add("cd-timeline__content--bounce-out"); + } + })(i); + } + } + var verticalTimelines = document.getElementsByClassName("js-cd-timeline"), verticalTimelinesArray = [], scrolling = false; @@ -60,11 +79,25 @@ (!window.requestAnimationFrame) ? setTimeout(checkTimelineScroll, 250) : window.requestAnimationFrame(checkTimelineScroll); } }); + + function animationEnd(event) { + if (event.target.classList.contains("cd-timeline__img--bounce-out")) { + event.target.classList.add("cd-timeline__img--hidden"); + event.target.classList.remove("cd-timeline__img--bounce-out"); + } else if (event.target.classList.contains("cd-timeline__content--bounce-out")) { + event.target.classList.add("cd-timeline__content--hidden"); + event.target.classList.remove("cd-timeline__content--bounce-out"); + } + } + + window.addEventListener("animationend", animationEnd); + window.addEventListener("webkitAnimationEnd", animationEnd); } function checkTimelineScroll() { verticalTimelinesArray.forEach(function(timeline){ timeline.showBlocks(); + timeline.hideBlocksScroll(); }); scrolling = false; }; diff --git a/pydis_site/templates/404.html b/pydis_site/templates/404.html new file mode 100644 index 00000000..42e317d2 --- /dev/null +++ b/pydis_site/templates/404.html @@ -0,0 +1,34 @@ +{% load static %} + +<!DOCTYPE html> +<html lang="en"> + +<head> + <title>Python Discord | 404</title> + + <meta charset="UTF-8"> + + <link rel="preconnect" href="https://fonts.gstatic.com"> + <link href="https://fonts.googleapis.com/css2?family=Hind:wght@400;600&display=swap" rel="stylesheet"> + <link rel="stylesheet" href="{% static "css/error_pages.css" %}"> +</head> + +<body> + <div class="error-box"> + <div class="logo-box"> + <img src="https://raw.githubusercontent.com/python-discord/branding/b67897df93e572c1576a9026eb78c785a794d226/logos/logo_banner/logo_site_banner.svg" + alt="Python Discord banner" /> + </div> + <div class="content-box"> + <h1>404 — Not Found</h1> + <p>We couldn't find the page you're looking for. Here are a few things to try out:</p> + <ul> + <li>Double check the URL. Are you sure you typed it out correctly? + <li>Come join <a href="https://discord.gg/python">our Discord Server</a>. Maybe we can help you out over + there + </ul> + </div> + </div> +</body> + +</html> diff --git a/pydis_site/templates/500.html b/pydis_site/templates/500.html new file mode 100644 index 00000000..869892ec --- /dev/null +++ b/pydis_site/templates/500.html @@ -0,0 +1,29 @@ +{% load static %} + +<!DOCTYPE html> +<html lang="en"> + +<head> + <title>Python Discord | 500</title> + + <meta charset="UTF-8"> + + <link rel="preconnect" href="https://fonts.gstatic.com"> + <link href="https://fonts.googleapis.com/css2?family=Hind:wght@400;600&display=swap" rel="stylesheet"> + <link rel="stylesheet" href="{% static "css/error_pages.css" %}"> +</head> + +<body> + <div class="error-box"> + <div class="logo-box"> + <img src="https://raw.githubusercontent.com/python-discord/branding/b67897df93e572c1576a9026eb78c785a794d226/logos/logo_banner/logo_site_banner.svg" + alt="Python Discord banner" /> + </div> + <div class="content-box"> + <h1>500 — Internal Server Error</h1> + <p>Something went wrong at our end. Please try again shortly, or if the problem persists, please let us know <a href="https://discord.gg/python">on Discord</a>.</p> + </div> + </div> +</body> + +</html> diff --git a/pydis_site/templates/base/footer.html b/pydis_site/templates/base/footer.html index 90f06f3c..bca43b5d 100644 --- a/pydis_site/templates/base/footer.html +++ b/pydis_site/templates/base/footer.html @@ -1,7 +1,7 @@ <footer class="footer has-background-dark has-text-light"> <div class="content has-text-centered"> <p> - Built with <a href="https://www.djangoproject.com/"><span id="django-logo">django</span></a> and <a href="https://bulma.io"><span id="bulma-logo">Bulma</span></a> <br/> © {% now "Y" %} <span id="pydis-text">Python Discord</span> + Powered by <a href="https://www.linode.com/?r=3bc18ce876ff43ea31f201b91e8e119c9753f085"><span id="linode-logo">Linode</span></a><br>Built with <a href="https://www.djangoproject.com/"><span id="django-logo">django</span></a> and <a href="https://bulma.io"><span id="bulma-logo">Bulma</span></a> <br/> © {% now "Y" %} <span id="pydis-text">Python Discord</span> </p> </div> </footer> diff --git a/pydis_site/templates/base/navbar.html b/pydis_site/templates/base/navbar.html index 1bf5b7aa..64e3654b 100644 --- a/pydis_site/templates/base/navbar.html +++ b/pydis_site/templates/base/navbar.html @@ -20,7 +20,7 @@ <div class="navbar-menu is-paddingless" id="navbar_menu"> <div class="navbar-end"> - {# Discord invite - only visible in the hamburger on mobile sizes. #} + {# Burger-menu Discord #} <a class="navbar-item is-hidden-desktop" href="https://discord.gg/python"> <span class="icon is-size-4 is-medium"><i class="fab fa-discord"></i></span> <span> Discord</span> @@ -57,7 +57,7 @@ </a> {# More #} - <div class="navbar-item has-dropdown is-hoverable has-left-margin-1"> + <div class="navbar-item has-dropdown is-hoverable"> <a class="navbar-link is-hidden-touch"> More </a> @@ -125,12 +125,14 @@ </div> </div> + + {# Desktop Nav Discord #} + <div id="discord-btn" class="buttons is-hidden-touch"> + <a href="https://discord.gg/python" class="button is-large is-primary">Discord</a> + </div> + </div> - {# Join us on Discord! #} - <a class="navbar-item is-fullsize has-no-highlight has-left-margin-1" href="https://discord.gg/python"> - <img class="is-hidden-touch" src="{% static "images/navbar/navbar_discordjoin.svg" %}" alt="Join us on Discord!"/> - </a> </div> </nav> diff --git a/pydis_site/templates/home/index.html b/pydis_site/templates/home/index.html index f31363a4..04815b7f 100644 --- a/pydis_site/templates/home/index.html +++ b/pydis_site/templates/home/index.html @@ -9,12 +9,69 @@ {% block content %} {% include "base/navbar.html" %} - <section class="section"> + <!-- Mobile-only Notice --> + <section id="mobile-notice" class="message is-primary is-hidden-tablet"> + <div class="message-header"> + <p>100K Member Milestone!</p> + </div> + <div class="message-body"> + Thanks to all our members for helping us create this friendly and helpful community! + <br><br> + As a nice treat, we've created a <a href="{% url 'timeline' %}">Timeline page</a> for people + to discover the events that made our community what it is today. Be sure to check it out! + </div> + </section> + + <!-- Wave Hero --> + <section id="wave-hero" class="section is-hidden-mobile"> + + <div class="container"> + <div class="columns is-variable is-8"> + + {# Embedded Welcome video #} + <div id="wave-hero-left" class="column is-half"> + <div class="force-aspect-container"> + <iframe + class="force-aspect-content" + src="https://www.youtube.com/embed/ZH26PuX3re0" + srcdoc=" + <style> + *{padding:0;margin:0;overflow:hidden} + html,body{height:100%} + img,span{position:absolute;width:100%;top:0;bottom:0;margin:auto} + span{height:1.5em;text-align:center;font:68px/1.5 sans-serif;color:#FFFFFFEE;text-shadow:0 0 0.1em #00000020} + </style> + <a href=https://www.youtube.com/embed/ZH26PuX3re0?autoplay=1> + <img src='{% static "images/frontpage/welcome.jpg" %}' alt='Welcome to Python Discord'> + <span>▶</span> + </a>" + allow="autoplay; accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" + allowfullscreen + ></iframe> + </div> + </div> + + {# Right side content #} + <div id="wave-hero-right" class="column is-half"> + <img src="{% static "images/events/100k.png" %}" alt="100K members!"> + </div> - {# Who are we? #} - <div class="container is-spaced"> + </div> + </div> + + {# Animated wave elements #} + <span id="front-wave" class="wave"></span> + <span id="back-wave" class="wave"></span> + <span id="bottom-wave" class="wave"></span> + + </section> + + <!-- Main Body --> + <section id="body" class="section"> + + <div class="container"> <h1 class="is-size-1">Who are we?</h1> - <br> + <div class="columns is-desktop"> <div class="column is-half-desktop content"> <p> @@ -31,68 +88,122 @@ </p> <p> You can find help with most Python-related problems in one of our help channels. - Our staff of over 50 dedicated expert Helpers are available around the clock + Our staff of over 90 dedicated expert Helpers are available around the clock in every timezone. Whether you're looking to learn the language or working on a complex project, we've got someone who can help you if you get stuck. </p> </div> - {# Right column container #} - <div class="column is-half-desktop"> - <iframe width="560" height="315" src="https://www.youtube.com/embed/ZH26PuX3re0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> - </div> - </div> + {# Showcase box #} + <section id="showcase" class="column is-half-desktop has-text-centered"> + <article class="box"> + + <header class="title">New Timeline!</header> - {# Projects #} - <h1 class="is-size-1">Projects</h1> - <br> - <div class="columns is-multiline is-tablet"> - - {# Display projects from HomeView.repos #} - {% for repo in repo_data %} - <div class="column is-one-third-desktop is-half-tablet"> - <div class="card has-equal-height github-card"> - <div class="card-content"> - <div class="repo-headline"> - <i class="fab fa-github"></i> - <a href="https://github.com/{{ repo.repo_name }}"> <strong>{{ repo.repo_name }}</strong></a> - </div> - <div> - {{ repo.description }} - <br><br> - </span><span class="repo-language-dot {{ repo.language | lower }}"></span> {{ repo.language }} - <span id="repo-footer-item"><i class="fas fa-star"></i> {{ repo.stargazers }}</span> - <span id="repo-footer-item"><i class="fas fa-code-branch"></i> {{ repo.forks }}</span> - </div> - </div> + <div class="mini-timeline"> + <i class="fa fa-asterisk"></i> + <i class="fa fa-code"></i> + <i class="fab fa-python"></i> + <i class="fa fa-alien-monster"></i> + <i class="fa fa-duck"></i> + <i class="fa fa-bug"></i> </div> - </div> - {% endfor %} + + <p class="subtitle"> + Start from our humble beginnings to discover the events that made our community what it is today. + </p> + + <div class="buttons are-large is-centered"> + <a href="{% url 'timeline' %}" class="button is-primary"> + <span>Check it out!</span> + <span class="icon"> + <i class="fas fa-arrow-right"></i> + </span> + </a> + </div> + + </article> + </section> </div> </div> </section> - {# Sponsors #} - <section class="section-sp hero is-light"> - <div id="sponsors-hero" class="hero-body"> + <!-- Projects --> + {% if repo_data %} + <section id="projects" class="section"> + <div class="container"> + <h1 class="is-size-1">Projects</h1> + + <div class="columns is-multiline is-tablet"> + + {# Generate project data from HomeView.repos #} + {% for repo in repo_data %} + <div class="column is-one-third-desktop is-half-tablet"> + + <a href="https://github.com/{{ repo.repo_name }}"> + <article class="card"> + + <header class="card-header"> + <span class="card-header-icon"> + <span class="icon"><i class="fab fa-github"></i></span> + </span> + <div class="card-header-title"> + {{ repo.repo_name|cut:"python-discord/" }} + </div> + </header> + + <p class="card-content"> + {{ repo.description }} + </p> + + <footer class="card-footer"> + <div class="card-footer-item"> + <i class="repo-language-dot {{ repo.language | lower }}"></i> + {{ repo.language }} + </div> + <div class="card-footer-item"> + <i class="fas fa-star"></i> + {{ repo.stargazers }} + </div> + <div class="card-footer-item"> + <i class="fas fa-code-branch"></i> + {{ repo.forks }} + </div> + </footer> + + </article> + </a> + + </div> + {% endfor %} + + </div> + + </div> + </section> + {% endif %} + + <!-- Sponsors --> + <section id="sponsors" class="hero is-light"> + <div class="hero-body"> <div class="container"> <h1 class="title is-6 has-text-grey"> Sponsors </h1> <div class="columns is-mobile is-multiline"> - <a href="https://linode.com" class="column is-narrow"> + <a href="https://www.linode.com/?r=3bc18ce876ff43ea31f201b91e8e119c9753f085" class="column is-narrow"> <img src="{% static "images/sponsors/linode.png" %}" alt="Linode"/> </a> <a href="https://jetbrains.com" class="column is-narrow"> <img src="{% static "images/sponsors/jetbrains.png" %}" alt="JetBrains"/> </a> - <a href="https://adafruit.com" class="column is-narrow"> - <img src="{% static "images/sponsors/adafruit.png" %}" alt="Adafruit"/> - </a> <a href="https://sentry.io" class="column is-narrow"> <img src="{% static "images/sponsors/sentry.png" %}" alt="Sentry"/> </a> + <a href="https://notion.so" class="column is-narrow"> + <img src="{% static "images/sponsors/notion.png" %}" alt="Notion"/> + </a> </div> </div> </div> diff --git a/pydis_site/templates/home/timeline.html b/pydis_site/templates/home/timeline.html index 5c71f3a7..f3c58fc2 100644 --- a/pydis_site/templates/home/timeline.html +++ b/pydis_site/templates/home/timeline.html @@ -14,7 +14,7 @@ <div class="container max-width-lg cd-timeline__container"> <div class="cd-timeline__block"> <div class="cd-timeline__img cd-timeline__img--picture"> - <img src={% static "images/timeline/cd-icon-picture.svg" %} alt="Picture"> + <img src="{% static "images/timeline/cd-icon-picture.svg" %}" alt="Picture"> </div> <div class="cd-timeline__content text-component"> @@ -231,8 +231,8 @@ <div class="cd-timeline__content text-component"> <h2>PyDis hits 15,000 members; the “hot ones special” video is released</h2> - <div class="video-container"> - <iframe class="video" src="https://www.youtube.com/embed/DIBXg8Qh7bA" frameborder="0" + <div class="force-aspect-container"> + <iframe class="force-aspect-content" src="https://www.youtube.com/embed/DIBXg8Qh7bA" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> </div> @@ -319,8 +319,8 @@ developers join us to judge the event and help out our members during the event. One of them, @tshirtman, even joins our staff!</p> - <div class="video-container"> - <iframe class="video" src="https://www.youtube.com/embed/8fbZsGrqBzo" frameborder="0" + <div class="force-aspect-container"> + <iframe class="force-aspect-content" src="https://www.youtube.com/embed/8fbZsGrqBzo" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> </div> @@ -377,8 +377,8 @@ Several of the Code Jam participants also end up getting involved contributing to the Arcade repository.</p> - <div class="video-container"> - <iframe class="video" src="https://www.youtube.com/embed/KkLXMvKfEgs" frameborder="0" + <div class="force-aspect-container"> + <iframe class="force-aspect-content" src="https://www.youtube.com/embed/KkLXMvKfEgs" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> </div> @@ -450,8 +450,8 @@ Code Jam for 2020 attracts hundreds of participants, and sees the creation of some fantastic projects. Check them out in our judge stream below:</p> - <div class="video-container"> - <iframe class="video" src="https://www.youtube.com/embed/OFtm8f2iu6c" frameborder="0" + <div class="force-aspect-container"> + <iframe class="force-aspect-content" src="https://www.youtube.com/embed/OFtm8f2iu6c" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> </div> @@ -479,6 +479,25 @@ </div> </div> + <div class="cd-timeline__block"> + <div class="cd-timeline__img cd-timeline__img--picture"> + <img src="{% static "images/timeline/cd-icon-picture.svg" %}" alt="Picture"> + </div> + + <div class="cd-timeline__content text-component"> + <h2>Python Discord hosts the 2020 CPython Core Developer Q&A</h2> + <div class="force-aspect-container"> + <iframe class="force-aspect-content" src="https://www.youtube.com/embed/gXMdfBTcOfQ" frameborder="0" + allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" + allowfullscreen></iframe> + </div> + + <div class="flex justify-between items-center"> + <span class="cd-timeline__date">Oct 21st, 2020</span> + </div> + </div> + </div> + <div class="cd-timeline__block"> <div class="cd-timeline__img pastel-dark-blue cd-timeline__img--picture"> <i class="fa fa-users"></i> @@ -490,7 +509,7 @@ and one we're very proud of. To commemorate it, we create this timeline.</p> <div class="flex justify-between items-center"> - <span class="cd-timeline__date">Sep ??, 2020</span> + <span class="cd-timeline__date">Oct 22nd, 2020</span> </div> </div> </div> |