From 655e3172532205cb087f9342853d4cb746326d86 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Caux?= <git@jscaux.org>
Date: Thu, 28 Oct 2021 09:31:07 +0200
Subject: [PATCH] Rework homepage, introduce portal page

---
 scipost_django/ontology/context_processors.py |  22 ++-
 scipost_django/ontology/forms.py              |  51 ++++-
 .../ontology/session_specialty_form.html      |   8 +
 scipost_django/ontology/urls.py               |  10 +
 scipost_django/ontology/views.py              |  42 ++++-
 .../scipost/assets/css/_list_group.scss       |  36 ----
 .../static/scipost/assets/css/_navbar.scss    |  15 ++
 .../scipost/templates/scipost/_hx_news.html   |  14 ++
 ...pates_in.html => _hx_participates_in.html} |   0
 ...ublications.html => _hx_publications.html} |   3 +
 ..._index_sponsors.html => _hx_sponsors.html} |   6 +-
 ..._submissions.html => _hx_submissions.html} |   3 +
 .../templates/scipost/_index_news.html        |  21 ---
 .../scipost/templates/scipost/bare_base.html  |   4 +-
 .../scipost/templates/scipost/header.html     |  15 +-
 .../header_with_acad_field_and_spec.html      |  22 +++
 .../scipost/templates/scipost/index.html      | 175 +++++++++++++-----
 .../scipost/templates/scipost/portal.html     |  52 ++++++
 scipost_django/scipost/urls.py                |  41 ++--
 scipost_django/scipost/views.py               |  41 ++--
 20 files changed, 420 insertions(+), 161 deletions(-)
 create mode 100644 scipost_django/ontology/templates/ontology/session_specialty_form.html
 create mode 100644 scipost_django/scipost/templates/scipost/_hx_news.html
 rename scipost_django/scipost/templates/scipost/{_index_participates_in.html => _hx_participates_in.html} (100%)
 rename scipost_django/scipost/templates/scipost/{_index_publications.html => _hx_publications.html} (88%)
 rename scipost_django/scipost/templates/scipost/{_index_sponsors.html => _hx_sponsors.html} (75%)
 rename scipost_django/scipost/templates/scipost/{_index_submissions.html => _hx_submissions.html} (80%)
 delete mode 100644 scipost_django/scipost/templates/scipost/_index_news.html
 create mode 100644 scipost_django/scipost/templates/scipost/header_with_acad_field_and_spec.html
 create mode 100644 scipost_django/scipost/templates/scipost/portal.html

diff --git a/scipost_django/ontology/context_processors.py b/scipost_django/ontology/context_processors.py
index 2b8e0d02f..93cf1d60a 100644
--- a/scipost_django/ontology/context_processors.py
+++ b/scipost_django/ontology/context_processors.py
@@ -3,7 +3,7 @@ __license__ = "AGPL v3"
 
 
 from .models import Branch, AcademicField, Specialty
-from .forms import SessionAcademicFieldForm
+from .forms import SessionAcademicFieldForm, SessionSpecialtyForm
 
 
 def ontology_processor(request):
@@ -15,13 +15,27 @@ def ontology_processor(request):
         'branches': Branch.objects.all(),
         'acad_fields': AcademicField.objects.all(),
     }
-    initial = {}
+    initial_acad_field = {}
     if request.session.get('session_acad_field_slug', None):
         try:
             context['session_acad_field'] = AcademicField.objects.get(
                 slug=request.session.get('session_acad_field_slug'))
-            initial['acad_field_slug'] = request.session.get('session_acad_field_slug')
+            initial_acad_field['acad_field_slug'] = request.session.get('session_acad_field_slug')
         except AcademicField.DoesNotExist:
             context['session_acad_field'] = None
-    context['session_acad_field_form'] = SessionAcademicFieldForm(initial=initial)
+    context['session_acad_field_form'] = SessionAcademicFieldForm(initial=initial_acad_field)
+    initial_specialty = {}
+    # If AcademicField is set, deal with Specialty
+    if context['session_acad_field']:
+        if request.session.get('session_specialty_slug', None):
+            try:
+                context['session_specialty'] = Specialty.objects.get(
+                    slug=request.session.get('session_specialty_slug'))
+                initial_specialty['specialty_slug'] = request.session.get('session_specialty_slug')
+            except Specialty.DoesNotExist:
+                context['session_specialty'] = None
+            context['session_specialty_form'] = SessionSpecialtyForm(
+                acad_field_slug=request.session['session_acad_field_slug'],
+                initial=initial_specialty
+            )
     return context
