# Archipelago App UI Standards - For Apps Without Native UIs

**Version:** 1.0  
**Last Updated:** 2026-02-03

## Overview

This document defines the **standard UI pattern** for containerized applications that don't have their own web interface (e.g., Bitcoin Core, LND, Core Lightning, mempool backend services, etc.).

These UIs provide a simple, elegant way to:
- Monitor service status and metrics
- View connection information (RPC, REST, gRPC endpoints)
- Access logs and settings
- Copy configuration details for external tools

---

## Architecture

```
┌─────────────────────────────────────┐
│ Nginx Container (Alpine)            │
│ - Serves static HTML/CSS/JS         │
│ - Port 8XXX (unique per service)    │
│ - Optional: Proxies RPC/API calls   │
└─────────────────────────────────────┘
```

### File Structure

```
docker/
├── {service-name}-ui/
│   ├── index.html          # Main UI file
│   ├── Dockerfile          # Container build
│   ├── nginx.conf          # Nginx config
│   ├── {service-icon}      # App icon (svg/webp/png)
│   └── bg-{theme}.jpg      # Background image
```

---

## Standard UI Components

### 1. **CSS Class System**

#### `.glass-card` - Main Container Cards
Used for: Header, main sections, modals

```css
.glass-card {
    position: relative;
    background: rgba(0, 0, 0, 0.60);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    box-shadow:
        0 8px 24px rgba(0, 0, 0, 0.45),
        inset 0 1px 0 rgba(255, 255, 255, 0.22);
    border-radius: 1rem;
    overflow-x: hidden;
    overflow-y: visible;
    border: none;
}

.glass-card::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 2px;
    background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), transparent);
    -webkit-mask: 
        linear-gradient(#fff 0 0) content-box, 
        linear-gradient(#fff 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    pointer-events: none;
    z-index: 1;
}

.glass-card > * {
    position: relative;
    z-index: 2;
}
```

#### `.info-card` - Stat Display Cards
Used for: Status badges, metric displays (non-interactive)

```css
.info-card {
    position: relative;
    background: rgba(0, 0, 0, 0.60);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    box-shadow:
        0 8px 24px rgba(0, 0, 0, 0.45),
        inset 0 1px 0 rgba(255, 255, 255, 0.22);
    border-radius: 16px;
    padding: 12px;
    border: none;
}

.info-card::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 2px;
    background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), transparent);
    -webkit-mask: 
        linear-gradient(#fff 0 0) content-box, 
        linear-gradient(#fff 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    pointer-events: none;
}
```

**No hover effects** - These are display-only elements.

#### `.info-card-button` - Interactive Action Buttons
Used for: Primary action buttons (Copy Info, View Logs, etc.)

```css
.info-card-button {
    /* Same base styles as .info-card */
    position: relative;
    background: rgba(0, 0, 0, 0.60);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    box-shadow:
        0 8px 24px rgba(0, 0, 0, 0.45),
        inset 0 1px 0 rgba(255, 255, 255, 0.22);
    border-radius: 16px;
    padding: 12px;
    transition: all 0.3s ease;
    border: none;
    cursor: pointer;
    color: rgba(255, 255, 255, 0.9);
}

.info-card-button::before {
    /* Same gradient as .info-card */
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 2px;
    background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), transparent);
    -webkit-mask: 
        linear-gradient(#fff 0 0) content-box, 
        linear-gradient(#fff 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    pointer-events: none;
    transition: all 0.3s ease;
}

/* Hover state - lifts and brightens */
.info-card-button:hover {
    transform: translateY(-2px);
    background: rgba(0, 0, 0, 0.35);
    box-shadow:
        0 12px 32px rgba(0, 0, 0, 0.6),
        inset 0 1px 0 rgba(255, 255, 255, 0.25);
    color: rgba(255, 255, 255, 1);
}

.info-card-button:hover::before {
    background: linear-gradient(135deg, rgba(255, 255, 255, 0.3), transparent);
}

/* Active state - press down */
.info-card-button:active {
    transform: translateY(1px);
}
```

#### `.glass-button` - Secondary Buttons
Used for: Settings, Close (×), secondary actions

```css
.glass-button {
    background-color: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    border: 1px solid rgba(255, 255, 255, 0.18);
    color: rgba(255, 255, 255, 0.9);
    transition: all 0.3s ease;
}

.glass-button:hover {
    color: white;
    background-color: rgba(0, 0, 0, 0.7);
}
```

#### Simple Info Rows - `bg-white/5`
Used for: Non-interactive info rows (RPC Host, Network, Status, etc.)

```html
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
    <div class="flex items-center gap-3">
        <svg class="w-5 h-5 text-white/60"><!-- icon --></svg>
        <span class="text-white/80 text-sm">Label</span>
    </div>
    <span class="text-white/60 text-sm">Value</span>
</div>
```

**No gradient borders** - These are simple read-only display elements.

---

## Standard Layout Pattern

