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.pngBinary files differ new 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.jpgBinary files differ new 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.pngBinary files differ deleted 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.pngBinary files differ new 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> | 