diff --git a/scipost_django/ontology/forms.py b/scipost_django/ontology/forms.py
index 5ab547dc3..b27e04efc 100644
--- a/scipost_django/ontology/forms.py
+++ b/scipost_django/ontology/forms.py
@@ -10,7 +10,7 @@ from crispy_bootstrap5.bootstrap5 import FloatingField
 from dal import autocomplete
 
 from .constants import TOPIC_RELATIONS_ASYM
-from .models import Branch, AcademicField, Tag, Topic
+from .models import Branch, AcademicField, Specialty, Tag, Topic
 
 
 def academic_field_slug_choices():
@@ -20,16 +20,16 @@ def academic_field_slug_choices():
         )),
     )
     for branch in Branch.objects.all():
-        if branch.name == 'Multidisciplinary':
-            continue
-        subchoices = ()
-        for acad_field in branch.academic_fields.all():
-            subchoices += (
-                (acad_field.slug, acad_field.name),
+        if branch.name != 'Multidisciplinary' and branch.journals.active().exists():
+            subchoices = ()
+            for acad_field in branch.academic_fields.all():
+                if acad_field.journals.active().exists():
+                    subchoices += (
+                        (acad_field.slug, acad_field.name),
+                    )
+            choices += (
+                (branch.name, subchoices),
             )
-        choices += (
-            (branch.name, subchoices),
-        )
     return choices
 
 
@@ -49,6 +49,37 @@ class SessionAcademicFieldForm(forms.Form):
         )
 
 
