diff --git a/app/apps/api/serializers.py b/app/apps/api/serializers.py
index 61532a14ac5367a20821b0fc24bcf713866e73e5..e69add839008d6dc165a8575f89a6b53b472510a 100644
--- a/app/apps/api/serializers.py
+++ b/app/apps/api/serializers.py
@@ -102,11 +102,15 @@ class DocumentSerializer(serializers.ModelSerializer):
     transcriptions = TranscriptionSerializer(many=True, read_only=True)
     valid_block_types = BlockTypeSerializer(many=True, read_only=True)
     valid_line_types = LineTypeSerializer(many=True, read_only=True)
+    parts_count = serializers.SerializerMethodField()
 
     class Meta:
         model = Document
         fields = ('pk', 'name', 'transcriptions',
-                  'valid_block_types', 'valid_line_types')
+                  'valid_block_types', 'valid_line_types', 'parts_count')
+
+    def get_parts_count(self, document):
+        return document.parts.count()
 
 
 class PartSerializer(serializers.ModelSerializer):
@@ -127,6 +131,7 @@ class PartSerializer(serializers.ModelSerializer):
             'image',
             'bw_image',
             'workflow',
+            'order',
             'recoverable',
             'transcription_progress'
         )
diff --git a/app/apps/api/views.py b/app/apps/api/views.py
index 19fc37659b438eebc26ae297a4cfada6ccc9d976..23722ea8683c7e712fc6e4761100ada5eac108f5 100644
--- a/app/apps/api/views.py
+++ b/app/apps/api/views.py
@@ -4,7 +4,9 @@ import logging
 from django.conf import settings
 from django.core.exceptions import PermissionDenied
 from django.db.models import Prefetch
+from django.http import HttpResponseRedirect
 from django.shortcuts import get_object_or_404
+from django.urls import reverse
 
 from rest_framework.decorators import action
 from rest_framework.response import Response
@@ -193,6 +195,22 @@ class PartViewSet(DocumentPermissionMixin, ModelViewSet):
         else:  # list & create
             return PartSerializer
 
+    @action(detail=False, methods=['get'])
+    def byorder(self, request, document_pk=None):
+        try:
+            order = int(request.GET.get('order'))
+        except ValueError:
+            return Response({'error': 'invalid order.'})
+        if not order:
+            return Response({'error': 'pass order as an url parameter.'})
+        try:
+            part = self.get_queryset().get(order=order)
+        except DocumentPart.DoesNotExist:
+            return Response({'error': 'Out of bounds.'})
+        return HttpResponseRedirect(reverse('api:part-detail',
+                                            kwargs={'document_pk': self.kwargs.get('document_pk'),
+                                                    'pk': part.pk}))
+
     @action(detail=True, methods=['post'])
     def move(self, request, document_pk=None, pk=None):
         part = DocumentPart.objects.get(document=document_pk, pk=pk)
diff --git a/app/apps/core/migrations/0043_auto_20210324_1016.py b/app/apps/core/migrations/0043_auto_20210324_1016.py
new file mode 100644
index 0000000000000000000000000000000000000000..f95b68dbe5e845947915d0a9ea60764dc7b002be
--- /dev/null
+++ b/app/apps/core/migrations/0043_auto_20210324_1016.py
@@ -0,0 +1,29 @@
+# Generated by Django 2.2.19 on 2021-03-24 10:16
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+def forward(apps, se):
+    # delete any unbound model
+    OcrModel = apps.get_model('core', 'OcrModel')
+    OcrModel.objects.filter(document__isnull=True).delete()
+
+
+def backward(apps, se):
+    pass
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('core', '0042_auto_20201009_1015'),
+    ]
+
+    operations = [
+        migrations.RunPython(forward, backward),
+        migrations.AlterField(
+            model_name='ocrmodel',
+            name='document',
+            field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='ocr_models', to='core.Document'),
+        ),
+    ]
diff --git a/app/apps/core/models.py b/app/apps/core/models.py
index aad2b42151b11b7c417bfdcdafa843a1ab8731c5..680f26f313b5a88b18c3f5133f0737c4cf062fef 100644
--- a/app/apps/core/models.py
+++ b/app/apps/core/models.py
@@ -244,6 +244,13 @@ class Document(models.Model):
             else:
                 return 'ltr'
 
+    @cached_property
+    def last_edited_part(self):
+        try:
+            return self.parts.order_by('-updated_at')[0]
+        except IndexError:
+            return None
+
     @property
     def training_model(self):
         return self.ocr_models.filter(training=True).first()
diff --git a/app/apps/users/admin.py b/app/apps/users/admin.py
index d44bb4caff795746b50e2ce697a1c71584637f90..f67807b6cbe7c604385310d9d2176dd4d68e2328 100644
--- a/app/apps/users/admin.py
+++ b/app/apps/users/admin.py
@@ -5,7 +5,6 @@ from django.contrib.auth.forms import UserChangeForm, UserCreationForm
 from django.contrib import messages
 from django.utils.translation import ngettext
 
-
 from users.models import User, ResearchField, Invitation, ContactUs, GroupOwner
 
 
@@ -15,8 +14,11 @@ class MyUserChangeForm(UserChangeForm):
 
 
 class MyUserCreationForm(UserCreationForm):
+    email = forms.EmailField()
+
     class Meta(UserCreationForm.Meta):
         model = User
+        fields = UserCreationForm.Meta.fields + ('email',)
 
     def clean_username(self):
         username = self.cleaned_data['username']
@@ -24,7 +26,7 @@ class MyUserCreationForm(UserCreationForm):
             User.objects.get(username=username)
         except User.DoesNotExist:
             return username
-        raise forms.ValidationError(self.error_messages['duplicate_username'])
+        raise forms.ValidationError('Duplicate username.')
 
 
 class MyUserAdmin(UserAdmin):
@@ -32,9 +34,13 @@ class MyUserAdmin(UserAdmin):
     add_form = MyUserCreationForm
     list_display = UserAdmin.list_display + ('last_login',)
     fieldsets = UserAdmin.fieldsets + (
-        (None, {'fields': ('fields','onboarding')}),  # second fields refers to research fields
+        (None, {'fields': ('fields', 'onboarding')}),  # second fields refers to research fields
+    )
+    add_fieldsets = (
+        (None, {
+            'fields': ('username', 'email', 'password1', 'password2')}
+        ),
     )
-
 
 class InvitationAdmin(admin.ModelAdmin):
     date_hierarchy = 'created_at'
diff --git a/app/apps/users/migrations/0014_auto_20210324_1007.py b/app/apps/users/migrations/0014_auto_20210324_1007.py
new file mode 100644
index 0000000000000000000000000000000000000000..b30d5f894fbeefe077fc286d8e48c67f5b8f0f8a
--- /dev/null
+++ b/app/apps/users/migrations/0014_auto_20210324_1007.py
@@ -0,0 +1,17 @@
+# Generated by Django 2.2.19 on 2021-03-24 10:07
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('users', '0013_auto_20210106_1305'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='contactus',
+            options={'verbose_name': 'Contact message', 'verbose_name_plural': 'Contact messages'},
+        ),
+    ]
diff --git a/app/escriptorium/context_processors.py b/app/escriptorium/context_processors.py
index 5c226b31c36778f6b5d5cb7d7de0e460a24fa622..fb733ea58179f4a6571e0843cf85720fea1a97bf 100644
--- a/app/escriptorium/context_processors.py
+++ b/app/escriptorium/context_processors.py
@@ -5,3 +5,6 @@ def enable_cookie_consent(request):
     return {'ENABLE_COOKIE_CONSENT': getattr(settings,
                                              'ENABLE_COOKIE_CONSENT',
                                              True)}