### 1. **Header Section** (`.glass-card`)

```html
<div class="glass-card p-6 mb-6">
    <div class="flex flex-col md:flex-row items-center md:items-center gap-4 md:gap-6">
        <!-- Logo (left) -->
        <div class="flex-shrink-0">
            <div class="logo-gradient-border">
                <img src="/assets/img/app-icons/{service-icon}" alt="{Service Name}" />
            </div>
        </div>
        
        <!-- Title and Description (center) -->
        <div class="flex-1 min-w-0">
            <h1 class="text-3xl font-bold text-white mb-2">{Service Name}</h1>
            <p class="text-white/70">{Service Description}</p>
        </div>

        <!-- Status Info (right) - OPTIONAL for headers with status -->
        <div class="w-full md:w-auto flex flex-col md:flex-row gap-3 md:gap-4">
            <div class="info-card flex items-center gap-3">
                <!-- Status info -->
            </div>
        </div>
    </div>
</div>
```

### 2. **Quick Status Bar** (`.glass-card` with `.info-card` grid)

```html
<div class="glass-card p-6 mb-6">
    <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
        <div class="info-card flex items-center justify-between">
            <!-- Status indicator -->
        </div>
        <!-- ... more status cards -->
    </div>
</div>
```

### 3. **Main Content Sections** (`.glass-card` grid)

```html
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
    <!-- Service 1: Node Status -->
    <div class="glass-card p-6">
        <div class="flex items-start gap-4 mb-4">
            <div class="flex-shrink-0 w-12 h-12 rounded-lg bg-white/10 flex items-center justify-center">
                <svg><!-- icon --></svg>
            </div>
            <div class="flex-1">
                <h2 class="text-xl font-semibold text-white mb-2">Section Title</h2>
                <p class="text-white/70 text-sm mb-4">Section description</p>
            </div>
        </div>

        <div class="space-y-3">
            <!-- Info rows (bg-white/5) -->
        </div>

        <button class="mt-4 w-full info-card-button text-sm font-medium" onclick="action()">
            Action Button
        </button>
    </div>
    
    <!-- ... more sections -->
</div>
```

### 4. **Modals** (`.glass-card` with backdrop)

```html
<div class="modal hidden fixed inset-0 bg-black/80 backdrop-blur-sm z-50 items-center justify-center p-4" id="modalId">
    <div class="glass-card p-6 max-w-2xl w-full max-h-[80vh] overflow-y-auto">
        <div class="flex justify-between items-center mb-4">
            <h2 class="text-2xl font-bold text-white">Modal Title</h2>
            <button onclick="closeModal()" class="glass-button px-3 py-2 rounded-lg text-xl font-medium">×</button>
        </div>
        <!-- Modal content -->
    </div>
</div>
```

---

## Visual Hierarchy

### **Container Importance (Most → Least)**

1. **`.glass-card`** - Main containers, sections, modals
   - Gradient border, strong blur (24px), inset highlights
   
2. **`.info-card`** - Stat displays, status badges
   - Gradient border, backdrop blur, **NO hover effects**
   
3. **`.info-card-button`** - Primary action buttons
   - Same as `.info-card` in default state
   - **WITH hover effects** (lift, brighten, enhanced gradient)
   
4. **`bg-white/5`** - Simple info rows
   - Dark background, **NO borders**, **NO hover**
   
5. **`.glass-button`** - Secondary buttons
   - Simple glass effect, minimal hover

---

## Port Assignments

Reserve unique ports for each service UI:

```
8334 - Bitcoin Knots UI
8081 - LND UI
8082 - Core Lightning UI (future)
8083 - Mempool UI (future)
8084 - BTCPay Server UI (future)
...
```

Update backend's `docker_packages.rs` to map these ports:

```rust
} else if app_id == "lnd" {
    Some("http://localhost:8081".to_string())
} else if app_id == "bitcoin-knots" {
    Some("http://localhost:8334".to_string())
}
```

---

## Dockerfile Template

```dockerfile
FROM docker.io/library/nginx:alpine

# Copy the HTML file
COPY index.html /usr/share/nginx/html/

# Create directories for assets
RUN mkdir -p /usr/share/nginx/html/assets/img/app-icons && \
    mkdir -p /usr/share/nginx/html/assets/img

# Copy assets
COPY {service-icon} /usr/share/nginx/html/assets/img/app-icons/
COPY bg-{theme}.jpg /usr/share/nginx/html/assets/img/

# Copy nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
```

---

## Nginx Config Template

### Simple Static Serving (LND, most services)

```nginx
server {
    listen 8XXX;  # Unique port for this service
    server_name _;

    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }
}
```

### With RPC Proxy (Bitcoin Knots)

