diff --git a/Gemfile.lock b/Gemfile.lock
index 3015273b40f537d36787c8bd68c6c12e69e3188a..664b81dd513a8f47037aaecb93f8d6b344af9d7e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -8,7 +8,7 @@ GIT
 
 GIT
   remote: https://github.com/noesya/summernote-rails.git
-  revision: f7ce423738fa98a1a2bacc509d6906e03b498a0e
+  revision: 32fd182c929cdcacaa6e3bd3569871bd025fa669
   specs:
     summernote-rails (0.8.20.1)
       nokogiri
@@ -78,11 +78,11 @@ GEM
       erubi (~> 1.11)
       rails-dom-testing (~> 2.2)
       rails-html-sanitizer (~> 1.6)
-    active_storage_validations (1.2.0)
-      activejob (>= 5.2.0)
-      activemodel (>= 5.2.0)
-      activestorage (>= 5.2.0)
-      activesupport (>= 5.2.0)
+    active_storage_validations (1.3.0)
+      activejob (>= 6.1.4)
+      activemodel (>= 6.1.4)
+      activestorage (>= 6.1.4)
+      activesupport (>= 6.1.4)
     activejob (7.2.1.1)
       activesupport (= 7.2.1.1)
       globalid (>= 0.3.6)
@@ -123,20 +123,20 @@ GEM
     autoprefixer-rails (10.4.19.0)
       execjs (~> 2)
     aws-eventstream (1.3.0)
-    aws-partitions (1.991.0)
-    aws-sdk-core (3.209.1)
+    aws-partitions (1.992.0)
+    aws-sdk-core (3.211.0)
       aws-eventstream (~> 1, >= 1.3.0)
-      aws-partitions (~> 1, >= 1.651.0)
+      aws-partitions (~> 1, >= 1.992.0)
       aws-sigv4 (~> 1.9)
       jmespath (~> 1, >= 1.6.1)
-    aws-sdk-kms (1.94.0)
-      aws-sdk-core (~> 3, >= 3.207.0)
+    aws-sdk-kms (1.95.0)
+      aws-sdk-core (~> 3, >= 3.210.0)
       aws-sigv4 (~> 1.5)
-    aws-sdk-s3 (1.168.0)
-      aws-sdk-core (~> 3, >= 3.207.0)
+    aws-sdk-s3 (1.169.0)
+      aws-sdk-core (~> 3, >= 3.210.0)
       aws-sdk-kms (~> 1)
       aws-sigv4 (~> 1.5)
-    aws-sigv4 (1.10.0)
+    aws-sigv4 (1.10.1)
       aws-eventstream (~> 1, >= 1.0.2)
     base64 (0.2.0)
     bcrypt (3.1.20)
@@ -530,7 +530,7 @@ GEM
       nokogiri (~> 1)
       rubyzip (>= 1.3.0, < 3.0.0)
     rotp (6.3.0)
-    rspec-core (3.13.1)
+    rspec-core (3.13.2)
       rspec-support (~> 3.13.0)
     rspec-expectations (3.13.3)
       diff-lcs (>= 1.2.0, < 2.0)
@@ -597,7 +597,7 @@ GEM
     simple_form (5.3.1)
       actionpack (>= 5.2)
       activemodel (>= 5.2)
-    simple_form_bs5_file_input (0.1.4)
+    simple_form_bs5_file_input (0.1.5)
       rails
       simple_form
     simple_form_password_with_hints (0.0.7)
@@ -672,7 +672,7 @@ GEM
     websocket-extensions (0.1.5)
     xpath (3.2.0)
       nokogiri (~> 1.8)
-    zeitwerk (2.7.0)
+    zeitwerk (2.7.1)
     zlib (2.1.1)
 
 PLATFORMS
