Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
name: Entangled VPN — Connected Shell
description: Calm signal-bench Operate UI — matte B&W, path-first chroma, dense peer channels.
colors:
bg: "#0a0a0a"
surface: "#111111"
raised: "#181818"
border: "#2a2a2a"
hairline: "#3a3a3a"
muted: "#8a8a8a"
text: "#f0f0f0"
dim: "#5c5c5c"
cta: "#ffffff"
cta-ink: "#0a0a0a"
live: "#2fbf62"
fault: "#e23d3d"
bg-light: "#f5f5f5"
surface-light: "#ececec"
live-light: "#1a8f45"
fault-light: "#c41e1e"
typography:
sans:
fontFamily: "ui-sans-serif, 'Segoe UI', system-ui, sans-serif"
fontSize: "13px"
fontWeight: 400
lineHeight: 1.45
mono:
fontFamily: "ui-monospace, 'Cascadia Code', Consolas, monospace"
fontSize: "12px"
fontWeight: 500
lineHeight: 1.35
label:
fontFamily: "ui-sans-serif, 'Segoe UI', system-ui, sans-serif"
fontSize: "10px"
fontWeight: 600
lineHeight: 1.2
letterSpacing: "0.08em"
rounded:
sm: "4px"
md: "6px"
spacing:
xs: "4px"
sm: "8px"
md: "12px"
lg: "16px"
xl: "24px"
components:
btn:
backgroundColor: "transparent"
textColor: "{colors.text}"
rounded: "{rounded.md}"
padding: "6px 12px"
height: "32px"
btn-cta:
backgroundColor: "{colors.cta}"
textColor: "{colors.cta-ink}"
rounded: "{rounded.md}"
padding: "6px 12px"
height: "32px"
---

# Design System: Entangled VPN — Connected Shell

## Overview

Connected mode is an Operate surface for a friend LAN. It borrows the *idea* of a signal bench — path quality as the primary instrument — without dressing up as a CRT or Discord clone. Intro (`ConnectView` + starfield) is a separate surface and is not restyled here.

## Thesis

Room + VIP + path are the first things you see. Peers are a dense channel list. Chat is available without becoming the product. Green and red are scarce and meaningful.

## Colors

Neutrals only for structure. Two chroma roles:

| Token | Use |
|---|---|
| `--live` | Direct / p2p path only |
| `--fault` | Relay, reconnecting, unread, errors |

Everything else is black, white, or grey. Light theme inverts neutrals and keeps the same two chroma roles.

Connected tokens live under `html[data-app="connected"]` so intro keeps `:root` / Bungee / starfield unchanged.

## Typography

Connected shell uses system sans for chrome and mono for VIP, IPs, ping, and path. Do not load a second display face for connected mode (intro keeps Bungee). Labels are small uppercase with modest tracking — used as field captions in the header, not as kickers above every pane title.

## Layout (first viewport)

1. **Header band** — Entangled wordmark (quiet), active room, copyable VIP, path status (dot + short label), peers count, settings, disconnect
2. **Left rail** — saved networks, create / join
3. **Main** — peers as home (`network`); chat and settings as alternate main panes (keep existing view routing)
4. **Footer** — slim status strip for server / version only (facts already in the header are not repeated loudly)

No starfield behind the connected shell. Opaque bench surfaces only.

## Components

- Ghost / quiet / CTA buttons with 4–6px radius
- 8px path dots (`live` / `fault` / idle grey)
- Peer rows: nick, mono IP, path, ping, ping + chat actions
- Confirm dialogs for destructive actions
- Theme control in settings (and optional header)
- SVG icons only (no Unicode `⋯` / `▼` as chrome)

## Motion

Short opacity / border transitions. Honor `prefers-reduced-motion`. No view-transition gimmicks, grain, or glow.

## Do

- Derive one `pathAggregate`: `direct` | `relay` | `reconnecting` | `disconnected`
- Ship disconnect in connected chrome
- Keep ConnectView markup and `space.css` frozen
- Add Vite `sim.js` + `?connected=1` for preview
- Prefer deletion of ASCII chrome (`[~]`, `[>]`) in connected views