+def specialty_slug_choices(acad_field_slug):
+    specialties = Specialty.objects.filter(
+        acad_field__slug=acad_field_slug) if acad_field_slug else Specialty.objects.none()
+    choices = (
+        ('', '--------'),
+    )
+    for specialty in specialties.all():
+        choices += ((specialty.slug, str(specialty)),)
+    return choices
+
+
+class SessionSpecialtyForm(forms.Form):
+    specialty_slug = forms.ChoiceField(
+        label='Specialty',
+    )
+
+    def __init__(self, *args, **kwargs):
+        try:
+            acad_field_slug = kwargs.pop('acad_field_slug')
+        except KeyError:
+            acad_field_slug = ''
+        super().__init__(*args, **kwargs)
+        self.fields['specialty_slug'].choices = specialty_slug_choices(acad_field_slug)
+        self.helper = FormHelper(self)
+        self.helper.disable_csrf = True
+        self.helper.show_errors = True
+        self.helper.layout = Layout(
+            Div(FloatingField('specialty_slug'))
+        )
+
+
 class SelectTagsForm(forms.Form):
     tags = forms.ModelMultipleChoiceField(
         queryset=Tag.objects.all(),
diff --git a/scipost_django/ontology/templates/ontology/session_specialty_form.html b/scipost_django/ontology/templates/ontology/session_specialty_form.html
new file mode 100644
index 000000000..aeb52e69f
--- /dev/null
+++ b/scipost_django/ontology/templates/ontology/session_specialty_form.html
@@ -0,0 +1,8 @@
+{% load crispy_forms_tags %}
+<form id="session_specialty_form"
+      hx-get="{% url 'ontology:set_session_specialty' %}"
+      hx-trigger="change"
+      hx-swap="outerHTML"
+>
+  {% crispy session_specialty_form %}
+</form>
diff --git a/scipost_django/ontology/urls.py b/scipost_django/ontology/urls.py
index 1f504a004..f5f38e6db 100644
--- a/scipost_django/ontology/urls.py
+++ b/scipost_django/ontology/urls.py
@@ -39,6 +39,16 @@ urlpatterns = [
         views.set_session_acad_field,
         name='set_session_acad_field'
     ),
+    path(
+        '_hx_session_specialty_form',
+        views._hx_session_specialty_form,
+        name='_hx_session_specialty_form'
+    ),
+    path(
+        'set_session_specialty',
+        views.set_session_specialty,
+        name='set_session_specialty'
+    ),
     path(
         '',
         views.ontology,
diff --git a/scipost_django/ontology/views.py b/scipost_django/ontology/views.py
index 1ca413595..2d981cd3a 100644
--- a/scipost_django/ontology/views.py
+++ b/scipost_django/ontology/views.py
@@ -17,7 +17,7 @@ from guardian.decorators import permission_required
 
 from .models import AcademicField, Specialty, Tag, Topic, RelationAsym
 from .forms import (
-    SessionAcademicFieldForm,
+    SessionAcademicFieldForm, SessionSpecialtyForm,
     SelectTagsForm, SelectLinkedTopicForm,
     AddRelationAsymForm
 )
@@ -31,6 +31,7 @@ def set_session_acad_field(request):
     form = SessionAcademicFieldForm(request.GET or None)
     if form.is_valid():
         request.session['session_acad_field_slug'] = form.cleaned_data['acad_field_slug']
+        request.session['session_specialty_slug'] = ''
     try:
         initial = {
             'acad_field_slug': AcademicField.objects.get(
@@ -48,6 +49,45 @@ def set_session_acad_field(request):
     return response
 
 
+def _hx_session_specialty_form(request):
+    """Serve the session Specialty choice form."""
+    context = {
+        'session_specialty_form': SessionSpecialtyForm(
+            acad_field_slug=request.session.get('session_acad_field_slug', None),
+            initial={ 'specialty_slug': request.session.get('session_specialty_slug', None)}
+        )
+    }
+    return render(request, 'ontology/session_specialty_form.html', context)
+
+
+def set_session_specialty(request):
+    """Set the Specialty to be viewed in the current user session."""
+    form = SessionSpecialtyForm(
+        request.GET or None,
+        acad_field_slug=request.session.get('session_acad_field_slug', ''),
+    )
+    if form.is_valid():
+        request.session['session_specialty_slug'] = form.cleaned_data['specialty_slug']
+    try:
+        initial = {
+            'specialty_slug': Specialty.objects.get(
+                slug=request.session['session_specialty_slug']).slug
+        }
+    except (KeyError, Specialty.DoesNotExist):
+        initial = {}
+    form = SessionSpecialtyForm(
+        acad_field_slug=request.session['session_acad_field_slug'],
+        initial=initial
+    )
+    response = render(
+        request,
+        'ontology/session_specialty_form.html',
+        context={ 'session_specialty_form': form}
+    )
+    response['HX-Trigger'] = 'session-specialty-set'
+    return response
+
+
 def ontology(request):
     context = {
         'select_linked_topic_form': SelectLinkedTopicForm(),
diff --git a/scipost_django/scipost/static/scipost/assets/css/_list_group.scss b/scipost_django/scipost/static/scipost/assets/css/_list_group.scss
index 626ea2b2e..c7044c28b 100644
--- a/scipost_django/scipost/static/scipost/assets/css/_list_group.scss
+++ b/scipost_django/scipost/static/scipost/assets/css/_list_group.scss
@@ -107,42 +107,6 @@ li,
 }
 
 
-ul.news-list {
-    list-style: none;
-    margin: 0;
-    padding: 0;
-
-    h3 {
-        margin: 0.25rem 0;
-
-        a {
-            color: $scipost-darkblue;
-        }
-    }
-
-    li {
-        margin-right: 0.5rem;
-        padding: 0.35rem 0;
-        border-top: 1px solid #bbb;
-
-        &:first-child {
-            border-top: 0;
-        }
-
-        p {
-            max-height: 0;
-            overflow: hidden;
-            padding: 0;
-            margin: 0.5rem 0;
-        }
-
-        &:first-child p {
-            max-height: 1000px;
-        }
-    }
-}
-
-
 ul.communications {
     list-style: none;
     padding: 0;
diff --git a/scipost_django/scipost/static/scipost/assets/css/_navbar.scss b/scipost_django/scipost/static/scipost/assets/css/_navbar.scss
index a6d5647f4..52ad00bec 100644
--- a/scipost_django/scipost/static/scipost/assets/css/_navbar.scss
+++ b/scipost_django/scipost/static/scipost/assets/css/_navbar.scss
@@ -252,3 +252,18 @@ header .nav-item {
 	}
     }
 }
+
+#session_specialty_form {
+
+    div #div_id_specialty_slug {
+	margin: 0rem !important;
+
+	#id_specialty_slug {
+	    background-color: $scipost-darkblue;
+	    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
+	    border: 2px solid $scipost-lightblue;
+	    border-radius: 2px;
+	    color: $white;
+	}
+    }
+}
diff --git a/scipost_django/scipost/templates/scipost/_hx_news.html b/scipost_django/scipost/templates/scipost/_hx_news.html
new file mode 100644
index 000000000..03adff8f1
--- /dev/null
+++ b/scipost_django/scipost/templates/scipost/_hx_news.html
@@ -0,0 +1,14 @@
+<div id="further-news" class="sp-swap"
+     hx-get="{% url 'scipost:_hx_news' %}"
+     hx-trigger="every 15s"
+     hx-swap="outerHTML swap:1s"
+>
+  <h3><a href="{% url 'news:news' %}#news_{{ news.id }}">{{ news.headline }}</a></h3>
+  <div class="text-muted">{{ news.date|date:'j F Y' }}</div>
+  <p>
+    {{ news.blurb_short }}
+    <br>
+    <br>
+    <a href="{% url 'news:news' %}#news_{{ news.id }}" class="my-1">Read more &rarr;</a>
+  </p>
+</div>
diff --git a/scipost_django/scipost/templates/scipost/_index_participates_in.html b/scipost_django/scipost/templates/scipost/_hx_participates_in.html
similarity index 100%
rename from scipost_django/scipost/templates/scipost/_index_participates_in.html
rename to scipost_django/scipost/templates/scipost/_hx_participates_in.html
diff --git a/scipost_django/scipost/templates/scipost/_index_publications.html b/scipost_django/scipost/templates/scipost/_hx_publications.html
similarity index 88%
rename from scipost_django/scipost/templates/scipost/_index_publications.html
rename to scipost_django/scipost/templates/scipost/_hx_publications.html
index 5b19820dd..cb2e9dadb 100644
--- a/scipost_django/scipost/templates/scipost/_index_publications.html
+++ b/scipost_django/scipost/templates/scipost/_hx_publications.html
@@ -1,5 +1,7 @@
 {% load journals_extras %}
 
