diff --git a/components/accordion/accordion.component.yml b/components/accordion/accordion.component.yml
index 5c1a7a2176959f1c7d6419d3b06443dceed23f49..9b054436089ff98d57c82bf717b55ed3b88a79c7 100644
--- a/components/accordion/accordion.component.yml
+++ b/components/accordion/accordion.component.yml
@@ -10,6 +10,9 @@ props:
     attributes:
       type: Drupal\Core\Template\Attribute
       title: Attributes
+    button_attributes:
+      type: Drupal\Core\Template\Attribute
+      title: Button attributes
     content:
       type: string
       title: Content
diff --git a/components/accordion/accordion.twig b/components/accordion/accordion.twig
index 1b4df84b2346a2b07ee71b663be4b8b47c5841f6..ec6060f682e2d5f19e338859e8ea2826c40722e5 100644
--- a/components/accordion/accordion.twig
+++ b/components/accordion/accordion.twig
@@ -1,13 +1,16 @@
 {% set accordion_id = accordion_id|default('accordion-' ~ random()) %}
 {% set attributes = attributes|default(create_attribute()) %}
+{% set button_attributes = button_attributes|default(create_attribute()) %}
 {% set title_tag = title_tag|default('h3') %}
 
 <section{{ attributes.addClass('fr-accordion') }}>
   <{{ title_tag }} class="fr-accordion__title">
-    <button class="fr-accordion__btn"
-            aria-expanded="{{ expanded ? 'true' : 'false' }}"
-            aria-controls="{{ accordion_id }}"
-            type="button">
+    <button{{ button_attributes
+      .addClass('fr-accordion__btn')
+      .setAttribute('type', button)
+      .setAttribute('aria-expanded', expanded ? 'true' : 'false')
+      .setAttribute('aria-controls', accordion_id) }}
+    >
       {{ title }}
     </button>
   </{{ title_tag }}>
diff --git a/components/card/card.component.yml b/components/card/card.component.yml
index 6b9c967617293e8dfbab22a9ac28c406a7d30511..cc6b0da53a45fcc843cf6e6b7b82fe0159457ddf 100644
--- a/components/card/card.component.yml
+++ b/components/card/card.component.yml
@@ -72,6 +72,9 @@ props:
     title:
       type: string
       title: Title
+    title_attributes:
+      type: Drupal\Core\Template\Attribute
+      title: Title attributes
     title_tag:
       type: string
       title: Title HTML tag
diff --git a/components/card/card.twig b/components/card/card.twig
index 1f95bf3b81169d78e02ffff4c684af9264dae92b..1b32a00aae381c35db197feaa431af63d0dba429 100644
--- a/components/card/card.twig
+++ b/components/card/card.twig
@@ -1,6 +1,7 @@
 {% set attributes = attributes|default(create_attribute()) %}
 {% set title_tag = title_tag|default('h3') %}
 {% set link_attributes = link_attributes|default(create_attribute()) %}
+{% set title_attributes = title_attributes|default(create_attribute()) %}
 
 {% if variant %}
   {% set attributes = attributes.addClass('fr-card--' ~ variant) %}
@@ -32,7 +33,7 @@
 <div{{ attributes.addClass('fr-card') }}>
   <div class="fr-card__body">
     <div class="fr-card__content">
-      <{{ title_tag }} class="fr-card__title">
+      <{{ title_tag }}{{ title_attributes.addClass('fr-card__title') }}>
         {% if use_button %}
           <buton{{ link_attributes }}>{{ title }}</buton>
         {% elseif url %}
diff --git a/components/modal/modal.component.yml b/components/modal/modal.component.yml
index 183171f0f715836b03caa9fa2dd41c7465e8d994..e2dff3c07ea3dbe05d5d198df549e2b0da6f5452 100644
--- a/components/modal/modal.component.yml
+++ b/components/modal/modal.component.yml
@@ -15,16 +15,25 @@ props:
       type: string
       title: Button title attribute
       description: 'By default: "Close", or the translated string in another language.'
+    content:
+      type: string
+      title: Content
+    footer:
+      type: string
+      title: Footer
     html_id:
       type: string
       title: HTML identifier
     title:
       type: string
       title: Title
+    title_tag:
+      type: string
+      title: Title HTML tag
+      default: h1
 slots:
   modal_content:
     title: Modal content
-    required: true
   modal_footer:
     title: Modal footer
   modal_title:
diff --git a/components/modal/modal.twig b/components/modal/modal.twig
index 6acc70c9fcb35c25aa077053ff14fcbe3e7fec39..652e233cd6a4831d27dcf59e7bbd78e455d23279 100644
--- a/components/modal/modal.twig
+++ b/components/modal/modal.twig
@@ -2,6 +2,7 @@
 {% set button_label = button_label|default('Close'|t) %}
 {% set button_title = button_title|default('Close'|t) %}
 {% set html_id = html_id|default('modal-default') %}
+{% set title_tag = title_tag|default('h1') %}
 
 <dialog id="{{ html_id }}" class="fr-modal" role="dialog" aria-labelledby="{{ html_id ~ '-title' }}">
   <div class="fr-container fr-container--fluid fr-container-md">
@@ -14,19 +15,20 @@
             </button>
           </div>
           <div class="fr-modal__content">
-            <div id="{{ html_id ~ '-title' }}" class="fr-modal__title">
+            <{{ title_tag }} id="{{ html_id ~ '-title' }}" class="fr-modal__title">
               {% block modal_title %}
                 {{ title }}
               {% endblock modal_title %}
-            </div>
+            </{{ title_tag }}>
             {% block modal_content %}
-              <p>{{ 'Default content.'|t }}</p>
+              {{ content }}
             {% endblock modal_content %}
           </div>
-          {% if modal_footer or block('modal_footer') is defined %}
+
+          {% if footer or (block('modal_footer') is defined and block('modal_footer')|trim is not empty) %}
             <div class="fr-modal__footer">
               {% block modal_footer %}
-                {{ modal_footer }}
+                {{ footer }}
               {% endblock modal_footer %}
             </div>
           {% endif %}
diff --git a/components/transcription/transcription.component.yml b/components/transcription/transcription.component.yml
index 3c89bd474967b386122e71d6e39e2c313f4afdc1..0c0f6d8870c4197792aff8502c4bb868c954f355 100644
--- a/components/transcription/transcription.component.yml
+++ b/components/transcription/transcription.component.yml
@@ -7,10 +7,20 @@ props:
     attributes:
       type: Drupal\Core\Template\Attribute
       title: Attributes
+    button_label:
+      type: string
+      title: Button label
+    button_title:
+      type: string
+      title: Button title attribute
     content:
       type: string
       title: Content
       description: Display into transcription modal.
+    expanded:
+      type: boolean
+      title: Is expanded?
+      default: false
     modal_id:
       type: string
       title: Modal identifier
diff --git a/components/transcription/transcription.twig b/components/transcription/transcription.twig
index e47af06b837a9f062d8b88183169e9ea0a0a1eed..150816ef6986609e949860002e844a1d0501283c 100644
--- a/components/transcription/transcription.twig
+++ b/components/transcription/transcription.twig
@@ -1,20 +1,23 @@
 {% set attributes = attributes|default(create_attribute()) %}
+{% set button_label = button_label|default('Enlarge'|t) %}
+{% set button_title = button_title|default('Enlarge transcription'|t) %}
 {% set title = title|default('Transcription'|t) %}
 {% set transcription_id = transcription_id|default('transcription-' ~ random()) %}
 {% set collapse_id = 'fr-transcription-collapse-' ~ transcription_id %}
 {% set modal_id = 'fr-transcription-modal-' ~ transcription_id %}
 {% set modal_title = modal_title|default(title) %}
+{% set expanded = expanded ?? false %}
 
 <div{{ attributes.addClass('fr-transcription') }}>
-  <button type="button" class="fr-transcription__btn" aria-expanded="false" aria-controls="{{ collapse_id }}">
+  <button type="button" class="fr-transcription__btn" aria-expanded="{{ expanded ? 'true': 'false' }}" aria-controls="{{ collapse_id }}">
     {{ title }}
   </button>
   <div class="fr-collapse" id="{{ collapse_id }}">
 
     <div class="fr-transcription__footer">
       <div class="fr-transcription__actions-group">
-        <button type="button" class="fr-btn fr-btn--fullscreen" aria-controls="{{ modal_id }}" data-fr-opened="false" title="{{ 'Enlarge transcription'|t }}">
-          {{ 'Enlarge'|t }}
+        <button type="button" class="fr-btn fr-btn--fullscreen" aria-controls="{{ modal_id }}" data-fr-opened="false" title="{{ button_title }}">
+          {{ button_label }}
         </button>
       </div>
     </div>
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000000000000000000000000000000000000..c35d1e992b9ebd6eb2454446b98cb59f5ba55a97
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,18 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "e2b57849f956d4c67658848cdb027ed8",
+    "packages": [],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": [],
+    "plugin-api-version": "2.6.0"
+}
diff --git a/css/core.css b/css/base/core.css
similarity index 100%
rename from css/core.css
rename to css/base/core.css
diff --git a/css/ckeditor5.css b/css/ckeditor5.css
index a1ca5daca89d9231f3d281f3b3c6975166fa00a6..94e00f699e6279fe692f75abd269ab331681f1b9 100644
--- a/css/ckeditor5.css
+++ b/css/ckeditor5.css
@@ -111,7 +111,7 @@
   --underline-thickness: 0.0625em;
   --underline-img: linear-gradient(0deg, currentcolor, currentcolor);
   --xl-base: 1em;
-  --ol-content: counters(li-counter, ".") ".  ";
+  --ol-content: counters(li-counter, ".") ".  "; /* stylelint-disable-line no-irregular-whitespace */
 
   color: var(--text-default-grey);
   font-family: Marianne,arial,sans-serif;
@@ -252,78 +252,103 @@
 .ck-content ol[start] {
   counter-reset: none;
 }
+
 .ck-content ol[start="0"] {
   counter-reset: li-counter -1;
 }
+
 .ck-content ol[start="2"] {
   counter-reset: li-counter 1;
 }
+
 .ck-content ol[start="3"] {
   counter-reset: li-counter 2;
 }
+
 .ck-content ol[start="4"] {
   counter-reset: li-counter 3;
 }
+
 .ck-content ol[start="5"] {
   counter-reset: li-counter 4;
 }
+
 .ck-content ol[start="6"] {
   counter-reset: li-counter 5;
 }
+
 .ck-content ol[start="7"] {
   counter-reset: li-counter 6;
 }
+
 .ck-content ol[start="8"] {
   counter-reset: li-counter 7;
 }
+
 .ck-content ol[start="9"] {
   counter-reset: li-counter 8;
 }
+
 .ck-content ol[start="10"] {
   counter-reset: li-counter 9;
 }
+
 .ck-content ol[start="11"] {
   counter-reset: li-counter 10;
 }
+
 .ck-content ol[start="12"] {
   counter-reset: li-counter 11;
 }
+
 .ck-content ol[start="13"] {
   counter-reset: li-counter 12;
 }
+
 .ck-content ol[start="14"] {
   counter-reset: li-counter 13;
 }
+
 .ck-content ol[start="15"] {
   counter-reset: li-counter 14;
 }
+
 .ck-content ol[start="16"] {
   counter-reset: li-counter 15;
 }
+
 .ck-content ol[start="17"] {
   counter-reset: li-counter 16;
 }
+
 .ck-content ol[start="18"] {
   counter-reset: li-counter 17;
 }
+
 .ck-content ol[start="19"] {
   counter-reset: li-counter 18;
 }
+
 .ck-content ol[start="20"] {
   counter-reset: li-counter 19;
 }
+
 .ck-content ol[start="21"] {
   counter-reset: li-counter 20;
 }
+
 .ck-content ol[start="22"] {
   counter-reset: li-counter 21;
 }
+
 .ck-content ol[start="23"] {
   counter-reset: li-counter 22;
 }
+
 .ck-content ol[start="24"] {
   counter-reset: li-counter 23;
 }
+
 .ck-content ol[start="25"] {
   counter-reset: li-counter 24;
 }