```nginx
server {
    listen 8334;
    server_name _;

    root /usr/share/nginx/html;
    index index.html;

    # RPC proxy to avoid CORS issues
    location /bitcoin-rpc/ {
        proxy_pass http://127.0.0.1:8332/;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Authorization "Basic {BASE64_ENCODED_CREDS}";
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
        add_header Access-Control-Allow-Headers "Content-Type, Authorization";
        if ($request_method = OPTIONS) {
            return 204;
        }
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}
```

---

## Deployment

### Build and Run

```bash
# Build the image
cd docker/{service}-ui
sudo podman build -t {service}-ui:latest .

# Run the container
sudo podman run -d \
  --name {service}-ui \
  --restart unless-stopped \
  --network=host \
  --label 'com.archipelago.parent-app={service-id}' \
  {service}-ui:latest
```

### Backend Integration

Update `core/archipelago/src/container/docker_packages.rs`:

```rust
} else if app_id == "{service-id}" {
    Some("http://localhost:8XXX".to_string())
}
```

---

## Reference Implementations

### ✅ Bitcoin Knots UI
- **Location:** `docker/bitcoin-ui/`
- **Port:** 8334
- **Features:** 
  - Live sync status with animations
  - RPC proxy for CORS handling
  - Real-time block updates
  - Connection info display

### ✅ LND UI
- **Location:** `docker/lnd-ui/`
- **Port:** 8081
- **Features:**
  - Node status monitoring
  - Channel count display
  - REST API + gRPC info
  - Settings and logs modals

---

## Benefits of This Approach

1. **Consistency** - All service UIs look and feel the same
2. **Lightweight** - Nginx Alpine base (~10MB)
3. **Fast Development** - Copy template, customize content
4. **Mobile Responsive** - Works on all screen sizes
5. **Low Resource Usage** - Static files, minimal CPU/RAM
6. **Easy Maintenance** - Single pattern to update globally

---

## Creating a New Service UI

### Step-by-Step Process

1. **Create Directory Structure**
   ```bash
   mkdir -p docker/{service}-ui
   cd docker/{service}-ui
   ```

2. **Copy Template Files**
   ```bash
   cp ../bitcoin-ui/index.html ./
   cp ../bitcoin-ui/Dockerfile ./
   cp ../bitcoin-ui/nginx.conf ./
   ```

3. **Customize `index.html`**
   - Update title, service name, description
   - Modify status cards for your service's metrics
   - Update connection info sections (RPC/REST/gRPC)
   - Adjust modal content

4. **Copy Assets**
   ```bash
   cp ../../neode-ui/public/assets/img/app-icons/{service-icon} ./
   cp ../../neode-ui/public/assets/img/bg-{theme}.jpg ./
   ```

5. **Update Nginx Config**
   - Set unique port number
   - Add RPC proxy if needed

6. **Update Dockerfile**
   - Update asset COPY commands
   - Verify port EXPOSE

7. **Build and Deploy**
   ```bash
   # Deploy to dev server
   sshpass -p "archipelago" rsync -avz --delete ./ archipelago@192.168.1.228:/tmp/{service}-ui-build/
   
   # Build on server
   sshpass -p "archipelago" ssh archipelago@192.168.1.228 \
     "cd /tmp/{service}-ui-build && \
      sudo podman build -t {service}-ui:latest . && \
      sudo podman stop {service}-ui 2>/dev/null || true && \
      sudo podman rm {service}-ui 2>/dev/null || true && \
      sudo podman run -d --name {service}-ui --restart unless-stopped \
      --network=host --label 'com.archipelago.parent-app={service-id}' \
      {service}-ui:latest"
   ```

8. **Update Backend**
   - Edit `core/archipelago/src/container/docker_packages.rs`
   - Add port mapping for your service

---

## Testing Checklist

- [ ] UI loads correctly at `http://{server}:8XXX/`
- [ ] Logo displays properly
- [ ] Background image loads
- [ ] All status cards show correct info
- [ ] Buttons have proper hover effects
- [ ] Modals open and close correctly
- [ ] Mobile responsive (test on phone)
- [ ] Glass effects render correctly
- [ ] Gradient borders visible
- [ ] Cache busting works (no stale content)

---

## Future Enhancements

- **Live Data Updates** - WebSocket connections for real-time status
- **Interactive Charts** - Add Chart.js for visualizing metrics
- **Theme Variations** - Allow users to select background themes
- **Dark/Light Mode** - Toggle between color schemes
- **Internationalization** - Support multiple languages
- **Accessibility** - Improve screen reader support

---

## File Locations

- **UI Standards Doc:** `/Users/dorian/Projects/archy/.cursor/rules/APP-UI-STANDARDS.md`
- **Global UI Standards:** `/Users/dorian/Projects/archy/.cursor/rules/UI-STANDARDS.md`
- **Reference Implementations:**
  - Bitcoin UI: `/Users/dorian/Projects/archy/docker/bitcoin-ui/`
  - LND UI: `/Users/dorian/Projects/archy/docker/lnd-ui/`

---

**Version:** 1.0  
**Maintained by:** Archipelago Development Team  
**Last Updated:** 2026-02-03