+<p class="mb-0 px-3 text-muted">{{ session_acad_field }}{% if session_specialty %}: {{ session_specialty }}{% endif %}</p>
+<hr>
 <ul class="list-group list-group-flush px-3 mb-3">
   {% for publication in publications %}
     <li class="list-group-item py-2">
@@ -26,5 +28,6 @@
     </div>
   </div>
 {% else %}
+  <hr>
   <p class="mb-3 px-3"><a href="{% url 'journals:publications' %}">View all Publications</a></p>
 {% endif %}
diff --git a/scipost_django/scipost/templates/scipost/_index_sponsors.html b/scipost_django/scipost/templates/scipost/_hx_sponsors.html
similarity index 75%
rename from scipost_django/scipost/templates/scipost/_index_sponsors.html
rename to scipost_django/scipost/templates/scipost/_hx_sponsors.html
index 472c7bfb2..933d0298d 100644
--- a/scipost_django/scipost/templates/scipost/_index_sponsors.html
+++ b/scipost_django/scipost/templates/scipost/_hx_sponsors.html
@@ -1,7 +1,7 @@
 {% load static %}
-<div id="sponsor-logos"
-     hx-get="{% url 'scipost:_index_sponsors' %}"
-     hx-trigger="every 15s"
+<div id="sponsor-logos" class="sp-swap"
+     hx-get="{% url 'scipost:_hx_sponsors' %}"
+     hx-trigger="every 16s"
      hx-swap="outerHTML swap:1s"
 >
   <ul class="list list-unstyled">
diff --git a/scipost_django/scipost/templates/scipost/_index_submissions.html b/scipost_django/scipost/templates/scipost/_hx_submissions.html
similarity index 80%
rename from scipost_django/scipost/templates/scipost/_index_submissions.html
rename to scipost_django/scipost/templates/scipost/_hx_submissions.html
index 457a6043e..9ba0ef78b 100644
--- a/scipost_django/scipost/templates/scipost/_index_submissions.html
+++ b/scipost_django/scipost/templates/scipost/_hx_submissions.html
@@ -1,5 +1,7 @@
 {% load journals_extras %}
 
+<p class="mb-0 px-3 text-muted">{{ session_acad_field }}{% if session_specialty %}: {{ session_specialty }}{% endif %}</p>
+<hr>
 <ul class="list-group list-group-flush px-3 mb-3">
   {% for submission in submissions %}
     <li class="list-group-item py-2">
@@ -13,5 +15,6 @@
 </ul>
 
 {% if not session_acad_field or session_acad_field.journals.active|length > 0 %}
+  <hr>
   <p class="mb-3 px-3"><a href="{% url 'submissions:submissions' %}">View all Submissions</a></p>
 {% endif %}
diff --git a/scipost_django/scipost/templates/scipost/_index_news.html b/scipost_django/scipost/templates/scipost/_index_news.html
deleted file mode 100644
index e47e22fbe..000000000
--- a/scipost_django/scipost/templates/scipost/_index_news.html
+++ /dev/null
@@ -1,21 +0,0 @@
-<div id="news">
-  <h2 class="title">
-    News
-    <button class="btn btn-link btn-sm m-0 mb-1" href="{% url 'scipost:feeds' %}" aria-label="RSS feeds">{% include 'bi/rss-fill.html' %}</button>
-  </h2>
-  <ul class="news-list">
-    {% for news in news_items %}
-      <li>
-        <h3><a href="{% url 'news:news' %}#news_{{ news.id }}">{{ news.headline }}</a></h3>
-        <div class="text-muted">{{ news.date|date:'j F Y' }}</div>
-        <p>
-	  {{ news.blurb_short }}
-	  <br>
-	  <br>
-	  <a href="{% url 'news:news' %}#news_{{ news.id }}" class="my-1">Read more &rarr;</a>
-        </p>
-      </li>
-    {% endfor %}
-  </ul>
-  <a href="{% url 'news:news' %}" class="my-1">See complete News list</a>
-</div>
diff --git a/scipost_django/scipost/templates/scipost/bare_base.html b/scipost_django/scipost/templates/scipost/bare_base.html
index 5379d5f12..365b663bc 100644
--- a/scipost_django/scipost/templates/scipost/bare_base.html
+++ b/scipost_django/scipost/templates/scipost/bare_base.html
@@ -25,7 +25,9 @@
   </head>
 
   <body class="{% block body_class %}{% endblock %}">