+
+def custom_homepage(request):
+    return {'CUSTOM_HOME': getattr(settings, 'CUSTOM_HOME', False)}
diff --git a/app/escriptorium/local_settings.py.example b/app/escriptorium/local_settings.py.example
index 56d974991e957b565b652834acc7908e2271b7c5..d7337561f992b14c43c74383d592ee9316429fee 100644
--- a/app/escriptorium/local_settings.py.example
+++ b/app/escriptorium/local_settings.py.example
@@ -39,3 +39,8 @@ DEBUG_TOOLBAR_PANELS = [
 ]
 
 # USE_CELERY = False
+# CELERY_TASK_ALWAYS_EAGER = True
+
+# LOGGING['loggers']['kraken']['level'] = 'DEBUG'
+
+# CUSTOM_HOME = True
diff --git a/app/escriptorium/settings.py b/app/escriptorium/settings.py
index a019a3270f3f6688d5b2fb59b8093d763b7b487a..4f6471c79ba21d39cea427be34c4c4a6f3a7c5f9 100644
--- a/app/escriptorium/settings.py
+++ b/app/escriptorium/settings.py
@@ -41,6 +41,8 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
 # SECURITY WARNING: don't run with debug turned on in production!
 DEBUG = os.getenv('DEBUG', False)
 
+CUSTOM_HOME = os.getenv('CUSTOM_HOME', False)
+
 ALLOWED_HOSTS = ['*']
 
 ASGI_APPLICATION = "escriptorium.routing.application"