diff --git a/css/component/action-links.css b/css/component/action-links.css
new file mode 100644
index 0000000000000000000000000000000000000000..3cb17c608ec7ae3231cf1715d65bd0e56b1d65cb
--- /dev/null
+++ b/css/component/action-links.css
@@ -0,0 +1,17 @@
+/**
+ * @file
+ * Styles for local action links.
+*/
+
+.local-actions {
+  --local-action-margin: 1rem;
+
+  list-style: none;
+  margin: 0 calc(var(--local-action-margin) * -1);
+  padding: 0;
+}
+
+.local-actions li {
+  display: inline-block;
+  margin: 0 var(--local-action-margin);
+}
diff --git a/css/component/checkbox.css b/css/component/checkbox.css
new file mode 100644
index 0000000000000000000000000000000000000000..462f7872729cdae398d5a4fe318de5eb91f549e7
--- /dev/null
+++ b/css/component/checkbox.css
@@ -0,0 +1,19 @@
+/**
+ * @file
+ * Manage styles for checkbox.
+ */
+
+/* Fix checkbox rendering when label is visually hidden */
+.fr-checkbox-group input[type="checkbox"] + label.visually-hidden {
+  width: 1.5rem;
+  height: 1.5rem;
+  margin-left: 0;
+  margin-top: 0;
+  padding-left: 1.5rem;
+  clip: auto;
+  z-index: 1;
+}
+
+.fr-checkbox-group input[type="checkbox"] + label.visually-hidden::before {
+  left: 0;
+}
diff --git a/css/component/dropbutton.css b/css/component/dropbutton.css
new file mode 100644
index 0000000000000000000000000000000000000000..fbf48812eaa7181d7288b00b3dd2d5207bc6cbad
--- /dev/null
+++ b/css/component/dropbutton.css
@@ -0,0 +1,39 @@
+/**
+ * @file
+ * Manage styles for dropbutton.
+ */
+
+html.js .dropbutton-wrapper .dropbutton .secondary-action {
+  display: block;
+}
+
+.dropbutton-wrapper:not(.open) .dropbutton__item:first-of-type ~ .dropbutton__item {
+  visibility: hidden;
+  /**
+  * By setting a height of 1px, the dropbutton items are hidden
+  * from view while still occupying minimal space, ensuring the layout remains intact.
+  */
+  height: 1px;
+}
+
+.dropbutton__items {
+  position: fixed;
+  padding: .25rem;
+  background-color: var(--background-default-grey);
+}
+
+.dropbutton__items a {
+  margin-right: 2em;
+}
+
+.dropbutton li {
+  padding-bottom: 0;
+}
+
+.js td .dropbutton-multiple {
+  padding-right: 0;
+}
+
+.js .dropbutton li, .js .dropbutton a {
+  display: inline-block;
+}
diff --git a/css/component/form.css b/css/component/form.css
new file mode 100644
index 0000000000000000000000000000000000000000..e8c9651ada73ea184335c3c208f1009f62569348
--- /dev/null
+++ b/css/component/form.css
@@ -0,0 +1,119 @@
+/**
+ * @file
+ * Manage styles for form.
+ */
+
+:root {
+  --form-spacing: 1.5rem;
+  --form-spacing-s: calc(var(--form-spacing) * .67);
+  --form-spacing-xs: calc(var(--form-spacing) / 3);
+  --form-item-spacing: var(--form-spacing);
+
+  /* Label font-size * line-height */
+  --form-label-line-height: calc(1rem * 1.5rem);
+  /* Spacing between label and input. */
+  --form-label-input-spacing: .5rem;
+}
+
+/* Provide a class to remove spacing. */
+.dsfr4drupal-form--no-spacing {
+  --form-spacing: 0;
+}
+
+/* Unset form spacing for default DSFR forms. */
+.fr-follow__newsletter,
+.fr-search-bar {
+  --form-spacing: 0;
+}
+
+/* Provide a class to wrap form with borders. */
+.dsfr4drupal-form--bordered {
+  padding: var(--form-spacing-xs) var(--form-spacing-s) var(--form-spacing-s);
+  border: 1px solid var(--border-default-grey);
+}
+
+.fr-checkbox-group + .fr-checkbox-group,
+.fr-radio-group + .fr-radio-group {
+  margin-top:  .25rem;
+}
+
+.form-actions a + button,
+.form-actions button + button,
+.form-actions button + a {
+  margin-left: var(--form-item-spacing);
+}
+
+.fr-label > .field-edit-link {
+  font-size: .75em;
+}
+
+.fr-label .field-edit-link > button {
+  padding: 0;
+}
+
+.fr-upload-group {
+  padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing);
+  border: 1px solid var(--border-default-grey);
+}
+
+.fr-upload-group > label {
+  font-weight: 700;
+}
+
+/* Remove specific spacing into fieldset - START */
+.fr-fieldset__content .fr-checkbox-group input[type="checkbox"] + label::before,
+.fr-fieldset__content .fr-radio-group:not(.fr-radio-rich) input[type="radio"] + label::before {
+  top: auto;
+}
+
+.fr-fieldset__content .fr-checkbox-group label,
+.fr-fieldset__content .fr-radio-group label {
+  padding: 0;
+}
+/* Remove specific spacing into fieldset - END */
+
+main[role="main"] > .fr-container > form,
+main[role="main"] > .fr-container--fluid > form {
+  margin-block: var(--form-spacing);
+}
+
+.form-wrapper:not(:last-child) {
+  margin-bottom: var(--form-item-spacing);
+}
+
+.form-wrapper.js-filter-wrapper {
+  font-size: .75em;
+}
+
+/* Disable table scrolling into form item - START */
+.form-item .fr-table__container {
+  overflow: initial;
+}
+
+.form-item .fr-table > table caption {
+  max-width: calc(100vw - 2rem);
+}
+
+.form-item .fr-table .fr-table__wrapper .fr-table__content table th,
+.form-item .fr-table .fr-table__wrapper .fr-table__content table td {
+  white-space: normal;
+}
+/* Disable table scrolling into form - END */
+
+.js-filter-wrapper {
+  margin-top: calc(var(--form-item-spacing) * -.75);
+}
+
+.js input.form-autocomplete {
+  padding-right: 2.5rem;
+  background-position: calc(100% - 1rem) 50%;
+}
+
+.paragraphs-tabs-wrapper {
+  padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing);
+  border: 1px solid var(--border-default-grey);
+}
+
+.paragraphs-tabs-wrapper > .form-item {
+  margin-block: 0;
+}
diff --git a/css/drupal.node.preview.css b/css/component/node-preview.css
similarity index 69%
rename from css/drupal.node.preview.css
rename to css/component/node-preview.css
index bf08321352c675e92dae8727ee02f744723e5e13..860c39f0e8a3fbc89ab2131fc4612e9e1e53f5f1 100644
--- a/css/drupal.node.preview.css
+++ b/css/component/node-preview.css
@@ -1,6 +1,6 @@
 /**
  * @file
- * Manage node preview styling.
+ * Manage styles for node preview.
  */
 
 .node-preview-container {
@@ -8,7 +8,7 @@
   background-position: 0 100%;
   background-repeat: no-repeat;
   background-size: 100% .25rem;
-  background-image: linear-gradient(0deg,var(--border-plain-grey),var(--border-plain-grey));
+  background-image: linear-gradient(0deg,var(--border-default-grey),var(--border-default-grey));
   padding: 2rem 2rem 2.25rem;
   /* Based on DSFR header z-index. */
   z-index: calc(var(--ground) + 751);
diff --git a/css/component/paragraphs.admin.css b/css/component/paragraphs.admin.css
new file mode 100644
index 0000000000000000000000000000000000000000..bcf024f37a55d7d0be2e1750678a6212cdd3ec61
--- /dev/null
+++ b/css/component/paragraphs.admin.css
@@ -0,0 +1,29 @@
+/**
+ * @file
+ * Manage styles for paragraphs admin.
+ */
+
+.paragraph-type-title {
+  font-size: 1.5em;
+  font-weight: 700;
+  margin-top: var(--form-spacing-xs);
+}
+
+.js .field--widget-entity-reference-paragraphs td {
+  /* Force DSFR table styles */
+  padding: .5rem 1rem;
+}
+
+.js .field--widget-entity-reference-paragraphs .field-multiple-table > thead > tr > th:nth-child(2),
+.js .field--widget-entity-reference-paragraphs .field-multiple-table > tbody > tr > td:nth-child(3) {
+  padding: 0;
+}
+
+.js .field--widget-entity-reference-paragraphs .field-multiple-drag {
+  vertical-align: middle;
+  padding-right: 0;
+}
+
+.paragraphs-tabs-wrapper .field-multiple-table > thead > tr .field-label .paragraphs-actions {
+  margin-inline: auto 0;
+}
diff --git a/css/component/paragraphs.widget.css b/css/component/paragraphs.widget.css
new file mode 100644
index 0000000000000000000000000000000000000000..2c299990f740750c57a3607d65242532a2762c18
--- /dev/null
+++ b/css/component/paragraphs.widget.css
@@ -0,0 +1,45 @@
+/**
+ * @file
+ * Manage styles for paragraphs widget.
+ */
+
+.paragraph-type-label {
+  font-size: 1.5em;
+  font-weight: 700;
+}
+
+.js .field--widget-paragraphs td {
+  /* Force DSFR table styles */
+  padding: .5rem 1rem;
+}
+
+.js .field--widget-paragraphs .field-multiple-drag {
+  min-width: auto;
+}
+
+.js .field--widget-paragraphs .draggable .tabledrag-handle {
+  padding: 0;
+}
+
+.js .field--widget-paragraphs .draggable .field-multiple-drag {
+  padding-right: 0;
+}
+
+.paragraphs-tabs-wrapper .field-multiple-table > thead > tr > th:nth-child(2),
+.paragraphs-tabs-wrapper .field-multiple-table > tbody > tr > td:nth-child(3) {
+  padding: 0;
+}
+
+.paragraphs-tabs-wrapper .field-multiple-table > thead > tr .field-label .label {
+  display: inline-block;
+}
+
+.paragraphs-tabs-wrapper .field-multiple-table > thead > tr .field-label .paragraphs-actions {
+  margin-inline: auto 0;
+}
+
+.is-horizontal .paragraphs-tabs:first-of-type {
+  /* Remove unwanted styles */
+  position: static;
+  background-color: transparent;
+}
diff --git a/css/component/radio.css b/css/component/radio.css
new file mode 100644
index 0000000000000000000000000000000000000000..ffc48409cd9fd8ca6aac015e5fdc9ee35d2e402e
--- /dev/null
+++ b/css/component/radio.css
@@ -0,0 +1,12 @@
+/**
+ * @file
+ * Manage styles for radio.
+ */
+
+.fr-fieldset .fr-fieldset__content .fr-radio-group:not(.fr-radio-rich) input[type="radio"] {
+  top: auto;
+}
+
+.fr-fieldset .fr-fieldset__content .fr-radio-group:not(.fr-radio-rich) input[type="radio"] + label {
+  background-position: left center;
+}
diff --git a/css/component/table.css b/css/component/table.css
new file mode 100644
index 0000000000000000000000000000000000000000..962213b1f10a2c38c08ecf48659c548dfdd3acf6
--- /dev/null
+++ b/css/component/table.css
@@ -0,0 +1,14 @@
+/**
+ * @file
+ * Manage styles for table.
+ */
+
+.fr-table__content td .fr-checkbox-group,
+.fr-table__content th .fr-checkbox-group {
+  vertical-align: top;
+}
+
+.fr-table__content th.select-all input[type="checkbox"] {
+  width: 1.5rem;
+  height: 1.5rem;
+}
diff --git a/css/component/tabledrag.css b/css/component/tabledrag.css
new file mode 100644
index 0000000000000000000000000000000000000000..76f5569ba94e1ebd8930d37d994caef2c39ef35c
--- /dev/null
+++ b/css/component/tabledrag.css
@@ -0,0 +1,42 @@
+/**
+ * @file
+ * Manage styles for tabledrag.
+ */
+
+a.tabledrag-handle[href] {
+  background-image: none;
+}
+
+.draggable a.tabledrag-handle {
+  margin: 0;
+}
+
+a.tabledrag-handle .handle {
+  margin: 0;
+  padding: 0;
+  min-width: 16px;
+  min-height: 16px;
+  height: 100%;
+  background-position: center center;
+}
+
+.draggable.drag td,
+.draggable.drag th {
+  background-color: var(--background-default-grey-active);
+}
+
+.field-multiple-drag {
+  max-width: fit-content;
+}
+
+.tabledrag-toggle-weight {
+  font-size: .75em;
+}
+
+.tabledrag-toggle-weight-wrapper + .fr-table {
+  margin-top: 0;
+}
+
+.tabledrag-changed-warning {
+  color: var(--warning-425-625);
+}
diff --git a/css/toolbar.css b/css/component/toolbar.css
similarity index 86%
rename from css/toolbar.css
rename to css/component/toolbar.css
index 1989351ca3c2578409252ead1ecce2777441b1c1..235df43c2aaf6e3c8b3ca82123ec289947385077 100644
--- a/css/toolbar.css
+++ b/css/component/toolbar.css
@@ -1,6 +1,6 @@
 /**
  * @file
- * Fix toolbar styling.
+ * Manage styles for toolbar.
  */
 
 .toolbar a {
diff --git a/css/component/views-exposed-form.css b/css/component/views-exposed-form.css
new file mode 100644
index 0000000000000000000000000000000000000000..263b11ac228b1b805297d39631b662bd60ce5ab0
--- /dev/null
+++ b/css/component/views-exposed-form.css
@@ -0,0 +1,39 @@
+/**
+ * @file
+ * Manage styles for views exposed form.
+ */
+
+/**
+ * Use flexbox and some margin resets to make the fields + actions go inline.
+ */
+.views-exposed-form--inline {
+  display: flex;
+  flex-wrap: wrap;
+  margin-block: var(--form-spacing-s);
+}
+
+.views-exposed-form--preview.views-exposed-form--preview {
+  margin-top: 0;
+}
+
+.views-exposed-form--inline .views-exposed-form__item {
+  max-width: 100%;
+  margin-block: var(--form-spacing-s) 0;
+  margin-inline: 0 var(--form-spacing-xs);
+}
+
+.views-exposed-form--inline .form-item--no-label,
+.views-exposed-form--inline .views-exposed-form__item.views-exposed-form__item.views-exposed-form__item--actions {
+  margin-block: var(--form-spacing-s) 0;
+  align-self: flex-end;
+}
+
+.views-exposed-form--inline .form-item--no-label,
+.views-exposed-form--inline .views-exposed-form__item.views-exposed-form__item--actions {
+  margin-top: calc(var(--form-label-line-height) + var(--form-label-input-spacing));
+}
+
+.views-exposed-form--inline .fr-input-group:not(:last-child),
+.views-exposed-form--inline .fr-select-group:not(:last-child) {
+  margin-bottom: 0;
+}
diff --git a/css/card.css b/css/theme/card.css
similarity index 59%
rename from css/card.css
rename to css/theme/card.css
index 47925848e327b6bd01b2883630f9a5600f9d6e4b..32f5bf9a87ad13f53305b26647a30c690d6993e7 100644
--- a/css/card.css
+++ b/css/theme/card.css
@@ -1,6 +1,6 @@
 /**
  * @file
- * Fix card styling with "time" html tag.
+ * Fix styles for card with "time" html tag.
  */
 
 .fr-card__detail time {
diff --git a/css/display.button.css b/css/theme/display.button.css
similarity index 100%
rename from css/display.button.css
rename to css/theme/display.button.css
diff --git a/css/theme/horizontal-tabs.css b/css/theme/horizontal-tabs.css
new file mode 100644
index 0000000000000000000000000000000000000000..6943d5d17e23fe9cd09c08c8a9f28e43917f8e4d
--- /dev/null
+++ b/css/theme/horizontal-tabs.css
@@ -0,0 +1,62 @@
+/**
+ * @file
+ * Manage styles for horizontal tabs.
+ * Try as much as possible to reproduce the styles of the DSFR tabs.
+ */
+
+.horizontal-tabs {
+  margin: 0 0 var(--form-spacing);
+  border: 0;
+}
+
+.horizontal-tabs .horizontal-tabs-list {
+  /* Remove unwanted styles */
+  background: none;
+  border: 0;
+
+  /* Force DSFR tabs styles */
+  display: flex;
+  margin: -4px 0;
+  padding: 4px .75rem;
+}
+
+.horizontal-tabs ul.horizontal-tabs-list li a {
+  /* Force DSFR tabs styles */
+  display: inline-flex;
+  padding: .5rem 1rem;
+  position: relative;
+}
+
+.horizontal-tabs .horizontal-tab-button {
+  /* Remove unwanted styles */
+  float: none;
+  background: none;
+  border: 0;
+  min-width: auto;
+}
+
+.horizontal-tabs .horizontal-tab-button a:hover {
+  /* Force DSFR tabs styles */
+  background-color: var(--hover-tint);
+}
+
+.horizontal-tabs-panes {
+  border: 1px solid var(--border-default-grey);
+  border-top: 0;
+  padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing-s);
+}
+
+.horizontal-tabs-panes .horizontal-tabs-pane {
+  padding: 0;
+}
+
+.horizontal-tabs-list.fr-tabs__list + .horizontal-tabs-panes {
+  /* Fix order */
+  order: 3;
+}
+
+.horizontal-tabs-panes .fr-accordion__btn,
+.horizontal-tabs-panes .fr-input,
+.horizontal-tabs-panes .fr-select {
+  box-sizing: border-box;
+}
diff --git a/css/theme/layout-builder.css b/css/theme/layout-builder.css
new file mode 100644
index 0000000000000000000000000000000000000000..1f1274fe6cf45092089bc92d6431251afe571f99
--- /dev/null
+++ b/css/theme/layout-builder.css
@@ -0,0 +1,43 @@
+/**
+ * @file
+ * Manage styles for layout builder.
+ */
+
+.layout-builder__link--add {
+  color: var(--text-default-grey);
+  padding-left: 0;
+}
+
+.layout-builder__link--add[href]:hover {
+  --underline-hover-width: 0;
+}
+
+.layout-builder__link--add::before {
+  content: '';
+  display: inline-block;
+  vertical-align: middle;
+  width: 16px;
+  height: 16px;
+  background-color: var(--text-default-grey);
+  mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16px' height='16px'%3e%3cpath d='M0.656,9.023c0,0.274,0.224,0.5,0.499,0.5l4.853,0.001c0.274-0.001,0.501,0.226,0.5,0.5l0.001,4.853 c-0.001,0.273,0.227,0.5,0.501,0.5l1.995-0.009c0.273-0.003,0.497-0.229,0.5-0.503l0.002-4.806c0-0.272,0.228-0.5,0.499-0.502 l4.831-0.021c0.271-0.005,0.497-0.23,0.501-0.502l0.008-1.998c0-0.276-0.225-0.5-0.499-0.5l-4.852,0c-0.275,0-0.502-0.228-0.501-0.5 L9.493,1.184c0-0.275-0.225-0.499-0.5-0.499L6.997,0.693C6.722,0.694,6.496,0.92,6.495,1.195L6.476,6.026 c-0.001,0.274-0.227,0.5-0.501,0.5L1.167,6.525C0.892,6.526,0.665,6.752,0.665,7.026L0.656,9.023z'/%3e%3c/svg%3e");
+  mask-position: left center;
+  mask-repeat: no-repeat;
+  mask-size: 100%;
+  margin-right: .25rem;
+}
+
+.layout-builder__link--add::before:hover {
+  background-color: var(--hover-tint);
+}
+
+.layout-builder__link--remove {
+  /* Force layout builder styles. */
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3e%3cpath fill='%23bebebe' d='M3.51 13.925c.194.194.512.195.706.001l3.432-3.431c.194-.194.514-.194.708 0l3.432 3.431c.192.194.514.193.707-.001l1.405-1.417c.191-.195.189-.514-.002-.709l-3.397-3.4c-.192-.193-.192-.514-.002-.708l3.401-3.43c.189-.195.189-.515 0-.709l-1.407-1.418c-.195-.195-.513-.195-.707-.001l-3.43 3.431c-.195.194-.516.194-.708 0l-3.432-3.431c-.195-.195-.512-.194-.706.001l-1.407 1.417c-.194.195-.194.515 0 .71l3.403 3.429c.193.195.193.514-.001.708l-3.4 3.399c-.194.195-.195.516-.001.709l1.406 1.419z'/%3e%3c/svg%3e");
+  background-position: center center;
+  background-repeat: no-repeat;
+  background-size: auto;
+}
+
+.layout-builder__region {
+  margin-top: .25rem;
+}
diff --git a/css/theme/media-library.css b/css/theme/media-library.css
new file mode 100644
index 0000000000000000000000000000000000000000..a31b7ac1932e5503b7d882cb9947a0d5711cbd3d
--- /dev/null
+++ b/css/theme/media-library.css
@@ -0,0 +1,68 @@
+/**
+ * @file
+ * Manage styles for media library.
+ */
+
+.media-library-content {
+  padding: 1em;
+}
+
+/* Remove margin at the end of the form */
+.js-media-library-view .views-form {
+  margin-bottom: calc(var(--form-spacing) * -1);
+}
+
+.js-media-library-view .views-row {
+  position: relative;
+  margin-block: var(--form-item-spacing) !important; /* stylelint-disable-line declaration-no-important */
+}
+
+.js-media-library-view .views-row .fr-checkbox-group {
+  position: absolute;
+  margin-top: .5rem;
+  margin-left: .5rem;
+}
+
+.js-media-library-add-form-added-media {
+  --ul-type: none;
+}
+
+.js-media-library-add-form-added-media li {
+  padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing);
+  border: 1px solid var(--border-default-grey);
+}
+
+.js-media-library-add-form-added-media img {
+  display: block;
+  margin: 0 auto;
+  max-width: 100%;
+  height: auto;
+}
+
+.js-media-library-add-form-added-media .form-item-name label {
+  display: inline-block;
+  font-weight: 700;
+}
+
+.media-library-select-all input[type="checkbox"] {
+  display: inline-block;
+  vertical-align: bottom;
+  width: 1.5rem;
+  height: 1.5rem;
+  margin-right: .5rem;
+}
+
+.media-library-item__edit,
+.media-library-item__remove {
+  position: absolute;
+  z-index: 1;
+  top: 1.25rem;
+}
+
+.media-library-item__edit {
+  right: 3.5rem;
+}
+
+.media-library-item__remove {
+  right: 1.25rem;
+}
diff --git a/css/navigation.header.css b/css/theme/navigation.header.css
similarity index 100%
rename from css/navigation.header.css
rename to css/theme/navigation.header.css
diff --git a/css/pager.css b/css/theme/pager.css
similarity index 84%
rename from css/pager.css
rename to css/theme/pager.css
index a1aa43f03c3098f19e624e5de7c049f8254e94e1..ca2627ca87bc99e4e5d6ad8c58cc9c4d670f3848 100644
--- a/css/pager.css
+++ b/css/theme/pager.css
@@ -1,6 +1,6 @@
 /**
  * @file
- * Fix pagination to allow AJAX use.
+ * Fix styles for pagination to allow AJAX use.
  */
 
 .fr-pagination__link[href=""],
diff --git a/css/theme/select2.css b/css/theme/select2.css
new file mode 100644
index 0000000000000000000000000000000000000000..755286317c8b74c000c5d90e4218bfa919a1ca0e
--- /dev/null
+++ b/css/theme/select2.css
@@ -0,0 +1,165 @@
+/**
+ * @file
+ * Manage styles for Select2 widget.
+ */
+
+/* Duplicate "select" component styles - START */
+.select2-container .selection {
+  display: block;
+  appearance: none;
+  width: 100%;
+  border-radius: 0.25rem 0.25rem 0 0;
+  font-size: 1rem;
+  line-height: 1.5rem;
+  padding: 0.5rem 2.5rem 0.5rem 1rem;
+  background-repeat: no-repeat;
+  background-position: calc(100% - 1rem) 50%;
+  background-size: 1rem 1rem;
+  color: var(--text-default-grey);
+  background-color: var(--background-contrast-grey);
+
+  --idle: transparent;
+  --hover: var(--background-contrast-grey-hover);
+  --active: var(--background-contrast-grey-active);
+
+  box-shadow: inset 0 -2px 0 0 var(--border-plain-grey);
+
+  --data-uri-svg: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23161616' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>");
+
+  background-image: var(--data-uri-svg);
+}
+
+.fr-fieldset--error .select2-container,
+.fr-select-group--error .select2-container {
+  box-shadow: inset 0 -2px 0 0 var(--border-plain-error);
+}
+
+.select2-container:-webkit-autofill,
+.select2-container:-webkit-autofill:hover,
+.select2-container:-webkit-autofill:focus {
+  box-shadow: inset 0 -2px 0 0 var(--border-plain-grey), inset 0 0 0 1000px var(--background-contrast-blue-france);
+  -webkit-text-fill-color: var(--text-label-grey);
+}
+
+.fr-select:disabled + .select2-container {
+  --data-uri-svg: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23929292' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>");
+
+  color: var(--text-disabled-grey);
+  box-shadow: inset 0 -2px 0 0 var(--border-disabled-grey);
+  background-image: var(--data-uri-svg);
+}
+
+:root[data-fr-theme="dark"] .select2-container {
+  --data-uri-svg: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23fff' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>");
+}
+
+:root[data-fr-theme="dark"] .fr-select:disabled + .select2-container {
+  --data-uri-svg: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23666' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>");
+
+  cursor: not-allowed;
+}
+
+@media (-ms-high-contrast: active), (forced-colors: active) {
+  .select2-container {
+    background-image: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' fill='canvastext'><path d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>");
+  }
+}
+/* Duplicate "select" component styles - END */
+
+.select2-container .select2-selection--multiple {
+  background-color: transparent;
+  padding: 0;
+  display: block;
+  min-height: auto;
+}
+
+.select2-container .select2-selection--multiple,
+.select2-container.select2-container--focus .select2-selection--multiple {
+  border: 0;
+}
+
+.select2-container .select2-search {
+  display: inline-block;
+}
+
+.select2-container .select2-search--inline .select2-search__field {
+  margin: 0;
+  font-size: inherit;
+  font-family: Marianne, arial, sans-serif;
+  height: 24px;
+}
+
+.select2-container .select2-search--inline .select2-search__field::placeholder {
+  color: var(--text-default-grey);
+}
+
+.select2-container .select2-selection--single .select2-selection__placeholder {
+  color: var(--text-mention-grey);
+}
+
+.select2-dropdown {
+  margin-top: 2px;
+  padding: .25rem;
+  background-color: var(--background-contrast-grey);
+}
+
+.select2-container--open .select2-dropdown--below {
+  border: 1px solid var(--border-plain-grey);
+}
+
+.select2-container--default .select2-results__option--highlighted.select2-results__option--selectable {
+  background-color: var(--background-contrast-grey-hover);
+  color: var(--text-default-grey);
+}
+
+.select2-selection__rendered .select2-selection__choice {
+  margin-right: .5rem;
+}
+
+.select2-container--default .select2-selection--multiple .select2-selection__choice {
+  position: relative;
+  display: inline-flex;
+  background-color: var(--background-action-high-blue-france);
+  color: var(--text-inverted-blue-france);
+  border-radius: 1rem;
+  font-size: .875rem;
+  padding: .25rem .75rem;
+  border: 0;
+}
+
+.select2-container--default .select2-selection--multiple .select2-selection__choice:hover {
+  background-color: var(--background-action-high-blue-france-hover);
+}
+
+.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
+  position: initial;
+  left: auto;
+  top: auto;
+  display: flex;
+  order: 2;
+  padding: 0 .25rem;
+  border: 0;
+  font-size: 1.5em;
+  line-height: 1;
+  color: var(--text-inverted-blue-france);
+}
+
+.select2-container--default .select2-selection--multiple .select2-selection__choice__remove::before {
+  content: "";
+  position: absolute;
+  display: block;
+  inset: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 1;
+}
+
+.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover,
+.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:focus {
+  background-color: transparent;
+  color: inherit;
+}
+
+.select2-container--default .select2-selection--multiple .select2-selection__choice__display {
+  display: flex;
+}
diff --git a/css/theme/system.admin.css b/css/theme/system.admin.css
new file mode 100644
index 0000000000000000000000000000000000000000..510c76b10ea5b6dda4851aba28f127efc0d0c87e
--- /dev/null
+++ b/css/theme/system.admin.css
@@ -0,0 +1,23 @@
+/**
+ * @file
+ * Manage styles for system admin.
+ */
+
+.compact-link {
+  text-align: right;
+}
+
+.panel {
+  padding: 1rem 1rem .5rem;
+  border: 1px solid var(--border-default-grey);
+}
+
+.panel + .panel {
+  margin-top: 1.5rem;
+}
+
+.list-group dd + dt {
+  margin-top: .25rem;
+  padding-top: .5rem;
+  border-top: 1px solid var(--border-default-grey);
+}
diff --git a/css/tile.css b/css/theme/tile.css
similarity index 59%
rename from css/tile.css
rename to css/theme/tile.css
index 494eca32ed3294b76d8b877683fe21f81f9ab636..13c9ba4caf92a9812499af164a0fcc6806c35842 100644
--- a/css/tile.css
+++ b/css/theme/tile.css
@@ -1,6 +1,6 @@
 /**
  * @file
- * Fix card styling with "time" html tag.
+ * Fix styles for tile with "time" html tag.
  */
 
 .fr-tile__detail time {
diff --git a/css/tooltip.css b/css/theme/tooltip.css
similarity index 89%
rename from css/tooltip.css
rename to css/theme/tooltip.css
index c7fac6daea5227412d92ed16bfe20c73ee6fa4f2..9d75cb1a6eb3847ca016e8e706ca06651697ea07 100644
--- a/css/tooltip.css
+++ b/css/theme/tooltip.css
@@ -1,6 +1,6 @@
 /**
  * @file
- * Fix tooltip styling.
+ * Fix styles for tooltip.
  */
 
 .fr-btn--tooltip {
diff --git a/css/theme/ui-dialog.css b/css/theme/ui-dialog.css
new file mode 100644
index 0000000000000000000000000000000000000000..a682040fc621e172419abdecf02bd0ea20e367c1
--- /dev/null
+++ b/css/theme/ui-dialog.css
@@ -0,0 +1,95 @@
+/**
+ * @file
+ * Manage styles for UI dialog.
+ * Try as much as possible to reproduce the styles of the DSFR modal.
+ */
+
+.ui-dialog {
+  padding: 0;
+  max-height: 80vh !important; /* stylelint-disable-line declaration-no-important */
+  filter: drop-shadow(var(--lifted-shadow));
+}
+
+.ui-dialog .ui-dialog-titlebar {
+  margin: 0;
+  padding: 4rem 2rem 0;
+}
+
+.ui-dialog .ui-dialog-title {
+  --title-spacing: 0 0 1rem 0;
+
+  margin: var(--title-spacing);
+}
+
+.ui-dialog .ui-dialog-content {
+  margin-bottom: 4rem;
+  padding: 0 2rem;
+}
+
+.ui-widget-content {
+  background: var(--background-default-grey);
+  color: var(--text-default-grey);
+}
+
+.ui-widget.ui-widget-content {
+  border: 0;
+}
+
+.ui-widget-header {
+  background: none;
+  border: 0;
+  color: var(--text-title-grey);
+  font-size: 1.375rem;
+  font-weight: 700;
+}
+
+@media (width >= 48em) {
+  .ui-widget-header {
+    font-size: 1.5rem;
+    line-height: 2rem;
+  }
+}
+
+.ui-dialog .ui-dialog-titlebar-close {
+  top: 1rem;
+  right: 2rem;
+  margin: 0;
+  padding: .25rem .75rem;
+  width: auto;
+  height: auto;
+  min-height: 2rem;
+  background: none;
+  border: 0;
+  text-align: right;
+  text-indent: initial;
+  color: var(--text-action-high-blue-france);
+  font-size: .875rem;
+  font-weight: 500;
+  line-height: 1.5rem;
+  overflow: initial;
+}
+
+.ui-dialog .ui-dialog-titlebar-close:hover {
+  background-color: var(--hover-tint);
+}
+
+.ui-dialog .ui-dialog-titlebar-close::after {
+  --icon-size: 1rem;
+
+  background-color: currentcolor;
+  content: '';
+  display: inline-block;
+  flex: 0 0 auto;
+  height: var(--icon-size);
+  margin-left: .5rem;
+  margin-right: -.125rem;
+  mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggZD0ibTEyIDEwLjYgNC45NS00Ljk2IDEuNCAxLjRMMTMuNDIgMTJsNC45NiA0Ljk1LTEuNCAxLjRMMTIgMTMuNDJsLTQuOTUgNC45Ni0xLjQtMS40TDEwLjU4IDEyIDUuNjMgNy4wNWwxLjQtMS40eiIvPjwvc3ZnPg==");
+  mask-size: 100% 100%;
+  vertical-align: calc((.75em - var(--icon-size)) * .5);
+  width: var(--icon-size);
+}
+
+.ui-dialog .ui-dialog-titlebar-close .ui-button-icon,
+.ui-dialog .ui-dialog-titlebar-close  .ui-button-icon-space {
+  display: none;
+}
diff --git a/css/theme/vertical-tabs.css b/css/theme/vertical-tabs.css
new file mode 100644
index 0000000000000000000000000000000000000000..bf97ad7561d4e0de151e5a74dfec78b9aca49a6a
--- /dev/null
+++ b/css/theme/vertical-tabs.css
@@ -0,0 +1,51 @@
+/**
+ * @file
+ * Manage styles for vertical tabs.
+ */
+
+.vertical-tabs__menu [href] {
+  background-image: none;
+}
+
+.vertical-tabs__menu li {
+  --li-bottom: 0;
+}
+
+.vertical-tabs__menu-item a:hover {
+  outline: none;
+}
+
+.vertical-tabs__menu-item.is-selected a {
+  background-color: var(--background-default-grey);
+  background-image: linear-gradient(0deg,var(--border-active-blue-france),var(--border-active-blue-france)),linear-gradient(0deg,var(--border-default-grey),var(--border-default-grey)),linear-gradient(0deg,var(--border-default-grey),var(--border-default-grey)),linear-gradient(0deg,var(--border-default-grey),var(--border-default-grey));
+  background-size: 100% 2px,1px calc(100% - 1px),1px calc(100% - 1px),0 1px;
+  color: var(--text-active-blue-france);
+}
+
+.vertical-tabs__menu-item:not(.is-selected) a {
+  background-color: var(--background-action-low-blue-france);
+}
+
+.vertical-tabs__menu-item:not(.is-selected) a:hover {
+  background-color: var(--background-action-low-blue-france-hover);
+}
+
+.vertical-tabs__menu-item.is-selected .vertical-tabs__menu-item-title {
+  color: inherit;
+}
+
+.vertical-tabs__menu-item a:focus .vertical-tabs__menu-item-title,
+.vertical-tabs__menu-item a:active .vertical-tabs__menu-item-title,
+.vertical-tabs__menu-item a:hover .vertical-tabs__menu-item-title {
+  text-decoration: none;
+}
+
+.vertical-tabs__panes {
+  padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing-s);
+}
+
+.vertical-tabs__pane .fr-accordion__btn,
+.vertical-tabs__pane .fr-input,
+.vertical-tabs__pane .fr-select {
+  box-sizing: border-box;
+}
diff --git a/dsfr4drupal.info.yml b/dsfr4drupal.info.yml
index 17068cbc63c869ae29a26234185913350226d0f7..caf13fbad435a7b3d6a223205658ce630dd77ed0 100644
--- a/dsfr4drupal.info.yml
+++ b/dsfr4drupal.info.yml
@@ -33,21 +33,49 @@ ckeditor5-stylesheets:
   - css/ckeditor5.css
 
 libraries:
+  - dsfr4drupal/core
   - dsfr4drupal/utility
 
 libraries-extend:
+  core/drupal.dialog:
+    - dsfr4drupal/drupal.dialog
+  core/drupal.dropbutton:
+    - dsfr4drupal/drupal.dropbutton
   core/drupal.message:
     - dsfr4drupal/drupal.message
+  core/drupal.tabledrag:
+    - dsfr4drupal/drupal.tabledrag
+  core/drupal.vertical-tabs:
+    - dsfr4drupal/drupal.vertical-tabs
+  field_group/element.horizontal_tabs:
+    - dsfr4drupal/element.horizontal_tabs
+  layout_builder/drupal.layout_builder:
+    - dsfr4drupal/drupal.layout_builder
+  media_library/view:
+    - dsfr4drupal/media_library.theme
+    - dsfr4drupal/media_library.view
+  media_library/widget:
+    - dsfr4drupal/media_library.theme
   navigation/navigation.layout:
     - dsfr4drupal/navigation.layout
   node/drupal.node.preview:
     - dsfr4drupal/drupal.node.preview
+  paragraphs/drupal.paragraphs.admin:
+    - dsfr4drupal/drupal.paragraphs.admin
+  paragraphs/drupal.paragraphs.widget:
+    - dsfr4drupal/drupal.paragraphs.widget
+  select2/select2:
+    - dsfr4drupal/select2
+  system/admin:
+    - dsfr4drupal/admin
   tarte_au_citron/tarte_au_citron_lib:
     - dsfr4drupal/tarteaucitron
   tacjs/tarteaucitron.js:
     - dsfr4drupal/tarteaucitron
   toolbar/toolbar:
     - dsfr4drupal/toolbar
+  views/views.module:
+    - dsfr4drupal/views
 
 libraries-override:
   tacjs/tarteaucitron.js:
diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml
index 99244fcd9ffbe0874e8a0c089ae64e694b866a2b..57f6416718aa05c67f6e145dd4435cca72dd74f3 100644
--- a/dsfr4drupal.libraries.yml
+++ b/dsfr4drupal.libraries.yml
@@ -2,11 +2,19 @@
 # These Javascript files are needed for old/outdated browsers.
 # @see: https://www.systeme-de-design.gouv.fr/utilisation-et-organisation/developpeurs/prise-en-main/
 
+admin:
+  css:
+    theme:
+      css/theme/system.admin.css: {}
+
 component.accordion:
   css:
     component:
       /libraries/dsfr/dist/component/accordion/accordion.min.css: { minified: true }
+  js:
+    js/accordion.js: {}
   dependencies:
+    - core/once
     - dsfr4drupal/core
 
 component.alert:
@@ -62,7 +70,7 @@ component.card:
     component:
       /libraries/dsfr/dist/component/card/card.min.css: { minified: true }
     theme:
-      css/card.css: {}
+      css/theme/card.css: {}
   dependencies:
     - dsfr4drupal/component.button
     - dsfr4drupal/core
@@ -71,6 +79,7 @@ component.checkbox:
   css:
     component:
       /libraries/dsfr/dist/component/checkbox/checkbox.min.css: { minified: true }
+      css/component/checkbox.css: {}
   dependencies:
     - dsfr4drupal/component.form
     - dsfr4drupal/core
@@ -111,7 +120,9 @@ component.display:
 component.display.button:
   css:
     theme:
-      css/display.button.css: {}
+      css/theme/display.button.css: {}
+  dependencies:
+    - dsfr4drupal/core
 
 component.connect:
   css:
@@ -139,6 +150,7 @@ component.form:
   css:
     component:
       /libraries/dsfr/dist/component/form/form.min.css: { minified: true }
+      css/component/form.css: {}
   dependencies:
     - dsfr4drupal/core
 
@@ -242,7 +254,7 @@ component.pagination:
     component:
       /libraries/dsfr/dist/component/pagination/pagination.min.css: { minified: true }
     theme:
-      css/pager.css: {}
+      css/theme/pager.css: {}
   dependencies:
     - dsfr4drupal/core
 
@@ -301,6 +313,7 @@ component.radio:
   css:
     component:
       /libraries/dsfr/dist/component/radio/radio.min.css: { minified: true }
+      css/component/radio.css: {}
   dependencies:
     - dsfr4drupal/component.form
     - dsfr4drupal/core
@@ -397,6 +410,7 @@ component.table:
   css:
     component:
       /libraries/dsfr/dist/component/table/table.min.css: { minified: true }
+      css/component/table.css: {}
   js:
     /libraries/dsfr/dist/component/table/table.module.min.js:
       minified: true
@@ -425,7 +439,7 @@ component.tile:
     component:
       /libraries/dsfr/dist/component/tile/tile.min.css: { minified: true }
     theme:
-      css/tile.css: {}
+      css/theme/tile.css: {}
   dependencies:
     - dsfr4drupal/core
 
@@ -452,7 +466,7 @@ component.tooltip:
     component:
       /libraries/dsfr/dist/component/tooltip/tooltip.min.css: { minified: true }
     theme:
-      css/tooltip.css: {}
+      css/theme/tooltip.css: {}
   js:
     /libraries/dsfr/dist/component/tooltip/tooltip.module.min.js:
       minified: true
@@ -523,7 +537,7 @@ core:
   css:
     base:
       /libraries/dsfr/dist/core/core.min.css: { minified: true }
-      css/core.css: {}
+      css/base/core.css: {}
   js:
     js/core.js: {}
     /libraries/dsfr/dist/core/core.module.min.js:
@@ -548,6 +562,27 @@ core:
       verbose: true
       level: info
 
+drupal.dialog:
+  css:
+    theme:
+      # Need to fix weight to 99 to override "Gin toolbar" styles.
+      css/theme/ui-dialog.css: { weight: 100 }
+
+drupal.dropbutton:
+  css:
+    component:
+      css/component/dropbutton.css: {}
+  js:
+    js/dropbutton.js: {}
+  dependencies:
+    - core/drupal
+    - core/once
+
+drupal.layout_builder:
+  css:
+    theme:
+      css/theme/layout-builder.css: {}
+
 drupal.message:
   js:
     js/messages.js: {}
@@ -555,9 +590,55 @@ drupal.message:
     - dsfr4drupal/component.alert
 
 drupal.node.preview:
+  css:
+    component:
+      css/component/node-preview.css: {}
+
+drupal.paragraphs.admin:
+  css:
+    component:
+      css/component/paragraphs.admin.css: { }
+
+drupal.paragraphs.widget:
+  css:
+    component:
+      css/component/paragraphs.widget.css: { }
+  js:
+    js/paragraphs.widget.js: {}
+  dependencies:
+    - core/drupal
+    - core/once
+    - dsfr4drupal/component.tab
+
+drupal.tabledrag:
+  css:
+    component:
+      css/component/tabledrag.css: {}
+  js:
+    js/tabledrag.js: {}
+  dependencies:
+    - core/drupal
+    - core/jquery
+
+drupal.vertical-tabs:
+  css:
+    theme:
+      css/theme/vertical-tabs.css: {}
+
+element.horizontal_tabs:
   css:
     theme:
-      css/drupal.node.preview.css: {}
+      css/theme/horizontal-tabs.css: {}
+  js:
+    js/horizontal-tabs.js: {}
+  dependencies:
+    - core/once
+    - dsfr4drupal/component.tab
+
+local-actions:
+  css:
+    component:
+      css/component/action-links.css: {}
 
 #legacy:
 #  js:
@@ -568,10 +649,22 @@ drupal.node.preview:
 #      # Move DSFR modules to first load to improve Javascript file aggregation.
 #      weight: -50
 
+media_library.theme:
+  css:
+    theme:
+      css/theme/media-library.css: {}
+
+media_library.view:
+  js:
+     js/media-library.view.js: {}
+  dependencies:
+    - core/drupal
+    - core/once
+
 navigation.layout:
   css:
     theme:
-      css/navigation.header.css: {}
+      css/theme/navigation.header.css: {}
 
 scheme:
   css:
@@ -587,6 +680,11 @@ scheme:
   dependencies:
     - dsfr4drupal/core
 
+select2:
+  css:
+    theme:
+      css/theme/select2.css: {}
+
 tarteaucitron:
   css:
     theme:
@@ -594,8 +692,8 @@ tarteaucitron:
 
 toolbar:
   css:
-    theme:
-      css/toolbar.css: {}
+    component:
+      css/component/toolbar.css: {}
 
 utility:
   css:
@@ -608,3 +706,10 @@ utility.icons:
   css:
     base:
       /libraries/dsfr/dist/utility/icons/icons.min.css: { minified: true }
+
+views:
+  css:
+    component:
+      css/component/views-exposed-form.css: {}
+  dependencies:
+    - dsfr4drupal/drupal.form
diff --git a/dsfr4drupal.theme b/dsfr4drupal.theme
index 657df94570f61247cf4f375696e23030214f3fb4..0a9999418b3469a9250812a2fe79e9b620bf0906 100644
--- a/dsfr4drupal.theme
+++ b/dsfr4drupal.theme
@@ -10,6 +10,7 @@ declare(strict_types=1);
 use Drupal\Core\Asset\AttachedAssetsInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\dsfr4drupal\Dsfr4DrupalInterface;
+use Drupal\dsfr4drupal\Dsfr4DrupalPreRender;
 
 // Include all files from the includes directory.
 $includes_path = dirname(__FILE__) . '/includes/*.theme';
@@ -17,6 +18,18 @@ foreach (glob($includes_path) as $file) {
   require_once dirname(__FILE__) . '/includes/' . basename($file);
 }
 
+/**
+ * Implements hook_element_info_alter().
+ */
+function dsfr4drupal_element_info_alter(array &$type): void {
+  if (isset($type['horizontal_tabs'])) {
+    $type['horizontal_tabs']['#pre_render'][] = [Dsfr4DrupalPreRender::class, 'horizontalTabs'];
+  }
+  if (isset($type['vertical_tabs'])) {
+    $type['vertical_tabs']['#pre_render'][] = [Dsfr4DrupalPreRender::class, 'verticalTabs'];
+  }
+}
+
 /**
  * Implements hook_preprocess().
  */
diff --git a/includes/field.theme b/includes/field.theme
index a9988cd234d4a814b95e78926d19c17d789e8ccb..f1dec96f1b43ea73f8f8bf913328c25881ece6b3 100644
--- a/includes/field.theme
+++ b/includes/field.theme
@@ -32,3 +32,15 @@ function dsfr4drupal_preprocess_field(array &$variables): void {
     }
   }
 }
