diff --git a/src/snek/app.py b/src/snek/app.py
index 24af03b..227c23a 100644
--- a/src/snek/app.py
+++ b/src/snek/app.py
@@ -55,6 +55,7 @@ from snek.view.upload import UploadView
 from snek.view.user import UserView
 from snek.view.web import WebView
 from snek.view.channel import ChannelAttachmentView
+from snek.view.settings.containers import ContainersIndexView, ContainersCreateView, ContainersUpdateView, ContainersDeleteView
 from snek.webdav import WebdavApplication
 from snek.sgit import GitApplication
 SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34"
@@ -208,6 +209,10 @@ class Application(BaseApplication):
         self.router.add_view("/settings/repositories/create.html", RepositoriesCreateView)
         self.router.add_view("/settings/repositories/repository/{name}/update.html", RepositoriesUpdateView)
         self.router.add_view("/settings/repositories/repository/{name}/delete.html", RepositoriesDeleteView)
+        self.router.add_view("/settings/containers/index.html", ContainersIndexView)
+        self.router.add_view("/settings/containers/create.html", ContainersCreateView)
+        self.router.add_view("/settings/containers/container/{uid}/update.html", ContainersUpdateView)
+        self.router.add_view("/settings/containers/container/{uid}/delete.html", ContainersDeleteView)
         self.webdav = WebdavApplication(self)
         self.git = GitApplication(self)
         self.add_subapp("/webdav", self.webdav)
diff --git a/src/snek/static/chat-input.js b/src/snek/static/chat-input.js
index 2d0914e..cec3d57 100644
--- a/src/snek/static/chat-input.js
+++ b/src/snek/static/chat-input.js
@@ -53,7 +53,8 @@ class ChatInputComponent extends HTMLElement {
         this.textarea.focus();
     }
 
