-
+
+
+
+ GhostAPI
+
+ /
+ Live Traffic
diff --git a/src/dashboard/styles.css b/src/dashboard/styles.css
index e7415c4..95b344a 100644
--- a/src/dashboard/styles.css
+++ b/src/dashboard/styles.css
@@ -76,6 +76,15 @@ body {
opacity: 0;
border-right: none;
}
+.sidebar-backdrop {
+ display: none;
+ position: fixed;
+ inset: 0;
+ z-index: 30;
+ border: 0;
+ background: rgba(0, 0, 0, 0.58);
+ backdrop-filter: blur(2px);
+}
/* Workspace Switcher */
.ws-switcher {
@@ -292,9 +301,9 @@ body {
.topbar {
height: 56px;
border-bottom: 1px solid var(--border);
- display: flex;
+ display: grid;
+ grid-template-columns: minmax(44px, 1fr) auto minmax(44px, 1fr);
align-items: center;
- justify-content: space-between;
padding: 0 16px;
background: var(--card);
flex-shrink: 0;
@@ -303,6 +312,8 @@ body {
display: flex;
align-items: center;
gap: 12px;
+ grid-column: 1;
+ justify-self: start;
}
.toggle-btn {
padding: 6px;
@@ -326,6 +337,35 @@ body {
font-size: 14px;
color: var(--muted-foreground);
}
+.topbar-center {
+ position: fixed;
+ top: 0;
+ left: 50%;
+ z-index: 5;
+ height: 56px;
+ transform: translateX(-50%);
+ min-width: 0;
+}
+.brand-lockup {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ color: var(--foreground);
+}
+.brand-lockup strong {
+ font-size: 14px;
+ font-weight: 650;
+ letter-spacing: -0.01em;
+}
+.topbar-brand-icon {
+ width: 22px;
+ height: 22px;
+ border-radius: 6px;
+ object-fit: cover;
+}
+.breadcrumb-divider {
+ color: rgba(161, 161, 170, 0.42);
+}
.breadcrumb-active {
font-weight: 500;
color: var(--foreground);
@@ -335,6 +375,8 @@ body {
display: flex;
align-items: center;
gap: 12px;
+ grid-column: 3;
+ justify-self: end;
}
.status-indicator {
display: flex;
@@ -820,6 +862,129 @@ body {
}
}
+@media (max-width: 760px) {
+ .sidebar {
+ position: fixed;
+ inset: 0 auto 0 0;
+ z-index: 40;
+ width: min(84vw, 300px);
+ transform: translateX(-100%);
+ opacity: 0;
+ pointer-events: none;
+ border-right: 1px solid rgba(255, 255, 255, 0.14);
+ background: #0d0d10;
+ box-shadow: 24px 0 64px rgba(0, 0, 0, 0.42);
+ transition:
+ transform 0.22s ease,
+ opacity 0.22s ease;
+ }
+ .sidebar.closed {
+ width: min(84vw, 300px);
+ padding: 12px;
+ opacity: 0;
+ border-right: 1px solid rgba(255, 255, 255, 0.14);
+ }
+ .sidebar.mobile-open {
+ transform: translateX(0);
+ opacity: 1;
+ pointer-events: auto;
+ }
+ .sidebar-backdrop.open {
+ display: block;
+ }
+ .topbar {
+ height: 52px;
+ padding: 0 12px;
+ grid-template-columns: 36px minmax(0, 1fr) 36px;
+ }
+ .topbar-center {
+ height: 52px;
+ max-width: 100%;
+ gap: 6px;
+ }
+ .breadcrumb-active {
+ max-width: 32vw;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ .status-indicator {
+ padding: 5px;
+ border-radius: 50%;
+ }
+ #status-text {
+ display: none;
+ }
+ .content-wrapper {
+ flex-direction: column;
+ }
+ .event-list-pane {
+ width: 100%;
+ height: 42%;
+ min-height: 210px;
+ border-right: none;
+ border-bottom: 1px solid var(--border);
+ }
+ .details-pane {
+ min-height: 0;
+ }
+ .dep-header {
+ align-items: flex-start;
+ flex-direction: column;
+ gap: 12px;
+ padding: 12px 16px;
+ }
+ .dep-title {
+ width: 100%;
+ overflow: hidden;
+ }
+ .dep-title > span:last-child {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ .dep-badges {
+ width: 100%;
+ flex-wrap: wrap;
+ }
+ .json-split {
+ flex-direction: column;
+ overflow: auto;
+ }
+ .json-pane {
+ min-height: 230px;
+ border-right: none;
+ border-bottom: 1px solid var(--border);
+ }
+ .json-pane:last-child {
+ border-bottom: none;
+ }
+ .rules-modal-header {
+ flex-wrap: wrap;
+ }
+}
+
+@media (max-width: 420px) {
+ .brand-lockup strong {
+ font-size: 13px;
+ }
+ .topbar-brand-icon {
+ width: 19px;
+ height: 19px;
+ }
+ .breadcrumb-divider,
+ .breadcrumb-active {
+ font-size: 12px;
+ }
+ .event-list-pane {
+ height: 46%;
+ min-height: 190px;
+ }
+ .event-card {
+ padding: 10px 12px;
+ }
+}
+
@media (max-height: 720px) {
.setup-modal-content {
height: calc(100vh - 48px);
diff --git a/test/dashboardAssets.test.ts b/test/dashboardAssets.test.ts
index 4893666..49629a6 100644
--- a/test/dashboardAssets.test.ts
+++ b/test/dashboardAssets.test.ts
@@ -19,6 +19,7 @@ describe("dashboard assets", () => {
expect(app).toContain("els.eventCount.textContent = list.length");
expect(app).toContain('scenarioButton("Arm"');
expect(app).not.toContain('scenarioButton("Replay"');
+ expect(app).toContain("closeMobileSidebar");
});
it("does not load dashboard assets from external origins", async () => {
@@ -34,4 +35,23 @@ describe("dashboard assets", () => {
expect(content).not.toContain("@import url(");
}
});
+
+ it("keeps the product identity centered and provides a mobile layout", async () => {
+ const [html, styles, readme] = await Promise.all([
+ readFile("src/dashboard/index.html", "utf8"),
+ readFile("src/dashboard/styles.css", "utf8"),
+ readFile("README.md", "utf8"),
+ ]);
+
+ expect(html).toContain('class="breadcrumb topbar-center"');
+ expect(html).toContain('id="sidebar-backdrop"');
+ expect(styles).toContain("grid-template-columns: minmax(44px, 1fr) auto");
+ expect(styles).toContain("grid-column: 3");
+ expect(styles).toContain("transform: translateX(-50%)");
+ expect(styles).toContain("@media (max-width: 760px)");
+ expect(styles).toContain(".sidebar.mobile-open");
+ expect(styles).toContain("flex-direction: column");
+ expect(readme).toContain('
');
+ expect(readme).toContain('Quickstart');
+ });
});
diff --git a/docs/assets/dashboard.png b/docs/assets/dashboard.png
index 71ab5e9..634a346 100644
Binary files a/docs/assets/dashboard.png and b/docs/assets/dashboard.png differ
diff --git a/src/dashboard/app.js b/src/dashboard/app.js
index 409f27a..2d78ff5 100644
--- a/src/dashboard/app.js
+++ b/src/dashboard/app.js
@@ -1,6 +1,7 @@
// DOM References
const els = {
sidebar: document.getElementById("sidebar"),
+ sidebarBackdrop: document.getElementById("sidebar-backdrop"),
toggleBtn: document.getElementById("sidebar-toggle"),
statusIndicator: document.getElementById("status-indicator"),
statusText: document.getElementById("status-text"),
@@ -82,7 +83,25 @@ async function fetchProviderManifests() {
// Side-effects & Listeners
function setupInteractions() {
- els.toggleBtn.onclick = () => els.sidebar.classList.toggle("closed");
+ els.toggleBtn.onclick = () => {
+ if (window.matchMedia("(max-width: 760px)").matches) {
+ const open = els.sidebar.classList.toggle("mobile-open");
+ els.sidebarBackdrop.classList.toggle("open", open);
+ return;
+ }
+ els.sidebar.classList.toggle("closed");
+ };
+ els.sidebarBackdrop.onclick = closeMobileSidebar;
+ els.sidebar.addEventListener("click", (event) => {
+ if (event.target.closest(".nav-item")) closeMobileSidebar();
+ });
+ window.addEventListener("resize", () => {
+ if (window.matchMedia("(max-width: 760px)").matches) {
+ els.sidebar.classList.remove("closed");
+ return;
+ }
+ closeMobileSidebar();
+ });
// Command + K
document.addEventListener("keydown", (e) => {
@@ -183,8 +202,14 @@ window.setFilter = (filter) => {
}
renderList();
+ closeMobileSidebar();
};
+function closeMobileSidebar() {
+ els.sidebar.classList.remove("mobile-open");
+ els.sidebarBackdrop.classList.remove("open");
+}
+
window.clearStorage = async (target, event) => {
try {
const btnText = event.currentTarget.querySelector(".nav-item-title");
diff --git a/src/dashboard/index.html b/src/dashboard/index.html
index dbbf965..1d5c207 100644
--- a/src/dashboard/index.html
+++ b/src/dashboard/index.html
@@ -400,12 +400,25 @@
+
+