+
+/**
+ * Implements hook_preprocess_HOOK() for "field__media__thumbnail".
+ */
+function dsfr4drupal_preprocess_field__media__thumbnail(array &$variables): void {
+  $items = &$variables['items'];
+
+  // Add "fr-responsive-img" by default.
+  foreach ($items as &$item) {
+    $item['content']['#item_attributes']['class'][] = 'fr-responsive-img';
+  }
+}
diff --git a/includes/form.theme b/includes/form.theme
index 1e613965d5c01cd1a4471a3c017f87549cde09d8..7f73ee61b30edd20023494c2af8f429d772bf221 100644
--- a/includes/form.theme
+++ b/includes/form.theme
@@ -34,6 +34,29 @@ function dsfr4drupal_form_node_preview_form_select_alter(array &$form, FormState
   $form['#attached']['library'][] = 'dsfr4drupal/component.link';
 }
 
+/**
+ * Implements hook_form_FORM_ID_alter() for "views_exposed_form".
+ */
+function dsfr4drupal_form_views_exposed_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
+  /** @var \Drupal\views\ViewExecutable $view */
+  $view = $form_state->getStorage()['view'];
+
+  $view_ids = [
+    // Stylize media library widget modal.
+    'media_library',
+  ];
+
+  if (
+    // Stylize views exposed form on admin pages.
+    \Drupal::service('router.admin_context')->isAdminRoute() ||
+    // Specific rule for some views.
+    in_array($view->id(), $view_ids)
+  ) {
+    $form['#attributes']['class'][] = 'views-exposed-form--inline';
+    $form['#attributes']['class'][] = 'dsfr4drupal-form--bordered';
+  }
+}
+
 /**
  * Implements hook_preprocess_HOOK() for "fieldset".
  */