@@ -87,7 +89,8 @@ ROOT_URLCONF = 'escriptorium.urls'
 TEMPLATES = [
     {
         'BACKEND': 'django.template.backends.django.DjangoTemplates',
-        'DIRS': [os.path.join(PROJECT_ROOT, 'templates')],
+        'DIRS': [os.path.join(PROJECT_ROOT, 'templates'),
+                 os.path.join(BASE_DIR, 'homepage')],  # custom homepage dir (volume in docker)
         'APP_DIRS': True,
         'OPTIONS': {
             'context_processors': [
@@ -95,7 +98,8 @@ TEMPLATES = [
                 'django.template.context_processors.request',
                 'django.contrib.auth.context_processors.auth',
                 'django.contrib.messages.context_processors.messages',
-                'escriptorium.context_processors.enable_cookie_consent'
+                'escriptorium.context_processors.enable_cookie_consent',
+                'escriptorium.context_processors.custom_homepage'
             ],
         },
     },
@@ -219,6 +223,7 @@ STATIC_URL = '/static/'
 STATIC_ROOT = os.path.join(BASE_DIR, 'static')
 STATICFILES_DIRS = [
     os.path.join(PROJECT_ROOT, 'static'),
+    os.path.join(BASE_DIR, 'homepage'),  # custom homepage directory
     FRONTEND_DIR
 ]
 
diff --git a/app/escriptorium/static/images/ephe.png b/app/escriptorium/static/images/ephe.png
new file mode 100644
index 0000000000000000000000000000000000000000..6856c3805d904a95eb64c4611840b829eb974b61
Binary files /dev/null and b/app/escriptorium/static/images/ephe.png differ
diff --git a/app/escriptorium/static/images/escriptorium_hd.png b/app/escriptorium/static/images/escriptorium_hd.png
new file mode 100644
index 0000000000000000000000000000000000000000..20afca793e0282bab0f11c4a26b83bdfbf94a990
Binary files /dev/null and b/app/escriptorium/static/images/escriptorium_hd.png differ
diff --git a/app/escriptorium/static/images/favicon.ico b/app/escriptorium/static/images/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..7278052495125e8ceba83c4ddbc80dade813f193
Binary files /dev/null and b/app/escriptorium/static/images/favicon.ico differ
diff --git a/app/escriptorium/static/images/h2020.jpg b/app/escriptorium/static/images/h2020.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9977dd90d6a92066fb847a2823d1f71fe0799ddb
Binary files /dev/null and b/app/escriptorium/static/images/h2020.jpg differ
diff --git a/app/escriptorium/static/images/horizon.jpg b/app/escriptorium/static/images/horizon.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1ef9d3b92b72e271613c1c228d7a0502c6afd899
Binary files /dev/null and b/app/escriptorium/static/images/horizon.jpg differ
diff --git a/app/escriptorium/static/images/inria.png b/app/escriptorium/static/images/inria.png
new file mode 100644
index 0000000000000000000000000000000000000000..b2997fd5a37783cd23d6962dfa692ee9773cbe0a
Binary files /dev/null and b/app/escriptorium/static/images/inria.png differ
diff --git a/app/escriptorium/static/images/openiti.png b/app/escriptorium/static/images/openiti.png
new file mode 100644
index 0000000000000000000000000000000000000000..0be04e49b604f0ea013c52a317e93d28a7db4055
Binary files /dev/null and b/app/escriptorium/static/images/openiti.png differ
diff --git a/app/escriptorium/static/images/psl.png b/app/escriptorium/static/images/psl.png
new file mode 100644
index 0000000000000000000000000000000000000000..a5df9793088b1b6c6e78321a80abfe5b7ac57076
Binary files /dev/null and b/app/escriptorium/static/images/psl.png differ
diff --git a/app/escriptorium/static/images/pslscripta.png b/app/escriptorium/static/images/pslscripta.png
new file mode 100644
index 0000000000000000000000000000000000000000..e5850eb2544e912f0a865216d3afeb8f54544aa7
Binary files /dev/null and b/app/escriptorium/static/images/pslscripta.png differ
diff --git a/app/escriptorium/static/images/regionidf.jpg b/app/escriptorium/static/images/regionidf.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c8d08a09da84bbc3b8add3d8f14fc8bc45886b39
Binary files /dev/null and b/app/escriptorium/static/images/regionidf.jpg differ
diff --git a/app/escriptorium/static/images/t1.png b/app/escriptorium/static/images/t1.png
new file mode 100644
index 0000000000000000000000000000000000000000..926bf47050f74f8fd309498a40ca433d3578f109
Binary files /dev/null and b/app/escriptorium/static/images/t1.png differ
diff --git a/app/escriptorium/static/images/t2.png b/app/escriptorium/static/images/t2.png
new file mode 100644
index 0000000000000000000000000000000000000000..c51f244e1a7ed83c5b722951bd5bef1a96fa41b0
Binary files /dev/null and b/app/escriptorium/static/images/t2.png differ
diff --git a/app/escriptorium/static/images/t3.png b/app/escriptorium/static/images/t3.png
new file mode 100644
index 0000000000000000000000000000000000000000..17d0f6b526489d9e566eeeab5de9d18312875524
Binary files /dev/null and b/app/escriptorium/static/images/t3.png differ
diff --git a/app/escriptorium/templates/base.html b/app/escriptorium/templates/base.html
index 780cdb3ddb69f4975df0009f0690dde9bd3ccbf3..ac673a3fa27359b2a874bef5bd2dc37c0398a55a 100644
--- a/app/escriptorium/templates/base.html
+++ b/app/escriptorium/templates/base.html
@@ -10,8 +10,8 @@
     {% comment %}
     <meta name="description" content="">
     <meta name="author" content="">
-    <link rel="icon" href="../../favicon.ico">
     {% endcomment %}
+    <link rel="icon" href="{% static "images/favicon.ico" %}">
 
     <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
     <!--[if lt IE 9]>
@@ -25,20 +25,24 @@
     {% endblock styles %}
   </head>
   <body class="{% block bodyclass %}{% endblock %}">
-    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
-      <a class="navbar-brand" href="{% url 'home' %}">eScriptorium</a>
-      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
-        <span class="navbar-toggler-icon"></span>
-      </button>
+      <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
+          <a class="navbar-brand" href="{% url 'home' %}">
+              <img src="{% static "images/escriptorium_hd.png" %}" width="30" height="30" class="mb-1">
+              eScriptorium
+          </a>
 
-      <div class="collapse navbar-collapse" id="navbarSupportedContent">
-        <ul class="navbar-nav mr-auto">
-          <li class="nav-item {% block nav-home-class %}{% endblock %}">
-            <a class="nav-link" href="{% url 'home'%}">Home</a>
-          </li>
-          <li class="nav-item">
-            <a class="nav-link" href="#">{% trans 'About' %}</a>
+              <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
+                  <span class="navbar-toggler-icon"></span>
+              </button>
+
+              <div class="collapse navbar-collapse" id="navbarSupportedContent">
+                  <ul class="navbar-nav mr-auto">
+                      <li class="nav-item {% block nav-home-class %}{% endblock %}">
+                          <a class="nav-link" href="{% url 'home'%}">Home</a>
           </li>
+          <!-- <li class="nav-item">
+               <a class="nav-link" href="#">{% trans 'About' %}</a>
+               </li> -->
           <li class="nav-item">
             <a class="nav-link" href="{% url 'contactus' %}">{% trans 'Contact' %}</a>
           </li>
diff --git a/app/escriptorium/templates/core/document_images.html b/app/escriptorium/templates/core/document_images.html
index 23043bad9b6f98c559b366a8c3c0791ed68fa678..7a0b01ed6cafdec2bcaa2fae639a9bd6504a0915 100644
--- a/app/escriptorium/templates/core/document_images.html
+++ b/app/escriptorium/templates/core/document_images.html
@@ -76,6 +76,7 @@
   <div id="image-card-{pk}" class="card">
     <div style="position: relative;">
       <button class="js-card-select-hdl"><i class="fas fa-square"></i></button>
+      <span class="js-card-order"></span>
       <button title="{% trans 'Delete this image and all its data (segmentation, transcriptions)' %}" class="close mr-1 js-card-delete" aria-label="Close"><span aria-hidden="true">&times;</span></button>
       <img style="object-fit: cover;" width="180" height="180" class="card-img-top lazy">
       <div class="card-btns">
diff --git a/app/escriptorium/templates/core/document_list.html b/app/escriptorium/templates/core/document_list.html
index d37afee3430b928f9201e3fb0ee7b6b0ac4675df..4b881cf8e630f07076da04d33cc8e680ecabc498 100644
--- a/app/escriptorium/templates/core/document_list.html
+++ b/app/escriptorium/templates/core/document_list.html
@@ -13,11 +13,11 @@
   <div class="col-md-12 col-md-offset-4">
     <a href="{% url 'document-create' %}" class="btn btn-success float-sm-right">{% trans 'Create new' %}</a>
     <h2>{% trans "My Documents" %}</h2>
-    
-    <table class="table table-hover">
+
+    <table id="document-list" class="table table-hover">
       <tbody>
         {% for document in object_list %}
-        <tr>
+        <tr onclick="document.location='{% url 'document-images' pk=document.pk %}'" role="button">
           <td>
             {% with part=document.parts.first %}
             {% if part %}
@@ -47,16 +47,26 @@
           </td>
           {% endcomment %}
           <td class="text-right">
-            {# <a href="{% url 'document-detail' pk=document.pk %}" class="btn btn-info disabled" title="{% trans 'View' %}"><i class="fas fa-eye"></i></a> #}
-            <a href="{% url 'document-update' pk=document.pk %}" class="btn btn-info" title="{% trans 'Edit' %}"><i class="fas fa-edit"></i></a>
+              {# <a href="{% url 'document-detail' pk=document.pk %}" class="btn btn-info disabled" title="{% trans 'View' %}"><i class="fas fa-eye"></i></a> #}
+              {# Note that doing one query per row is a lot faster than a subquery for some reason #}
+              {% if document.last_edited_part %}
+              <a href="{% url 'document-part-edit' pk=document.pk part_pk=document.last_edited_part.pk %}"
+                 class="btn btn-info"
+                 title="{% trans 'Edit last updated Element' %}">
+                  <i class="fas fa-edit"></i>
+              </a>
+              {% endif %}
+
+              <form method="post" class="inline-form" action="{% url 'document-publish' pk=document.pk %}" onsubmit="return confirm('{% trans "Do you really want to delete the document?" %}');">{% csrf_token %}
+                  <input type="hidden" name="workflow_state" value="{{ document.WORKFLOW_STATE_ARCHIVED }}">
+                  <button type="submit" value="" class="nav-item btn btn-danger" title="{% trans 'Delete' %}"><i class="fas fa-trash"></i></button>
+              </form>
           </td>
         </tr>
         {% endfor %}
       </tbody>
     </table>
   </div>
-
-
 </div>
 {% include 'includes/pagination.html' %}
 {% endblock %}
diff --git a/app/escriptorium/templates/core/document_nav.html b/app/escriptorium/templates/core/document_nav.html
index aef99ffd93fc6344d10c7de00b9b31669c8accf5..a0dea118aaca16d763f3d92ca547ad3d743206d3 100644
--- a/app/escriptorium/templates/core/document_nav.html
+++ b/app/escriptorium/templates/core/document_nav.html
@@ -14,7 +14,7 @@
       <a href="{% if document and models_count %}{% url 'document-models' document_pk=document.pk %}{% else %}#{% endif %}" class="nav-item nav-link {% if not document or not models_count %}disabled{% endif %}" id="nav-models-tab" role="tab" aria-controls="nav-doc" aria-selected="true">{% trans "Models" %}</a>
       {% endwith %}
       {% endif %}
-      {% block extra_nav %}{% endblock %}
+      {% block extra_nav %}<div class="nav-div nav-item ml-5 ">{{document.name}}</div>{% endblock %}
     </div>
   </nav>
 
diff --git a/app/escriptorium/templates/core/home.html b/app/escriptorium/templates/core/home.html
index 188cf43eecb68c28dc8858724919bbc3e4d68d00..99ae44ea2977a2f3705058af1f80160ce15eab84 100644
--- a/app/escriptorium/templates/core/home.html
+++ b/app/escriptorium/templates/core/home.html
@@ -1,14 +1,87 @@
 {% extends 'base.html' %}
-{% load i18n %}
+{% load i18n staticfiles %}
 {% block head_title %}{% trans "Homepage" %}{% endblock %}
 
 {% block nav-home-class %}active{% endblock %}
 
 {% block body %}
-<div class="jumbotron">
-  <h1>eScriptorium</h1>
-  <p>{% trans "A project providing digital recognition of handwritten documents using machine learning techniques." %}</p>
-  <p><small>Version {{ VERSION_DATE }}
-    <br/>{{ KRAKEN_VERSION }}</small></p>
+<div class="jumbotron text-center bg-light">
+    {% if CUSTOM_HOME %}
+    {% include "additional_heading.html" %}
+    {% else %}
+    <h1 class="jumbotron-heading">eScriptorium</h1>
+    <p class="lead text-muted">{% trans "A project providing digital recognition of handwritten documents using machine learning techniques." %}</p>
+    {% endif %}
 </div>
+
+<div class="container my-5">
+    <div class="row">
+        <div class="col-lg-4 text-center">
+            <img class="rounded-circle" src="{% static "images/t1.png" %}" alt="" width="140" height="140">
+            <h2>{% trans "Data Interchange" %}</h2>
+            <p class="text-muted">{% trans "Import or Export transcriptions with Alto or Page XML, Import images as zip or IIIF. Access data from any application through a full Rest API." %}</p>
+        </div>
+        <div class="col-lg-4 text-center">
+            <img class="rounded-circle" src="{% static "images/t2.png" %}" alt="Generic placeholder image" width="140" height="140">
+            <h2>{% trans "Manual Edition" %}</h2>
+            <p class="text-muted">{% trans "Make use of an ergonomic user interface leveraging modern browser technology to edit segmentations and transcriptions." %}</p>
+        </div>
+        <div class="col-lg-4 text-center">
+            <img class="rounded-circle" src="{% static "images/t3.png" %}" alt="Generic placeholder image" width="140" height="140">
+            <h2>{% trans "Automatic Transcription" %}</h2>
+            <p class="text-muted">{% trans "Train and apply new neural networks to vastly speed up the transcription process of large corpora." %}</p>
+        </div>
+    </div>
+
+    <hr/>
+</div>
+
+
+<div class="row">
+    <div class="my-5 container-fluid text-center">
+        <a href="https://www.ephe.psl.eu" target="_blank">
+            <img src="{% static "images/ephe.png" "%}" alt="EPHE logo" height="100">
+        </a>
+        <a href="https://www.resilience-ri.eu/" class="pl-4" target="_blank">
+            <img src="{% static "images/resilience.png" "%}" alt="Resilience logo" height="60">
+        </a>
+        <a href="https://scripta.psl.eu/en/" class="pl-4" target="_blank">
+            <img src="{% static "images/pslscripta.png" "%}" alt="Scripta PSL logo" height="50">
+        </a>
+        <a href="https://ec.europa.eu/programmes/horizon2020/" class="pl-4" target="_blank">
+            <img src="{% static "images/h2020.jpg" "%}" alt="Horizon 2020 logo" height="80">
+        </a>
+        <a href="https://inria.fr/fr" class="pl-4" target="_blank">
+            <img src="{% static "images/inria.png" "%}" alt="INRIA logo" height="60">
+        </a>
+        <a href="https://www.openiti.org/" class="pl-4" target="_blank">
+            <img src="{% static "images/openiti.png" "%}" alt="DIM Region ile de france logo" height="90">
+        </a>
+    </div>
+
+    {% if CUSTOM_HOME %}
+    {% include "additional_icons.html" %}
+    {% endif %}
+</div>
+
+<footer class="bg-light mt-5 pt-1">
+    <div class="container">
+        <div class="row">
+            <div class="col-6">
+                <h5>{% trans "Resources" %}</h5>
+                <ul class="list-unstyled">
+                    <li><a href="https://lectaurep.hypotheses.org/documentation/escriptorium-tutorial-en" target="_blank">{% trans "Tutorial" %}</a></li>
+                    <li><a href="https://gitlab.inria.fr/scripta/escriptorium" target="_blank">{% trans "eScriptorium open source code" %}</a> Version {{ VERSION_DATE }}</li>
+                    <li><a href="https://escripta.hypotheses.org/" target="_blank">{% trans "Scripta Blog" %}</a></li>
+                    <li><a href="http://kraken.re/" target="_blank">{{ KRAKEN_VERSION|capfirst }}</a></li>
+                    <li><a href="https://gitlab.inria.fr/scripta/escriptorium/-/wikis/home" target="_blank">Wiki</a></li>
+                </ul>
+            </div>
+
+            {% if CUSTOM_HOME %}
+            {% include "additional_footer.html" %}
+            {% endif %}
+        </div>
+    </div>
+</footer>
 {% endblock %}
