From a7446d131413da9f013a56d3541192d8ab1e22b0 Mon Sep 17 00:00:00 2001
From: retoor <retoor@molodetz.nl>
Date: Sat, 18 Jan 2025 13:21:38 +0100
Subject: [PATCH] Progress.

---
 .gitignore                                   |   2 +-
 Dockerfile                                   |  40 ++++
 Makefile                                     |   6 +-
 cache/crc321300331366.cache                  |   0
 cache/crc322507170282.cache                  |   0
 compose.yml                                  |  12 ++
 setup.cfg                                    |   4 +
 src/snek/app.py                              |  46 ++++-
 src/snek/gunicorn.py                         |   3 +
 src/snek/http.py                             |  83 ++++++++
 src/snek/middleware.py                       |  32 +++
 src/snek/static/app.js                       |  77 +++++++
 src/snek/static/html_frame.css               |   9 +
 src/snek/static/html_frame.js                |  39 ++++
 src/snek/static/prachtig-gitter_like.html    | 172 ++++++++++++++++
 src/snek/static/register.css                 |  95 +++++++++
 src/snek/static/styles.css                   | 203 +++++++++++++++++++
 src/snek/templates/login.html                |  20 ++
 src/snek/templates/prachtig_gitter_like.html |  51 +++++
 src/snek/templates/register.html             |  22 ++
 src/snek/templates/test.html                 |  63 ++++++
 src/snek/templates/test2.html                | 122 +++++++++++
 22 files changed, 1095 insertions(+), 6 deletions(-)
 create mode 100644 Dockerfile
 create mode 100644 cache/crc321300331366.cache
 create mode 100644 cache/crc322507170282.cache
 create mode 100644 compose.yml
 create mode 100644 src/snek/gunicorn.py
 create mode 100644 src/snek/http.py
 create mode 100644 src/snek/middleware.py
 create mode 100644 src/snek/static/app.js
 create mode 100644 src/snek/static/html_frame.css
 create mode 100644 src/snek/static/html_frame.js
 create mode 100644 src/snek/static/prachtig-gitter_like.html
 create mode 100644 src/snek/static/register.css
 create mode 100644 src/snek/static/styles.css
 create mode 100644 src/snek/templates/login.html
 create mode 100644 src/snek/templates/prachtig_gitter_like.html
 create mode 100644 src/snek/templates/register.html
 create mode 100644 src/snek/templates/test.html
 create mode 100644 src/snek/templates/test2.html

diff --git a/.gitignore b/.gitignore
index 8cb2598..3747073 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,7 @@
 .vscode
 .history
 *.db*