@@ -52,6 +75,9 @@ function dsfr4drupal_preprocess_fieldset(array &$variables): void {
     $variables['attributes']['aria-labelledby'] .= ' ' . $variables['error_id'];
   }
 
+  // Show help text as tooltip.
+  $variables['description_tooltip'] = theme_get_setting('form_description_tooltip') ?? FALSE;
+
   $variables['#attached']['library'][] = 'dsfr4drupal/component.form';
 }
 
@@ -70,6 +96,29 @@ function dsfr4drupal_preprocess_form(array &$variables): void {
   }
 }
 
+/**
+ * Implements hook_preprocess_HOOK() for "views_exposed_form".
+ */
+function dsfr4drupal_preprocess_views_exposed_form(array &$variables): void {
+  $form = &$variables['form'];
+
+  // Add BEM classes for items in the form.
+  // Sorted keys.
+  foreach (Element::children($form, TRUE) as $child_key) {
+    if (!empty($form[$child_key]['#type'])) {
+      if ($form[$child_key]['#type'] === 'actions') {
+        // We need the key of the element that precedes the actions' element.
+        $form[$child_key]['#attributes']['class'][] = 'views-exposed-form__item';
+        $form[$child_key]['#attributes']['class'][] = 'views-exposed-form__item--actions';
+      }
+
+      if (!in_array($form[$child_key]['#type'], ['hidden', 'actions'])) {
+        $form[$child_key]['#wrapper_attributes']['class'][] = 'views-exposed-form__item';
+      }
+    }
+  }
+}
+
 /**
  * Implements hook_preprocess_HOOK() for "webform".
  */
