diff --git a/VERSION b/VERSION
index 10c2c0c3d62137a46deee939bc5e6ad677a518c5..46b81d815a23b1a6b60bc9160f21295a5f9e4e75 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.10.0
+2.11.0
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 0050f0def95fe6a6ad3a5e112f47ae714bbb47e8..55c1ae03038f525a97690561310e66eb4fe61f73 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -1835,9 +1835,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001512",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz",
-      "integrity": "sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw==",
+      "version": "1.0.30001651",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz",
+      "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==",
       "dev": true,
       "funding": [
         {
diff --git a/frontend/src/assets/previews/event_poster.png b/frontend/src/assets/previews/event_poster.png
new file mode 100644
index 0000000000000000000000000000000000000000..192353ea377fcf46577f48ffe49fd0de5c8833cf
Binary files /dev/null and b/frontend/src/assets/previews/event_poster.png differ
diff --git a/frontend/src/assets/previews/kamery.jpg b/frontend/src/assets/previews/kamery.jpg
deleted file mode 100644
index 5765fc44b1c086bdde7970162a56acad5e2d5596..0000000000000000000000000000000000000000
Binary files a/frontend/src/assets/previews/kamery.jpg and /dev/null differ
diff --git a/frontend/src/assets/template/event_poster/base.png b/frontend/src/assets/template/event_poster/base.png
new file mode 100644
index 0000000000000000000000000000000000000000..f70d106785cde2183a924cbdd0e48381258f43fe
Binary files /dev/null and b/frontend/src/assets/template/event_poster/base.png differ
diff --git a/frontend/src/templates.js b/frontend/src/templates.js
index 66ff3fb513cdadd17942a5083a79a4b4a67cd665..1a7c5d437f3a8b0ee28fdebd00dbafb090e1734e 100644
--- a/frontend/src/templates.js
+++ b/frontend/src/templates.js
@@ -7,6 +7,7 @@ import newspaperQuoteMiddleImage from "./assets/previews/newspaper_quote_middle.
 import facebookSurveyImage from "./assets/previews/facebook_survey.png";
 import twitterBannerImage from "./assets/previews/twitter_banner.png";
 import posterImage from "./assets/previews/poster.png";
+import eventPosterImage from "./assets/previews/event_poster.png";
 import regionalSuccessImage from "./assets/previews/regional_success.png";
 import socialCoverLargeTextImage from "./assets/previews/social_cover_large_text.png";
 
@@ -99,6 +100,15 @@ const TEMPLATES = {
       title: "Plakát",
     },
   },
+  event_poster: {
+    name: "Plakát - událost",
+    image: eventPosterImage,
+    path: "/event-poster",
+    component: () => import("./views/event_poster/EventPoster.vue"),
+    meta: {
+      title: "Plakát - událost",
+    },
+  },
   twitter_banner: {
     name: "Twitter banner",
     image: twitterBannerImage,
diff --git a/frontend/src/views/event_poster/EventPoster.vue b/frontend/src/views/event_poster/EventPoster.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7243d2f36f340b919cf01c1ee59735f9bd766a2e
--- /dev/null
+++ b/frontend/src/views/event_poster/EventPoster.vue
@@ -0,0 +1,243 @@
+<script setup>
+import { watch, ref } from "vue";
+
+import COLORS from "../../colors";
+import PEOPLE from "../../people";
+import TEMPLATES from "../../templates";
+import DEFAULT_CONTRACTOR from "../../contractors";
+import {
+  loadFonts,
+  loadCanvasStorage,
+  setCanvasStorage,
+  updateAutoRedrawStorage,
+  toRawDeep,
+} from "../../utils";
+
+import Canvas from "../../components/canvas/Canvas.vue";
+import redraw from "./canvas";
+
+import Navbar from "../../components/Navbar.vue";
+import MainContainer from "../../components/MainContainer.vue";
+import ImageInput from "../../components/inputs/ImageInput.vue";
+import LongTextInput from "../../components/inputs/text/LongTextInput.vue";
+import ShortTextInput from "../../components/inputs/text/ShortTextInput.vue";
+import RangeInput from "../../components/inputs/RangeInput.vue";
+import InputSeparator from "../../components/inputs/InputSeparator.vue";
+import SelectInput from "../../components/inputs/SelectInput.vue";
+import MultipleColorPicker from "../../components/inputs/colors/MultipleColorPicker.vue";
+import ReloadButton from "../../components/reload/ReloadButton.vue";
+import AutoReloadCheckbox from "../../components/reload/AutoReloadCheckbox.vue";
+</script>
+
+<script>
+await loadFonts([
+  "12px Bebas Neue",
+  "12px Roboto Condensed",
+  "bold 12px Roboto Condensed",
+]);
+
+export default {
+  components: {
+    Canvas,
+    Navbar,
+    MainContainer,
+    ImageInput,
+    LongTextInput,
+    ShortTextInput,
+    RangeInput,
+    SelectInput,
+    InputSeparator,
+    MultipleColorPicker,
+  },
+  data() {
+    const predefinedColors = {
+      base: {
+        name: "Základní barvy",
+        colors: {
+          background: COLORS.white,
+          baseText: COLORS.black,
+          contractedByText: COLORS.gray1,
+          arrow: COLORS.yellow1,
+        },
+      },
+    };
+
+    return {
+      mainText: null,
+
+      mainImage: null,
+
+      eventLocation: null,
+      eventDate: null,
+      
+      firstColumn: null,
+      secondColumn: null,
+
+      contractedBy: DEFAULT_CONTRACTOR,
+
+      colorLabels: {
+        background: "Pozadí",
+        baseText: "Text",
+        highlightedText: "Zvýrazněný text",
+        arrow: "Šipka",
+      },
+
+      predefinedColors: predefinedColors,
+      colors: predefinedColors.base.colors,
+      autoRedraw: false,
+    };
+  },
+  async created() {
+    await loadCanvasStorage(this);
+  },
+  methods: {
+    async reloadCanvasProperties() {
+      const canvasProperties = {
+        mainImage: this.mainImage,
+        mainText: this.mainText,
+        eventLocation: this.eventLocation,
+        eventDate: this.eventDate,
+        firstColumn: this.firstColumn,
+        secondColumn: this.secondColumn,
+        contractedBy: this.contractedBy,
+        colors: this.colors,
+      };
+
+      if (canvasProperties.mainText) {
+        window.fileName = canvasProperties.mainText;
+      }
+
+      await this.$refs.canvas.redraw(canvasProperties);
+
+      let canvasPropertiesToSave = structuredClone(toRawDeep(canvasProperties));
+      delete canvasPropertiesToSave.colors;
+
+      setCanvasStorage(canvasPropertiesToSave);
+    },
+  },
+  mounted() {
+    this.$watch(
+      (vm) => [
+        vm.mainText,
+        vm.mainImage,
+        vm.eventLocation,
+        vm.eventDate,
+        vm.firstColumn,
+        vm.secondColumn,
+        vm.contractedBy,
+        vm.colors,
+      ],
+      async (value) => {
+        if (this.autoRedraw) {
+          await this.reloadCanvasProperties();
+        }
+      },
+      {
+        immediate: true,
+        deep: true,
+      },
+    );
+
+    this.$watch(
+      (vm) => [vm.autoRedraw],
+      async (value) => {
+        updateAutoRedrawStorage(this.autoRedraw);
+
+        if (this.autoRedraw) {
+          await this.reloadCanvasProperties();
+        }
+      },
+    );
+  },
+};
+</script>
+
+<template>
+  <header>
+    <Navbar :defaultTemplate="TEMPLATES.event_poster"></Navbar>
+  </header>
+  <main>
+    <MainContainer>
+      <template v-slot:left>
+        <Canvas
+          ref="canvas"
+          :redrawFunction="redraw"
+          width="3625"
+          height="5078"
+        />
+      </template>
+
+      <template v-slot:right>
+        <ReloadButton :parentRefs="$refs" @click="reloadCanvasProperties" />
+        <AutoReloadCheckbox v-model="autoRedraw" />
+
+        <ImageInput
+          name="Obrázek"
+          v-model="mainImage"
+          :important="true"
+          zIndex="11"
+        />
+
+        <ShortTextInput
+          name="Nadpis"
+          v-model="mainText"
+          v-model:relatedModel="mainText"
+          :important="true"
+          zIndex="10"
+        />
+
+        <ShortTextInput
+          name="Datum"
+          v-model="eventDate"
+          :important="false"
+          zIndex="9"
+        />
+        <ShortTextInput
+          name="Lokace"
+          v-model="eventLocation"
+          :important="false"
+          zIndex="8"
+        />
+
+        <LongTextInput
+          name="Sloupec 1"
+          v-model="firstColumn"
+          :important="true"
+          :highlightable="true"
+          zIndex="7"
+        />
+        <LongTextInput
+          name="Sloupec 2"
+          v-model="secondColumn"
+          :important="true"
+          :highlightable="true"
+          zIndex="6"
+        />
+
+        <InputSeparator />
+
+        <MultipleColorPicker
+          name="Barvy"
+          v-model="colors"
+          :important="false"
+          :colorLabels="colorLabels"
+          :predefinedColors="predefinedColors"
+          :defaultPredefinedColors="predefinedColors.base"
+          zIndex="5"
+        ></MultipleColorPicker>
+
+        <ShortTextInput
+          name="Zadavatel a zpracovatel"
+          v-model="contractedBy"
+          :defaultValue="DEFAULT_CONTRACTOR"
+          :important="false"
+          zIndex="4"
+        />
+      </template>
+    </MainContainer>
+  </main>
+</template>
+
+<style>
+@import "vue-select/dist/vue-select.css";
+</style>
diff --git a/frontend/src/views/event_poster/canvas.js b/frontend/src/views/event_poster/canvas.js
new file mode 100644
index 0000000000000000000000000000000000000000..9289ea4dac3ada26bdfd25a0a7468804c0b49dca
--- /dev/null
+++ b/frontend/src/views/event_poster/canvas.js
@@ -0,0 +1,321 @@
+import { fabric } from "fabric";
+import {
+  clearObjects,
+  sortObjects,
+  transformHighlightedText,
+  checkTextBoxHeight,
+} from "../../components/canvas/utils";
+import { PaddedHighlightingTextbox } from "../../components/canvas/textbox";
+import overlayURL from "../../assets/template/event_poster/base.png";
+
+let mainImage = null;
+let mainImageSource = null;
+let overlayImage = null;
+let pointerDownEventAssigned = false;
+let eventDateText = null;
+let eventLocationText = null;
+let mainText = null;
+let firstColumn = null;
+let secondColumn = null;
+
+let contractedByTextbox = null;
+
+const removeDownEventListener = () => {
+  document
+    .getElementsByClassName("upper-canvas")[0]
+    .removeEventListener("pointerdown", canvasPointerDownEvent);
+};
+
+let upEventFunction = null;
+let canvasPointerDownEvent = null;
+
+const redraw = async (canvas, options) => {
+  clearObjects(
+    [
+      eventDateText,
+      eventLocationText,
+      contractedByTextbox,
+      mainText,
+      firstColumn,
+      secondColumn,
+    ],
+    canvas,
+  );
+
+  const bottomMarginText = 200;
+  const bottomFontSize = 90;
+
+  const mainTextMarginBottom = 2200;
+  const mainTextSize = 600;
+  const mainTextMarginSides = 250;
+
+  const secondaryTextSize = 300;
+  const secondaryTextMarginBottom = 1600;
+  const secondaryTextMarginSides = 325;
+
+  const columnsMarginTop = -1000;
+  const columnsMarginBetween = 100;
+  const columnsMaxWidth = 1500;
+  const columnTextSize = 130;
+  const columnLineHeight = 1;
+
+  const contractedByTextSize = Math.ceil(canvas.height * 0.014);
+  const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9);
+  const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03);
+  const contractedByTextBottomMargin = Math.ceil(canvas.height * 0.012);
+
+  document
+    .getElementsByClassName("upper-canvas")[0]
+    .removeEventListener("pointerup", upEventFunction);
+  document
+    .getElementsByClassName("upper-canvas")[0]
+    .removeEventListener("pointerout", upEventFunction);
+  document
+    .getElementsByClassName("upper-canvas")[0]
+    .removeEventListener("pointercancel", upEventFunction);
+
+  canvas.preserveObjectStacking = true;
+
+  /* BEGIN Main image render */
+
+  if (
+    options.mainImage !== null &&
+    (!canvas.contains(mainImage) ||
+      mainImage === null ||
+      options.mainImage.src !== mainImageSource)
+  ) {
+    if (mainImage !== null) {
+      canvas.remove(mainImage);
+    }
+
+    mainImage = new fabric.Image(options.mainImage, {
+      left: 0,
+      top: 0,
+      zIndex: 10,
+    });
+
+    mainImage.controls = {
+      ...fabric.Image.prototype.controls,
+      mtr: new fabric.Control({ visible: false }),
+    };
+
+    if (mainImage.width >= mainImage.height) {
+      mainImage.scaleToHeight(canvas.height);
+    } else {
+      mainImage.scaleToWidth(canvas.width);
+    }
+
+    canvas.add(mainImage);
+    mainImageSource = options.mainImage.src;
+    // canvas.centerObject(mainImage)
+
+    removeDownEventListener();
+    pointerDownEventAssigned = false;
+  } else if (mainImage !== null && options.mainImage === null) {
+    canvas.remove(mainImage);
+
+    removeDownEventListener();
+    pointerDownEventAssigned = false;
+  }
+
+  /* END Main image render */
+
+  /* BEGIN Overlay render */
+
+  if (overlayImage === null) {
+    overlayImage = new Image();
+
+    await new Promise((resolve) => {
+      overlayImage.onload = () => {
+        resolve();
+      };
+
+      overlayImage.src = overlayURL;
+    });
+
+    overlayImage = new fabric.Image(overlayImage, {
+      top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?!
+      left: -20,
+      zIndex: 20,
+      selectable: false,
+    });
+    overlayImage.scaleToWidth(canvas.width + 22);
+
+    canvas.add(overlayImage);
+  }
+
+  /* END Overlay render */
+
+  /* BEGIN Main text render */
+
+  if (options.mainText !== null) {
+    mainText = new fabric.Textbox(options.mainText, {
+      left: mainTextMarginSides,
+      top: canvas.height - mainTextMarginBottom,
+      width: canvas.width - mainTextMarginSides * 2,
+      textAlign: "center",
+      fontFamily: "Bebas Neue",
+      fontSize: mainTextSize,
+      fill: "#fec900",
+      selectable: false,
+      zIndex: 40,
+    });
+
+    // Keep to a single line no matter what
+    canvas.add(mainText);
+  
+    while (mainText._textLines.length > 1) {
+      mainText.set({
+        fontSize: mainText.fontSize - 20,
+        top: mainText.top + 10,
+      });
+      canvas.renderAll();
+    }
+  }
+
+  /* END Main text render */
+
+  /* BEGIN Event date text render */
+
+  if (options.eventDate !== null) {
+    eventDateText = new fabric.Text(options.eventDate, {
+      left: secondaryTextMarginSides,
+      top: canvas.height - secondaryTextMarginBottom,
+      fontFamily: "Bebas Neue",
+      fontSize: secondaryTextSize,
+      fill: "#fec900",
+      selectable: false,
+      zIndex: 40,
+    });
+
+    canvas.add(eventDateText);
+  }
+
+  /* END Event date text render */
+
+  /* BEGIN Event location text render */
+
+  if (options.eventLocation !== null) {
+    eventLocationText = new fabric.Text(options.eventLocation, {
+      left: canvas.width - secondaryTextMarginSides,
+      top: canvas.height - secondaryTextMarginBottom,
+      fontFamily: "Bebas Neue",
+      fontSize: secondaryTextSize,
+      textAlign: "right",
+      fill: "#fec900",
+      selectable: false,
+      zIndex: 40,
+    });
+
+    canvas.add(eventLocationText);
+
+    eventLocationText.set({
+      left: eventLocationText.left - eventLocationText.width
+    });
+    canvas.renderAll();
+  }
+
+  /* END Event location text render */
+
+  /* BEGIN Column text render */
+
+  if (options.firstColumn !== null) {
+    firstColumn = new fabric.Textbox(options.firstColumn, {
+      left: mainTextMarginSides,
+      top: canvas.height + columnsMarginTop,
+      width: columnsMaxWidth,
+      fontFamily: "Roboto Condensed",
+      fontSize: columnTextSize,
+      lineHeight: columnLineHeight,
+      fill: "#000",
+      selectable: false,
+      zIndex: 40,
+    });
+
+    canvas.add(firstColumn);
+  }
+
+  if (options.secondColumn !== null) {
+    secondColumn = new fabric.Textbox(options.secondColumn, {
+      left: mainTextMarginSides + columnsMarginBetween + columnsMaxWidth,
+      top: canvas.height + columnsMarginTop,
+      width: columnsMaxWidth,
+      fontFamily: "Roboto Condensed",
+      fontSize: columnTextSize,
+      lineHeight: columnLineHeight,
+      fill: "#000",
+      selectable: false,
+      zIndex: 40,
+    });
+
+    canvas.add(secondColumn);
+  }
+
+  /* END Column text render */
+
+  /* BEGIN Contracted by render */
+
+  if (options.contractedBy !== null) {
+    contractedByTextbox = new fabric.Textbox(options.contractedBy, {
+      left:
+        canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin,
+      top: canvas.height - contractedByTextBottomMargin - contractedByTextSize,
+      width: contractedByTextMaxWidth,
+      fontFamily: "Roboto Condensed",
+      fontSize: contractedByTextSize,
+      textAlign: "right",
+      fill: "#505050",
+      selectable: false,
+      zIndex: 40,
+    });
+
+    checkTextBoxHeight(contractedByTextbox, 1);
+
+    canvas.add(contractedByTextbox);
+  }
+
+  /* END Contracted by render */
+
+  sortObjects(canvas);
+
+  canvasPointerDownEvent = (event) => {
+    let activeObject = canvas.getActiveObject();
+
+    if (activeObject === null) {
+      return;
+    }
+
+    // if (activeObject._element.src == mainImage._element.src) {
+    // return
+    // }
+
+    canvas.remove(overlayImage);
+    overlayImage = null;
+  };
+
+  if (!pointerDownEventAssigned) {
+    document
+      .getElementsByClassName("upper-canvas")[0]
+      .addEventListener("pointerdown", canvasPointerDownEvent);
+
+    pointerDownEventAssigned = true;
+  }
+
+  upEventFunction = (event) => {
+    redraw(canvas, options);
+  };
+
+  document
+    .getElementsByClassName("upper-canvas")[0]
+    .addEventListener("pointerup", upEventFunction);
+
+  document
+    .getElementsByClassName("upper-canvas")[0]
+    .addEventListener("pointerout", upEventFunction);
+
+  document
+    .getElementsByClassName("upper-canvas")[0]
+    .addEventListener("pointercancel", upEventFunction);
+};
+
+export default redraw;