-    {% include 'scipost/header.html' %}
+    {% block header %}
+      {% include 'scipost/header.html' %}
+    {% endblock header %}
     {% include 'scipost/navbar.html' %}
     {% block breadcrumb %}{% endblock breadcrumb %}
 
diff --git a/scipost_django/scipost/templates/scipost/header.html b/scipost_django/scipost/templates/scipost/header.html
index d3eb9719c..a6bc53055 100644
--- a/scipost_django/scipost/templates/scipost/header.html
+++ b/scipost_django/scipost/templates/scipost/header.html
@@ -8,16 +8,6 @@
         <a href="{% url 'scipost:index' %}"><img src="{% static 'scipost/images/logo_scipost_RGB_HTML_groot.png' %}" alt="SciPost logo" width="180" /></a>
       </div>
 
-      <div class="mx-auto my-4 my-lg-auto">
-	<form id="session_acad_field_form"
-	      hx-get="{% url 'ontology:set_session_acad_field' %}"
-	      hx-trigger="change"
-	      hx-swap="outerHTML"
-	>
-	  {% crispy session_acad_field_form %}
-	</form>
-      </div>
-
       <div class="ms-auto me-0 my-4 my-lg-auto">
 	<form id="header-search-form" method="get" action="{% url 'scipost:search' %}">
           <div class="form-floating">
@@ -28,7 +18,10 @@
 	  </div>
 	</form>
       </div>
-
     </div>
+
+    {% block header_sup %}
+    {% endblock header_sup %}
+
   </div>
 </header>
diff --git a/scipost_django/scipost/templates/scipost/header_with_acad_field_and_spec.html b/scipost_django/scipost/templates/scipost/header_with_acad_field_and_spec.html
new file mode 100644
index 000000000..bdf10ec28
--- /dev/null
+++ b/scipost_django/scipost/templates/scipost/header_with_acad_field_and_spec.html
@@ -0,0 +1,22 @@
+{% extends 'scipost/header.html' %}
+{% load crispy_forms_tags %}
+
+{% block header_sup %}
+  <div class="d-lg-flex justify-content-between">
+    <div class="mx-auto my-4 my-lg-auto">
+      <form id="session_acad_field_form"
+	    hx-get="{% url 'ontology:set_session_acad_field' %}"
+	    hx-trigger="change"
+	    hx-swap="outerHTML"
+      >
+	{% crispy session_acad_field_form %}
+      </form>
+    </div>
+    <div class="mx-auto my-4 my-lg-auto"
+	 hx-get="{% url 'ontology:_hx_session_specialty_form' %}"
+	 hx-trigger="load, session-acad-field-set from:body"
+    >
+    </div>
+
+  </div>
+{% endblock header_sup %}
diff --git a/scipost_django/scipost/templates/scipost/index.html b/scipost_django/scipost/templates/scipost/index.html
index 07e85742a..934de2e8e 100644
--- a/scipost_django/scipost/templates/scipost/index.html
+++ b/scipost_django/scipost/templates/scipost/index.html
@@ -7,13 +7,13 @@
 {% block headsup %}
   {{ block.super }}
   <style>
-   #sponsor-logos.htmx-added {
+   .sp-swap.htmx-added {
        opacity: 0;
    }
-   #sponsor-logos.htmx-swapping {
+   .sp-swap.htmx-swapping {
        opacity: 0;
    }
-   #sponsor-logos {
+   .sp-swap {
        opacity: 1;
        transition: opacity 1s;
    }
@@ -27,58 +27,141 @@
 
 {% block content %}
 
-  <div class="row">
+  <div class="row gx-4">
     <div class="col-md-4">