@@ -134,7 +183,7 @@ function dsfr4drupal_preprocess_form_element(array &$variables): void {
  */
 function dsfr4drupal_preprocess_form_element_label(array &$variables): void {
   // We need to have description in form element label template.
-  $variables['description'] = $variables['element']['#description'];
+  $variables['description'] = $variables['element']['#description'] ?? '';
 
   // Show help text as tooltip.
   $variables['description_tooltip'] = theme_get_setting('form_description_tooltip') ?? FALSE;
@@ -265,6 +314,23 @@ function dsfr4drupal_preprocess_textarea(array &$variables): void {
   }
 }
 
+/**
+ * Implements hook_theme_suggestions_HOOK_alter() for "form_element".
+ */
+function dsfr4drupal_theme_suggestions_form_element_alter(array &$suggestions, array $variables): void {
+  if (empty($suggestions)) {
+    $suggestions[] = 'form_element';
+  }
+
+  // Set suggestions by form element type.
+  $new_suggestions = [];
+  foreach ($suggestions as $suggestion) {
+    $new_suggestions[] = $suggestion;
+    $new_suggestions[] = $suggestion . '__' . $variables['element']['#type'];
+  }
+  $suggestions = $new_suggestions;
+}
+
 /**
  * Implements hook_theme_suggestions_HOOK_alter() for "input".
  */
diff --git a/includes/menu.theme b/includes/menu.theme
index 36748b1581a706cf530b67b2aba063f1a9b22e0b..08f0483fc07225f361280956fe86d0c809db1232 100644
--- a/includes/menu.theme
+++ b/includes/menu.theme
@@ -26,3 +26,13 @@ function dsfr4drupal_preprocess_menu(array &$variables): void {
   // Define menu "aria-label".
   $variables['aria_label'] = $menu ? $menu->label() : '';
 }
+
+/**
+ * Implements hook_preprocess_HOOK() for "menu".
+ */
+function dsfr4drupal_preprocess_menu_local_action(array &$variables): void {
+  $link = &$variables['link'];
+
+  $link['#options']['attributes']['class'][] = 'fr-btn';
+  $link['#attached']['library'][] = 'dsfr4drupal/component.button';
+}
diff --git a/includes/system.theme b/includes/system.theme
new file mode 100644
index 0000000000000000000000000000000000000000..7b3bf794ecc5f2011669470004c1530c42b666f4
--- /dev/null
+++ b/includes/system.theme
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Functions to support system theming in the "DSFR for Drupal" theme.
+ */
+
+declare(strict_types=1);
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter() for details.
+ */
+function dsfr4drupal_theme_suggestions_details_alter(array &$suggestions, array $variables): void {
+  /**
+   * The property "#horizontal_tab_item" is added during element prerender.
+   * @see: \Drupal\dsfr4drupal\Dsfr4DrupalPrerender::horizontalTabs()
+   */
+  if (!empty($variables['element']['#horizontal_tab_item'])) {
+    $suggestions[] = 'details__horizontal_tabs';
+  }
+
+  /**
+   * The property "#vertical_tab_item" is added during element prerender.
+   * @see: \Drupal\dsfr4drupal\Dsfr4DrupalPrerender::verticalTabs()
+   */
+  if (!empty($variables['element']['#vertical_tab_item'])) {
+    $suggestions[] = 'details__vertical_tabs';
+  }
+
+  /**
+   * Styles are reset with Drupal dialog off canvas.
+   * Use default details rendering instead of DSFR accordion component.
+   */
+  if (\Drupal::request()->query->get('_wrapper_format') === 'drupal_dialog.off_canvas') {
+    $suggestions[] = 'details__dialog_off_canvas';
+  }
+}
diff --git a/includes/views.theme b/includes/views.theme
new file mode 100644
index 0000000000000000000000000000000000000000..cd4484da4defd2c6cc80dfe8553244994581b6a3
--- /dev/null
+++ b/includes/views.theme
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * Functions to support views theming in the "DSFR for Drupal" theme.
+ */
+
+declare(strict_types=1);
+use Drupal\views\ViewExecutable;
+
+/**
+ * Implements hook_preprocss_hook() for "views_view__media_library".
+ */
+function dsfr4drupal_preprocess_views_view__media_library(array &$variables): void {
+  if (empty($variables['header'])) {
+    return;
+  }
+
+  foreach ($variables['header'] as &$header) {
+    $options = &$header['#options'];
+
+    // Add tab component attributes.
+    $options['attributes']['aria-selected'] = 'false';
+    $options['attributes']['class'][] = 'fr-tabs__tab';
+    $options['attributes']['role'] = 'tab';
+
+    // Set tab active.
+    if ($options['view']->current_display === $options['target_display_id']) {
+      $options['attributes']['aria-selected'] = 'true';
+      $options['attributes']['class'][] = 'fr-tabs__tab--selected';
+    }
+  }
+
+  $variables['#attached']['library'][] = 'dsfr4drupal/component.tab';
+  $variables['#attached']['library'][] = 'dsfr4drupal/component.link';
+}
+
+/**
+ * Implements hook_views_pre_render().
+ */
+function dsfr4drupal_views_pre_render(ViewExecutable $view) {
+  if (
+    $view->id() === 'media_library' &&
+    $view->current_display === 'page'
+  ) {
+    $add_classes = function (&$option, array $classes_to_add) {
+      $classes = preg_split('/\s+/', $option);
+      $classes = array_filter($classes);
+      $classes = array_merge($classes, $classes_to_add);
+      $option = implode(' ', array_unique($classes));
+    };
+
+    if (array_key_exists('edit_media', $view->field)) {
+      $add_classes(
+        $view->field['edit_media']->options['alter']['link_class'],
+        ['media-library-item__edit', 'fr-btn', 'fr-btn--secondary', 'fr-btn--sm', 'fr-icon-ball-pen-fill']
+      );
+    }
+    if (array_key_exists('delete_media', $view->field)) {
+      $add_classes(
+        $view->field['delete_media']->options['alter']['link_class'],
+        ['media-library-item__remove', 'fr-btn', 'fr-btn--secondary', 'fr-btn--sm', 'fr-icon-close-line']
+      );
+    }
+  }
+}
diff --git a/js/accordion.js b/js/accordion.js
new file mode 100644
index 0000000000000000000000000000000000000000..e2202d9d5fdd4eabfe4bf3d9bc4701313afa30e5
--- /dev/null
+++ b/js/accordion.js
@@ -0,0 +1,23 @@
+/**
+ * @file
+ * Manage accordion features with DSFR.
+ */
+
+((Drupal, once) => {
+  "use strict";
+
+  Drupal.behaviors.dsfrAccordion = {
+    attach: (context) => {
+
+      // Search all accordion in current context.
+      once("dsfr-accordion", ".fr-accordion", context).forEach((element) => {
+        // Manage accordion button click event.
+        element.querySelector(".fr-accordion__btn").addEventListener("click", event => {
+          // Disable form submission.
+          event.preventDefault();
+        });
+      });
+    }
+  };
+
+})(Drupal, once);
diff --git a/js/dropbutton.js b/js/dropbutton.js
new file mode 100644
index 0000000000000000000000000000000000000000..403cdddd9f66a56ef662a62bd41a05e53f10b17d
--- /dev/null
+++ b/js/dropbutton.js
@@ -0,0 +1,76 @@
+/**
+ * @file
+ * Manage "dropbutton" behaviors.
+ */
+
+((Drupal, once) => {
+  Drupal.behaviors.dsfr4drupalDropbutton = {
+    attach: function (context) {
+      once("dsfr4drupal-dropbutton", ".dropbutton-multiple:has(.dropbutton--dsfr4drupal)", context).forEach(element => {
+        element.querySelector(".dropbutton-toggle").addEventListener("click", () => {
+          this.updatePosition(element);
+
+          window.addEventListener("scroll", () => Drupal.debounce(this.updatePositionIfOpen(element), 100));
+          window.addEventListener("resize", () => Drupal.debounce(this.updatePositionIfOpen(element), 100));
+        });
+      });
+    },
+
+    updatePosition: (element) => {
+      const preferredDir = document.documentElement.dir ?? "ltr";
+      const secondaryAction = element.querySelector(".secondary-action");
+      const dropMenu = secondaryAction.querySelector(".dropbutton__items");
+      const toggleHeight = element.offsetHeight;
+      const dropMenuWidth = dropMenu.offsetWidth;
+      const dropMenuHeight = dropMenu.offsetHeight;
+      const boundingRect = secondaryAction.getBoundingClientRect();
+      const spaceBelow = window.innerHeight - boundingRect.bottom;
+      const spaceLeft = boundingRect.left;
+      const spaceRight = window.innerWidth - boundingRect.right;
+
+      // Calculate the menu position based on available space and the preferred
+      // reading direction.
+      const leftAlignStyles = {
+        left: `${boundingRect.left}px`,
+        right: "auto"
+      };
+      const rightAlignStyles = {
+        left: "auto",
+        right: `${window.innerWidth - boundingRect.right}px`
+      };
+
+      if (preferredDir === "ltr") {
+        if (spaceRight >= dropMenuWidth) {
+          Object.assign(dropMenu.style, leftAlignStyles);
+        }
+        else {
+          Object.assign(dropMenu.style, rightAlignStyles);
+        }
+      }
+      else {
+        if (spaceLeft >= dropMenuWidth) {
+          Object.assign(dropMenu.style, rightAlignStyles);
+        }
+        else {
+          Object.assign(dropMenu.style, leftAlignStyles);
+        }
+      }
+
+      if (spaceBelow >= dropMenuHeight) {
+        dropMenu.style.top = `${boundingRect.bottom}px`;
+      }
+      else {
+        dropMenu.style.top = `${boundingRect.top - toggleHeight - dropMenuHeight}px`
+      }
+
+    },
+
+    updatePositionIfOpen: (element) => {
+      if (element.classList.contains("open")) {
+        this.updatePosition(element);
+      }
+    },
+
+  };
+
+})(Drupal, once);
diff --git a/js/horizontal-tabs.js b/js/horizontal-tabs.js
new file mode 100644
index 0000000000000000000000000000000000000000..030a42db3099275c3f37b9f4d15492178965d8f8
--- /dev/null
+++ b/js/horizontal-tabs.js
@@ -0,0 +1,51 @@
+/**
+ * @file
+ * Manage horizontal tabs features with DSFR.
+ */
+
+((Drupal, once) => {
+  "use strict";
+
+  const CLASS_LIST = "fr-tabs__list";
+  const CLASS_TAB = "fr-tabs__tab";
+  const CLASS_TAB_SELECTED = "fr-tabs__tab--selected";
+  const CLASS_TABS = "fr-tabs";
+
+  Drupal.behaviors.dsfrHorizontalTabs = {
+    attach: (context) => {
+
+      // Search all horizontal tabs in current context.
+      once("dsfr-horizontal-tabs", ".horizontal-tabs", context).forEach((element) => {
+        const wrapper = element.querySelector(`.${CLASS_TABS}`);
+        const list = element.querySelector(`.horizontal-tabs-list.${CLASS_LIST}`);
+        if (!wrapper || !list) {
+          return;
+        }
+
+        wrapper.append(list);
+
+        list.querySelectorAll("li").forEach((item) => {
+          const link = item.querySelector("a");
+          link.classList.add(CLASS_TAB);
+
+          if (item.classList.contains("selected")) {
+            link.classList.add(CLASS_TAB_SELECTED);
+            link.setAttribute("aria-selected", "true");
+          }
+
+          link.addEventListener("click", () => {
+            // Remove selected class on old active link.
+            const selected = list.querySelector(`.${CLASS_TAB_SELECTED}`);
+            selected.classList.remove(CLASS_TAB_SELECTED);
+            selected.removeAttribute("aria-selected");
+
+            // Add class to new element.
+            link.classList.add(CLASS_TAB_SELECTED);
+            link.setAttribute("aria-selected", "true");
+          })
+        });
+      });
+    }
+  };
+
+})(Drupal, once);
diff --git a/js/media-library.view.js b/js/media-library.view.js
new file mode 100644
index 0000000000000000000000000000000000000000..e360d7d1cae0b06211e95f2937ea2bece26ae6bf
--- /dev/null
+++ b/js/media-library.view.js
@@ -0,0 +1,24 @@
+/**
+ * @file
+ * Manage media library view behaviors.
+ */
+
+((Drupal, once) => {
+  "use strict";
+
+
+  Drupal.behaviors.dsfrMediaLibraryView = {
+    attach: (context) => {
+      const selectAll =  once("dsfr-media-library-select-all", ".media-library-select-all", context);
+      if (!selectAll.length) {
+        return;
+      }
+
+      selectAll.forEach(element => {
+        // Move "select all" element after header.
+        context.querySelector("#edit-header").after(element);
+      });
+    },
+  };
+
+})(Drupal, once);
diff --git a/js/paragraphs.widget.js b/js/paragraphs.widget.js
new file mode 100644
index 0000000000000000000000000000000000000000..d9bb779a51db215d6f5be83f132b31a1902f0ed9
--- /dev/null
+++ b/js/paragraphs.widget.js
@@ -0,0 +1,60 @@
+/**
+ * @file
+ * Manage paragraphs widget rendering with DSFR styles.
+ */
+
+(function (Drupal, once) {
+  "use strict";
+
+  const CLASS_LIST = "fr-tabs__list";
+  const CLASS_TAB = "fr-tabs__tab";
+  const CLASS_TAB_SELECTED = "fr-tabs__tab--selected";
+  const CLASS_TABS = "fr-tabs";
+
+  Drupal.behaviors.dsfrParagraphsWidget = {
+    attach: (context) => {
+      const wrappers = once("dsfr-paragraphs-tabs-wrapper", ".paragraphs-tabs-wrapper", context);
+      if (!wrappers) {
+        return;
+      }
+
+      wrappers.forEach(wrapper => {
+        const list = wrapper.querySelector(".paragraphs-tabs");
+        if (
+          !list ||
+          list.classList.contains("paragraphs-tabs-hide")
+        ) {
+          return;
+        }
+
+        const listWrapper = document.createElement("div");
+        listWrapper.classList.add(CLASS_TABS);
+        wrapper.prepend(listWrapper);
+
+        listWrapper.appendChild(list);
+        list.classList.add(CLASS_LIST);
+
+        list.querySelectorAll("li > a").forEach(link => {
+          link.classList.add(CLASS_TAB);
+
+          if (link.classList.contains("is-active")) {
+            link.classList.add(CLASS_TAB_SELECTED);
+            link.setAttribute("aria-selected", "true");
+          }
+
+          link.addEventListener("click", () => {
+            // Remove selected class on old active link.
+            const selected = list.querySelector(`.${CLASS_TAB_SELECTED}`);
+            selected.classList.remove(CLASS_TAB_SELECTED);
+            selected.removeAttribute("aria-selected");
+
+            // Add class to new element.
+            link.classList.add(CLASS_TAB_SELECTED);
+            link.setAttribute("aria-selected", "true");
+          })
+        });
+      });
+    },
+  };
+
+})(Drupal, once);
diff --git a/js/tabledrag.js b/js/tabledrag.js
new file mode 100644
index 0000000000000000000000000000000000000000..5b6ecea6e5dab2f073987e04bcd1b5874eef23cc
--- /dev/null
+++ b/js/tabledrag.js
@@ -0,0 +1,80 @@
+/**
+ * @file
+ * Manage tabbledrag features with DSFR.
+ */
+
+/**
+ * Triggers when weights columns are toggled.
+ *
+ * @event columnschange
+ */
+
+(function ($, Drupal) {
+  "use strict";
+
+  const initColumnsOriginal = Drupal.tableDrag.prototype.initColumns;
+  const addChangedWarningOriginal = Drupal.tableDrag.prototype.row.prototype.addChangedWarning;
+
+  /**
+   * {@inheritdoc}
+   */
+  Drupal.tableDrag.prototype.row.prototype.addChangedWarning = function () {
+    const $table = $(this.table.parentNode);
+
+    // Do not add the changed warning if one is already present.
+    if (!$table.find(".tabledrag-changed-warning").length) {
+      addChangedWarningOriginal.call(this);
+
+      const $tableWrapper = $table.parents(".fr-table");
+
+      // Move changed warning message before DSFR table wrapper.
+      $tableWrapper.parent().prepend($table.find(".tabledrag-changed-warning"));
+    }
+  };
+
+  /**
+   * {@inheritdoc}
+   */
+  Drupal.tableDrag.prototype.initColumns = function () {
+    const $tableWrapper = this.$table.parents(".fr-table");
+    const $toggleWeightWrapper = this.$table.prev();
+
+    // Move toggle weight wrapper before DSFR table wrapper.
+    $tableWrapper.before($toggleWeightWrapper);
+
+    // Call original "initColumns" method.
+    initColumnsOriginal.call(this);
+  };
+
+  $.extend(
+    Drupal.theme,
+    /** @lends Drupal.theme */ {
+      /**
+       * Constructs contents of the toggle weight button.
+       *
+       * @param {boolean} show
+       *   If the table weights are currently displayed.
+       *
+       * @return {string}
+       *  HTML markup for the weight toggle button content.
+       */
+      toggleButtonContent: (show) => {
+        const classes = [
+          "tabledrag-toggle-weight",
+          "fr-icon--sm",
+        ];
+        let text = "";
+        if (show) {
+          classes.push("fr-icon-eye-off-line");
+          text = Drupal.t("Hide row weights");
+        }
+ else {
+          classes.push("fr-icon-eye-line");
+          text = Drupal.t("Show row weights");
+        }
+        return `<span class="${classes.join(" ")}" aria-hidden="true"></span>&nbsp;${text}`;
+      },
+    },
+  );
+
+})(jQuery, Drupal);
diff --git a/src/Dsfr4DrupalPreRender.php b/src/Dsfr4DrupalPreRender.php
new file mode 100644
index 0000000000000000000000000000000000000000..cbea2666e7cc3969a8642cf19f0c864f72921fa5
--- /dev/null
+++ b/src/Dsfr4DrupalPreRender.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Drupal\dsfr4drupal;
+
+use Drupal\Core\Render\Element;
+use Drupal\Core\Security\TrustedCallbackInterface;
+
+/**
+ * Implements trusted prerender callbacks for the "DSFR for Drupal" theme.
+ *
+ * @internal
+ */
+class Dsfr4DrupalPreRender implements TrustedCallbackInterface {
+
+  /**
+   * Prerender callback for Horizontal Tabs element.
+   *
+   * @param array $element
+   *   The render array element.
+   *
+   * @return array
+   *   The new render array element.
+   */
+  public static function horizontalTabs(array $element): array {
+    return self::tabs($element, 'horizontal');
+  }
+
+  /**
+   * Prerender callback for Vertical Tabs element.
+   *
+   * @param array $element
+   *   The render array element.
+   *
+   * @return array
+   *   The new render array element.
+   */
+  public static function verticalTabs(array $element): array {
+    return self::tabs($element, 'vertical');
+  }
+
+  /**
+   * Prerender callback for tabs element.
+   *
+   * @param array $element
+   *   The render array element.
+   *
+   * @return array
+   *   The new render array element.
+   */
+  private static function tabs(array $element, string $orientation): array {
+    $isDetails = isset($element['group']['#type']) && $element['group']['#type'] === 'details';
+    $existingGroups = isset($element['group']['#groups']) && is_array($element['group']['#groups']);
+
+    // If the horizontal/vertical tabs have a details group, add attributes to those
+    // details elements so they are styled as accordion items and have BEM classes.
+    if ($isDetails && $existingGroups) {
+      $groupKeys = Element::children($element['group']['#groups'], TRUE);
+
+      $groupKey = implode('][', $element['#parents']);
+      // Only check siblings against groups because we are only looking for
+      // group elements.
+      if (in_array($groupKey, $groupKeys)) {
+        $childrenKeys = Element::children($element['group']['#groups'][$groupKey], TRUE);
+
+        foreach ($childrenKeys as $childKey) {
+          $type = $element['group']['#groups'][$groupKey][$childKey]['#type'] ?? NULL;
+          if ($type === 'details') {
+            $element['group']['#groups'][$groupKey][$childKey]['#' . $orientation . '_tab_item'] = TRUE;
+          }
+        }
+      }
+    }
+
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function trustedCallbacks(): array {
+    return [
+      'horizontalTabs',
+      'verticalTabs',
+    ];
+  }
+
+}
diff --git a/templates/base/base-page-404.html.twig b/templates/base/base-page-404.html.twig
index a0ab027b3f55dda57affc3ccd526deb9b2cde9b7..17535f67e9be4d545c6c96d5e7db3052f048ee9c 100644
--- a/templates/base/base-page-404.html.twig
+++ b/templates/base/base-page-404.html.twig
@@ -1,6 +1,6 @@
 {% set links = links|default([
   {
-    'url': url('<front>')|render|render,
+    'url': 'internal:/<front>',
     'title': 'Homepage'|t,
   },
 ]) %}
diff --git a/templates/base/base-page-500.html.twig b/templates/base/base-page-500.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..4180e5b53d50339d19d106e837d36c4f7816e605
--- /dev/null
+++ b/templates/base/base-page-500.html.twig
@@ -0,0 +1,38 @@
+{% set links = links|default([
+  {
+    'url': 'internal:/contact',
+    'title': 'Contact us'|t,
+  },
+]) %}
+{% set subtitle = subtitle|default('Error 500'|t) %}
+{% set title = title|default('Unexpected error'|t) %}
+{% set text_lead = text_lead|default('Sorry, there is an issue with the service, we are working to resolve it as quickly as possible.'|t) %}
+{% set text = text|default('Please try refreshing the page or try again later.'|t) %}
+
+<div class="fr-py-0 fr-col-12 fr-col-md-6">
+  <h1>{{ title }}</h1>
+  <p class="fr-text--sm fr-mb-3w">{{ subtitle }}</p>
+  <p class="fr-text--lead fr-mb-3w">{{ text_lead }}</p>
+  <p class="fr-text--sm fr-mb-5w">{{ text }}</p>
+  {% if links %}
+    <ul class="fr-btns-group fr-btns-group--inline-md">
+      {% for link in links %}
+        <li>
+          {{ link(link.title, link.url, {'class': 'fr-btn'}) }}
+        </li>
+      {% endfor %}
+    </ul>
+    {{ attach_library('dsfr4drupal/component.button') }}
+  {% endif %}
+</div>
+<div class="fr-col-12 fr-col-md-3 fr-col-offset-md-1 fr-px-6w fr-px-md-0 fr-py-0">
+  <svg xmlns="http://www.w3.org/2000/svg" class="fr-responsive-img fr-artwork" aria-hidden="true" width="160" height="200" viewBox="0 0 160 200">
+    <use class="fr-artwork-motif" href="{{ dsfr_path }}artwork/background/ovoid.svg#artwork-motif"></use>
+    <use class="fr-artwork-background" href="{{ dsfr_path }}artwork/background/ovoid.svg#artwork-background"></use>
+    <g transform="translate(40, 60)">
+      <use class="fr-artwork-decorative" href="{{ dsfr_path }}artwork/pictograms/system/technical-error.svg#artwork-decorative"></use>
+      <use class="fr-artwork-minor" href="{{ dsfr_path }}artwork/pictograms/system/technical-error.svg#artwork-minor"></use>
+      <use class="fr-artwork-major" href="{{ dsfr_path }}artwork/pictograms/system/technical-error.svg#artwork-major"></use>
+    </g>
+  </svg>
+</div>
diff --git a/templates/block/block--local-actions-block.html.twig b/templates/block/block--local-actions-block.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..04c8bbe0719525e4888756fbfc024c2bc0d8dd47
--- /dev/null
+++ b/templates/block/block--local-actions-block.html.twig
@@ -0,0 +1,10 @@
+{% extends 'block.html.twig' %}
+
+{% block content %}
+  {% if content %}
+    <ul class="local-actions fr-mb-4v">
+      {{ content }}
+    </ul>
+    {{ attach_library('dsfr4drupal/local-actions') }}
+  {% endif %}
+{% endblock %}
diff --git a/templates/block/block.html.twig b/templates/block/block.html.twig
index 1ac5205a55bc73f94855306a5c9cef2f88a7d46a..221b492ba84074805c2dbddf4e950437736f20e9 100644
--- a/templates/block/block.html.twig
+++ b/templates/block/block.html.twig
@@ -1,16 +1,16 @@
 {% if attributes is not empty %}
-<div{{ attributes }}>
+  <div{{ attributes }}>
 {% endif %}
 
-  {{ title_prefix }}
-  {% if label %}
-    <h2{{ title_attributes }}>{{ label }}</h2>
-  {% endif %}
-  {{ title_suffix }}
-  {% block content %}
-    {{ content }}
-  {% endblock %}
+{{ title_prefix }}
+{% if label %}
+  <h2{{ title_attributes }}>{{ label }}</h2>
+{% endif %}
+{{ title_suffix }}
+{% block content %}
+  {{ content }}
+{% endblock %}
 
 {% if attributes is not empty %}
-</div>
+  </div>
 {% endif %}
diff --git a/templates/field/field--media--thumbnail.html.twig b/templates/field/field--media--thumbnail.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..df508d39da13111caff8d7ee0f6db67b82e2c573
--- /dev/null
+++ b/templates/field/field--media--thumbnail.html.twig
@@ -0,0 +1,2 @@
+{# Include default "field" template without alter it to benefits of preprocessing. #}
+{% include 'field.html.twig' %}
diff --git a/templates/field/field--node--created.html.twig b/templates/field/field--node--created.html.twig
index 82077662f79056d5bb50588dd967fe4522941f86..2e5679c52b52c55a24eb9d1bf6e75669622635b2 100644
--- a/templates/field/field--node--created.html.twig
+++ b/templates/field/field--node--created.html.twig
@@ -1,5 +1,5 @@
 {% if not is_inline %}
-  {% include "field.html.twig" %}
+  {% include 'field.html.twig' %}
 {% else %}
   {%- if attributes is not empty -%}
     <span{{ attributes }}>
diff --git a/templates/field/field--node--title.html.twig b/templates/field/field--node--title.html.twig
index 82077662f79056d5bb50588dd967fe4522941f86..2e5679c52b52c55a24eb9d1bf6e75669622635b2 100644
--- a/templates/field/field--node--title.html.twig
+++ b/templates/field/field--node--title.html.twig
@@ -1,5 +1,5 @@
 {% if not is_inline %}
-  {% include "field.html.twig" %}
+  {% include 'field.html.twig' %}
 {% else %}
   {%- if attributes is not empty -%}
     <span{{ attributes }}>
diff --git a/templates/form/details--horizontal-tabs.html.twig b/templates/form/details--horizontal-tabs.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..c5a902d161069050858a2c218f19f9d1dfdaff04
--- /dev/null
+++ b/templates/form/details--horizontal-tabs.html.twig
@@ -0,0 +1,2 @@
+{# Specific "details" templating for vertical tabs. Do not use DSFR accordion. #}
+{% include '@system/templates/details.html.twig' %}
diff --git a/templates/form/details--vertical-tabs.html.twig b/templates/form/details--vertical-tabs.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..c5a902d161069050858a2c218f19f9d1dfdaff04
--- /dev/null
+++ b/templates/form/details--vertical-tabs.html.twig
@@ -0,0 +1,2 @@
+{# Specific "details" templating for vertical tabs. Do not use DSFR accordion. #}
+{% include '@system/templates/details.html.twig' %}
diff --git a/templates/form/field-multiple-value-form.html.twig b/templates/form/field-multiple-value-form.html.twig
index 46964d3879296e7426d6f1c3bb7da79fac8c9ca0..6654c13d0eced13be358d1cf4707bb81151541c7 100644
--- a/templates/form/field-multiple-value-form.html.twig
+++ b/templates/form/field-multiple-value-form.html.twig
@@ -1,7 +1,7 @@
 {% if multiple %}
   {% set classes = [
     'js-form-item',
-    'form-item'
+    'form-item',
   ] %}
 
   <div{{ attributes.addClass(classes) }}>
diff --git a/templates/form/fieldset.html.twig b/templates/form/fieldset.html.twig
index 2143e1571708e75a85d6505cca00654ec8c6b863..1584727d3cf86ee36fd25367b09cd168fa8ef227 100644
--- a/templates/form/fieldset.html.twig
+++ b/templates/form/fieldset.html.twig
@@ -11,10 +11,17 @@
   required ? 'form-required',
 ] %}
 <div class="fr-form-group">
-  <fieldset{{ attributes.setAttribute('role', 'group').addClass(classes) }}>
+  <fieldset{{ attributes.addClass(classes) }}>
     <legend{{ legend.attributes.addClass(legend_classes) }}>{{ legend.title }}
       {% if description.content %}
-        <span class="fr-hint-text">{{ description.content }}</span>
+        {% if description_tooltip %}
+          {{ include('dsfr4drupal:tooltip', {
+            'title': tooltip_title|default('Help text'|t),
+            'tooltip': description.content,
+          }, with_context=false) }}
+        {% else %}
+          <span class="fr-hint-text">{{ description.content }}</span>
+        {% endif %}
       {% endif %}
     </legend>
     <div class="fr-fieldset__content">
diff --git a/templates/form/form-element--checkbox.html.twig b/templates/form/form-element--checkbox.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..ebc4eb61be8ed3b7462b9a97d51ecfcd13bbc1f4
--- /dev/null
+++ b/templates/form/form-element--checkbox.html.twig
@@ -0,0 +1,26 @@
+{% set classes = [
+  'js-form-item',
+  'form-item',
+  'js-form-type-' ~ type|clean_class,
+  'form-item-' ~ name|clean_class,
+  'js-form-item-' ~ name|clean_class,
+  title_display not in ['after', 'before'] ? 'form-item--no-label',
+  disabled == 'disabled' ? 'form-disabled',
+] %}
+
+<div{{ attributes.addClass(classes) }}>
+  {% if prefix is not empty %}
+    <span class="field-prefix">{{ prefix }}</span>
+  {% endif %}
+
+  {{ children }}
+  {% if suffix is not empty %}
+    <span class="field-suffix">{{ suffix }}</span>
+  {% endif %}
+  {# The label is always display after to display checkbox. #}
+  {{ label }}
+  {# In case of we need to display errors in form element. #}
+  {% if show_error %}
+    {% include '@dsfr4drupal/templates/form/form-element.error.html.twig' %}
+  {% endif %}
+</div>
diff --git a/templates/form/form-element--radio.html.twig b/templates/form/form-element--radio.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..74456c61ce3f71958afe3e7c2740f7ae4a06656e
--- /dev/null
+++ b/templates/form/form-element--radio.html.twig
@@ -0,0 +1 @@
+{% include 'form-element--checkbox.html.twig' %}
diff --git a/templates/form/form-element-label.html.twig b/templates/form/form-element-label.html.twig
index a8138ad01bd13847008a74b8ce63a85fd2c860f9..0686a5cc5527bebd034b609e469680f7e7dd5254 100644
--- a/templates/form/form-element-label.html.twig
+++ b/templates/form/form-element-label.html.twig
@@ -1,4 +1,3 @@
-{% set tooltip_title = tooltip_title|default('Help text'|t) %}
 {% set classes = [
   title_display == 'after' ? 'option',
   title_display == 'invisible' ? 'visually-hidden',
@@ -11,7 +10,7 @@
     {% if description.content %}
       {% if description_tooltip %}
         {{ include('dsfr4drupal:tooltip', {
-          'title': tooltip_title,
+          'title': tooltip_title|default('Help text'|t),
           'tooltip': description.content,
         }, with_context=false) }}
       {% else %}
diff --git a/templates/form/form-element.html.twig b/templates/form/form-element.html.twig
index adee85c6d1edfe27c80a459f67a1eabb2b134cc3..c436fac980d2c123085b0762137682485ce3e50f 100644
--- a/templates/form/form-element.html.twig
+++ b/templates/form/form-element.html.twig
@@ -4,7 +4,7 @@
   'js-form-type-' ~ type|clean_class,
   'form-item-' ~ name|clean_class,
   'js-form-item-' ~ name|clean_class,
-  title_display not in ['after', 'before'] ? 'form-no-label',
+  title_display not in ['after', 'before'] ? 'form-item--no-label',
   disabled == 'disabled' ? 'form-disabled',
 ] %}
 
diff --git a/templates/form/horizontal-tabs.html.twig b/templates/form/horizontal-tabs.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..7af90c489620d55ed0aad64e34953c14fba94147
--- /dev/null
+++ b/templates/form/horizontal-tabs.html.twig
@@ -0,0 +1,6 @@
+<div data-horizontal-tabs class="horizontal-tabs">
+  <div class="fr-tabs"></div>
+  {# Cannot add list into "fr-tabs", otherwise it's not populated. #}
+  <ul data-horizontal-tabs-list class="horizontal-tabs-list fr-tabs__list"></ul>
+  <div data-horizontal-tabs-panes{{ attributes }}>{{ children }}</div>
+</div>
diff --git a/templates/form/input--password.html.twig b/templates/form/input--password.html.twig
index 0fb51b568d25b48db30c3b2d7b46dac12dc53929..93718b48752ed651a6e827d501c439d72be24661 100644
--- a/templates/form/input--password.html.twig
+++ b/templates/form/input--password.html.twig
@@ -13,7 +13,6 @@
     <label class="fr--password__checkbox fr-label" for="{{ show_password_id }}">
       {{ show_password_label }}
     </label>
-    </div>
   </div>
 {% endif %}
 {{ attach_library('dsfr4drupal/component.password') }}
diff --git a/templates/form/links--dropbutton.html.twig b/templates/form/links--dropbutton.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..0b94975dc92e13ac725182aeb71c6234b543caa6
--- /dev/null
+++ b/templates/form/links--dropbutton.html.twig
@@ -0,0 +1,63 @@
+{#
+/**
+ * @file
+ * Theme override for a set of links.
+ *
+ * Available variables:
+ * - attributes: Attributes for the UL containing the list of links.
+ * - links: Links to be output.
+ *   Each link will have the following elements:
+ *   - link: (optional) A render array that returns a link. See
+ *     template_preprocess_links() for details how it is generated.
+ *   - text: The link text.
+ *   - attributes: HTML attributes for the list item element.
+ *   - text_attributes: (optional) HTML attributes for the span element if no
+ *     'url' was supplied.
+ * - heading: (optional) A heading to precede the links.
+ *   - text: The heading text.
+ *   - level: The heading level (e.g. 'h2', 'h3').
+ *   - attributes: (optional) A keyed list of attributes for the heading.
+ *   If the heading is a string, it will be used as the text of the heading and
+ *   the level will default to 'h2'.
+ *
+ *   Headings should be used on navigation menus and any list of links that
+ *   consistently appears on multiple pages. To make the heading invisible use
+ *   the 'visually-hidden' CSS class. Do not use 'display:none', which
+ *   removes it from screen readers and assistive technology. Headings allow
+ *   screen reader and keyboard only users to navigate to or skip the links.
+ *   See http://juicystudio.com/article/screen-readers-display-none.php and
+ *   http://www.w3.org/TR/WCAG-TECHS/H42.html for more information.
+ *
+ * @see template_preprocess_links()
+ */
+#}
+{% if links -%}
+  {%- if heading -%}
+    {%- if heading.level -%}
+      <{{ heading.level }}{{ heading.attributes }}>{{ heading.text }}</{{ heading.level }}>
+    {%- else -%}
+      <h2{{ heading.attributes }}>{{ heading.text }}</h2>
+    {%- endif -%}
+  {%- endif -%}
+  <ul{{ attributes.addClass('dropbutton--dsfr4drupal') }}>
+    {%- for item in links -%}
+      {% if loop.index == 2 %}
+      <li class="dropbutton__item">
+        <ul class="dropbutton__items">
+      {% endif %}
+      <li{{ item.attributes.addClass('dropbutton__item') }}>
+        {%- if item.link -%}
+          {{ item.link }}
+        {%- elseif item.text_attributes -%}
+          <span{{ item.text_attributes }}>{{ item.text }}</span>
+        {%- else -%}
+          {{ item.text }}
+        {%- endif -%}
+      </li>
+      {% if loop.length > 1 and loop.last %}
+        </ul>
+      </li>
+      {% endif %}
+    {%- endfor -%}
+  </ul>
+{%- endif %}
diff --git a/templates/layout/page.html.twig b/templates/layout/page.html.twig
index 3d79ad9be714e8e07ec94b3a0d07c37dabca81e6..0433eeaae686bee59e7a63b02751268ddbab2345 100644
--- a/templates/layout/page.html.twig
+++ b/templates/layout/page.html.twig
@@ -11,7 +11,7 @@
 <div class="{{ container_class|default('fr-container') }}">
   {{ page.breadcrumb }}
 </div>
-<main id="main" role="main" class="{{ container_class|default('fr-container') }}" tabindex="-1">
+<main id="main" role="main" tabindex="-1">
   <div class="{{ container_class|default('fr-container') }}">
     {{ page.content }}
   </div>
diff --git a/templates/media/media--media-library.html.twig b/templates/media/media--media-library.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..e51ace768486f31a7e5c78f4761444994a0b9cf1
--- /dev/null
+++ b/templates/media/media--media-library.html.twig
@@ -0,0 +1,20 @@
+{% if attributes is not empty %}
+  <div{{ attributes }}>
+{% endif %}
+
+<div{{ preview_attributes.addClass('js-media-library-item-preview') }}>
+  {{ include('dsfr4drupal:card', {
+    'image': content.thumbnail ? content.thumbnail : '',
+    'title': name,
+    'title_attributes': metadata_attributes,
+    'description': content|without('thumbnail'),
+    'no_arrow': true,
+    'detail': not status ? 'unpublished'|t : '',
+    'detail_icon': 'fr-icon-warning-fill',
+    'variant': 'sm',
+  }, with_context=false) }}
+</div>
+
+{% if attributes is not empty %}
+  </div>
+{% endif %}
diff --git a/templates/media/media.html.twig b/templates/media/media.html.twig
index 427b3f58ac1f7c17e932389ff82b1c512c9722ff..349904d1145183f70f9451d963037fa56f13345f 100644
--- a/templates/media/media.html.twig
+++ b/templates/media/media.html.twig
@@ -1,10 +1,10 @@
 {% if attributes is not empty %}
-<div{{ attributes }}>
+  <div{{ attributes }}>
 {% endif %}
 
- {{ title_suffix.contextual_links }}
- {{ content }}
+{{ title_suffix.contextual_links }}
+{{ content }}
 
 {% if attributes is not empty %}
-</div>
+  </div>
 {% endif %}
diff --git a/templates/menu/menu-local-tasks.html.twig b/templates/menu/menu-local-tasks.html.twig
index 90c4ed730ba4e8992dfead1825da3c3d135558dd..22097ad7c26e67a2514b59d50ed8dfbe38550b77 100644
--- a/templates/menu/menu-local-tasks.html.twig
+++ b/templates/menu/menu-local-tasks.html.twig
@@ -1,3 +1,5 @@
+{% set attributes = attributes.addClass('fr-mb-4v') %}
+
 {% if primary %}
   <h2 class="visually-hidden">{{ 'Primary tabs'|t }}</h2>
   {{ include('dsfr4drupal:tabs', {'tabs': primary}) }}
diff --git a/templates/node/node.html.twig b/templates/node/node.html.twig
index b23d6da932ae43e4fbe607095bd1463e367ce319..f46d52da5ba3f41998778792191a0c87e912e822 100644
--- a/templates/node/node.html.twig
+++ b/templates/node/node.html.twig
@@ -1,17 +1,17 @@
 {% if attributes is not empty %}
-<div{{ attributes }}>
+  <div{{ attributes }}>
 {% endif %}
 
-  {% if content_attributes is not empty %}
+{% if content_attributes is not empty %}
   <div{{ content_attributes }}>
-  {% endif %}
+{% endif %}
 
-    {{ content }}
+{{ content }}
 
-  {% if content_attributes is not empty %}
+{% if content_attributes is not empty %}
   </div>
-  {% endif %}
+{% endif %}
 
 {% if attributes is not empty %}
-</div>
+  </div>
 {% endif %}
diff --git a/templates/region/region.html.twig b/templates/region/region.html.twig
index ce195584fefa48b188831488049f9c2c11c9a816..80f6ad31dc0b7b6938928c45fa5fc5a75e3f278a 100644
--- a/templates/region/region.html.twig
+++ b/templates/region/region.html.twig
@@ -1,11 +1,11 @@
 {% if content %}
   {% if attributes is not empty %}
-  <div{{ attributes }}>
+    <div{{ attributes }}>
   {% endif %}
 
-    {{ content }}
+  {{ content }}
 
   {% if attributes is not empty %}
-  </div>
+    </div>
   {% endif %}
 {% endif %}
diff --git a/templates/system/admin-block-content.html.twig b/templates/system/admin-block-content.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..c3f443387d5b91b0dd0c89ea31f145c97cee9fcd
--- /dev/null
+++ b/templates/system/admin-block-content.html.twig
@@ -0,0 +1,14 @@
+{% set classes = [
+  'list-group',
+  compact ? 'compact',
+] %}
+{% if content %}
+  <dl{{ attributes.addClass(classes) }}>
+    {% for item in content %}
+      <dt class="list-group__link fr-mt-2v">{{ item.link }}</dt>
+      {% if item.description %}
+        <dd class="list-group__description">{{ item.description }}</dd>
+      {% endif %}
+    {% endfor %}
+  </dl>
+{% endif %}
diff --git a/templates/system/admin-page.html.twig b/templates/system/admin-page.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..b82b265a438d7d3d711573acea4ee1f1e3f500fa
--- /dev/null
+++ b/templates/system/admin-page.html.twig
@@ -0,0 +1,13 @@
+<div class="clearfix">
+  {{ system_compact_link|add_class('fr-text--sm') }}
+
+  <div class="fr-grid-row fr-grid-row--gutters">
+    {% for container in containers %}
+      <div class="fr-col-12 fr-col-md-6">
+        {% for block in container.blocks %}
+          {{ block }}
+        {% endfor %}
+      </div>
+    {% endfor %}
+  </div>
+</div>
diff --git a/templates/system/details--dialog-off-canvas.html.twig b/templates/system/details--dialog-off-canvas.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..9bbbd4924c7d922e2bd5e52ba9d1a48aee263d03
--- /dev/null
+++ b/templates/system/details--dialog-off-canvas.html.twig
@@ -0,0 +1,2 @@
+{# Styles are reset with Drupal dialog off canvas. Use default details rendering instead of DSFR accordion component. #}
+{% include '@system/templates/details.html.twig' %}
diff --git a/templates/system/details.html.twig b/templates/system/details.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..f9dce319b46a7591e2f13382d9201a3bedee0f0a
--- /dev/null
+++ b/templates/system/details.html.twig
@@ -0,0 +1,17 @@
+{% set content %}
+  {% if errors %}
+    <div>
+      {{ errors }}
+    </div>
+  {% endif %}
+
+  {{ description }}
+  {{ children }}
+  {{ value }}
+{% endset %}
+
+{{ include('dsfr4drupal:accordion', {
+  'button_attributes': create_attribute({'class': required ? ['js-form-required', 'form-required'] : ''}),
+  'expanded': not element['#attributes']['open'] is empty,
+  'title_tag': 'div',
+}) }}
diff --git a/templates/system/status-report-general-info.html.twig b/templates/system/status-report-general-info.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..c1554f7bf23ed601b2587c37d2d1cc2756a55a36
--- /dev/null
+++ b/templates/system/status-report-general-info.html.twig
@@ -0,0 +1,61 @@
+<h2>{{ 'General System Information'|t }}</h2>
+<div class="fr-grid-row fr-grid-row--gutters fr-mb-4v">
+  <div class="fr-col-12 fr-col-md-6">
+    <div class="system-status-general-info__item">
+      <h3 class="fr-h5 system-status-general-info__item-title">{{ 'Drupal Version'|t }}</h3>
+      {{ drupal.value }}
+      {% if drupal.description %}
+        {{ drupal.description }}
+      {% endif %}
+    </div>
+  </div>
+  <div class="fr-col-12 fr-col-md-6">
+    <div class="system-status-general-info__item">
+      <h3 class="fr-h5 system-status-general-info__item-title">{{ 'Web Server'|t }}</h3>
+      {{ webserver.value }}
+      {% if webserver.description %}
+        {{ webserver.description }}
+      {% endif %}
+    </div>
+  </div>
+  <div class="fr-col-12 fr-col-md-6">
+    <div class="system-status-general-info__item">
+      <h3 class="fr-h5 system-status-general-info__item-title">{{ 'PHP'|t }}</h3>
+      <h4 class="fr-h6">{{ 'Version'|t }}</h4> {{ php.value }}
+      {% if php.description %}
+        {{ php.description }}
+      {% endif %}
+
+      <h4 class="fr-h6">{{ 'Memory limit'|t }}</h4>{{ php_memory_limit.value }}
+      {% if php_memory_limit.description %}
+        {{ php_memory_limit.description }}
+      {% endif %}
+    </div>
+  </div>
+  <div class="fr-col-12 fr-col-md-6">
+    <div class="system-status-general-info__item">
+      <h3 class="fr-h5 system-status-general-info__item-title">{{ 'Database'|t }}</h3>
+      <h4 class="fr-h6">{{ 'Version'|t }}</h4>{{ database_system_version.value }}
+      {% if database_system_version.description %}
+        {{ database_system_version.description }}
+      {% endif %}
+
+      <h4 class="fr-h6">{{ 'System'|t }}</h4>{{ database_system.value }}
+      {% if database_system.description %}
+        {{ database_system.description }}
+      {% endif %}
+    </div>
+  </div>
+  <div class="fr-col-12">
+    <div class="system-status-general-info__item">
+      <h3 class="fr-h5 system-status-general-info__item-title">{{ 'Last Cron Run'|t }}</h3>
+      {{ cron.value }}
+      {% if cron.run_cron %}
+        {{ cron.run_cron }}
+      {% endif %}
+      {% if cron.description %}
+        {{ cron.description }}
+      {% endif %}
+    </div>
+  </div>
+</div>
diff --git a/templates/views/views-view--media-library.html.twig b/templates/views/views-view--media-library.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..d31d2c279da24761ce8b18a162170d9a801e38a7
--- /dev/null
+++ b/templates/views/views-view--media-library.html.twig
@@ -0,0 +1,43 @@
+{% set classes = [
+  dom_id ? 'js-view-dom-id-' ~ dom_id,
+] %}
+
+<div{{ attributes.addClass(classes) }}>
+  {{ title_prefix }}
+  {{ title }}
+  {{ title_suffix }}
+
+  {% if header %}
+    <header class="fr-tabs">
+      <ul class="fr-tabs__list" role="tablist">
+        {% for link in header %}
+          <li role="presentation">
+            {{ link }}
+          </li>
+        {% endfor %}
+      </ul>
+    </header>
+    {{ attach_library('dsfr4drupal/component.tab') }}
+  {% endif %}
+
+  {{ exposed }}
+  {{ attachment_before }}
+
+  {% if rows -%}
+    {{ rows }}
+  {% elseif empty -%}
+    {{ empty }}
+  {% endif %}
+  {{ pager }}
+
+  {{ attachment_after }}
+  {{ more }}
+
+  {% if footer %}
+    <footer>
+      {{ footer }}
+    </footer>
+  {% endif %}
+
+  {{ feed_icons }}
+</div>
diff --git a/templates/views/views-view-unformatted--media-library.html.twig b/templates/views/views-view-unformatted--media-library.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..61fa26d4877eea14c9f5e9d2880ecc60fe1b6530
--- /dev/null
+++ b/templates/views/views-view-unformatted--media-library.html.twig
@@ -0,0 +1,15 @@
+{% set row_classes = [
+  default_row_class ? 'views-row',
+] %}
+
+{% if title %}
+  <h3>{{ title }}</h3>
+{% endif %}
+
+<div class="fr-grid-row fr-grid-row--gutters">
+  {% for row in rows %}
+    <div{{ row.attributes.addClass(row_classes).addClass(['fr-col-12', 'fr-col-sm-6', 'fr-col-md-4', 'fr-col-lg-3']) }}>
+      {{- row.content -}}
+    </div>
+  {% endfor %}
+</div>
diff --git a/translations/fr.po b/translations/fr.po
index e3ef9201881f2d9e236e711f1bacc178591866c1..792f25db923b0b69a3a5617e5dff455d83ed400e 100644
--- a/translations/fr.po
+++ b/translations/fr.po
@@ -268,6 +268,9 @@ msgstr "@code - @label"
 msgid "Page not found"
 msgstr "Page non trouvée"
 
+msgid "Unexpected error"
+msgstr "Erreur inattendue"
+
 msgid "Show"
 msgstr "Afficher"
 
@@ -277,9 +280,15 @@ msgstr "Afficher le mot de passe"
 msgid "Error 404"
 msgstr "Erreur 404"
 
+msgid "Error 500"
+msgstr "Erreur 500"
+
 msgid "The page you are looking for cannot be found. We apologize for the inconvenience caused."
 msgstr "La page que vous cherchez est introuvable. Excusez-nous pour la gène occasionnée."
 
+msgid "Sorry, there is an issue with the service, we are working to resolve it as quickly as possible."
+msgstr "Désolé, le service rencontre un problème, nous travaillons pour le résoudre le plus rapidement possible."
+
 msgid ""
 "If you typed the web address into the browser, verify that it is correct. "
 "The page may no longer be available.<br />In this case, to continue your visit you can consult our home page, "
@@ -289,6 +298,12 @@ msgstr ""
 "La page n’est peut-être plus disponible.<br />Dans ce cas, pour continuer votre visite vous pouvez consulter notre page d’accueil, "
 "ou effectuer une recherche avec notre moteur de recherche en haut de page.<br />Sinon contactez-nous pour que l’on puisse vous rediriger vers la bonne information."
 
+msgid "Please try refreshing the page or try again later."
+msgstr "Essayez de rafraîchir la page ou bien ressayez plus tard."
+
+msgid "Contact us"
+msgstr "Contactez-nous"
+
 msgid "Copy to clipboard"
 msgstr "Copier dans le presse papier"
 
diff --git a/vendor/autoload.php b/vendor/autoload.php
new file mode 100644
index 0000000000000000000000000000000000000000..8d66326355f80a560cb2e1bac318a69949cc0c73
--- /dev/null
+++ b/vendor/autoload.php
@@ -0,0 +1,25 @@
+<?php
+
+// autoload.php @generated by Composer
+
+if (PHP_VERSION_ID < 50600) {
+    if (!headers_sent()) {
+        header('HTTP/1.1 500 Internal Server Error');
+    }
+    $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
+    if (!ini_get('display_errors')) {
+        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+            fwrite(STDERR, $err);
+        } elseif (!headers_sent()) {
+            echo $err;
+        }
+    }
+    trigger_error(
+        $err,
+        E_USER_ERROR
+    );
+}
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInite2b57849f956d4c67658848cdb027ed8::getLoader();
diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php
new file mode 100644
index 0000000000000000000000000000000000000000..7824d8f7eafe8db890975f0fa2dfab31435900da
--- /dev/null
+++ b/vendor/composer/ClassLoader.php
@@ -0,0 +1,579 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ *     $loader = new \Composer\Autoload\ClassLoader();
+ *
+ *     // register classes with namespaces
+ *     $loader->add('Symfony\Component', __DIR__.'/component');
+ *     $loader->add('Symfony',           __DIR__.'/framework');
+ *
+ *     // activate the autoloader
+ *     $loader->register();
+ *
+ *     // to enable searching the include path (eg. for PEAR packages)
+ *     $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @see    https://www.php-fig.org/psr/psr-0/
+ * @see    https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+    /** @var \Closure(string):void */
+    private static $includeFile;
+
+    /** @var string|null */
+    private $vendorDir;
+
+    // PSR-4
+    /**
+     * @var array<string, array<string, int>>
+     */
+    private $prefixLengthsPsr4 = array();
+    /**
+     * @var array<string, list<string>>
+     */
+    private $prefixDirsPsr4 = array();
+    /**
+     * @var list<string>
+     */
+    private $fallbackDirsPsr4 = array();
+
+    // PSR-0
+    /**
+     * List of PSR-0 prefixes
+     *
+     * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
+     *
+     * @var array<string, array<string, list<string>>>
+     */
+    private $prefixesPsr0 = array();
+    /**
+     * @var list<string>
+     */
+    private $fallbackDirsPsr0 = array();
+
+    /** @var bool */
+    private $useIncludePath = false;
+
+    /**
+     * @var array<string, string>
+     */
+    private $classMap = array();
+
+    /** @var bool */
+    private $classMapAuthoritative = false;
+
+    /**
+     * @var array<string, bool>
+     */
+    private $missingClasses = array();
+
+    /** @var string|null */
+    private $apcuPrefix;
+
+    /**
+     * @var array<string, self>
+     */
+    private static $registeredLoaders = array();
+
+    /**
+     * @param string|null $vendorDir
+     */
+    public function __construct($vendorDir = null)
+    {
+        $this->vendorDir = $vendorDir;
+        self::initializeIncludeClosure();
+    }
+
+    /**
+     * @return array<string, list<string>>
+     */
+    public function getPrefixes()
+    {
+        if (!empty($this->prefixesPsr0)) {
+            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+        }
+
+        return array();
+    }
+
+    /**
+     * @return array<string, list<string>>
+     */
+    public function getPrefixesPsr4()
+    {
+        return $this->prefixDirsPsr4;
+    }
+
+    /**
+     * @return list<string>
+     */
+    public function getFallbackDirs()
+    {
+        return $this->fallbackDirsPsr0;
+    }
+
+    /**
+     * @return list<string>
+     */
+    public function getFallbackDirsPsr4()
+    {
+        return $this->fallbackDirsPsr4;
+    }
+
+    /**
+     * @return array<string, string> Array of classname => path
+     */
+    public function getClassMap()
+    {
+        return $this->classMap;
+    }
+
+    /**
+     * @param array<string, string> $classMap Class to filename map
+     *
+     * @return void
+     */
+    public function addClassMap(array $classMap)
+    {
+        if ($this->classMap) {
+            $this->classMap = array_merge($this->classMap, $classMap);
+        } else {
+            $this->classMap = $classMap;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix, either
+     * appending or prepending to the ones previously set for this prefix.
+     *
+     * @param string              $prefix  The prefix
+     * @param list<string>|string $paths   The PSR-0 root directories
+     * @param bool                $prepend Whether to prepend the directories
+     *
+     * @return void
+     */
+    public function add($prefix, $paths, $prepend = false)
+    {
+        $paths = (array) $paths;
+        if (!$prefix) {
+            if ($prepend) {
+                $this->fallbackDirsPsr0 = array_merge(
+                    $paths,
+                    $this->fallbackDirsPsr0
+                );
+            } else {
+                $this->fallbackDirsPsr0 = array_merge(
+                    $this->fallbackDirsPsr0,
+                    $paths
+                );
+            }
+
+            return;
+        }
+
+        $first = $prefix[0];
+        if (!isset($this->prefixesPsr0[$first][$prefix])) {
+            $this->prefixesPsr0[$first][$prefix] = $paths;
+
+            return;
+        }
+        if ($prepend) {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                $paths,
+                $this->prefixesPsr0[$first][$prefix]
+            );
+        } else {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                $this->prefixesPsr0[$first][$prefix],
+                $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace, either
+     * appending or prepending to the ones previously set for this namespace.
+     *
+     * @param string              $prefix  The prefix/namespace, with trailing '\\'
+     * @param list<string>|string $paths   The PSR-4 base directories
+     * @param bool                $prepend Whether to prepend the directories
+     *
+     * @throws \InvalidArgumentException
+     *
+     * @return void
+     */
+    public function addPsr4($prefix, $paths, $prepend = false)
+    {
+        $paths = (array) $paths;
+        if (!$prefix) {
+            // Register directories for the root namespace.
+            if ($prepend) {
+                $this->fallbackDirsPsr4 = array_merge(
+                    $paths,
+                    $this->fallbackDirsPsr4
+                );
+            } else {
+                $this->fallbackDirsPsr4 = array_merge(
+                    $this->fallbackDirsPsr4,
+                    $paths
+                );
+            }
+        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+            // Register directories for a new namespace.
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = $paths;
+        } elseif ($prepend) {
+            // Prepend directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                $paths,
+                $this->prefixDirsPsr4[$prefix]
+            );
+        } else {
+            // Append directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                $this->prefixDirsPsr4[$prefix],
+                $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix,
+     * replacing any others previously set for this prefix.
+     *
+     * @param string              $prefix The prefix
+     * @param list<string>|string $paths  The PSR-0 base directories
+     *
+     * @return void
+     */
+    public function set($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr0 = (array) $paths;
+        } else {
+            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace,
+     * replacing any others previously set for this namespace.
+     *
+     * @param string              $prefix The prefix/namespace, with trailing '\\'
+     * @param list<string>|string $paths  The PSR-4 base directories
+     *
+     * @throws \InvalidArgumentException
+     *
+     * @return void
+     */
+    public function setPsr4($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr4 = (array) $paths;
+        } else {
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Turns on searching the include path for class files.
+     *
+     * @param bool $useIncludePath
+     *
+     * @return void
+     */
+    public function setUseIncludePath($useIncludePath)
+    {
+        $this->useIncludePath = $useIncludePath;
+    }
+
+    /**
+     * Can be used to check if the autoloader uses the include path to check
+     * for classes.
+     *
+     * @return bool
+     */
+    public function getUseIncludePath()
+    {
+        return $this->useIncludePath;
+    }
+
+    /**
+     * Turns off searching the prefix and fallback directories for classes
+     * that have not been registered with the class map.
+     *
+     * @param bool $classMapAuthoritative
+     *
+     * @return void
+     */
+    public function setClassMapAuthoritative($classMapAuthoritative)
+    {
+        $this->classMapAuthoritative = $classMapAuthoritative;
+    }
+
+    /**
+     * Should class lookup fail if not found in the current class map?
+     *
+     * @return bool
+     */
+    public function isClassMapAuthoritative()
+    {
+        return $this->classMapAuthoritative;
+    }
+
+    /**
+     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+     *
+     * @param string|null $apcuPrefix
+     *
+     * @return void
+     */
+    public function setApcuPrefix($apcuPrefix)
+    {
+        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+    }
+
+    /**
+     * The APCu prefix in use, or null if APCu caching is not enabled.
+     *
+     * @return string|null
+     */
+    public function getApcuPrefix()
+    {
+        return $this->apcuPrefix;
+    }
+
+    /**
+     * Registers this instance as an autoloader.
+     *
+     * @param bool $prepend Whether to prepend the autoloader or not
+     *
+     * @return void
+     */
+    public function register($prepend = false)
+    {
+        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+        if (null === $this->vendorDir) {
+            return;
+        }
+
+        if ($prepend) {
+            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+        } else {
+            unset(self::$registeredLoaders[$this->vendorDir]);
+            self::$registeredLoaders[$this->vendorDir] = $this;
+        }
+    }
+
+    /**
+     * Unregisters this instance as an autoloader.
+     *
+     * @return void
+     */
+    public function unregister()
+    {
+        spl_autoload_unregister(array($this, 'loadClass'));
+
+        if (null !== $this->vendorDir) {
+            unset(self::$registeredLoaders[$this->vendorDir]);
+        }
+    }
+
+    /**
+     * Loads the given class or interface.
+     *
+     * @param  string    $class The name of the class
+     * @return true|null True if loaded, null otherwise
+     */
+    public function loadClass($class)
+    {
+        if ($file = $this->findFile($class)) {
+            $includeFile = self::$includeFile;
+            $includeFile($file);
+
+            return true;
+        }
+
+        return null;
+    }
+
+    /**
+     * Finds the path to the file where the class is defined.
+     *
+     * @param string $class The name of the class
+     *
+     * @return string|false The path if found, false otherwise
+     */
+    public function findFile($class)
+    {
+        // class map lookup
+        if (isset($this->classMap[$class])) {
+            return $this->classMap[$class];
+        }
+        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+            return false;
+        }
+        if (null !== $this->apcuPrefix) {
+            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+            if ($hit) {
+                return $file;
+            }
+        }
+
+        $file = $this->findFileWithExtension($class, '.php');
+
+        // Search for Hack files if we are running on HHVM
+        if (false === $file && defined('HHVM_VERSION')) {
+            $file = $this->findFileWithExtension($class, '.hh');
+        }
+
+        if (null !== $this->apcuPrefix) {
+            apcu_add($this->apcuPrefix.$class, $file);
+        }
+
+        if (false === $file) {
+            // Remember that this class does not exist.
+            $this->missingClasses[$class] = true;
+        }
+
+        return $file;
+    }
+
+    /**
+     * Returns the currently registered loaders keyed by their corresponding vendor directories.
+     *
+     * @return array<string, self>
+     */
+    public static function getRegisteredLoaders()
+    {
+        return self::$registeredLoaders;
+    }
+
+    /**
+     * @param  string       $class
+     * @param  string       $ext
+     * @return string|false
+     */
+    private function findFileWithExtension($class, $ext)
+    {
+        // PSR-4 lookup
+        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+        $first = $class[0];
+        if (isset($this->prefixLengthsPsr4[$first])) {
+            $subPath = $class;
+            while (false !== $lastPos = strrpos($subPath, '\\')) {
+                $subPath = substr($subPath, 0, $lastPos);
+                $search = $subPath . '\\';
+                if (isset($this->prefixDirsPsr4[$search])) {
+                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
+                        if (file_exists($file = $dir . $pathEnd)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-4 fallback dirs
+        foreach ($this->fallbackDirsPsr4 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 lookup
+        if (false !== $pos = strrpos($class, '\\')) {
+            // namespaced class name
+            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+        } else {
+            // PEAR-like class name
+            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+        }
+
+        if (isset($this->prefixesPsr0[$first])) {
+            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+                if (0 === strpos($class, $prefix)) {
+                    foreach ($dirs as $dir) {
+                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-0 fallback dirs
+        foreach ($this->fallbackDirsPsr0 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 include paths.
+        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+            return $file;
+        }
+
+        return false;
+    }
+
+    /**
+     * @return void
+     */
+    private static function initializeIncludeClosure()
+    {
+        if (self::$includeFile !== null) {
+            return;
+        }
+
+        /**
+         * Scope isolated include.
+         *
+         * Prevents access to $this/self from included files.
+         *
+         * @param  string $file
+         * @return void
+         */
+        self::$includeFile = \Closure::bind(static function($file) {
+            include $file;
+        }, null, null);
+    }
+}
diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php
new file mode 100644
index 0000000000000000000000000000000000000000..51e734a774b3ed9ca110a921cb40a74f8c7905c2
--- /dev/null
+++ b/vendor/composer/InstalledVersions.php
@@ -0,0 +1,359 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ *
+ * @final
+ */
+class InstalledVersions
+{
+    /**
+     * @var mixed[]|null
+     * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
+     */
+    private static $installed;
+
+    /**
+     * @var bool|null
+     */
+    private static $canGetVendors;
+
+    /**
+     * @var array[]
+     * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+     */
+    private static $installedByVendor = array();
+
+    /**
+     * Returns a list of all package names which are present, either by being installed, replaced or provided
+     *
+     * @return string[]
+     * @psalm-return list<string>
+     */
+    public static function getInstalledPackages()
+    {
+        $packages = array();
+        foreach (self::getInstalled() as $installed) {
+            $packages[] = array_keys($installed['versions']);
+        }
+
+        if (1 === \count($packages)) {
+            return $packages[0];
+        }
+
+        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+    }
+
+    /**
+     * Returns a list of all package names with a specific type e.g. 'library'
+     *
+     * @param  string   $type
+     * @return string[]
+     * @psalm-return list<string>
+     */
+    public static function getInstalledPackagesByType($type)
+    {
+        $packagesByType = array();
+
+        foreach (self::getInstalled() as $installed) {
+            foreach ($installed['versions'] as $name => $package) {
+                if (isset($package['type']) && $package['type'] === $type) {
+                    $packagesByType[] = $name;
+                }
+            }
+        }
+
+        return $packagesByType;
+    }
+
+    /**
+     * Checks whether the given package is installed
+     *
+     * This also returns true if the package name is provided or replaced by another package
+     *
+     * @param  string $packageName
+     * @param  bool   $includeDevRequirements
+     * @return bool
+     */
+    public static function isInstalled($packageName, $includeDevRequirements = true)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (isset($installed['versions'][$packageName])) {
+                return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks whether the given package satisfies a version constraint
+     *
+     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+     *
+     *   Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+     *
+     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
+     * @param  string        $packageName
+     * @param  string|null   $constraint  A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+     * @return bool
+     */
+    public static function satisfies(VersionParser $parser, $packageName, $constraint)
+    {
+        $constraint = $parser->parseConstraints((string) $constraint);
+        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+        return $provided->matches($constraint);
+    }
+
+    /**
+     * Returns a version constraint representing all the range(s) which are installed for a given package
+     *
+     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+     * whether a given version of a package is installed, and not just whether it exists
+     *
+     * @param  string $packageName
+     * @return string Version constraint usable with composer/semver
+     */
+    public static function getVersionRanges($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            $ranges = array();
+            if (isset($installed['versions'][$packageName]['pretty_version'])) {
+                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+            }
+            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+            }
+            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+            }
+            if (array_key_exists('provided', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+            }
+
+            return implode(' || ', $ranges);
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+     */
+    public static function getVersion($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['version'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['version'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+     */
+    public static function getPrettyVersion($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['pretty_version'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+     */
+    public static function getReference($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['reference'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['reference'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+     */
+    public static function getInstallPath($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @return array
+     * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
+     */
+    public static function getRootPackage()
+    {
+        $installed = self::getInstalled();
+
+        return $installed[0]['root'];
+    }
+
+    /**
+     * Returns the raw installed.php data for custom implementations
+     *
+     * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+     * @return array[]
+     * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
+     */
+    public static function getRawData()
+    {
+        @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+        if (null === self::$installed) {
+            // only require the installed.php file if this file is loaded from its dumped location,
+            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+            if (substr(__DIR__, -8, 1) !== 'C') {
+                self::$installed = include __DIR__ . '/installed.php';
+            } else {
+                self::$installed = array();
+            }
+        }
+
+        return self::$installed;
+    }
+
+    /**
+     * Returns the raw data of all installed.php which are currently loaded for custom implementations
+     *
+     * @return array[]
+     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+     */
+    public static function getAllRawData()
+    {
+        return self::getInstalled();
+    }
+
+    /**
+     * Lets you reload the static array from another file
+     *
+     * This is only useful for complex integrations in which a project needs to use
+     * this class but then also needs to execute another project's autoloader in process,
+     * and wants to ensure both projects have access to their version of installed.php.
+     *
+     * A typical case would be PHPUnit, where it would need to make sure it reads all
+     * the data it needs from this class, then call reload() with
+     * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+     * the project in which it runs can then also use this class safely, without
+     * interference between PHPUnit's dependencies and the project's dependencies.
+     *
+     * @param  array[] $data A vendor/composer/installed.php data set
+     * @return void
+     *
+     * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
+     */
+    public static function reload($data)
+    {
+        self::$installed = $data;
+        self::$installedByVendor = array();
+    }
+
+    /**
+     * @return array[]
+     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+     */
+    private static function getInstalled()
+    {
+        if (null === self::$canGetVendors) {
+            self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+        }
+
+        $installed = array();
+
+        if (self::$canGetVendors) {
+            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+                if (isset(self::$installedByVendor[$vendorDir])) {
+                    $installed[] = self::$installedByVendor[$vendorDir];
+                } elseif (is_file($vendorDir.'/composer/installed.php')) {
+                    /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
+                    $required = require $vendorDir.'/composer/installed.php';
+                    $installed[] = self::$installedByVendor[$vendorDir] = $required;
+                    if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
+                        self::$installed = $installed[count($installed) - 1];
+                    }
+                }
+            }
+        }
+
+        if (null === self::$installed) {
+            // only require the installed.php file if this file is loaded from its dumped location,
+            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+            if (substr(__DIR__, -8, 1) !== 'C') {
+                /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
+                $required = require __DIR__ . '/installed.php';
+                self::$installed = $required;
+            } else {
+                self::$installed = array();
+            }
+        }
+
+        if (self::$installed !== array()) {
+            $installed[] = self::$installed;
+        }
+
+        return $installed;
+    }
+}
diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..f27399a042d95c4708af3a8c74d35d338763cf8f
--- /dev/null
+++ b/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
new file mode 100644
index 0000000000000000000000000000000000000000..0fb0a2c194b8590999a5ed79e357d4a9c1e9d8b8
--- /dev/null
+++ b/vendor/composer/autoload_classmap.php
@@ -0,0 +1,10 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+    'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
+);
diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php
new file mode 100644
index 0000000000000000000000000000000000000000..15a2ff3ad6d8d6ea2b6b1f9552c62d745ffc9bf4
--- /dev/null
+++ b/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+);
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
new file mode 100644
index 0000000000000000000000000000000000000000..3890ddc2409b78e2720b8ec2db64d76b18811b3d
--- /dev/null
+++ b/vendor/composer/autoload_psr4.php
@@ -0,0 +1,9 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+);
diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php
new file mode 100644
index 0000000000000000000000000000000000000000..5c6bdc2aac2439eda41cec7dcde9b259b3e54eb1
--- /dev/null
+++ b/vendor/composer/autoload_real.php
@@ -0,0 +1,36 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInite2b57849f956d4c67658848cdb027ed8
+{
+    private static $loader;
+
+    public static function loadClassLoader($class)
+    {
+        if ('Composer\Autoload\ClassLoader' === $class) {
+            require __DIR__ . '/ClassLoader.php';
+        }
+    }
+
+    /**
+     * @return \Composer\Autoload\ClassLoader
+     */
+    public static function getLoader()
+    {
+        if (null !== self::$loader) {
+            return self::$loader;
+        }
+
+        spl_autoload_register(array('ComposerAutoloaderInite2b57849f956d4c67658848cdb027ed8', 'loadClassLoader'), true, true);
+        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
+        spl_autoload_unregister(array('ComposerAutoloaderInite2b57849f956d4c67658848cdb027ed8', 'loadClassLoader'));
+
+        require __DIR__ . '/autoload_static.php';
+        call_user_func(\Composer\Autoload\ComposerStaticInite2b57849f956d4c67658848cdb027ed8::getInitializer($loader));
+
+        $loader->register(true);
+
+        return $loader;
+    }
+}
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
new file mode 100644
index 0000000000000000000000000000000000000000..3e4da4b5236ce7f8115d22974ee5d82678e7a4ed
--- /dev/null
+++ b/vendor/composer/autoload_static.php
@@ -0,0 +1,20 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInite2b57849f956d4c67658848cdb027ed8
+{
+    public static $classMap = array (
+        'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+    );
+
+    public static function getInitializer(ClassLoader $loader)
+    {
+        return \Closure::bind(function () use ($loader) {
+            $loader->classMap = ComposerStaticInite2b57849f956d4c67658848cdb027ed8::$classMap;
+
+        }, null, ClassLoader::class);
+    }
+}
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
new file mode 100644
index 0000000000000000000000000000000000000000..87fda747e6ce957c1bf1b4d373bb5c60dbf96e7d
--- /dev/null
+++ b/vendor/composer/installed.json
@@ -0,0 +1,5 @@
+{
+    "packages": [],
+    "dev": true,
+    "dev-package-names": []
+}
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
new file mode 100644
index 0000000000000000000000000000000000000000..61d003e1314dc1510e74e71c8a68064a5d2f5a03
--- /dev/null
+++ b/vendor/composer/installed.php
@@ -0,0 +1,23 @@
+<?php return array(
+    'root' => array(
+        'name' => 'drupal/dsfr4drupal',
+        'pretty_version' => '1.x-dev',
+        'version' => '1.9999999.9999999.9999999-dev',
+        'reference' => 'c48cfd677155caefea8b2a0c84ffe527588e0484',
+        'type' => 'drupal-theme',
+        'install_path' => __DIR__ . '/../../',
+        'aliases' => array(),
+        'dev' => true,
+    ),
+    'versions' => array(
+        'drupal/dsfr4drupal' => array(
+            'pretty_version' => '1.x-dev',
+            'version' => '1.9999999.9999999.9999999-dev',
+            'reference' => 'c48cfd677155caefea8b2a0c84ffe527588e0484',
+            'type' => 'drupal-theme',
+            'install_path' => __DIR__ . '/../../',
+            'aliases' => array(),
+            'dev_requirement' => false,
+        ),
+    ),
+);