## Don't

- Invent crypto / throughput / engine footers
- Paint green or red for decoration
- Port the previous over-instrumented LED/graticule costume wholesale
- Change intro brand, tagline, or starfield
48 changes: 48 additions & 0 deletions PRODUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Product

<!-- impeccable:product-schema 1 -->

## Platform

web

## Users

Small friend group on Windows. Admin Wintun client. Join a named room for a session of games, files, chat.

## Product Purpose

Private virtual LAN (`10.242.0.0/24`). Success: in a room, have a VIP, peers reachable (P2P or relay), chat works.

## Positioning

Mesh for a handful of people, not a consumer VPN. Rooms, pipe invites, owner delete, in-mesh chat.

## Operating Context

Wails v2 desktop. Connect screen first (frozen). After connect: networks, peers, chat, settings. Path: P2P, UDP relay, or WebSocket. Languages: en, ru, zh. Vite sim for frontend preview.

## Capabilities and Constraints

- Connected: networks rail, peers home, chat, settings (header only), disconnect, theme, copy VIP, ping, DMs.
- Intro / ConnectView + space.css are out of scope and must keep current look.
- Do not invent crypto claims, server hosts, or fake telemetry.
- Preview: Vite DEV sim with `?connected=1` when `window.go` is absent.

## Brand Commitments

- Name: Entangled. Intro stays as-is.
- Connected palette: high-contrast black-and-white. Subtle green only for Direct/p2p. Subtle red only for relay/fault/unread/error. No other chroma.
- Direction: signal-bench Operate shell — path quality first, dense desktop craft, not Discord neon or CRT costume.
- Connected may look different from intro; both must feel like the same product.

## Product Principles

- Room and virtual IP are the facts that matter.
- Path quality is the primary chrome signal.
- Density for a desktop window.
- Prefer real status over decorative instruments.

## Accessibility & Inclusion

Keyboard focus, `prefers-reduced-motion`, existing i18n keys. No English-only new copy without locale keys.
2 changes: 0 additions & 2 deletions client/frontend/dist/assets/index-5j6ZJn19.js

This file was deleted.

1 change: 1 addition & 0 deletions client/frontend/dist/assets/index-C-aPttjH.css

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions client/frontend/dist/assets/index-C4YP2y5T.js

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion client/frontend/dist/assets/index-coA7a2P7.css

This file was deleted.

4 changes: 2 additions & 2 deletions client/frontend/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<meta http-equiv="pragma" content="no-cache" />
<meta http-equiv="expires" content="0" />
<title>Entangled VPN</title>
<script type="module" crossorigin src="/assets/index-5j6ZJn19.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-coA7a2P7.css">
<script type="module" crossorigin src="/assets/index-C4YP2y5T.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C-aPttjH.css">
</head>
<body>
<div id="app"></div>
Expand Down
79 changes: 55 additions & 24 deletions client/frontend/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
import { onMount, onDestroy } from 'svelte'
import { status, peers, view, settings, notifications, durableError, clearDurableError, addNotification, addChatMessage, addSystemChat } from './lib/stores/app.js'
import { currentLang, setLang, t, fmt } from './lib/locales/index.js'
import { derivePathAggregate } from './lib/path.js'
import Sidebar from './lib/components/Sidebar.svelte'
import PeerList from './lib/components/PeerList.svelte'
import StatusBar from './lib/components/StatusBar.svelte'
import ConnectView from './lib/components/ConnectView.svelte'
import ChatView from './lib/components/ChatView.svelte'
import SettingsView from './lib/components/SettingsView.svelte'
import ConnectedHeader from './lib/components/ConnectedHeader.svelte'

function clampScale(n) {
const v = Number(n) || 100
Expand All @@ -27,6 +29,13 @@
}
}

$: document.documentElement.setAttribute(
'data-app',
$view === 'connect' ? 'connect' : 'connected',
)

$: pathAggregate = derivePathAggregate($status, $peers)