-      <!-- Latest publications -->
-      <div class="card card-publications bg-light px-1 mb-2 scipost-bar">
-        <div class="card-body pb-0">
-          <h2 class="title mb-3">Latest Publications</h2>
-          <hr class="sm mb-0 mt-2">
-        </div>
-	<div hx-get="{% url 'scipost:_index_publications' %}"
-	     hx-trigger="load, session-acad-field-set from:body"
-	>
+      <h2 class="highlight text-center mt-0">Portals</h2>
+      <div class="d-flex justify-content-center">
+	<div class="btn-group-vertical w-100" role="group">
+	  {% for branch in branches %}
+	    {% if branch.name != 'Multidisciplinary' and branch.journals.all|length > 0 %}
+	      {% for acad_field in branch.academic_fields.all %}
+		{% if acad_field.journals.active.all|length > 0 %}
+		  <a type="button" class="btn-primary w-100 m-1 p-2 text-center" href={% url 'scipost:portal' %}?field={{ acad_field.slug }}><strong>{{ acad_field.name }}</strong></a>
+		{% endif %}
+	      {% endfor %}
+	    {% endif %}
+	  {% endfor %}
 	</div>
-      </div><!-- End latest publications -->
+      </div>
     </div>
 
-    <div class="col-md-4">
-      <!-- Latest submissions -->
-      <div class="card card-submissions bg-light px-1 mb-2 scipost-bar">
-        <div class="card-body pb-0">
-          <h2 class="title mb-3">Latest Submissions</h2>
-          <hr class="sm mb-0 mt-2">
-        </div>
-	<div hx-get="{% url 'scipost:_index_submissions' %}"
-	     hx-trigger="load, session-acad-field-set from:body"
-	>
+    <div class="col-md-8">
+      <div class="card">
+	<div class="card-header">
+	  <h1>Genuinely Open Publishing</h1>
+	</div>
+	<div class="card-body">
+	  <p>SciPost is a complete publishing infrastructure serving professional scientists worldwide.</p>
+	  <div class="row g-2">
+	    <div class="col-lg-6">
+	      <div class="card">
+		<div class="card-header">
+		  <h3>We implement<br><a href="{% url 'scipost:about' %}#GOA">Genuine Open Access</a></h3>
+		</div>
+		<div class="card-body">
+		  <ul>
+		    <li>Community Ownership</li>
+		    <li>Open Infrastructure</li>
+		    <li>Copyright to Authors</li>
+		    <li>Open Access</li>
+		    <li>Open Citations</li>
+		    <li>Fee Free</li>
+		    <li>Non Profit</li>
+		    <li>Open Finances</li>
+		    <li>Academic Editing</li>
+		  </ul>
+		</div>
+	      </div>
+	    </div>
+	    <div class="col-lg-6">
+	      <div class="card mb-2">
+		<div class="card-header">
+		  <h3>Info</h3>
+		</div>
+		<div class="card-body">
+		  <ul>
+		    <li><a href="{% url 'scipost:contact' %}">Contact us</a></li>
+		    <li><a href="{% url 'scipost:about' %}">About SciPost</a></li>
+		    <li><a href="{% url 'scipost:foundation' %}">The Foundation</a></li>
+		    <li><a href="{% url 'careers:jobopenings' %}">Careers&#64;SciPost</a></li>
+		    <li><a href="{% url 'scipost:FAQ' %}">FAQ</a></li>
+		  </ul>
+		</div>
+	      </div>
+
+	      {% if not user.is_authenticated %}
+		<!-- Register -->
+		<div class="card border border-danger">
+		  <div class="card-header">
+		    <h3>Register</h3>
+		  </div>
+		  <div class="card-body">
+		    <p class="mb-1">Professional scientists (PhD students and above) can become Contributors to SciPost by filling the <a href="{% url 'scipost:register' %}">registration form</a>.</p>
+		  </div><!-- End Register -->
+		</div>
+	      {% endif %}
+	    </div>
+	  </div>
 	</div>
-      </div><!-- End latest submissions -->
-    </div>
-
-    <div class="col-md-4">
-      {% if not user.is_authenticated %}
-	<!-- Register -->
-	<div id="register">
-	  <h2>Register</h2>
-	  <p class="mb-1">Professional scientists (PhD students and above) can become Contributors to SciPost by filling the <a href="{% url 'scipost:register' %}">registration form</a>.</p>
-	</div><!-- End Register -->
-
-	<hr class="lg">
-      {% endif %}
-
-      <div hx-get="{% url 'scipost:_index_news' %}"
-	   hx-trigger="load"
-      >
       </div>
-
     </div>
-
   </div>
+
 {% endblock %}
 
 
 {% block content_footer %}
