Index: lan-frontend/src/App.jsx
===================================================================
--- lan-frontend/src/App.jsx	(revision e4c61dd6cd86e06265bc2bd91adba84a0f04044a)
+++ lan-frontend/src/App.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
@@ -1,4 +1,5 @@
-import React, { useEffect, useMemo, useState } from "react";
+import React, {useEffect, useMemo, useState} from "react";
 import "./styles/theme.css";
+import "./styles/netinel.css";
 
 import Login from "./components/Login";
@@ -7,270 +8,329 @@
 import ComputerDetailsModal from "./components/ComputerDetailsModal";
 import AIAssistantDrawer from "./components/AIAssistantDrawer";
+import NetIntelViz from "./components/NetIntelViz.jsx";
+import SideMenu from "./components/SideMenu";
+
 
 export default function App() {
-  const [stats, setStats] = useState(null);
-  const [computers, setComputers] = useState([]);
-  const [loading, setLoading] = useState(true);
-  const [lastUpdated, setLastUpdated] = useState(null);
-
-  const [query, setQuery] = useState("");
-  const [status, setStatus] = useState("");
-
-  const [selectedComputer, setSelectedComputer] = useState(null);
-  const [aiOpen, setAiOpen] = useState(false);
-  const [aiScope, setAiScope] = useState(null);
-  const [selectedEnv, setSelectedEnv] = useState("default");
-
-  const [me, setMe] = useState(null);
-  const [meLoading, setMeLoading] = useState(true);
-
-  // NEW: environment setting (switch)
-  const [saveProcHistory, setSaveProcHistory] = useState(false);
-
-  async function loadMe() {
-    setMeLoading(true);
-    try {
-      const r = await fetch("/api/me", { credentials: "include" });
-      if (r.ok) {
-        const data = await r.json();
-        setMe(data.user);
-      } else {
-        setMe(null);
-      }
-    } catch (e) {
-      console.error("loadMe error", e);
-      setMe(null);
-    } finally {
-      setMeLoading(false);
+    const [stats, setStats] = useState(null);
+    const [computers, setComputers] = useState([]);
+    const [loading, setLoading] = useState(true);
+    const [lastUpdated, setLastUpdated] = useState(null);
+
+    const [query, setQuery] = useState("");
+    const [status, setStatus] = useState("");
+
+    const [selectedComputer, setSelectedComputer] = useState(null);
+    const [aiOpen, setAiOpen] = useState(false);
+    const [aiScope, setAiScope] = useState(null);
+    const [selectedEnv, setSelectedEnv] = useState("default");
+
+    const [me, setMe] = useState(null);
+    const [meLoading, setMeLoading] = useState(true);
+
+    const [activeView, setActiveView] = useState("dashboard");
+// "dashboard" | "visualizations" | "account"
+
+
+    // NEW: environment setting (switch)
+    const [saveProcHistory, setSaveProcHistory] = useState(false);
+
+    async function loadMe() {
+        setMeLoading(true);
+        try {
+            const r = await fetch("/api/me", {credentials: "include"});
+            if (r.ok) {
+                const data = await r.json();
+                setMe(data.user);
+            } else {
+                setMe(null);
+            }
+        } catch (e) {
+            console.error("loadMe error", e);
+            setMe(null);
+        } finally {
+            setMeLoading(false);
+        }
     }
-  }
-
-  async function refresh() {
-    try {
-      setLoading(true);
-      const [s, c] = await Promise.all([
-        fetch("/api/stats", { credentials: "include" }).then((r) => r.json()),
-        fetch("/api/computers", {
-          credentials: "include",
-          headers: { "X-Env": selectedEnv },
-        }).then((r) => r.json()),
-      ]);
-
-      setStats(s);
-      setComputers(Array.isArray(c) ? c : []);
-      setLastUpdated(new Date().toISOString());
-    } catch (e) {
-      console.error("refresh error", e);
-    } finally {
-      setLoading(false);
+
+    async function refresh() {
+        try {
+            setLoading(true);
+            const [s, c] = await Promise.all([
+                fetch("/api/stats", {credentials: "include"}).then((r) => r.json()),
+                fetch("/api/computers", {
+                    credentials: "include",
+                    headers: {"X-Env": selectedEnv},
+                }).then((r) => r.json()),
+            ]);
+
+            setStats(s);
+            setComputers(Array.isArray(c) ? c : []);
+            setLastUpdated(new Date().toISOString());
+        } catch (e) {
+            console.error("refresh error", e);
+        } finally {
+            setLoading(false);
+        }
     }
-  }
-
-  // NEW: load env settings (switch state) for selected env
-  async function loadEnvSettings(env) {
-    try {
-      // само admin ќе има пристап (ако не си admin, ќе падне во else и ќе стане false)
-      const r = await fetch(`/api/admin/env-settings/${encodeURIComponent(env)}`, {
-        credentials: "include",
-      });
-
-      if (r.ok) {
-        const data = await r.json();
-        setSaveProcHistory(!!data.save_process_history);
-      } else {
-        setSaveProcHistory(false);
-      }
-    } catch (e) {
-      console.error("loadEnvSettings error", e);
-      setSaveProcHistory(false);
+
+    // NEW: load env settings (switch state) for selected env
+    async function loadEnvSettings(env) {
+        try {
+            // само admin ќе има пристап (ако не си admin, ќе падне во else и ќе стане false)
+            const r = await fetch(`/api/admin/env-settings/${encodeURIComponent(env)}`, {
+                credentials: "include",
+            });
+
+            if (r.ok) {
+                const data = await r.json();
+                setSaveProcHistory(!!data.save_process_history);
+            } else {
+                setSaveProcHistory(false);
+            }
+        } catch (e) {
+            console.error("loadEnvSettings error", e);
+            setSaveProcHistory(false);
+        }
     }
-  }
-
-  // NEW: set env setting (toggle)
-  async function setEnvProcessHistory(env, enabled) {
-    // optimistic UI
-    setSaveProcHistory(enabled);
-
-    try {
-      const r = await fetch(`/api/admin/env-settings/${encodeURIComponent(env)}`, {
-        method: "POST",
-        credentials: "include",
-        headers: { "Content-Type": "application/json" },
-        body: JSON.stringify({ save_process_history: enabled }),
-      });
-
-      if (r.ok) {
-        const data = await r.json();
-        setSaveProcHistory(!!data.save_process_history);
-      } else {
-        // rollback
-        setSaveProcHistory(!enabled);
-        console.error("setEnvProcessHistory failed", await r.text());
-      }
-    } catch (e) {
-      setSaveProcHistory(!enabled);
-      console.error("setEnvProcessHistory error", e);
+
+    // NEW: set env setting (toggle)
+    async function setEnvProcessHistory(env, enabled) {
+        // optimistic UI
+        setSaveProcHistory(enabled);
+
+        try {
+            const r = await fetch(`/api/admin/env-settings/${encodeURIComponent(env)}`, {
+                method: "POST",
+                credentials: "include",
+                headers: {"Content-Type": "application/json"},
+                body: JSON.stringify({save_process_history: enabled}),
+            });
+
+            if (r.ok) {
+                const data = await r.json();
+                setSaveProcHistory(!!data.save_process_history);
+            } else {
+                // rollback
+                setSaveProcHistory(!enabled);
+                console.error("setEnvProcessHistory failed", await r.text());
+            }
+        } catch (e) {
+            setSaveProcHistory(!enabled);
+            console.error("setEnvProcessHistory error", e);
+        }
     }
-  }
-
-  // on mount: check session
-  useEffect(() => {
-    loadMe();
-  }, []);
-
-  // when logged in OR env changed, refresh data + load env switch state
-  useEffect(() => {
-    if (!me) return;
-    loadEnvSettings(selectedEnv);
-    refresh();
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [me, selectedEnv]);
-
-  const filtered = useMemo(() => {
-    const q = query.trim().toLowerCase();
-    return (computers || [])
-      .filter((c) => (status ? c.status === status : true))
-      .filter((c) => {
-        if (!q) return true;
-        const hay = [c.name, c.user, c.ip, c.os, c.status]
-          .filter(Boolean)
-          .join(" ")
-          .toLowerCase();
-        return hay.includes(q);
-      });
-  }, [computers, query, status]);
-
-  // Gate UI
-  if (meLoading) return <div style={{ padding: 20 }}>Loading…</div>;
-  if (!me) return <Login onDone={loadMe} />;
-
-  return (
-    <div className="app-wrap">
-      <TopBar
-        onOpenAI={() => setAiOpen(true)}
-        onRefresh={refresh}
-        lastUpdated={lastUpdated}
-        onEnvChange={(env) => setSelectedEnv(env)}
-        selectedEnv={selectedEnv}
-        me={me}
-        onLoggedOut={loadMe}
-        // NEW props for switch:
-        saveProcHistory={saveProcHistory}
-        onToggleProcHistory={(enabled) => setEnvProcessHistory(selectedEnv, enabled)}
-      />
-
-      {/* STATS */}
-      <div className="stats-grid">
-        <div className="stat-card">
-          <div className="label">Total Computers</div>
-          <div className="value">{computers.length}</div>
-          <div className="sub">Monitored Systems</div>
+
+    // on mount: check session
+    useEffect(() => {
+        loadMe();
+    }, []);
+
+    // when logged in OR env changed, refresh data + load env switch state
+    useEffect(() => {
+        if (!me) return;
+        loadEnvSettings(selectedEnv);
+        refresh();
+        // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, [me, selectedEnv]);
+
+    const filtered = useMemo(() => {
+        const q = query.trim().toLowerCase();
+        return (computers || [])
+            .filter((c) => (status ? c.status === status : true))
+            .filter((c) => {
+                if (!q) return true;
+                const hay = [c.name, c.user, c.ip, c.os, c.status]
+                    .filter(Boolean)
+                    .join(" ")
+                    .toLowerCase();
+                return hay.includes(q);
+            });
+    }, [computers, query, status]);
+
+    // Gate UI
+    if (meLoading) return <div style={{padding: 20}}>Loading…</div>;
+    if (!me) return <Login onDone={loadMe}/>;
+
+    return (
+        <div className="app-shell">
+            <div className="layout">
+                <SideMenu activeView={activeView} setActiveView={setActiveView}/>
+                <main className="content">
+                    {/* TopBar + pages */}
+                </main>
+            </div>
+
+            <div className="cont">
+                <TopBar
+                    onOpenAI={() => setAiOpen(true)}
+                    onRefresh={refresh}
+                    lastUpdated={lastUpdated}
+                    onEnvChange={(env) => setSelectedEnv(env)}
+                    selectedEnv={selectedEnv}
+                    me={me}
+                    onLoggedOut={loadMe}
+                    saveProcHistory={saveProcHistory}
+                    onToggleProcHistory={(enabled) => setEnvProcessHistory(selectedEnv, enabled)}
+                />
+
+                <div className="app-main">
+                    {/* ====== VIEW SWITCH ====== */}
+                    {activeView === "dashboard" && (
+                        <>
+                            {/* STATS */}
+                            <div className="stats-grid">
+                                <div className="stat-card">
+                                    <div className="label">Total Computers</div>
+                                    <div className="value">{computers.length}</div>
+                                    <div className="sub">Monitored Systems</div>
+                                </div>
+
+                                <div className="stat-card">
+                                    <div className="label">Sysmon Events (24h)</div>
+                                    <div className="value">{stats?.total_sysmon_events ?? "—"}</div>
+                                    <div className="sub">Security Events</div>
+                                </div>
+
+                                <div className="stat-card">
+                                    <div className="label">Network Connections (24h)</div>
+                                    <div className="value">{stats?.total_network_connections ?? "—"}</div>
+                                    <div className="sub">Captured Connections</div>
+                                </div>
+                            </div>
+
+                            {/* FILTERS */}
+                            <div className="panel" style={{marginBottom: 14}}>
+                                <div className="panel-head">
+                                    <div className="panel-title">Filters</div>
+                                    <div style={{color: "var(--dim)", fontSize: 12}}>
+                                        {loading ? "Refreshing…" : `${filtered.length} shown`}
+                                    </div>
+                                </div>
+                                <div className="panel-body">
+                                    <div className="filters">
+                                        <input
+                                            className="input"
+                                            value={query}
+                                            onChange={(e) => setQuery(e.target.value)}
+                                            placeholder="Search by name / user / ip / os…"
+                                        />
+
+                                        <select
+                                            className="select"
+                                            value={status}
+                                            onChange={(e) => setStatus(e.target.value)}
+                                        >
+                                            <option value="">All status</option>
+                                            <option value="online">Online</option>
+                                            <option value="idle">Idle</option>
+                                            <option value="offline">Offline</option>
+                                        </select>
+
+                                        <button className="btn primary" onClick={refresh}>
+                                            Refresh
+                                        </button>
+                                    </div>
+                                </div>
+                            </div>
+
+                            {/* COMPUTERS */}
+                            <div className="panel">
+                                <div className="panel-head">
+                                    <div className="panel-title">Connected Computers</div>
+                                    <div style={{display: "flex", gap: 10, alignItems: "center"}}>
+                                        <span className="pill">{filtered.length} computers</span>
+                                        <button
+                                            className="btn"
+                                            onClick={() => {
+                                                setQuery("");
+                                                setStatus("");
+                                            }}
+                                        >
+                                            Reset
+                                        </button>
+                                    </div>
+                                </div>
+                                <div className="panel-body">
+                                    <div className="computers-grid">
+                                        {filtered.map((c) => (
+                                            <ComputerCard
+                                                key={c.name}
+                                                c={c}
+                                                onOpen={(pc) => {
+                                                    setSelectedComputer(pc);
+                                                    setAiScope(pc.name);
+                                                }}
+                                            />
+                                        ))}
+                                        {filtered.length === 0 && (
+                                            <div style={{color: "var(--dim)"}}>
+                                                No computers match filter.
+                                            </div>
+                                        )}
+                                    </div>
+                                </div>
+                            </div>
+                        </>
+                    )}
+
+                    {activeView === "visualizations" && (
+                        <>
+                            <div className="panel" style={{marginBottom: 14}}>
+                                <div className="panel-head">
+                                    <div className="panel-title">Visualizations</div>
+                                    <div style={{color: "var(--dim)", fontSize: 12}}>
+                                        Network graphs / charts / tables
+                                    </div>
+                                </div>
+                                <div className="panel-body">
+                                    <NetIntelViz baseUrl="http://localhost:5555"/>
+                                </div>
+                            </div>
+                        </>
+                    )}
+
+                    {activeView === "account" && (
+                        <div className="panel">
+                            <div className="panel-head">
+                                <div className="panel-title">Account</div>
+                                <div style={{color: "var(--dim)", fontSize: 12}}>
+                                    Profile info
+                                </div>
+                            </div>
+                            <div className="panel-body">
+                                <div style={{display: "grid", gap: 10}}>
+                                    <div className="pill">Email: {me.email}</div>
+                                    <div className="pill">Role: {me.role}</div>
+                                    <div className="pill">Tenant ID: {me.tenant_id}</div>
+                                    <div className="pill">Name: {me.name || "—"}</div>
+                                </div>
+                            </div>
+                        </div>
+                    )}
+                </div>
+
+                {/* DETAILS MODAL */}
+                <ComputerDetailsModal
+                    open={!!selectedComputer}
+                    computer={selectedComputer}
+                    onClose={() => setSelectedComputer(null)}
+                    onAskAI={(name) => {
+                        setAiScope(name);
+                        setAiOpen(true);
+                    }}
+                />
+
+                {/* AI DRAWER */}
+                <AIAssistantDrawer
+                    open={aiOpen}
+                    onClose={() => setAiOpen(false)}
+                    computers={computers}
+                    scope={aiScope}
+                    setScope={setAiScope}
+                    apiBase={import.meta.env.VITE_API_BASE}
+                />
+            </div>
         </div>
-
-        <div className="stat-card">
-          <div className="label">Sysmon Events (24h)</div>
-          <div className="value">{stats?.total_sysmon_events ?? "—"}</div>
-          <div className="sub">Security Events</div>
-        </div>
-
-        <div className="stat-card">
-          <div className="label">Network Connections (24h)</div>
-          <div className="value">{stats?.total_network_connections ?? "—"}</div>
-          <div className="sub">Captured Connections</div>
-        </div>
-      </div>
-
-      {/* FILTERS */}
-      <div className="panel" style={{ marginBottom: 14 }}>
-        <div className="panel-head">
-          <div className="panel-title">Filters</div>
-          <div style={{ color: "var(--dim)", fontSize: 12 }}>
-            {loading ? "Refreshing…" : `${filtered.length} shown`}
-          </div>
-        </div>
-        <div className="panel-body">
-          <div className="filters">
-            <input
-              className="input"
-              value={query}
-              onChange={(e) => setQuery(e.target.value)}
-              placeholder="Search by name / user / ip / os…"
-            />
-
-            <select
-              className="select"
-              value={status}
-              onChange={(e) => setStatus(e.target.value)}
-            >
-              <option value="">All status</option>
-              <option value="online">Online</option>
-              <option value="idle">Idle</option>
-              <option value="offline">Offline</option>
-            </select>
-
-            <button className="btn primary" onClick={refresh}>
-              Refresh
-            </button>
-          </div>
-        </div>
-      </div>
-
-      {/* COMPUTERS */}
-      <div className="panel">
-        <div className="panel-head">
-          <div className="panel-title">Connected Computers</div>
-          <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
-            <span className="pill">{filtered.length} computers</span>
-            <button
-              className="btn"
-              onClick={() => {
-                setQuery("");
-                setStatus("");
-              }}
-            >
-              Reset
-            </button>
-          </div>
-        </div>
-        <div className="panel-body">
-          <div className="computers-grid">
-            {filtered.map((c) => (
-              <ComputerCard
-                key={c.name}
-                c={c}
-                onOpen={(pc) => {
-                  setSelectedComputer(pc);
-                  setAiScope(pc.name);
-                }}
-              />
-            ))}
-            {filtered.length === 0 && (
-              <div style={{ color: "var(--dim)" }}>No computers match filter.</div>
-            )}
-          </div>
-        </div>
-      </div>
-
-      {/* DETAILS MODAL */}
-      <ComputerDetailsModal
-        open={!!selectedComputer}
-        computer={selectedComputer}
-        onClose={() => setSelectedComputer(null)}
-        onAskAI={(name) => {
-          setAiScope(name);
-          setAiOpen(true);
-        }}
-      />
-
-      {/* AI DRAWER */}
-      <AIAssistantDrawer
-        open={aiOpen}
-        onClose={() => setAiOpen(false)}
-        computers={computers}
-        scope={aiScope}
-        setScope={setAiScope}
-        apiBase={import.meta.env.VITE_API_BASE}
-      />
-    </div>
-  );
+    );
 }
Index: lan-frontend/src/components/NetIntelViz.jsx
===================================================================
--- lan-frontend/src/components/NetIntelViz.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ lan-frontend/src/components/NetIntelViz.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
@@ -0,0 +1,653 @@
+import React, { useEffect, useMemo, useState } from "react";
+import {
+  LineChart,
+  Line,
+  XAxis,
+  YAxis,
+  Tooltip,
+  Legend,
+  ResponsiveContainer,
+  CartesianGrid,
+} from "recharts";
+
+const VIZ = {
+  STATS: "stats",
+  COMPUTERS: "computers",
+  COMPUTER_DETAILS: "computer_details",
+  NET_GRAPH: "net_graph",
+};
+
+function fmtTs(ts) {
+  if (!ts) return "";
+  return String(ts).replace("T", " ").replace("Z", "");
+}
+
+async function fetchJson(url, { method = "GET", headers = {}, body } = {}) {
+  const res = await fetch(url, {
+    method,
+    credentials: "include",
+    headers: {
+      "Content-Type": "application/json",
+      ...headers,
+    },
+    body: body ? JSON.stringify(body) : undefined,
+  });
+
+  const text = await res.text();
+  let data;
+  try {
+    data = text ? JSON.parse(text) : null;
+  } catch {
+    data = { raw: text };
+  }
+  if (!res.ok) {
+    const msg = data?.error || data?.message || res.statusText;
+    throw new Error(msg);
+  }
+  return data;
+}
+
+/** ✅ Use existing theme.css look: panel / btn / select / input / pill */
+function Panel({ title, right, children, subtitle }) {
+  return (
+    <div className="panel" style={{ marginBottom: 14 }}>
+      <div className="panel-head">
+        <div>
+          <div className="panel-title">{title}</div>
+          {subtitle ? (
+            <div style={{ color: "var(--dim)", fontSize: 12, marginTop: 4 }}>{subtitle}</div>
+          ) : null}
+        </div>
+        {right}
+      </div>
+      <div className="panel-body">{children}</div>
+    </div>
+  );
+}
+
+function Select({ label, value, onChange, options, disabled }) {
+  return (
+    <label style={{ display: "grid", gap: 6 }}>
+      <div style={{ fontSize: 12, color: "var(--dim)" }}>{label}</div>
+      <select
+        className="select"
+        value={value}
+        disabled={disabled}
+        onChange={(e) => onChange(e.target.value)}
+      >
+        {options.map((o) => (
+          <option key={o.value} value={o.value}>
+            {o.label}
+          </option>
+        ))}
+      </select>
+    </label>
+  );
+}
+
+function Pill({ children }) {
+  return <span className="pill">{children}</span>;
+}
+
+function Table({ columns, rows, emptyText = "Нема податоци." }) {
+  return (
+    <div style={{ overflow: "auto" }}>
+      <table className="table">
+        <thead>
+          <tr>
+            {columns.map((c) => (
+              <th key={c.key}>{c.title}</th>
+            ))}
+          </tr>
+        </thead>
+        <tbody>
+          {rows.length === 0 ? (
+            <tr>
+              <td colSpan={columns.length} style={{ color: "var(--dim)", padding: 12 }}>
+                {emptyText}
+              </td>
+            </tr>
+          ) : (
+            rows.map((r, idx) => (
+              <tr key={idx}>
+                {columns.map((c) => (
+                  <td key={c.key}>{c.render ? c.render(r) : String(r[c.key] ?? "")}</td>
+                ))}
+              </tr>
+            ))
+          )}
+        </tbody>
+      </table>
+    </div>
+  );
+}
+
+export default function NetIntelViz({ baseUrl = "" }) {
+  const [loading, setLoading] = useState(false);
+  const [err, setErr] = useState("");
+
+  const [me, setMe] = useState(null);
+  const [envs, setEnvs] = useState(["default"]);
+  const [env, setEnv] = useState("default");
+
+  const [viz, setViz] = useState(VIZ.NET_GRAPH);
+
+  const [stats, setStats] = useState(null);
+  const [computers, setComputers] = useState([]);
+  const [computerName, setComputerName] = useState("");
+  const [computerDetails, setComputerDetails] = useState(null);
+
+  const [sinceHours, setSinceHours] = useState("24");
+  const [graph, setGraph] = useState(null);
+
+  const api = useMemo(() => (baseUrl || "").replace(/\/+$/, ""), [baseUrl]);
+
+  const envOptions = useMemo(() => {
+    const uniq = [...new Set(envs)];
+    return uniq.map((e) => ({ value: e, label: e }));
+  }, [envs]);
+
+  const computerOptions = useMemo(() => {
+    const opts = computers.map((c) => ({ value: c.name, label: c.name }));
+    return [{ value: "", label: "all" }, ...opts];
+  }, [computers]);
+
+  const vizOptions = [
+    { value: VIZ.STATS, label: "Stats (24h)" },
+    { value: VIZ.COMPUTERS, label: "Computers" },
+    { value: VIZ.COMPUTER_DETAILS, label: "Computer details" },
+    { value: VIZ.NET_GRAPH, label: "Network graph" },
+  ];
+
+  async function loadMe() {
+    const data = await fetchJson(`${api}/api/me`);
+    setMe(data?.user || null);
+  }
+
+  async function loadEnvs() {
+    try {
+      const data = await fetchJson(`${api}/api/admin/environments`);
+      const list = data?.environments?.length ? data.environments : ["default"];
+      setEnvs(list);
+      if (!list.includes(env)) setEnv(list[0] || "default");
+    } catch {
+      setEnvs(["default"]);
+      setEnv("default");
+    }
+  }
+
+  async function loadStats(selectedEnv) {
+    const data = await fetchJson(`${api}/api/stats`, {
+      headers: { "X-Env": selectedEnv || env || "default" },
+    });
+    setStats(data);
+  }
+
+  async function loadComputers(selectedEnv) {
+    const data = await fetchJson(`${api}/api/computers`, {
+      headers: { "X-Env": selectedEnv || env || "default" },
+    });
+    setComputers(Array.isArray(data) ? data : []);
+  }
+
+  async function loadComputerDetails(name) {
+    if (!name) {
+      setComputerDetails(null);
+      return;
+    }
+    const data = await fetchJson(`${api}/api/computer/${encodeURIComponent(name)}`, {
+      headers: { "X-Env": env || "default" },
+    });
+    setComputerDetails(data);
+  }
+
+  async function loadNetGraph() {
+    const qs = new URLSearchParams();
+    qs.set("env", env || "default");
+    qs.set("since_hours", String(parseInt(sinceHours || "24", 10) || 24));
+    if (computerName) qs.set("computer", computerName);
+
+    const data = await fetchJson(`${api}/api/graph/net?${qs.toString()}`, {
+      headers: { "X-Env": env || "default" },
+    });
+    setGraph(data);
+  }
+  function MiniBipartiteGraph({ nodes = [], edges = [] }) {
+  const comps = nodes.filter((n) => (n.subTitle || "").toLowerCase().includes("computer"));
+  const ips = nodes.filter((n) => (n.subTitle || "").toLowerCase().includes("remote"));
+
+  const width = 1100;
+  const height = 520;
+  const pad = 40;
+
+  const compPos = comps.map((n, i) => ({
+    ...n,
+    x: pad,
+    y: pad + (i * (height - 2 * pad)) / Math.max(1, comps.length - 1),
+  }));
+
+  const ipPos = ips.map((n, i) => ({
+    ...n,
+    x: width - pad,
+    y: pad + (i * (height - 2 * pad)) / Math.max(1, ips.length - 1),
+  }));
+
+  const pos = new Map();
+  [...compPos, ...ipPos].forEach((n) => pos.set(n.id, n));
+
+  const shownEdges = edges.slice(0, 120);
+
+  return (
+    <div style={{ overflow: "auto" }}>
+      <svg width={width} height={height} style={{ borderRadius: 16, background: "rgba(255,255,255,0.03)" }}>
+        {shownEdges.map((e, idx) => {
+          const s = pos.get(e.source);
+          const t = pos.get(e.target);
+          if (!s || !t) return null;
+          const v = Number(e.mainStat || 1);
+          const strokeWidth = Math.max(1, Math.min(6, Math.log2(v + 1)));
+          return (
+            <line
+              key={idx}
+              x1={s.x}
+              y1={s.y}
+              x2={t.x}
+              y2={t.y}
+              stroke="rgba(120,180,255,0.25)"
+              strokeWidth={strokeWidth}
+            />
+          );
+        })}
+
+        {compPos.map((n) => (
+          <g key={n.id}>
+            <circle cx={n.x} cy={n.y} r={6} fill="rgba(255,255,255,0.9)" />
+            <text x={n.x + 12} y={n.y + 4} fontSize="11" fill="rgba(255,255,255,0.85)">
+              {n.title}
+            </text>
+          </g>
+        ))}
+
+        {ipPos.map((n) => (
+          <g key={n.id}>
+            <circle cx={n.x} cy={n.y} r={6} fill="rgba(80,160,255,0.95)" />
+            <text x={n.x - 12} y={n.y + 4} textAnchor="end" fontSize="11" fill="rgba(255,255,255,0.85)">
+              {n.title}
+            </text>
+          </g>
+        ))}
+      </svg>
+
+      <div style={{ color: "var(--dim)", fontSize: 12, marginTop: 10 }}>
+        Tip: ако има премногу линии, намали Hours или избери конкретен Computer.
+      </div>
+    </div>
+  );
+}
+
+
+  async function refreshAll(nextViz = viz, nextEnv = env) {
+    setErr("");
+    setLoading(true);
+    try {
+      if (nextViz === VIZ.STATS) {
+        await loadStats(nextEnv);
+      } else if (nextViz === VIZ.COMPUTERS) {
+        await loadComputers(nextEnv);
+      } else if (nextViz === VIZ.COMPUTER_DETAILS) {
+        await loadComputers(nextEnv);
+        if (computerName) await loadComputerDetails(computerName);
+      } else if (nextViz === VIZ.NET_GRAPH) {
+        await loadComputers(nextEnv);
+        await loadNetGraph();
+      }
+    } catch (e) {
+      setErr(e.message || "Грешка при вчитување.");
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  useEffect(() => {
+    (async () => {
+      setLoading(true);
+      setErr("");
+      try {
+        await loadMe();
+        await loadEnvs();
+        await refreshAll(VIZ.NET_GRAPH, env);
+      } catch (e) {
+        setErr(e.message || "Неуспешно поврзување.");
+      } finally {
+        setLoading(false);
+      }
+    })();
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [api]);
+
+  const historySeries = useMemo(() => {
+    const hist = computerDetails?.history || [];
+    const rows = [...hist].reverse().slice(-80);
+    return rows.map((r) => ({
+      t: fmtTs(r.timestamp),
+      cpu: Number(r.cpu_usage ?? 0),
+      ram: Number(r.ram_usage ?? 0),
+      disk: Number(r.disk_usage ?? 0),
+      sent: Number(r.network_sent_mb ?? 0),
+      recv: Number(r.network_recv_mb ?? 0),
+    }));
+  }, [computerDetails]);
+
+  const nodes = graph?.nodes || [];
+  const edges = graph?.edges || [];
+  const meta = graph?.meta || {};
+
+  return (
+    <div>
+      {/* HEADER / CONTROLS */}
+      <Panel
+        title="Visualizations"
+        subtitle={me ? `Logged: ${me.email} • tenant: ${me.tenant_id} • role: ${me.role}` : "Not logged"}
+        right={
+          <div style={{ display: "flex", gap: 10, alignItems: "end", flexWrap: "wrap" }}>
+            <Select
+              label="Env"
+              value={env}
+              disabled={loading}
+              onChange={(v) => {
+                setEnv(v);
+                refreshAll(viz, v);
+              }}
+              options={envOptions}
+            />
+
+            <Select
+              label="View"
+              value={viz}
+              disabled={loading}
+              onChange={(v) => {
+                setViz(v);
+                refreshAll(v, env);
+              }}
+              options={vizOptions}
+            />
+
+            {(viz === VIZ.COMPUTER_DETAILS || viz === VIZ.NET_GRAPH) && (
+              <Select
+                label="Computer"
+                value={computerName}
+                disabled={loading}
+                onChange={(v) => {
+                  setComputerName(v);
+                  if (viz === VIZ.COMPUTER_DETAILS) loadComputerDetails(v).catch((e) => setErr(e.message));
+                  if (viz === VIZ.NET_GRAPH) loadNetGraph().catch((e) => setErr(e.message));
+                }}
+                options={computerOptions}
+              />
+            )}
+
+            {viz === VIZ.NET_GRAPH && (
+              <Select
+                label="Hours"
+                value={sinceHours}
+                disabled={loading}
+                onChange={(v) => setSinceHours(v)}
+                options={[
+                  { value: "1", label: "1" },
+                  { value: "6", label: "6" },
+                  { value: "12", label: "12" },
+                  { value: "24", label: "24" },
+                  { value: "48", label: "48" },
+                  { value: "72", label: "72" },
+                ]}
+              />
+            )}
+
+            <button className="btn primary" disabled={loading} onClick={() => refreshAll(viz, env)}>
+              {loading ? "Loading…" : "Refresh"}
+            </button>
+          </div>
+        }
+      >
+        {err ? (
+          <div style={{ color: "#ffb4b4", background: "rgba(255,0,0,0.08)", padding: 10, borderRadius: 12 }}>
+            {err}
+          </div>
+        ) : (
+          <div style={{ color: "var(--dim)", fontSize: 12 }}>
+            Избери “View” за да смениш што се прикажува.
+          </div>
+        )}
+      </Panel>
+      <Panel title="Graph (SVG)" subtitle="Computers left, IPs right, lines = connections">
+  <MiniBipartiteGraph nodes={nodes} edges={edges} />
+</Panel>
+
+
+      {/* STATS */}
+      {viz === VIZ.STATS && (
+        <Panel title="Stats (24h)" subtitle="From /api/stats">
+          <div className="stats-grid" style={{ marginTop: 0 }}>
+            <div className="stat-card">
+              <div className="label">Sysmon Events</div>
+              <div className="value">{stats?.total_sysmon_events ?? "—"}</div>
+              <div className="sub">Last 24 hours</div>
+            </div>
+            <div className="stat-card">
+              <div className="label">Network Connections</div>
+              <div className="value">{stats?.total_network_connections ?? "—"}</div>
+              <div className="sub">Last 24 hours</div>
+            </div>
+            <div className="stat-card">
+              <div className="label">Server Time</div>
+              <div className="value" style={{ fontSize: 18 }}>
+                {fmtTs(stats?.server_time) || "—"}
+              </div>
+              <div className="sub">Now</div>
+            </div>
+          </div>
+        </Panel>
+      )}
+
+      {/* COMPUTERS */}
+      {viz === VIZ.COMPUTERS && (
+        <Panel title="Computers" subtitle="From /api/computers">
+          <Table
+            columns={[
+              { key: "name", title: "Name" },
+              { key: "user", title: "User" },
+              { key: "ip", title: "IP" },
+              { key: "os", title: "OS" },
+              { key: "status", title: "Status" },
+              { key: "avg_cpu", title: "Avg CPU" },
+              { key: "avg_ram", title: "Avg RAM" },
+              { key: "recent_logs", title: "Logs(24h)" },
+              { key: "recent_sysmon", title: "Sysmon(24h)" },
+              { key: "last_seen", title: "Last seen", render: (r) => fmtTs(r.last_seen) },
+            ]}
+            rows={computers}
+            emptyText="No computers for this env."
+          />
+        </Panel>
+      )}
+
+      {/* COMPUTER DETAILS */}
+      {viz === VIZ.COMPUTER_DETAILS && (
+        <>
+          <Panel
+            title="Computer details"
+            subtitle="From /api/computer/<name>"
+            right={
+              <button
+                className="btn"
+                disabled={loading || !computerName}
+                onClick={() => loadComputerDetails(computerName).catch((e) => setErr(e.message))}
+              >
+                Reload
+              </button>
+            }
+          >
+            {!computerName ? (
+              <div style={{ color: "var(--dim)" }}>Select a computer.</div>
+            ) : !computerDetails ? (
+              <div style={{ color: "var(--dim)" }}>Loading…</div>
+            ) : (
+              <div className="stats-grid" style={{ marginTop: 0 }}>
+                <div className="stat-card">
+                  <div className="label">Name</div>
+                  <div className="value">{computerDetails.computer?.name}</div>
+                  <div className="sub">{computerDetails.computer?.ip} • {computerDetails.computer?.os}</div>
+                </div>
+                <div className="stat-card">
+                  <div className="label">User</div>
+                  <div className="value">{computerDetails.computer?.user || "—"}</div>
+                  <div className="sub">
+                    {fmtTs(computerDetails.computer?.first_seen)} → {fmtTs(computerDetails.computer?.last_seen)}
+                  </div>
+                </div>
+                <div className="stat-card">
+                  <div className="label">Total logs</div>
+                  <div className="value">{computerDetails.computer?.total_logs ?? "—"}</div>
+                  <div className="sub">Env: {computerDetails.computer?.env_name || env}</div>
+                </div>
+              </div>
+            )}
+          </Panel>
+
+          <Panel title="Performance charts" subtitle="Last ~80 history rows">
+            {historySeries.length === 0 ? (
+              <div style={{ color: "var(--dim)" }}>No history data.</div>
+            ) : (
+              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
+                <div style={{ height: 280 }}>
+                  <div style={{ marginBottom: 8, color: "var(--dim)", fontSize: 12 }}>CPU / RAM / Disk</div>
+                  <ResponsiveContainer width="100%" height="100%">
+                    <LineChart data={historySeries}>
+                      <CartesianGrid strokeDasharray="3 3" />
+                      <XAxis dataKey="t" hide />
+                      <YAxis />
+                      <Tooltip />
+                      <Legend />
+                      <Line type="monotone" dataKey="cpu" dot={false} />
+                      <Line type="monotone" dataKey="ram" dot={false} />
+                      <Line type="monotone" dataKey="disk" dot={false} />
+                    </LineChart>
+                  </ResponsiveContainer>
+                </div>
+
+                <div style={{ height: 280 }}>
+                  <div style={{ marginBottom: 8, color: "var(--dim)", fontSize: 12 }}>Network MB sent/recv</div>
+                  <ResponsiveContainer width="100%" height="100%">
+                    <LineChart data={historySeries}>
+                      <CartesianGrid strokeDasharray="3 3" />
+                      <XAxis dataKey="t" hide />
+                      <YAxis />
+                      <Tooltip />
+                      <Legend />
+                      <Line type="monotone" dataKey="sent" dot={false} />
+                      <Line type="monotone" dataKey="recv" dot={false} />
+                    </LineChart>
+                  </ResponsiveContainer>
+                </div>
+              </div>
+            )}
+          </Panel>
+
+          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
+            <Panel title="Processes (current)" subtitle="computer_processes_current (limit 200)">
+              <Table
+                columns={[
+                  { key: "pid", title: "PID" },
+                  { key: "name", title: "Name" },
+                  { key: "username", title: "User" },
+                  { key: "cpu_percent", title: "CPU%" },
+                  { key: "memory_mb", title: "MemMB" },
+                  { key: "timestamp", title: "Time", render: (r) => fmtTs(r.timestamp) },
+                ]}
+                rows={computerDetails?.recent_processes || []}
+              />
+            </Panel>
+
+            <Panel title="Sysmon events" subtitle="sysmon_events (limit 200)">
+              <Table
+                columns={[
+                  { key: "event_id", title: "Event" },
+                  { key: "event_type", title: "Type" },
+                  {
+                    key: "message",
+                    title: "Message",
+                    render: (r) =>
+                      (r.message || "").slice(0, 70) + ((r.message || "").length > 70 ? "…" : ""),
+                  },
+                  { key: "timestamp", title: "Time", render: (r) => fmtTs(r.timestamp) },
+                ]}
+                rows={computerDetails?.sysmon_events || []}
+              />
+            </Panel>
+          </div>
+
+          <Panel title="Network connections" subtitle="network_connections (limit 100)">
+            <Table
+              columns={[
+                { key: "pid", title: "PID" },
+                { key: "process_name", title: "Process" },
+                { key: "local_address", title: "Local" },
+                { key: "remote_address", title: "Remote" },
+                { key: "status", title: "Status" },
+                { key: "timestamp", title: "Time", render: (r) => fmtTs(r.timestamp) },
+              ]}
+              rows={computerDetails?.network_connections || []}
+            />
+          </Panel>
+        </>
+      )}
+
+      {/* NET GRAPH */}
+      {viz === VIZ.NET_GRAPH && (
+        <>
+          <Panel
+            title="Network graph (computer → remote IP)"
+            subtitle="From /api/graph/net"
+            right={
+              <button className="btn" disabled={loading} onClick={() => loadNetGraph().catch((e) => setErr(e.message))}>
+                Reload graph
+              </button>
+            }
+          >
+            <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
+              <Pill>env: {meta?.env || env}</Pill>
+              <Pill>hours: {meta?.since_hours ?? sinceHours}</Pill>
+              <Pill>computer: {meta?.computer || "all"}</Pill>
+              <Pill>nodes: {nodes.length}</Pill>
+              <Pill>edges: {edges.length}</Pill>
+            </div>
+          </Panel>
+
+          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
+            <Panel title="Top edges" subtitle="Each row = number of connections">
+              <Table
+                columns={[
+                  { key: "source", title: "Source" },
+                  { key: "target", title: "Target" },
+                  { key: "mainStat", title: "Count" },
+                ]}
+                rows={edges.slice(0, 80)}
+                emptyText="No edges (maybe no traffic in that period)."
+              />
+            </Panel>
+
+            <Panel title="Nodes" subtitle="Computers + remote IP">
+              <Table
+                columns={[
+                  { key: "title", title: "Title" },
+                  { key: "subTitle", title: "Type" },
+                ]}
+                rows={nodes.slice(0, 120)}
+              />
+            </Panel>
+          </div>
+        </>
+      )}
+    </div>
+  );
+}
Index: lan-frontend/src/components/SideMenu.jsx
===================================================================
--- lan-frontend/src/components/SideMenu.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ lan-frontend/src/components/SideMenu.jsx	(revision ba17441aef626e29462dd37a6d1d317687b77768)
@@ -0,0 +1,101 @@
+import React, {useEffect, useRef, useState} from "react";
+
+function Item({active, icon, label, onClick, collapsed}) {
+    return (
+        <button
+            onClick={onClick}
+            className={`side-item ${active ? "active" : ""}`}
+            title={label}
+            type="button"
+        >
+            <span className="side-icon">{icon}</span>
+            {!collapsed && <span className="side-label">{label}</span>}
+        </button>
+    );
+}
+
+export default function SideMenu({activeView, setActiveView}) {
+    const [open, setOpen] = useState(false);
+    const rootRef = useRef(null);
+
+    // close on click outside (ако е open преку click)
+    useEffect(() => {
+        function onDocClick(e) {
+            if (!open) return;
+            if (!rootRef.current) return;
+            if (!rootRef.current.contains(e.target)) setOpen(false);
+        }
+
+        document.addEventListener("mousedown", onDocClick);
+        return () => document.removeEventListener("mousedown", onDocClick);
+    }, [open]);
+
+    const collapsed = !open;
+
+    return (
+        <aside
+            ref={rootRef}
+            className={`side ${open ? "open" : "collapsed"}`}
+            onMouseEnter={() => setOpen(true)}   // hover open
+            onMouseLeave={() => setOpen(false)}  // hover close
+        >
+            {/* мал “handle” што се гледа секогаш */}
+            <button
+                className="side-handle"
+                type="button"
+                onClick={() => setOpen((v) => !v)}  // click toggle (ако не сакаш click, избриши ова)
+                aria-label="Toggle menu"
+                title="Menu"
+            >
+                {open ? "◀" : "▶"}
+            </button>
+
+            <div className="side-inner">
+                <div className="side-brand">
+                    <div className="side-brand-dot"/>
+                    {!collapsed && (
+                        <div className="side-brand-text">
+                            <div className="side-brand-title">netIntel</div>
+                            <div className="side-brand-sub">tenant dashboard</div>
+                        </div>
+                    )}
+                </div>
+
+
+                <div className="side-section">
+                    {/*<div className="side-section-title">MAIN</div>*/}
+
+                    <Item
+                        collapsed={collapsed}
+                        active={activeView === "dashboard"}
+                        icon="🖥️"
+                        label="Dashboard"
+                        onClick={() => setActiveView("dashboard")}
+                    />
+
+                    <Item
+                        collapsed={collapsed}
+                        active={activeView === "visualizations"}
+                        icon="📈"
+                        label="Visualizations"
+                        onClick={() => setActiveView("visualizations")}
+                    />
+
+                    <Item
+                        collapsed={collapsed}
+                        active={activeView === "account"}
+                        icon="👤"
+                        label="Account"
+                        onClick={() => setActiveView("account")}
+                    />
+                </div>
+
+                {!collapsed && (
+                    <div className="side-footer">
+                        <div className="side-footer-hint">Tip: користи Visualizations за графови</div>
+                    </div>
+                )}
+            </div>
+        </aside>
+    );
+}
Index: lan-frontend/src/styles/netinel.css
===================================================================
--- lan-frontend/src/styles/netinel.css	(revision ba17441aef626e29462dd37a6d1d317687b77768)
+++ lan-frontend/src/styles/netinel.css	(revision ba17441aef626e29462dd37a6d1d317687b77768)
@@ -0,0 +1,125 @@
+/* netintel.css - basic styling without Tailwind */
+
+.netintel-wrap {
+  max-width: 1100px;
+  margin: 0 auto;
+  padding: 16px;
+  color: #e5e7eb;
+  font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
+}
+
+.netintel-title {
+  font-size: 24px;
+  font-weight: 700;
+  margin-bottom: 6px;
+}
+
+.netintel-sub {
+  font-size: 13px;
+  opacity: 0.85;
+  margin-bottom: 14px;
+}
+
+.netintel-toolbar {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+  align-items: end;
+  margin-bottom: 14px;
+}
+
+.netintel-field {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.netintel-label {
+  font-size: 11px;
+  opacity: 0.85;
+}
+
+.netintel-select,
+.netintel-btn {
+  background: rgba(255,255,255,0.06);
+  border: 1px solid rgba(255,255,255,0.15);
+  color: #e5e7eb;
+  border-radius: 10px;
+  padding: 8px 10px;
+  font-size: 14px;
+  outline: none;
+}
+
+.netintel-btn {
+  cursor: pointer;
+}
+
+.netintel-btn:hover {
+  background: rgba(255,255,255,0.10);
+}
+
+.netintel-card {
+  background: rgba(255,255,255,0.06);
+  border: 1px solid rgba(255,255,255,0.12);
+  border-radius: 16px;
+  padding: 14px;
+  margin-bottom: 14px;
+}
+
+.netintel-card h3 {
+  margin: 0 0 6px;
+  font-size: 16px;
+}
+
+.netintel-card p {
+  margin: 0 0 10px;
+  font-size: 13px;
+  opacity: 0.85;
+}
+
+.netintel-pill {
+  display: inline-flex;
+  align-items: center;
+  padding: 2px 8px;
+  border-radius: 999px;
+  font-size: 12px;
+  background: rgba(255,255,255,0.08);
+  border: 1px solid rgba(255,255,255,0.12);
+  margin-right: 6px;
+  margin-bottom: 6px;
+}
+
+.netintel-tablewrap {
+  overflow: auto;
+  border-radius: 12px;
+  border: 1px solid rgba(255,255,255,0.12);
+}
+
+.netintel-table {
+  width: 100%;
+  border-collapse: collapse;
+  min-width: 700px;
+}
+
+.netintel-table th {
+  text-align: left;
+  font-weight: 600;
+  font-size: 13px;
+  background: rgba(0,0,0,0.25);
+  padding: 10px;
+}
+
+.netintel-table td {
+  padding: 10px;
+  border-top: 1px solid rgba(255,255,255,0.08);
+  font-size: 13px;
+}
+
+.netintel-error {
+  background: rgba(239, 68, 68, 0.12);
+  border: 1px solid rgba(239, 68, 68, 0.25);
+  color: #fecaca;
+  padding: 10px 12px;
+  border-radius: 12px;
+  margin-bottom: 12px;
+}
Index: lan-frontend/src/styles/theme.css
===================================================================
--- lan-frontend/src/styles/theme.css	(revision e4c61dd6cd86e06265bc2bd91adba84a0f04044a)
+++ lan-frontend/src/styles/theme.css	(revision ba17441aef626e29462dd37a6d1d317687b77768)
@@ -3,4 +3,7 @@
    Dark Navy + Light Blue
    ========================= */
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
 
 :root{
@@ -548,2 +551,264 @@
   border-color: rgba(88, 167, 255, .35);
 }
+
+/* ====== LAYOUT with LEFT SIDEBAR ====== */
+.app-shell {
+  display: grid;
+  grid-template-columns: 240px 1fr;
+  min-height: 100vh;
+}
+
+.app-main {
+  padding: 14px;
+}
+
+/* Sidebar */
+.side {
+  background: rgba(255,255,255,0.04);
+  border-right: 1px solid rgba(255,255,255,0.10);
+  padding: 14px;
+  position: sticky;
+  top: 0;
+  height: 100vh;
+}
+
+.side-brand {
+  display: flex;
+  gap: 10px;
+  align-items: center;
+  padding: 10px;
+  border: 1px solid rgba(255,255,255,0.10);
+  border-radius: 14px;
+  background: rgba(0,0,0,0.12);
+  margin-bottom: 14px;
+}
+
+.side-brand-dot {
+  width: 10px;
+  height: 10px;
+  border-radius: 999px;
+  background: #7dd3fc;
+  box-shadow: 0 0 0 4px rgba(125,211,252,0.15);
+}
+
+.side-brand-title {
+  font-weight: 700;
+  letter-spacing: 0.2px;
+}
+
+.side-brand-sub {
+  font-size: 12px;
+  color: var(--dim);
+}
+
+.side-section-title {
+  font-size: 11px;
+  color: var(--dim);
+  margin: 12px 8px 8px;
+  letter-spacing: 0.12em;
+}
+
+.side-item {
+  width: 100%;
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 10px 12px;
+  border-radius: 12px;
+  border: 1px solid transparent;
+  background: transparent;
+  color: inherit;
+  cursor: pointer;
+  text-align: left;
+}
+
+.side-item:hover {
+  background: rgba(255,255,255,0.06);
+  border-color: rgba(255,255,255,0.08);
+}
+
+.side-item.active {
+  background: rgba(125,211,252,0.12);
+  border-color: rgba(125,211,252,0.25);
+}
+
+.side-icon {
+  width: 26px;
+  display: grid;
+  place-items: center;
+  font-size: 18px;
+}
+
+.side-label {
+  font-size: 14px;
+  font-weight: 600;
+}
+
+.side-footer {
+  position: absolute;
+  bottom: 12px;
+  left: 14px;
+  right: 14px;
+  padding: 10px;
+  border-radius: 12px;
+  border: 1px solid rgba(255,255,255,0.10);
+  background: rgba(0,0,0,0.12);
+}
+
+.side-footer-hint {
+  font-size: 12px;
+  color: var(--dim);
+}
+/* ==== Collapsible Side Menu ==== */
+.side {
+  position: sticky;
+  top: 0;
+  height: 100vh;
+  z-index: 20;
+
+  width: 260px;
+  transition: width 180ms ease;
+  border-right: 1px solid rgba(255,255,255,0.08);
+  background: rgba(6, 12, 24, 0.72);
+  backdrop-filter: blur(10px);
+}
+
+.side.collapsed {
+  width: 54px; /* само тенка лента */
+}
+
+.side-inner {
+  height: 100%;
+  padding: 14px 10px;
+  display: flex;
+  flex-direction: column;
+}
+
+.side-handle {
+  position: absolute;
+  left: 6px;
+  top: 10px;
+  width: 42px;
+  height: 34px;
+  border-radius: 12px;
+  border: 1px solid rgba(255,255,255,0.12);
+  background: rgba(255,255,255,0.06);
+  color: rgba(255,255,255,0.9);
+  cursor: pointer;
+}
+
+.side-brand {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 10px;
+  margin-top: 38px; /* за да не се судри со handle */
+  border-radius: 16px;
+  background: rgba(255,255,255,0.03);
+  border: 1px solid rgba(255,255,255,0.06);
+}
+
+.side-brand-dot {
+  width: 12px;
+  height: 12px;
+  border-radius: 999px;
+  background: rgba(80, 160, 255, 0.95);
+  box-shadow: 0 0 18px rgba(80,160,255,0.45);
+}
+
+.side-brand-text { min-width: 0; }
+.side-brand-title { font-weight: 800; color: rgba(255,255,255,0.92); }
+.side-brand-sub { font-size: 12px; color: rgba(255,255,255,0.55); }
+
+.side-section { margin-top: 14px; }
+.side-section-title {
+  font-size: 11px;
+  letter-spacing: 0.12em;
+  color: rgba(255,255,255,0.45);
+  padding: 10px;
+}
+
+.side-item {
+  width: 100%;
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 10px 12px;
+  margin: 6px 0;
+  border-radius: 14px;
+  border: 1px solid transparent;
+  background: transparent;
+  color: rgba(255,255,255,0.82);
+  cursor: pointer;
+  text-align: left;
+}
+
+.side-item:hover {
+  background: rgba(255,255,255,0.06);
+  border-color: rgba(255,255,255,0.08);
+}
+
+.side-item.active {
+  background: rgba(80,160,255,0.16);
+  border-color: rgba(80,160,255,0.25);
+}
+
+.side-icon {
+  width: 28px;
+  display: grid;
+  place-items: center;
+  font-size: 18px;
+}
+
+.side-label {
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.side-footer {
+  margin-top: auto;
+  padding: 10px;
+  color: rgba(255,255,255,0.55);
+  font-size: 12px;
+}
+.layout { display: flex; }
+.content { flex: 1; min-width: 0; }
+/* collapsed = центрирање иконите + тргање празни простори */
+.side.collapsed .side-inner {
+  padding: 10px 6px;
+  align-items: center;
+}
+
+.side.collapsed .side-brand {
+  width: 42px;
+  padding: 10px 0;
+  justify-content: center;
+  margin-top: 48px;
+}
+
+.side.collapsed .side-item {
+  justify-content: center;
+  padding: 10px 0;
+}
+
+.side.collapsed .side-icon {
+  width: 42px;
+}
+
+/* handle во средина (поубаво) */
+.side-handle {
+  left: 6px;
+  top: 10px;
+  width: 42px;
+  height: 34px;
+}
+
+/* кога е open да си е нормално */
+.side.open .side-inner {
+  align-items: stretch;
+}
+
+.cont{
+  margin-right: 50px;
+}
