diff --git a/TASKS.md b/TASKS.md
index 38b6cfb..959ed3d 100644
--- a/TASKS.md
+++ b/TASKS.md
@@ -2,19 +2,19 @@
## Current
-### `feat/observability-api`
+### `feat/alpine-ui`
-Status: completed
+Status: in_progress
DoD:
-- `Logs` page и `Usage` page имеют backend-контракт на реальных данных
-- `test-runs` и `MCP tools/call` пишут продуктовые invocation logs
-- usage endpoints отдают summary, timeline, breakdown по operation и agent
-- observability не смешана с application logs
+- `apps/ui` заменен на новый Alpine.js UI из `test-ui`
+- `test-ui` остается нетронутым как fallback
+- UI продолжает раздаваться как статический контейнер через текущий deployment path
+- legacy React/Vite артефакты удалены из `apps/ui`
## Next
-- `feat/alpine-ui`
+- `feat/demo-assets`
## Backlog
diff --git a/apps/ui/Dockerfile b/apps/ui/Dockerfile
index 8be8867..419229c 100644
--- a/apps/ui/Dockerfile
+++ b/apps/ui/Dockerfile
@@ -2,6 +2,11 @@ FROM nginx:1.27-alpine
COPY apps/ui/nginx.conf /etc/nginx/conf.d/default.conf
COPY apps/ui/index.html /usr/share/nginx/html/index.html
+COPY apps/ui/css /usr/share/nginx/html/css
+COPY apps/ui/data /usr/share/nginx/html/data
+COPY apps/ui/html /usr/share/nginx/html/html
+COPY apps/ui/icons /usr/share/nginx/html/icons
+COPY apps/ui/js /usr/share/nginx/html/js
COPY Crank.png /usr/share/nginx/html/Crank.png
EXPOSE 3000
diff --git a/apps/ui/css/catalog.css b/apps/ui/css/catalog.css
new file mode 100644
index 0000000..20003fd
--- /dev/null
+++ b/apps/ui/css/catalog.css
@@ -0,0 +1,579 @@
+/* ── Page header ── */
+.page-header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ margin-bottom: 32px;
+}
+.page-heading {
+ font-size: 24px;
+ font-weight: 700;
+ letter-spacing: -0.5px;
+ color: var(--text-primary);
+ margin-bottom: 4px;
+}
+.page-subheading { font-size: 14px; color: var(--text-muted); }
+
+.btn-new {
+ display: inline-flex;
+ align-items: center;
+ gap: 7px;
+ padding: 9px 18px;
+ border-radius: var(--radius);
+ background: var(--accent);
+ color: #fff;
+ font-size: 13.5px;
+ font-weight: 600;
+ border: 1px solid var(--accent-dim);
+ box-shadow: 0 1px 4px rgba(13,148,136,0.4);
+ transition: all 0.15s;
+ white-space: nowrap;
+ text-decoration: none;
+}
+.btn-new:hover {
+ background: var(--accent-dim);
+ box-shadow: 0 2px 10px rgba(13,148,136,0.5);
+ transform: translateY(-1px);
+}
+
+/* ── Stats ── */
+.stats-row {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 1px;
+ background: var(--border);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ overflow: hidden;
+ margin-bottom: 28px;
+}
+.stat-card { background: var(--bg-surface); padding: 18px 22px; }
+.stat-label {
+ font-size: 11px;
+ color: var(--text-muted);
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ margin-bottom: 6px;
+}
+.stat-value {
+ font-size: 26px;
+ font-weight: 700;
+ letter-spacing: -1px;
+ color: var(--text-primary);
+}
+.stat-delta { font-size: 12px; color: var(--text-muted); margin-top: 3px; }
+.stat-delta.up { color: var(--green); }
+.stat-delta.down { color: var(--red); }
+
+/* ── Tabs ── */
+.tab-row {
+ display: flex;
+ align-items: center;
+ border-bottom: 1px solid var(--border);
+ margin-bottom: 20px;
+}
+.tab-item {
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ padding: 10px 18px;
+ font-size: 14px;
+ font-weight: 450;
+ color: var(--text-muted);
+ cursor: pointer;
+ border: none;
+ background: none;
+ border-bottom: 2px solid transparent;
+ margin-bottom: -1px;
+ transition: color 0.1s;
+}
+.tab-item:hover { color: var(--text-secondary); }
+.tab-item.active {
+ color: var(--text-primary);
+ font-weight: 600;
+ border-bottom-color: var(--accent-bright);
+}
+.tab-count {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--bg-overlay);
+ border: 1px solid var(--border);
+ border-radius: 10px;
+ padding: 0 6px;
+ height: 18px;
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--text-muted);
+ min-width: 20px;
+}
+.tab-item.active .tab-count {
+ background: var(--accent);
+ color: #fff;
+ border-color: var(--accent-dim);
+}
+
+/* ── Filter bar ── */
+.filter-bar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 16px;
+ flex-wrap: wrap;
+}
+
+.search-box {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 7px 12px;
+ width: 220px;
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+.search-box:focus-within {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px var(--accent-ring);
+}
+.search-box input {
+ border: none;
+ background: none;
+ outline: none;
+ font-size: 13.5px;
+ color: var(--text-primary);
+ width: 100%;
+ font-family: inherit;
+}
+.search-box input::placeholder { color: var(--text-muted); }
+.search-box svg { color: var(--text-muted); flex-shrink: 0; }
+
+/* ── Dropdown filter chip ── */
+.filter-dropdown { position: relative; }
+
+.filter-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 7px 12px;
+ border-radius: var(--radius);
+ border: 1px solid var(--border);
+ background: var(--bg-surface);
+ font-size: 13px;
+ color: var(--text-secondary);
+ transition: all 0.1s;
+ white-space: nowrap;
+}
+.filter-chip:hover { border-color: var(--bg-muted); background: var(--bg-overlay); color: var(--text-primary); }
+.filter-chip.has-value {
+ border-color: var(--accent);
+ color: var(--accent-bright);
+ background: var(--accent-glow);
+}
+.filter-chip .chevron {
+ opacity: 0.6;
+ transition: transform 0.15s;
+}
+.filter-chip.open .chevron { transform: rotate(180deg); }
+
+.dropdown-menu {
+ position: absolute;
+ top: calc(100% + 6px);
+ left: 0;
+ min-width: 180px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ box-shadow: var(--shadow);
+ z-index: 150;
+ overflow: hidden;
+ padding: 4px;
+}
+.dropdown-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8px 10px;
+ border-radius: var(--radius-sm);
+ font-size: 13.5px;
+ color: var(--text-secondary);
+ border: none;
+ background: none;
+ width: 100%;
+ text-align: left;
+ cursor: pointer;
+ transition: all 0.1s;
+}
+.dropdown-item:hover { background: var(--bg-overlay); color: var(--text-primary); }
+.dropdown-item.selected { color: var(--accent-bright); }
+.dropdown-item .check {
+ opacity: 0;
+ color: var(--accent);
+}
+.dropdown-item.selected .check { opacity: 1; }
+.dropdown-divider {
+ height: 1px;
+ background: var(--border-subtle);
+ margin: 4px 0;
+}
+.dropdown-clear {
+ display: flex;
+ align-items: center;
+ padding: 7px 10px;
+ font-size: 12px;
+ color: var(--text-muted);
+ border: none;
+ background: none;
+ width: 100%;
+ text-align: left;
+ cursor: pointer;
+ border-radius: var(--radius-sm);
+ transition: all 0.1s;
+}
+.dropdown-clear:hover { color: var(--red); background: var(--red-bg); }
+
+/* ── Filter right side ── */
+.filter-right {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-left: auto;
+}
+.results-count { font-size: 13px; color: var(--text-muted); white-space: nowrap; }
+
+.sort-dropdown { position: relative; }
+.sort-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 7px 12px;
+ border-radius: var(--radius);
+ border: 1px solid var(--border);
+ background: var(--bg-surface);
+ font-size: 13px;
+ color: var(--text-secondary);
+ transition: all 0.1s;
+ white-space: nowrap;
+}
+.sort-chip:hover { border-color: var(--bg-muted); background: var(--bg-overlay); color: var(--text-primary); }
+.sort-chip .chevron { opacity: 0.6; transition: transform 0.15s; }
+.sort-chip.open .chevron { transform: rotate(180deg); }
+
+/* ── Table ── */
+.table-wrap {
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ overflow: hidden;
+}
+.table-wrap table {
+ width: 100%;
+ border-collapse: collapse;
+}
+.table-wrap thead tr {
+ background: var(--bg-canvas);
+ border-bottom: 1px solid var(--border);
+}
+.table-wrap th {
+ padding: 10px 16px;
+ text-align: left;
+ font-size: 11.5px;
+ font-weight: 700;
+ color: var(--text-muted);
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ white-space: nowrap;
+ user-select: none;
+}
+.th-sortable { cursor: pointer; }
+.th-sortable:hover { color: var(--text-secondary); }
+.th-sortable.sort-active { color: var(--text-secondary); }
+.sort-arrow { margin-left: 4px; opacity: 0.5; font-size: 11px; }
+.sort-arrow.active { opacity: 1; color: var(--accent-bright); }
+
+.table-wrap tbody tr {
+ border-bottom: 1px solid var(--border-subtle);
+ transition: background 0.1s;
+}
+.table-wrap tbody tr:last-child { border-bottom: none; }
+.table-wrap tbody tr:hover { background: var(--bg-overlay); }
+.table-wrap td { padding: 14px 16px; vertical-align: middle; }
+
+.op-name { font-family: 'JetBrains Mono', 'Fira Code', monospace; font-size: 13px; font-weight: 500; color: var(--text-primary); }
+.op-display { font-size: 12px; color: var(--text-muted); margin-top: 3px; }
+
+/* ── Badges ── */
+.badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ padding: 3px 8px;
+ border-radius: var(--radius-sm);
+ font-size: 11.5px;
+ font-weight: 700;
+ white-space: nowrap;
+ border: 1px solid;
+ letter-spacing: 0.02em;
+}
+.badge-rest { color: var(--blue); background: var(--blue-bg); border-color: var(--blue-border); }
+.badge-graphql { color: var(--purple); background: var(--purple-bg); border-color: var(--purple-border); }
+.badge-grpc { color: #14b8a6; background: rgba(13,148,136,0.1); border-color: rgba(13,148,136,0.25); }
+
+.status-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 3px 9px;
+ border-radius: var(--radius-sm);
+ font-size: 12.5px;
+ font-weight: 500;
+}
+.status-dot {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ flex-shrink: 0;
+}
+.status-active { color: var(--green); background: var(--green-bg); }
+.status-active .status-dot { background: var(--green); box-shadow: 0 0 0 2px rgba(63,185,80,0.2); }
+.status-draft { color: var(--amber); background: var(--amber-bg); }
+.status-draft .status-dot { background: var(--amber); }
+.status-error { color: var(--red); background: var(--red-bg); }
+.status-error .status-dot { background: var(--red); box-shadow: 0 0 0 2px rgba(248,81,73,0.2); }
+.status-inactive { color: var(--text-muted); background: var(--bg-overlay); }
+.status-inactive .status-dot { background: var(--text-muted); }
+
+.target-url-cell {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
+ font-size: 12px;
+ color: var(--text-secondary);
+ max-width: 280px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.target-url-cell a { color: inherit; text-decoration: none; }
+.target-url-cell svg { flex-shrink: 0; opacity: 0.5; }
+
+.date-cell { font-size: 13px; color: var(--text-muted); white-space: nowrap; }
+
+.row-actions { display: flex; align-items: center; gap: 4px; justify-content: flex-end; }
+.row-btn {
+ padding: 5px 11px;
+ border-radius: var(--radius-sm);
+ border: 1px solid var(--border);
+ background: var(--bg-surface);
+ font-size: 12.5px;
+ color: var(--text-secondary);
+ transition: all 0.1s;
+}
+.row-btn:hover { border-color: var(--bg-muted); color: var(--text-primary); background: var(--bg-overlay); }
+.row-btn.danger:hover { border-color: var(--red-border); color: var(--red); background: var(--red-bg); }
+
+.row-btn-edit {
+ width: 34px;
+ height: 34px;
+ padding: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: var(--radius);
+}
+
+/* ── Empty state ── */
+.empty-state {
+ padding: 60px 20px;
+ text-align: center;
+ color: var(--text-muted);
+}
+.empty-state h3 { font-size: 16px; color: var(--text-secondary); margin-bottom: 8px; }
+.empty-state p { font-size: 13.5px; }
+
+/* ── Table footer / pagination ── */
+.table-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 14px 20px;
+ border-top: 1px solid var(--border);
+ background: var(--bg-canvas);
+}
+.table-footer-info { font-size: 13px; color: var(--text-muted); }
+
+.pagination { display: flex; align-items: center; gap: 4px; }
+.page-btn {
+ width: 32px;
+ height: 32px;
+ border-radius: var(--radius);
+ border: 1px solid var(--border);
+ background: var(--bg-surface);
+ font-size: 13px;
+ color: var(--text-secondary);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.1s;
+}
+.page-btn:hover:not(:disabled) { border-color: var(--bg-muted); color: var(--text-primary); }
+.page-btn.active { background: var(--accent); color: #fff; border-color: var(--accent-dim); font-weight: 600; }
+.page-btn:disabled { opacity: 0.3; cursor: not-allowed; }
+
+/* ── Banner ── */
+.banner {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 11px 16px;
+ border-radius: var(--radius);
+ background: rgba(13,148,136,0.07);
+ border: 1px solid rgba(13,148,136,0.2);
+ color: var(--accent-bright);
+ font-size: 13.5px;
+ margin-bottom: 24px;
+}
+.banner-close {
+ margin-left: auto;
+ background: none;
+ border: none;
+ color: var(--accent-bright);
+ font-size: 16px;
+ opacity: 0.6;
+ line-height: 1;
+}
+.banner-close:hover { opacity: 1; }
+
+/* ═══════════════ RESPONSIVE ═══════════════ */
+
+/* Tablet: ≤ 960px */
+@media (max-width: 960px) {
+ .stats-row { grid-template-columns: repeat(2, 1fr); }
+ .col-url { display: none; }
+ .results-count { display: none; }
+}
+
+/* Narrow / mobile: ≤ 720px */
+@media (max-width: 720px) {
+ /* --- Скрываем колонку CREATED --- */
+ .col-created { display: none; }
+
+ /* --- Filter bar → grid --- */
+ .filter-bar {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 6px;
+ }
+ /* Search во всю ширину */
+ .search-box {
+ grid-column: 1 / -1;
+ width: 100%;
+ }
+ /* Каждый dropdown занимает ячейку */
+ .filter-dropdown {
+ width: 100%;
+ }
+ .filter-chip {
+ width: 100%;
+ justify-content: space-between;
+ }
+ /* Sort справа, во всю строку */
+ .filter-right {
+ grid-column: 1 / -1;
+ margin-left: 0;
+ justify-content: flex-end;
+ }
+ .sort-chip { white-space: nowrap; }
+
+ /* action column compact */
+ .row-actions { justify-content: center; }
+
+ /* --- Таблица --- */
+ .table-wrap td { padding: 12px 10px; }
+ .table-wrap th { padding: 8px 10px; }
+}
+
+/* Mobile: ≤ 480px */
+@media (max-width: 480px) {
+ .tab-item { padding: 10px 8px; font-size: 13px; gap: 5px; }
+
+ .page-header { flex-direction: column; gap: 12px; align-items: flex-start; }
+ .btn-new { width: 100%; justify-content: center; }
+
+}
+
+/* ── Active filter chips ── */
+.active-filters {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 6px;
+ padding: 8px 0 4px;
+}
+.active-filter-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ padding: 3px 8px 3px 10px;
+ background: rgba(13,148,136,0.12);
+ border: 1px solid rgba(13,148,136,0.3);
+ border-radius: 20px;
+ font-size: 12px;
+ color: var(--accent);
+}
+.active-filter-chip-remove {
+ display: flex;
+ align-items: center;
+ background: none;
+ border: none;
+ padding: 1px;
+ cursor: pointer;
+ color: var(--accent);
+ opacity: 0.7;
+ border-radius: 50%;
+ transition: opacity 0.1s;
+}
+.active-filter-chip-remove:hover { opacity: 1; }
+.active-filter-clear-all {
+ background: none;
+ border: none;
+ font-size: 12px;
+ color: var(--text-muted);
+ cursor: pointer;
+ padding: 3px 6px;
+ border-radius: 4px;
+ transition: color 0.1s;
+}
+.active-filter-clear-all:hover { color: var(--text-secondary); }
+
+/* ── Operation agent badges ── */
+.op-agent-badges {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+ margin-top: 4px;
+}
+.op-agent-badge {
+ display: inline-block;
+ padding: 1px 6px;
+ font-size: 10px;
+ font-weight: 500;
+ background: rgba(99,102,241,0.12);
+ border: 1px solid rgba(99,102,241,0.25);
+ border-radius: 3px;
+ color: #818cf8;
+ white-space: nowrap;
+}
+
+/* ── Row delete button ── */
+.row-btn-delete {
+ color: var(--text-muted);
+ transition: color 0.1s, background 0.1s;
+}
+.row-btn-delete:hover {
+ color: var(--red, #f87171);
+ background: rgba(248,113,113,0.08);
+}
diff --git a/apps/ui/css/layout.css b/apps/ui/css/layout.css
new file mode 100644
index 0000000..2092915
--- /dev/null
+++ b/apps/ui/css/layout.css
@@ -0,0 +1,231 @@
+/* ── Navbar ── */
+.navbar {
+ height: 60px;
+ background: var(--bg-surface);
+ border-bottom: 1px solid var(--border);
+ display: flex;
+ align-items: center;
+ padding: 0 40px;
+ position: sticky;
+ top: 0;
+ z-index: 100;
+ box-shadow: 0 1px 0 var(--border-subtle), 0 4px 16px rgba(0,0,0,0.4);
+}
+
+.nav-logo {
+ display: flex;
+ align-items: center;
+ gap: 9px;
+ margin-right: 36px;
+ text-decoration: none;
+}
+.nav-logo-mark {
+ width: 28px;
+ height: 28px;
+ background: linear-gradient(135deg, #0d9488, #0891b2);
+ border-radius: 7px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-size: 13px;
+ font-weight: 800;
+ flex-shrink: 0;
+}
+.nav-logo-text {
+ font-size: 15px;
+ font-weight: 700;
+ letter-spacing: -0.4px;
+ color: var(--text-primary);
+}
+
+.nav-links {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+ flex: 1;
+}
+.nav-link {
+ padding: 6px 14px;
+ border-radius: var(--radius);
+ font-size: 14px;
+ font-weight: 450;
+ color: var(--text-muted);
+ text-decoration: none;
+ border: none;
+ background: none;
+ transition: all 0.1s;
+}
+.nav-link:hover { background: var(--bg-overlay); color: var(--text-secondary); }
+.nav-link.active { color: var(--text-primary); font-weight: 550; background: var(--bg-overlay); }
+
+.nav-right {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-left: auto;
+}
+.nav-kbd {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 4px 9px;
+ font-size: 12px;
+ color: var(--text-muted);
+ background: var(--bg-overlay);
+ cursor: pointer;
+}
+.nav-icon-btn {
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: var(--radius);
+ border: none;
+ background: none;
+ color: var(--text-muted);
+ transition: all 0.1s;
+ position: relative;
+}
+.nav-icon-btn:hover { background: var(--bg-overlay); color: var(--text-secondary); }
+.nav-divider { width: 1px; height: 20px; background: var(--border); }
+
+.nav-avatar {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #0d9488, #6366f1);
+ color: #fff;
+ font-size: 11.5px;
+ font-weight: 700;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ border: 2px solid transparent;
+ transition: border-color 0.15s;
+}
+.nav-avatar:hover { border-color: var(--accent); }
+
+/* ── User dropdown ── */
+.user-menu {
+ position: relative;
+}
+.user-dropdown {
+ position: absolute;
+ top: calc(100% + 8px);
+ right: 0;
+ min-width: 180px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ box-shadow: var(--shadow);
+ overflow: hidden;
+ z-index: 200;
+}
+.user-dropdown-header {
+ padding: 12px 14px;
+ border-bottom: 1px solid var(--border-subtle);
+}
+.user-dropdown-name {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-primary);
+}
+.user-dropdown-role {
+ font-size: 11.5px;
+ color: var(--text-muted);
+ margin-top: 2px;
+}
+.user-dropdown-item {
+ display: flex;
+ align-items: center;
+ gap: 9px;
+ padding: 9px 14px;
+ font-size: 13.5px;
+ color: var(--text-secondary);
+ border: none;
+ background: none;
+ width: 100%;
+ text-align: left;
+ transition: all 0.1s;
+}
+.user-dropdown-item:hover { background: var(--bg-overlay); color: var(--text-primary); }
+.user-dropdown-item.danger:hover { background: var(--red-bg); color: var(--red); }
+.user-dropdown-item svg { flex-shrink: 0; opacity: 0.7; }
+
+/* ── Page container ── */
+.page {
+ max-width: 1160px;
+ margin: 0 auto;
+ padding: 36px 40px 60px;
+}
+
+/* ── Hamburger button (hidden on desktop) ── */
+.nav-hamburger {
+ display: none;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 5px;
+ width: 32px;
+ height: 32px;
+ border: none;
+ background: none;
+ cursor: pointer;
+ padding: 4px;
+ border-radius: var(--radius);
+ transition: background 0.1s;
+ order: 10; /* after other right items */
+}
+.nav-hamburger:hover { background: var(--bg-overlay); }
+.nav-hamburger span {
+ display: block;
+ width: 18px;
+ height: 1.5px;
+ background: var(--text-muted);
+ border-radius: 1px;
+ transition: all 0.2s;
+ transform-origin: center;
+}
+.nav-hamburger.open span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
+.nav-hamburger.open span:nth-child(2) { opacity: 0; }
+.nav-hamburger.open span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }
+
+/* ── Mobile nav dropdown ── */
+.mobile-nav {
+ display: none;
+ position: sticky;
+ top: 60px;
+ z-index: 99;
+ background: var(--bg-surface);
+ border-bottom: 1px solid var(--border);
+ padding: 8px 16px 12px;
+ box-shadow: 0 4px 16px rgba(0,0,0,0.4);
+}
+.mobile-nav-link {
+ display: block;
+ padding: 10px 12px;
+ border-radius: var(--radius);
+ font-size: 14px;
+ font-weight: 500;
+ color: var(--text-muted);
+ text-decoration: none;
+ transition: all 0.1s;
+}
+.mobile-nav-link:hover { background: var(--bg-overlay); color: var(--text-secondary); }
+.mobile-nav-link.active { color: var(--text-primary); background: var(--bg-overlay); }
+
+/* ── Responsive breakpoints ── */
+@media (max-width: 720px) {
+ .navbar { padding: 0 16px; }
+ .nav-links { display: none; }
+ .nav-kbd { display: none; }
+ .nav-hamburger { display: flex; }
+ .mobile-nav { display: block; }
+
+ .page { padding: 20px 16px 40px; }
+}
diff --git a/apps/ui/css/login.css b/apps/ui/css/login.css
new file mode 100644
index 0000000..3cb948c
--- /dev/null
+++ b/apps/ui/css/login.css
@@ -0,0 +1,192 @@
+.login-page {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 24px;
+ background:
+ radial-gradient(ellipse 70% 50% at 50% -20%, rgba(13,148,136,0.12) 0%, transparent 70%),
+ var(--bg-canvas);
+}
+
+.login-card {
+ width: 100%;
+ max-width: 380px;
+}
+
+.login-logo {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ justify-content: center;
+ margin-bottom: 36px;
+}
+
+.login-logo-mark {
+ width: 36px;
+ height: 36px;
+ background: linear-gradient(135deg, #0d9488, #0891b2);
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-size: 17px;
+ font-weight: 800;
+}
+
+.login-logo-text {
+ font-size: 20px;
+ font-weight: 700;
+ letter-spacing: -0.5px;
+ color: var(--text-primary);
+}
+
+.login-box {
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: 14px;
+ padding: 32px;
+ box-shadow: 0 8px 40px rgba(0,0,0,0.4);
+}
+
+.login-heading {
+ font-size: 20px;
+ font-weight: 700;
+ letter-spacing: -0.4px;
+ color: var(--text-primary);
+ margin-bottom: 4px;
+}
+
+.login-sub {
+ font-size: 13.5px;
+ color: var(--text-muted);
+ margin-bottom: 28px;
+}
+
+.field {
+ margin-bottom: 16px;
+}
+
+.field-label {
+ display: block;
+ font-size: 12.5px;
+ font-weight: 500;
+ color: var(--text-secondary);
+ margin-bottom: 6px;
+}
+
+.field-input {
+ width: 100%;
+ padding: 9px 12px;
+ background: var(--bg-canvas);
+ border: 1px solid var(--border);
+ border-radius: 7px;
+ font-family: 'Inter', sans-serif;
+ font-size: 13.5px;
+ color: var(--text-primary);
+ outline: none;
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+
+.field-input::placeholder { color: var(--text-muted); }
+
+.field-input:focus {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px rgba(13,148,136,0.25);
+}
+
+.field-footer {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 5px;
+}
+
+.field-link {
+ font-size: 12px;
+ color: var(--accent);
+ text-decoration: none;
+}
+
+.field-link:hover { text-decoration: underline; }
+
+.btn-signin {
+ width: 100%;
+ padding: 10px;
+ margin-top: 8px;
+ background: var(--accent);
+ color: #fff;
+ border: 1px solid var(--accent-dim);
+ border-radius: 7px;
+ font-family: 'Inter', sans-serif;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.15s;
+ box-shadow: 0 1px 4px rgba(13,148,136,0.4);
+}
+
+.btn-signin:hover {
+ background: var(--accent-dim);
+ box-shadow: 0 2px 10px rgba(13,148,136,0.5), 0 0 0 3px rgba(13,148,136,0.25);
+}
+
+.btn-signin:active { transform: translateY(1px); }
+
+.login-error {
+ display: none;
+ background: var(--red-bg);
+ border: 1px solid var(--red-border);
+ border-radius: 7px;
+ padding: 10px 14px;
+ font-size: 13px;
+ color: var(--red);
+ margin-bottom: 16px;
+}
+
+.login-footer {
+ text-align: center;
+ margin-top: 20px;
+ font-size: 12.5px;
+ color: var(--text-muted);
+}
+
+.login-footer a { color: var(--accent); text-decoration: none; }
+.login-footer a:hover { text-decoration: underline; }
+
+.login-divider {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin: 22px 0;
+}
+
+.login-divider-line { flex: 1; height: 1px; background: var(--border); }
+.login-divider-text { font-size: 11.5px; color: var(--text-muted); white-space: nowrap; }
+
+.btn-sso {
+ width: 100%;
+ padding: 9px;
+ background: var(--bg-overlay);
+ color: var(--text-secondary);
+ border: 1px solid var(--border);
+ border-radius: 7px;
+ font-family: 'Inter', sans-serif;
+ font-size: 13.5px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.15s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 9px;
+}
+
+.btn-sso:hover { background: var(--bg-muted); color: var(--text-primary); }
+
+.version-badge {
+ text-align: center;
+ margin-top: 28px;
+ font-size: 11.5px;
+ color: var(--text-muted);
+}
diff --git a/apps/ui/css/logs.css b/apps/ui/css/logs.css
new file mode 100644
index 0000000..e3f4a04
--- /dev/null
+++ b/apps/ui/css/logs.css
@@ -0,0 +1,125 @@
+.log-toolbar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 12px 20px;
+ border-bottom: 1px solid var(--border-subtle);
+ flex-wrap: wrap;
+}
+
+.live-dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: var(--green);
+ animation: pulse 1.8s ease-in-out infinite;
+ flex-shrink: 0;
+}
+
+@keyframes pulse {
+ 0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(63,185,80,0.6); }
+ 50% { opacity: 0.7; box-shadow: 0 0 0 5px rgba(63,185,80,0); }
+}
+
+.live-label {
+ font-size: 11.5px;
+ font-weight: 600;
+ color: var(--green);
+ letter-spacing: 0.2px;
+}
+
+.toolbar-sep { width: 1px; height: 16px; background: var(--border); flex-shrink: 0; }
+
+.time-range-select {
+ background: var(--bg-canvas);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 5px 28px 5px 10px;
+ font-family: 'Inter', sans-serif;
+ font-size: 12.5px;
+ color: var(--text-secondary);
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12'%3E%3Cpath fill='%236e7681' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 8px center;
+ cursor: pointer;
+ outline: none;
+}
+
+.time-range-select:focus {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px var(--accent-ring);
+}
+
+.log-op-name {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 11.5px;
+ color: var(--text-muted);
+ background: var(--bg-overlay);
+ border: 1px solid var(--border-subtle);
+ padding: 1px 7px;
+ border-radius: 4px;
+ white-space: nowrap;
+}
+
+.log-status {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 11.5px;
+ font-weight: 600;
+}
+
+.log-status.ok { color: var(--green); }
+.log-status.err { color: var(--red); }
+.log-status.warn { color: var(--amber); }
+
+.log-entry-expanded {
+ padding: 10px 20px 16px calc(20px + 152px + 12px + 58px + 12px);
+ background: var(--bg-canvas);
+ border-bottom: 1px solid var(--border-subtle);
+ display: none;
+}
+
+.log-entry-expanded.open { display: block; }
+
+.log-detail-block {
+ background: #161b22;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 12px 14px;
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 12px;
+ color: #c9d1d9;
+ line-height: 1.65;
+ white-space: pre-wrap;
+ word-break: break-all;
+}
+
+.log-detail-label {
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.5px;
+ text-transform: uppercase;
+ color: var(--text-muted);
+ margin-bottom: 6px;
+ margin-top: 12px;
+}
+
+.log-detail-label:first-child { margin-top: 0; }
+
+.refresh-btn {
+ margin-left: auto;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 5px 12px;
+ background: var(--bg-overlay);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ font-size: 12.5px;
+ color: var(--text-muted);
+ cursor: pointer;
+ font-family: 'Inter', sans-serif;
+ transition: all 0.12s;
+}
+
+.refresh-btn:hover { color: var(--text-secondary); background: var(--bg-muted); }
diff --git a/apps/ui/css/pages.css b/apps/ui/css/pages.css
new file mode 100644
index 0000000..1fb23f3
--- /dev/null
+++ b/apps/ui/css/pages.css
@@ -0,0 +1,1458 @@
+/* ══════════════════════════════════════════════════
+ SHARED INNER-PAGE STYLES
+ Used by: api-keys, logs, usage, settings
+══════════════════════════════════════════════════ */
+
+/* ── Dropdown divider (used in navbar and elsewhere) ── */
+.dropdown-divider {
+ height: 1px;
+ background: var(--border-subtle);
+ margin: 4px 0;
+}
+
+/* ══════════════════════════════════════════════════
+ PAGE HEADER
+══════════════════════════════════════════════════ */
+.page-header {
+ display: flex;
+ align-items: flex-start;
+ gap: 16px;
+ margin-bottom: 28px;
+}
+
+.page-header-text { flex: 1; }
+
+.page-title {
+ font-size: 22px;
+ font-weight: 700;
+ letter-spacing: -0.4px;
+ color: var(--text-primary);
+ line-height: 1.2;
+}
+
+.page-subtitle {
+ font-size: 13.5px;
+ color: var(--text-muted);
+ margin-top: 4px;
+ line-height: 1.5;
+}
+
+.page-header-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex-shrink: 0;
+}
+
+/* ══════════════════════════════════════════════════
+ BUTTONS
+══════════════════════════════════════════════════ */
+.btn-primary {
+ display: inline-flex;
+ align-items: center;
+ gap: 7px;
+ padding: 8px 16px;
+ background: var(--accent);
+ color: #fff;
+ border: 1px solid var(--accent-dim);
+ border-radius: var(--radius);
+ font-size: 13.5px;
+ font-weight: 500;
+ font-family: 'Inter', sans-serif;
+ cursor: pointer;
+ transition: all 0.15s;
+ white-space: nowrap;
+}
+
+.btn-primary:hover {
+ background: var(--accent-dim);
+ box-shadow: 0 0 0 3px var(--accent-ring);
+}
+
+.btn-secondary {
+ display: inline-flex;
+ align-items: center;
+ gap: 7px;
+ padding: 8px 16px;
+ background: var(--bg-overlay);
+ color: var(--text-secondary);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ font-size: 13.5px;
+ font-weight: 500;
+ font-family: 'Inter', sans-serif;
+ cursor: pointer;
+ transition: all 0.15s;
+ white-space: nowrap;
+}
+
+.btn-secondary:hover { background: var(--bg-muted); color: var(--text-primary); }
+
+.btn-danger {
+ display: inline-flex;
+ align-items: center;
+ gap: 7px;
+ padding: 8px 16px;
+ background: transparent;
+ color: var(--red);
+ border: 1px solid rgba(248, 81, 73, 0.3);
+ border-radius: var(--radius);
+ font-size: 13.5px;
+ font-weight: 500;
+ font-family: 'Inter', sans-serif;
+ cursor: pointer;
+ transition: all 0.15s;
+}
+
+.btn-danger:hover { background: var(--red-bg); border-color: var(--red-border); }
+
+.btn-icon {
+ width: 30px;
+ height: 30px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--bg-overlay);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ color: var(--text-muted);
+ cursor: pointer;
+ transition: all 0.15s;
+ flex-shrink: 0;
+}
+
+.btn-icon:hover { background: var(--bg-muted); color: var(--text-secondary); }
+
+.btn-icon.danger:hover { background: var(--red-bg); color: var(--red); border-color: var(--red-border); }
+
+/* ══════════════════════════════════════════════════
+ SECTION CARD
+══════════════════════════════════════════════════ */
+.section-card {
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ overflow: hidden;
+ margin-bottom: 20px;
+}
+
+.section-card-header {
+ padding: 14px 20px;
+ border-bottom: 1px solid var(--border-subtle);
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.section-card-title {
+ font-size: 13.5px;
+ font-weight: 600;
+ color: var(--text-primary);
+ flex: 1;
+}
+
+.section-card-subtitle {
+ font-size: 12px;
+ color: var(--text-muted);
+ margin-top: 1px;
+}
+
+.section-card-body { padding: 20px; }
+
+/* ══════════════════════════════════════════════════
+ CALLOUT / INFO BOX
+══════════════════════════════════════════════════ */
+.callout {
+ display: flex;
+ gap: 12px;
+ padding: 13px 16px;
+ border-radius: var(--radius);
+ margin-bottom: 20px;
+ border: 1px solid;
+ font-size: 13px;
+ line-height: 1.55;
+}
+
+.callout.info {
+ background: rgba(88, 166, 255, 0.07);
+ border-color: var(--blue-border);
+ color: var(--text-secondary);
+}
+
+.callout.warning {
+ background: var(--amber-bg);
+ border-color: var(--amber-border);
+ color: var(--text-secondary);
+}
+
+.callout.danger {
+ background: var(--red-bg);
+ border-color: var(--red-border);
+ color: var(--text-secondary);
+}
+
+.callout-icon { flex-shrink: 0; margin-top: 1px; }
+.callout strong { color: var(--text-primary); }
+
+/* ══════════════════════════════════════════════════
+ STAT CARDS
+══════════════════════════════════════════════════ */
+.stats-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 12px;
+ margin-bottom: 24px;
+}
+
+.stat-card {
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ padding: 16px 18px;
+}
+
+.stat-label {
+ font-size: 11.5px;
+ font-weight: 500;
+ color: var(--text-muted);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ margin-bottom: 8px;
+}
+
+.stat-value {
+ font-size: 28px;
+ font-weight: 700;
+ letter-spacing: -1px;
+ color: var(--text-primary);
+ line-height: 1;
+ margin-bottom: 4px;
+ font-variant-numeric: tabular-nums;
+}
+
+.stat-delta {
+ font-size: 12px;
+ color: var(--text-muted);
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.stat-delta.up { color: var(--green); }
+.stat-delta.down { color: var(--red); }
+
+/* ══════════════════════════════════════════════════
+ DATA TABLE
+══════════════════════════════════════════════════ */
+.data-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 13.5px;
+}
+
+.data-table th {
+ padding: 10px 16px;
+ text-align: left;
+ font-size: 11.5px;
+ font-weight: 600;
+ color: var(--text-muted);
+ text-transform: uppercase;
+ letter-spacing: 0.4px;
+ background: var(--bg-canvas);
+ border-bottom: 1px solid var(--border);
+ white-space: nowrap;
+}
+
+.data-table td {
+ padding: 12px 16px;
+ color: var(--text-secondary);
+ border-bottom: 1px solid var(--border-subtle);
+ vertical-align: middle;
+}
+
+.data-table tbody tr:last-child td { border-bottom: none; }
+
+.data-table tbody tr:hover td { background: var(--bg-overlay); }
+
+.data-table .col-name { color: var(--text-primary); font-weight: 500; }
+.data-table .col-mono {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 12.5px;
+ color: var(--text-secondary);
+}
+
+.data-table .col-actions {
+ text-align: right;
+ white-space: nowrap;
+}
+
+.data-table .col-actions { display: flex; justify-content: flex-end; gap: 6px; }
+
+/* ══════════════════════════════════════════════════
+ BADGES
+══════════════════════════════════════════════════ */
+.badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ padding: 2px 8px;
+ border-radius: 4px;
+ font-size: 11.5px;
+ font-weight: 500;
+ border: 1px solid;
+ white-space: nowrap;
+}
+
+.badge-active { background: var(--green-bg); border-color: var(--green-border); color: var(--green); }
+.badge-revoked { background: var(--red-bg); border-color: var(--red-border); color: var(--red); }
+.badge-draft { background: var(--amber-bg); border-color: var(--amber-border); color: var(--amber); }
+.badge-scope {
+ background: var(--bg-overlay);
+ border-color: var(--border);
+ color: var(--text-muted);
+ font-size: 10.5px;
+ padding: 1px 6px;
+}
+
+.log-level {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ padding: 2px 8px;
+ border-radius: 4px;
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.3px;
+ text-transform: uppercase;
+ border: 1px solid;
+ font-variant-numeric: tabular-nums;
+ white-space: nowrap;
+}
+
+.log-level.info { background: rgba(88,166,255,0.08); border-color: var(--blue-border); color: var(--blue); }
+.log-level.warn { background: var(--amber-bg); border-color: var(--amber-border); color: var(--amber); }
+.log-level.error { background: var(--red-bg); border-color: var(--red-border); color: var(--red); }
+.log-level.debug { background: var(--bg-overlay); border-color: var(--border); color: var(--text-muted); }
+
+/* ══════════════════════════════════════════════════
+ PAGE TABS
+══════════════════════════════════════════════════ */
+.page-tabs {
+ display: flex;
+ gap: 2px;
+ border-bottom: 1px solid var(--border);
+ margin-bottom: 20px;
+}
+
+.page-tab {
+ padding: 8px 16px;
+ font-size: 13.5px;
+ font-weight: 450;
+ color: var(--text-muted);
+ border: none;
+ background: none;
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+ margin-bottom: -1px;
+ transition: all 0.15s;
+ font-family: 'Inter', sans-serif;
+ display: flex;
+ align-items: center;
+ gap: 7px;
+}
+
+.page-tab:hover { color: var(--text-secondary); }
+.page-tab.active { color: var(--text-primary); border-bottom-color: var(--accent); font-weight: 550; }
+
+.page-tab-count {
+ font-size: 11px;
+ background: var(--bg-overlay);
+ border: 1px solid var(--border);
+ border-radius: 10px;
+ padding: 0 6px;
+ color: var(--text-muted);
+ font-weight: 500;
+ min-width: 18px;
+ text-align: center;
+}
+
+.page-tab.active .page-tab-count {
+ background: var(--accent-glow);
+ border-color: rgba(13,148,136,0.2);
+ color: var(--accent);
+}
+
+/* ══════════════════════════════════════════════════
+ FILTER BAR
+══════════════════════════════════════════════════ */
+.filter-bar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 16px;
+ flex-wrap: wrap;
+}
+
+.filter-bar-search {
+ position: relative;
+ flex: 1;
+ min-width: 180px;
+ max-width: 320px;
+}
+
+.filter-bar-search svg {
+ position: absolute;
+ left: 10px;
+ top: 50%;
+ transform: translateY(-50%);
+ color: var(--text-muted);
+ pointer-events: none;
+}
+
+.filter-bar-search input {
+ width: 100%;
+ padding: 7px 12px 7px 32px;
+ background: var(--bg-canvas);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ font-family: 'Inter', sans-serif;
+ font-size: 13px;
+ color: var(--text-primary);
+ outline: none;
+ transition: border-color 0.15s;
+}
+
+.filter-bar-search input::placeholder { color: var(--text-muted); }
+.filter-bar-search input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
+
+.filter-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 5px 12px;
+ background: var(--bg-overlay);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ font-size: 12.5px;
+ font-weight: 450;
+ color: var(--text-muted);
+ cursor: pointer;
+ transition: all 0.12s;
+ font-family: 'Inter', sans-serif;
+}
+
+.filter-chip:hover { background: var(--bg-muted); color: var(--text-secondary); }
+.filter-chip.active { background: var(--accent-glow); border-color: rgba(13,148,136,0.3); color: var(--accent); }
+
+/* ══════════════════════════════════════════════════
+ LOG ENTRY
+══════════════════════════════════════════════════ */
+.log-list {
+ display: flex;
+ flex-direction: column;
+}
+
+.log-entry {
+ display: flex;
+ align-items: flex-start;
+ gap: 12px;
+ padding: 10px 20px;
+ border-bottom: 1px solid var(--border-subtle);
+ transition: background 0.1s;
+ font-size: 13px;
+ cursor: pointer;
+}
+
+.log-entry:last-child { border-bottom: none; }
+.log-entry:hover { background: var(--bg-overlay); }
+
+.log-time {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 11.5px;
+ color: var(--text-muted);
+ white-space: nowrap;
+ padding-top: 2px;
+ min-width: 152px;
+ flex-shrink: 0;
+}
+
+.log-msg {
+ flex: 1;
+ line-height: 1.5;
+ color: var(--text-secondary);
+ word-break: break-word;
+}
+
+.log-msg strong { color: var(--text-primary); font-weight: 500; }
+
+.log-duration {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 11.5px;
+ color: var(--text-muted);
+ white-space: nowrap;
+ flex-shrink: 0;
+ padding-top: 2px;
+ min-width: 54px;
+ text-align: right;
+}
+
+.log-duration.slow { color: var(--amber); }
+.log-duration.error { color: var(--red); }
+
+/* ══════════════════════════════════════════════════
+ SETTINGS LAYOUT
+══════════════════════════════════════════════════ */
+.settings-layout {
+ display: grid;
+ grid-template-columns: 200px 1fr;
+ gap: 32px;
+ align-items: flex-start;
+}
+
+.settings-nav {
+ position: sticky;
+ top: 80px;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.settings-nav-item {
+ display: flex;
+ align-items: center;
+ gap: 9px;
+ padding: 8px 12px;
+ border-radius: var(--radius);
+ font-size: 13.5px;
+ color: var(--text-muted);
+ text-decoration: none;
+ transition: all 0.1s;
+ cursor: pointer;
+ border: none;
+ background: none;
+ font-family: 'Inter', sans-serif;
+ width: 100%;
+ text-align: left;
+}
+
+.settings-nav-item svg { flex-shrink: 0; opacity: 0.7; }
+.settings-nav-item:hover { background: var(--bg-overlay); color: var(--text-secondary); }
+.settings-nav-item.active { background: var(--bg-overlay); color: var(--text-primary); font-weight: 500; }
+.settings-nav-item.danger { color: var(--red); opacity: 0.8; }
+.settings-nav-item.danger:hover { background: var(--red-bg); opacity: 1; }
+
+.settings-nav-divider { height: 1px; background: var(--border-subtle); margin: 6px 4px; }
+
+/* ── Form elements (for settings forms) ── */
+.field-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
+.field-label {
+ font-size: 12.5px;
+ font-weight: 500;
+ color: var(--text-secondary);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+.field-hint { font-size: 12px; color: var(--text-muted); line-height: 1.55; }
+.field-input,
+.field-select,
+.field-textarea {
+ background: var(--bg-canvas);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 8px 12px;
+ font-family: 'Inter', sans-serif;
+ font-size: 13.5px;
+ color: var(--text-primary);
+ width: 100%;
+ outline: none;
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+.field-input::placeholder, .field-textarea::placeholder { color: var(--text-muted); }
+.field-input:focus, .field-select:focus, .field-textarea:focus {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px var(--accent-ring);
+}
+.field-select {
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%236e7681' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 11px center;
+ padding-right: 32px;
+ cursor: pointer;
+}
+.field-textarea { resize: vertical; min-height: 80px; line-height: 1.6; }
+.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
+
+/* ── Section divider ── */
+.settings-section-divider {
+ height: 1px;
+ background: var(--border);
+ margin: 24px 0;
+}
+
+/* ── API key display ── */
+.key-reveal {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ background: var(--bg-canvas);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 8px 12px;
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 13px;
+ color: var(--text-secondary);
+ letter-spacing: 0.2px;
+}
+
+.key-reveal .key-mask { flex: 1; letter-spacing: 2px; color: var(--text-muted); }
+.key-reveal .key-copy { flex-shrink: 0; cursor: pointer; color: var(--text-muted); }
+.key-reveal .key-copy:hover { color: var(--accent); }
+
+/* ══════════════════════════════════════════════════
+ EMPTY STATE
+══════════════════════════════════════════════════ */
+.empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 60px 24px;
+ text-align: center;
+}
+
+.empty-state-icon {
+ width: 44px;
+ height: 44px;
+ background: var(--bg-overlay);
+ border: 1px solid var(--border);
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--text-muted);
+ margin-bottom: 14px;
+}
+
+.empty-state-title {
+ font-size: 15px;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin-bottom: 6px;
+}
+
+.empty-state-text {
+ font-size: 13.5px;
+ color: var(--text-muted);
+ max-width: 320px;
+ line-height: 1.6;
+ margin-bottom: 20px;
+}
+
+/* ══════════════════════════════════════════════════
+ MODAL OVERLAY
+══════════════════════════════════════════════════ */
+.modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.65);
+ z-index: 500;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 24px;
+ display: none;
+}
+
+.modal-overlay.open { display: flex; }
+
+.modal {
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ width: 100%;
+ max-width: 480px;
+ box-shadow: 0 8px 40px rgba(0,0,0,0.6);
+ overflow: hidden;
+}
+
+.modal-header {
+ padding: 18px 20px 14px;
+ border-bottom: 1px solid var(--border-subtle);
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.modal-title {
+ font-size: 15px;
+ font-weight: 600;
+ color: var(--text-primary);
+ flex: 1;
+}
+
+.modal-close {
+ width: 26px;
+ height: 26px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: none;
+ background: none;
+ color: var(--text-muted);
+ cursor: pointer;
+ border-radius: var(--radius);
+ transition: all 0.1s;
+}
+.modal-close:hover { background: var(--bg-overlay); color: var(--text-primary); }
+
+.modal-body { padding: 20px; }
+.modal-footer { padding: 14px 20px; border-top: 1px solid var(--border-subtle); display: flex; justify-content: flex-end; gap: 8px; }
+
+/* ══════════════════════════════════════════════════
+ RESPONSIVE
+══════════════════════════════════════════════════ */
+@media (max-width: 960px) {
+ .stats-grid { grid-template-columns: repeat(2, 1fr); }
+}
+
+@media (max-width: 720px) {
+ .page-header { flex-direction: column; gap: 12px; }
+ .page-header-actions { width: 100%; }
+ .settings-layout { grid-template-columns: 1fr; }
+ .settings-nav { position: static; flex-direction: row; flex-wrap: wrap; }
+ .filter-bar { flex-direction: column; align-items: stretch; }
+ .filter-bar-search { max-width: none; }
+ .stats-grid { grid-template-columns: 1fr 1fr; }
+ .field-row { grid-template-columns: 1fr; }
+}
+
+@media (max-width: 480px) {
+ .stats-grid { grid-template-columns: 1fr; }
+}
+
+/* ── Language switcher ── */
+.lang-switcher {
+ display: flex;
+ gap: 8px;
+}
+
+.lang-btn {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 18px;
+ background: var(--bg-canvas);
+ border: 1.5px solid var(--border);
+ border-radius: 8px;
+ font-family: 'Inter', sans-serif;
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--text-secondary);
+ cursor: pointer;
+ transition: border-color 0.15s, color 0.15s, background 0.15s;
+}
+
+.lang-btn:hover {
+ border-color: var(--accent);
+ color: var(--text-primary);
+}
+
+.lang-btn.active {
+ border-color: var(--accent);
+ background: var(--accent-glow);
+ color: #fff;
+ font-weight: 600;
+}
+
+.lang-flag {
+ font-size: 16px;
+ line-height: 1;
+}
+
+
+/* ═══════════════════════════════════════════════════
+ Workspace switcher
+═══════════════════════════════════════════════════ */
+.ws-switcher {
+ position: relative;
+ display: flex;
+ align-items: center;
+ margin-left: 4px;
+}
+
+.ws-switcher-trigger {
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ padding: 4px 8px 4px 4px;
+ background: none;
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ cursor: pointer;
+ color: var(--text-primary);
+ font-size: 13px;
+ font-weight: 500;
+ transition: border-color 0.15s, background 0.15s;
+ max-width: 200px;
+ white-space: nowrap;
+}
+.ws-switcher-trigger:hover { border-color: var(--border-muted, #444c56); background: rgba(255,255,255,0.03); }
+
+.ws-dot {
+ width: 22px;
+ height: 22px;
+ border-radius: 5px;
+ background: #0d9488;
+ color: #fff;
+ font-size: 11px;
+ font-weight: 700;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+}
+
+.ws-current-name {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 140px;
+}
+
+.ws-dropdown {
+ position: absolute;
+ top: calc(100% + 6px);
+ left: 0;
+ min-width: 230px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: 10px;
+ box-shadow: 0 8px 28px rgba(0,0,0,0.45);
+ z-index: 200;
+ overflow: hidden;
+}
+
+.ws-dropdown-label {
+ padding: 10px 14px 4px;
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--text-muted);
+}
+
+.ws-dropdown-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 14px;
+ cursor: pointer;
+ transition: background 0.1s;
+}
+.ws-dropdown-item:hover { background: rgba(255,255,255,0.04); }
+.ws-dropdown-item.active { background: rgba(56,139,253,0.08); }
+
+.ws-item-dot {
+ width: 30px;
+ height: 30px;
+ border-radius: 7px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 13px;
+ font-weight: 700;
+ color: #fff;
+ flex-shrink: 0;
+}
+
+.ws-item-info { flex: 1; min-width: 0; }
+.ws-item-name {
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--text-primary);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.ws-item-role { font-size: 11px; color: var(--text-muted); }
+
+.ws-dropdown-footer { border-top: 1px solid var(--border-subtle); padding: 6px 8px; }
+.ws-dropdown-create {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ width: 100%;
+ padding: 6px 8px;
+ background: none;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 13px;
+ color: var(--text-secondary);
+ transition: background 0.1s, color 0.1s;
+}
+.ws-dropdown-create:hover { background: rgba(255,255,255,0.04); color: var(--text-primary); }
+
+
+/* ═══════════════════════════════════════════════════
+ Agents page — info callout
+═══════════════════════════════════════════════════ */
+.agents-info-callout {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 16px;
+ background: rgba(56,139,253,0.07);
+ border: 1px solid rgba(56,139,253,0.2);
+ border-radius: 8px;
+ font-size: 13px;
+ color: var(--text-secondary);
+ line-height: 1.5;
+ margin-bottom: 24px;
+}
+.agents-info-callout strong { color: var(--text-primary); }
+
+
+/* ═══════════════════════════════════════════════════
+ Agent cards grid
+═══════════════════════════════════════════════════ */
+.agents-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
+ gap: 16px;
+ margin-bottom: 40px;
+}
+
+.agent-card {
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+ transition: border-color 0.15s;
+}
+.agent-card:hover { border-color: var(--border-muted, #444c56); }
+
+.agent-card-header {
+ display: flex;
+ align-items: flex-start;
+ gap: 12px;
+}
+
+.agent-card-icon {
+ width: 38px;
+ height: 38px;
+ border-radius: 9px;
+ background: rgba(56,139,253,0.1);
+ border: 1px solid rgba(56,139,253,0.2);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ color: var(--accent);
+}
+
+.agent-card-title-wrap { flex: 1; min-width: 0; }
+.agent-card-name {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--text-primary);
+ line-height: 1.3;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.agent-card-slug {
+ font-family: var(--font-mono, monospace);
+ font-size: 11px;
+ color: var(--text-muted);
+ margin-top: 2px;
+}
+
+.agent-status-badge {
+ font-size: 11px;
+ font-weight: 600;
+ padding: 2px 8px;
+ border-radius: 20px;
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+.agent-status-active { background: rgba(63,185,80,0.15); color: #3fb950; border: 1px solid rgba(63,185,80,0.3); }
+.agent-status-draft { background: rgba(139,148,158,0.15); color: #8b949e; border: 1px solid rgba(139,148,158,0.3); }
+.agent-status-inactive{ background: rgba(248,81,73,0.1); color: #f85149; border: 1px solid rgba(248,81,73,0.2); }
+
+.agent-card-desc {
+ font-size: 13px;
+ color: var(--text-secondary);
+ line-height: 1.55;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+.agent-card-stats {
+ display: flex;
+ gap: 16px;
+ flex-wrap: wrap;
+}
+.agent-stat {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ font-size: 12px;
+ color: var(--text-muted);
+}
+.agent-stat-value { font-weight: 600; color: var(--text-secondary); }
+
+.agent-endpoint-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 7px 10px;
+ background: var(--bg-canvas);
+ border: 1px solid var(--border-subtle);
+ border-radius: 6px;
+}
+.agent-endpoint-url {
+ flex: 1;
+ font-family: var(--font-mono, monospace);
+ font-size: 11px;
+ color: var(--text-muted);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.agent-endpoint-copy {
+ background: none;
+ border: none;
+ padding: 2px 4px;
+ cursor: pointer;
+ color: var(--text-muted);
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ transition: color 0.1s, background 0.1s;
+ flex-shrink: 0;
+}
+.agent-endpoint-copy:hover { color: var(--accent); background: rgba(56,139,253,0.1); }
+
+.agent-card-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-top: 12px;
+ border-top: 1px solid var(--border-subtle);
+}
+.agent-card-date { font-size: 11px; color: var(--text-muted); }
+
+.agent-card-actions { display: flex; gap: 6px; }
+.agent-action-btn {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ padding: 4px 10px;
+ background: none;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--text-secondary);
+ cursor: pointer;
+ transition: border-color 0.15s, color 0.15s, background 0.15s;
+}
+.agent-action-btn:hover { border-color: var(--border-muted, #444c56); color: var(--text-primary); background: rgba(255,255,255,0.03); }
+.agent-action-btn.danger:hover { border-color: var(--error, #f87171); color: var(--error, #f87171); background: rgba(248,113,113,0.06); }
+
+
+/* ═══════════════════════════════════════════════════
+ Drawer (side panel)
+═══════════════════════════════════════════════════ */
+.drawer-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0,0,0,0.45);
+ z-index: 150;
+ backdrop-filter: blur(2px);
+}
+
+.drawer {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ width: 520px;
+ max-width: 100vw;
+ background: var(--bg-surface);
+ border-left: 1px solid var(--border);
+ z-index: 151;
+ display: flex;
+ flex-direction: column;
+ transform: translateX(100%);
+ transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1);
+}
+.drawer.open { transform: translateX(0); }
+
+.drawer-header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16px;
+ padding: 24px 24px 20px;
+ border-bottom: 1px solid var(--border-subtle);
+ flex-shrink: 0;
+}
+.drawer-title { font-size: 16px; font-weight: 600; color: var(--text-primary); }
+.drawer-subtitle{ font-size: 13px; color: var(--text-muted); margin-top: 3px; }
+.drawer-close {
+ background: none;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 6px;
+ cursor: pointer;
+ color: var(--text-muted);
+ display: flex;
+ align-items: center;
+ transition: border-color 0.15s, color 0.15s;
+ flex-shrink: 0;
+}
+.drawer-close:hover { border-color: var(--error, #f87171); color: var(--error, #f87171); }
+
+.drawer-body {
+ flex: 1;
+ overflow-y: auto;
+ padding: 24px;
+ display: flex;
+ flex-direction: column;
+ gap: 28px;
+}
+
+.drawer-section {}
+.drawer-section-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 4px;
+}
+.drawer-section-title {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin-bottom: 14px;
+}
+.drawer-section-header .drawer-section-title { margin-bottom: 0; }
+.drawer-section-count {
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--accent);
+ background: rgba(56,139,253,0.1);
+ padding: 2px 8px;
+ border-radius: 20px;
+}
+.drawer-section-sub {
+ font-size: 12px;
+ color: var(--text-muted);
+ margin-bottom: 12px;
+ line-height: 1.5;
+}
+
+.drawer-footer {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 8px;
+ padding: 16px 24px;
+ border-top: 1px solid var(--border-subtle);
+ flex-shrink: 0;
+ background: var(--bg-surface);
+}
+
+
+/* ═══════════════════════════════════════════════════
+ Agent status toggle (in drawer)
+═══════════════════════════════════════════════════ */
+.agent-status-toggle {
+ display: flex;
+ gap: 0;
+ background: var(--bg-canvas);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 3px;
+}
+.agent-status-opt {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ padding: 6px 12px;
+ background: none;
+ border: none;
+ border-radius: 6px;
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--text-muted);
+ cursor: pointer;
+ transition: background 0.15s, color 0.15s;
+}
+.agent-status-opt.active {
+ background: var(--bg-surface);
+ color: var(--text-primary);
+ box-shadow: 0 1px 3px rgba(0,0,0,0.25);
+}
+.status-dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ flex-shrink: 0;
+}
+
+
+/* ═══════════════════════════════════════════════════
+ Operations picker (in drawer)
+═══════════════════════════════════════════════════ */
+.ops-picker {
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.ops-picker-search {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 12px;
+ border-bottom: 1px solid var(--border-subtle);
+ background: var(--bg-canvas);
+}
+.ops-picker-search-input {
+ flex: 1;
+ background: none;
+ border: none;
+ outline: none;
+ font-size: 13px;
+ color: var(--text-primary);
+ font-family: inherit;
+}
+.ops-picker-search-input::placeholder { color: var(--text-muted); }
+.ops-picker-clear {
+ background: none;
+ border: none;
+ padding: 2px;
+ cursor: pointer;
+ color: var(--text-muted);
+ display: flex;
+ align-items: center;
+ border-radius: 3px;
+}
+.ops-picker-clear:hover { color: var(--text-secondary); }
+
+.ops-picker-list {
+ max-height: 260px;
+ overflow-y: auto;
+}
+
+.ops-picker-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 9px 12px;
+ cursor: pointer;
+ border-bottom: 1px solid var(--border-subtle);
+ transition: background 0.1s;
+}
+.ops-picker-item:last-child { border-bottom: none; }
+.ops-picker-item:hover { background: rgba(255,255,255,0.03); }
+.ops-picker-item.selected { background: rgba(56,139,253,0.07); }
+
+.ops-picker-check {
+ width: 16px;
+ height: 16px;
+ border: 1.5px solid var(--border);
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ transition: border-color 0.1s, background 0.1s;
+}
+.ops-picker-item.selected .ops-picker-check {
+ border-color: var(--accent);
+ background: rgba(56,139,253,0.12);
+}
+
+.ops-picker-info { flex: 1; min-width: 0; }
+.ops-picker-name {
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--text-primary);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.ops-picker-slug {
+ font-family: var(--font-mono, monospace);
+ font-size: 10px;
+ color: var(--text-muted);
+ margin-top: 1px;
+}
+
+.ops-picker-status-dot {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ flex-shrink: 0;
+}
+.ops-status-active { background: #3fb950; }
+.ops-status-draft { background: #8b949e; }
+.ops-status-error { background: #f85149; }
+.ops-status-inactive { background: #8b949e; }
+
+.ops-picker-empty {
+ padding: 20px;
+ text-align: center;
+ font-size: 13px;
+ color: var(--text-muted);
+}
+
+.ops-picker-footer {
+ padding: 8px 12px;
+ border-top: 1px solid var(--border-subtle);
+ background: var(--bg-canvas);
+ font-size: 12px;
+ color: var(--text-secondary);
+ display: flex;
+ align-items: center;
+}
+
+
+/* ═══════════════════════════════════════════════════
+ Agents — recommendation callout
+═══════════════════════════════════════════════════ */
+.agents-rec-callout {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ margin-top: 10px;
+ padding: 10px 12px;
+ background: rgba(210,153,34,0.08);
+ border: 1px solid rgba(210,153,34,0.25);
+ border-radius: 7px;
+ font-size: 12px;
+ color: var(--text-secondary);
+ line-height: 1.5;
+}
+.agents-rec-callout svg { flex-shrink: 0; color: #d2991f; margin-top: 1px; }
+.agents-rec-callout strong { color: var(--text-primary); }
+
+
+/* ═══════════════════════════════════════════════════
+ Settings — Members enhanced
+═══════════════════════════════════════════════════ */
+.member-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 10px 0;
+ border-bottom: 1px solid var(--border-subtle);
+}
+.member-row:last-child { border-bottom: none; }
+
+.member-last-active {
+ font-size: 12px;
+ color: var(--text-muted);
+ white-space: nowrap;
+ min-width: 80px;
+ text-align: right;
+}
+
+.member-role-select {
+ background: var(--bg-canvas);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ color: var(--text-secondary);
+ font-size: 12px;
+ font-weight: 500;
+ padding: 4px 8px;
+ cursor: pointer;
+ outline: none;
+ transition: border-color 0.15s;
+ min-width: 110px;
+}
+.member-role-select:hover { border-color: var(--accent); }
+.member-role-select:focus { border-color: var(--accent); }
+
+.member-remove-btn {
+ background: none;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 5px 6px;
+ cursor: pointer;
+ color: var(--text-muted);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: border-color 0.15s, color 0.15s, background 0.15s;
+ flex-shrink: 0;
+}
+.member-remove-btn:hover {
+ border-color: var(--error, #f87171);
+ color: var(--error, #f87171);
+ background: rgba(248,113,113,0.07);
+}
+
+.member-role-badge {
+ font-size: 11px;
+ font-weight: 600;
+ padding: 2px 9px;
+ border-radius: 20px;
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+.role-owner { background: rgba(56,139,253,0.15); color: #58a6ff; border: 1px solid rgba(56,139,253,0.3); }
+.role-admin { background: rgba(139,92,246,0.15); color: #a78bfa; border: 1px solid rgba(139,92,246,0.3); }
+.role-developer { background: rgba(63,185,80,0.12); color: #3fb950; border: 1px solid rgba(63,185,80,0.25); }
+.role-viewer { background: rgba(139,148,158,0.15); color: #8b949e; border: 1px solid rgba(139,148,158,0.3); }
+
+.role-def-row {
+ display: flex;
+ align-items: flex-start;
+ gap: 16px;
+ padding: 11px 20px;
+ border-bottom: 1px solid var(--border-subtle);
+}
+.role-def-row:last-child { border-bottom: none; }
+.role-def-text {
+ font-size: 13px;
+ color: var(--text-secondary);
+ line-height: 1.5;
+}
+
+/* ── Workspace dropdown management links ── */
+.ws-dropdown-divider {
+ height: 1px;
+ background: var(--border-subtle);
+ margin: 4px 0;
+}
+.ws-dropdown-mgmt-link {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 7px 14px;
+ font-size: 13px;
+ color: var(--text-secondary);
+ text-decoration: none;
+ transition: background 0.1s, color 0.1s;
+ cursor: pointer;
+}
+.ws-dropdown-mgmt-link:hover {
+ background: rgba(255,255,255,0.04);
+ color: var(--text-primary);
+}
+.ws-dropdown-mgmt-link svg {
+ flex-shrink: 0;
+ opacity: 0.7;
+}
diff --git a/apps/ui/css/settings.css b/apps/ui/css/settings.css
new file mode 100644
index 0000000..452ddc1
--- /dev/null
+++ b/apps/ui/css/settings.css
@@ -0,0 +1,144 @@
+.avatar-upload {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 16px;
+ background: var(--bg-canvas);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ margin-bottom: 4px;
+}
+
+.avatar-large {
+ width: 52px;
+ height: 52px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #0d9488, #6366f1);
+ color: #fff;
+ font-size: 18px;
+ font-weight: 700;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ border: 2px solid var(--border);
+}
+
+.avatar-upload-actions { flex: 1; }
+.avatar-name { font-size: 14px; font-weight: 600; color: var(--text-primary); margin-bottom: 3px; }
+.avatar-sub { font-size: 12px; color: var(--text-muted); margin-bottom: 8px; }
+
+.save-bar {
+ position: sticky;
+ bottom: 0;
+ background: rgba(13,17,23,0.9);
+ backdrop-filter: blur(12px);
+ border-top: 1px solid var(--border);
+ padding: 12px 20px;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 8px;
+ margin-top: auto;
+ display: none;
+}
+
+.save-bar.visible { display: flex; }
+
+.section-anchor { scroll-margin-top: 80px; }
+
+.danger-zone-action {
+ display: flex;
+ align-items: flex-start;
+ gap: 16px;
+ padding: 16px;
+ border-bottom: 1px solid var(--border-subtle);
+}
+
+.danger-zone-action:last-child { border-bottom: none; }
+
+.danger-zone-text { flex: 1; }
+.danger-zone-title { font-size: 13.5px; font-weight: 500; color: var(--text-primary); margin-bottom: 3px; }
+.danger-zone-desc { font-size: 12.5px; color: var(--text-muted); line-height: 1.5; }
+
+.toggle-setting-row {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ padding: 14px 0;
+ border-bottom: 1px solid var(--border-subtle);
+}
+
+.toggle-setting-row:last-child { border-bottom: none; }
+
+.toggle-sm {
+ width: 36px;
+ height: 20px;
+ border-radius: 10px;
+ background: var(--bg-muted);
+ border: 1px solid var(--border);
+ position: relative;
+ flex-shrink: 0;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.toggle-sm.on { background: var(--accent); border-color: var(--accent-dim); }
+
+.toggle-sm::after {
+ content: '';
+ position: absolute;
+ left: 2px; top: 1px;
+ width: 16px; height: 16px;
+ border-radius: 50%;
+ background: #fff;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.3);
+ transition: transform 0.2s;
+}
+
+.toggle-sm.on::after { transform: translateX(16px); }
+
+.toggle-setting-text { flex: 1; }
+.toggle-setting-label { font-size: 13.5px; font-weight: 500; color: var(--text-primary); }
+.toggle-setting-desc { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
+
+.member-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 10px 0;
+ border-bottom: 1px solid var(--border-subtle);
+}
+
+.member-row:last-child { border-bottom: none; }
+
+.member-avatar {
+ width: 30px;
+ height: 30px;
+ border-radius: 50%;
+ background: var(--bg-muted);
+ border: 1px solid var(--border);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 11px;
+ font-weight: 700;
+ color: var(--text-secondary);
+ flex-shrink: 0;
+}
+
+.member-info { flex: 1; }
+.member-name { font-size: 13.5px; color: var(--text-primary); font-weight: 500; }
+.member-email { font-size: 12px; color: var(--text-muted); }
+
+.member-role-badge {
+ font-size: 11px;
+ font-weight: 600;
+ padding: 2px 8px;
+ border-radius: 4px;
+ border: 1px solid;
+}
+
+.role-admin { background: rgba(13,148,136,0.1); border-color: rgba(13,148,136,0.25); color: var(--accent); }
+.role-member { background: var(--bg-overlay); border-color: var(--border); color: var(--text-muted); }
+.role-viewer { background: rgba(88,166,255,0.07); border-color: var(--blue-border); color: var(--blue); }
diff --git a/apps/ui/css/usage.css b/apps/ui/css/usage.css
new file mode 100644
index 0000000..9785ebe
--- /dev/null
+++ b/apps/ui/css/usage.css
@@ -0,0 +1,122 @@
+.period-select {
+ background: var(--bg-canvas);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 7px 28px 7px 11px;
+ font-family: 'Inter', sans-serif;
+ font-size: 13px;
+ color: var(--text-secondary);
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12'%3E%3Cpath fill='%236e7681' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 8px center;
+ cursor: pointer;
+ outline: none;
+ transition: border-color 0.15s;
+}
+
+.period-select:focus {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px var(--accent-ring);
+}
+
+.chart-placeholder {
+ background: var(--bg-canvas);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ height: 200px;
+ display: flex;
+ align-items: flex-end;
+ padding: 12px 16px;
+ gap: 4px;
+ overflow: hidden;
+ margin-bottom: 24px;
+ position: relative;
+}
+
+.chart-bar {
+ flex: 1;
+ border-radius: 3px 3px 0 0;
+ min-width: 0;
+ transition: opacity 0.15s;
+ position: relative;
+ cursor: pointer;
+}
+
+.chart-bar:hover { opacity: 0.8; }
+.chart-bar:hover::after {
+ content: attr(data-tip);
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: 5px;
+ padding: 4px 8px;
+ font-size: 11px;
+ color: var(--text-primary);
+ white-space: nowrap;
+ pointer-events: none;
+ margin-bottom: 4px;
+}
+
+.chart-bar.success { background: linear-gradient(180deg, var(--accent) 0%, rgba(13,148,136,0.4) 100%); }
+.chart-bar.error { background: linear-gradient(180deg, var(--red) 0%, rgba(248,81,73,0.3) 100%); }
+
+.chart-label {
+ position: absolute;
+ bottom: -20px;
+ left: 0;
+ right: 0;
+ text-align: center;
+ font-size: 10px;
+ color: var(--text-muted);
+}
+
+.chart-wrap {
+ position: relative;
+ padding-bottom: 24px;
+}
+
+.chart-y-grid {
+ position: absolute;
+ left: 0; right: 0; top: 0; bottom: 24px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ pointer-events: none;
+}
+
+.chart-y-line {
+ width: 100%;
+ height: 1px;
+ background: var(--border-subtle);
+}
+
+.quota-bar-wrap {
+ height: 8px;
+ background: var(--bg-muted);
+ border-radius: 4px;
+ overflow: hidden;
+ margin: 8px 0 4px;
+}
+
+.quota-bar-fill {
+ height: 100%;
+ border-radius: 4px;
+ background: linear-gradient(90deg, var(--accent), #7c3aed);
+}
+
+.latency-bar {
+ display: flex;
+ height: 12px;
+ border-radius: 3px;
+ overflow: hidden;
+ gap: 2px;
+}
+
+.latency-seg { border-radius: 2px; }
+.latency-p50 { background: var(--accent); }
+.latency-p95 { background: var(--amber); }
+.latency-p99 { background: var(--red); }
diff --git a/apps/ui/css/variables.css b/apps/ui/css/variables.css
new file mode 100644
index 0000000..b48a1a7
--- /dev/null
+++ b/apps/ui/css/variables.css
@@ -0,0 +1,64 @@
+:root {
+ --bg-canvas: #0d1117;
+ --bg-surface: #161b22;
+ --bg-overlay: #21262d;
+ --bg-muted: #30363d;
+ --border: rgba(48, 54, 61, 0.9);
+ --border-subtle: rgba(33, 38, 45, 0.9);
+
+ --text-primary: #e6edf3;
+ --text-secondary: #8b949e;
+ --text-muted: #6e7681;
+
+ --accent: #0d9488;
+ --accent-dim: #0f766e;
+ --accent-bright: #14b8a6;
+ --accent-glow: rgba(13, 148, 136, 0.15);
+ --accent-ring: rgba(13, 148, 136, 0.25);
+
+ --red: #f85149;
+ --red-bg: rgba(248, 81, 73, 0.10);
+ --red-border: rgba(248, 81, 73, 0.25);
+ --amber: #d29922;
+ --amber-bg: rgba(210, 153, 34, 0.10);
+ --amber-border: rgba(210, 153, 34, 0.25);
+ --green: #3fb950;
+ --green-bg: rgba(63, 185, 80, 0.10);
+ --green-border: rgba(63, 185, 80, 0.25);
+ --blue: #58a6ff;
+ --blue-bg: rgba(88, 166, 255, 0.08);
+ --blue-border: rgba(88, 166, 255, 0.20);
+ --purple: #bc8cff;
+ --purple-bg: rgba(188, 140, 255, 0.08);
+ --purple-border: rgba(188, 140, 255, 0.20);
+
+ --radius-sm: 5px;
+ --radius: 7px;
+ --radius-lg: 10px;
+
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
+ --shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 4px 16px rgba(0, 0, 0, 0.3);
+}
+
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+
+html, body {
+ height: 100%;
+ min-width: 360px;
+ font-family: 'Inter', system-ui, sans-serif;
+ background: var(--bg-canvas);
+ color: var(--text-primary);
+ font-size: 14px;
+ line-height: 1.5;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+a { color: inherit; text-decoration: none; }
+button { cursor: pointer; font-family: inherit; }
+
+::-webkit-scrollbar { width: 6px; height: 6px; }
+::-webkit-scrollbar-track { background: transparent; }
+::-webkit-scrollbar-thumb { background: var(--bg-muted); border-radius: 3px; }
+::-webkit-scrollbar-thumb:hover { background: #484f58; }
+::selection { background: var(--accent-glow); color: var(--text-primary); }
diff --git a/apps/ui/css/wizard.css b/apps/ui/css/wizard.css
new file mode 100644
index 0000000..0cfa730
--- /dev/null
+++ b/apps/ui/css/wizard.css
@@ -0,0 +1,1884 @@
+/* ══════════════════════════════════════════════════
+ WIZARD — shell
+══════════════════════════════════════════════════ */
+.wizard-shell {
+ min-height: calc(100vh - 60px);
+ display: flex;
+ flex-direction: column;
+}
+
+/* ══════════════════════════════════════════════════
+ TOP PROGRESS STRIP
+══════════════════════════════════════════════════ */
+.progress-strip {
+ background: var(--bg-surface);
+ border-bottom: 1px solid var(--border);
+ padding: 0 40px;
+ display: flex;
+ align-items: center;
+ height: 56px;
+ gap: 20px;
+ position: sticky;
+ top: 60px;
+ z-index: 30;
+ box-shadow: 0 1px 0 var(--border-subtle), 0 4px 16px rgba(0, 0, 0, 0.4);
+}
+
+.back-to-catalog-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 12px;
+ border-radius: 6px;
+ background: none;
+ border: 1px solid var(--border);
+ color: var(--text-secondary);
+ font-size: 13px;
+ font-weight: 500;
+ cursor: pointer;
+ font-family: 'Inter', sans-serif;
+ transition: all 0.15s;
+ white-space: nowrap;
+ text-decoration: none;
+}
+
+.back-to-catalog-btn:hover {
+ background: var(--bg-overlay);
+ color: var(--text-primary);
+ border-color: var(--bg-muted);
+}
+
+.progress-divider {
+ width: 1px;
+ height: 20px;
+ background: var(--border);
+ margin: 0 4px;
+}
+
+.progress-label {
+ font-size: 13px;
+ color: var(--text-secondary);
+ white-space: nowrap;
+}
+
+.progress-bar-wrap {
+ flex: 1;
+ height: 4px;
+ background: var(--bg-muted);
+ border-radius: 10px;
+ overflow: hidden;
+ min-width: 80px;
+}
+
+.progress-bar-fill {
+ height: 100%;
+ width: 20%;
+ background: linear-gradient(90deg, var(--accent), #7c3aed);
+ border-radius: 10px;
+ transition: width 0.4s ease;
+ position: relative;
+}
+
+.progress-bar-fill::after {
+ content: '';
+ position: absolute;
+ right: -1px;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: #7c3aed;
+ box-shadow: 0 0 0 2px var(--bg-surface), 0 0 8px rgba(124, 58, 237, 0.6);
+}
+
+.progress-pct {
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--accent);
+ white-space: nowrap;
+ min-width: 34px;
+ text-align: right;
+ font-variant-numeric: tabular-nums;
+}
+
+.progress-close {
+ margin-left: 4px;
+ width: 28px;
+ height: 28px;
+ border-radius: 6px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: none;
+ border: 1px solid var(--border);
+ color: var(--text-muted);
+ cursor: pointer;
+ transition: all 0.15s;
+}
+
+.progress-close:hover {
+ background: var(--bg-overlay);
+ color: var(--text-primary);
+ border-color: var(--bg-muted);
+}
+
+/* ══════════════════════════════════════════════════
+ WIZARD BODY
+══════════════════════════════════════════════════ */
+.wizard-body {
+ flex: 1;
+ display: flex;
+ max-width: 1100px;
+ width: 100%;
+ margin: 0 auto;
+ padding: 40px 40px 140px;
+ gap: 36px;
+ align-items: flex-start;
+}
+
+/* ══════════════════════════════════════════════════
+ STEP SIDEBAR
+══════════════════════════════════════════════════ */
+.step-sidebar {
+ width: 248px;
+ flex-shrink: 0;
+ position: sticky;
+ top: 136px;
+}
+
+.step-sidebar-card {
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ overflow: hidden;
+}
+
+.step-sidebar-header {
+ padding: 16px 18px 14px;
+ border-bottom: 1px solid var(--border-subtle);
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.step-sidebar-logo-mark {
+ width: 24px;
+ height: 24px;
+ border-radius: 6px;
+ background: linear-gradient(135deg, var(--accent), #7c3aed);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-size: 11px;
+ font-weight: 800;
+ flex-shrink: 0;
+}
+
+.step-sidebar-brand {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-primary);
+ letter-spacing: -0.2px;
+}
+
+.step-sidebar-brand span {
+ color: var(--text-muted);
+ font-weight: 400;
+}
+
+.step-sidebar-body {
+ padding: 18px 14px 14px;
+}
+
+.step-sidebar-section-label {
+ font-size: 10px;
+ font-weight: 600;
+ letter-spacing: 0.8px;
+ text-transform: uppercase;
+ color: var(--text-muted);
+ padding: 0 4px;
+ margin-bottom: 14px;
+}
+
+/* Step list */
+.steps-list {
+ display: flex;
+ flex-direction: column;
+ position: relative;
+}
+
+.steps-list::before {
+ content: '';
+ position: absolute;
+ left: 17px;
+ top: 32px;
+ bottom: 32px;
+ width: 1px;
+ background: var(--border);
+ z-index: 0;
+}
+
+.step-item {
+ display: flex;
+ align-items: flex-start;
+ gap: 12px;
+ padding: 9px 10px;
+ border-radius: 8px;
+ cursor: pointer;
+ position: relative;
+ z-index: 1;
+ transition: background 0.15s;
+ border: 1px solid transparent;
+}
+
+.step-item:hover { background: var(--bg-overlay); }
+
+.step-item.active {
+ background: var(--accent-glow);
+ border-color: rgba(13, 148, 136, 0.2);
+}
+
+.step-item.pending:hover { background: rgba(33, 38, 45, 0.6); }
+
+/* Step indicator circle */
+.step-indicator {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ font-size: 12px;
+ font-weight: 700;
+ border: 2px solid var(--step-pending);
+ background: var(--bg-overlay);
+ color: var(--text-muted);
+ transition: all 0.2s;
+ position: relative;
+ z-index: 2;
+}
+
+.step-item.done .step-indicator {
+ background: var(--step-done);
+ border-color: var(--step-done);
+ color: var(--bg-canvas);
+ box-shadow: 0 0 0 3px rgba(63, 185, 80, 0.15);
+}
+
+.step-item.active .step-indicator {
+ background: var(--bg-surface);
+ border-color: var(--step-active);
+ color: var(--accent);
+ box-shadow: 0 0 0 4px var(--accent-ring);
+ font-weight: 700;
+}
+
+.step-item.pending .step-indicator {
+ background: var(--bg-overlay);
+ border-color: var(--step-pending);
+ color: var(--text-muted);
+}
+
+/* Step text content */
+.step-content {
+ flex: 1;
+ min-width: 0;
+ padding-top: 3px;
+}
+
+.step-number {
+ font-size: 10.5px;
+ color: var(--text-muted);
+ font-weight: 500;
+ margin-bottom: 1px;
+ letter-spacing: 0.2px;
+}
+
+.step-name {
+ font-size: 13.5px;
+ font-weight: 500;
+ color: var(--text-secondary);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.step-item.done .step-name { color: var(--text-primary); }
+.step-item.active .step-name { color: var(--accent-bright); font-weight: 600; }
+.step-item.pending .step-name { color: var(--text-muted); }
+
+.step-status-text {
+ font-size: 11px;
+ margin-top: 2px;
+}
+
+.step-item.done .step-status-text { color: var(--step-done); }
+.step-item.active .step-status-text { color: var(--accent); }
+.step-item.pending .step-status-text { color: var(--text-muted); }
+
+/* Sidebar footer help card */
+.sidebar-help {
+ margin-top: 4px;
+ background: var(--bg-overlay);
+ border: 1px solid var(--border-subtle);
+ border-radius: 8px;
+ padding: 14px 16px;
+}
+
+.sidebar-help-title {
+ font-size: 12.5px;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin-bottom: 6px;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.sidebar-help-title svg { opacity: 0.7; flex-shrink: 0; }
+
+.sidebar-help-text {
+ font-size: 12px;
+ color: var(--text-secondary);
+ line-height: 1.6;
+}
+
+.sidebar-help-link {
+ color: var(--accent);
+ text-decoration: none;
+ font-weight: 500;
+}
+
+.sidebar-help-link:hover { text-decoration: underline; }
+
+/* ══════════════════════════════════════════════════
+ MAIN STEP PANEL
+══════════════════════════════════════════════════ */
+.step-panel {
+ flex: 1;
+ min-width: 0;
+}
+
+.step-panel-header {
+ margin-bottom: 28px;
+}
+
+.step-panel-eyebrow {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 11.5px;
+ font-weight: 600;
+ letter-spacing: 0.6px;
+ text-transform: uppercase;
+ color: var(--accent);
+ margin-bottom: 10px;
+}
+
+.step-panel-eyebrow-line {
+ width: 20px;
+ height: 2px;
+ background: var(--accent);
+ border-radius: 1px;
+ flex-shrink: 0;
+}
+
+.step-panel-title {
+ font-size: 26px;
+ font-weight: 700;
+ letter-spacing: -0.6px;
+ color: var(--text-primary);
+ margin-bottom: 8px;
+ line-height: 1.2;
+}
+
+.step-panel-subtitle {
+ font-size: 14px;
+ color: var(--text-secondary);
+ line-height: 1.65;
+ max-width: 580px;
+}
+
+/* ── Protocol cards ── */
+.protocol-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 12px;
+ margin-bottom: 24px;
+}
+
+.protocol-card {
+ background: var(--bg-surface);
+ border: 1.5px solid var(--border);
+ border-radius: 10px;
+ padding: 20px 18px 18px;
+ cursor: pointer;
+ transition: all 0.18s ease;
+ position: relative;
+ overflow: hidden;
+}
+
+.protocol-card:hover {
+ border-color: var(--accent);
+ background: rgba(13, 148, 136, 0.06);
+ transform: translateY(-1px);
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(13, 148, 136, 0.15);
+}
+
+.protocol-card.selected {
+ border-color: var(--accent);
+ background: var(--accent-glow);
+ box-shadow:
+ 0 0 0 1px var(--accent),
+ 0 4px 24px rgba(0, 0, 0, 0.5),
+ 0 0 20px rgba(13, 148, 136, 0.12);
+}
+
+.protocol-card-check {
+ position: absolute;
+ top: 12px;
+ right: 12px;
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ background: var(--accent);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ transform: scale(0.6);
+ transition: all 0.18s ease;
+ flex-shrink: 0;
+}
+
+.protocol-card.selected .protocol-card-check {
+ opacity: 1;
+ transform: scale(1);
+}
+
+.protocol-card-check svg {
+ width: 10px;
+ height: 10px;
+ stroke: #fff;
+ stroke-width: 2.5;
+ fill: none;
+}
+
+.protocol-icon-wrap {
+ width: 44px;
+ height: 44px;
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 14px;
+ flex-shrink: 0;
+ transition: all 0.18s;
+}
+
+.protocol-card.rest .protocol-icon-wrap {
+ background: rgba(88, 166, 255, 0.12);
+ border: 1px solid rgba(88, 166, 255, 0.2);
+}
+
+.protocol-card.graphql .protocol-icon-wrap {
+ background: rgba(188, 140, 255, 0.12);
+ border: 1px solid rgba(188, 140, 255, 0.2);
+}
+
+.protocol-card.grpc .protocol-icon-wrap {
+ background: rgba(13, 148, 136, 0.12);
+ border: 1px solid rgba(13, 148, 136, 0.2);
+}
+
+.protocol-card.selected.rest .protocol-icon-wrap {
+ background: rgba(88, 166, 255, 0.18);
+ border-color: rgba(88, 166, 255, 0.35);
+}
+
+.protocol-card.selected.graphql .protocol-icon-wrap {
+ background: rgba(188, 140, 255, 0.18);
+ border-color: rgba(188, 140, 255, 0.35);
+}
+
+.protocol-card.selected.grpc .protocol-icon-wrap {
+ background: rgba(13, 148, 136, 0.2);
+ border-color: rgba(13, 148, 136, 0.4);
+}
+
+.protocol-card-name {
+ font-size: 15px;
+ font-weight: 700;
+ color: var(--text-primary);
+ margin-bottom: 4px;
+ letter-spacing: -0.2px;
+}
+
+.protocol-card.selected .protocol-card-name { color: #fff; }
+
+.protocol-card-tagline {
+ font-size: 12px;
+ color: var(--text-secondary);
+ margin-bottom: 14px;
+ line-height: 1.5;
+}
+
+.protocol-card.selected .protocol-card-tagline { color: var(--accent-bright); }
+
+.protocol-card-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 5px;
+}
+
+.protocol-tag {
+ font-size: 10.5px;
+ font-weight: 500;
+ padding: 2px 8px;
+ border-radius: 4px;
+ border: 1px solid var(--border);
+ background: var(--bg-overlay);
+ color: var(--text-muted);
+ letter-spacing: 0.1px;
+ transition: all 0.15s;
+}
+
+.protocol-card.selected .protocol-tag {
+ background: rgba(13, 148, 136, 0.12);
+ border-color: rgba(13, 148, 136, 0.3);
+ color: var(--accent-bright);
+}
+
+.protocol-card.rest.selected .protocol-tag {
+ background: rgba(88, 166, 255, 0.1);
+ border-color: rgba(88, 166, 255, 0.25);
+ color: #58a6ff;
+}
+
+.protocol-card.graphql.selected .protocol-tag {
+ background: rgba(188, 140, 255, 0.1);
+ border-color: rgba(188, 140, 255, 0.25);
+ color: #bc8cff;
+}
+
+/* ── REST method picker ── */
+.method-section {
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: 10px;
+ overflow: hidden;
+ margin-bottom: 20px;
+}
+
+.method-section-header {
+ padding: 14px 20px;
+ border-bottom: 1px solid var(--border-subtle);
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.method-section-title {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+.method-section-subtitle {
+ font-size: 12px;
+ color: var(--text-muted);
+ margin-left: auto;
+}
+
+.method-section-body {
+ padding: 18px 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.method-grid {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 8px;
+}
+
+.method-btn {
+ padding: 10px 8px;
+ border-radius: 7px;
+ border: 1.5px solid var(--border);
+ background: var(--bg-overlay);
+ cursor: pointer;
+ transition: all 0.15s;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 5px;
+ font-family: 'Inter', sans-serif;
+}
+
+.method-btn:hover { border-color: var(--bg-muted); background: rgba(33, 38, 45, 0.8); }
+
+.method-btn.selected-get { border-color: #3fb950; background: rgba(63, 185, 80, 0.08); }
+.method-btn.selected-post { border-color: #58a6ff; background: rgba(88, 166, 255, 0.08); }
+.method-btn.selected-put { border-color: #d29922; background: rgba(210, 153, 34, 0.08); }
+.method-btn.selected-patch { border-color: #bc8cff; background: rgba(188, 140, 255, 0.08); }
+.method-btn.selected-delete { border-color: #f85149; background: rgba(248, 81, 73, 0.08); }
+
+.method-verb {
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.5px;
+ font-variant-numeric: tabular-nums;
+ color: var(--text-muted);
+}
+
+.method-btn.selected-get .method-verb { color: #3fb950; }
+.method-btn.selected-post .method-verb { color: #58a6ff; }
+.method-btn.selected-put .method-verb { color: #d29922; }
+.method-btn.selected-patch .method-verb { color: #bc8cff; }
+.method-btn.selected-delete .method-verb { color: #f85149; }
+
+.method-label { font-size: 10px; color: var(--text-muted); }
+
+/* ── Info callout ── */
+.info-callout {
+ display: flex;
+ gap: 12px;
+ padding: 14px 16px;
+ background: rgba(13, 148, 136, 0.07);
+ border: 1px solid rgba(13, 148, 136, 0.2);
+ border-radius: 8px;
+}
+
+.info-callout-icon {
+ width: 18px;
+ height: 18px;
+ flex-shrink: 0;
+ margin-top: 1px;
+ color: var(--accent);
+}
+
+.info-callout-body { flex: 1; }
+
+.info-callout-title {
+ font-size: 12.5px;
+ font-weight: 600;
+ color: var(--accent-bright);
+ margin-bottom: 4px;
+}
+
+.info-callout-text {
+ font-size: 12px;
+ color: var(--text-secondary);
+ line-height: 1.65;
+}
+
+.info-callout-text a {
+ color: var(--accent);
+ text-decoration: none;
+ font-weight: 500;
+}
+
+.info-callout-text a:hover { text-decoration: underline; }
+
+/* ── Config card ── */
+.config-card {
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: 10px;
+ overflow: hidden;
+ margin-bottom: 20px;
+}
+
+.config-card-header {
+ padding: 14px 20px;
+ border-bottom: 1px solid var(--border-subtle);
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.config-card-header-icon {
+ width: 28px;
+ height: 28px;
+ border-radius: 7px;
+ background: var(--bg-overlay);
+ border: 1px solid var(--border);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 13px;
+ flex-shrink: 0;
+}
+
+.config-card-title {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+.config-card-subtitle {
+ font-size: 12px;
+ color: var(--text-muted);
+ margin-top: 1px;
+}
+
+.config-card-optional {
+ margin-left: auto;
+ font-size: 10.5px;
+ font-weight: 500;
+ color: var(--text-muted);
+ background: var(--bg-overlay);
+ border: 1px solid var(--border-subtle);
+ padding: 2px 8px;
+ border-radius: 10px;
+}
+
+.config-card-body {
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+/* Form fields */
+.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
+.form-group { display: flex; flex-direction: column; gap: 6px; }
+
+.form-label {
+ font-size: 12.5px;
+ font-weight: 500;
+ color: var(--text-secondary);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.form-label-required {
+ font-size: 10px;
+ font-weight: 600;
+ padding: 1px 5px;
+ border-radius: 4px;
+ background: rgba(248, 81, 73, 0.12);
+ color: var(--red);
+ border: 1px solid rgba(248, 81, 73, 0.2);
+ letter-spacing: 0.2px;
+}
+
+.form-label-optional {
+ font-size: 11px;
+ color: var(--text-muted);
+ font-weight: 400;
+}
+
+.form-hint {
+ font-size: 11.5px;
+ color: var(--text-muted);
+ line-height: 1.55;
+}
+
+.form-input,
+.form-textarea,
+.form-select {
+ background: var(--bg-canvas);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 9px 12px;
+ font-family: 'Inter', sans-serif;
+ font-size: 13.5px;
+ color: var(--text-primary);
+ width: 100%;
+ outline: none;
+ transition: border-color 0.15s, box-shadow 0.15s;
+ appearance: none;
+ -webkit-appearance: none;
+}
+
+.form-input::placeholder,
+.form-textarea::placeholder { color: var(--text-muted); }
+
+.form-input:focus,
+.form-textarea:focus,
+.form-select:focus {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px var(--accent-ring);
+}
+
+.form-select {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%236e7681' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 11px center;
+ padding-right: 32px;
+ cursor: pointer;
+}
+
+.form-textarea {
+ resize: vertical;
+ min-height: 90px;
+ line-height: 1.6;
+}
+
+/* Toggle switch */
+.toggle-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 14px;
+ border-radius: 8px;
+ background: var(--bg-overlay);
+ border: 1px solid var(--border-subtle);
+ cursor: pointer;
+ transition: background 0.15s;
+}
+
+.toggle-row:hover { background: rgba(33, 38, 45, 0.8); }
+
+.toggle {
+ width: 36px;
+ height: 20px;
+ border-radius: 10px;
+ background: var(--bg-muted);
+ border: 1px solid var(--border);
+ position: relative;
+ flex-shrink: 0;
+ transition: background 0.2s;
+ cursor: pointer;
+}
+
+.toggle.on {
+ background: var(--accent);
+ border-color: var(--accent-dim);
+}
+
+.toggle::after {
+ content: '';
+ position: absolute;
+ left: 2px;
+ top: 1px;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background: #fff;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.3);
+ transition: transform 0.2s ease;
+}
+
+.toggle.on::after { transform: translateX(16px); }
+
+.toggle-text { flex: 1; }
+.toggle-label { font-size: 13px; font-weight: 500; color: var(--text-primary); }
+.toggle-desc { font-size: 11.5px; color: var(--text-muted); margin-top: 1px; }
+
+/* ══════════════════════════════════════════════════
+ BOTTOM ACTION BAR — frosted dark glass
+══════════════════════════════════════════════════ */
+.action-bar {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: rgba(13, 17, 23, 0.82);
+ backdrop-filter: blur(16px) saturate(140%);
+ -webkit-backdrop-filter: blur(16px) saturate(140%);
+ border-top: 1px solid rgba(48, 54, 61, 0.7);
+ z-index: 50;
+ box-shadow:
+ 0 -1px 0 rgba(255, 255, 255, 0.03),
+ 0 -8px 32px rgba(0, 0, 0, 0.6);
+}
+
+.action-bar-inner {
+ max-width: 1100px;
+ margin: 0 auto;
+ padding: 0 40px;
+ height: 70px;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+/* Buttons */
+.btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 9px 18px;
+ border-radius: 7px;
+ font-family: 'Inter', sans-serif;
+ font-size: 13.5px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.15s;
+ border: none;
+ white-space: nowrap;
+}
+
+.btn-back {
+ background: var(--bg-overlay);
+ color: var(--text-secondary);
+ border: 1px solid var(--border);
+}
+
+.btn-back:hover {
+ background: var(--bg-muted);
+ color: var(--text-primary);
+ border-color: var(--bg-muted);
+}
+
+.btn-save-draft {
+ background: none;
+ color: var(--text-muted);
+ border: none;
+ font-size: 13px;
+ font-weight: 400;
+ cursor: pointer;
+ font-family: 'Inter', sans-serif;
+ padding: 9px 12px;
+ border-radius: 6px;
+ transition: all 0.15s;
+}
+
+.btn-save-draft:hover {
+ color: var(--text-secondary);
+ background: rgba(33, 38, 45, 0.6);
+}
+
+.action-bar-spacer { flex: 1; }
+
+.step-counter {
+ font-size: 12.5px;
+ color: var(--text-muted);
+ font-variant-numeric: tabular-nums;
+}
+
+.step-counter strong {
+ color: var(--text-secondary);
+ font-weight: 600;
+}
+
+.btn-continue {
+ background: var(--accent);
+ color: #fff;
+ padding: 10px 24px;
+ font-size: 14px;
+ border: 1px solid var(--accent-dim);
+ box-shadow: 0 1px 4px rgba(13, 148, 136, 0.4);
+ transition: all 0.15s;
+}
+
+.btn-continue:hover {
+ background: var(--accent-dim);
+ border-color: transparent;
+ box-shadow:
+ 0 2px 10px rgba(13, 148, 136, 0.5),
+ 0 0 0 3px var(--accent-ring);
+ transform: translateY(-1px);
+}
+
+.btn-continue:active {
+ transform: translateY(0);
+ box-shadow: 0 1px 4px rgba(13, 148, 136, 0.3);
+}
+
+/* ── Code editor ── */
+.code-block {
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.code-toolbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8px 14px;
+ background: #1e2330;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.06);
+}
+
+.code-dots { display: flex; gap: 5px; }
+.code-dots span { width: 10px; height: 10px; border-radius: 50%; }
+.code-dots span:nth-child(1) { background: #ff5f56; }
+.code-dots span:nth-child(2) { background: #ffbd2e; }
+.code-dots span:nth-child(3) { background: #27c93f; }
+
+.code-toolbar-label {
+ font-size: 10.5px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: #8b949e;
+}
+
+.code-textarea {
+ background: #161b22 !important;
+ color: #c9d1d9 !important;
+ font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace !important;
+ font-size: 12.5px !important;
+ line-height: 1.65 !important;
+ border: none !important;
+ border-radius: 0 !important;
+ resize: vertical;
+ min-height: 160px;
+}
+
+.code-textarea:focus {
+ border-color: transparent !important;
+ box-shadow: none !important;
+}
+
+/* ── Section divider ── */
+.section-divider {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin: 8px 0;
+}
+
+.section-divider-label {
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.07em;
+ text-transform: uppercase;
+ color: var(--text-muted);
+ white-space: nowrap;
+}
+
+.section-divider-line {
+ flex: 1;
+ height: 1px;
+ background: var(--border);
+}
+
+/* ── Step pane ── */
+.step-pane { display: flex; flex-direction: column; }
+
+/* ── Monospace input variant ── */
+.input-mono {
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
+ font-size: 13px;
+ letter-spacing: -0.2px;
+}
+
+/* ── Step-state colours (wizard-specific) ── */
+:root {
+ --step-done: var(--green);
+ --step-active: var(--accent);
+ --step-pending: var(--bg-muted);
+}
+
+/* ══════════════════════════════════════════════
+ Upstream selector (Step 3)
+ ══════════════════════════════════════════════ */
+
+/* ── Combobox trigger ── */
+.upstream-combobox {
+ position: relative;
+}
+
+.upstream-combobox-trigger {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 9px 12px;
+ background: var(--bg-canvas);
+ border: 1px solid var(--border);
+ border-radius: 7px;
+ cursor: pointer;
+ transition: border-color 0.15s, box-shadow 0.15s;
+ user-select: none;
+}
+
+.upstream-combobox-trigger:hover { border-color: var(--accent); }
+
+.upstream-combobox.open .upstream-combobox-trigger {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px rgba(13, 148, 136, 0.15);
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+.upstream-combobox-value {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.upstream-combobox-placeholder {
+ font-size: 13.5px;
+ color: var(--text-muted);
+}
+
+.upstream-combobox-chevron {
+ flex-shrink: 0;
+ transition: transform 0.2s;
+}
+.upstream-combobox.open .upstream-combobox-chevron { transform: rotate(180deg); }
+
+/* Value when upstream is selected (shown inside trigger) */
+.upstream-trigger-name {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-primary);
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
+ letter-spacing: -0.2px;
+}
+
+.upstream-trigger-url {
+ font-size: 11.5px;
+ color: var(--text-muted);
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
+ letter-spacing: -0.2px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+/* ── Dropdown panel ── */
+.upstream-dropdown {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ z-index: 200;
+ background: var(--bg-surface);
+ border: 1px solid var(--accent);
+ border-top: none;
+ border-bottom-left-radius: 7px;
+ border-bottom-right-radius: 7px;
+ box-shadow: 0 8px 24px rgba(0,0,0,0.35);
+ overflow: hidden;
+}
+
+/* Search row */
+.upstream-search-wrap {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 9px 12px;
+ border-bottom: 1px solid var(--border-subtle);
+}
+
+.upstream-search-input {
+ flex: 1;
+ background: transparent;
+ border: none;
+ outline: none;
+ font-family: 'Inter', sans-serif;
+ font-size: 13px;
+ color: var(--text-primary);
+}
+.upstream-search-input::placeholder { color: var(--text-muted); }
+
+/* List of upstream items */
+.upstream-dropdown-list {
+ max-height: 220px;
+ overflow-y: auto;
+ scrollbar-width: thin;
+ scrollbar-color: var(--border) transparent;
+}
+
+.upstream-dropdown-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 9px 14px;
+ cursor: pointer;
+ transition: background 0.12s;
+}
+.upstream-dropdown-item:hover { background: rgba(13, 148, 136, 0.07); }
+.upstream-dropdown-item.selected { background: rgba(13, 148, 136, 0.1); }
+
+.upstream-dropdown-item-info { flex: 1; min-width: 0; }
+
+.upstream-dropdown-item-name {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-primary);
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
+ letter-spacing: -0.2px;
+}
+
+.upstream-dropdown-item-url {
+ font-size: 11px;
+ color: var(--text-muted);
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
+ letter-spacing: -0.2px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.upstream-dropdown-item-check {
+ opacity: 0;
+ flex-shrink: 0;
+}
+.upstream-dropdown-item.selected .upstream-dropdown-item-check { opacity: 1; }
+
+/* Empty state */
+.upstream-dropdown-empty {
+ padding: 16px 14px;
+ font-size: 12.5px;
+ color: var(--text-muted);
+ text-align: center;
+}
+
+/* Footer: Register new */
+/* ── Register new upstream trigger row ── */
+.upstream-new-trigger {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 14px;
+ border: 1.5px dashed var(--border);
+ border-radius: 8px;
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--accent);
+ transition: border-color 0.15s, background 0.15s;
+ user-select: none;
+}
+.upstream-new-trigger:hover {
+ border-color: var(--accent);
+ background: rgba(13, 148, 136, 0.04);
+}
+.upstream-new-trigger.active {
+ border-style: solid;
+ border-color: var(--accent);
+ background: rgba(13, 148, 136, 0.06);
+}
+
+.upstream-new-trigger-radio {
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ border: 1.5px solid var(--border);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ transition: border-color 0.15s;
+}
+.upstream-new-trigger.active .upstream-new-trigger-radio {
+ border-color: var(--accent);
+}
+.upstream-new-trigger-dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: var(--accent);
+ opacity: 0;
+ transform: scale(0.5);
+ transition: opacity 0.15s, transform 0.15s;
+}
+.upstream-new-trigger.active .upstream-new-trigger-dot {
+ opacity: 1;
+ transform: scale(1);
+}
+
+/* ── Auth badges ── */
+.upstream-auth-badge {
+ font-size: 10.5px;
+ font-weight: 600;
+ letter-spacing: 0.2px;
+ padding: 2px 8px;
+ border-radius: 4px;
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+.upstream-auth-badge.auth-bearer {
+ background: rgba(88, 166, 255, 0.1);
+ color: #58a6ff;
+ border: 1px solid rgba(88, 166, 255, 0.2);
+}
+.upstream-auth-badge.auth-apikey {
+ background: rgba(188, 140, 255, 0.1);
+ color: #bc8cff;
+ border: 1px solid rgba(188, 140, 255, 0.2);
+}
+.upstream-auth-badge.auth-none {
+ background: rgba(139, 148, 158, 0.08);
+ color: var(--text-muted);
+ border: 1px solid var(--border-subtle);
+}
+
+/* ── Selected upstream preview (under combobox) ── */
+.upstream-preview {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 14px;
+ background: var(--accent-glow);
+ border: 1px solid rgba(13, 148, 136, 0.3);
+ border-radius: 7px;
+}
+
+.upstream-preview-name {
+ font-size: 13px;
+ font-weight: 600;
+ color: #fff;
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
+ letter-spacing: -0.2px;
+}
+
+.upstream-preview-url {
+ font-size: 11.5px;
+ color: var(--accent-bright, var(--accent));
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
+ letter-spacing: -0.2px;
+ flex: 1;
+ min-width: 0;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.upstream-preview-change {
+ font-size: 11.5px;
+ font-weight: 500;
+ color: var(--text-muted);
+ background: none;
+ border: 1px solid var(--border);
+ border-radius: 5px;
+ padding: 3px 10px;
+ cursor: pointer;
+ transition: border-color 0.15s, color 0.15s;
+ flex-shrink: 0;
+}
+.upstream-preview-change:hover { border-color: var(--text-secondary); color: var(--text-primary); }
+
+/* ── Register new upstream inline form ── */
+.upstream-new-form {
+ background: var(--bg-canvas);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.upstream-new-form-inner {
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+/* ── Small action buttons ── */
+.btn-primary-sm {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 7px 16px;
+ font-family: 'Inter', sans-serif;
+ font-size: 12.5px;
+ font-weight: 600;
+ border-radius: 6px;
+ border: none;
+ cursor: pointer;
+ background: var(--accent);
+ color: #fff;
+ transition: opacity 0.15s;
+}
+.btn-primary-sm:hover { opacity: 0.88; }
+
+.btn-ghost-sm {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 7px 16px;
+ font-family: 'Inter', sans-serif;
+ font-size: 12.5px;
+ font-weight: 500;
+ border-radius: 6px;
+ border: 1px solid var(--border);
+ cursor: pointer;
+ background: transparent;
+ color: var(--text-secondary);
+ transition: border-color 0.15s, color 0.15s;
+}
+.btn-ghost-sm:hover { border-color: var(--text-secondary); color: var(--text-primary); }
+
+/* ══════════════════════════════════════════════
+ HTTP method picker (Step 4 REST)
+ ══════════════════════════════════════════════ */
+
+.method-grid {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 8px;
+}
+
+.method-card {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ padding: 14px 8px;
+ background: var(--bg-canvas);
+ border: 1.5px solid var(--border);
+ border-radius: 8px;
+ cursor: pointer;
+ transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
+ font-family: 'Inter', sans-serif;
+}
+
+.method-card:hover {
+ border-color: var(--accent);
+ background: rgba(13, 148, 136, 0.04);
+}
+
+.method-card.active {
+ border-color: var(--accent);
+ background: var(--accent-glow);
+ box-shadow: 0 0 0 3px rgba(13, 148, 136, 0.15);
+}
+
+.method-name {
+ font-size: 14px;
+ font-weight: 700;
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
+ color: var(--text-primary);
+ letter-spacing: 0.5px;
+}
+
+.method-card.active .method-name { color: #fff; }
+
+.method-desc {
+ font-size: 11px;
+ color: var(--text-muted);
+ font-weight: 400;
+}
+
+.method-card.active .method-desc { color: var(--accent-bright, var(--accent)); }
+
+.method-callout {
+ display: flex;
+ align-items: flex-start;
+ gap: 10px;
+ padding: 11px 14px;
+ background: rgba(13, 148, 136, 0.07);
+ border: 1px solid rgba(13, 148, 136, 0.25);
+ border-radius: 7px;
+ font-size: 12.5px;
+ color: var(--text-secondary);
+ line-height: 1.55;
+ margin-top: 4px;
+}
+
+/* ══════════════════════════════════════════════
+ GraphQL operation type cards (Step 4 GraphQL)
+ ══════════════════════════════════════════════ */
+
+.gql-type-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 10px;
+}
+
+.gql-type-card {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 6px;
+ padding: 16px 18px;
+ background: var(--bg-canvas);
+ border: 1.5px solid var(--border);
+ border-radius: 8px;
+ cursor: pointer;
+ transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
+ text-align: left;
+ font-family: 'Inter', sans-serif;
+}
+
+.gql-type-card:hover {
+ border-color: var(--accent);
+ background: rgba(13, 148, 136, 0.04);
+}
+
+.gql-type-card.active {
+ border-color: var(--accent);
+ background: var(--accent-glow);
+ box-shadow: 0 0 0 3px rgba(13, 148, 136, 0.15);
+}
+
+.gql-type-keyword {
+ font-size: 15px;
+ font-weight: 700;
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
+ color: var(--accent);
+ letter-spacing: -0.3px;
+}
+
+.gql-type-card.active .gql-type-keyword { color: #5eead4; }
+
+.gql-type-desc {
+ font-size: 12px;
+ color: var(--text-muted);
+ line-height: 1.4;
+}
+
+.gql-type-card.active .gql-type-desc { color: var(--text-secondary); }
+
+
+/* ══════════════════════════════════════════════
+ Protocol card — disabled tag (Streaming)
+══════════════════════════════════════════════ */
+.protocol-tag-disabled {
+ text-decoration: line-through;
+ color: var(--error, #f87171);
+ opacity: 0.75;
+}
+
+
+/* ══════════════════════════════════════════════
+ gRPC — Proto upload
+══════════════════════════════════════════════ */
+.proto-dropzone {
+ border: 1.5px dashed var(--border);
+ border-radius: 10px;
+ padding: 36px 24px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+ cursor: pointer;
+ transition: border-color 0.15s, background 0.15s;
+ background: var(--bg-canvas);
+}
+.proto-dropzone:hover,
+.proto-dropzone.drag-over {
+ border-color: var(--accent);
+ background: rgba(56,139,253,0.05);
+}
+.proto-dropzone.drag-over { border-style: solid; }
+
+.proto-dropzone-title {
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--text-primary);
+}
+.proto-dropzone-sub {
+ font-size: 12px;
+ color: var(--text-muted);
+}
+
+.proto-file-info {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 14px;
+ background: rgba(56,139,253,0.07);
+ border: 1px solid rgba(56,139,253,0.25);
+ border-radius: 8px;
+}
+.proto-file-name {
+ font-family: var(--font-mono, monospace);
+ font-size: 13px;
+ color: var(--accent);
+ flex: 1;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.proto-file-size {
+ font-size: 11px;
+ color: var(--text-muted);
+ white-space: nowrap;
+}
+.proto-file-change {
+ background: none;
+ border: 1px solid var(--border);
+ border-radius: 5px;
+ color: var(--text-secondary);
+ font-size: 11px;
+ padding: 3px 9px;
+ cursor: pointer;
+ white-space: nowrap;
+ transition: border-color 0.15s, color 0.15s;
+}
+.proto-file-change:hover { border-color: var(--accent); color: var(--accent); }
+
+
+/* ══════════════════════════════════════════════
+ gRPC — Service/method list
+══════════════════════════════════════════════ */
+.proto-service-group {
+ padding-top: 16px;
+}
+.proto-service-group + .proto-service-group {
+ border-top: 1px solid var(--border-subtle);
+ margin-top: 4px;
+}
+
+.proto-service-label {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--text-muted);
+ margin-bottom: 10px;
+}
+.proto-service-label svg { opacity: 0.5; }
+
+.proto-method-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.proto-method-card {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ padding: 10px 14px;
+ background: var(--bg-canvas);
+ border: 1.5px solid var(--border);
+ border-radius: 8px;
+ cursor: pointer;
+ text-align: left;
+ transition: border-color 0.15s, background 0.15s;
+ min-width: 160px;
+}
+.proto-method-card:hover {
+ border-color: var(--border-muted, var(--accent));
+ background: rgba(56,139,253,0.04);
+}
+.proto-method-card.active {
+ border-color: var(--accent);
+ background: rgba(56,139,253,0.08);
+}
+
+.proto-method-name {
+ font-family: var(--font-mono, monospace);
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-primary);
+}
+.proto-method-card.active .proto-method-name { color: var(--accent); }
+
+.proto-method-types {
+ font-size: 11px;
+ color: var(--text-muted);
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+.proto-method-types svg { opacity: 0.5; flex-shrink: 0; }
+
+.proto-empty {
+ padding: 24px;
+ text-align: center;
+ color: var(--text-muted);
+ font-size: 13px;
+}
+
+
+/* ══════════════════════════════════════════════
+ gRPC — Method signature detail
+══════════════════════════════════════════════ */
+.proto-path-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 8px 12px;
+ background: var(--bg-canvas);
+ border: 1px solid var(--border-subtle);
+ border-radius: 6px;
+}
+.proto-path-label {
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ color: var(--text-muted);
+ white-space: nowrap;
+}
+.proto-path-value {
+ font-family: var(--font-mono, monospace);
+ font-size: 12px;
+ color: var(--accent);
+ word-break: break-all;
+}
+
+.proto-sig-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 16px;
+}
+@media (max-width: 680px) {
+ .proto-sig-grid { grid-template-columns: 1fr; }
+}
+
+.proto-sig-col {
+ display: flex;
+ flex-direction: column;
+ gap: 0;
+ border: 1px solid var(--border-subtle);
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.proto-sig-col-header {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 12px;
+ background: var(--bg-surface);
+ border-bottom: 1px solid var(--border-subtle);
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ color: var(--text-secondary);
+}
+.proto-sig-col-header svg { opacity: 0.6; }
+
+.proto-type-badge {
+ font-family: var(--font-mono, monospace);
+ font-size: 11px;
+ background: rgba(56,139,253,0.12);
+ color: var(--accent);
+ padding: 1px 6px;
+ border-radius: 4px;
+ margin-left: auto;
+}
+
+.proto-field-list {
+ padding: 6px 0;
+}
+
+.proto-field-row {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ padding: 5px 12px;
+ font-size: 12px;
+ border-bottom: 1px solid var(--border-subtle);
+}
+.proto-field-row:last-child { border-bottom: none; }
+
+.proto-field-name {
+ font-family: var(--font-mono, monospace);
+ color: var(--text-primary);
+ font-size: 12px;
+}
+.proto-field-type {
+ font-family: var(--font-mono, monospace);
+ font-size: 11px;
+ color: var(--text-muted);
+}
+
+.proto-field-empty {
+ padding: 16px 12px;
+ font-size: 12px;
+ color: var(--text-secondary);
+ text-align: center;
+ line-height: 1.5;
+}
+
+
+/* ══════════════════════════════════════════════
+ gRPC — Source tabs
+══════════════════════════════════════════════ */
+.grpc-source-tabs {
+ display: flex;
+ gap: 0;
+ margin-bottom: 20px;
+ background: var(--bg-canvas);
+ border: 1px solid var(--border);
+ border-radius: 9px;
+ padding: 3px;
+}
+
+.grpc-source-btn {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 7px;
+ padding: 8px 14px;
+ background: none;
+ border: none;
+ border-radius: 7px;
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--text-muted);
+ cursor: pointer;
+ transition: background 0.15s, color 0.15s;
+ white-space: nowrap;
+}
+.grpc-source-btn svg { flex-shrink: 0; opacity: 0.7; }
+.grpc-source-btn:hover { color: var(--text-secondary); background: rgba(255,255,255,0.04); }
+.grpc-source-btn.active {
+ background: var(--bg-surface);
+ color: var(--text-primary);
+ box-shadow: 0 1px 3px rgba(0,0,0,0.3);
+}
+.grpc-source-btn.active svg { opacity: 1; }
+
+
+/* ══════════════════════════════════════════════
+ gRPC — Reflection panel
+══════════════════════════════════════════════ */
+.grpc-reflect-upstream {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 14px;
+ background: var(--bg-canvas);
+ border: 1px solid var(--border-subtle);
+ border-radius: 8px;
+}
+.grpc-reflect-upstream-info {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ min-width: 0;
+}
+.grpc-reflect-upstream-name {
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--text-primary);
+}
+.grpc-reflect-upstream-url {
+ font-family: var(--font-mono, monospace);
+ font-size: 11px;
+ color: var(--text-muted);
+}
+
+.grpc-reflect-status {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-size: 13px;
+ color: var(--text-secondary);
+}
+.grpc-reflect-spinner {
+ width: 14px;
+ height: 14px;
+ border: 2px solid var(--border);
+ border-top-color: var(--accent);
+ border-radius: 50%;
+ animation: grpc-spin 0.7s linear infinite;
+ flex-shrink: 0;
+}
+@keyframes grpc-spin { to { transform: rotate(360deg); } }
+
+.grpc-reflect-error-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 13px;
+ color: var(--error, #f87171);
+}
diff --git a/apps/ui/css/workspace-setup.css b/apps/ui/css/workspace-setup.css
new file mode 100644
index 0000000..3638106
--- /dev/null
+++ b/apps/ui/css/workspace-setup.css
@@ -0,0 +1,223 @@
+.ws-setup-page {
+ min-height: 100vh;
+ background: var(--bg-canvas);
+ display: flex;
+ flex-direction: column;
+}
+.ws-setup-topbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 32px;
+ border-bottom: 1px solid var(--border-subtle);
+}
+.ws-setup-logo {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ text-decoration: none;
+ color: var(--text-primary);
+ font-size: 15px;
+ font-weight: 600;
+}
+.ws-setup-logo-mark {
+ width: 30px;
+ height: 30px;
+ background: #0d9488;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.ws-setup-back {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 13px;
+ color: var(--text-muted);
+ text-decoration: none;
+ padding: 6px 10px;
+ border-radius: 6px;
+ transition: color 0.15s, background 0.15s;
+}
+.ws-setup-back:hover { color: var(--text-secondary); background: rgba(255,255,255,0.04); }
+
+.ws-setup-body {
+ flex: 1;
+ display: flex;
+ justify-content: center;
+ padding: 48px 24px 80px;
+}
+.ws-setup-container {
+ width: 100%;
+ max-width: 600px;
+}
+.ws-setup-header { margin-bottom: 32px; }
+.ws-setup-title {
+ font-size: 24px;
+ font-weight: 700;
+ color: var(--text-primary);
+ margin-bottom: 6px;
+}
+.ws-setup-subtitle {
+ font-size: 14px;
+ color: var(--text-muted);
+ line-height: 1.5;
+}
+
+.ws-form-section {
+ background: var(--bg-surface);
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ padding: 24px;
+ margin-bottom: 16px;
+}
+.ws-form-section-title {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin-bottom: 18px;
+}
+
+/* Avatar */
+.ws-avatar-row {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ margin-bottom: 20px;
+}
+.ws-avatar-preview {
+ width: 52px;
+ height: 52px;
+ border-radius: 12px;
+ background: #0d9488;
+ color: #fff;
+ font-size: 22px;
+ font-weight: 700;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ transition: background 0.2s;
+}
+.ws-avatar-hint { font-size: 12px; color: var(--text-muted); line-height: 1.5; }
+.ws-color-swatches { display: flex; gap: 6px; margin-top: 6px; }
+.ws-color-swatch {
+ width: 20px;
+ height: 20px;
+ border-radius: 5px;
+ cursor: pointer;
+ border: 2px solid transparent;
+ transition: transform 0.1s, border-color 0.1s;
+}
+.ws-color-swatch:hover { transform: scale(1.15); }
+.ws-color-swatch.active { border-color: var(--text-primary); }
+
+/* Invite rows */
+.invite-rows { display: flex; flex-direction: column; gap: 8px; }
+.invite-row { display: flex; align-items: center; gap: 8px; }
+.invite-row .form-input { margin-bottom: 0; }
+.invite-row-remove {
+ background: none;
+ border: none;
+ padding: 6px;
+ cursor: pointer;
+ color: var(--text-muted);
+ border-radius: 5px;
+ display: flex;
+ align-items: center;
+ transition: color 0.1s, background 0.1s;
+ flex-shrink: 0;
+}
+.invite-row-remove:hover { color: var(--error, #f87171); background: rgba(248,113,113,0.08); }
+
+/* Members */
+.member-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 10px 0;
+ border-bottom: 1px solid var(--border-subtle);
+}
+.member-row:last-child { border-bottom: none; }
+.member-avatar {
+ width: 30px;
+ height: 30px;
+ border-radius: 50%;
+ background: var(--bg-muted);
+ border: 1px solid var(--border);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 11px;
+ font-weight: 700;
+ color: var(--text-secondary);
+ flex-shrink: 0;
+}
+.member-info { flex: 1; }
+.member-name { font-size: 13.5px; color: var(--text-primary); font-weight: 500; }
+.member-email { font-size: 12px; color: var(--text-muted); }
+.member-last-active { font-size: 12px; color: var(--text-muted); white-space: nowrap; }
+
+/* Danger zone */
+.danger-zone-action {
+ display: flex;
+ align-items: flex-start;
+ gap: 16px;
+ padding: 16px 24px;
+ border-bottom: 1px solid var(--border-subtle);
+}
+.danger-zone-action:last-child { border-bottom: none; }
+.danger-zone-text { flex: 1; }
+.danger-zone-title { font-size: 13.5px; font-weight: 500; color: var(--text-primary); margin-bottom: 3px; }
+.danger-zone-desc { font-size: 12.5px; color: var(--text-muted); line-height: 1.5; }
+
+/* Lang switcher */
+.lang-switcher { display: flex; gap: 6px; margin-top: 6px; }
+.lang-btn {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 14px;
+ border-radius: 6px;
+ border: 1px solid var(--border);
+ background: var(--bg-canvas);
+ color: var(--text-secondary);
+ font-size: 13px;
+ cursor: pointer;
+ transition: background 0.15s, border-color 0.15s, color 0.15s;
+}
+.lang-btn.active {
+ background: rgba(13,148,136,0.1);
+ border-color: rgba(13,148,136,0.35);
+ color: var(--accent);
+}
+.lang-btn:hover:not(.active) { background: rgba(255,255,255,0.04); color: var(--text-primary); }
+.lang-flag { font-size: 14px; }
+
+/* Actions row */
+.ws-setup-actions {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 10px;
+ margin-top: 24px;
+}
+.ws-setup-footer-note {
+ font-size: 12px;
+ color: var(--text-muted);
+ text-align: center;
+ margin-top: 20px;
+ line-height: 1.5;
+}
+
+/* Roles reference */
+.role-def-row {
+ display: flex;
+ align-items: flex-start;
+ gap: 16px;
+ padding: 11px 0;
+ border-bottom: 1px solid var(--border-subtle);
+}
+.role-def-row:last-child { border-bottom: none; }
+.role-def-text { font-size: 13px; color: var(--text-secondary); line-height: 1.5; }
diff --git a/apps/ui/data/agents.json b/apps/ui/data/agents.json
new file mode 100644
index 0000000..e78cea5
--- /dev/null
+++ b/apps/ui/data/agents.json
@@ -0,0 +1,58 @@
+[
+ {
+ "id": "ag-001",
+ "slug": "customer-support",
+ "display_name": "Customer Support",
+ "description": "Handles ticket creation, status lookups, and customer data retrieval. Powers the support team's Claude integration.",
+ "status": "active",
+ "operation_count": 5,
+ "operation_ids": ["op_01", "op_02", "op_07", "op_09", "op_10"],
+ "key_count": 2,
+ "created_at": "2024-11-15T10:22:00Z",
+ "created_by": "AT",
+ "last_called_at": "2025-03-28T18:44:00Z",
+ "calls_today": 312
+ },
+ {
+ "id": "ag-002",
+ "slug": "sales-pipeline",
+ "display_name": "Sales Pipeline",
+ "description": "CRM operations for the sales team: lead creation, deal updates, contact lookup, and company enrichment.",
+ "status": "active",
+ "operation_count": 5,
+ "operation_ids": ["op_01", "op_06", "op_08", "op_11", "op_12"],
+ "key_count": 1,
+ "created_at": "2024-11-28T14:10:00Z",
+ "created_by": "AT",
+ "last_called_at": "2025-03-29T09:01:00Z",
+ "calls_today": 541
+ },
+ {
+ "id": "ag-003",
+ "slug": "internal-devops",
+ "display_name": "Internal DevOps",
+ "description": "Infrastructure tooling: deployment triggers, service health checks, and incident management workflows.",
+ "status": "draft",
+ "operation_count": 3,
+ "operation_ids": ["op_04", "op_05", "op_11"],
+ "key_count": 0,
+ "created_at": "2025-01-08T09:45:00Z",
+ "created_by": "AT",
+ "last_called_at": null,
+ "calls_today": 0
+ },
+ {
+ "id": "ag-004",
+ "slug": "product-analytics",
+ "display_name": "Product Analytics",
+ "description": "Queries usage metrics, feature adoption data, and user behaviour reports from the analytics stack.",
+ "status": "active",
+ "operation_count": 4,
+ "operation_ids": ["op_02", "op_03", "op_08", "op_12"],
+ "key_count": 3,
+ "created_at": "2025-02-01T11:30:00Z",
+ "created_by": "AT",
+ "last_called_at": "2025-03-29T11:22:00Z",
+ "calls_today": 87
+ }
+]
diff --git a/apps/ui/data/keys.json b/apps/ui/data/keys.json
new file mode 100644
index 0000000..146eb79
--- /dev/null
+++ b/apps/ui/data/keys.json
@@ -0,0 +1,15 @@
+{
+ "acme-workspace": [
+ { "id": "key_01", "name": "Production", "prefix": "mcp_prod_4f8a…", "scopes": ["read","write","deploy"], "created": "2025-11-15", "lastUsed": "Today", "status": "active" },
+ { "id": "key_02", "name": "CI / CD pipeline", "prefix": "mcp_ci_b21c…", "scopes": ["read","deploy"], "created": "2025-12-01", "lastUsed": "2 days ago", "status": "active" },
+ { "id": "key_03", "name": "Dev local", "prefix": "mcp_dev_7e3f…", "scopes": ["read","write"], "created": "2026-01-08", "lastUsed": "Never", "status": "active" },
+ { "id": "key_04", "name": "Legacy integration", "prefix": "mcp_leg_c90d…", "scopes": ["read"], "created": "2025-09-22", "lastUsed": "45 days ago", "status": "revoked" }
+ ],
+ "startup-demo": [
+ { "id": "key_11", "name": "Backend service", "prefix": "mcp_back_9a1b…", "scopes": ["read","write"], "created": "2026-01-20", "lastUsed": "Today", "status": "active" },
+ { "id": "key_12", "name": "Analytics pipeline", "prefix": "mcp_anal_3c7d…", "scopes": ["read"], "created": "2026-02-05", "lastUsed": "3 days ago", "status": "active" }
+ ],
+ "personal": [
+ { "id": "key_21", "name": "Local experiments", "prefix": "mcp_loc_f2e8…", "scopes": ["read","write","deploy"], "created": "2026-03-01", "lastUsed": "Yesterday", "status": "active" }
+ ]
+}
diff --git a/apps/ui/data/operations.json b/apps/ui/data/operations.json
new file mode 100644
index 0000000..c0d4cd9
--- /dev/null
+++ b/apps/ui/data/operations.json
@@ -0,0 +1,134 @@
+[
+ {
+ "id": "op_01",
+ "name": "create_crm_lead",
+ "display_name": "Create CRM Lead",
+ "protocol": "rest",
+ "method": "POST",
+ "status": "active",
+ "category": "CRM",
+ "target_url": "https://api.acme.com/v2/crm/leads",
+ "created_at": "2026-03-21"
+ },
+ {
+ "id": "op_02",
+ "name": "get_user_profile",
+ "display_name": "Get User Profile",
+ "protocol": "rest",
+ "method": "GET",
+ "status": "active",
+ "category": "Users",
+ "target_url": "https://api.acme.com/v1/users/{id}",
+ "created_at": "2026-03-18"
+ },
+ {
+ "id": "op_03",
+ "name": "search_knowledge_base",
+ "display_name": "Search Knowledge Base",
+ "protocol": "graphql",
+ "method": null,
+ "status": "active",
+ "category": "Search",
+ "target_url": "https://graph.notion-int.acme.com/graphql",
+ "created_at": "2026-03-14"
+ },
+ {
+ "id": "op_04",
+ "name": "send_slack_message",
+ "display_name": "Send Slack Message",
+ "protocol": "rest",
+ "method": "POST",
+ "status": "error",
+ "category": "Notifications",
+ "target_url": "https://slack.com/api/chat.postMessage",
+ "created_at": "2026-03-10"
+ },
+ {
+ "id": "op_05",
+ "name": "stream_telemetry",
+ "display_name": "Stream Telemetry Events",
+ "protocol": "grpc",
+ "method": null,
+ "status": "draft",
+ "category": "Observability",
+ "target_url": "grpc://telemetry.internal.acme.com:9090",
+ "created_at": "2026-03-05"
+ },
+ {
+ "id": "op_06",
+ "name": "update_deal_stage",
+ "display_name": "Update Deal Stage",
+ "protocol": "rest",
+ "method": "PATCH",
+ "status": "inactive",
+ "category": "CRM",
+ "target_url": "https://api.acme.com/v2/crm/deals/{id}",
+ "created_at": "2026-02-28"
+ },
+ {
+ "id": "op_07",
+ "name": "fetch_invoice",
+ "display_name": "Fetch Invoice",
+ "protocol": "rest",
+ "method": "GET",
+ "status": "active",
+ "category": "Billing",
+ "target_url": "https://billing.acme.com/v1/invoices/{id}",
+ "created_at": "2026-02-20"
+ },
+ {
+ "id": "op_08",
+ "name": "list_products",
+ "display_name": "List Products",
+ "protocol": "graphql",
+ "method": null,
+ "status": "active",
+ "category": "Catalog",
+ "target_url": "https://shop.acme.com/graphql",
+ "created_at": "2026-02-15"
+ },
+ {
+ "id": "op_09",
+ "name": "create_support_ticket",
+ "display_name": "Create Support Ticket",
+ "protocol": "rest",
+ "method": "POST",
+ "status": "active",
+ "category": "Support",
+ "target_url": "https://support.acme.com/api/tickets",
+ "created_at": "2026-02-10"
+ },
+ {
+ "id": "op_10",
+ "name": "get_order_status",
+ "display_name": "Get Order Status",
+ "protocol": "rest",
+ "method": "GET",
+ "status": "draft",
+ "category": "Orders",
+ "target_url": "https://orders.acme.com/v2/status/{id}",
+ "created_at": "2026-02-05"
+ },
+ {
+ "id": "op_11",
+ "name": "sync_contacts",
+ "display_name": "Sync Contacts",
+ "protocol": "grpc",
+ "method": null,
+ "status": "active",
+ "category": "CRM",
+ "target_url": "grpc://contacts.internal.acme.com:9091",
+ "created_at": "2026-01-28"
+ },
+ {
+ "id": "op_12",
+ "name": "send_email_campaign",
+ "display_name": "Send Email Campaign",
+ "protocol": "rest",
+ "method": "POST",
+ "status": "active",
+ "category": "Marketing",
+ "target_url": "https://mail.acme.com/api/v3/campaigns/send",
+ "created_at": "2026-01-20"
+ }
+]
diff --git a/apps/ui/html/agents.html b/apps/ui/html/agents.html
new file mode 100644
index 0000000..61bc34f
--- /dev/null
+++ b/apps/ui/html/agents.html
@@ -0,0 +1,380 @@
+
+
+
+
+
+
+ Crank — Agents
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Crank
+
+
+
+
+
+ A
+ acme-workspace
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Total agents
+
—
+
across this workspace
+
+
+
+
Operations exposed
+
—
+
—
+
+
+
Calls today
+
—
+
all agents combined
+
+
+
+
+
+
+
+
+
+
+
+ of agents
+
+
+
+
+
+
+
+
Each agent gets its own MCP endpoint. Connect your LLM client to a specific agent so it only sees the tools it needs — not all ? operations in this workspace.
+
+
+
+
+
+
+
+
+
No agents yet
+
Create your first agent to get a dedicated MCP endpoint with a curated set of tools.
+
+
+ New agent
+
+
+
+
+
+
No agents match " "
+
Try a different search term.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Identity
+
+
+ Display name required
+
+
+
+
+
+
+ Description (optional)
+
+
+
+
+
+
+
+
+
+
Select which operations this agent exposes. LLMs connecting to this agent will only see these tools.
+
+
+
+
+
+
+
+
+
+
+ No operations match " "
+
+
+
+
+
+
+
+
+
+
You've selected tools. LLMs perform best with <15 tools per agent. Consider splitting into separate agents by use-case.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/html/api-keys.html b/apps/ui/html/api-keys.html
new file mode 100644
index 0000000..daeff65
--- /dev/null
+++ b/apps/ui/html/api-keys.html
@@ -0,0 +1,281 @@
+
+
+
+
+
+
+ Crank — API Keys
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Crank
+
+
+
+
+
+ A
+ acme-workspace
+
+
+
+
+
+
+
+
+
+
+
+
+ ⌘K
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Keys are only shown once. Copy and store them securely immediately after creation — Crank stores only a hash. Revoked keys cannot be restored.
+
+
+
+
+
+
+
+
+
+
+ Name
+ Key prefix
+ Scopes
+ Created
+ Last used
+ Status
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ read
+
+
List operations, read configs, query logs and usage metrics.
+
+
+
+ write
+
+
Create, update, and delete operations and their configurations.
+
+
+
+ deploy
+
+
Publish draft operations, rollback deployments, and manage traffic splits.
+
+
+
+
+
+
+
+
+
+
+
+
+
Key name
+
+
A descriptive label to identify the key. Only visible to admins.
+
+
+
+
+
+
+
+
+
Copy this key now. It won't be shown again.
+
+
+
mcp_prod_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/html/fragments/lang-switcher.html b/apps/ui/html/fragments/lang-switcher.html
new file mode 100644
index 0000000..96a8403
--- /dev/null
+++ b/apps/ui/html/fragments/lang-switcher.html
@@ -0,0 +1,13 @@
+
+
Interface language
+
+
+ 🇬🇧
+ English
+
+
+ 🇷🇺
+ Русский
+
+
+
diff --git a/apps/ui/html/login.html b/apps/ui/html/login.html
new file mode 100644
index 0000000..e987eab
--- /dev/null
+++ b/apps/ui/html/login.html
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+ Crank — Sign in
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Sign in
+
Welcome back to your workspace
+
+
+ Invalid email or password. Please try again.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Google SSO
+
+
+
+
+
+
+
Crank v0.9.0-beta
+
+
+
+
+
+
+
diff --git a/apps/ui/html/logs.html b/apps/ui/html/logs.html
new file mode 100644
index 0000000..efbdc0c
--- /dev/null
+++ b/apps/ui/html/logs.html
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+ Crank — Logs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Crank
+
+
+
+
+
+ A
+ acme-workspace
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/html/settings.html b/apps/ui/html/settings.html
new file mode 100644
index 0000000..b6a8d1c
--- /dev/null
+++ b/apps/ui/html/settings.html
@@ -0,0 +1,597 @@
+
+
+
+
+
+
+ Crank — Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Crank
+
+
+
+
+
+ A
+ acme-workspace
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Profile
+
+
+
+
+
+ Security
+
+
+
+
+
+
+ Notifications
+
+
+
+
+
+
+
+
+
+
+
+
Workspace name
+
+
Used in API endpoints and across the console. Only lowercase letters, numbers and hyphens.
+
+
+ Display name
+
+
+
+ Description (optional)
+
+
+
+
+ Timezone
+
+ UTC
+ America/New_York
+ America/Los_Angeles
+ Europe/London
+ Europe/Paris
+ Asia/Tokyo
+
+
+
+ Default protocol
+
+ REST / HTTP
+ GraphQL
+ gRPC
+
+
+
+
+
+
+ Language
+
+
+
+ 🇬🇧
+ English
+
+
+ 🇷🇺
+ Русский
+
+
+
Interface display language
+
+
+
+ Save changes
+
+
+
+
+
+
+
+
+
+
+
+
AT
+
+
Operator
+
operator@acme-workspace
+
Change photo
+
+
+
Avatar will appear in the console and team views.
+
+
+
+
+
Email address
+
+
Changing email requires re-verification. You'll receive a confirmation link.
+
+
+
+ Save profile
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
AT
+
+
Operator (you)
+
operator@acme.com
+
+
Today
+
Owner
+
+
+
+
BS
+
+
Blake Smith
+
blake@acme.com
+
+
2 days ago
+
+ Admin
+ Developer
+ Viewer
+
+
+
+
+
+
+
+
CL
+
+
Carol Lane
+
carol@acme.com
+
+
1 week ago
+
+ Admin
+ Developer
+ Viewer
+
+
+
+
+
+
+
+
DM
+
+
Dan Morgan
+
dan@acme.com
+
+
Never
+
+ Admin
+ Developer
+ Viewer
+
+
+
+
+
+
+
+
+
+
Pending invites 1
+
+
?
+
+
john@startup.com
+
Invited 3 days ago · not yet accepted
+
+
—
+
Developer
+
+
+
+
+
+
+
+
+
+
+
+
+
Owner
+
Full workspace control, billing access, can transfer or delete the workspace. Only one owner per workspace.
+
+
+
Admin
+
Manage members and roles, create and edit all operations and agents, manage API keys.
+
+
+
Developer
+
Create and edit operations and agents. Cannot manage members or workspace settings.
+
+
+
Viewer
+
Read-only access to operations, agents, and logs. Cannot create or modify anything.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Current password
+
+
+
+
+ Change password
+
+
+
+
+
+
Two-factor authentication
+
+
+
+
TOTP authenticator app
+
Use Google Authenticator, 1Password, or any TOTP-compatible app.
+
+
Enabled
+
Configure
+
+
+
+
+
Hardware security key
+
FIDO2 / WebAuthn passkey or YubiKey.
+
+
Add key
+
+
+
+
+
+
+
+
+
+
Chrome · macOS — current
+
Last active: now · 93.184.216.34 (US)
+
+
+
+
+
Safari · iPhone
+
Last active: 2 days ago · 93.184.216.34 (US)
+
+
+
+
+
+
+
+
Firefox · Linux
+
Last active: 8 days ago · 172.16.0.4 (Internal)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Operation error spike
+
Alert when error rate exceeds 5% over a 5-minute window.
+
+
+
+
+
+
Upstream latency degradation
+
Alert when p99 latency exceeds the baseline by 3×.
+
+
+
+
+
+
Quota approaching limit
+
Email when any operation reaches 80% of its monthly quota.
+
+
+
+
+
+
New team member joined
+
Notify workspace admins when someone accepts an invite.
+
+
+
+
+
+
Weekly usage digest
+
Email summary of invocations, errors and latency each Monday.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Rename workspace
+
Changes the workspace slug used in all API endpoints. Existing API keys will continue to work. All existing tool call references will break.
+
+
Rename
+
+
+
+
Export all data
+
Download a ZIP archive of all operation configs, schemas, mappings and invocation history as JSON. May take a few minutes.
+
+
Export
+
+
+
+
Delete workspace
+
Permanently deletes all operations, keys, logs and settings. This action cannot be undone. You will be logged out immediately.
+
+
Delete workspace
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/html/usage.html b/apps/ui/html/usage.html
new file mode 100644
index 0000000..88380bb
--- /dev/null
+++ b/apps/ui/html/usage.html
@@ -0,0 +1,228 @@
+
+
+
+
+
+
+ Crank — Usage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Crank
+
+
+
+
+
+ A
+ acme-workspace
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Total invocations
+
28,341
+
+
+ +12% vs prior period
+
+
+
+
Success rate
+
98.6%
+
+
+ +0.3pp vs prior period
+
+
+
+
Median latency (p50)
+
124ms
+
+
+ -18ms vs prior period
+
+
+
+
p99 latency
+
2.1s
+
+
+ +340ms vs prior period
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Operation
+ Protocol
+ Calls
+ Errors
+ Error rate
+ Latency (p50 / p95 / p99)
+ Quota
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/html/wizard/index.html b/apps/ui/html/wizard/index.html
new file mode 100644
index 0000000..8862771
--- /dev/null
+++ b/apps/ui/html/wizard/index.html
@@ -0,0 +1,238 @@
+
+
+
+
+
+ Crank — New Operation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Crank
+
+
+
+
+
+ A
+ acme-workspace
+
+
+
+
+
+
+
+
+
+
+
+
+ ⌘K
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Operations
+
+
+
Create operation
+
+
20%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back
+
+
+
Save draft
+
+
+
+
Step 1 of 5
+
+
+ Continue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/html/wizard/step1.html b/apps/ui/html/wizard/step1.html
new file mode 100644
index 0000000..3a843e5
--- /dev/null
+++ b/apps/ui/html/wizard/step1.html
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
REST / HTTP
+
Standard HTTP verbs over a RESTful endpoint. The most broadly supported option.
+
+ GET
+ POST
+ PUT
+ PATCH
+ DELETE
+
+
+
+
+
+
+
GraphQL
+
Query and mutate a GraphQL schema. Supports queries, mutations, and subscriptions.
+
+ Query
+ Mutation
+
+
+
+
+
+
+
gRPC
+
High-performance binary RPC over HTTP/2. Ideal for low-latency internal services.
+
+ Unary
+ Protobuf
+
+
+
+
+
+
+
+
+
+
+
+
+
You can change this later
+
+ Switching protocol after configuring subsequent steps will clear schema and mapping data. Save your operation as a draft before experimenting. See
protocol migration guide for details.
+
+
+
+
+
diff --git a/apps/ui/html/wizard/step2.html b/apps/ui/html/wizard/step2.html
new file mode 100644
index 0000000..a5e02a0
--- /dev/null
+++ b/apps/ui/html/wizard/step2.html
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Select an upstream…
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Register new upstream
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/html/wizard/step3-graphql.html b/apps/ui/html/wizard/step3-graphql.html
new file mode 100644
index 0000000..c2335dd
--- /dev/null
+++ b/apps/ui/html/wizard/step3-graphql.html
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+ query
+ Fetch data without side-effects
+
+
+ mutation
+ Create, update or delete data
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Variables are mapped in step 5
+
+ Declare all variables in the query document using the $variable: Type syntax. In step 5 you will map MCP input fields to these variables. Crank serialises the final {"query","variables"} payload automatically.
+
+
+
+
+
diff --git a/apps/ui/html/wizard/step3-grpc.html b/apps/ui/html/wizard/step3-grpc.html
new file mode 100644
index 0000000..0146128
--- /dev/null
+++ b/apps/ui/html/wizard/step3-grpc.html
@@ -0,0 +1,274 @@
+
+
+
+
+
+
+
+ Proto file
+
+
+
+ Server reflection
+
+
+
+ Manual
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Drop .proto file here
+
or click to browse
+
+
+
+
+
+
+
+ Parse proto
+ Cancel
+
+
+
+
+
+
+
+
+
Streaming methods excluded
+
0 streaming method(s) found and hidden — not compatible with MCP tool calls.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ No upstream selected
+ Select an upstream in step 2 first
+
+
+
+
+
+
+ Discover methods
+
+
+
+
+
+
+
+
+
Reflection not available on this server.
+
Retry
+
+
+
+
+
+
+
+
+
Streaming methods excluded
+
0 streaming method(s) discovered and hidden.
+
+
+
+
+
+
+
+
+
+
+
+
+
Input and output schemas are defined in step 4
+
In manual mode you only need the route. The MCP tool input/output schemas — which the LLM uses to call this tool — are configured in the next step regardless of Protobuf definitions.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/html/wizard/step3-rest.html b/apps/ui/html/wizard/step3-rest.html
new file mode 100644
index 0000000..de94ea3
--- /dev/null
+++ b/apps/ui/html/wizard/step3-rest.html
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+ GET
+ Read
+
+
+ POST
+ Create
+
+
+ PUT
+ Replace
+
+
+ PATCH
+ Update
+
+
+ DELETE
+ Remove
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/html/wizard/step4.html b/apps/ui/html/wizard/step4.html
new file mode 100644
index 0000000..a14377f
--- /dev/null
+++ b/apps/ui/html/wizard/step4.html
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+
+
+
+
Writing effective descriptions
+
+ Start with a verb ("Creates", "Fetches", "Updates"). Mention key input requirements. Describe what a successful response looks like. Avoid vague phrasing like "handles" or "manages". See
description best practices →
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Schemas are protocol-agnostic
+
+ These schemas describe the MCP contract, not the upstream wire format. Field mapping in Step 5 translates between the two. You can upload a sample JSON response to auto-generate the output schema.
+
+
+
+
diff --git a/apps/ui/html/wizard/step5.html b/apps/ui/html/wizard/step5.html
new file mode 100644
index 0000000..13c4d25
--- /dev/null
+++ b/apps/ui/html/wizard/step5.html
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Operation will be saved as a draft
+
+ Clicking "Create operation" saves the contract in draft status. You can run tests, inspect metadata and publish from the operation workspace. Drafts are never exposed to MCP clients.
+
+
+
+
diff --git a/apps/ui/html/workspace-setup.html b/apps/ui/html/workspace-setup.html
new file mode 100644
index 0000000..6aab8a5
--- /dev/null
+++ b/apps/ui/html/workspace-setup.html
@@ -0,0 +1,327 @@
+
+
+
+
+
+
+ Crank — Workspace
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/icons/agents/agent.svg b/apps/ui/icons/agents/agent.svg
new file mode 100644
index 0000000..7abbba5
--- /dev/null
+++ b/apps/ui/icons/agents/agent.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/icons/agents/eye.svg b/apps/ui/icons/agents/eye.svg
new file mode 100644
index 0000000..f843b1b
--- /dev/null
+++ b/apps/ui/icons/agents/eye.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/apps/ui/icons/agents/key.svg b/apps/ui/icons/agents/key.svg
new file mode 100644
index 0000000..c208db3
--- /dev/null
+++ b/apps/ui/icons/agents/key.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/arrow-down.svg b/apps/ui/icons/general/arrow-down.svg
new file mode 100644
index 0000000..7577633
--- /dev/null
+++ b/apps/ui/icons/general/arrow-down.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/arrow-left.svg b/apps/ui/icons/general/arrow-left.svg
new file mode 100644
index 0000000..a34bc94
--- /dev/null
+++ b/apps/ui/icons/general/arrow-left.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/arrow-right.svg b/apps/ui/icons/general/arrow-right.svg
new file mode 100644
index 0000000..71e439f
--- /dev/null
+++ b/apps/ui/icons/general/arrow-right.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/arrow-up.svg b/apps/ui/icons/general/arrow-up.svg
new file mode 100644
index 0000000..85d9676
--- /dev/null
+++ b/apps/ui/icons/general/arrow-up.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/bell.svg b/apps/ui/icons/general/bell.svg
new file mode 100644
index 0000000..7ccd650
--- /dev/null
+++ b/apps/ui/icons/general/bell.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/check-sm.svg b/apps/ui/icons/general/check-sm.svg
new file mode 100644
index 0000000..3449e27
--- /dev/null
+++ b/apps/ui/icons/general/check-sm.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/check.svg b/apps/ui/icons/general/check.svg
new file mode 100644
index 0000000..250df56
--- /dev/null
+++ b/apps/ui/icons/general/check.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/chevron-down.svg b/apps/ui/icons/general/chevron-down.svg
new file mode 100644
index 0000000..07aaf73
--- /dev/null
+++ b/apps/ui/icons/general/chevron-down.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/close.svg b/apps/ui/icons/general/close.svg
new file mode 100644
index 0000000..648c2e9
--- /dev/null
+++ b/apps/ui/icons/general/close.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/copy.svg b/apps/ui/icons/general/copy.svg
new file mode 100644
index 0000000..3a586b9
--- /dev/null
+++ b/apps/ui/icons/general/copy.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/crank-logo.svg b/apps/ui/icons/general/crank-logo.svg
new file mode 100644
index 0000000..b33e89f
--- /dev/null
+++ b/apps/ui/icons/general/crank-logo.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/download.svg b/apps/ui/icons/general/download.svg
new file mode 100644
index 0000000..494bbb5
--- /dev/null
+++ b/apps/ui/icons/general/download.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/edit.svg b/apps/ui/icons/general/edit.svg
new file mode 100644
index 0000000..0a2419e
--- /dev/null
+++ b/apps/ui/icons/general/edit.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/external.svg b/apps/ui/icons/general/external.svg
new file mode 100644
index 0000000..893d2df
--- /dev/null
+++ b/apps/ui/icons/general/external.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/grid.svg b/apps/ui/icons/general/grid.svg
new file mode 100644
index 0000000..98effa2
--- /dev/null
+++ b/apps/ui/icons/general/grid.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/info-circle.svg b/apps/ui/icons/general/info-circle.svg
new file mode 100644
index 0000000..cdc8fd5
--- /dev/null
+++ b/apps/ui/icons/general/info-circle.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/info.svg b/apps/ui/icons/general/info.svg
new file mode 100644
index 0000000..1270118
--- /dev/null
+++ b/apps/ui/icons/general/info.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/logout.svg b/apps/ui/icons/general/logout.svg
new file mode 100644
index 0000000..a769622
--- /dev/null
+++ b/apps/ui/icons/general/logout.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/plus-sm.svg b/apps/ui/icons/general/plus-sm.svg
new file mode 100644
index 0000000..bd555c4
--- /dev/null
+++ b/apps/ui/icons/general/plus-sm.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/plus.svg b/apps/ui/icons/general/plus.svg
new file mode 100644
index 0000000..f192806
--- /dev/null
+++ b/apps/ui/icons/general/plus.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/refresh.svg b/apps/ui/icons/general/refresh.svg
new file mode 100644
index 0000000..a64b2de
--- /dev/null
+++ b/apps/ui/icons/general/refresh.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/search.svg b/apps/ui/icons/general/search.svg
new file mode 100644
index 0000000..ac71407
--- /dev/null
+++ b/apps/ui/icons/general/search.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/settings.svg b/apps/ui/icons/general/settings.svg
new file mode 100644
index 0000000..556e1cd
--- /dev/null
+++ b/apps/ui/icons/general/settings.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/trash.svg b/apps/ui/icons/general/trash.svg
new file mode 100644
index 0000000..658080d
--- /dev/null
+++ b/apps/ui/icons/general/trash.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/user.svg b/apps/ui/icons/general/user.svg
new file mode 100644
index 0000000..e9ef4cd
--- /dev/null
+++ b/apps/ui/icons/general/user.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/general/warning.svg b/apps/ui/icons/general/warning.svg
new file mode 100644
index 0000000..3b96ca1
--- /dev/null
+++ b/apps/ui/icons/general/warning.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/apps/ui/icons/login/google.svg b/apps/ui/icons/login/google.svg
new file mode 100644
index 0000000..19c999b
--- /dev/null
+++ b/apps/ui/icons/login/google.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/icons/wizard/clock.svg b/apps/ui/icons/wizard/clock.svg
new file mode 100644
index 0000000..2b3ba0b
--- /dev/null
+++ b/apps/ui/icons/wizard/clock.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/apps/ui/icons/wizard/code.svg b/apps/ui/icons/wizard/code.svg
new file mode 100644
index 0000000..31327ef
--- /dev/null
+++ b/apps/ui/icons/wizard/code.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/wizard/file.svg b/apps/ui/icons/wizard/file.svg
new file mode 100644
index 0000000..aee4e64
--- /dev/null
+++ b/apps/ui/icons/wizard/file.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/wizard/graphql-protocol.svg b/apps/ui/icons/wizard/graphql-protocol.svg
new file mode 100644
index 0000000..08a5fa5
--- /dev/null
+++ b/apps/ui/icons/wizard/graphql-protocol.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/icons/wizard/grpc-protocol.svg b/apps/ui/icons/wizard/grpc-protocol.svg
new file mode 100644
index 0000000..bde9d0a
--- /dev/null
+++ b/apps/ui/icons/wizard/grpc-protocol.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/apps/ui/icons/wizard/reflection.svg b/apps/ui/icons/wizard/reflection.svg
new file mode 100644
index 0000000..19f923f
--- /dev/null
+++ b/apps/ui/icons/wizard/reflection.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/apps/ui/icons/wizard/rest-protocol.svg b/apps/ui/icons/wizard/rest-protocol.svg
new file mode 100644
index 0000000..97bc44e
--- /dev/null
+++ b/apps/ui/icons/wizard/rest-protocol.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/icons/wizard/server.svg b/apps/ui/icons/wizard/server.svg
new file mode 100644
index 0000000..e58ca74
--- /dev/null
+++ b/apps/ui/icons/wizard/server.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui/icons/wizard/upload.svg b/apps/ui/icons/wizard/upload.svg
new file mode 100644
index 0000000..1dc7232
--- /dev/null
+++ b/apps/ui/icons/wizard/upload.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/ui/index.html b/apps/ui/index.html
index 81d9d27..88c2b92 100644
--- a/apps/ui/index.html
+++ b/apps/ui/index.html
@@ -1,94 +1,418 @@
-
+
-
-
-
- Crank Console
-
-
-
-
-
- UI replacement in progress
- Crank Console
-
- The legacy React/Vite UI has been removed. A new operator console is being integrated on top of the same
- deployment path.
-
-
- Backend services, MCP endpoints and deployment contracts remain intact. Replace this static entrypoint with
- the new Alpine.js UI when it is ready.
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Total operations
+
—
+
+3 this month
+
+
+
+
Requests today
+
4,821
+
+12% vs yesterday
+
+
+
Avg. latency
+
187ms
+
+23ms this week
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Showing of
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Clear all
+
+
+
+
+
+
+
+
+
+
+
+
No operations found
+
Try adjusting your search or filters.
+
+
+
+
+
+
+
+ NAME
+
+
+
+
+ PROTOCOL
+ STATUS
+
+ CREATED
+
+
+
+
+ TARGET URL
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+