-    connectedCallback() {
+    async connectedCallback() {
+        this.user = await app.rpc.getUser(null);
         this.liveType = this.getAttribute("live-type") === "true";
         this.liveTypeInterval = parseInt(this.getAttribute("live-type-interval")) || 3;
         this.channelUid = this.getAttribute("channel");
@@ -193,7 +194,7 @@ class ChatInputComponent extends HTMLElement {
         if (this.trackSecondsBetweenEvents(this.lastUpdateEvent, new Date()) > 1) {
             this.lastUpdateEvent = new Date();
             if (typeof app !== "undefined" && app.rpc && typeof app.rpc.set_typing === "function") {
-                app.rpc.set_typing(this.channelUid);
+                app.rpc.set_typing(this.channelUid,this.user.color);
             }
         }
     }
diff --git a/src/snek/static/sandbox.css b/src/snek/static/sandbox.css
index 1419fe4..c2ffffb 100644
--- a/src/snek/static/sandbox.css
+++ b/src/snek/static/sandbox.css
@@ -1,28 +1,42 @@
-/* each star */
-    .star {
-      position: absolute;
-      width: 2px;
-      height: 2px;
-      background: #fff;
-      border-radius: 50%;
-      opacity: 0;
-      /* flicker animation */
-      animation: twinkle ease-in-out infinite;
-    }
 
-    @keyframes twinkle {
-      0%, 100% { opacity: 0; }
-      50%      { opacity: 1; }
-    }
+.star {
+  position: absolute;
+  width: 2px;
+  height: 2px;
+  background: var(--star-color, #fff);
+  border-radius: 50%;
+  opacity: 0;
+  transition: background 0.5s ease;
+  animation: twinkle ease-in-out infinite;
+}
 
-    /* optional page content */
-    .content {
-      position: relative;
-      z-index: 1;
-      color: #eee;
-      font-family: sans-serif;
-      text-align: center;
-      top: 40%;
-      transform: translateY(-40%);
-    }
+@keyframes twinkle {
+  0%, 100% { opacity: 0; }
+  50%      { opacity: 1; }
+}
 
+@keyframes star-glow-frames {
+    0% {
+	box-shadow: 0 0 5px --star-color;
+    }
+    50% {
+	box-shadow: 0 0 20px --star-color, 0 0 30px --star-color;
+    }
+    100% {
+	box-shadow: 0 0 5px --star-color;
+    }
+}
+
+.star-glow {
+    animation: star-glow-frames 1s;
+}
+
+.content {
+  position: relative;
+  z-index: 1;
+  color: var(--star-content-color, #eee);
+  font-family: sans-serif;
+  text-align: center;
+  top: 40%;
+  transform: translateY(-40%);
+}
diff --git a/src/snek/templates/sandbox.html b/src/snek/templates/sandbox.html
index 6fd9b3f..0322b00 100644
--- a/src/snek/templates/sandbox.html
+++ b/src/snek/templates/sandbox.html
@@ -1,31 +1,56 @@
 
-							<script>
-                            	
-    // number of stars you want
-    const STAR_COUNT = 200;
-    const body = document.body;
+<script type="module">
+import { app } from "/app.js";
 
-    for (let i = 0; i < STAR_COUNT; i++) {
-      const star = document.createElement('div');
-      star.classList.add('star');
+const STAR_COUNT = 200;
+const body = document.body;
 
-      // random position within the viewport
-      star.style.left  = Math.random() * 100 + '%';
-      star.style.top   = Math.random() * 100 + '%';
+function createStar() {
+  const star = document.createElement('div');
+  star.classList.add('star');
+  star.style.left = `${Math.random() * 100}%`;
+  star.style.top = `${Math.random() * 100}%`;
+  const size = Math.random() * 2 + 1;
+  star.style.width = `${size}px`;
+  star.style.height = `${size}px`;
+  const duration = Math.random() * 3 + 2;
+  const delay = Math.random() * 5;
+  star.style.animationDuration = `${duration}s`;
+  star.style.animationDelay = `${delay}s`;
+  body.appendChild(star);
+}
 
-      // random size (optional)
-      const size = Math.random() * 2 + 1; // between 1px and 3px
-      star.style.width  = size + 'px';
-      star.style.height = size + 'px';
+Array.from({ length: STAR_COUNT }, createStar);
 
-      // random animation timing for natural flicker
-      const duration = Math.random() * 3 + 2;   // 2s–5s
-      const delay    = Math.random() * 5;       // 0s–5s
-      star.style.animationDuration = duration + 's';
-      star.style.animationDelay    = delay + 's';
+function lightenColor(hex, percent) {
+  const num = parseInt(hex.replace("#", ""), 16);
+  let r = (num >> 16) + Math.round(255 * percent / 100);
+  let g = ((num >> 8) & 0x00FF) + Math.round(255 * percent / 100);
+  let b = (num & 0x0000FF) + Math.round(255 * percent / 100);
+  r = Math.min(255, r);
+  g = Math.min(255, g);
+  b = Math.min(255, b);
+  return `#${(1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1)}`;
+}
 
-      body.appendChild(star);
-    }
-  
+const originalColor = document.documentElement.style.getPropertyValue("--star-color").trim();
 
-							</script>
+function glowCSSVariable(varName, glowColor, duration = 500) {
+  const root = document.documentElement;
+
+  //igetComputedStyle(root).getPropertyValue(varName).trim();
+  glowColor = lightenColor(glowColor, 20);
+  root.style.setProperty(varName, glowColor);
+  setTimeout(() => {
+    root.style.setProperty(varName, originalColor);
+  }, duration);
+}
+
+function updateStarColorDelayed(color) {
+  glowCSSVariable('--star-color', color, 2500);
+}
+
+app.ws.addEventListener("set_typing", (data) => {
+  updateStarColorDelayed(data.data.color);
+});
+</script>
diff --git a/src/snek/view/rpc.py b/src/snek/view/rpc.py
index de94633..fd48124 100644
--- a/src/snek/view/rpc.py
+++ b/src/snek/view/rpc.py
@@ -36,9 +36,11 @@ class RPCView(BaseView):
         async def db_update(self, table_name, record):
             self._require_login()
             return await self.services.db.update(self.user_uid, table_name, record)
-        async def set_typing(self,channel_uid):
+        async def set_typing(self,channel_uid,color=None):
             self._require_login()
             user = await self.services.user.get(self.user_uid)
+            if not color:
+                color = user["color"]
             return await self.services.socket.broadcast(channel_uid, {
                 "channel_uid": "293ecf12-08c9-494b-b423-48ba1a2d12c2",
                 "event": "set_typing",
@@ -47,7 +49,8 @@ class RPCView(BaseView):
                     "user_uid": user['uid'],
                     "username": user["username"],
                     "nick": user["nick"],
-                    "channel_uid": channel_uid
+                    "channel_uid": channel_uid,
+                    "color": color
                 }
             })