+
+  <div class="container-fluid text-start secondary pt-4 mt-5 border-top border-primary">
+    <div class="row">
+      <div class="col">
+	<h1 class="title">
+	  News
+	  <button class="btn btn-link btn-sm m-0 mb-1" href="{% url 'scipost:feeds' %}" aria-label="RSS feeds">{% include 'bi/rss-fill.html' %}</button>
+	</h1>
+      </div>
+      <div class="col">
+	<a href="{% url 'news:news' %}" class="my-1 float-right">See complete News list</a>
+      </div>
+    </div>
+
+    <div class="row g-2">
+      <div class="col-md-6">
+	<div class="card">
+	  <div class="card-header">
+	    <h3>Latest</h3>
+	  </div>
+	  <div class="card-body">
+            <h3><a href="{% url 'news:news' %}#news_{{ latest_newsitem.id }}">{{ latest_newsitem.headline }}</a></h3>
+            <div class="text-muted">{{ latest_newsitem.date|date:'j F Y' }}</div>
+            <p>
+	      {{ latest_newsitem.blurb_short }}
+	      <br>
+	      <br>
+	      <a href="{% url 'news:news' %}#news_{{ latest_newsitem.id }}" class="my-1">Read more &rarr;</a>
+            </p>
+	  </div>
+	</div>
+      </div>
+
+      <div class="col-md-6">
+	<div class="card">
+	  <div class="card-header">
+	    <h3>Further news</h3>
+	  </div>
+	  <div class="card-body">
+	    <div id="further-news" class="sp-swap"
+		 hx-get="{% url 'scipost:_hx_news' %}"
+		 hx-trigger="load"
+		 hx-swap="outerHTML swap:1s"
+	    >
+	    </div>
+	  </div>
+	</div>
+      </div>
+    </div>
+  </div>
+
   <div class="container-fluid text-start secondary pt-4 mt-5 border-top border-primary">
     <div class="row">
       <div class="col-md-8">
@@ -96,8 +179,8 @@
       <div class="col-md-4">
 	<a href="{% url 'sponsors:sponsors' %}">See all sponsors</a>
 	<br>
-	<div id="sponsor-logos"
-	     hx-get="{% url 'scipost:_index_sponsors' %}"
+	<div id="sponsor-logos" class="sp-swap"
+	     hx-get="{% url 'scipost:_hx_sponsors' %}"
 	     hx-trigger="load"
 	     hx-swap="outerHTML swap:1s"
 	>
@@ -107,7 +190,7 @@
 
     <hr>
 
-    <div hx-get="{% url 'scipost:_index_participates_in' %}"
+    <div hx-get="{% url 'scipost:_hx_participates_in' %}"
 	 hx-trigger="load"
     >
     </div>
diff --git a/scipost_django/scipost/templates/scipost/portal.html b/scipost_django/scipost/templates/scipost/portal.html
new file mode 100644
index 000000000..1bb714e54
--- /dev/null
+++ b/scipost_django/scipost/templates/scipost/portal.html
@@ -0,0 +1,52 @@
+{% extends 'scipost/base.html' %}
+
+{% load render_bundle from webpack_loader %}
+{% load static %}
+
+
+{% block body_class %}{{ block.super }} homepage{% endblock %}
+
+{% block meta_description %}{{ block.super }} homepage{% endblock meta_description %}
+{% block pagetitle %}Homepage{% endblock pagetitle %}
+
+{% block header %}
+  {% include 'scipost/header_with_acad_field_and_spec.html' %}
+{% endblock %}
+
+
+{% block content %}
+
+  <div class="row">
+    <div class="col-md-4">
+      <!-- Latest publications -->
+      <div class="card card-publications bg-light px-1 mb-2 scipost-bar">
+        <div class="card-body pb-0">
+          <h2 class="title mb-3">Latest Publications</h2>
+          <hr class="sm mb-0 mt-2">
+        </div>
+	<div hx-get="{% url 'scipost:_hx_publications' %}"
+	     hx-trigger="load, session-acad-field-set from:body, session-specialty-set from:body"
+	>
+	</div>
+      </div><!-- End latest publications -->
+    </div>
+
+    <div class="col-md-4">
+      <!-- Latest submissions -->
+      <div class="card card-submissions bg-light px-1 mb-2 scipost-bar">
+        <div class="card-body pb-0">
+          <h2 class="title mb-3">Latest Submissions</h2>
+          <hr class="sm mb-0 mt-2">
+        </div>
+	<div hx-get="{% url 'scipost:_hx_submissions' %}"
+	     hx-trigger="load, session-acad-field-set from:body, session-specialty-set from:body"
+	>
+	</div>
+      </div><!-- End latest submissions -->
+    </div>
+
+    <div class="col-md-4">
+    </div>
+
+  </div>
+{% endblock %}
diff --git a/scipost_django/scipost/urls.py b/scipost_django/scipost/urls.py
index f5eece501..88c8564f9 100644
--- a/scipost_django/scipost/urls.py
+++ b/scipost_django/scipost/urls.py
@@ -66,35 +66,46 @@ urlpatterns = [
         TemplateView.as_view(template_name='search/search.html'),
         name='search'
     ),
+
+    # Homepage
     path(
         '',
         views.index,
         name='index'
     ),