diff --git a/app/assets/images/communication/blocks/templates/title.jpg b/app/assets/images/communication/blocks/templates/title.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..15a329fa0183a3ea23a2ac2da2808b2ef2b51037
Binary files /dev/null and b/app/assets/images/communication/blocks/templates/title.jpg differ
diff --git a/app/assets/images/communication/blocks/templates/title/classic.png b/app/assets/images/communication/blocks/templates/title/classic.png
new file mode 100644
index 0000000000000000000000000000000000000000..8aa33631804c40ecf864f8f1c599eb5640144216
Binary files /dev/null and b/app/assets/images/communication/blocks/templates/title/classic.png differ
diff --git a/app/assets/images/communication/blocks/templates/title/collapsed.png b/app/assets/images/communication/blocks/templates/title/collapsed.png
new file mode 100644
index 0000000000000000000000000000000000000000..66665ed9c0edfacbb6babd94fe99873747c3aa2f
Binary files /dev/null and b/app/assets/images/communication/blocks/templates/title/collapsed.png differ
diff --git a/app/assets/javascripts/admin/commons/content_editor/offcanvas.js b/app/assets/javascripts/admin/commons/content_editor/offcanvas.js
index 7d1984b2a5b75fdff964b0f45d5a8ddd98adba05..db6376cf905619a85736d31c3955dbc6b02cd09b 100644
--- a/app/assets/javascripts/admin/commons/content_editor/offcanvas.js
+++ b/app/assets/javascripts/admin/commons/content_editor/offcanvas.js
@@ -13,13 +13,14 @@ window.osuny.contentEditor.offcanvas = {
         var i,
             button;
         this.editButtons = document.querySelectorAll('.js-content-editor__element__edit');
-        this.addBlockButton = document.querySelector('.js-content-editor__add-block');
+        this.addBlockButtons = document.querySelectorAll('.js-content-editor__add-block');
         for (i = 0; i < this.editButtons.length; i += 1) {
             button = this.editButtons[i];
             button.addEventListener('click', this.onBlockActionClick.bind(this));
         }
-        if (this.addBlockButton) {
-            this.addBlockButton.addEventListener('click', this.onBlockActionClick.bind(this));
+        for (i = 0; i < this.addBlockButtons.length; i += 1) {
+            button = this.addBlockButtons[i];
+            button.addEventListener('click', this.onBlockActionClick.bind(this));
         }
     },
 
diff --git a/app/assets/javascripts/admin/commons/content_editor/sort.js b/app/assets/javascripts/admin/commons/content_editor/sort.js
index 1cb1564cb9e0024a4e1fa95bfdf77cc548d6c6f5..a48a072d4a658ae64c05c55a4ec1b68cb1ac8e76 100644
--- a/app/assets/javascripts/admin/commons/content_editor/sort.js
+++ b/app/assets/javascripts/admin/commons/content_editor/sort.js
@@ -4,8 +4,7 @@ window.osuny.contentEditor.sort = {
         'use strict';
         this.container = document.querySelector('.js-content-editor');
         this.sortableContainers = this.container.querySelectorAll('.js-content-editor-sortable-container');
-        this.sortHeadingsUrl = this.container.getAttribute('data-sort-headings-url');
-        this.sortBlocksUrl = this.container.getAttribute('data-sort-blocks-url');
+        this.sortUrl = this.container.getAttribute('data-sort-url');
         this.initSortable();
     },
 
@@ -22,44 +21,18 @@ window.osuny.contentEditor.sort = {
     },
 
     onSortableEnd: function (event) {
-        'use strict';
-        if (event.from.classList.contains('content-editor--write')) {
-            this.sortModeWrite(event.to);
-        } else if (event.from.classList.contains('content-editor--organize')) {
-            this.sortModeOrganize(event.to);
-        }
-    },
-
-    // Mode écriture du contenu
-    sortModeWrite: function (to) {
         'use strict';
         var ids = [],
             child,
             i;
-        for (i = 0; i < to.children.length; i += 1) {
-            child = to.children[i];
-            // Nous utilisons une route déjà existante, dédiée aux blocs,
-            // pour gérer à la fois des blocs et des headings.
-            // Ca manque d'élégance.
+        for (i = 0; i < event.to.children.length; i += 1) {
+            child = event.to.children[i];
             ids.push({
                 id: child.dataset.id,
                 kind: child.dataset.kind
             });
         }
-        $.post(this.sortBlocksUrl, { ids: ids });
-    },
-
-    // Mode organisation du plan
-    sortModeOrganize: function (to) {
-        'use strict';
-        var ids = [],
-            child,
-            i;
-        for (i = 0; i < to.children.length; i += 1) {
-            child = to.children[i];
-            ids.push(child.dataset.id);
-        }
-        $.post(this.sortHeadingsUrl, { ids: ids });
+        $.post(this.sortUrl, { ids: ids });
     },
 
     invoke: function () {
diff --git a/app/assets/stylesheets/admin/components/content-editor.sass b/app/assets/stylesheets/admin/components/content-editor.sass
index bd3599017776563fd2df0ecac4ceeb100ca10e29..672ee76585e14a6df5c4333478a760f3e1e540a6 100644
--- a/app/assets/stylesheets/admin/components/content-editor.sass
+++ b/app/assets/stylesheets/admin/components/content-editor.sass
@@ -33,13 +33,8 @@
                 transition: opacity 0.5s ease
                 &:hover
                     opacity: 1
-            &--heading
-                &.sortable-chosen
-                    .content-editor__elements
-                        display: none
-            &--mode-structure
-                .content-editor__elements__handle
-                    cursor: move
+            .content-editor__elements__handle
+                cursor: move
             &--hover
                 opacity: 0
                 transition: opacity 0.25s ease
@@ -67,6 +62,7 @@
             &--organizations
                 .organizations-list
                     display: flex
+                    flex-wrap: wrap
                     div
                         align-items: center
                         display: flex
diff --git a/app/assets/stylesheets/admin/design-system/image.sass b/app/assets/stylesheets/admin/design-system/image.sass
index c39bc07a4def7b2bb9cf8eec16d65084d49143d0..d5a4b23de10d15a67385f764829164e0ac385f4c 100644
--- a/app/assets/stylesheets/admin/design-system/image.sass
+++ b/app/assets/stylesheets/admin/design-system/image.sass
@@ -3,6 +3,6 @@
     border-radius: 100%
     object-fit: cover
     width: 100%
-.img-fluid
+.svg-fluid
     block-size: 100%
     inline-size: 100%
\ No newline at end of file
diff --git a/app/controllers/admin/communication/blocks/application_controller.rb b/app/controllers/admin/communication/blocks/application_controller.rb
deleted file mode 100644
index 1399c37ac184db805cae2845149d8badf1961879..0000000000000000000000000000000000000000
--- a/app/controllers/admin/communication/blocks/application_controller.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-class Admin::Communication::Blocks::ApplicationController < Admin::Communication::ApplicationController
-
-  protected
-
-  def breadcrumb
-    short_breadcrumb
-  end
-end
diff --git a/app/controllers/admin/communication/blocks/headings_controller.rb b/app/controllers/admin/communication/blocks/headings_controller.rb
deleted file mode 100644
index 7cbf4c119174a4632f3479d8a858f3fc1d462760..0000000000000000000000000000000000000000
--- a/app/controllers/admin/communication/blocks/headings_controller.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-class Admin::Communication::Blocks::HeadingsController < Admin::Communication::Blocks::ApplicationController
-  load_and_authorize_resource class: Communication::Block::Heading,
-                              through: :current_university,
-                              through_association: :communication_block_headings
-
-  def reorder
-    ids = params[:ids] || []
-    ids.each.with_index do |id, index|
-      @heading = current_university.communication_block_headings.find(id)
-      @heading.update_columns parent_id: nil,
-                              level: Communication::Block::Heading::DEFAULT_LEVEL,
-                              position: index + 1
-    end
-    if @heading.about&.respond_to?(:is_direct_object?)
-      @heading.about.is_direct_object?  ? @heading.about.sync_with_git
-                                        : @heading.about.touch # Sync indirect object's direct sources through after_touch
-    end
-  end
-
-  def new
-    @heading.about = PolymorphicObjectFinder.find(
-      params,
-      key: :about,
-      university: current_university,
-      mandatory_module: Contentful
-    )
-    breadcrumb
-  end
-
-  def edit
-    breadcrumb
-  end
-
-  def create
-    if @heading.save
-      redirect_to about_path,
-                  notice: t('admin.successfully_created_html', model: @heading.to_s)
-    else
-      breadcrumb
-      render :new, status: :unprocessable_entity
-    end
-  end
-
-  def update
-    if @heading.update(heading_params)
-      redirect_to about_path,
-                  notice: t('admin.successfully_updated_html', model: @heading.to_s)
-    else
-      breadcrumb
-      add_breadcrumb t('edit')
-      render :edit, status: :unprocessable_entity
-    end
-  end
-
-  def destroy
-    path = about_path
-    @heading.destroy
-    redirect_to path,
-                notice: t('admin.successfully_destroyed_html', model: @heading.to_s)
-  end
-
-  protected
-
-  # TODO factorize with blocks (no need, because this will disappear when heading becomes a special block)
-  def website_id
-    params[:website_id] || @heading.about&.website.id
-  rescue
-  end
-
-  def extranet_id
-    params[:extranet_id] || @heading.about&.extranet.id
-  rescue
-  end
-
-  def journal_id
-    params[:journal_id] || @heading.about&.journal.id
-  rescue
-  end
-
-  def about_path
-    # Les headings sont toujours affectés à des localisations
-    l10n = @heading.about
-    # La localisation porte sur un objet, par exemple une University::Person ou un Communication::Website::Post
-    object_edited = l10n.about
-    # La formation ou la page concernée
-    path_method = "admin_#{object_edited.class.base_class.to_s.parameterize.underscore}_path"
-    path_method_options = {
-      id: object_edited.id,
-      lang: l10n.language,
-      website_id: website_id,
-      extranet_id: extranet_id,
-      journal_id: journal_id
-    }
-    public_send path_method, **path_method_options
-  end
-  # TODO /factorize
-
-  def breadcrumb
-    super
-    add_breadcrumb @heading.about, about_path
-    if @heading.new_record?
-      add_breadcrumb t('admin.communication.blocks.headings.add')
-    else
-      add_breadcrumb @heading
-    end
-  end
-
-  def heading_params
-    params.require(:communication_block_heading)
-          .permit(:about_id, :about_type, :title)
-          .merge(university_id: current_university.id)
-  end
-end
diff --git a/app/controllers/server/websites_controller.rb b/app/controllers/server/websites_controller.rb
index a2daca9d4f7298441d903bf9d927d8379b7de227..8f41c6f9970a64d9b51946aa936e132f9b82570f 100644
--- a/app/controllers/server/websites_controller.rb
+++ b/app/controllers/server/websites_controller.rb
@@ -15,6 +15,11 @@ class Server::WebsitesController < Server::ApplicationController
     redirect_back(fallback_location: server_websites_path, notice: t('server_admin.websites.clean_and_rebuild_all_websites_notice'))
   end
 
+  def clean_and_rebuild
+    Communication::Website::CleanAndRebuildJob.perform_later(@website.id)
+    redirect_back(fallback_location: server_website_path(@website), notice: t('server_admin.websites.clean_and_rebuild_website_notice'))
+  end
+
   def sync_theme_version
     @website.get_current_theme_version!
   end
diff --git a/app/helpers/admin/blocks_helper.rb b/app/helpers/admin/blocks_helper.rb
index 22c29435377f69b672b9a1f5ac85ebb7a1ac0406..a2afc930accb8a95c2372f23bd01939f5399667f 100644
--- a/app/helpers/admin/blocks_helper.rb
+++ b/app/helpers/admin/blocks_helper.rb
@@ -42,7 +42,7 @@ module Admin::BlocksHelper
   def block_html_class(block)
     html_class = "block block-#{block.template_kind}"
     html_class += " block-#{block.template_kind}--#{block.template.layout}" if block.template.respond_to?(:layout)
-    html_class += " block-titled" if block.template.heading_title
+    html_class += " block-titled" if block.title.present?
     html_class
   end
 end
diff --git a/app/models/communication/block.rb b/app/models/communication/block.rb
index c4dc544fd920a58ec32b14cd38f0ef03cf0b05c7..3261dc4a4f9de6e14bf7ad3f0d3a04453e2d56cd 100644
--- a/app/models/communication/block.rb
+++ b/app/models/communication/block.rb
@@ -32,6 +32,14 @@
 #  fk_rails_90ac986fab  (heading_id => communication_block_headings.id)
 #
 class Communication::Block < ApplicationRecord
+  BLOCK_COPY_COOKIE = 'osuny-content-editor-block-copy'
+  CATEGORIES = {
+    basic: [:title, :chapter, :image, :video, :sound, :datatable],
+    storytelling: [:key_figures, :features, :gallery, :call_to_action, :testimonials, :timeline],
+    references: [:pages, :posts, :persons, :organizations, :agenda, :programs, :locations, :projects, :papers, :volumes],
+    utilities: [:files, :definitions, :contact, :links, :license, :embed]
+  }
+
   include AsIndirectObject
   include Orderable
   include WithAccessibility
@@ -41,18 +49,6 @@ class Communication::Block < ApplicationRecord
   include WithUniversity
   include Sanitizable
 
-  BLOCK_COPY_COOKIE = 'osuny-content-editor-block-copy'
-
-  belongs_to  :about, polymorphic: true
-  belongs_to  :communication_website,
-              class_name: "Communication::Website",
-              optional: true
-  alias       :website :communication_website
-
-  # We do not use the :touch option of the belongs_to association
-  # because we do not want to touch the about when destroying the block.
-  after_save :touch_about#, :touch_targets # FIXME
-
   # Les numéros sont un peu en vrac
   # Dans l'idée, pour le futur
   # 1000 basic
@@ -85,22 +81,26 @@ class Communication::Block < ApplicationRecord
     sound: 1005,
     testimonials: 400,
     timeline: 700,
+    title: 1001,
     video: 52,
     volumes: 3310
-  }
+  }, prefix: :template
 
-  CATEGORIES = {
-    basic: [:chapter, :image, :video, :sound, :datatable],
-    storytelling: [:key_figures, :features, :gallery, :call_to_action, :testimonials, :timeline],
-    references: [:pages, :posts, :persons, :organizations, :agenda, :programs, :locations, :projects, :papers, :volumes],
-    utilities: [:files, :definitions, :contact, :links, :license, :embed]
-  }
+  belongs_to  :about, polymorphic: true
+  belongs_to  :communication_website,
+              class_name: "Communication::Website",
+              optional: true
+  alias       :website :communication_website
+
+  before_validation :set_university_and_website_from_about, on: :create
+
+  # We do not use the :touch option of the belongs_to association
+  # because we do not want to touch the about when destroying the block.
+  after_save :touch_about#, :touch_targets # FIXME
 
   scope :published, -> { where(published: true) }
   scope :without_heading, -> { where(heading: nil) }
 
-  before_validation :set_university_and_website_from_about, on: :create
-
   # When we set data from json, we pass it to the template.
   # The json we save is first sanitized and prepared by the template.
   def data=(value)
@@ -123,6 +123,7 @@ class Communication::Block < ApplicationRecord
 
   def language
     return @language if defined?(@language)
+    return unless about.respond_to?(:language)
     @language ||= about.language
   end
 
@@ -135,15 +136,13 @@ class Communication::Block < ApplicationRecord
   def paste(about)
     block = self.dup
     block.about = about
-    block.heading = nil
     block.save
     block
   end
 
-  def localize_for!(new_localization, localized_heading_id = nil)
+  def localize_for!(new_localization)
     localized_block = self.dup
     localized_block.about = new_localization
-    localized_block.heading_id = localized_heading_id
     localized_block.save
   end
 
@@ -152,7 +151,11 @@ class Communication::Block < ApplicationRecord
   end
 
   def full_text
-    template.full_text
+    "#{title} #{template.full_text}"
+  end
+
+  def slug
+    title.blank? ? '' : "#{title.parameterize}"
   end
 
   def to_s
diff --git a/app/models/communication/block/heading.rb b/app/models/communication/block/heading.rb
index 282c388ad61f80a09e738ef89b81def864704efb..9b16590ccfe6baf33e51d49201a6821a86c6cc4b 100644
--- a/app/models/communication/block/heading.rb
+++ b/app/models/communication/block/heading.rb
@@ -27,6 +27,8 @@
 #  fk_rails_6d3de8388e  (parent_id => communication_block_headings.id)
 #  fk_rails_ae82723550  (university_id => universities.id)
 #
+
+# TODO TITLE remove
 class Communication::Block::Heading < ApplicationRecord
   include AsIndirectObject
   include Orderable
diff --git a/app/models/communication/block/template/base.rb b/app/models/communication/block/template/base.rb
index 29a39d5ab2368b5a27eba2965b73dfd0e93e3ade..3639ff7838598fc88f8f1aa2e9571d5f65afb44c 100644
--- a/app/models/communication/block/template/base.rb
+++ b/app/models/communication/block/template/base.rb
@@ -79,10 +79,6 @@ class Communication::Block::Template::Base
     false
   end
 
-  def heading_title
-    block.title
-  end
-
   def empty?
     false
   end
diff --git a/app/models/communication/block/template/key_figure/element.rb b/app/models/communication/block/template/key_figure/element.rb
index afa1c2fff054c486e5bce050b526db3d290dc729..38c60d11eaa851eb73f95319332778a70aae84c4 100644
--- a/app/models/communication/block/template/key_figure/element.rb
+++ b/app/models/communication/block/template/key_figure/element.rb
@@ -2,7 +2,7 @@ class Communication::Block::Template::KeyFigure::Element < Communication::Block:
 
   has_component :number, :number
   has_component :unit, :string
-  has_component :description, :rich_text
+  has_component :description, :string
   has_component :image, :image
 
   def blob
diff --git a/app/models/communication/block/template/page.rb b/app/models/communication/block/template/page.rb
index 77c867c0b8d3ef256f2ec92ed14f5a851892b423..4889efca62406a1091257d36a75ba0413402d71d 100644
--- a/app/models/communication/block/template/page.rb
+++ b/app/models/communication/block/template/page.rb
@@ -36,11 +36,6 @@ class Communication::Block::Template::Page < Communication::Block::Template::Bas
     selected_pages
   end
 
-  def heading_title
-    block.title.present?  ? block.title
-                          : page&.to_s_in(block.language)
-  end
-
   protected
 
   def selected_pages_selection
diff --git a/app/models/communication/block/template/program.rb b/app/models/communication/block/template/program.rb
index 9fda4e04b6d2cef477c60eac3b74faa1d248c8ea..995c94d23b5601c2b9db06759b887f9468f3f652 100644
--- a/app/models/communication/block/template/program.rb
+++ b/app/models/communication/block/template/program.rb
@@ -12,7 +12,12 @@ class Communication::Block::Template::Program < Communication::Block::Template::
   end
 
   def selected_programs
-    @selected_programs ||= elements.map { |element| element.program }.compact
+    @selected_programs ||= elements.map { |element| 
+      program = element.program
+      l10n = program.localization_for(about.language)
+      next if l10n.draft?
+      element.program 
+    }.compact
   end
 
   def allowed_for_about?
diff --git a/app/models/communication/block/template/title.rb b/app/models/communication/block/template/title.rb
new file mode 100644
index 0000000000000000000000000000000000000000..344c817fb1fcb99496424e9920cc567c73eab082
--- /dev/null
+++ b/app/models/communication/block/template/title.rb
@@ -0,0 +1,9 @@
+class Communication::Block::Template::Title < Communication::Block::Template::Base
+
+  has_layouts [:classic, :collapsed]
+
+  def empty?
+    block.title.blank?
+  end
+
+end
diff --git a/app/models/communication/block/with_heading_ranks.rb b/app/models/communication/block/with_heading_ranks.rb
index f993fa6b58b366733f2ee0188c9713d5568b114f..e990743902c61fc8a6c09a503f0e66cb523355c2 100644
--- a/app/models/communication/block/with_heading_ranks.rb
+++ b/app/models/communication/block/with_heading_ranks.rb
@@ -4,20 +4,17 @@ module Communication::Block::WithHeadingRanks
   DEFAULT_HEADING_LEVEL = 2 # h1 is the page title
 
   included do
+    # TODO TITLE remove
     belongs_to :heading, optional: true
-  
-    before_validation :set_heading_from_about, on: :create
   end
 
   def heading_rank_self
-    template.heading_title.present? ? heading_rank_base
-                                    : false
+    title.present? ? heading_rank_base : false
   end
 
   def heading_rank_children
     return false unless heading_children?
-    heading_rank_self ? heading_rank_self + 1
-                      : heading_rank_base
+    heading_rank_self ? heading_rank_self + 1 : heading_rank_base
   end
 
   def heading_children?
@@ -26,13 +23,18 @@ module Communication::Block::WithHeadingRanks
 
   protected
 
-  def set_heading_from_about
-    # IMPROVEMENT: Ne gère que le 1er niveau actuellement
-    self.heading ||= about.headings.root.ordered.last
+  def heading_rank_base
+    block_title.present? ? block_title.heading_rank_self + 1 : DEFAULT_HEADING_LEVEL
   end
 
-  def heading_rank_base
-    heading.present?  ? heading.level + 1
-                      : DEFAULT_HEADING_LEVEL
+  # A block can belong to a title, meaning it is below the title
+  def block_title
+    return if template_title?
+    about.blocks
+          .template_title # We are looking for title blocks
+          .where('position < ?', position) # Before this block
+          .order(position: :desc)
+          .limit(1)
+          .first
   end
 end
diff --git a/app/models/communication/website/agenda/event.rb b/app/models/communication/website/agenda/event.rb
index 0e9ecc006c689ad4b3cbd622843f92e1c2840a45..56875c64a9b956b66ea68449cede5c50ef45c1ea 100644
--- a/app/models/communication/website/agenda/event.rb
+++ b/app/models/communication/website/agenda/event.rb
@@ -89,6 +89,6 @@ class Communication::Website::Agenda::Event < ApplicationRecord
   protected
 
   def abouts_with_agenda_block
-    website.blocks.agenda.collect(&:about)
+    website.blocks.template_agenda.collect(&:about)
   end
 end
diff --git a/app/models/communication/website/page.rb b/app/models/communication/website/page.rb
index 783c522c060fb17a6a9b59ad36d4e399c2dc807e..a4271734078d54cf805ca17ec3c1be5fc0d1d900 100644
--- a/app/models/communication/website/page.rb
+++ b/app/models/communication/website/page.rb
@@ -148,7 +148,7 @@ class Communication::Website::Page < ApplicationRecord
   end
 
   def abouts_with_page_block
-    website.blocks.pages.collect(&:about)
+    website.blocks.template_pages.collect(&:about)
   end
 
   def touch_elements_if_special_page_in_hierarchy
diff --git a/app/models/communication/website/page/accessibility.rb b/app/models/communication/website/page/accessibility.rb
index d8719cafe7b52ce3f5d1fe402454fa27968b986b..1642dc997994300c2b278c01f10f1e573bf19374 100644
--- a/app/models/communication/website/page/accessibility.rb
+++ b/app/models/communication/website/page/accessibility.rb
@@ -25,8 +25,8 @@ class Communication::Website::Page::Accessibility < Communication::Website::Page
   protected
 
   def generate_declaration(l10n)
-    heading = l10n.generate_heading 'Déclaration d\'accessibilité'
-    l10n.generate_block(heading, :files, {
+    l10n.generate_block(:title, title: 'Déclaration d\'accessibilité')
+    l10n.generate_block(:files, data: {
       description: "<p>#{website} s'engage à rendre son site internet accessible conformément à l'article 47 de la loi n° 2005-102 du 11 février 2005.</p><p>Cette déclaration d'accessibilité s'applique au site #{website.url}.</p>",
       elements: [
         { title: "Schéma pluriannuel d’accessibilité", file: {} },
@@ -36,8 +36,8 @@ class Communication::Website::Page::Accessibility < Communication::Website::Page
   end
 
   def generate_results(l10n)
-    heading = l10n.generate_heading 'Résultats des tests'
-    l10n.generate_block(heading, :key_figures, {
+    l10n.generate_block(:title, title: 'Résultats des tests')
+    l10n.generate_block(:key_figures, data: {
       description: "<p>Le contre-audit de conformité, finalisé le 00/00/0000 par la société [nom de la société], révèle que :</p>",
       elements:[
         { number: nil, unit: "%", description: "de conformité au RGAA" },
@@ -47,36 +47,36 @@ class Communication::Website::Page::Accessibility < Communication::Website::Page
   end
 
   def generate_conformity(l10n)
-    heading = l10n.generate_heading 'État de conformité'
-    l10n.generate_block(heading, :chapter, {
+    l10n.generate_block(:title, title: 'État de conformité')
+    l10n.generate_block(:chapter, data: {
       text: "<p>Le site #{website} (#{website.url}) est <b>[non conforme, partiellement conforme, totalement conforme]</b> avec le référentiel général d’amélioration de l’accessibilité (RGAA), version 4 en raison des non-conformités et des dérogations énumérées ci-dessous.</p>"
     })
   end
 
   def generate_unaccessible(l10n)
-    heading = l10n.generate_heading 'Contenus non accessibles'
-    l10n.generate_block(heading, :chapter, {
+    l10n.generate_block(:title, title: 'Contenus non accessibles')
+    l10n.generate_block(:chapter, data: {
       text: "<p>Les contenus listés ci-dessous ne sont pas accessibles pour les raisons suivantes.<br></p><p><b>Dérogations pour charge disproportionnée</b></p><ul><li></li></ul><p><b>Contenus non soumis à l'obligation d'accessibilité</b></p><ul><li></li></ul>"
     })
   end
 
   def generate_conditions(l10n)
-    heading = l10n.generate_heading 'Établissement de cette déclaration d\'accessibilité'
-    l10n.generate_block(heading, :chapter, {
+    l10n.generate_block(:title, title: 'Établissement de cette déclaration d\'accessibilité')
+    l10n.generate_block(:chapter, data: {
       text: "<p>Cette déclaration a été établie le <b>00/00/0000</b>.<br></p><p>Technologies utilisées pour la réalisation du site :</p><ul>\n<li>HTML5</li>\n<li>CSS</li>\n<li>Javascript</li>\n<li>Hugo</li>\n</ul><p>Agents utilisateurs et technologies d'assistance utilisés pour vérifier l'accessibilité :</p><ul>\n<li>NVDA</li>\n<li>VoiceOver</li>\n</ul><p>La vérification de l'accessibilité a été effectuée au travers de tests manuels, assistés par les outils suivants :</p><ul>\n<li>Accessibility Insights for Web</li>\n<li>ArcToolkit</li>\n<li>Assistant RGAA</li>\n<li>Axe DevTool</li>\n<li>Color Contrast Analyser</li>\n<li>eAccessibility (PDF accessibility checker)</li>\n<li>Inspecteur de composants</li>\n<li>Web Developer Toolbar</li>\n<li>WCAG Contrast checker</li>\n<li>Validateur HTML du W3C</li>\n</ul><ul>\n</ul><p>Pages du site ayant fait l'objet de la vérification de conformité</p><ul>\n<li></li>\n</ul>"
     })
   end
 
   def generate_environment(l10n)
-    heading = l10n.generate_heading 'Environnement de test'
-    l10n.generate_block(heading, :chapter, {
+    l10n.generate_block(:title, title: 'Environnement de test')
+    l10n.generate_block(:chapter, data: {
       text: "<p>Les vérifications de restitution de contenus ont été réalisées sur la base de la combinaison fournie par la base de référence du RGAA, avec les versions suivantes :</p><ul>\n<li>Sur ordinateur MacOS avec Google Chrome et VoiceOver</li>\n<li>Sur ordinateur MacOS avec Safari et VoiceOver</li>\n<li>Sur ordinateur Windows avec Firefox et NVDA</li>\n<li>Sur mobile Android avec Google Chrome et Talkback</li>\n</ul>"
     })
   end
 
   def generate_problem(l10n)
-    heading = l10n.generate_heading 'Voies de recours'
-    l10n.generate_block(heading, :chapter, {
+    l10n.generate_block(:title, title: 'Voies de recours')
+    l10n.generate_block(:chapter, data: {
       text: "<p>Si vous avez signalé au responsable du site internet un défaut d'accessibilité qui vous empêche d'accéder à un contenu ou à un des services du portail et n'avez pas obtenu de réponse satisfaisante, vous êtes en droit de :</p><p><b></b></p><ul>\n<li><p><a href=\"https://formulaire.defenseurdesdroits.fr/\" target=\"_blank\">Écrire un message au Défenseur des droits</a> (formulaire en ligne) ;</p></li>\n<li><p>Contacter le <a href=\"https://www.defenseurdesdroits.fr/saisir/delegues\" target=\"_blank\">délégué du Défenseur des droits dans votre région</a> ;</p></li>\n<li><p>Envoyer un courrier postal (gratuit, ne pas mettre de timbre) à cette adresse : Défenseur des droits - Libre réponse 71120 - 75342 Paris CEDEX 07.</p></li>\n</ul><ul>\n</ul>"
     })
   end
diff --git a/app/models/communication/website/portfolio/project.rb b/app/models/communication/website/portfolio/project.rb
index bef8dff9fa335e614aa6cfaf752fc9492858dde4..2a5ea8190328e55661f5d77d94758dbb996c40b0 100644
--- a/app/models/communication/website/portfolio/project.rb
+++ b/app/models/communication/website/portfolio/project.rb
@@ -95,7 +95,7 @@ class Communication::Website::Portfolio::Project < ApplicationRecord
   protected
 
   def abouts_with_projects_block
-    website.blocks.projects.collect(&:about)
+    website.blocks.template_projects.collect(&:about)
   end
 
 end
diff --git a/app/models/communication/website/post.rb b/app/models/communication/website/post.rb
index 6239c6c3cdc62ed5ba4689181c0cec9f7ad357e9..8941e140ae69ce59d10b314e452a421d924bb1e8 100644
--- a/app/models/communication/website/post.rb
+++ b/app/models/communication/website/post.rb
@@ -117,12 +117,12 @@ class Communication::Website::Post < ApplicationRecord
   end
 
   def abouts_with_post_block
-    website.blocks.posts.collect(&:about)
+    website.blocks.template_posts.collect(&:about)
     # Potentiel gain de performance (25%)
     # Méthode collect : X abouts = X requêtes
     # Méthode ci-dessous : X abouts = 6 requêtes
-    # website.post_categories.where(id: website.blocks.posts.where(about_type: "Communication::Website::Post::Category").distinct.pluck(:about_id)) +
-    # website.pages.where(id: website.blocks.posts.where(about_type: "Communication::Website::Page").distinct.pluck(:about_id)) +
-    # website.posts.where(id: website.blocks.posts.where(about_type: "Communication::Website::Post").distinct.pluck(:about_id))
+    # website.post_categories.where(id: website.blocks.template_posts.where(about_type: "Communication::Website::Post::Category").distinct.pluck(:about_id)) +
+    # website.pages.where(id: website.blocks.template_posts.where(about_type: "Communication::Website::Page").distinct.pluck(:about_id)) +
+    # website.posts.where(id: website.blocks.template_posts.where(about_type: "Communication::Website::Post").distinct.pluck(:about_id))
   end
 end
diff --git a/app/models/communication/website/post/category.rb b/app/models/communication/website/post/category.rb
index ec43b625fb32680a1a419fcada8e7156810705b4..2b5473c445be634e3ba6114d86350b1ddadccc21 100644
--- a/app/models/communication/website/post/category.rb
+++ b/app/models/communication/website/post/category.rb
@@ -86,12 +86,12 @@ class Communication::Website::Post::Category < ApplicationRecord
 
   # Same as the Post object
   def abouts_with_post_block
-    website.blocks.posts.collect(&:about)
+    website.blocks.template_posts.collect(&:about)
     # Potentiel gain de performance (25%)
     # Méthode collect : X abouts = X requêtes
     # Méthode ci-dessous : X abouts = 6 requêtes
-    # website.post_categories.where(id: website.blocks.posts.where(about_type: "Communication::Website::Post::Category").distinct.pluck(:about_id)) +
-    # website.pages.where(id: website.blocks.posts.where(about_type: "Communication::Website::Page").distinct.pluck(:about_id)) +
-    # website.posts.where(id: website.blocks.posts.where(about_type: "Communication::Website::Post").distinct.pluck(:about_id))
+    # website.post_categories.where(id: website.blocks.template_posts.where(about_type: "Communication::Website::Post::Category").distinct.pluck(:about_id)) +
+    # website.pages.where(id: website.blocks.template_posts.where(about_type: "Communication::Website::Page").distinct.pluck(:about_id)) +
+    # website.posts.where(id: website.blocks.template_posts.where(about_type: "Communication::Website::Post").distinct.pluck(:about_id))
   end
 end
diff --git a/app/models/communication/website/with_security.rb b/app/models/communication/website/with_security.rb
index 9803230b6abf77d55cb78ed44a009169cd4aa251..b374f50b10e81c721e421f48360b18996a0ea004 100644
--- a/app/models/communication/website/with_security.rb
+++ b/app/models/communication/website/with_security.rb
@@ -35,7 +35,7 @@ module Communication::Website::WithSecurity
 
   def allowed_domains_from_blocks_video(blocks)
     list = []
-    blocks.where(template_kind: :video).each do |block|
+    blocks.template_video.each do |block|
       video_url = block.template.url
       next unless video_url.present?
       list.concat Video::Provider.find(video_url).csp_domains
@@ -52,7 +52,7 @@ module Communication::Website::WithSecurity
 
   def allowed_domains_from_blocks_embed(blocks)
     list = []
-    blocks.where(template_kind: :embed).published.each do |block|
+    blocks.template_embed.published.each do |block|
       code = block.template.code
       # https://stackoverflow.com/questions/25095176/extracting-all-urls-from-a-page-using-ruby
       code.scan(/[[:lower:]]+:\/\/[^\s"]+/).each do |url|
diff --git a/app/models/concerns/as_localization.rb b/app/models/concerns/as_localization.rb
index 0548f2b4397f28b77d1506326a26bd7d30576429..545917402324dd8fcbe3c440e8e446ffc553f6da 100644
--- a/app/models/concerns/as_localization.rb
+++ b/app/models/concerns/as_localization.rb
@@ -92,13 +92,9 @@ module AsLocalization
   end
 
   def localize_contents!(localization)
-    blocks.without_heading.ordered.each do |block|
+    blocks.ordered.each do |block|
       block.localize_for!(localization)
     end
-
-    headings.root.ordered.each do |heading|
-      heading.localize_for!(localization)
-    end
   end
 
   # Utility method to duplicate attachments
diff --git a/app/models/concerns/contentful.rb b/app/models/concerns/contentful.rb
index 1e01c5b123c2ad48838736401468d6c680032cf5..27f966fa7bcfb32d2d8be2944a81ec0b03446e8c 100644
--- a/app/models/concerns/contentful.rb
+++ b/app/models/concerns/contentful.rb
@@ -1,23 +1,15 @@
 module Contentful
   extend ActiveSupport::Concern
 
+  LARGE_NUMBER_OF_BLOCKS = 5
+
   included do
     has_many :blocks, as: :about, class_name: 'Communication::Block', dependent: :destroy
     has_many :headings, as: :about, class_name: 'Communication::Block::Heading', dependent: :destroy
   end
 
   def contents
-    unless @contents
-      @contents = []
-      blocks.without_heading.published.ordered.each do |block|
-        @contents << block
-      end
-      headings.ordered.each do |heading|
-        @contents << heading
-        @contents.concat heading.blocks
-      end
-    end
-    @contents
+    @contents ||= blocks.published.ordered
   end
 
   def contents_full_text
@@ -25,19 +17,19 @@ module Contentful
   end
 
   def contents_dependencies
-    blocks + headings
+    blocks
   end
 
-  # Basic rule is: TOC if 2 titles or more
-  def show_toc?
-    headings.many?
+  def large_number_of_blocks?
+    blocks.count >= LARGE_NUMBER_OF_BLOCKS
   end
 
-  def generate_heading(title)
-    headings.create(university: university, title: title)
+  # Basic rule is: TOC if 2 titles or more
+  def show_toc?
+    blocks.template_title.published.many?
   end
 
-  def generate_block(heading, kind, data)
-    blocks.create(university: university, heading: heading, template_kind: kind, data: data.to_json)
+  def generate_block(kind, title: nil, data: {})
+    blocks.create(university: university, template_kind: kind, title: title, data: data.to_json)
   end
 end
diff --git a/app/models/concerns/duplicable.rb b/app/models/concerns/duplicable.rb
index 3bd842318d5c3835db858e443885a32a6a88d608..b89ea32d048bd3bbe1cf50dd6c5d4eed2cb73597 100644
--- a/app/models/concerns/duplicable.rb
+++ b/app/models/concerns/duplicable.rb
@@ -39,34 +39,15 @@ module Duplicable
 
   def duplicate_blocks(from, to)
     return unless from.respond_to?(:contents)
-    from.blocks.without_heading.ordered.each do |block|
+    from.blocks.ordered.each do |block|
       duplicate_block(to, block)
     end
-    from.headings.root.ordered.each do |heading|
-      duplicate_heading(to, heading)
-    end
   end
 
-  def duplicate_block(instance, block, heading_id = nil)
+  def duplicate_block(instance, block)
     duplicated_block = block.duplicate
     duplicated_block.about = instance
     duplicated_block.position = block.position
-    duplicated_block.heading_id = heading_id
     duplicated_block.save
   end
-
-  def duplicate_heading(instance, heading, parent_id = nil)
-    duplicated_heading = heading.duplicate
-    duplicated_heading.about = instance
-    duplicated_heading.position = heading.position
-    duplicated_heading.parent_id = parent_id
-    duplicated_heading.save
-
-    heading.blocks.ordered.each do |block|
-      duplicate_block(instance, block, duplicated_heading.id)
-    end
-    heading.children.ordered.each do |child|
-      duplicate_heading(instance, child, duplicated_heading.id)
-    end
-  end
 end
\ No newline at end of file
diff --git a/app/models/education/diploma/localization.rb b/app/models/education/diploma/localization.rb
index 350ed7fb565a15f399b43b93b6ef0d165ccf1825..0749e321cc3a4d05c8a12181fd468dcf3006aa71 100644
--- a/app/models/education/diploma/localization.rb
+++ b/app/models/education/diploma/localization.rb
@@ -95,7 +95,7 @@ class Education::Diploma::Localization < ApplicationRecord
   end
 
   def backlinks_blocks(website)
-    website.blocks.diplomas
+    website.blocks.template_diplomas
   end
 
 end
diff --git a/app/models/university/organization/localization.rb b/app/models/university/organization/localization.rb
index 9fad776ed1f75a22d8080a69855be11434c77cb3..9a471a2cd32914f93327822883bdefa910d3d1ce 100644
--- a/app/models/university/organization/localization.rb
+++ b/app/models/university/organization/localization.rb
@@ -97,7 +97,7 @@ class University::Organization::Localization < ApplicationRecord
   end
 
   def backlinks_blocks(website)
-    website.blocks.organizations
+    website.blocks.template_organizations
   end
 
 end
diff --git a/app/models/university/person/localization.rb b/app/models/university/person/localization.rb
index 8f9ac29180c7930073fc6bf1fd27b411d060ca53..2da26591fa59eea2328e44eba343ca28d4cd7f70 100644
--- a/app/models/university/person/localization.rb
+++ b/app/models/university/person/localization.rb
@@ -117,7 +117,7 @@ class University::Person::Localization < ApplicationRecord
   protected
 
   def backlinks_blocks(website)
-    website.blocks.persons
+    website.blocks.template_persons
   end
 
   def prepare_name
diff --git a/app/services/importers/api/osuny/communication/website/base.rb b/app/services/importers/api/osuny/communication/website/base.rb
index 2ee0ccdfc29c99c749f74e591e957aa9deafb438..b91f54993d102ec18bdeb42b8cfe1697a4cba147 100644
--- a/app/services/importers/api/osuny/communication/website/base.rb
+++ b/app/services/importers/api/osuny/communication/website/base.rb
@@ -38,27 +38,16 @@ class Importers::Api::Osuny::Communication::Website::Base
     blocks.each do |b|
       migration_identifier = b[:migration_identifier]
       template_kind = b[:template_kind]
-      if template_kind == 'title'
-        heading = object.headings
-                        .where(
-                          university: university,
-                          migration_identifier: migration_identifier
-                        )
-                        .first_or_initialize
-        heading.title = b[:title]
-        heading.save
-      else
-        block = object.blocks
-                      .where(
-                        university: university,
-                        migration_identifier: migration_identifier,
-                        template_kind: template_kind
-                      )
-                      .first_or_initialize
-        block.heading = heading
-        block.data = block.template.data.merge b[:data]
-        block.save
-      end
+      block = object.blocks
+                    .where(
+                      university: university,
+                      migration_identifier: migration_identifier,
+                      template_kind: template_kind
+                    )
+                    .first_or_initialize
+      block.title = b[:title]
+      block.data = block.template.data.merge b[:data]
+      block.save
     end
   end
 end
\ No newline at end of file
diff --git a/app/services/migrations/fix_key_figures.rb b/app/services/migrations/fix_key_figures.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6d9170a2ee1686bc149427f7c7c6d76a5c5ad817
--- /dev/null
+++ b/app/services/migrations/fix_key_figures.rb
@@ -0,0 +1,40 @@
+module Migrations
+  class FixKeyFigures
+    def self.migrate
+      begin
+        suspend_callbacks
+        new.migrate
+      ensure
+        resume_callbacks
+      end
+    end
+
+    def migrate
+      Communication::Block.template_key_figures.each do |block|
+        block.template.elements.each do |element|
+          next if element.description.blank?
+          puts element.description
+          string = ActionController::Base.helpers.strip_tags(element.description.to_s)
+          puts "-> #{string}"
+          element.description = string
+        end
+        block.data = block.template.data
+        block.save
+      end
+    end
+
+    protected
+
+    def self.suspend_callbacks
+      Communication::Block.skip_callback :save, :after, :connect_and_sync_direct_sources
+      Communication::Block.skip_callback :save, :after, :clean_websites_if_necessary
+      Communication::Block.skip_callback :save, :after, :touch_about
+    end
+
+    def self.resume_callbacks
+      Communication::Block.set_callback :save, :after, :connect_and_sync_direct_sources
+      Communication::Block.set_callback :save, :after, :clean_websites_if_necessary
+      Communication::Block.set_callback :save, :after, :touch_about
+    end
+  end
+end
\ No newline at end of file
diff --git a/app/services/migrations/turn_to_html.rb b/app/services/migrations/turn_to_html.rb
deleted file mode 100644
index 7ea4b3e393a091575ab38160d73b8c0c25686c85..0000000000000000000000000000000000000000
--- a/app/services/migrations/turn_to_html.rb
+++ /dev/null
@@ -1,195 +0,0 @@
-module Migrations
-  class TurnToHtml
-    def self.migrate
-      begin
-        suspend_callbacks
-        new.migrate
-      ensure
-        resume_callbacks
-      end
-    end
-
-    def migrate
-      migrate_summaries
-      migrate_definitions_blocks
-      migrate_embed_blocks
-      migrate_features_blocks
-      migrate_gallery_blocks
-      migrate_key_figures_blocks
-      migrate_links_blocks
-      migrate_sound_blocks
-      migrate_testimonials_blocks
-      migrate_timeline_blocks
-      migrate_video_blocks
-    end
-
-    protected
-
-    def self.suspend_callbacks
-      Communication::Block.skip_callback :save, :after, :connect_and_sync_direct_sources
-      Communication::Block.skip_callback :save, :after, :clean_websites_if_necessary
-      Communication::Block.skip_callback :save, :after, :touch_about
-    end
-
-    def self.resume_callbacks
-      Communication::Block.set_callback :save, :after, :connect_and_sync_direct_sources
-      Communication::Block.set_callback :save, :after, :clean_websites_if_necessary
-      Communication::Block.set_callback :save, :after, :touch_about
-    end
-
-    CLASSES_WITH_SUMMARIES = [
-      Administration::Location::Localization,
-      Communication::Extranet::Post::Localization,
-      Communication::Website::Agenda::Category::Localization,
-      Communication::Website::Agenda::Event::Localization,
-      Communication::Website::Page::Localization,
-      Communication::Website::Portfolio::Category::Localization,
-      Communication::Website::Portfolio::Project::Localization,
-      Communication::Website::Post::Localization,
-      Communication::Website::Post::Category::Localization,
-      Education::Diploma::Localization,
-      Education::Program::Localization,
-      Research::Journal::Localization,
-      Research::Journal::Paper::Localization,
-      Research::Journal::Volume::Localization,
-      Research::Laboratory::Axis::Localization,
-      University::Organization::Localization,
-      University::Person::Localization
-    ]
-
-    def migrate_summaries
-      CLASSES_WITH_SUMMARIES.each do |klass|
-        klass.where.not(summary: [nil, '']).find_each do |object|
-          next if object.summary.start_with?('<p>')
-          object.update_column :summary, "<p>#{object.summary}</p>"
-        end
-      end
-    end
-
-    def migrate_definitions_blocks
-      Communication::Block.definitions.each do |block|
-        block.template.elements.each do |element|
-          next if element.description.blank?
-          next if element.description.start_with?('<p>')
-          element.description = "<p>#{element.description}</p>"
-        end
-        block.data = block.template.data
-        block.save
-      end
-    end
-
-    def migrate_embed_blocks
-      Communication::Block.embed.each do |block|
-        next if block.template.transcription.blank?
-        next if block.template.transcription.start_with?('<p>')
-        block.template.transcription = "<p>#{block.template.transcription}</p>"
-        block.data = block.template.data
-        block.save
-      end
-    end
-
-    def migrate_features_blocks
-      Communication::Block.features.each do |block|
-        block.template.elements.each do |element|
-          next if element.description.blank? && element.credit.blank?
-          unless element.description.start_with?('<p>')
-            element.description = "<p>#{element.description}</p>"
-          end
-          unless element.credit.start_with?('<p>')
-            element.credit = "<p>#{element.credit}</p>"
-          end
-        end
-        block.data = block.template.data
-        block.save
-      end
-    end
-
-    def migrate_gallery_blocks
-      Communication::Block.gallery.each do |block|
-        block.template.elements.each do |element|
-          next if element.text.blank? && element.credit.blank?
-          unless element.text.start_with?('<p>')
-            element.text = "<p>#{element.text}</p>"
-          end
-          unless element.credit.start_with?('<p>')
-            element.credit = "<p>#{element.credit}</p>"
-          end
-        end
-        block.data = block.template.data
-        block.save
-      end
-    end
-
-    def migrate_key_figures_blocks
-      Communication::Block.key_figures.each do |block|
-        block.template.elements.each do |element|
-          next if element.description.blank?
-          next if element.description.start_with?('<p>')
-          element.description = "<p>#{element.description}</p>"
-        end
-        block.data = block.template.data
-        block.save
-      end
-    end
-
-    def migrate_links_blocks
-      Communication::Block.links.each do |block|
-        if block.template.description.present? && !block.template.description.start_with?('<p>')
-          block.template.description = "<p>#{block.template.description}</p>"
-        end
-        block.template.elements.each do |element|
-          next if element.description.blank?
-          next if element.description.start_with?('<p>')
-          element.description = "<p>#{element.description}</p>"
-        end
-        block.data = block.template.data
-        block.save
-      end
-    end
-
-    def migrate_sound_blocks
-      Communication::Block.sound.each do |block|
-        next if block.template.transcription.blank?
-        next if block.template.transcription.start_with?('<p>')
-        block.template.transcription = "<p>#{block.template.transcription}</p>"
-        block.data = block.template.data
-        block.save
-      end
-    end
-
-    def migrate_testimonials_blocks
-      Communication::Block.testimonials.each do |block|
-        block.template.elements.each do |element|
-          next if element.text.blank?
-          next if element.text.start_with?('<p>')
-          element.text = "<p>#{element.text}</p>"
-        end
-        block.data = block.template.data
-        block.save
-      end
-    end
-
-    def migrate_timeline_blocks
-      Communication::Block.timeline.each do |block|
-        block.template.elements.each do |element|
-          next if element.text.blank?
-          next if element.text.start_with?('<p>')
-          element.text = "<p>#{element.text}</p>"
-        end
-        block.data = block.template.data
-        block.save
-      end
-    end
-
-    def migrate_video_blocks
-      Communication::Block.video.each do |block|
-        next if block.template.transcription.blank?
-        next if block.template.transcription.start_with?('<p>')
-        block.template.transcription = "<p>#{block.template.transcription}</p>"
-        block.data = block.template.data
-        block.save
-      end
-    end
-
-  end
-end
\ No newline at end of file
diff --git a/app/views/admin/communication/blocks/_add.html.erb b/app/views/admin/communication/blocks/_add.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..1be3f510a2234a296dc2665e26238034390dd923
--- /dev/null
+++ b/app/views/admin/communication/blocks/_add.html.erb
@@ -0,0 +1,7 @@
+<div class="content-editor__actions row my-4">
+  <div class="offset-lg-4 col-lg-8 col-xxl-6">
+    <%= link_to t('admin.communication.blocks.add'),
+                new_admin_communication_block_path(about_id: about.id, about_type: about.class.name),
+                class: 'btn btn-lg btn-dark js-content-editor__add-block' if can? :create, Communication::Block %>
+  </div>
+</div>
diff --git a/app/views/admin/communication/blocks/_block.html.erb b/app/views/admin/communication/blocks/_block.html.erb
index 9b5efa2902b56d306a6797756b24ba6d534b040c..bee446048756c6be256198563921ff27c38132b6 100644
--- a/app/views/admin/communication/blocks/_block.html.erb
+++ b/app/views/admin/communication/blocks/_block.html.erb
@@ -1,6 +1,5 @@
 <div  id="block-<%= block.id %>"
       class=" content-editor__elements__element
-              content-editor__elements__element--block
               js-content-editor-element"
       data-id="<%= block.id %>"
       data-kind="block">
@@ -46,8 +45,7 @@
         <div class="content-editor__elements__preview
                     content-editor__elements__preview--<%= block.template_kind %>">
           <div id="snippet-<%= block.id %>">
-            <%= render "admin/communication/blocks/title", block: block %>
-            <%= render "admin/communication/blocks/templates/#{block.template_kind}/snippet", block: block %>
+            <%= render 'admin/communication/blocks/block_snippet', block: block %>
           </div>
         </div>
         <%= render 'admin/application/a11y/status', about: block %>
diff --git a/app/views/admin/communication/blocks/_block_snippet.html.erb b/app/views/admin/communication/blocks/_block_snippet.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..4765d50452313352f5ada330ba62bad4ee3c1078
--- /dev/null
+++ b/app/views/admin/communication/blocks/_block_snippet.html.erb
@@ -0,0 +1,2 @@
+<%= render "admin/communication/blocks/title", block: block unless block.template_title? %>
+<%= render "admin/communication/blocks/templates/#{block.template_kind}/snippet", block: block %>
\ No newline at end of file
diff --git a/app/views/admin/communication/blocks/_static.html.erb b/app/views/admin/communication/blocks/_static.html.erb
index 82681b409078ba7afde8c4b0457b3ffcb46d1406..a16e67127a2a9a60ccf7b8e09d18ea9b92618e51 100644
--- a/app/views/admin/communication/blocks/_static.html.erb
+++ b/app/views/admin/communication/blocks/_static.html.erb
@@ -5,7 +5,9 @@ should_render_data = block.data && block.data.present?
   - kind: block
     template: <%= block.template_kind %>
     title: >-
-      <%= prepare_text_for_static block.template.heading_title %>
+      <%= prepare_text_for_static block.title %>
+    slug: >-
+      <%= block.slug %>
     ranks:
       self: <%= block.heading_rank_self %>
 <% if block.heading_children? %>
diff --git a/app/views/admin/communication/blocks/_title.html.erb b/app/views/admin/communication/blocks/_title.html.erb
index 21b8ae5b2bb14b6106d694dee88e3b3670edf0cc..3d9c07e0e98e93652b0b60c760ae9b78c1ee2f3d 100644
--- a/app/views/admin/communication/blocks/_title.html.erb
+++ b/app/views/admin/communication/blocks/_title.html.erb
@@ -1,3 +1,3 @@
 <% if block.title.present? %>
   <div><%= osuny_label block.title %></div>
-<% end %>
\ No newline at end of file
+<% end %>
diff --git a/app/views/admin/communication/blocks/edit.html.erb b/app/views/admin/communication/blocks/edit.html.erb
index 1625c11a32fd389af815ad47d396ba4820f44519..c4e0a81f4b9d57b3e7f3c84e2b6fa0a30982a763 100644
--- a/app/views/admin/communication/blocks/edit.html.erb
+++ b/app/views/admin/communication/blocks/edit.html.erb
@@ -35,14 +35,33 @@
     <div>
       <a  class="text-muted small"
           data-bs-toggle="collapse" 
-          href="#htmlClass" 
+          href="#advanced" 
           role="button" 
           aria-expanded="false" 
-          aria-controls="htmlClass">
+          aria-controls="advanced">
         <i class="bi bi-gear-fill"></i>
         <%= t('admin.advanced_settings') %>
       </a>
-      <div class="collapse mt-3" id="htmlClass">
+      <div class="collapse mt-3" id="advanced">
+        <% 
+        if @block.title.present? 
+          slug = @block.slug
+          about_l10n = @block.about
+          about = about_l10n.about
+          %>
+          <div class="mb-5">
+            <%= osuny_label t('admin.communication.blocks.advanced.anchor') %>
+            <pre>#<%= slug %></pre>
+            <%
+            if about.is_direct_object?
+              website = about.website
+              path = about_l10n.current_permalink_in_website(website)&.path
+              %>
+              <%= osuny_label t('admin.communication.blocks.advanced.url_with_anchor') %>
+              <pre><%= path %>#<%= slug %></pre>
+            <% end %>
+          </div>
+        <% end %>
         <%= f.input :html_class %>
         <% if @block.html_class.present? %>
           <pre><%= @block.html_class_prepared %></pre>
diff --git a/app/views/admin/communication/blocks/headings/_form.html.erb b/app/views/admin/communication/blocks/headings/_form.html.erb
deleted file mode 100644
index b3838a45a6bc07c0e93a1ca867ce8af538c990e4..0000000000000000000000000000000000000000
--- a/app/views/admin/communication/blocks/headings/_form.html.erb
+++ /dev/null
@@ -1,23 +0,0 @@
-<%
-url = heading.persisted?  ? admin_communication_heading_path(heading)
-                          : admin_communication_headings_path
-%>
-<%= simple_form_for [:admin, heading], url: url do |f| %>
-  <%= f.error_notification %>
-  <%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %>
-  <%= f.input :title %>
-  <%= f.input :about_type, as: :hidden %>
-  <%= f.input :about_id, as: :hidden %>
-  <% if heading.persisted?%>
-    <% content_for :action_bar_left do %>
-      <%= link_to t('delete'),
-                  admin_communication_heading_path(heading), 
-                  method: :delete,
-                  data: { confirm: t('please_confirm') },
-                  class: button_classes_danger if can? :destroy, heading %>
-    <% end %>
-  <% end %>
-  <% content_for :action_bar_right do %>
-    <%= submit f %>
-  <% end %>
-<% end %>
diff --git a/app/views/admin/communication/blocks/headings/_heading-for-mode-structure.html.erb b/app/views/admin/communication/blocks/headings/_heading-for-mode-structure.html.erb
deleted file mode 100644
index 029ab25ed4138bf0e26e33c7f82f4ab329e2cd17..0000000000000000000000000000000000000000
--- a/app/views/admin/communication/blocks/headings/_heading-for-mode-structure.html.erb
+++ /dev/null
@@ -1,20 +0,0 @@
-<div  class=" content-editor__elements__element
-              content-editor__elements__element--heading
-              content-editor__elements__element--mode-structure
-              js-content-editor-element
-              mt-3"
-      data-id="<%= heading.id %>"
-      data-level="<%= heading.level %>"
-      data-kind="heading">
-  <div  class="content-editor__elements__handle"
-        style="padding-left: <%= (heading.level - Communication::Block::Heading::DEFAULT_LEVEL) * 48 %>px">
-    <span class="h4"><%= heading %></span>
-    <i class="<%= Icon::SORT %>"></i>
-    <p class="small text-muted">
-      <%= t('admin.communication.contents.blocks.quantity', count: heading.blocks.count) %>
-    </p>
-  </div>
-</div>
-<% heading.children.ordered.each do |child| %>
-  <%= render 'admin/communication/blocks/headings/heading', heading: child %>
-<% end %>
diff --git a/app/views/admin/communication/blocks/headings/_heading.html.erb b/app/views/admin/communication/blocks/headings/_heading.html.erb
deleted file mode 100644
index 0a3a0b4b42336f72bcb3901d60617fd71eeda3da..0000000000000000000000000000000000000000
--- a/app/views/admin/communication/blocks/headings/_heading.html.erb
+++ /dev/null
@@ -1,41 +0,0 @@
-<div  class=" content-editor__elements__element
-              content-editor__elements__element--heading
-              js-content-editor-element"
-      data-id="<%= heading.id %>"
-      data-level="<%= heading.level %>"
-      data-kind="heading">
-  <div class="row">
-    <div class="col-lg-4 text-lg-end pt-2">
-      <%= osuny_label Communication::Block::Heading.human_attribute_name(:title) %>
-    </div>
-    <div class="col-lg-8 col-xxl-6">
-      <div  class=" card
-                    card-body
-                    px-5
-                    border-bottom
-                    border-light">
-        <div class="text-end">
-          <% if can?(:update, heading) %>
-            <span class=" content-editor__elements__handle
-                          content-editor__elements__element--hover">
-              <span class="handle">
-                <i class="<%= Icon::SORT %>"></i>
-                <span class="small"><%= t 'move' %></span>
-              </span>
-            </span>
-          <% end %>
-          <%= link_to t('edit'),
-                      edit_admin_communication_heading_path(heading),
-                      class: 'action ms-2'%>
-        </div>
-        <h2 class="h3 mb-0"><%= heading %></h2>
-      </div>
-    </div>
-  </div>
-</div>
-<% heading.blocks.ordered.each do |block| %>
-  <%= render 'admin/communication/blocks/block', block: block %>
-<% end %>
-<% heading.children.ordered.each do |child| %>
-  <%= render 'admin/communication/blocks/headings/heading', heading: child %>
-<% end %>
diff --git a/app/views/admin/communication/blocks/headings/_show.html.erb b/app/views/admin/communication/blocks/headings/_show.html.erb
deleted file mode 100644
index 7268a2abc351412c5bfb4421a0533f0e76c2fd56..0000000000000000000000000000000000000000
--- a/app/views/admin/communication/blocks/headings/_show.html.erb
+++ /dev/null
@@ -1,9 +0,0 @@
-<div class="heading container">
-  <h2><%= heading %></h2>
-</div>
-<% heading.blocks.published.ordered.each do |block| %>
-  <%= render "admin/communication/blocks/templates/#{block.template_kind}/show", block: block, strip_javascript: strip_javascript %>
-<% end %>
-<% heading.children.ordered.each do |child| %>
-  <%= render 'admin/communication/blocks/headings/show', heading: child, strip_javascript: strip_javascript %>
-<% end %>
diff --git a/app/views/admin/communication/blocks/headings/_static.html.erb b/app/views/admin/communication/blocks/headings/_static.html.erb
deleted file mode 100644
index c1194af8be610228fe6588acb8775d305fda5079..0000000000000000000000000000000000000000
--- a/app/views/admin/communication/blocks/headings/_static.html.erb
+++ /dev/null
@@ -1,13 +0,0 @@
-  - kind: heading
-    title: >-
-      <%= prepare_text_for_static heading.title %>
-    slug: "<%= heading.slug %>"
-    position: <%= heading.position %>
-    rank: <%= heading.level %>
-<% heading.blocks.published.ordered.each do |block| %>
-<%= render 'admin/communication/blocks/static', block: block %>
-<% end %>
-<% children = heading.children %>
-<% children.ordered.each do |child| %>
-<%= render 'admin/communication/blocks/headings/static', heading: child %>
-<% end %>
diff --git a/app/views/admin/communication/blocks/headings/edit.html.erb b/app/views/admin/communication/blocks/headings/edit.html.erb
deleted file mode 100644
index fa54c115a75d21f60c197f12f00c0390f74ad936..0000000000000000000000000000000000000000
--- a/app/views/admin/communication/blocks/headings/edit.html.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-<% content_for :title, @heading %>
-<%= render 'form', heading: @heading %>
diff --git a/app/views/admin/communication/blocks/headings/new.html.erb b/app/views/admin/communication/blocks/headings/new.html.erb
deleted file mode 100644
index 43b08afec5cb3b4d0fadc057436bc1260e4aa7f7..0000000000000000000000000000000000000000
--- a/app/views/admin/communication/blocks/headings/new.html.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-<% content_for :title, t('admin.communication.blocks.headings.add') %>
-<%= render 'form', heading: @heading %>
diff --git a/app/views/admin/communication/blocks/show.html.erb b/app/views/admin/communication/blocks/show.html.erb
index 4afc8a2c964227da6211e31285e94b47ce919486..791f8e108d126e964d470ddbe33e8b4928595e81 100644
--- a/app/views/admin/communication/blocks/show.html.erb
+++ b/app/views/admin/communication/blocks/show.html.erb
@@ -1,2 +1 @@
-<%= render "admin/communication/blocks/title", block: @block %>
-<%= render "admin/communication/blocks/templates/#{@block.template_kind}/snippet", block: @block %>
\ No newline at end of file
+<%= render 'admin/communication/blocks/block_snippet', block: @block %>
\ No newline at end of file
diff --git a/app/views/admin/communication/blocks/show.json.jbuilder b/app/views/admin/communication/blocks/show.json.jbuilder
index 981ebbe3fe7ed849c9347ded971a1b225a6b09ea..6611e2e731bafd512b3cd83e111e68d9076c2c2b 100644
--- a/app/views/admin/communication/blocks/show.json.jbuilder
+++ b/app/views/admin/communication/blocks/show.json.jbuilder
@@ -3,7 +3,3 @@ json.extract! @block,
               :published,
               :position,
               :data
-heading = @block.heading
-json.heading do
-  json.extract! heading, :id, :title
-end if heading
\ No newline at end of file
diff --git a/app/views/admin/communication/blocks/templates/agenda/_snippet.html.erb b/app/views/admin/communication/blocks/templates/agenda/_snippet.html.erb
index 6f0270aa3d22a29cde86d3af211d94f8bc637352..4e5c4c856f4b0d688e489f9d2d96043818c367a8 100644
--- a/app/views/admin/communication/blocks/templates/agenda/_snippet.html.erb
+++ b/app/views/admin/communication/blocks/templates/agenda/_snippet.html.erb
@@ -1,15 +1,33 @@
-<p class="mb-0">
-  <% if block.template.mode == 'categories' %>
+<% if block.template.mode == 'categories' %>
+  <p>
     <%= @website.agenda_categories
                 .map { |category| category.to_s_in(block.language) }
                 .join(', ') %>
+
+  </p>
+<% else %>
+  <% if block.template.selected_events.any? %>
+    <div class="row">
+      <%
+      block.template.selected_events.each do |event|
+        l10n = event.localization_for(block.about.language)
+        next if l10n.nil?
+        %>
+        <div class="col-sm-6 mb-3">
+          <%= kamifusen_tag l10n.featured_image, class: 'img-fluid mb-2' if l10n.featured_image.attached? %>
+          <p class="mb-0">
+            <%= l10n.title %>
+            <% if l10n.subtitle.present? %>
+              <br><%= l10n.subtitle %>
+            <% end %>
+          </p>
+          <p class="small">
+            <%= render 'admin/communication/websites/agenda/events/dates', event: event, l10n: l10n, detailed: false %>
+          </p>
+        </div>
+      <% end %>
+    </div>
   <% else %>
-    <% if block.template.selected_events.any? %>
-      <%= block.template.selected_events
-                        .map { |event| event.to_s_in(block.language) }
-                        .join(', ') %>
-    <% else %>
-      <%= block.template.no_event_message %>
-    <% end %>
+   <p><%= block.template.no_event_message %></p>
   <% end %>
-</p>
\ No newline at end of file
+<% end %>
\ No newline at end of file
diff --git a/app/views/admin/communication/blocks/templates/organizations/_snippet.html.erb b/app/views/admin/communication/blocks/templates/organizations/_snippet.html.erb
index ee2bc745e8050eb2f8572500261483e5b211f55e..148c7df4b4a5a8ea921941595e6b5d0e322a5f80 100644
--- a/app/views/admin/communication/blocks/templates/organizations/_snippet.html.erb
+++ b/app/views/admin/communication/blocks/templates/organizations/_snippet.html.erb
@@ -1,7 +1,7 @@
 <div class="organizations-list">
   <% block.template.selected_elements.each do |element| %>
     <% next if element.best_name.blank? %>
-    <div class="me-2 bg-light p-2">
+    <div class="me-2 mb-2 bg-light p-2">
       <% if element.best_logo_blob %>
         <%= kamifusen_tag element.best_logo_blob %>
       <% else %>
diff --git a/app/views/admin/communication/blocks/templates/pages/_snippet.html.erb b/app/views/admin/communication/blocks/templates/pages/_snippet.html.erb
index c1794f60d7243032358981bec9daf41623f84e2f..5615ec60ce2a99bce0d80d62ee966a0012873927 100644
--- a/app/views/admin/communication/blocks/templates/pages/_snippet.html.erb
+++ b/app/views/admin/communication/blocks/templates/pages/_snippet.html.erb
@@ -5,9 +5,7 @@
     next if page_l10n.nil? 
     %>
     <div class="col-sm-6 mb-3">
-      <% if page_l10n.featured_image.attached? %>
-        <%= kamifusen_tag page_l10n.featured_image %>
-      <% end %>
+      <%= kamifusen_tag page_l10n.featured_image, class: 'img-fluid mb-2' if page_l10n.featured_image.attached? %>
       <p><%= page_l10n.to_s %></p>
     </div>
   <% end %>
diff --git a/app/views/admin/communication/blocks/templates/posts/_snippet.html.erb b/app/views/admin/communication/blocks/templates/posts/_snippet.html.erb
index bfbf93ddde96ed90bfff4d8517276e9c864a3bc5..affea60fee5c525980fd70e084043c8633f3efda 100644
--- a/app/views/admin/communication/blocks/templates/posts/_snippet.html.erb
+++ b/app/views/admin/communication/blocks/templates/posts/_snippet.html.erb
@@ -8,9 +8,11 @@
     l10n = post.localization_for(block.language)
     next if l10n.nil?
     %>
-    <div class="col-lg-6 mb-4">
-      <%= kamifusen_tag l10n.featured_image, width: 300, class: 'img-fluid mb-2' if l10n.featured_image.attached? %>
-      <p><%= post.to_s_in(current_language) %></p>
+    <div class="col-lg-6">
+      <article class="mb-4">
+        <%= kamifusen_tag l10n.featured_image, width: 300, class: 'img-fluid mb-2' if l10n.featured_image.attached? %>
+        <p><%= post.to_s_in(current_language) %></p>
+      </article>
     </div>
   <% end %>
   </div>
diff --git a/app/views/admin/communication/blocks/templates/title/_edit.html.erb b/app/views/admin/communication/blocks/templates/title/_edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..2ed091c76b433f1f5115aba5e3bcb3abcead580e
--- /dev/null
+++ b/app/views/admin/communication/blocks/templates/title/_edit.html.erb
@@ -0,0 +1 @@
+<%= block_component_edit block, :layout %>
diff --git a/app/views/admin/communication/blocks/templates/title/_show.html.erb b/app/views/admin/communication/blocks/templates/title/_show.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..5772add9b1343db02b2627e4dc726fc5c3fcf252
--- /dev/null
+++ b/app/views/admin/communication/blocks/templates/title/_show.html.erb
@@ -0,0 +1,7 @@
+<div class="<%= block_html_class(block) %>">
+  <div class="container">
+    <div class="block-content">
+      <h2><%= block.title %></h2>
+    </div>
+  </div>
+</div>
\ No newline at end of file
diff --git a/app/views/admin/communication/blocks/templates/title/_snippet.html.erb b/app/views/admin/communication/blocks/templates/title/_snippet.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..595278545d7e3a77eac30ab79f51d19ed265785c
--- /dev/null
+++ b/app/views/admin/communication/blocks/templates/title/_snippet.html.erb
@@ -0,0 +1 @@
+<h2 class="h3 mb-3"><%= block.title %></h2>
\ No newline at end of file
diff --git a/app/views/admin/communication/blocks/templates/title/_static.html.erb b/app/views/admin/communication/blocks/templates/title/_static.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..adc1134f88e03aa38df30730c3999ee6db681333
--- /dev/null
+++ b/app/views/admin/communication/blocks/templates/title/_static.html.erb
@@ -0,0 +1 @@
+      layout: <%= block.template.layout %>
diff --git a/app/views/admin/communication/contents/_editor.html.erb b/app/views/admin/communication/contents/_editor.html.erb
index ce829950e00a0942e1da1c1eb35d8548c8c8c9be..d1750259c48ae7d8d7c9684577315e24109d7f7b 100644
--- a/app/views/admin/communication/contents/_editor.html.erb
+++ b/app/views/admin/communication/contents/_editor.html.erb
@@ -1,10 +1,9 @@
 <%
-mode_expert = about.headings.many?
+mode_expert = about.large_number_of_blocks?
 %>
+
 <div  class="js-content-editor mb-5"
-      data-sort-blocks-url="<%= reorder_admin_communication_blocks_path(website_id: nil, extranet_id: nil) %>"
-      data-sort-headings-url="<%= reorder_admin_communication_headings_path(website_id: nil, extranet_id: nil) %>"
-      >
+      data-sort-url="<%= reorder_admin_communication_blocks_path(website_id: nil, extranet_id: nil) %>">
   <% if mode_expert %>
     <div class="row">
       <div class="offset-lg-4 col-lg-8 col-xxl-6">
diff --git a/app/views/admin/communication/contents/_show.html.erb b/app/views/admin/communication/contents/_show.html.erb
index 3af7a1e7daf3e6203335de961657465ca89206db..8e6fe84db3891fa5d84fccd00dd34cf08c4164d3 100644
--- a/app/views/admin/communication/contents/_show.html.erb
+++ b/app/views/admin/communication/contents/_show.html.erb
@@ -1,7 +1,4 @@
 <% strip_javascript ||= false %>
-<% about.blocks.without_heading.published.ordered.each do |block| %>
+<% about.blocks.published.ordered.each do |block| %>
   <%= render "admin/communication/blocks/templates/#{block.template_kind}/show", block: block, strip_javascript: strip_javascript %>
 <% end %>
-<% about.headings.root.ordered.each do |heading| %>
-  <%= render 'admin/communication/blocks/headings/show', heading: heading, strip_javascript: strip_javascript %>
-<% end %>
diff --git a/app/views/admin/communication/contents/_static.html.erb b/app/views/admin/communication/contents/_static.html.erb
index 0c8dd1a29447fb81aea9e6f27edaa6e917e4d70a..5e8632c9688ca88e59a92b5534219b743c7d1e39 100644
--- a/app/views/admin/communication/contents/_static.html.erb
+++ b/app/views/admin/communication/contents/_static.html.erb
@@ -7,9 +7,6 @@ contents_reading_time:
   text: >-
     <%= distance_of_time_in_words(0, about.contents_full_text.reading_time, locale: locale) %>
 contents:
-<% about.blocks.without_heading.published.ordered.each do |block| %>
+<% about.blocks.published.ordered.each do |block| %>
 <%= render 'admin/communication/blocks/static', block: block unless block.empty? %>
-<% end %>
-<% about.headings.root.ordered.each do |heading| %>
-<%= render 'admin/communication/blocks/headings/static', heading: heading %>
-<% end %>
+<% end %>
\ No newline at end of file
diff --git a/app/views/admin/communication/contents/_structure.html.erb b/app/views/admin/communication/contents/_structure.html.erb
index e60d2a42e5ff4b2184a2d24e3f44824f12eeb8d4..7cfe6f24297c8a99dd6d9f889b6e9b5c79a345e0 100644
--- a/app/views/admin/communication/contents/_structure.html.erb
+++ b/app/views/admin/communication/contents/_structure.html.erb
@@ -1,7 +1,22 @@
 <div  class=" content-editor__elements
               content-editor--organize
-              js-content-editor-sortable-container">
-  <% about.headings.root.ordered.each do |heading| %>
-    <%= render 'admin/communication/blocks/headings/heading-for-mode-structure', heading: heading %>
+              js-content-editor-sortable-container
+              row g-2">
+  <% about.blocks.ordered.each do |block| %>
+    <div  class=" content-editor__elements__element
+                  js-content-editor-element"
+          data-id="<%= block.id %>">
+      <div class="<%= osuny_card_classes(horizontal: true) %> content-editor__elements__handle ms-0">
+        <div class="card-body">
+          <i class="<%= Icon::SORT %> me-2"></i>
+          <%= block.template_kind_i18n %>
+        </div>
+        <div class="card-footer small text-muted">
+          <% if block.full_text.present? %>
+            <%= block.full_text.truncate_words(10) %>
+          <% end %>
+        </div>
+      </div>
+    </div>
   <% end %>
 </div>
diff --git a/app/views/admin/communication/contents/_write.html.erb b/app/views/admin/communication/contents/_write.html.erb
index 61adce7a385ba929351cef1e49b01af3a52d8b45..37286bd96087514bc31c89622b9581b4d3437862 100644
--- a/app/views/admin/communication/contents/_write.html.erb
+++ b/app/views/admin/communication/contents/_write.html.erb
@@ -1,23 +1,12 @@
+<%= render 'admin/communication/blocks/add', about: about if about.large_number_of_blocks? %>
 <div  class=" content-editor__elements
               content-editor--write
               content-editor__elements__root
               js-content-editor-sortable-container"
       id="content-editor-elements-root">
-  <% about.blocks.without_heading.ordered.each do |block| %>
+  <% about.blocks.ordered.each do |block| %>
     <%= render 'admin/communication/blocks/block', block: block %>
   <% end %>
-  <% about.headings.root.ordered.each do |heading| %>
-    <%= render 'admin/communication/blocks/headings/heading', heading: heading %>
-  <% end %>
 </div>
 <%= render "admin/communication/blocks/paste_block", about: about %>
-<div class="content-editor__actions row mt-4">
-  <div class="offset-lg-4 col-lg-8 col-xxl-6">
-    <%= link_to t('admin.communication.blocks.headings.add'),
-                new_admin_communication_heading_path(about_id: about.id, about_type: about.class.name),
-                class: 'btn btn-lg btn-dark' if can? :create, Communication::Block::Heading %>
-    <%= link_to t('admin.communication.blocks.add'),
-                new_admin_communication_block_path(about_id: about.id, about_type: about.class.name),
-                class: 'btn btn-lg btn-dark js-content-editor__add-block' if can? :create, Communication::Block %>
-  </div>
-</div>
\ No newline at end of file
+<%= render 'admin/communication/blocks/add', about: about %>
diff --git a/app/views/admin/communication/websites/pages/show/special_pages/_accessibility.html.erb b/app/views/admin/communication/websites/pages/show/special_pages/_accessibility.html.erb
index cf5bd13dfb7191cacf35dfcec13211b4e8ced3ba..8b4177c5625b14afaa375c18fe7cd4b79a2bd6ef 100644
--- a/app/views/admin/communication/websites/pages/show/special_pages/_accessibility.html.erb
+++ b/app/views/admin/communication/websites/pages/show/special_pages/_accessibility.html.erb
@@ -1,4 +1,4 @@
-<% if @l10n.blocks.none? && @l10n.headings.none? && @l10n.language.iso_code == "fr" %>
+<% if @l10n.blocks.none? && @l10n.language.iso_code == "fr" %>
   <%= link_to 'Générer le modèle de déclaration',
               generate_admin_communication_website_page_path(@page),
               method: :post,
diff --git a/app/views/admin/education/programs/show.html.erb b/app/views/admin/education/programs/show.html.erb
index 93b64e2d389dc28be5657ededacaf1d94266e07a..f9af489af048a277ea67191e984fa696ad5b0032 100644
--- a/app/views/admin/education/programs/show.html.erb
+++ b/app/views/admin/education/programs/show.html.erb
@@ -96,7 +96,7 @@
     <% if @l10n.logo.attached? %>
       <%= osuny_label Education::Program::Localization.human_attribute_name('logo') %>
       <div class="mb-5">
-        <%= kamifusen_tag @l10n.logo, class: 'img-fluid' %>
+        <%= kamifusen_tag @l10n.logo, class: 'img-fluid svg-fluid' %>
       </div>
     <% end %>
     <%= render 'admin/application/featured_image/show', about: @l10n, small: true %>
diff --git a/app/views/admin/education/programs/static.html.erb b/app/views/admin/education/programs/static.html.erb
index 25758814616c1680ed31a22575e58b1f6f6abe9e..43adf448e323543bab6a5e967ee4653bedb04444 100644
--- a/app/views/admin/education/programs/static.html.erb
+++ b/app/views/admin/education/programs/static.html.erb
@@ -33,8 +33,6 @@ registration_url: >-
   <%= @l10n.registration_url %>
 <%= render 'admin/application/meta_description/static', about: @l10n %>
 <%= render 'admin/application/summary/static', about: @l10n %>
-presentation: >-
-  <%= prepare_text_for_static @l10n.presentation %>
 position: <%= program.position %>
 <% if @l10n.parent %>
 parent:
@@ -134,6 +132,8 @@ certifications:
     certified: <%= program.qualiopi_certified %>
     text: >-
       <%= prepare_html_for_static @l10n.qualiopi_text %>
+presentation: >-
+  <%= prepare_html_for_static @l10n.presentation %>
 <%
 [
   :accessibility,
diff --git a/app/views/server/blocks/index.html.erb b/app/views/server/blocks/index.html.erb
index 2d603995195c99b4ee3c4a6f7005fcee40ee04fe..bee5fd4327c8210558af96f46e56269392e80752 100644
--- a/app/views/server/blocks/index.html.erb
+++ b/app/views/server/blocks/index.html.erb
@@ -12,7 +12,7 @@
       <% @templates.each do |template| %>
         <tr>
           <td><%= link_to t("enums.communication.block.template_kind.#{template}"), server_block_path(template) %></td>
-          <td><%= Communication::Block.send(template).count %></td>
+          <td><%= Communication::Block.send("template_#{template}").count %></td>
         </tr>
       <% end %>
     </tbody>
diff --git a/app/views/server/blocks/show.html.erb b/app/views/server/blocks/show.html.erb
index 1a3086e31db9f77190d6cac76884e2d4e9bf5985..fbca1025fa171ff28a5161334cd9389f1fd89cca 100644
--- a/app/views/server/blocks/show.html.erb
+++ b/app/views/server/blocks/show.html.erb
@@ -12,27 +12,36 @@
       </tr>
     </thead>
     <tbody>
-      <% @blocks.each do |block| %>
+      <%
+      @blocks.each do |block|
+        about = block.about
+        %>
         <tr>
           <td>
             <%= truncate "#{block}" %><br>
             <small><%= block.id %></small>
           </td>
           <td>
-            <% if block.about.present? %>
-              <%= truncate "#{block.about}" %>
+            <% if about.present?
+              begin
+                name = about.about.to_s_in(block.language)
+              rescue
+                name = about.to_s
+              end
+              %>
+              <p class="mb-0"><%= name %></p>
               <span class="badge bg-dark">
-                <%= block.about.class.model_name.human %>
+                <%= block.about_type %>
               </span>
             <% end %>
           </td>
           <td class="pe-3" width="600">
-            <%= link_to t('server_admin.blocks.see'), 
-                        "#collpase-#{block.id}", 
-                        class: 'btn btn-xs btn-light', 
-                        role: 'button', 
-                        'data-bs-toggle': 'collapse', 
-                        'aria-expanded': 'false', 
+            <%= link_to t('server_admin.blocks.see'),
+                        "#collpase-#{block.id}",
+                        class: 'btn btn-xs btn-light',
+                        role: 'button',
+                        'data-bs-toggle': 'collapse',
+                        'aria-expanded': 'false',
                         'aria-controls': "collpase-#{block.id}" %>
             <div class="collapse" id="collpase-<%= block.id %>">
               <textarea rows="10" class="form-control"><%= block.data %></textarea>
diff --git a/app/views/server/websites/show.html.erb b/app/views/server/websites/show.html.erb
index fe81f14186ae0deb5eac2135533dd40d8ed43ace..0c18dacbd17cfc52e58a90a2815e4df239b08172 100644
--- a/app/views/server/websites/show.html.erb
+++ b/app/views/server/websites/show.html.erb
@@ -91,6 +91,10 @@
               analyse_server_website_path(@website),
               method: :post,
               class: button_classes %>
+  <%= link_to 'Reconstruire (clean and rebuild)',
+              clean_and_rebuild_server_website_path(@website),
+              method: :post,
+              class: button_classes %>
   <%= link_to 'Changer d\'université',
               edit_server_website_path(@website),
               class: button_classes %>
diff --git a/app/views/showcase/home/_list.html.erb b/app/views/showcase/home/_list.html.erb
index 0ef78ef6146dadbe08917945b92473dcf7b09eb1..47a312c6c7bb68e25bdce079922239d10ea36df0 100644
--- a/app/views/showcase/home/_list.html.erb
+++ b/app/views/showcase/home/_list.html.erb
@@ -5,40 +5,42 @@
 
 <div class="row">
   <% @websites.each do |website| %>
-    <div class="col-lg-6 mb-5 pb-5">
-      <% if website.screenshot.attached? %>
-        <%= link_to website.url, target: :_blank, class: 'no-icon' do %>
-          <%= kamifusen_tag website.screenshot, 
-                            class: 'img-fluid rounded mb-3' %>
+    <div class="col-lg-6">
+      <article class="mb-5 pb-5">
+        <% if website.screenshot.attached? %>
+          <%= link_to website.url, target: :_blank, class: 'no-icon' do %>
+            <%= kamifusen_tag website.screenshot, 
+                              class: 'img-fluid rounded mb-3' %>
+          <% end %>
         <% end %>
-      <% end %>
-      <div class="row">
-        <div class="col-lg-10">
-          <h2 class="h4"><%= website.original_localization.to_s %></h2>
-          <div class="small mb-1">
-            <i class="bi bi-compass-fill me-1"></i>
-            <%= contact_link website.url, :website %>
-          </div>
-          <div class="small mb-1">
-            <i class="bi bi-github me-1"></i>
-            <%= contact_link website.repository_url, :github %>
-          </div>
-          <% website.showcase_tags.each do |tag| %>
-            <%= link_to tag, 
-                        showcase_tag_path(tag.slug), 
-                        class: 'btn btn-sm btn-outline-light text-dark rounded-pill' %>
-          <% end %>  
-        </div>
-        <% if website.university.logo.attached? %>
-          <div class="col-3 col-lg-2">
-            <div class="ratio ratio-1x1 mb-3">
-              <%= image_tag website.university.logo, 
-                            class: 'img-fluid p-3',
-                            style: 'filter: saturate(0)' %>
+        <div class="row">
+          <div class="col-lg-10">
+            <h2 class="h4"><%= website.original_localization.to_s %></h2>
+            <div class="small mb-1">
+              <i class="bi bi-compass-fill me-1"></i>
+              <%= contact_link website.url, :website %>
+            </div>
+            <div class="small mb-1">
+              <i class="bi bi-github me-1"></i>
+              <%= contact_link website.repository_url, :github %>
             </div>
+            <% website.showcase_tags.each do |tag| %>
+              <%= link_to tag, 
+                          showcase_tag_path(tag.slug), 
+                          class: 'btn btn-sm btn-outline-light text-dark rounded-pill' %>
+            <% end %>  
           </div>
-        <% end %>
-      </div>
+          <% if website.university.logo.attached? %>
+            <div class="col-3 col-lg-2">
+              <div class="ratio ratio-1x1 mb-3">
+                <%= image_tag website.university.logo, 
+                              class: 'img-fluid p-3',
+                              style: 'filter: saturate(0)' %>
+              </div>
+            </div>
+          <% end %>
+        </div>
+      </article>
     </div>
   <% end %>
 </div>
diff --git a/config/locales/communication/contents/en.yml b/config/locales/communication/contents/en.yml
index 5d4841c370054eb245bcb9c36138cd2d96e00acd..26a25cf1d9da0eb9df81d896a7fe9c397193f54f 100644
--- a/config/locales/communication/contents/en.yml
+++ b/config/locales/communication/contents/en.yml
@@ -4,6 +4,9 @@ en:
     communication:
       blocks:
         add: Add block
+        advanced:
+          anchor: Anchor
+          url_with_anchor: URL with anchor
         alerts:
           file_is_too_big: File is too big!
         categories:
@@ -662,6 +665,15 @@ en:
               vertical:
                 description: Events are shown vertically, along the page.
                 label: Vertical
+          title:
+            description: A level 2 title, that can eventually be shown folded, with its content hidden.
+            layouts:
+              classic:
+                description: The title, as simple as possible.
+                label: Classic
+              collapsed:
+                description: The title is folded, which means all the blocks after it are hidden, and there's a button to show them.
+                label: Folded
           video:
             description: An embedded video from most platforms, with the text transcription for accessibility, and no autoplay.
             edit:
@@ -702,7 +714,7 @@ en:
             zero: No block
         modes:
           structure:
-            description: In “Organize structure” mode, you do not see the blocks, only the titles. When you move a title, all the blocks follow. This is the ideal mode for organising long documents.
+            description: In “Organize structure” mode, you see only a small preview of each block. This is the ideal mode to organize long documents.
             tab: Organize structure
           write:
             description: In “Write content” mode, you can move blocks and headings anywhere you like, independently of each other. For example, you can move a heading higher up in the document without it taking its blocks with it.
@@ -736,5 +748,6 @@ en:
           sound: Sound
           testimonials: Testimonials
           timeline: Timeline
+          title: Title
           video: Video
-          volumes: Volumes
\ No newline at end of file
+          volumes: Volumes
diff --git a/config/locales/communication/contents/fr.yml b/config/locales/communication/contents/fr.yml
index b9b04048621d2d2eb5b2d768c9b64337338d2257..e60f00546bb4296c5fbc71fde2f6dc0f7291a030 100644
--- a/config/locales/communication/contents/fr.yml
+++ b/config/locales/communication/contents/fr.yml
@@ -4,6 +4,9 @@ fr:
     communication:
       blocks:
         add: Ajouter un bloc
+        advanced:
+          anchor: Ancre
+          url_with_anchor: URL avec ancre
         alerts:
           file_is_too_big: "Le fichier est trop lourd\_!"
         categories:
@@ -662,6 +665,15 @@ fr:
               vertical:
                 description: Les événements sont présentés verticalement, dans la continuité de la page.
                 label: Vertical
+          title:
+            description: Un titre de niveau 2, que l'on peut présenter replié si on le souhaite.
+            layouts:
+              classic:
+                description: Le titre, tout simplement.
+                label: Classique
+              collapsed:
+                description: Le titre replié, avec tous les blocs qui le suivent cachés et un bouton pour les afficher.
+                label: Replié
           video:
             description: Une vidéo intégrée dans la page depuis diverses plateformes, avec la transcription et sans lecture automatique.
             edit:
@@ -702,7 +714,7 @@ fr:
             zero: Aucun bloc
         modes:
           structure:
-            description: Dans le mode “Organiser le plan”, vous ne voyez pas les blocs mais seulement les titres. Lorsque vous déplacez un titre, tous les blocs suivent. C'est le mode idéal pour ranger les documents longs.
+            description: Dans le mode “Organiser le plan”, vous ne voyez qu'un petit aperçu de chaque bloc. C'est le mode idéal pour ranger les documents longs.
             tab: Organiser le plan
           write:
             description: Dans le mode “Écrire le contenu”, vous pouvez déplacer les blocs et les titres où vous voulez, indépendamment les uns des autres. Ainsi, vous pouvez déplacer un titre plus haut dans le document sans qu'il n'emporte ses blocs.
@@ -736,5 +748,6 @@ fr:
           sound: Son
           testimonials: Témoignages
           timeline: Frise chronologique
+          title: Titre
           video: Vidéo
-          volumes: Volumes
\ No newline at end of file
+          volumes: Volumes
diff --git a/config/locales/server_admin/en.yml b/config/locales/server_admin/en.yml
index aa1283e09704b424a54669c4372a476a7008a0cb..c452e48459bbfb44776e6c4009c8c06dafb64b4a 100644
--- a/config/locales/server_admin/en.yml
+++ b/config/locales/server_admin/en.yml
@@ -51,6 +51,7 @@ en:
           sync: Sync
           update: Update theme
       clean_and_rebuild_all_websites_notice: All themes will be updated. This can take a few minutes.
+      clean_and_rebuild_website_notice: The website will be rebuilt. This can take a few minutes.
       connections_count: "%{count} connexions"
       details: detail
       events_count: "%{count} events"
diff --git a/config/locales/server_admin/fr.yml b/config/locales/server_admin/fr.yml
index 374655e523498edf21bb0237e01b34b70ee0c892..77cf104d4085b57f934b077c3780e5bf072e7bab 100644
--- a/config/locales/server_admin/fr.yml
+++ b/config/locales/server_admin/fr.yml
@@ -51,6 +51,7 @@ fr:
           sync: Synchroniser
           update: Mettre à jour le thème
       clean_and_rebuild_all_websites_notice: Tous les thèmes vont être mis à jour. Cela peut prendre quelques minutes.
+      clean_and_rebuild_website_notice: Le site va être reconstruit. Cela peut prendre quelques minutes.
       connections_count: "%{count} connexions"
       details: détail
       events_count: "%{count} événements"
diff --git a/config/routes/server.rb b/config/routes/server.rb
index 63fc56496f5cb9054a3c250ce87196ae5180d679..a2505e63b659282c294d333475b2fa395bbf9d93 100644
--- a/config/routes/server.rb
+++ b/config/routes/server.rb
@@ -7,10 +7,11 @@ namespace :server do
       post :clean_and_rebuild_all_websites
     end
     member do
-      post :sync_theme_version
       post :analyse
-      post :update_theme
+      post :clean_and_rebuild
+      post :sync_theme_version
       post :unlock_for_background_jobs
+      post :update_theme
     end
   end
   resources :blocks, only: [:index, :show] do
diff --git a/lib/tasks/app.rake b/lib/tasks/app.rake
index ea5a56f3e98424a3ab95e54185f73c13121ace12..932fd10a2d10191cf8a30342139e93829548b1bb 100644
--- a/lib/tasks/app.rake
+++ b/lib/tasks/app.rake
@@ -8,7 +8,7 @@ namespace :app do
 
   desc 'Fix things'
   task fix: :environment do
-    Migrations::TurnToHtml.migrate
+    Migrations::FixKeyFigures.migrate
   end
 
   namespace :websites do