-
+*.png
 # ---> Python
 # Byte-compiled / optimized / DLL files
 __pycache__/
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..76436ba
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,40 @@
+FROM surnet/alpine-wkhtmltopdf:3.21.2-0.12.6-full as wkhtmltopdf 
+FROM python:3.10-alpine
+WORKDIR /code
+ENV FLASK_APP=app.py
+ENV FLASK_RUN_HOST=0.0.0.0
+RUN apk add --no-cache gcc musl-dev linux-headers git
+
+#WKHTMLTOPDFNEEDS
+RUN apk add --no-cache \
+    libstdc++ \
+    libx11 \
+    libxrender \
+    libxext \
+    libssl3 \
+    ca-certificates \
+    fontconfig \
+    freetype \
+    ttf-dejavu \
+    ttf-droid \
+    ttf-freefont \
+    ttf-liberation \
+    # more fonts
+  && apk add --no-cache --virtual .build-deps \
+    msttcorefonts-installer \
+  # Install microsoft fonts
+  && update-ms-fonts \
+  && fc-cache -f \
+  # Clean up when done
+  && rm -rf /tmp/* \
+  && apk del .build-deps
+COPY --from=wkhtmltopdf /bin/wkhtmltopdf /bin/wkhtmltopdf
+COPY --from=wkhtmltopdf /bin/wkhtmltoimage /bin/wkhtmltoimage
+COPY setup.cfg setup.cfg 
+COPY pyproject.toml pyproject.toml 
+COPY src src
+RUN pip install --upgrade pip
+RUN pip install -e .
+EXPOSE 8081
+
+CMD ["gunicorn", "-w", "10", "-k", "aiohttp.worker.GunicornWebWorker", "snek.gunicorn:app","--bind","0.0.0.0:8081"]
diff --git a/Makefile b/Makefile
index d41b81a..65c58fa 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,8 @@
 PYTHON=./.venv/bin/python 
 PIP=./.venv/bin/pip 
-APP=./venv/bin/snek.serve
+APP=./.venv/bin/snek.serve
+GUNICORN=./.venv/bin/gunicorn
+GUNICORN_WORKERS = 1
 PORT = 8081
 
 
@@ -9,5 +11,5 @@ install:
 	$(PIP) install -e .
 
 run:
-	$(APP) --port=$(PORT)
+	$(GUNICORN) -w $(GUNICORN_WORKERS) -k aiohttp.worker.GunicornWebWorker snek.gunicorn:app --bind 0.0.0.0:$(PORT) --reload
 	
diff --git a/cache/crc321300331366.cache b/cache/crc321300331366.cache
new file mode 100644
index 0000000..e69de29
diff --git a/cache/crc322507170282.cache b/cache/crc322507170282.cache
new file mode 100644
index 0000000..e69de29
diff --git a/compose.yml b/compose.yml
new file mode 100644
index 0000000..9186108
--- /dev/null
+++ b/compose.yml
@@ -0,0 +1,12 @@
+services:
+  snek:
+    build: .
+    ports:
+      - "8081:8081"
+    volumes:
+      - ./:/code
+    develop:
+      watch:
+        - action: sync
+          path: .
+          target: /code
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
index bb6480d..ca8353d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -15,6 +15,10 @@ package_dir =
 python_requires = >=3.7
 install_requires =
     app @ git+https://retoor.molodetz.nl/retoor/app
+    beautifulsoup4
+    gunicorn
+    imgkit
+    wkhtmltopdf
 
 [options.packages.find]
 where = src
diff --git a/src/snek/app.py b/src/snek/app.py
index feb2fc0..97fbb96 100644
--- a/src/snek/app.py
+++ b/src/snek/app.py
@@ -1,20 +1,60 @@
 from app.app import Application as BaseApplication
 from snek.forms import RegisterForm
 from aiohttp import web
+import aiohttp 
+import pathlib
+from snek import http 
+from snek.middleware import cors_allow_middleware,cors_middleware
+
 
 class Application(BaseApplication):
 
     def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
+        middlewares = [
+            cors_middleware,
+            web.normalize_path_middleware(merge_slashes=True)
+        ]
+        self.template_path = pathlib.Path(__file__).parent.joinpath("templates")
+        super().__init__(middlewares=middlewares, template_path=self.template_path,*args, **kwargs)
+        self.router.add_static("/",pathlib.Path(__file__).parent.joinpath("static"),name="static",show_index=True)
         self.router.add_get("/register", self.handle_register)
+        self.router.add_get("/login", self.handle_login)
+        self.router.add_get("/test", self.handle_test)
         self.router.add_post("/register", self.handle_register)
+        self.router.add_get("/http-get",self.handle_http_get)
+        self.router.add_get("/http-photo",self.handle_http_photo)
+
+    async def handle_test(self,request):
+
+        return await self.render_template("test.html",request,context={"name":"retoor"})
+
+    async def handle_http_get(self, request:web.Request):
+        url = request.query.get("url")
+        content = await http.get(url)
+        return web.Response(body=content)
+
+    async def handle_http_photo(self, request):
+        url = request.query.get("url")
+        path = await http.create_site_photo(url)
+        return web.Response(body=path.read_bytes(),headers={
+            "Content-Type": "image/png"
+        })
+
+    async def handle_login(self, request):
+        if request.method == "GET":
+            return await self.render_template("login.html", request)    #web.json_response({"form": RegisterForm().to_json()})
+        elif request.method == "POST":
+            return await self.render_template("login.html", request)    #web.json_response({"form": RegisterForm().to_json()})
+        
 
     async def handle_register(self, request):
         if request.method == "GET":
-            return web.json_response({"form": RegisterForm().to_json()})
+            return await self.render_template("register.html", request)    #web.json_response({"form": RegisterForm().to_json()})
         elif request.method == "POST":
             return self.render("register.html")
 
+app = Application()
+
 if __name__ == '__main__':
-    app = Application()
+
     web.run_app(app,port=8081,host="0.0.0.0")
diff --git a/src/snek/gunicorn.py b/src/snek/gunicorn.py
new file mode 100644
index 0000000..4055bbd
--- /dev/null
+++ b/src/snek/gunicorn.py
@@ -0,0 +1,3 @@
+from snek.app import app 
+
+application = app
diff --git a/src/snek/http.py b/src/snek/http.py
new file mode 100644
index 0000000..0b16bee
--- /dev/null
+++ b/src/snek/http.py
@@ -0,0 +1,83 @@
+from aiohttp import web 
+import aiohttp 
+from app.cache import time_cache_async
+from bs4 import BeautifulSoup
+from urllib.parse import urljoin
+import pathlib 
+import uuid 
+import imgkit 
+import asyncio
+import zlib
+import io 
+
+async def crc32(data):
+    try:
+        data = data.encode()
+    except:
+        pass 
+    result = "crc32" + str(zlib.crc32(data))
+    return result 
+
+async def get_file(name,suffix=".cache"):
+    name = await crc32(name)
+    path = pathlib.Path(".").joinpath("cache")
+    if not path.exists():
+        path.mkdir(parents=True,exist_ok=True)
+    path = path.joinpath(name + suffix)
+    return path
+
+
+
+async def public_touch(name=None):
+    path = pathlib.Path(".").joinpath(str(uuid.uuid4())+name)
+    path.open("wb").close()
+    return path 
+
+async def create_site_photo(url):
+    loop = asyncio.get_event_loop()
+    if not url.startswith("https"):
+        url = "https://" + url
+    output_path = await get_file("site-screenshot-" + url,".png")
+    
+    if output_path.exists():
+        return output_path
+    output_path.touch()
+    def make_photo():
+        imgkit.from_url(url, output_path.absolute())
+        return output_path 
+
+    return await loop.run_in_executor(None,make_photo)
+
+async def repair_links(base_url, html_content):
+    soup = BeautifulSoup(html_content, "html.parser")
+    for tag in soup.find_all(['a', 'img', 'link']):
+        if tag.has_attr('href') and not tag['href'].startswith("http"):  # For <a> and <link> tags
+            tag['href'] = urljoin(base_url, tag['href'])
+        if tag.has_attr('src') and not tag['src'].startswith("http"):  # For <img> tags
+            tag['src'] = urljoin(base_url, tag['src'])
+            print("Fixed: ",tag['src'])
+    return soup.prettify()
+
+async def is_html_content(content: bytes):
+    try:
+        content = content.decode(errors='ignore')
+    except:
+        pass 
+    marks = ['<html','<img','<p','<span','<div']
+    try:
+        content = content.lower()
+        for mark in marks:
+            if mark in content:
+                return True 
+    except Exception as ex:
+        print(ex)
+    return False 
+
+@time_cache_async(120)
+async def get(url):
+    async with aiohttp.ClientSession() as session:
+        response = await session.get(url)
+        content = await response.text()
+        if await is_html_content(content):
+            content = (await repair_links(url,content)).encode()
+        return content
\ No newline at end of file
diff --git a/src/snek/middleware.py b/src/snek/middleware.py
new file mode 100644
index 0000000..6b801ab
--- /dev/null
+++ b/src/snek/middleware.py
@@ -0,0 +1,32 @@
+from aiohttp import web 
+
+@web.middleware
+async def no_cors_middleware(request, handler):
+    response = await handler(request)
+    response.headers.pop("Access-Control-Allow-Origin", None)
+    return response
+
+@web.middleware
+async def cors_allow_middleware(request ,handler):
+    response = await handler(request)
+    response.headers["Access-Control-Allow-Origin"] = "*"
+    response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS, PUT, DELETE"
+    response.headers["Access-Control-Allow-Headers"] = "*"
+    return response 
+
+@web.middleware
+async def cors_middleware(request, handler):
+    # Handle preflight (OPTIONS) requests
+    if request.method == "OPTIONS":
+        response = web.Response()
+        response.headers["Access-Control-Allow-Origin"] = "*"
+        response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
+        response.headers["Access-Control-Allow-Headers"] = "*"
+        return response
+
+    # Handle actual requests
+    response = await handler(request)
+    response.headers["Access-Control-Allow-Origin"] = "*"
+    response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
+    response.headers["Access-Control-Allow-Headers"] = "*"
+    return response
\ No newline at end of file
diff --git a/src/snek/static/app.js b/src/snek/static/app.js
new file mode 100644
index 0000000..701beda
--- /dev/null
+++ b/src/snek/static/app.js
@@ -0,0 +1,77 @@
+
+
+class Message {
+    uid = null 
+    author = null
+    avatar = null 
+    text = null 
+    time = null
+    constructor(uid,avatar,author,text,time){
+        this.uid = uid 
+        this.avatar = avatar 
+        this.author = author 
+        this.text = text 
+        this.time = time 
+    }
+    
+    get links() {
+        if(!this.text)
+            return []
+        let result = []
+        for(let part in this.text.split(/[,; ]/)){
+            if(part.startsWith("http") || part.startsWith("www.") || part.indexOf(".com") || part.indexOf(".net") || part.indexOf(".io") || part.indexOf(".nl")){
+                result.push(part)
+
+            }
+        }
+        return result
+    }
+    get mentions() {
+        if(!this.text)
+            return []
+        let result = []
+        for(let part in this.text.split(/[,; ]/)){
+            if(part.startsWith("@")){
+                result.push(part)
+
+            }
+        }
+        return result 
+    }
+}
+
+
+class Messages {
+
+
+
+}
+
+
+
+
+class Room {
+    name = null 
+    messages = []
+    constructor(name){
+        this.name = name 
+    }
+    setMessages(list){
+        
+    }
+
+
+}
+
+
+class App {
+    rooms = []
+    constructor() {
+        this.rooms.push(new Room("General"))
+
+
+    }
+
+
+
+}
\ No newline at end of file
diff --git a/src/snek/static/html_frame.css b/src/snek/static/html_frame.css
new file mode 100644
index 0000000..6b64c76
--- /dev/null
+++ b/src/snek/static/html_frame.css
@@ -0,0 +1,9 @@
+.html-frame {
+    width: 100px;
+    height: 50px;
+    position: relative;
+    overflow: hidden;
+    clip-path: inset(0px 0px 50px 100px); /* Crop content */
+    border: 1px solid black;
+
+}
\ No newline at end of file
diff --git a/src/snek/static/html_frame.js b/src/snek/static/html_frame.js
new file mode 100644
index 0000000..19a0c34
--- /dev/null
+++ b/src/snek/static/html_frame.js
@@ -0,0 +1,39 @@
+class HTMLFrame extends HTMLElement {
+    constructor() {
+      super();
+      this.attachShadow({ mode: 'open' });
+      this.container = document.createElement('div');
+      this.shadowRoot.appendChild(this.container);
+    }
+
+    connectedCallback() {
+        this.container.classList.add("html_frame")
+      const url = this.getAttribute('url');
+      if (url) {
+        const fullUrl = url.startsWith("/") ? window.location.origin + url : new URL(window.location.origin + "/http-get")
+        if(!url.startsWith("/"))
+            fullUrl.searchParams.set('url', url)
+        console.info(fullUrl)    
+        this.fetchAndDisplayHtml(fullUrl.toString());
+      } else {
+        this.container.textContent = "No URL provided!";
+      }
+    }
+
+    async fetchAndDisplayHtml(url) {
+      try {
+        const response = await fetch(url);
+        if (!response.ok) {
+          throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`);
+        }
+        const html = await response.text();
+        this.container.innerHTML = html; // Insert the fetched HTML into the container
+        
+    } catch (error) {
+        this.container.textContent = `Error: ${error.message}`;
+      }
+    }
+  }
+
+  // Define the custom element
+  customElements.define('html-frame', HTMLFrame);
\ No newline at end of file
diff --git a/src/snek/static/prachtig-gitter_like.html b/src/snek/static/prachtig-gitter_like.html
new file mode 100644
index 0000000..bb30554
--- /dev/null
+++ b/src/snek/static/prachtig-gitter_like.html
@@ -0,0 +1,172 @@
+/* General Reset */
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
+/* Body Styling */
+body {
+  font-family: Arial, sans-serif;
+  background-color: #1a1a1a;
+  color: #e6e6e6;
+  line-height: 1.5;
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+}
+
+/* Header Navigation */
+header {
+  background-color: #0f0f0f;
+  padding: 10px 20px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+header .logo {
+  color: #fff;
+  font-size: 1.5em;
+  font-weight: bold;
+}
+
+header nav a {
+  color: #aaa;
+  text-decoration: none;
+  margin-left: 15px;
+  font-size: 1em;
+  transition: color 0.3s;
+}
+
+header nav a:hover {
+  color: #fff;
+}
+
+/* Main Layout */
+main {
+  display: flex;
+  flex: 1;
+  overflow: hidden;
+}
+
+/* Sidebar */
+.sidebar {
+  width: 250px;
+  background-color: #121212;
+  padding: 20px;
+  overflow-y: auto;
+  border-right: 1px solid #333;
+}
+
+.sidebar h2 {
+  color: #f05a28;
+  font-size: 1.2em;
+  margin-bottom: 20px;
+}
+
+.sidebar ul {
+  list-style: none;
+}
+
+.sidebar ul li {
+  margin-bottom: 15px;
+}
+
+.sidebar ul li a {
+  color: #ccc;
+  text-decoration: none;
+  font-size: 1em;
+  transition: color 0.3s;
+}
+
+.sidebar ul li a:hover {
+  color: #fff;
+}
+
+/* Chat Area */
+.chat-area {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  background-color: #1a1a1a;
+}
+
+.chat-header {
+  padding: 10px 20px;
+  background-color: #0f0f0f;
+  border-bottom: 1px solid #333;
+}
+
+.chat-header h2 {
+  font-size: 1.2em;
+  color: #fff;
+}
+
+.chat-messages {
+  flex: 1;
+  padding: 20px;
+  overflow-y: auto;
+  background: #1a1a1a;
+}
+
+.chat-messages .message {
+  margin-bottom: 15px;
+}
+
+.chat-messages .message .author {
+  font-weight: bold;
+  color: #f05a28;
+}
+
+.chat-messages .message .content {
+  margin-left: 10px;
+  color: #e6e6e6;
+}
+
+/* Input Area */
+.chat-input {
+  padding: 15px;
+  background-color: #121212;
+  display: flex;
+  align-items: center;
+  border-top: 1px solid #333;
+}
+
+.chat-input textarea {
+  flex: 1;
+  background-color: #1a1a1a;
+  color: #fff;
+  border: none;
+  padding: 10px;
+  border-radius: 5px;
+  resize: none;
+}
+
+.chat-input button {
+  background-color: #f05a28;
+  color: white;
+  border: none;
+  padding: 10px 15px;
+  margin-left: 10px;
+  border-radius: 5px;
+  cursor: pointer;
+  font-size: 1em;
+  transition: background-color 0.3s;
+}
+
+.chat-input button:hover {
+  background-color: #e04924;
+}
+
+/* Responsive Adjustments */
+@media (max-width: 768px) {
+  .sidebar {
+    display: none;
+  }
+
+  .chat-area {
+    flex: 1;
+  }
+}
+
diff --git a/src/snek/static/register.css b/src/snek/static/register.css
new file mode 100644
index 0000000..fc4ca0f
--- /dev/null
+++ b/src/snek/static/register.css
@@ -0,0 +1,95 @@
+/* General Reset */
+* {
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+  }
+  
+  /* Body Styling */
+  body {
+    font-family: Arial, sans-serif;
+    background-color: #1a1a1a;
+    color: #e6e6e6;
+    line-height: 1.5;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    height: 100vh;
+  }
+  
+  /* Registration Form Container */
+  .registration-container {
+    background-color: #0f0f0f;
+    border-radius: 10px;
+    padding: 30px;
+    width: 400px;
+    box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
+    text-align: center;
+  }
+  
+  /* Form Heading */
+  .registration-container h1 {
+    font-size: 2em;
+    color: #f05a28;
+    margin-bottom: 20px;
+  }
+  
+  /* Input Fields */
+  .registration-container input {
+    width: 100%;
+    padding: 10px;
+    margin: 10px 0;
+    border: 1px solid #333;
+    border-radius: 5px;
+    background-color: #1a1a1a;
+    color: #e6e6e6;
+    font-size: 1em;
+  }
+  
+  /* Submit Button */
+  .registration-container button {
+    width: 100%;
+    padding: 10px;
+    background-color: #f05a28;
+    border: none;
+    border-radius: 5px;
+    color: white;
+    font-size: 1em;
+    font-weight: bold;
+    cursor: pointer;
+    transition: background-color 0.3s;
+  }
+  
+  .registration-container button:hover {
+    background-color: #e04924;
+  }
+  
+  /* Links */
+  .registration-container a {
+    color: #f05a28;
+    text-decoration: none;
+    display: block;
+    margin-top: 15px;
+    font-size: 0.9em;
+    transition: color 0.3s;
+  }
+  
+  .registration-container a:hover {
+    color: #e04924;
+  }
+  
+  /* Error Message Styling */
+  .error {
+    color: #d8000c;
+    font-size: 0.9em;
+    margin-top: 5px;
+  }
+  
+  /* Responsive Design */
+  @media (max-width: 500px) {
+    .registration-container {
+      width: 90%;
+    }
+  }
+  
\ No newline at end of file
diff --git a/src/snek/static/styles.css b/src/snek/static/styles.css
new file mode 100644
index 0000000..83c1fb1
--- /dev/null
+++ b/src/snek/static/styles.css
@@ -0,0 +1,203 @@
+/* General Reset */
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
+/* Body Styling */
+body {
+  font-family: Arial, sans-serif;
+  background-color: #1a1a1a;
+  color: #e6e6e6;
+  line-height: 1.5;
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+}
+
+/* Header Navigation */
+header {
+  background-color: #0f0f0f;
+  padding: 10px 20px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+header .logo {
+  color: #fff;
+  font-size: 1.5em;
+  font-weight: bold;
+}
+
+header nav a {
+  color: #aaa;
+  text-decoration: none;
+  margin-left: 15px;
+  font-size: 1em;
+  transition: color 0.3s;
+}
+
+header nav a:hover {
+  color: #fff;
+}
+
+/* Main Layout */
+main {
+  display: flex;
+  flex: 1;
+  overflow: hidden;
+}
+
+/* Sidebar */
+.sidebar {
+  width: 250px;
+  background-color: #121212;
+  padding: 20px;
+  overflow-y: auto;
+  border-right: 1px solid #333;
+}
+
+.sidebar h2 {
+  color: #f05a28;
+  font-size: 1.2em;
+  margin-bottom: 20px;
+}
+
+.sidebar ul {
+  list-style: none;
+}
+
+.sidebar ul li {
+  margin-bottom: 15px;
+}
+
+.sidebar ul li a {
+  color: #ccc;
+  text-decoration: none;
+  font-size: 1em;
+  transition: color 0.3s;
+}
+
+.sidebar ul li a:hover {
+  color: #fff;
+}
+
+/* Chat Area */
+.chat-area {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  background-color: #1a1a1a;
+}
+
+.chat-header {
+  padding: 10px 20px;
+  background-color: #0f0f0f;
+  border-bottom: 1px solid #333;
+}
+
+.chat-header h2 {
+  font-size: 1.2em;
+  color: #fff;
+}
+
+/* Chat Messages */
+.chat-messages {
+  flex: 1;
+  padding: 20px;
+  overflow-y: auto;
+  background: #1a1a1a;
+}
+
+.chat-messages .message {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 15px;
+  padding: 10px;
+  background: #222;
+  border-radius: 8px;
+  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
+}
+
+.chat-messages .message .avatar {
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  background: #f05a28;
+  color: #fff;
+  font-size: 1em;
+  font-weight: bold;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-right: 15px;
+}
+
+.chat-messages .message .message-content {
+  flex: 1;
+}
+
+.chat-messages .message .message-content .author {
+  font-weight: bold;
+  color: #f05a28;
+  margin-bottom: 3px;
+}
+
+.chat-messages .message .message-content .text {
+  margin-bottom: 5px;
+  color: #e6e6e6;
+}
+
+.chat-messages .message .message-content .time {
+  font-size: 0.8em;
+  color: #aaa;
+}
+
+/* Input Area */
+.chat-input {
+  padding: 15px;
+  background-color: #121212;
+  display: flex;
+  align-items: center;
+  border-top: 1px solid #333;
+}
+
+.chat-input textarea {
+  flex: 1;
+  background-color: #1a1a1a;
+  color: white;
+  border: none;
+  padding: 10px;
+  border-radius: 5px;
+  resize: none;
+}
+
+.chat-input button {
+  background-color: #f05a28;
+  color: white;
+  border: none;
+  padding: 10px 15px;
+  margin-left: 10px;
+  border-radius: 5px;
+  cursor: pointer;
+  font-size: 1em;
+  transition: background-color 0.3s;
+}
+
+.chat-input button:hover {
+  background-color: #e04924;
+}
+
+/* Responsive Adjustments */
+@media (max-width: 768px) {
+  .sidebar {
+    display: none;
+  }
+
+  .chat-area {
+    flex: 1;
+  }
+}
+
diff --git a/src/snek/templates/login.html b/src/snek/templates/login.html
new file mode 100644
index 0000000..d37f3fa
--- /dev/null
+++ b/src/snek/templates/login.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Register</title>
+  <link rel="stylesheet" href="register.css">
+</head>
+<body>
+  <div class="registration-container">
+    <h1>Login</h1>
+    <form>
+      <input type="text" name="username" placeholder="Username or password" required>
+      <input type="password" name="password" placeholder="Password" required>
+      <button type="submit">Create Account</button>
+      <a href="/register">Not having an account yet? Register here.</a>
+    </form>
+  </div>
+</body>
+</html>
diff --git a/src/snek/templates/prachtig_gitter_like.html b/src/snek/templates/prachtig_gitter_like.html
new file mode 100644
index 0000000..cd2d863
--- /dev/null
+++ b/src/snek/templates/prachtig_gitter_like.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Dark Themed Chat Application</title>
+  <link rel="stylesheet" href="styles.css">
+</head>
+<body>
+  <header>
+    <div class="logo">Molodetz Chat</div>
+    <nav>
+      <a href="#">Home</a>
+      <a href="#">Rooms</a>
+      <a href="#">Settings</a>
+      <a href="#">Logout</a>
+    </nav>
+  </header>
+  <main>
+    <aside class="sidebar">
+      <h2>Chat Rooms</h2>
+      <ul>
+        <li><a href="#">General</a></li>
+        <li><a href="#">Development</a></li>
+        <li><a href="#">Support</a></li>
+        <li><a href="#">Random</a></li>
+      </ul>
+    </aside>
+    <section class="chat-area">
+      <div class="chat-header">
+        <h2>General</h2>
+      </div>
+      <div class="chat-messages">
+        <div class="message">
+          <span class="author">Alice:</span>
+          <span class="content">Hello, everyone!</span>
+        </div>
+        <div class="message">
+          <span class="author">Bob:</span>
+          <span class="content">Hi Alice! How are you?</span>
+        </div>
+      </div>
+      <div class="chat-input">
+        <textarea placeholder="Type a message..." rows="2"></textarea>
+        <button>Send</button>
+      </div>
+    </section>
+  </main>
+</body>
+</html>
+
diff --git a/src/snek/templates/register.html b/src/snek/templates/register.html
new file mode 100644
index 0000000..da41629
--- /dev/null
+++ b/src/snek/templates/register.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Register</title>
+  <link rel="stylesheet" href="register.css">
+</head>
+<body>
+  <div class="registration-container">
+    <h1>Register</h1>
+    <form>
+      <input type="text" name="username" placeholder="Username" required>
+      <input type="email" name="email" placeholder="Email Address" required>
+      <input type="password" name="password" placeholder="Password" required>
+      <input type="password" name="confirm_password" placeholder="Confirm Password" required>
+      <button type="submit">Create Account</button>
+      <a href="#">Already have an account? Login here.</a>
+    </form>
+  </div>
+</body>
+</html>
diff --git a/src/snek/templates/test.html b/src/snek/templates/test.html
new file mode 100644
index 0000000..c23103a
--- /dev/null
+++ b/src/snek/templates/test.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Dark Themed Chat Application</title>
+  <link rel="stylesheet" href="styles.css">
+  <script src="/html_frame.js"></script>
+  <script src="/html_frame.css"></script>
+</head>
+<body>
+  <header>
+    <div class="logo">Molodetz Chat</div>
+    <nav>
+      <a href="#">Home</a>
+      <a href="#">Rooms</a>
+      <a href="#">Settings</a>
+      <a href="#">Logout</a>
+    </nav>
+  </header>
+  <main>
+    <aside class="sidebar">
+      <h2>Chat Rooms</h2>
+      <ul>
+        <li><a href="#">General</a></li>
+        <li><a href="#">Development</a></li>
+        <li><a href="#">Support</a></li>
+        <li><a href="#">Random</a></li>
+      </ul>
+    </aside>
+    <section class="chat-area">
+      <div class="chat-header">
+        <h2>General</h2>
+      </div>
+      <div class="chat-messages">
+        <div class="message">
+          <div class="avatar">A</div>
+          <div class="message-content">
+            <div class="author">Alice</div>
+            <div class="text">Hello, everyone!</div>
+            <div class="time">10:45 AM</div>
+          </div>
+        </div>
+        <html-frame class="html-frame" url="/register"></html-frame>
+        <div class="message">
+          <div class="avatar">B</div>
+          <div class="message-content">
+            <div class="author">Bob</div>
+            <div class="text">Hi Alice! How are you?</div>
+            <div class="time">10:46 AM</div>
+          </div>
+        </div>
+      </div>
+      <div class="chat-input">
+        <textarea placeholder="Type a message..." rows="2"></textarea>
+        <button>Send</button>
+      </div>
+    </section>
+  </main>
+  
+</body>
+</html>
+
diff --git a/src/snek/templates/test2.html b/src/snek/templates/test2.html
new file mode 100644
index 0000000..9aad83a
--- /dev/null
+++ b/src/snek/templates/test2.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Dynamic Form Component</title>
+  <style>
+    .form-container {
+      max-width: 400px;
+      margin: 20px auto;
+      padding: 20px;
+      border: 1px solid #ccc;
+      border-radius: 5px;
+      background-color: #f9f9f9;
+    }
+    .form-field {
+      margin-bottom: 15px;
+    }
+    .form-field label {
+      font-weight: bold;
+      display: block;
+      margin-bottom: 5px;
+    }
+    .form-field input {
+      width: 100%;
+      padding: 8px;
+      box-sizing: border-box;
+      border: 1px solid #ddd;
+      border-radius: 3px;
+    }
+    .form-field .error {
+      color: red;
+      font-size: 0.9em;
+      margin-top: 5px;
+    }
+  </style>
+</head>
+<body>
+  <!-- Use the custom form component -->
+  <dynamic-form></dynamic-form>
+
+  <script>
+    class DynamicForm extends HTMLElement {
+      constructor() {
+        super();
+        this.attachShadow({ mode: 'open' });
+      }
+
+      connectedCallback() {
+        // Sample data for the form
+        const formData = {
+          form: {
+            uid: { required: true, value: "e13ad3b7-20b2-4c1a-b74e-b8c7d1abd107", html_type: "text", place_holder: "UID", is_valid: true },
+            created_at: { required: true, value: "2025-01-17 21:21:27.561769+00:00", html_type: "text", place_holder: "Created At", is_valid: true },
+            updated_at: { required: false, value: null, html_type: "text", place_holder: "Updated At", is_valid: true },
+            deleted_at: { required: false, value: null, html_type: "text", place_holder: "Deleted At", is_valid: true },
+            email: { required: true, value: null, html_type: "email", place_holder: "Email address", errors: ["Field is required."], is_valid: false },
+            password: { required: true, value: null, html_type: "password", place_holder: "Password", errors: ["Field is required."], is_valid: false },
+            username: { required: true, value: null, html_type: "text", place_holder: "Username", errors: ["Field is required."], is_valid: false }
+          }
+        };
+
+        // Render the form
+        this.render(formData);
+      }
+
+      render(data) {
+        const form = data.form;
+        const container = document.createElement('div');
+        container.className = 'form-container';
+
+        // Create a form element
+        const formElement = document.createElement('form');
+
+        // Generate form fields from the data
+        Object.entries(form).forEach(([fieldName, fieldData]) => {
+          const fieldContainer = document.createElement('div');
+          fieldContainer.className = 'form-field';
+
+          // Add label
+          const label = document.createElement('label');
+          label.textContent = fieldName.replace(/_/g, ' ').toUpperCase();
+          label.htmlFor = fieldName;
+          fieldContainer.appendChild(label);
+
+          // Add input field
+          const input = document.createElement('input');
+          input.type = fieldData.html_type || 'text';
+          input.name = fieldName;
+          input.value = fieldData.value || '';
+          input.placeholder = fieldData.place_holder || '';
+          input.required = fieldData.required || false;
+
+          // Append input to the container
+          fieldContainer.appendChild(input);
+
+          // Display validation errors
+          if (fieldData.errors && fieldData.errors.length > 0) {
+            const errorDiv = document.createElement('div');
+            errorDiv.className = 'error';
+            errorDiv.textContent = fieldData.errors.join(', ');
+            fieldContainer.appendChild(errorDiv);
+          }
+
+          // Append field to the form
+          formElement.appendChild(fieldContainer);
+        });
+
+        // Add the form to the container
+        container.appendChild(formElement);
+
+        // Append the container to the shadow DOM
+        this.shadowRoot.appendChild(container);
+      }
+    }
+
+    // Define the custom element
+    customElements.define('dynamic-form', DynamicForm);
+  </script>
+</body>
+</html>
+