+
+    # Portal
+    path(
+        'portal',
+        views.portal,
+        name='portal'
+    ),
+
+    # HTMX-delivered fragments
     path(
-        '_index_publications',
-        views._index_publications,
-        name='_index_publications'
+        '_hx_publications',
+        views._hx_publications,
+        name='_hx_publications'
     ),
     path(
-        '_index_submissions',
-        views._index_submissions,
-        name='_index_submissions'
+        '_hx_submissions',
+        views._hx_submissions,
+        name='_hx_submissions'
     ),
     path(
-        '_index_news.html',
-        views._index_news,
-        name='_index_news'
+        '_hx_news.html',
+        views._hx_news,
+        name='_hx_news'
     ),
     path(
-        '_index_participates_in',
-        TemplateView.as_view(template_name='scipost/_index_participates_in.html'),
-        name='_index_participates_in'
+        '_hx_participates_in',
+        TemplateView.as_view(template_name='scipost/_hx_participates_in.html'),
+        name='_hx_participates_in'
     ),
     path(
-        '_index_sponsors',
-        views._index_sponsors,
-        name='_index_sponsors'
+        '_hx_sponsors',
+        views._hx_sponsors,
+        name='_hx_sponsors'
     ),
 
     path(
diff --git a/scipost_django/scipost/views.py b/scipost_django/scipost/views.py
index a99c0d6fc..9c5e969c1 100644
--- a/scipost_django/scipost/views.py
+++ b/scipost_django/scipost/views.py
@@ -163,48 +163,63 @@ class SearchView(SearchView):
 def index(request):
     """Homepage view of SciPost."""
     context = {
-        'submissions': Submission.objects.public().order_by('-submission_date')[:3],
-        'publications': Publication.objects.published().order_by('-publication_date',
-                                                                 '-paper_nr')[:3],
+        'latest_newsitem': NewsItem.objects.homepage().order_by('-date').first(),
     }
     return render(request, 'scipost/index.html', context)
 
 
-def _index_submissions(request):
+def portal(request):
+    """
+    Academic portal entryway.
+    """
+    if request.GET.get('field', None):
+        request.session['session_acad_field_slug'] = request.GET.get('field', None)
+    request.session['session_specialty_slug'] = ''
+    return render(request, 'scipost/portal.html')
+
+
+def _hx_submissions(request):
     submissions = Submission.objects.public()
     session_acad_field_slug = request.session.get('session_acad_field_slug', None)
     if session_acad_field_slug and session_acad_field_slug != 'all':
         submissions = submissions.filter(acad_field__slug=session_acad_field_slug)
+    session_specialty_slug = request.session.get('session_specialty_slug', None)
+    if session_specialty_slug:
+        submissions = submissions.filter(specialties__slug=session_specialty_slug)
     context = {
         'submissions': submissions.order_by('-submission_date')[:3],
     }
-    return render(request, 'scipost/_index_submissions.html', context)
+    return render(request, 'scipost/_hx_submissions.html', context)
 
 
-def _index_publications(request):
+def _hx_publications(request):
     publications = Publication.objects.published()
     session_acad_field_slug = request.session.get('session_acad_field_slug', None)
     if session_acad_field_slug and session_acad_field_slug != 'all':
         publications = publications.filter(acad_field__slug=session_acad_field_slug)
+    session_specialty_slug = request.session.get('session_specialty_slug', None)
+    if session_specialty_slug:
+        publications = publications.filter(specialties__slug=session_specialty_slug)
     context = {
         'publications': publications.order_by('-publication_date', '-paper_nr')[:3],
     }
-    return render(request, 'scipost/_index_publications.html', context)
+    return render(request, 'scipost/_hx_publications.html', context)
 
 
-def _index_news(request):
+def _hx_news(request):
+    latest_newsitem_id = NewsItem.objects.homepage().order_by('-date').first().id
     context = {
-        'news_items': NewsItem.objects.homepage().order_by('-date')[:4],
-        'latest_newsitem': NewsItem.objects.homepage().order_by('-date').first(),
+        'news': NewsItem.objects.homepage().exclude(
+            pk=latest_newsitem_id).order_by('?').first(),
     }
-    return render(request, 'scipost/_index_news.html', context)
+    return render(request, 'scipost/_hx_news.html', context)
 
 
-def _index_sponsors(request):
+def _hx_sponsors(request):
     context = {
         'current_sponsors': Organization.objects.current_sponsors().order_by('?')[:1]
     }
-    return render(request, 'scipost/_index_sponsors.html', context)
+    return render(request, 'scipost/_hx_sponsors.html', context)
 
 
 def protected_serve(request, path, show_indexes=False):
-- 
GitLab