diff --git a/app/homepage_example/additional_footer.html b/app/homepage_example/additional_footer.html
new file mode 100644
index 0000000000000000000000000000000000000000..3b3cfba9bf0b49f68a2180447e47c3f869088f00
--- /dev/null
+++ b/app/homepage_example/additional_footer.html
@@ -0,0 +1,9 @@
+{% load i18n %}
+<div class="col-6">
+    <h5>{% trans "Powered by" %}</h5>
+    <ul class="list-unstyled">
+        <li><a href="https://www.python.org" target="_blank">Python</a></li>
+        <li><a href="https://www.djangoproject.com" target="_blank">Django</a></li>
+        <li><a href="https://vuejs.org" target="_blank">Vue.js</a></li>
+    </ul>
+</div>
diff --git a/app/homepage_example/additional_heading.html b/app/homepage_example/additional_heading.html
new file mode 100644
index 0000000000000000000000000000000000000000..7895815b24315554e859aca654f9dc4e61621a50
--- /dev/null
+++ b/app/homepage_example/additional_heading.html
@@ -0,0 +1,3 @@
+<h1 class="jumbotron-heading">eScriptorium</h1>
+<p class="lead text-muted">A project providing digital recognition of handwritten documents using machine learning techniques.</p>
+<p>eScriptorium accounts are created on invitation only for now. If your institution doesn't provide it, you can use the contact form to ask for access.</p>
diff --git a/app/homepage_example/additional_icons.html b/app/homepage_example/additional_icons.html
new file mode 100644
index 0000000000000000000000000000000000000000..1b6ece1af55126823c5e18d33b69f9d626352b21
--- /dev/null
+++ b/app/homepage_example/additional_icons.html
@@ -0,0 +1,11 @@
+{% load staticfiles %}
+</div> <!-- example to create another row -->
+<div class="row">
+    <div class="container-fluid text-center">
+        <a href="http://www.python.org" target="_blank">
+            <img src="{% static "python.png" %}" alt="custom logo" height="50">
+        </a>
+        <a href="http://www.djangoproject.com" target="_blank">
+            <img src="{% static "django.png" %}" alt="custom logo" height="50">
+        </a>
+    </div>
diff --git a/app/homepage_example/django.png b/app/homepage_example/django.png
new file mode 100644
index 0000000000000000000000000000000000000000..2a1d873eeb34af6bf0c69baec82fe45e1bf749b9
Binary files /dev/null and b/app/homepage_example/django.png differ
diff --git a/app/homepage_example/python.png b/app/homepage_example/python.png
new file mode 100644
index 0000000000000000000000000000000000000000..a215d687b662d4a105fc116492d0a3add6435cfb
Binary files /dev/null and b/app/homepage_example/python.png differ
diff --git a/app/requirements.txt b/app/requirements.txt
index facd20bafb489790e35c68229a5cfd2b915e23f4..578411a956c52c1b8fe894d5f9f3482d30970226 100644
--- a/app/requirements.txt
+++ b/app/requirements.txt
@@ -12,7 +12,7 @@ django-redis==4.10.0
 psycopg2-binary==2.7.6
 django-ordered-model==3.1.1
 easy-thumbnails==2.5
-git+https://github.com/mittagessen/kraken.git@3.0b21#egg=kraken
+git+https://github.com/mittagessen/kraken.git@3.0b23#egg=kraken
 django-cleanup==5.1.0
 djangorestframework==3.9.2
 drf-nested-routers==0.91
diff --git a/docker-compose.override.yml_example b/docker-compose.override.yml_example
index 519b21d512d6bd5e98a89c8eb850afe67e480eb1..5206c72563c9a520c9b913a09a72687c32044d1a 100644
--- a/docker-compose.override.yml_example
+++ b/docker-compose.override.yml_example
@@ -10,6 +10,12 @@ services:
       #     constraints:
       #       - node.hostname == frontend0
 
+      ### to customize the homepage, uncomment this
+      # environment:
+      #   - CUSTOM_HOME=True
+      # volumes:
+      #   - $PWD/app/homepage
+
     channelserver:
       restart: always
       # deploy:
diff --git a/docker-compose.yml b/docker-compose.yml
index b7bbb53b52f4ed4e023f75f5efd68eb81f49f84b..89b5980cd597a776019cb1c3370b13aae08a0a4d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -15,6 +15,8 @@ services:
   web:
     <<: *app
     command: uwsgi --ini /usr/src/app/uwsgi.ini
+    volumes:
+      - logs:/user/src/app/escriptorium/logs
     expose:
       - 8000
 
@@ -116,3 +118,4 @@ volumes:
    media:
    postgres:
    esdata:
