diff --git a/src/components/Countdown.vue b/src/components/Countdown.vue
new file mode 100644
index 0000000..ad9afba
--- /dev/null
+++ b/src/components/Countdown.vue
@@ -0,0 +1,64 @@
+<template>
+  <div :data-text="timerName" :style="style" class="countdown"></div>
+</template>
+
+<script>
+export default {
+  props: {
+    timerName: String,
+    timerDuration: Number
+  },
+  computed: {
+    style() {
+      return `--timer: ${this.timerDuration}`;
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+div {
+  width: 100%;
+  height: 1.6em;
+  border: 2px solid black;
+  background: rgba(0, 0, 0, 0.4);
+  position: relative;
+  z-index: 0;
+  margin-top: 0.3em;
+}
+
+div::before {
+  content: "";
+  position: absolute;
+  inset: 0;
+  background: rgba(255, 0, 0, 0.6);
+  z-index: 1;
+  animation: forwards countdown calc(var(--timer) * 1s) linear;
+}
+
+div::after{
+  position:absolute;
+  inset: 0;
+  text-align:center;
+  content: attr(data-text);
+  z-index: 2;
+  background: linear-gradient(
+    180deg,
+    rgba(255, 255, 255, 0) 5%,
+    rgba(255, 255, 255, 0.5) 15%,
+    rgba(255, 255, 255, 0) 35%,
+    rgba(0, 0, 0, 0) 60%,
+    rgba(0, 0, 0, 0.7) 100%
+  );
+}
+
+@keyframes countdown {
+  0% {
+    width: 100%;
+  }
+  100% {
+    width:0%;
+    display:none;
+  }
+}
+</style>
diff --git a/src/components/TownInfo.vue b/src/components/TownInfo.vue
index c99a2a3..6a63148 100644
--- a/src/components/TownInfo.vue
+++ b/src/components/TownInfo.vue
@@ -63,20 +63,27 @@
           :icon="teams.traveler > 1 ? 'user-friends' : 'user'"
         />
       </span>
-      <span v-if="grimoire.isNight">
-        Night phase
-        <font-awesome-icon :icon="['fas', 'cloud-moon']" />
-      </span>
-      <span v-if="grimoire.isRinging">
-        <audio
-          :autoplay="!grimoire.isMuted"
-          src="../assets/sounds/countdown.mp3"
-          :muted="grimoire.isMuted"
-        ></audio>
-        <font-awesome-icon :icon="['fas', 'music']" />
-        <font-awesome-icon :icon="['fas', 'bell']" />
-        <font-awesome-icon :icon="['fas', 'music']" />
-      </span>
+    </li>
+    <li v-if="grimoire.isNight">
+      <font-awesome-icon :icon="['fas', 'cloud-moon']" />
+      {{ locale.towninfo.nightPhase }}
+    </li>
+    <li v-if="grimoire.isRinging">
+      <audio
+        :autoplay="!grimoire.isMuted"
+        src="../assets/sounds/countdown.mp3"
+        :muted="grimoire.isMuted"
+      ></audio>
+      <font-awesome-icon :icon="['fas', 'music']" />
+      <font-awesome-icon :icon="['fas', 'bell']" />
+      <font-awesome-icon :icon="['fas', 'music']" />
+    </li>
+    <li>
+      <Countdown
+        v-if="grimoire.timer.duration"
+        :timerName="grimoire.timer.name"
+        :timerDuration="grimoire.timer.duration"
+      />
     </li>
   </ul>
 </template>
@@ -84,8 +91,12 @@
 <script>
 import gameJSON from "./../game";
 import { mapState } from "vuex";
+import Countdown from "./Countdown";
 
 export default {
+  components: {
+    Countdown
+  },
   computed: {
     teams: function() {
       const { players } = this.$store.state.players;
@@ -102,7 +113,10 @@ export default {
           ).length
       };
     },
-    ...mapState(["edition", "grimoire"]),
+    countdownStyle: function() {
+      return `--timer: ${this.$store.state.grimoire.timer.duration}`;
+    },
+    ...mapState(["edition", "grimoire", "locale"]),
     ...mapState("players", ["players"])
   }
 };
@@ -184,7 +198,7 @@ export default {
     background-repeat: no-repeat;
     background-size: 100% auto;
     position: absolute;
-    top: -25%;
+    top: -50%;
   }
 }
 </style>
diff --git a/src/components/TownSquare.vue b/src/components/TownSquare.vue
index 1da049f..54cd2e9 100644
--- a/src/components/TownSquare.vue
+++ b/src/components/TownSquare.vue
@@ -46,6 +46,52 @@
       </ul>
     </div>
 
+    <div
+      class="storytelling"
+      v-if="!session.isSpectator"
+      ref="storytelling"
+      :class="{ closed: !isTimeControlsOpen }"
+    >
+      <h3>
+        <span>{{ locale.townsquare.storytellerTools }}</span>
+        <font-awesome-icon
+          icon="times-circle"
+          @click.stop="toggleTimeControls"
+        />
+        <font-awesome-icon
+          icon="plus-circle"
+          @click.stop="toggleTimeControls"
+        />
+      </h3>
+      <div class="button-group">
+        <div @click="setTimer()" class="button">🕑 {{ timerDuration }} min</div>
+        <div @click="renameTimer()" class="button">🗏 {{ timerName }}</div>
+        <div
+          class="button demon"
+          @click="stopTimer()"
+          :class="{ disabled: !timerOn }"
+        >
+          ■
+        </div>
+        <div
+          class="button townfolk"
+          @click="startTimer()"
+          :class="{ disabled: timerOn }"
+        >
+          ⏔
+        </div>
+      </div>
+      <div class="button-group">
+        <div @click="toggleNight()" class="button" :class="{disabled: grimoire.isNight}">☀</div>
+        <div @click="toggleNight()" class="button" :class="{disabled: !grimoire.isNight}">☜</div>
+      </div>
+      <div class="button-group">
+        <div @click="toggleRinging()" class="button">
+          <font-awesome-icon :icon="['fas', 'bell']" />
+        </div>
+      </div>
+    </div>
+
     <div class="fabled" :class="{ closed: !isFabledOpen }" v-if="fabled.length">
       <h3>
         <span>{{ locale.townsquare.fabled }}</span>
@@ -113,7 +159,12 @@ export default {
       move: -1,
       nominate: -1,
       isBluffsOpen: true,
-      isFabledOpen: true
+      isFabledOpen: true,
+      isTimeControlsOpen: false,
+      timerName: "Timer",
+      timerDuration: 1,
+      timerOn: false,
+      timerEnder: false
     };
   },
   methods: {
@@ -123,10 +174,23 @@ export default {
     toggleFabled() {
       this.isFabledOpen = !this.isFabledOpen;
     },
+    toggleTimeControls() {
+      this.isTimeControlsOpen = !this.isTimeControlsOpen;
+    },
     removeFabled(index) {
       if (this.session.isSpectator) return;
       this.$store.commit("players/setFabled", { index });
     },
+    toggleNight() {
+      this.$store.commit("toggleNight");
+      if (this.grimoire.isNight) {
+        this.$store.commit("session/setMarkedPlayer", -1);
+      }
+    },
+    toggleRinging() {
+      this.$store.commit("toggleRinging", true);
+      setTimeout(this.$store.commit, 4000, "toggleRinging", false);
+    },
     handleTrigger(playerIndex, [method, params]) {
       if (typeof this[method] === "function") {
         this[method](playerIndex, params);
@@ -251,6 +315,33 @@ export default {
       this.move = -1;
       this.swap = -1;
       this.nominate = -1;
+    },
+    renameTimer() {
+      let newName = prompt("Timer Name", "");
+      if (newName === "") {
+        return;
+      }
+      this.timerName = newName.trim();
+    },
+    setTimer() {
+      let newDuration = prompt("Timer Duration in minutes");
+      if (isNaN(newDuration)) {
+        return alert("Incorrect number");
+      }
+      if (newDuration > 0) {
+        this.timerDuration = newDuration;
+      }
+    },
+    startTimer() {
+      let timer = { name: this.timerName, duration: this.timerDuration * 60 };
+      this.$store.commit("setTimer", timer);
+      this.timerOn = true;
+      this.timerEnder = setTimeout(this.stopTimer, timer.duration * 1000);
+    },
+    stopTimer() {
+      this.$store.commit("setTimer", {});
+      this.timerOn = false;
+      clearTimeout(this.timerEnder);
     }
   }
 };
@@ -396,15 +487,22 @@ export default {
 
 /***** Demon bluffs / Fabled *******/
 #townsquare > .bluffs,
-#townsquare > .fabled {
+#townsquare > .fabled,
+#townsquare > .storytelling {
   position: absolute;
+  left: 10px;
   &.bluffs {
     bottom: 10px;
   }
   &.fabled {
     top: 10px;
   }
-  left: 10px;
+  &.storytelling{
+    bottom: 20vmin;
+    left: auto;
+    right: 10px;
+    width: min-content;
+  }
   background: rgba(0, 0, 0, 0.5);
   border-radius: 10px;
   border: 3px solid black;
@@ -412,7 +510,7 @@ export default {
   transform-origin: bottom left;
   transform: scale(1);
   opacity: 1;
-  transition: all 200ms ease-in-out;
+  transition: all 250ms ease-in-out;
   z-index: 50;
 
   > svg {
@@ -465,6 +563,15 @@ export default {
       transition: all 250ms;
     }
   }
+  .button-group {
+    transition: all 250ms;
+    input {
+      background: none;
+      border: none;
+      color: white;
+      font-size: 1.1em;
+    }
+  }
   &.closed {
     svg.fa-times-circle {
       display: none;
@@ -473,6 +580,7 @@ export default {
       display: block;
     }
     ul li {
+      scale: 0;
       width: 0;
       height: 0;
       .night-order {
@@ -482,6 +590,12 @@ export default {
         border-width: 0;
       }
     }
+    .button-group,
+    .button-group * {
+      width: 0px;
+      height: 0px;
+      scale: 0;
+    }
   }
 }
 
diff --git a/src/components/Vote.vue b/src/components/Vote.vue
index 48f9e85..36f0b3c 100644
--- a/src/components/Vote.vue
+++ b/src/components/Vote.vue
@@ -17,7 +17,9 @@
       <em v-if="nominee.role.team !== 'traveler'">
         ({{ locale.vote.majorityIs }} {{ Math.ceil(alive / 2) }})
       </em>
-      <em v-else>({{ locale.vote.majorityIs }} {{ Math.ceil(players.length / 2) }})</em>
+      <em v-else>
+        ({{ locale.vote.majorityIs }} {{ Math.ceil(players.length / 2) }})
+      </em>
 
       <template v-if="!session.isSpectator">
         <div v-if="!session.isVoteInProgress && session.lockedVote < 1">
@@ -94,6 +96,11 @@
       <div v-else-if="!player">
         {{ locale.vote.seatToVote }}
       </div>
+      <Countdown
+        v-if="grimoire.timer.duration"
+        :timerName="grimoire.timer.name"
+        :timerDuration="grimoire.timer.duration"
+      />
     </div>
     <transition name="blur">
       <div
@@ -116,8 +123,12 @@
 
 <script>
 import { mapGetters, mapState } from "vuex";
+import Countdown from "./Countdown";
 
 export default {
+  components: {
+    Countdown
+  },
   computed: {
     ...mapState("players", ["players"]),
     ...mapState(["session", "grimoire", "locale"]),
diff --git a/src/store/index.js b/src/store/index.js
index 5694658..87ace2c 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -111,7 +111,11 @@ export default new Vuex.Store({
       isMuted: false,
       isImageOptIn: false,
       zoom: 0,
-      background: ""
+      background: "",
+      timer: {
+        name: "",
+        duration: 0
+      }
     },
     modals: {
       edition: false,
@@ -179,6 +183,9 @@ export default new Vuex.Store({
     toggleRinging: toggle("isRinging"),
     toggleGrimoire: toggle("isPublic"),
     toggleImageOptIn: toggle("isImageOptIn"),
+    setTimer(state, timer) {
+      state.grimoire.timer = timer;
+    },
     toggleModal({ modals }, name) {
       if (name) {
         modals[name] = !modals[name];
diff --git a/src/store/locale/en/ui.json b/src/store/locale/en/ui.json
index d8b3fc1..12034e4 100644
--- a/src/store/locale/en/ui.json
+++ b/src/store/locale/en/ui.json
@@ -91,10 +91,12 @@
   "townsquare":{
     "others": "Other characters",
     "bluffs": "Demon bluffs",
-    "fabled": "Fabled"
+    "fabled": "Fabled",
+    "storytellerTools": "Storytelling"
   },
   "towninfo":{
-    "addPlayers":"Please add more players!"
+    "addPlayers":"Please add more players!",
+    "nightPhase":"Night Phase"
   },
   "player":{
     "handUp": "Hand UP",
diff --git a/src/store/locale/fr/ui.json b/src/store/locale/fr/ui.json
index cbab18a..a50f900 100644
--- a/src/store/locale/fr/ui.json
+++ b/src/store/locale/fr/ui.json
@@ -91,10 +91,12 @@
   "townsquare":{
     "others": "Autres RĂŽles",
     "bluffs": "Bluffs de Démon",
-    "fabled": "Fabuleux"
+    "fabled": "Fabuleux",
+    "storytellerTools": "Narration"
   },
   "towninfo":{
-    "addPlayers":"Appuyez sur [A] pour ajouter plus de joueurs !"
+    "addPlayers": "Appuyez sur [A] pour ajouter plus de joueurs !",
+    "nightPhase": "C'est la nuit"
   },
   "player":{
     "handUp": "Main levée",
@@ -118,7 +120,7 @@
   "intro":{
     "header": "Bienvenue sur le Centre-ville Virtuel (non-officiel) pour Blood on the Clocktower! Veuillez ajouter des Joueurs via le",
     "menu": "Menu",
-    "body": "en haut Ă  droite ou en appuyant sur [A] pour commencer. Vous pouvez aussi rejoindre une ssession en appuyant sur [J].",
+    "body": "en haut Ă  droite ou en appuyant sur [A] pour commencer. Vous pouvez aussi rejoindre une session en appuyant sur [J].",
     "footerStart": "Ce programme est libre et ses sources peuvent ĂȘtre trouvĂ©es sur",
     "footerEnd": ". Ce site n'est pas affilié à The Pandemonium Institute. \"Blood on the Clocktower\" est une marque déposée de Steven Medway & The Pandemonium Institute."
   },
diff --git a/src/store/socket.js b/src/store/socket.js
index 5da2c65..36b3e9d 100644
--- a/src/store/socket.js
+++ b/src/store/socket.js
@@ -179,9 +179,10 @@ class LiveSession {
       case "isRinging":
         if (!this._isSpectator) return;
         this._store.commit("toggleRinging", params);
-        // if (params){
-        //   setTimeout(this._store.commit, 4000, "toggleRinging", false);
-        // }
+        break;
+      case "setTimer":
+        if (!this._isSpectator) return;
+        this._store.commit("setTimer", params);
         break;
       case "isVoteHistoryAllowed":
         if (!this._isSpectator) return;
@@ -285,6 +286,7 @@ class LiveSession {
         gamestate: this._gamestate,
         isNight: grimoire.isNight,
         isRinging: grimoire.isRinging,
+        timer: grimoire.timer,
         isVoteHistoryAllowed: session.isVoteHistoryAllowed,
         nomination: session.nomination,
         votingSpeed: session.votingSpeed,
@@ -310,6 +312,7 @@ class LiveSession {
       isNight,
       isVoteHistoryAllowed,
       isRinging,
+      timer,
       nomination,
       votingSpeed,
       votes,
@@ -361,6 +364,7 @@ class LiveSession {
       }
     });
     if (!isLightweight) {
+      this._store.commit("timer", timer);
       this._store.commit("toggleRinging", !!isRinging);
       this._store.commit("toggleNight", !!isNight);
       this._store.commit("session/setVoteHistoryAllowed", isVoteHistoryAllowed);
@@ -721,6 +725,14 @@ class LiveSession {
     this._send("isRinging", this._store.state.grimoire.isRinging);
   }
 
+  /**
+   * Start or stop a timer
+   */
+  setTimer() {
+    if (this._isSpectator) return;
+    this._send("setTimer", this._store.state.grimoire.timer);
+  }
+
   /**
    * Send the isVoteHistoryAllowed state. ST only
    */
@@ -905,6 +917,9 @@ export default store => {
       case "toggleRinging":
         session.setIsRinging();
         break;
+      case "setTimer":
+        session.setTimer();
+        break;
       case "setEdition":
         session.sendEdition();
         break;