diff --git a/extension/helpers.js b/extension/helpers.js
index d8bfc48..0b6ca06 100644
--- a/extension/helpers.js
+++ b/extension/helpers.js
@@ -9,7 +9,8 @@
 export const ERROR = Symbol();
 
 export const env = {};
-env.name = 'browser';
+env.name = 'extension';
+env.version = chrome.runtime.getManifest().version;
 
 env.openEnhancerMenu = () => chrome.runtime.sendMessage({ action: 'openEnhancerMenu' });
 env.focusNotion = () => chrome.runtime.sendMessage({ action: 'focusNotion' });
@@ -20,9 +21,11 @@ export const storage = {};
 
 storage.set = (id, key, value) =>
   new Promise((res, rej) => chrome.storage.sync.set({ [`[${id}]${key}`]: value }, res));
-storage.get = (id, key) =>
+storage.get = (id, key, fallback = undefined) =>
   new Promise((res, rej) =>
-    chrome.storage.sync.get([`[${id}]${key}`], (values) => res(values[`[${id}]${key}`]))
+    chrome.storage.sync.get([`[${id}]${key}`], (values) =>
+      res(values[`[${id}]${key}`] ?? fallback)
+    )
   );
 
 /** - */
@@ -127,7 +130,7 @@ fmt.Prism.hooks.add('complete', async (event) => {
   event.element.parentElement.removeAttribute('tabindex');
   event.element.parentElement.parentElement
     .querySelector('.copy-to-clipboard-button')
-    .prepend(web.createElement(await fs.getText('icons/fontawesome/copy.svg')));
+    .prepend(web.createElement(await fs.getText('icons/fa/copy.svg')));
   // if (!fmt.Prism._stylesheetLoaded) {
   //   web.loadStyleset('./dep/prism.css');
   //   fmt.Prism._stylesheetLoaded = true;
@@ -192,8 +195,14 @@ export const fs = {};
 /**
  * @param {string} path
  */
-fs.getJSON = (path) => fetch(chrome.runtime.getURL(path)).then((res) => res.json());
-fs.getText = (path) => fetch(chrome.runtime.getURL(path)).then((res) => res.text());
+fs.getJSON = (path) =>
+  fetch(path.startsWith('https://') ? path : chrome.runtime.getURL(path)).then((res) =>
+    res.json()
+  );
+fs.getText = (path) =>
+  fetch(path.startsWith('https://') ? path : chrome.runtime.getURL(path)).then((res) =>
+    res.text()
+  );
 
 fs.isFile = async (path) => {
   try {
diff --git a/extension/icons/fontawesome/caret-down.svg b/extension/icons/fa/caret-down.svg
similarity index 100%
rename from extension/icons/fontawesome/caret-down.svg
rename to extension/icons/fa/caret-down.svg
diff --git a/extension/icons/fontawesome/code.svg b/extension/icons/fa/code.svg
similarity index 100%
rename from extension/icons/fontawesome/code.svg
rename to extension/icons/fa/code.svg
diff --git a/extension/icons/fontawesome/copy.svg b/extension/icons/fa/copy.svg
similarity index 100%
rename from extension/icons/fontawesome/copy.svg
rename to extension/icons/fa/copy.svg
diff --git a/extension/icons/fontawesome/discord.svg b/extension/icons/fa/discord.svg
similarity index 100%
rename from extension/icons/fontawesome/discord.svg
rename to extension/icons/fa/discord.svg
diff --git a/extension/icons/fa/exclamation-triangle.svg b/extension/icons/fa/exclamation-triangle.svg
new file mode 100644
index 0000000..752b93d
--- /dev/null
+++ b/extension/icons/fa/exclamation-triangle.svg
@@ -0,0 +1,2 @@
+<!-- https://fontawesome.com/icons/exclamation-triangle?style=solid -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"></path></svg>
\ No newline at end of file
diff --git a/extension/icons/fontawesome/file.svg b/extension/icons/fa/file.svg
similarity index 100%
rename from extension/icons/fontawesome/file.svg
rename to extension/icons/fa/file.svg
diff --git a/extension/icons/fontawesome/info.svg b/extension/icons/fa/info.svg
similarity index 100%
rename from extension/icons/fontawesome/info.svg
rename to extension/icons/fa/info.svg
diff --git a/extension/icons/fontawesome/long-arrow-alt-left.svg b/extension/icons/fa/long-arrow-alt-left.svg
similarity index 100%
rename from extension/icons/fontawesome/long-arrow-alt-left.svg
rename to extension/icons/fa/long-arrow-alt-left.svg
diff --git a/extension/icons/fontawesome/long-arrow-alt-right.svg b/extension/icons/fa/long-arrow-alt-right.svg
similarity index 100%
rename from extension/icons/fontawesome/long-arrow-alt-right.svg
rename to extension/icons/fa/long-arrow-alt-right.svg
diff --git a/extension/icons/fa/redo.svg b/extension/icons/fa/redo.svg
new file mode 100644
index 0000000..66878a5
--- /dev/null
+++ b/extension/icons/fa/redo.svg
@@ -0,0 +1,2 @@
+<!-- https://fontawesome.com/icons/redo?style=solid -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M500.33 0h-47.41a12 12 0 0 0-12 12.57l4 82.76A247.42 247.42 0 0 0 256 8C119.34 8 7.9 119.53 8 256.19 8.1 393.07 119.1 504 256 504a247.1 247.1 0 0 0 166.18-63.91 12 12 0 0 0 .48-17.43l-34-34a12 12 0 0 0-16.38-.55A176 176 0 1 1 402.1 157.8l-101.53-4.87a12 12 0 0 0-12.57 12v47.41a12 12 0 0 0 12 12h200.33a12 12 0 0 0 12-12V12a12 12 0 0 0-12-12z"></path></svg>
\ No newline at end of file
diff --git a/extension/icons/monster/party.svg b/extension/icons/monster/party.svg
new file mode 100644
index 0000000..e0b975e
--- /dev/null
+++ b/extension/icons/monster/party.svg
@@ -0,0 +1 @@
+<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd"><path fill="currentColor" d="M17.517 10.012c.151.23.087.541-.144.692l-2.2 1.444c-.085.056-.179.082-.274.082-.162 0-.322-.079-.418-.225-.152-.231-.087-.541.143-.692l2.201-1.445c.23-.151.541-.089.692.144m-6.242-2.595l1.111-2.385c.116-.252.414-.36.664-.243.25.117.36.413.242.664l-1.111 2.386c-.085.181-.265.288-.453.288l-.211-.047c-.25-.115-.359-.413-.242-.663m-2.624.613c1.377-2.652 1.484-5.104.318-7.286-.178-.333.066-.734.441-.734.177 0 .351.095.442.264 1.33 2.49 1.225 5.254-.314 8.217-.089.171-.263.269-.444.269-.078 0-.156-.018-.23-.056-.245-.127-.341-.429-.213-.674m15.349 5.526c0 .352-.351.588-.671.47-2.808-1.028-5.254-.821-7.271.611-.088.063-.189.093-.29.093-.155 0-.309-.073-.406-.21-.16-.224-.108-.537.117-.696 2.301-1.637 5.059-1.884 8.193-.737.203.074.328.266.328.469m-16.484-2.608l2.865 7.517-2.248.964-2.753-7.512.778-2.176 1.358 1.207zm3.785 7.124l-2.168-5.687 5.025 4.463-2.857 1.224zm-8.27.419l.989 2.699-2.307.989 1.318-3.688zm1.823-5.103l2.358 6.435-2.271.973-1.384-3.777 1.297-3.631zm-4.853 10.612l15.997-6.853-10.283-9.137-5.714 15.99zm20.46-15.629l.552-.694.281.841.831.309-.713.528-.038.886-.723-.516-.854.238.268-.846-.491-.739.887-.007zm-1.384.885l-.639 2.019 2.041-.568 1.724 1.23.089-2.115 1.704-1.258-1.985-.739-.672-2.008-1.315 1.658-2.118.017 1.171 1.764zm-2.167-4.194c.593-.044.924-.141 1.074-.315.176-.204.226-.647.165-1.433-.023-.276.183-.517.459-.539.277-.016.515.18.537.456.063.806.059 1.62-.402 2.156-.429.499-1.13.602-1.76.647-.702.052-.72.243-.774.878-.056.67-.152 1.744-1.84 1.933-1.017.115-1.433.33-1.377 1.956.008.275-.207.325-.484.325h-.016c-.269 0-.491-.022-.5-.291-.049-1.461.191-2.655 2.265-2.887.874-.099.9-.404.956-1.072.054-.635.145-1.7 1.697-1.814m5.264-3.048c.454 0 .823.37.823.824 0 .454-.369.823-.823.823-.454 0-.824-.369-.824-.823 0-.454.37-.824.824-.824m0 2.647c1.006 0 1.823-.817 1.823-1.823s-.817-1.823-1.823-1.823c-1.007 0-1.824.817-1.824 1.823s.817 1.823 1.824 1.823m-8.446-3.662c.552 0 1 .449 1 .999 0 .551-.448.999-1 .999s-1-.448-1-.999c0-.55.448-.999 1-.999m0 2.998c1.103 0 1.999-.896 1.999-1.999 0-1.103-.896-1.998-1.999-1.998-1.104 0-2 .895-2 1.998s.896 1.999 2 1.999"/></svg>
\ No newline at end of file
diff --git a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/client.css b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/client.css
index be75895..0e4776d 100644
--- a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/client.css
+++ b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/client.css
@@ -9,6 +9,7 @@
   -webkit-user-select: none;
   transition: background 20ms ease-in 0s;
   cursor: pointer;
+  color: var(--theme--text_sidebar);
 }
 .enhancer--sidebarMenuTrigger:hover {
   background: var(--theme--button-hover);
@@ -26,11 +27,10 @@
   padding: 2px 14px;
   width: 100%;
 }
-.enhancer--sidebarMenuTrigger > div > div:first-child {
+.enhancer--sidebarMenuTrigger > div > :first-child {
   flex-shrink: 0;
   flex-grow: 0;
   border-radius: 3px;
-  color: var(--theme--text_sidebar);
   width: 22px;
   height: 22px;
   display: flex;
@@ -38,10 +38,33 @@
   justify-content: center;
   margin-right: 8px;
 }
-.enhancer--sidebarMenuTrigger > div > div:last-child {
+.enhancer--sidebarMenuTrigger > div > :nth-child(2) {
   flex: 1 1 auto;
   white-space: nowrap;
-  min-width: 0px;
   overflow: hidden;
   text-overflow: ellipsis;
 }
+.enhancer--notifications {
+  color: var(--theme--text);
+}
+.enhancer--notifications > div > :last-child {
+  display: flex;
+}
+.enhancer--notifications > div > :last-child > div {
+  margin-left: auto;
+  margin-bottom: 2px;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 16px;
+  height: 16px;
+  font-size: 10px;
+  font-weight: 600;
+  border-radius: 3px;
+  color: var(--theme--tag_new-text);
+  background: var(--theme--tag_new);
+}
+.enhancer--notifications > div > :last-child > div > span {
+  margin-bottom: 1px;
+  margin-left: -0.5px;
+}
diff --git a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/client.js b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/client.js
index 6afed32..b48889e 100644
--- a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/client.js
+++ b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/client.js
@@ -12,18 +12,37 @@ import { env, storage, web, fs } from '../../helpers.js';
 const sidebarSelector =
   '#notion-app > div > div.notion-cursor-listener > div.notion-sidebar-container > div > div > div > div:nth-child(4)';
 web.whenReady([sidebarSelector], async () => {
-  const enhancerIcon = await fs.getText('icons/colour.svg'),
-    enhancerSidebarElement = web.createElement(
-      `<div class="enhancer--sidebarMenuTrigger" role="button" tabindex="0"><div><div>${enhancerIcon}</div><div><div>notion-enhancer</div></div></div></div>`
+  const $enhancerSidebarElement = web.createElement(
+      web.html`<div class="enhancer--sidebarMenuTrigger" role="button" tabindex="0">
+        <div>
+          <div>${await fs.getText('icons/colour.svg')}</div>
+          <div><div>notion-enhancer</div></div>
+        </div>
+      </div>`
+    ),
+    notifications = {
+      list: await fs.getJSON('https://notion-enhancer.github.io/notifications.json'),
+      dismissed: await storage.get(_id, 'notifications', []),
+    };
+  notifications.waiting = notifications.list.filter(
+    ({ id }) => !notifications.dismissed.includes(id)
+  );
+  if (notifications.waiting.length) {
+    $enhancerSidebarElement.classList.add('enhancer--notifications');
+    $enhancerSidebarElement.children[0].append(
+      web.createElement(
+        web.html`<div><div><span>${notifications.waiting.length}</span></div></div>`
+      )
     );
+  }
   const setTheme = () =>
     storage.set(_id, 'theme', document.querySelector('.notion-dark-theme') ? 'dark' : 'light');
-  enhancerSidebarElement.addEventListener('click', () => {
+  $enhancerSidebarElement.addEventListener('click', () => {
     setTheme().then(env.openEnhancerMenu);
   });
   window.addEventListener('focus', setTheme);
   window.addEventListener('blur', setTheme);
   setTheme();
-  document.querySelector(sidebarSelector).appendChild(enhancerSidebarElement);
+  document.querySelector(sidebarSelector).appendChild($enhancerSidebarElement);
 });
 web.hotkeyListener(['Ctrl', 'Alt', 'E'], env.openEnhancerMenu);
diff --git a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.css b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.css
index d460fe4..5ab71ce 100644
--- a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.css
+++ b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.css
@@ -54,10 +54,10 @@ header h1 svg {
   padding-top: 0.3em;
   margin-right: 0.2em;
 }
-header h1 svg[data-fa='info'] {
+header h1 svg[data-icon='info'] {
   margin-right: 0em;
 }
-header h1 svg[data-fa='code'] {
+header h1 svg[data-icon='code'] {
   margin-right: 0.3em;
 }
 
@@ -372,16 +372,16 @@ label > span:not([class]) {
   overflow-x: auto;
 }
 
-.alert--list {
+.notification--list {
   position: absolute;
   bottom: 1.5rem;
   right: 1.5rem;
 }
-[role='alert'] {
+.notification {
+  background: var(--theme--block_grey);
+  color: var(--theme--block_grey-text);
   max-width: calc(100vw - 3rem);
   width: 25rem;
-  background: var(--theme--block_blue);
-  color: var(--theme--block_blue-text);
   font-size: var(--theme--font_ui-size);
   border-top: 4px currentColor solid;
   border-bottom-left-radius: 3px;
@@ -391,21 +391,26 @@ label > span:not([class]) {
   display: flex;
   margin-top: 1rem;
   position: relative;
+  transition: opacity 200ms ease-in-out, transform 115ms ease-out 200ms,
+    margin-top 115ms ease-out 200ms;
+  transform: scaleY(1);
+  transform-origin: 100% 100%;
+  opacity: 0;
 }
-[role='alert'] svg {
+.notification svg {
   height: 1.5rem;
   width: 1.5rem;
   margin-top: 0.25rem;
   margin-right: 1rem;
 }
-[role='alert'] h3 {
+.notification h3 {
   margin: 0 0 0.25rem 0;
   font-weight: bold;
 }
-[role='alert'] p {
+.notification p {
   margin: 0.25rem 0 0.5rem 0;
 }
-[role='alert'] .alert--dismiss {
+.notification .notification--dismiss {
   position: absolute;
   top: 0.5rem;
   right: 0.75rem;
@@ -418,10 +423,20 @@ label > span:not([class]) {
   transition: opacity 200ms ease-in-out;
   opacity: 0;
 }
-[role='alert']:hover .alert--dismiss,
-[role='alert']:focus-within .alert--dismiss {
+.notification:hover .notification--dismiss,
+.notification:focus-within .notification--dismiss {
   opacity: 1;
 }
+.notification.celebration,
+.notification.information {
+  background: var(--theme--block_blue);
+  color: var(--theme--block_blue-text);
+}
+.notification.warning,
+.notification.danger {
+  background: var(--theme--block_red);
+  color: var(--theme--block_red-text);
+}
 
 ::-webkit-scrollbar {
   background: transparent;
diff --git a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.html b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.html
index 14f00a8..43d89a0 100644
--- a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.html
+++ b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.html
@@ -15,51 +15,18 @@
         >
       </h1>
       <h1>
-        <span data-fa="info"></span><a href="https://notion-enhancer.github.io/">website</a>
+        <i data-icon="fa/info"></i><a href="https://notion-enhancer.github.io/">website</a>
       </h1>
       <h1>
-        <span data-fa="code"></span
+        <span data-icon="fa/code"></span
         ><a href="https://github.com/notion-enhancer/extension">source code</a>
       </h1>
-      <h1><span data-fa="discord"></span><a href="https://discord.gg/sFWPXtA">support</a></h1>
+      <h1><i data-icon="fa/discord"></i><a href="https://discord.gg/sFWPXtA">support</a></h1>
     </header>
     <main>
       <section data-container></section>
     </main>
-    <footer class="alert--list">
-      <div role="alert">
-        <div>
-          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
-            <path
-              fill="currentColor"
-              d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm12.73-1.41A8 8 0 1 0 4.34 4.34a8 8 0 0 0 11.32 11.32zM9 11V9h2v6H9v-4zm0-6h2v2H9V5z"
-            />
-          </svg>
-        </div>
-        <div>
-          <h3>v0.11.0 released</h3>
-          <p>
-            <a href="chrome://extensions">read the changelog</a> and
-            <a href="">update the extension</a>
-          </p>
-        </div>
-        <button class="alert--dismiss">&times;</button>
-      </div>
-      <div role="alert">
-        <div>
-          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
-            <path
-              fill="currentColor"
-              d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm12.73-1.41A8 8 0 1 0 4.34 4.34a8 8 0 0 0 11.32 11.32zM9 11V9h2v6H9v-4zm0-6h2v2H9V5z"
-            />
-          </svg>
-        </div>
-        <div>
-          <h3>alert</h3>
-          <p>description</p>
-        </div>
-      </div>
-    </footer>
+    <footer class="notification--list"></footer>
     <script src="./menu.js" type="module"></script>
   </body>
 </html>
diff --git a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.js b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.js
index 8d68a25..17ec19a 100644
--- a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.js
+++ b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.js
@@ -15,15 +15,9 @@ for (let mod of await registry.get()) {
   }
 }
 
-
 document.querySelector('img[data-target="notion"]').addEventListener('click', env.focusNotion);
 web.hotkeyListener(['Ctrl', 'Alt', 'E'], env.focusNotion);
 
-document.querySelectorAll('[data-fa]').forEach(icon => fs.getText(`icons/fontawesome/${icon.dataset.fa}.svg`).then(svg => {
-  svg = web.createElement(svg);
-  svg.dataset.fa = icon.dataset.fa; icon.replaceWith(svg);
-}))
-
 const components = {};
 components.card = {
   preview: ({ preview = '' }) =>
@@ -74,7 +68,7 @@ components.card = {
     web.createElement(
       web.html`<p class="library--expand">
         <a href="?view=mod&id=${web.escapeHtml(id)}">
-          <span>${await fs.getText('icons/fontawesome/long-arrow-alt-right.svg')}</span>
+          <span><i data-icon="fa/long-arrow-alt-right"></i></span>
           <span>settings & documentation</span>
         </a>
       </p>`
@@ -108,7 +102,7 @@ components.options = {
     >
       <p>${label}</p>
       <p class="library--select">
-        <span> ${await fs.getText('icons/fontawesome/caret-down.svg')}</span>
+        <span><i data-icon="fa/caret-down"></i></span>
         <select id="select--${web.escapeHtml(`${id}.${key}`)}">
           ${values.map(
             (value) =>
@@ -157,7 +151,7 @@ components.options = {
       />
       <p>${web.escapeHtml(label)}</p>
       <p class="library--file">
-        <span>${await fs.getText('icons/fontawesome/file.svg')}</span>
+        <span><i data-icon="fa/file"></i></span>
         <span class="library--file_path">choose file...</span>
       </p>
     </label>`);
@@ -182,7 +176,7 @@ components.documentation = {
   buttons: async ({ _dir }) =>
     web.createElement(web.html`<p class="documentation--buttons">
       <a href="?view=library">
-        <span>${await fs.getText('icons/fontawesome/long-arrow-alt-left.svg')}</span>
+        <span><i data-icon="fa/long-arrow-alt-left"></i></span>
         <span>back to library</span>
       </a>
       <a
@@ -190,7 +184,7 @@ components.documentation = {
           _dir
         )}"
       >
-        <span>${await fs.getText('icons/fontawesome/code.svg')}</span>
+        <span><i data-icon="fa/code"></i></span>
         <span>view source code</span>
       </a>
     </p>`),
@@ -291,6 +285,13 @@ const views = {
     document
       .querySelectorAll('a[href^="#"]')
       .forEach((a) => a.addEventListener('click', this._navigator));
+    document.querySelectorAll('[data-icon]').forEach((icon) =>
+      fs.getText(`icons/${icon.dataset.icon}.svg`).then((svg) => {
+        svg = web.createElement(svg);
+        svg.dataset.icon = icon.dataset.icon;
+        icon.replaceWith(svg);
+      })
+    );
   },
   async mod(mod) {
     this.$container.dataset.container = 'mod';
@@ -313,6 +314,76 @@ window.addEventListener('popstate', (ev) => {
   if (ev.state) views._load();
 });
 
+const notifications = {
+  _generate({ heading, message = '', type = 'information' }, callback = () => {}) {
+    let svg = '',
+      className = 'notification';
+    switch (type) {
+      case 'celebration':
+        svg = web.html`<i data-icon="monster/party"></i>`;
+        className += ' celebration';
+        break;
+      case 'information':
+        svg = web.html`<i data-icon="fa/info"></i>`;
+        className += ' information';
+        break;
+      case 'warning':
+        svg = web.html`<i data-icon="fa/exclamation-triangle"></i>`;
+        className += ' warning';
+        break;
+    }
+    const $notif = web.createElement(web.html`<div role="alert" class="${className}">
+      <div>${svg}</div>
+      <div>
+        <h3>${web.escapeHtml(heading)}</h3>
+        <p>${fmt.md.renderInline(message)}</p>
+      </div>
+      <button class="notification--dismiss">&times;</button>
+    </div>`);
+    $notif.querySelector('.notification--dismiss').addEventListener('click', (event) => {
+      $notif.style.opacity = 0;
+      $notif.style.transform = 'scaleY(0)';
+      $notif.style.marginTop = `-${
+        $notif.offsetHeight / parseFloat(getComputedStyle(document.documentElement).fontSize)
+      }rem`;
+      setTimeout(() => $notif.remove(), 400);
+      callback();
+    });
+    setTimeout(() => {
+      $notif.style.opacity = 1;
+    }, 100);
+    return $notif;
+  },
+  async load() {
+    let notifications = {
+      list: await fs.getJSON('https://notion-enhancer.github.io/notifications.json'),
+      dismissed: await storage.get(_id, 'notifications', []),
+    };
+    notifications.list = notifications.list.sort((a, b) => b.id - a.id);
+    notifications.waiting = notifications.list.filter(
+      ({ id }) => !notifications.dismissed.includes(id)
+    );
+    const $list = document.querySelector('.notification--list');
+    for (let notification of notifications.waiting) {
+      if (
+        notification.heading &&
+        notification.appears_on &&
+        (notification.appears_on.versions.includes('*') ||
+          notification.appears_on.versions.includes(env.version)) &&
+        notification.appears_on.extension
+      ) {
+        $list.append(
+          this._generate(notification, async () => {
+            const dismissed = await storage.get(_id, 'notifications', []);
+            storage.set(_id, 'notifications', [...new Set([...dismissed, notification.id])]);
+          })
+        );
+      }
+    }
+  },
+};
+notifications.load();
+
 async function theme() {
   document.documentElement.className = `notion-${
     (await storage.get(_id, 'theme')) || 'dark'