+   logs:
diff --git a/front/css/escriptorium.css b/front/css/escriptorium.css
index 2955b5d5dc5e5cde4c089f29277be0650e89f20d..85702e1ba33ef40bbb16495ec6e8c586dbf78dab 100644
--- a/front/css/escriptorium.css
+++ b/front/css/escriptorium.css
@@ -134,6 +134,7 @@ form.inline-form {
 
 #cards-container {
     overflow: auto;
+    counter-reset: card;
 }
 
 #cards-container .card {
@@ -192,6 +193,16 @@ form.inline-form {
     border: 0;
 }
 
+.js-card-order {
+    position: absolute;
+    left: 50%;
+}
+
+.js-card-order:after {
+    counter-increment: card;
+    content: counter(card);
+}
+
 .process-part-form h5 {
     color: var(--secondary);
     text-align: center;
@@ -381,6 +392,10 @@ form.inline-form {
     color: grey;
 }
 
+#gotoModal .modal-dialog {
+    max-width: 355px;
+}
+
 .col-sides {
     width: 30px;
     margin: 0 15px;
@@ -517,6 +532,8 @@ i.panel-icon {
     margin-top: 1rem;
     white-space: nowrap;
     padding: 0 2px;
+    box-sizing: content-box;
+    padding-bottom: 0.1em;
     transform-origin: top left;
     box-shadow: none;
 }
diff --git a/front/css/ttb.css b/front/css/ttb.css
new file mode 100644
index 0000000000000000000000000000000000000000..1dd3d09815820f0aa4aaf64bd201959ed95e4a74
--- /dev/null
+++ b/front/css/ttb.css
@@ -0,0 +1,57 @@
+/* vertical styles: */
+
+#vertical_text_input{
+    writing-mode: vertical-lr;
+    text-orientation: upright;
+    width:auto!important;
+    height: auto;
+    white-space: nowrap!important;
+    margin: .375rem 0;
+}
+#vertical_text_input:focus{
+    outline: none;
+}
+
+#textInputBorderWrapper{
+    box-sizing: content-box;
+    padding: 0 .75rem;
+}
+
+#diplomatic-lines.ttb{
+    writing-mode: vertical-lr;
+    text-orientation: upright;  
+}
+
+#diplomatic-lines.ttb div{
+    text-align: left;  
+    min-width:25px;
+    padding: 0;
+    margin: 0;
+    padding-top: 30px;
+
+    position: relative;
+    white-space: nowrap;
+}
+
+#diplomatic-lines.ttb div::before{
+    writing-mode: horizontal-tb !important;
+    position: absolute;
+    display: block; /*to set size properties*/
+    white-space: nowrap;
+    text-align: left;  
+
+    width: 100%; /*adapt to #diplomatic-lines.ttb div size - set to position:relative*/
+    height: auto;
+    
+    margin-left: 0;
+    margin-right: .5rem;
+    margin-top: -30px;
+    margin-bottom: 20px;
+
+    padding-left: .5rem;
+    padding-right: 0;
+
+    border-bottom: 1px solid #ddd;
+    border-right: none!important;
+    border-right-width:0!important;
+}
diff --git a/front/src/editor/api.js b/front/src/editor/api.js
index 2cf9820be7bec04bffbd951eff34405cf5afd6c5..528517857dc97a59db9de4cca80851f17b194ab5 100644
--- a/front/src/editor/api.js
+++ b/front/src/editor/api.js
@@ -10,6 +10,8 @@ export const retrieveDocument = async document_id => (await axios.get(`/document
 
 export const retrieveDocumentPart = async (document_id, part_id) => (await axios.get(`/documents/${document_id}/parts/${part_id}/`))
 
+export const retrieveDocumentPartByOrder = async (document_id, order) => (await axios.get(`/documents/${document_id}/parts/byorder/?order=${order}`))
+
 export const retrievePage = async (document_id, part_id, transcription, page) => (await axios.get(`/documents/${document_id}/parts/${part_id}/transcriptions/?transcription=${transcription}&page=${page}`))
 
 export const createContent = async (document_id, part_id, data) => (await axios.post(`/documents/${document_id}/parts/${part_id}/transcriptions/`, data))
@@ -40,4 +42,4 @@ export const bulkCreateLineTranscriptions = async (document_id, part_id, data) =
 
 export const bulkUpdateLineTranscriptions = async (document_id, part_id, data) => (await axios.put(`/documents/${document_id}/parts/${part_id}/transcriptions/bulk_update/`, data))
 
-export const moveLines = async (document_id, part_id, data) => (await axios.post(`/documents/${document_id}/parts/${part_id}/lines/move/`, data))
\ No newline at end of file
+export const moveLines = async (document_id, part_id, data) => (await axios.post(`/documents/${document_id}/parts/${part_id}/lines/move/`, data))
diff --git a/front/src/editor/store/document.js b/front/src/editor/store/document.js
index 88bf207411dee4dbf977714ab6b2ae4a6dd2dadc..ee3ffe0a4963cec7ce16f41ce070b43f7b240b5e 100644
--- a/front/src/editor/store/document.js
+++ b/front/src/editor/store/document.js
@@ -4,6 +4,7 @@ import * as api from '../api'
 export const initialState = () => ({
     id: null,
     name: "",
+    partsCount: 0,
     defaultTextDirection: null,
     mainTextDirection: null,
     readDirection: null,
@@ -39,6 +40,9 @@ export const mutations = {
     setTypes (state, types) {
         state.types = types
     },
+    setPartsCount(state, count) {
+        state.partsCount = count
+    },
     setBlockShortcuts(state, block) {
         state.blockShortcuts = block
     },
@@ -56,6 +60,7 @@ export const actions = {
         let data = resp.data
         commit('transcriptions/set', data.transcriptions, {root: true})
         commit('setTypes', { 'regions': data.valid_block_types, 'lines': data.valid_line_types })
+        commit('setPartsCount', data.parts_count)
     },
 
     async togglePanel ({state, commit}, panel) {
diff --git a/front/src/editor/store/parts.js b/front/src/editor/store/parts.js
index ed1ce9649b90cabf951ad7623d281a7a1b9c4fb0..3d366ad30afe5ac43d6b608573922bd0a126a16b 100644
--- a/front/src/editor/store/parts.js
+++ b/front/src/editor/store/parts.js
@@ -31,12 +31,19 @@ export const mutations = {
 }
 
 export const actions = {
-    async fetchPart ({commit, dispatch, rootState}, pk) {
-        commit('setPartPk', pk)
+    async fetchPart ({commit, dispatch, rootState}, {pk, order}) {
         if (!rootState.transcriptions.all.length) {
             await dispatch('document/fetchDocument', rootState.document.id, {root: true})
         }
-        const resp = await api.retrieveDocumentPart(rootState.document.id, pk)
+        var resp
+        if (pk) {
+            commit('setPartPk', pk)
+            resp = await api.retrieveDocumentPart(rootState.document.id, pk)
+        } else {
+            resp = await api.retrieveDocumentPartByOrder(rootState.document.id, order)
+            commit('setPartPk', resp.data.pk)
+        }
+
         let data = resp.data
 
         data.lines.forEach(function(line) {
@@ -63,7 +70,20 @@ export const actions = {
         commit('regions/reset', {}, {root: true})
         commit('lines/reset', {}, {root: true})
         commit('reset')
-        await dispatch('fetchPart', pk)
+        await dispatch('fetchPart', {pk: pk})
+    },
+
+    async loadPartByOrder({state, commit, dispatch, rootState}, order) {
+        commit('regions/reset', {}, {root: true})
+        commit('lines/reset', {}, {root: true})
+        commit('reset')
+        try {
+            await dispatch('fetchPart', {order: order})
+            await dispatch('transcriptions/getCurrentContent', rootState.transcriptions.selectedTranscription, {root: true})
+            await dispatch('transcriptions/getComparisonContent', {}, {root: true})
+        } catch (err) {
+            console.log('couldnt fetch part data!', err)
+        }
     },
 
     async loadPart({state, commit, dispatch, rootState}, direction) {
@@ -73,7 +93,7 @@ export const actions = {
         commit('lines/reset', {}, {root: true})
         commit('reset')
         try {
-            await dispatch('fetchPart', part)
+            await dispatch('fetchPart', {pk: part})
             await dispatch('transcriptions/getCurrentContent', rootState.transcriptions.selectedTranscription, {root: true})
             await dispatch('transcriptions/getComparisonContent', {}, {root: true})
         } catch (err) {
diff --git a/front/src/image_cards.js b/front/src/image_cards.js
index 8e4dc679c69f7bef6559afd15d2a76e8d0124ad3..15b904e4111105da27816b2375cb85de3838e1e3 100644
--- a/front/src/image_cards.js
+++ b/front/src/image_cards.js
@@ -36,6 +36,7 @@ function openWizard(proc) {
 class partCard {
     constructor(part) {
         this.pk = part.pk;
+        this.order = part.order;
         this.name = part.name;
         this.title = part.title;
         this.typology = part.typology;
diff --git a/front/src/main.js b/front/src/main.js
index 70a201056ccc4f1ddadd03a3b1fccaf56e94d086..6701affcac361dec89604a6ac195371a52bab37e 100644
--- a/front/src/main.js
+++ b/front/src/main.js
@@ -1,5 +1,6 @@
 import '../css/escriptorium.css';
 import '../css/rtl.css';
+import '../css/ttb.css';
 import './ajax.js';
 import { Alert, bootWebsocket, joinDocumentRoom } from './messages.js';
 window.Alert = Alert;
@@ -29,4 +30,4 @@ import { bootImageCards } from './image_cards.js';
 window.bootImageCards = bootImageCards;
 
 import { bootModels } from './models.js';
-window.bootModels = bootModels;
\ No newline at end of file
+window.bootModels = bootModels;
diff --git a/front/vue/components/DiploPanel.vue b/front/vue/components/DiploPanel.vue
index 4da1265f94f744da00cf08334cadcac945091fd8..82409240c3d97e54fa341ce7953f0d8dbfc8d93d 100644
--- a/front/vue/components/DiploPanel.vue
+++ b/front/vue/components/DiploPanel.vue
@@ -18,7 +18,9 @@
                         v-bind:key="'DL' + line.pk">
             </diploline>
 
-            <div id="diplomatic-lines"
+            <!--adding a class to get styles for ttb direction:-->
+            <div :class="$store.state.document.mainTextDirection"
+                    id="diplomatic-lines"
                     ref="diplomaticLines"
                     contenteditable="true"
                     autocomplete="off"
@@ -65,6 +67,9 @@ export default Vue.extend({
         }.bind(this), 10000);
     },
     mounted() {
+        // fix the original width so that when content texts are loaded/page refreshed with diplo panel, the panel width wont be bigger than other, especially for ttb text:
+        document.querySelector('#diplo-panel').style.width = document.querySelector('#diplo-panel').clientWidth + 'px';
+
         Vue.nextTick(function() {
             var vm = this ;
             vm.sortable = Sortable.create(this.$refs.diplomaticLines, {
diff --git a/front/vue/components/Editor.vue b/front/vue/components/Editor.vue
index 64b66e6deda18712e213f01b81d6a1c821866ee9..ca62331237f1f28ff9645bbdae1730643718a26a 100644
--- a/front/vue/components/Editor.vue
+++ b/front/vue/components/Editor.vue
@@ -81,7 +81,7 @@ export default {
         this.$store.commit('document/setMainTextDirection', this.mainTextDirection);
         this.$store.commit('document/setReadDirection', this.readDirection);
         try {
-            await this.$store.dispatch('parts/fetchPart', this.partId);
+            await this.$store.dispatch('parts/fetchPart', {pk: this.partId});
             let tr = userProfile.get('initialTranscriptions')
                   && userProfile.get('initialTranscriptions')[this.$store.state.document.id]
                   || this.$store.state.transcriptions.all[0].pk;
diff --git a/front/vue/components/ExtraInfo.vue b/front/vue/components/ExtraInfo.vue
index 218f0ae6cf2a6924731cc2ce26197765a288316d..9ddbf94d026984a843d8a899fd32057011a39bec 100644
--- a/front/vue/components/ExtraInfo.vue
+++ b/front/vue/components/ExtraInfo.vue
@@ -2,18 +2,59 @@
     <div>
         <div class="nav-div nav-item ml-2">
             <span v-if="$store.state.document.name" id="part-name">{{ $store.state.document.name }}</span>
-            <span id="part-title" v-if="$store.state.parts.loaded">{{ $store.state.parts.title }} - {{ $store.state.parts.filename }} - ({{ imageSize }})</span>
+            <span id="part-title"
+                  v-if="$store.state.parts.loaded"
+                  title="Click to go to another Element (Ctrl+Home)."
+                  data-toggle="modal"
+                  data-target="#gotoModal"
+                  role="button">{{ $store.state.parts.title }} - {{ $store.state.parts.filename }} - ({{ imageSize }})</span>
             <span class="loading" v-if="!$store.state.parts.loaded">Loading&#8230;</span>
         </div>
+
+        <div id="gotoModal"
+             class="modal ui-draggable show"
+             tabindex="-1"
+             role="dialog">
+          <div class="modal-dialog modal-dialog-centered">
+            <div class="modal-content">
+              <div class="modal-body">
+                Element #
+                <input type="number"
+                       v-if="$store.state.parts.loaded"
+                       min="1"
+                       :max="$store.state.document.partsCount"
+                       width="100%"
+                       v-bind:value="$store.state.parts.order+1"
+                       @change.lazy="goTo"/>
+                / {{$store.state.document.partsCount}}
+              </div>
+            </div>
+          </div>
+        </div>
     </div>
 </template>
 
 <script>
 export default {
+    async created() {
+        document.addEventListener('keyup', async function(event) {
+            if (event.ctrlKey && event.keyCode == 36) { // Home
+                $('#gotoModal').modal('show');
+            }
+        });
+    },
     computed: {
         imageSize() {
             return this.$store.state.parts.image.size[0]+'x'+this.$store.state.parts.image.size[1];
         },
+    },
+    methods: {
+        async goTo(ev) {
+            if (ev.target.value > 0 && ev.target.value <= parseInt(ev.target.attributes.max.value)) {
+              await this.$store.dispatch('parts/loadPartByOrder', ev.target.value-1);
+              $('#gotoModal').modal('hide');
+            }
+        }
     }
 }
 </script>
diff --git a/front/vue/components/TranscriptionModal.vue b/front/vue/components/TranscriptionModal.vue
index 640ce673e574cbc89aa637747007f41d7c46f5bd..5cb968f7afa9f6580d7c78900bb27bcfc6a32970 100644
--- a/front/vue/components/TranscriptionModal.vue
+++ b/front/vue/components/TranscriptionModal.vue
@@ -70,7 +70,8 @@
                     </div>
 
                     <div id="trans-input-container" ref="transInputContainer">
-                        <input v-on:keyup.down="$store.dispatch('lines/editLine', 'next')"
+                        <input v-if="$store.state.document.mainTextDirection != 'ttb'"
+                                v-on:keyup.down="$store.dispatch('lines/editLine', 'next')"
                                 v-on:keyup.up="$store.dispatch('lines/editLine', 'previous')"
                                 v-on:keyup.enter="$store.dispatch('lines/editLine', 'next')"
                                 id="trans-input"
@@ -80,6 +81,30 @@
                                 v-model.lazy="localTranscription"
                                 autocomplete="off"
                                 autofocus/>
+                        <!--Hidden input for ttb text: -->
+                        <input v-else
+                                id="trans-input" 
+                                ref="transInput"
+                                name="content" 
+                                type="hidden"
+                                v-model.lazy="localTranscription"
+                                autocomplete="off" />
+                        <!-- in this case, input field is replaced by: -->
+                        <div v-if="$store.state.document.mainTextDirection == 'ttb'"
+                            id="textInputWrapper">
+                            <div id="textInputBorderWrapper" class="form-control mb-2">
+                                <div    v-on:blur="localTranscription = $event.target.textContent"
+                                        v-on:keyup="recomputeInputCharsScaleY()"
+                                        v-on:keyup.right="$store.dispatch('lines/editLine', 'next')"
+                                        v-on:keyup.left="$store.dispatch('lines/editLine', 'previous')"
+                                        v-on:keyup.enter="cleanHTMLTags();recomputeInputCharsScaleY();$store.dispatch('lines/editLine', 'next')"
+                                        v-html="localTranscription"
+                                        id="vertical_text_input"
+                                            contenteditable="true">
+                                </div>
+                            </div>
+                        </div>
+
                         <small v-if="line.currentTrans && line.currentTrans.version_updated_at" class="form-text text-muted">
                             <span>by {{line.currentTrans.version_author}} ({{line.currentTrans.version_source}})</span>
                             <span>on {{momentDate}}</span>
@@ -185,9 +210,27 @@ export default Vue.extend({
         $(this.$refs.transModal).draggable({handle: '.modal-header'});
         $(this.$refs.transModal).resizable();
         this.computeStyles();
+        let modele = this;
 
         let input = this.$refs.transInput;
-        input.focus();
+
+        // no need to make focus on hiden input with a ttb text
+        if(this.$store.state.document.mainTextDirection != 'ttb'){
+            input.focus();
+        }else{  // avoid some br or other html tag for a copied text on an editable input div (vertical_text_input): 
+            // 
+            document.getElementById("vertical_text_input").addEventListener("paste", function(e) {
+
+                // cancel paste to treat its content before inserting it
+                e.preventDefault();
+
+                // get text representation of clipboard
+                var text = (e.originalEvent || e).clipboardData.getData('text/plain');
+                this.innerHTML = text;
+                modele.recomputeInputCharsScaleY();
+
+            }, false);
+        }
     },
     watch: {
         line(new_, old_) {
@@ -240,7 +283,19 @@ export default Vue.extend({
         close() {
             $(this.$refs.transModal).modal('hide');
         },
-
+        cleanHTMLTags(){
+            document.getElementById("vertical_text_input").innerHTML = document.getElementById("vertical_text_input").textContent;
+        },
+        recomputeInputCharsScaleY(){
+            
+            let inputHeight = document.getElementById("vertical_text_input").clientHeight;
+            let wrapperHeight = document.getElementById("textInputBorderWrapper").clientHeight;
+            let textScaleY = wrapperHeight / (inputHeight + 10);
+
+            // to avoid input text outside the border box:
+            if(inputHeight > wrapperHeight)
+                document.getElementById("vertical_text_input").style.transform = "scaleY("+textScaleY+")";
+        },
         comparedContent(content) {
             if (!this.line.currentTrans) return;
             let diff = Diff.diffChars(this.line.currentTrans.content, content);
@@ -286,6 +341,8 @@ export default Vue.extend({
 
             // calculate rotation needed to get the line horizontal
             let target_angle = 0;  // all lines should be topologically ltr
+            if(this.$store.state.document.mainTextDirection == 'ttb') // add a 90 angle for vertical texts
+                target_angle = 90; 
             let angle = target_angle - this.getLineAngle();
 
             // apply it to the polygon and get the resulting bbox
@@ -309,8 +366,14 @@ export default Vue.extend({
 
             let context = hContext*lineHeight;
             let visuHeight = lineHeight + 2*context;
-            modalImgContainer.style.height = visuHeight+'px';
 
+            if(this.$store.state.document.mainTextDirection != 'ttb'){
+                modalImgContainer.style.height = visuHeight+'px';
+            }else{
+                modalImgContainer.style.width = visuHeight+'px';
+                modalImgContainer.style.display = 'inline-block';
+                modalImgContainer.style.verticalAlign = 'top';
+            }
             let top = -(bbox.top*ratio - context);
             let left = -(bbox.left*ratio - context);
 
@@ -353,6 +416,8 @@ export default Vue.extend({
             // Content input
             let container = this.$refs.transInputContainer;
             let input = container.querySelector('#trans-input');
+            let verticalTextInput;
+
             // note: input is not up to date yet
             let content = this.line.currentTrans && this.line.currentTrans.content || '';
             let ruler = document.createElement('span');
@@ -360,13 +425,29 @@ export default Vue.extend({
             ruler.style.visibility = 'hidden';
             ruler.textContent = content;
             ruler.style.whiteSpace="nowrap"
+
+            if(this.$store.state.document.mainTextDirection == 'ttb'){
+                // put the container inline for vertical transcription:
+                container.style.display = 'inline-block';
+                verticalTextInput = container.querySelector('#vertical_text_input');
+                // apply vertical writing style to the ruler:
+                ruler.style.writingMode = 'vertical-lr';
+                ruler.style.textOrientation = 'upright';
+            }
+
             container.appendChild(ruler);
 
             let context = hContext*lineHeight;
             let fontSize = Math.max(15, Math.round(lineHeight*0.7));  // Note could depend on the script
             ruler.style.fontSize = fontSize+'px';
-            input.style.fontSize = fontSize+'px';
-            input.style.height = Math.round(fontSize*1.1)+'px';
+
+            if(this.$store.state.document.mainTextDirection != 'ttb'){
+                input.style.fontSize = fontSize+'px';
+                input.style.height = Math.round(fontSize*1.1)+'px';
+            }else{
+                verticalTextInput.style.fontSize = fontSize+'px';
+                verticalTextInput.style.width = Math.round(fontSize*1.1)+'px';
+            }
 
             if (this.$store.state.document.readDirection == 'rtl') {
                 container.style.marginRight = context+'px';
@@ -375,15 +456,40 @@ export default Vue.extend({
                 // TODO: deal with other directions
                 container.style.marginLeft = context+'px';
             }
-            if (content) {
-                let lineWidth = bbox.width*ratio;
-                var scaleX = Math.min(5,  lineWidth / ruler.clientWidth);
-                scaleX = Math.max(0.2, scaleX);
-                input.style.transform = 'scaleX('+ scaleX +')';
-                input.style.width = 100/scaleX + '%';
-            } else {
-                input.style.transform = 'none';
-                input.style.width = '100%'; //'calc(100% - '+context+'px)';
+            if(this.$store.state.document.mainTextDirection != 'ttb'){
+                if (content) {
+                    let lineWidth = bbox.width*ratio;
+                    var scaleX = Math.min(5,  lineWidth / ruler.clientWidth);
+                    scaleX = Math.max(0.2, scaleX);
+                    input.style.transform = 'scaleX('+ scaleX +')';
+                    input.style.width = 100/scaleX + '%';
+                } else {
+                    input.style.transform = 'none';
+                    input.style.width = '100%'; //'calc(100% - '+context+'px)';
+                }
+            }else{
+                let modalImgContainer = this.$refs.modalImgContainer;
+                let textInputWrapper = container.querySelector('#textInputWrapper');
+                let textInputBorderWrapper = container.querySelector('#textInputBorderWrapper');
+                if (content) {
+                    let lineWidth = bbox.height*ratio;
+                    var scaleY = Math.min(5,  lineWidth / ruler.clientHeight);
+                    //var scaleY = Math.min(5,  lineWidth / modalImgContainer.clientHeight);
+                    //var scaleY = Math.min(5,  modalImgContainer.clientHeight / ruler.clientHeight);
+                    //var scaleY = Math.min(5,  modalImgContainer.clientHeight / textInputWrapper.clientHeight) * 0.7;
+                    scaleY = Math.max(0.2, scaleY);
+                    verticalTextInput.style.transformOrigin = 'top';
+                    verticalTextInput.style.transform = 'scaleY('+ scaleY +')';
+                    //document.getElementById('vertical_text_input').style.height = 100/scaleY + '%'; // not needed here
+                } else {
+                    verticalTextInput.style.transform = 'none';
+                    verticalTextInput.style.height = modalImgContainer.clientHeight + 'px'; 
+                }  
+                textInputWrapper.style.height = modalImgContainer.clientHeight + 'px';
+                // simulate an input field border to fix it to the actual size of the image
+                textInputBorderWrapper.style.width = verticalTextInput.clientWidth+'px';
+                //textInputBorderWrapper.style.width = verticalTextInput.offsetWidth+'px';
+                textInputBorderWrapper.style.height = modalImgContainer.clientHeight+'px';
             }
             container.removeChild(ruler);  // done its job
         },
@@ -397,13 +503,33 @@ export default Vue.extend({
 
             let bbox = this.getRotatedLineBBox();
             let hContext = 0.3; // vertical context added around the line, in percentage
-            let ratio = modalImgContainer.clientWidth / (bbox.width + (2*bbox.height*hContext));
-            let MAX_HEIGHT = Math.round(Math.max(25, (window.innerHeight-230) / 3));
-            let lineHeight = Math.max(30, Math.round(bbox.height*ratio));
-            if (lineHeight > MAX_HEIGHT) {
-                // change the ratio so that the image can not get too big
-                ratio = (MAX_HEIGHT/lineHeight)*ratio;
-                lineHeight = MAX_HEIGHT;
+
+            //
+            let ratio = 1;
+            let lineHeight = 150; 
+
+            if(this.$store.state.document.mainTextDirection != 'ttb')
+            {
+                ratio = modalImgContainer.clientWidth / (bbox.width + (2*bbox.height*hContext));
+                let MAX_HEIGHT = Math.round(Math.max(25, (window.innerHeight-230) / 3));
+                lineHeight = Math.max(30, Math.round(bbox.height*ratio));
+                if (lineHeight > MAX_HEIGHT) {
+                    // change the ratio so that the image can not get too big
+                    ratio = (MAX_HEIGHT/lineHeight)*ratio;
+                    lineHeight = MAX_HEIGHT;
+                }
+            }else{ // permutation of sizes for ttb text
+
+                modalImgContainer.style.height=String(window.innerHeight-230) + "px";   //   needed to fix height or ratio is nulled
+                ratio = modalImgContainer.clientHeight / (bbox.height + (2*bbox.width*hContext));
+                let MAX_WIDTH = 30;
+                lineHeight = Math.max(30, Math.round(bbox.width*ratio));   
+                
+                if (lineHeight > MAX_WIDTH) {    
+                    // change the ratio so that the image can not get too big
+                    ratio = (MAX_WIDTH/lineHeight)*ratio;
+                    lineHeight = MAX_WIDTH;
+                }
             }
 
             this.computeImgStyles(bbox, ratio, lineHeight, hContext);
diff --git a/front/vue/components/VisuLine.vue b/front/vue/components/VisuLine.vue
index 4e1182534f7fe52303056cda7cbcdb1d03caf8ce..8c98b1f0db3122337d0eb580bf75a6ce665aab55 100644
--- a/front/vue/components/VisuLine.vue
+++ b/front/vue/components/VisuLine.vue
@@ -10,14 +10,28 @@
                 fill="none"
                 v-bind:stroke="pathStrokeColor"
                 v-bind:d="baselinePoints"></path>
+
+        <text :text-anchor="$store.state.document.defaultTextDirection == 'rtl' ? 'end' : ''"
+                ref="textElement"
+                lengthAdjust="spacingAndGlyphs" 
+                v-if="$store.state.document.mainTextDirection != 'ttb'">
+            <textPath v-bind:href="'#' + textPathId"
+                        v-if="line.currentTrans">
+                {{ line.currentTrans.content }}
+            </textPath>
+        </text>
+
         <text :text-anchor="$store.state.document.defaultTextDirection == 'rtl' ? 'end' : ''"
                 ref="textElement"
-                lengthAdjust="spacingAndGlyphs">
+                rotate="-90"
+                font-size="1em"
+                v-else>
             <textPath v-bind:href="'#' + textPathId"
                         v-if="line.currentTrans">
                 {{ line.currentTrans.content }}
             </textPath>
         </text>
+
     </g>
 </template>
 
@@ -26,6 +40,8 @@ import { LineBase } from '../../src/editor/mixins.js';
 
 export default Vue.extend({
     mixins: [LineBase],
+    mounted() {
+    },
     watch: {
         'line.currentTrans.content': function(n, o) {
             this.$nextTick(this.reset);
@@ -41,18 +57,43 @@ export default Vue.extend({
                 let poly = this.line.mask.flat(1).map(pt => Math.round(pt));
                 var area = 0;
                 // A = 1/2(x_1y_2-x_2y_1+x_2y_3-x_3y_2+...+x_(n-1)y_n-x_ny_(n-1)+x_ny_1-x_1y_n),
-                for (let i=0; i<poly.length; i++) {
-                    let j = (i+1) % poly.length; // loop back to 1
-                    area += poly[i][0]*poly[j][1] - poly[j][0]*poly[i][1];
+
+                var liste = String(poly).split(",");
+                var indexCoordonnee = 0;
+                var arrayCoordonnees = new Array();
+                var paire = [];
+                for(var i = 0; i < liste.length; i++){
+                    paire.push(liste[i]);
+                    if(indexCoordonnee==0){
+                        indexCoordonnee = 1;
+                    }else{
+                        indexCoordonnee = 0;
+                        arrayCoordonnees.push(paire);
+                        paire = new Array();
+                    }
+                }
+
+                for (let i=0; i<arrayCoordonnees.length; i++) {
+                    let j = (i+1) % arrayCoordonnees.length; // loop back to 1
+                    area += arrayCoordonnees[i][0]*arrayCoordonnees[j][1] - arrayCoordonnees[j][0]*arrayCoordonnees[i][1];
                 }
+
                 area = Math.abs(area*this.ratio);
                 lineHeight = area / this.$refs.pathElement.getTotalLength();
+
             } else {
                 lineHeight = 30;
             }
+ 
+            lineHeight = Math.max(Math.round(lineHeight), 5) * 0.3;
+
+            let ratio = 1/4;    //  more well suited for horizontal latin writings
+            if(this.$store.state.document.mainTextDirection == 'ttb')
+                ratio = 1/2;
+
+            this.$refs.textElement.setAttribute("font-size", String(lineHeight * (ratio)) + 'px'); 
 
-            lineHeight = Math.max(Math.min(Math.round(lineHeight), 100), 5);
-            this.$refs.textElement.style.fontSize =  lineHeight * (1/2) + 'px';
+            //return lineHeight+'px';
             return 10+'px';
         },
         computeTextLength() {
diff --git a/variables.env_example b/variables.env_example
index a3dabbb55d4630114510d7c2dd6ceaab3ece3c35..69e9c245e24702af7af536471100947411926c2d 100644
--- a/variables.env_example
+++ b/variables.env_example
@@ -4,10 +4,10 @@ SECRET_KEY=changeme
 REDIS_HOST=redis
 # REDIS_PORT=6379
 SQL_HOST=db
-# SQL_PORT=5432
-# POSTGRES_USER=postgres
-# POSTGRES_PASSWORD=postgres
-# POSTGRES_DB=escriptorium
+SQL_PORT=5432
+POSTGRES_USER=postgres
+POSTGRES_PASSWORD=postgres
+POSTGRES_DB=escriptorium
 
 DJANGO_SU_NAME=admin
 DJANGO_SU_EMAIL=admin@admin.com