diff --git a/BewerbungWebseite/Models/SiteContent/EditMarkup.cs b/BewerbungWebseite/Models/SiteContent/EditMarkup.cs
new file mode 100644
index 0000000..baa21c1
--- /dev/null
+++ b/BewerbungWebseite/Models/SiteContent/EditMarkup.cs
@@ -0,0 +1,60 @@
+using System.Text.Encodings.Web;
+using Microsoft.AspNetCore.Html;
+
+namespace BewerbungsSeite.Models.SiteContent;
+
+///
+/// Erzeugt die data-Attribute, an denen der Editor die bearbeitbaren Stellen
+/// erkennt. Ausserhalb des Edit-Modus kommt nichts zurueck, damit das Markup
+/// der oeffentlichen Seite unveraendert bleibt.
+/// (Razor laesst Attribute mit null-Wert nicht weg, deshalb dieser Weg.)
+///
+public static class EditMarkup
+{
+ private static readonly HtmlEncoder Encoder = HtmlEncoder.Default;
+
+ private static IHtmlContent Attributes(bool editMode, string markup) =>
+ editMode ? new HtmlString(markup) : HtmlString.Empty;
+
+ /// Kennzeichnet einen Bereich.
+ public static IHtmlContent Section(bool editMode, string name) =>
+ Attributes(editMode, $"data-section=\"{Encoder.Encode(name)}\"");
+
+ ///
+ /// Kennzeichnet ein bearbeitbares Textfeld. kommt aus
+ /// und landet als data-max im Markup, wo es der
+ /// Editor (page-editor.js) fuer den Live-Zaehler und die Eingabegrenze liest.
+ ///
+ public static IHtmlContent Field(bool editMode, string name, bool multiline = false, int? max = null) =>
+ Attributes(editMode,
+ $"data-field=\"{Encoder.Encode(name)}\"" +
+ (multiline ? " data-multiline=\"true\"" : string.Empty) +
+ (max.HasValue ? $" data-max=\"{max}\"" : string.Empty) +
+ " contenteditable=\"true\"");
+
+ /// Macht ein Element bearbeitbar, ohne ihm ein eigenes Feld zuzuordnen.
+ public static IHtmlContent Editable(bool editMode, int? max = null) =>
+ Attributes(editMode,
+ "contenteditable=\"true\"" + (max.HasValue ? $" data-max=\"{max}\"" : string.Empty));
+
+ /// Kennzeichnet einen Listencontainer.
+ public static IHtmlContent List(bool editMode, string name) =>
+ Attributes(editMode, $"data-list=\"{Encoder.Encode(name)}\"");
+
+ /// Kennzeichnet eine Liste aus reinen Texten.
+ public static IHtmlContent StringList(bool editMode, string name) =>
+ Attributes(editMode, $"data-list=\"{Encoder.Encode(name)}\" data-string-list=\"true\"");
+
+ /// Kennzeichnet einen Listeneintrag.
+ public static IHtmlContent Item(bool editMode) =>
+ Attributes(editMode, "data-item=\"true\"");
+
+ /// Kennzeichnet ein verschachteltes Objekt.
+ public static IHtmlContent Object(bool editMode, string name) =>
+ Attributes(editMode, $"data-object=\"{Encoder.Encode(name)}\"");
+
+ /// Kennzeichnet ein austauschbares Bild und haelt dessen gespeicherten Pfad fest.
+ public static IHtmlContent Image(bool editMode, string name, string path) =>
+ Attributes(editMode,
+ $"data-image=\"{Encoder.Encode(name)}\" data-value=\"{Encoder.Encode(path ?? string.Empty)}\"");
+}
diff --git a/BewerbungWebseite/Models/SiteContent/SectionView.cs b/BewerbungWebseite/Models/SiteContent/SectionView.cs
new file mode 100644
index 0000000..4f39bb0
--- /dev/null
+++ b/BewerbungWebseite/Models/SiteContent/SectionView.cs
@@ -0,0 +1,30 @@
+namespace BewerbungsSeite.Models.SiteContent;
+
+///
+/// Modell eines Section-Partials: der Inhalt des Bereichs und die Angabe,
+/// ob die Seite im Bearbeitungsmodus gerendert wird. Dasselbe Partial wird
+/// so von der oeffentlichen Seite und von der Admin-Seite benutzt.
+///
+public class SectionView(T section, bool editMode)
+{
+ public T Section { get; } = section;
+ public bool EditMode { get; } = editMode;
+}
+
+public static class SectionView
+{
+ public static SectionView For(T section, bool editMode) => new(section, editMode);
+}
+
+///
+/// Ein Textstueck, das im Edit-Modus in ein bearbeitbares Element eingepackt wird
+/// und im normalen Modus als reiner Text erscheint. So bleibt das oeffentliche
+/// Markup unveraendert, auch wenn der Text mit anderen Elementen eine Zeile teilt.
+///
+public class EditableText(string text, string field, bool editMode, int? max = null)
+{
+ public string Text { get; } = text;
+ public string Field { get; } = field;
+ public bool EditMode { get; } = editMode;
+ public int? Max { get; } = max;
+}