let sidebarWidth = 220
let isDragging = false
let startX, startWidth
Expand Down Expand Up @@ -127,8 +136,10 @@
})
</script>

<div class="space-bg" aria-hidden="true"></div>
<div class="space-dust" aria-hidden="true"></div>
{#if $view === 'connect'}
<div class="space-bg" aria-hidden="true"></div>
<div class="space-dust" aria-hidden="true"></div>
{/if}

{#if $view === 'connect'}
<div class="shell connect-shell">
Expand All @@ -137,7 +148,16 @@
</div>
</div>
{:else}
<div class="shell">
<div class="shell connected-shell">
<!--
THESIS: Room, VIP, and path are the first facts. Peers are a dense channel list. Chat is available without becoming the product. Green is Direct only. Red is relay, reconnect, unread, and error.
OWN-WORLD: Opaque matte bench. No starfield. System sans for chrome, system mono for VIP, IP, ping, and path. Intro keeps Bungee and space.
STORY: You are in a named room with a virtual IP. Path quality is the instrument. Disconnect lives in the header, not only on the connect screen.
FIRST VIEWPORT: Header band with wordmark, room, copyable VIP, path dot and label, peer count, settings, disconnect. Left rail of saved networks. Main peer table. Slim footer for server and version.
FORM: Signal-bench v2 from DESIGN.md on the master baseline. User-pinned B&W. One pathAggregate. No LED, graticule, or CRT costume.
FINISH: Dense, sharp, readable. Confirm before disconnect.
-->
<ConnectedHeader {pathAggregate} />
<div class="layout">
<div class="sidebar-wrapper" style="width: {sidebarWidth}px">
<Sidebar />
Expand All @@ -150,21 +170,23 @@
</div>

<main class="main-area">
<div class="view-fade" key={$view}>
{#if $view === 'network'}
<PeerList />
{:else if $view === 'chat'}
<ChatView />
{:else if $view === 'settings'}
<SettingsView />
{:else}
<div class="unknown-view">Unknown view: {$view}</div>
{/if}
</div>
{#key $view}
<div class="view-pane">
{#if $view === 'network'}
<PeerList />
{:else if $view === 'chat'}
<ChatView />
{:else if $view === 'settings'}
<SettingsView />
{:else}
<div class="unknown-view">Unknown view: {$view}</div>
{/if}
</div>
{/key}
</main>
</div>

<StatusBar />
<StatusBar {pathAggregate} />
</div>
{/if}

Expand Down Expand Up @@ -197,6 +219,9 @@
.connect-shell {
z-index: 1;
}
.connected-shell {
background: var(--bg-primary);
}
.layout {
display: flex;
flex: 1;
Expand Down Expand Up @@ -243,17 +268,23 @@
min-width: 0;
min-height: 0;
}
.view-fade {
.view-pane {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
min-width: 0;
animation: viewIn 0.2s ease-out;
animation: viewIn 180ms var(--ease-out, ease-out) both;
}
@keyframes viewIn {
0% { opacity: 0; transform: translateY(8px); }
100% { opacity: 1; transform: translateY(0); }
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.unknown-view {
padding: 40px;
Expand Down Expand Up @@ -294,6 +325,10 @@
.error-strip-dismiss:hover {
background: rgba(196, 92, 92, 0.12);
}
@keyframes slideIn {
from { transform: translateX(12px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.notification {
position: fixed;
top: 12px;
Expand All @@ -304,7 +339,7 @@
background: var(--bg-raised);
border: 1px solid var(--border);
color: var(--text-primary);
animation: slideIn 0.15s ease-out;
animation: slideIn 160ms var(--ease-out, ease-out) both;
}
.notification.error {
border-color: var(--error);
Expand All @@ -317,8 +352,4 @@
margin-right: 6px;
font-weight: 700;
}
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
</style>
1 change: 1 addition & 0 deletions client/frontend/src/app.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import './space.css';
@import './connected.css';

@font-face {
font-family: 'Bungee';
Expand Down
Loading
Loading