fix(lnd-ui,bitcoin-ui): canonical app icon, header stacking, square QR, button parity
Five review fixes across both node UIs. 1. LND now uses the app-store icon. It was shipping its own 182KB lnd.svg while the app store, My Apps and the signed catalog all render neode-ui/public/assets/img/app-icons/lnd.png (catalog.json points at /assets/img/app-icons/lnd.png). Same file is now vendored into the image, so the app header, the launcher and the store agree. That icon is a full-bleed square with an opaque white background rather than a transparent glyph, so it fills the frame and is clipped to the inner radius — exactly how bitcoin-ui frames its own icon — instead of being inset with padding on a dark plate. 2. Header no longer squishes at tablet widths. Both headers had a single 768px breakpoint, so between 768 and 1024 the title and description got crushed against the controls on the right (four status cards on bitcoin-ui) and overlapped. Both now use three breakpoints: fully stacked and centred below 768, logo + title on one row with the controls wrapped underneath below 1024, single row above. The app name and description are centred on mobile. 3. QR codes stay square. .conn-layout is a flex row on desktop and flex items stretch by default, so the white QR plate was being pulled to the height of the fields column and the square QR sat letterboxed in it. The plate is now a fixed square inside a black glass panel that absorbs the extra height, so the panel matches the fields and the QR stays square. 4. Buttons read as one family. The Settings button and both modal dismiss buttons used the flat .glass-button while every other button on the page used .info-card-button; they now all use the latter, via new .compact (inline) and .icon-only (square) variants so the shared style works at button size rather than only as a full-width card. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ba493fb0fc
commit
2e68384234
@@ -208,6 +208,21 @@
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* Same treatment, sized for a normal inline button rather than a
|
||||
full-width card, so every button on the page reads as one family
|
||||
instead of the flat glass-button used for one-off controls. */
|
||||
.info-card-button.compact {
|
||||
width: auto; display: inline-flex; align-items: center; justify-content: center;
|
||||
gap: 0.5rem; padding: 0.65rem 1rem; text-align: center;
|
||||
font-size: 0.875rem; font-weight: 500;
|
||||
}
|
||||
/* Square variant for modal dismiss buttons. */
|
||||
.info-card-button.icon-only {
|
||||
width: 2.75rem; height: 2.75rem; padding: 0; line-height: 1;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
font-size: 1.25rem; font-weight: 500;
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
@@ -444,6 +459,37 @@
|
||||
.number-update {
|
||||
animation: numberPulse 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
/* ── App header ──────────────────────────────────────────────────
|
||||
Three breakpoints, because the four status cards need real width
|
||||
before they can sit beside the title:
|
||||
< 768px fully stacked, identity centred, cards stacked
|
||||
< 1024px logo + title on one row, cards wrap underneath
|
||||
(this is the width where the title used to squish
|
||||
against the cards and overlap)
|
||||
>= 1024px single row */
|
||||
.app-header { display: flex; flex-direction: column; align-items: center; gap: 1rem; }
|
||||
.app-header-id { display: flex; flex-direction: column; align-items: center; gap: 1rem; width: 100%; min-width: 0; }
|
||||
.app-header-text { min-width: 0; width: 100%; text-align: center; }
|
||||
.app-header-actions {
|
||||
display: flex; flex-direction: column; gap: 0.75rem;
|
||||
width: 100%; align-items: stretch;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.app-header-actions { flex-direction: row; flex-wrap: wrap; justify-content: center; align-items: center; }
|
||||
.app-header-actions > * { flex: 1 1 auto; min-width: 10rem; }
|
||||
.app-header-actions > button { flex: 0 0 auto; min-width: 0; }
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.app-header-id { flex-direction: row; align-items: center; gap: 1.5rem; }
|
||||
.app-header-text { text-align: left; }
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.app-header { flex-direction: row; align-items: center; gap: 1.5rem; }
|
||||
.app-header-actions { width: auto; flex-shrink: 0; justify-content: flex-end; gap: 1rem; }
|
||||
.app-header-actions > * { flex: 0 0 auto; min-width: 0; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -455,7 +501,8 @@
|
||||
<div class="container">
|
||||
<!-- Header - Glass card with logo and node info -->
|
||||
<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">
|
||||
<div class="app-header">
|
||||
<div class="app-header-id">
|
||||
<!-- Logo - Top Left -->
|
||||
<div class="flex-shrink-0">
|
||||
<div class="logo-gradient-border">
|
||||
@@ -471,13 +518,14 @@
|
||||
</div>
|
||||
|
||||
<!-- Title and Description -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex-1 min-w-0 app-header-text">
|
||||
<h1 id="implName" class="text-3xl font-bold text-white mb-2">Bitcoin Node</h1>
|
||||
<p id="implTagline" class="text-white/70">Detecting implementation…</p>
|
||||
</div>
|
||||
</div><!-- /app-header-id -->
|
||||
|
||||
<!-- Node Status Info - Compact on Desktop -->
|
||||
<div class="w-full md:w-auto flex flex-col md:flex-row gap-3 md:gap-4 mt-4 md:mt-0">
|
||||
<div class="app-header-actions">
|
||||
<div class="info-card flex items-center gap-3">
|
||||
<div class="relative">
|
||||
<div class="w-3 h-3 rounded-full bg-green-400"></div>
|
||||
@@ -521,7 +569,7 @@
|
||||
|
||||
<button
|
||||
onclick="openSettings()"
|
||||
class="px-4 py-3 glass-button rounded-lg text-sm font-medium"
|
||||
class="info-card-button compact"
|
||||
>
|
||||
Settings
|
||||
</button>
|
||||
@@ -813,8 +861,10 @@
|
||||
</div>
|
||||
|
||||
<div class="conn-layout">
|
||||
<div class="qr-box" id="rpcQrBox">
|
||||
<div style="color:#999;font-size:12px;text-align:center;padding:2rem">Loading…</div>
|
||||
<div class="qr-panel">
|
||||
<div class="qr-box" id="rpcQrBox">
|
||||
<div style="color:#999;font-size:12px;text-align:center;padding:2rem">Loading…</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conn-fields">
|
||||
<div>
|
||||
@@ -866,8 +916,10 @@
|
||||
<h2 class="text-xl font-semibold text-white mb-1">P2P Details</h2>
|
||||
<p class="text-white/70 text-sm mb-4">Let another Bitcoin node peer directly with this one.</p>
|
||||
<div class="conn-layout">
|
||||
<div class="qr-box" id="p2pQrBox">
|
||||
<div style="color:#999;font-size:12px;text-align:center;padding:2rem">Loading…</div>
|
||||
<div class="qr-panel">
|
||||
<div class="qr-box" id="p2pQrBox">
|
||||
<div style="color:#999;font-size:12px;text-align:center;padding:2rem">Loading…</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conn-fields">
|
||||
<div>
|
||||
@@ -951,7 +1003,7 @@
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-[1fr_auto] gap-3">
|
||||
<input id="relayTorInput" class="w-full px-3 py-2 rounded-lg bg-black/40 border border-white/10 text-sm text-white placeholder-white/35" placeholder="http://exampleonion.onion/">
|
||||
<button class="glass-button px-4 py-2 rounded-lg text-sm font-medium" onclick="createRelayTorService()">Create Tor</button>
|
||||
<button class="info-card-button compact" onclick="createRelayTorService()">Create Tor</button>
|
||||
</div>
|
||||
<button class="gradient-button px-4 py-2 rounded-lg text-sm font-medium" onclick="saveRelaySettings()">Save Sharing Settings</button>
|
||||
</div>
|
||||
@@ -961,7 +1013,7 @@
|
||||
<select id="relayPeerSelect" class="w-full px-3 py-2 rounded-lg bg-black/40 border border-white/10 text-sm text-white" onchange="saveRelaySettings()">
|
||||
<option value="">No trusted nodes available</option>
|
||||
</select>
|
||||
<button id="relayRequestButton" class="glass-button px-4 py-2 rounded-lg text-sm font-medium" onclick="requestPeerRelay()">Request Access</button>
|
||||
<button id="relayRequestButton" class="info-card-button compact" onclick="requestPeerRelay()">Request Access</button>
|
||||
</div>
|
||||
<textarea id="relayRequestMessage" class="w-full px-3 py-2 rounded-lg bg-black/40 border border-white/10 text-sm text-white placeholder-white/35 min-h-[5rem]" placeholder="Optional note for the peer"></textarea>
|
||||
<div class="p-3 bg-white/5 rounded-lg">
|
||||
@@ -989,7 +1041,7 @@
|
||||
<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">Node Settings</h2>
|
||||
<button onclick="closeSettings()" class="glass-button px-3 py-2 rounded-lg text-xl font-medium">×</button>
|
||||
<button onclick="closeSettings()" class="info-card-button icon-only" aria-label="Close">×</button>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<div class="p-3 bg-white/5 rounded-lg">
|
||||
@@ -1021,7 +1073,7 @@
|
||||
<div class="glass-card p-6 max-w-4xl 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">Node Logs</h2>
|
||||
<button onclick="closeLogs()" class="glass-button px-3 py-2 rounded-lg text-xl font-medium">×</button>
|
||||
<button onclick="closeLogs()" class="info-card-button icon-only" aria-label="Close">×</button>
|
||||
</div>
|
||||
<div class="bg-black/40 rounded-lg p-4 font-mono text-xs text-white/80 whitespace-pre-wrap break-all" id="logsContent">
|
||||
Loading logs...
|
||||
@@ -1164,8 +1216,8 @@
|
||||
: 'text-yellow-300';
|
||||
const actions = req.direction === 'incoming' && req.status === 'pending'
|
||||
? `<div class="flex gap-2 mt-3">
|
||||
<button class="glass-button px-3 py-2 rounded-lg text-xs font-medium" onclick="approveRelayRequest('${escapeHtml(req.id)}')">Approve</button>
|
||||
<button class="glass-button px-3 py-2 rounded-lg text-xs font-medium" onclick="rejectRelayRequest('${escapeHtml(req.id)}')">Reject</button>
|
||||
<button class="info-card-button compact" style="font-size:0.75rem;padding:0.45rem 0.8rem" onclick="approveRelayRequest('${escapeHtml(req.id)}')">Approve</button>
|
||||
<button class="info-card-button compact" style="font-size:0.75rem;padding:0.45rem 0.8rem" onclick="rejectRelayRequest('${escapeHtml(req.id)}')">Reject</button>
|
||||
</div>`
|
||||
: '';
|
||||
return `<div class="p-3 bg-white/5 rounded-lg">
|
||||
|
||||
@@ -10,7 +10,10 @@ RUN mkdir -p /usr/share/nginx/html/assets/img/app-icons && \
|
||||
mkdir -p /usr/share/nginx/html/assets/img
|
||||
|
||||
# Copy assets
|
||||
COPY lnd.svg /usr/share/nginx/html/assets/img/app-icons/
|
||||
# Canonical app icon — the same file the app store / My Apps renders
|
||||
# (neode-ui/public/assets/img/app-icons/lnd.png, referenced by the signed
|
||||
# catalog as /assets/img/app-icons/lnd.png). Keep these in sync.
|
||||
COPY lnd.png /usr/share/nginx/html/assets/img/app-icons/
|
||||
COPY bg-web5.jpg /usr/share/nginx/html/assets/img/
|
||||
COPY bg-intro.jpg /usr/share/nginx/html/assets/img/
|
||||
|
||||
|
||||
+76
-28
@@ -80,6 +80,13 @@
|
||||
-webkit-mask-composite: xor; mask-composite: exclude; pointer-events: none; transition: all 0.3s ease;
|
||||
}
|
||||
.info-card-button > * { position: relative; z-index: 2; }
|
||||
/* Same treatment, sized for a normal inline button rather than a
|
||||
full-width card, so every button on the page reads as one family. */
|
||||
.info-card-button.compact {
|
||||
width: auto; display: inline-flex; align-items: center; justify-content: center;
|
||||
gap: 0.5rem; padding: 0.65rem 1rem; text-align: center;
|
||||
font-size: 0.875rem; font-weight: 500;
|
||||
}
|
||||
.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);
|
||||
@@ -108,17 +115,44 @@
|
||||
}
|
||||
.gradient-button:active { transform: translateY(1px); }
|
||||
|
||||
/* Logo — LND purple gradient ring */
|
||||
/* Logo — LND purple gradient ring. The app-store icon is a full-bleed
|
||||
square with an opaque white background (not a transparent glyph), so
|
||||
it fills the frame and gets clipped to the inner radius, exactly the
|
||||
way bitcoin-ui frames its icon. */
|
||||
.logo-gradient-border {
|
||||
position: relative; border-radius: 16px; padding: 3px;
|
||||
background: linear-gradient(135deg, rgba(167,139,250,0.85) 0%, rgba(0,0,0,0.8) 100%);
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.5); display: inline-block;
|
||||
}
|
||||
.logo-gradient-border::after { content: ''; position: absolute; inset: 3px; border-radius: 13px; background: #0d0b16; z-index: 0; }
|
||||
.logo-gradient-border img { border-radius: 13px; display: block; position: relative; z-index: 1; width: 64px; height: 64px; object-fit: contain; padding: 6px; }
|
||||
.logo-gradient-border::after { content: ''; position: absolute; inset: 3px; border-radius: 13px; background: #fff; z-index: 0; }
|
||||
.logo-gradient-border img { border-radius: 13px; display: block; position: relative; z-index: 1; width: 64px; height: 64px; object-fit: cover; }
|
||||
|
||||
.container { max-width: 1400px; margin: 0 auto; padding: 2rem; padding-bottom: 4rem; }
|
||||
|
||||
/* ── App header ──────────────────────────────────────────────────
|
||||
Three deliberate breakpoints, because the identity block and the
|
||||
controls both need room before they sit side by side:
|
||||
< 768px fully stacked, everything centred
|
||||
< 1024px logo + text on one row, controls stacked underneath
|
||||
(this is the width where the title used to squish
|
||||
against the controls)
|
||||
>= 1024px single row */
|
||||
.app-header { display: flex; flex-direction: column; align-items: center; gap: 1rem; }
|
||||
.app-header-id { display: flex; flex-direction: column; align-items: center; gap: 1rem; width: 100%; min-width: 0; }
|
||||
.app-header-text { min-width: 0; width: 100%; text-align: center; }
|
||||
.app-header-title { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; justify-content: center; margin-bottom: 0.25rem; }
|
||||
.app-header-actions { display: flex; align-items: center; gap: 0.75rem; justify-content: center; width: 100%; flex-wrap: wrap; }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.app-header-id { flex-direction: row; align-items: center; }
|
||||
.app-header-text { text-align: left; }
|
||||
.app-header-title { justify-content: flex-start; }
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.app-header { flex-direction: row; align-items: center; }
|
||||
.app-header-actions { width: auto; justify-content: flex-end; flex-shrink: 0; }
|
||||
}
|
||||
|
||||
/* Layout utilities */
|
||||
.flex { display: flex; } .flex-col { flex-direction: column; }
|
||||
.items-center { align-items: center; } .items-start { align-items: flex-start; } .items-end { align-items: flex-end; }
|
||||
@@ -250,7 +284,15 @@
|
||||
.conn-select:focus { border-color: rgba(255,255,255,0.25); }
|
||||
.conn-select option { background: #1a1a2e; color: white; }
|
||||
.conn-layout { display: flex; flex-direction: column; gap: 1.5rem; }
|
||||
.qr-box { flex-shrink: 0; width: 100%; max-width: 216px; aspect-ratio: 1; margin: 0 auto; background: white; border-radius: 0.75rem; padding: 0.6rem; display: flex; align-items: center; justify-content: center; }
|
||||
/* The QR plate stays square. It sits inside a glass panel that takes
|
||||
the height of the fields column, so a tall fields list no longer
|
||||
stretches the white plate into a letterboxed rectangle. */
|
||||
.qr-panel {
|
||||
flex-shrink: 0; display: flex; align-items: center; justify-content: center;
|
||||
padding: 1rem; background: rgba(0, 0, 0, 0.45);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 0.9rem;
|
||||
}
|
||||
.qr-box { width: 100%; max-width: 200px; aspect-ratio: 1; background: #fff; border-radius: 0.6rem; padding: 0.55rem; display: flex; align-items: center; justify-content: center; }
|
||||
.qr-box img { width: 100%; height: auto; display: block; image-rendering: pixelated; }
|
||||
.conn-fields { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.75rem; }
|
||||
|
||||
@@ -301,7 +343,7 @@
|
||||
.sm-grid-cols-3 { grid-template-columns: repeat(3, minmax(0,1fr)); }
|
||||
.balance-hero { grid-template-columns: repeat(3, minmax(0,1fr)); }
|
||||
.conn-layout { flex-direction: row; }
|
||||
.qr-box { margin: 0; width: 216px; }
|
||||
.qr-box { width: 200px; }
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md-grid-cols-2 { grid-template-columns: repeat(2, minmax(0,1fr)); }
|
||||
@@ -342,30 +384,32 @@
|
||||
<div class="container">
|
||||
<!-- ── Header ──────────────────────────────────────────────────── -->
|
||||
<div class="glass-card p-6 mb-6">
|
||||
<div class="flex flex-col md-flex-row items-center gap-4">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="logo-gradient-border">
|
||||
<img src="assets/img/app-icons/lnd.svg" alt="LND" onerror="this.style.display='none'" />
|
||||
<div class="app-header">
|
||||
<div class="app-header-id">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="logo-gradient-border">
|
||||
<img src="assets/img/app-icons/lnd.png" alt="LND" onerror="this.style.display='none'" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-header-text flex-1">
|
||||
<div class="app-header-title">
|
||||
<h1 class="text-2xl font-bold text-white">LND</h1>
|
||||
<span class="pill" id="headerStatusPill">
|
||||
<span class="status-dot-sm bg-grey" id="headerStatusDot"></span>
|
||||
<span id="headerStatusText">Connecting…</span>
|
||||
</span>
|
||||
<span class="pill" id="headerNetworkPill" style="display:none"></span>
|
||||
</div>
|
||||
<p class="text-white-70 text-sm" id="headerAlias">Lightning Network Daemon for instant Bitcoin payments</p>
|
||||
<p class="text-xs text-white-40 mt-1 font-mono truncate" id="headerPubkey">—</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0 w-full">
|
||||
<div class="flex items-center gap-3 flex-wrap mb-1">
|
||||
<h1 class="text-2xl font-bold text-white">LND</h1>
|
||||
<span class="pill" id="headerStatusPill">
|
||||
<span class="status-dot-sm bg-grey" id="headerStatusDot"></span>
|
||||
<span id="headerStatusText">Connecting…</span>
|
||||
</span>
|
||||
<span class="pill" id="headerNetworkPill" style="display:none"></span>
|
||||
</div>
|
||||
<p class="text-white-70 text-sm" id="headerAlias">Lightning Network Daemon for instant Bitcoin payments</p>
|
||||
<p class="text-xs text-white-40 mt-1 font-mono truncate" id="headerPubkey">—</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 flex-shrink-0">
|
||||
<div class="app-header-actions">
|
||||
<div class="unit-switch" role="group" aria-label="Display unit">
|
||||
<button id="unitSats" class="active" onclick="setUnit('sats')">sats</button>
|
||||
<button id="unitBtc" onclick="setUnit('btc')">BTC</button>
|
||||
</div>
|
||||
<button onclick="refreshAll()" class="glass-button flex items-center gap-2 px-4 py-2-5 rounded-lg text-sm font-medium" id="refreshBtn" title="Refresh">
|
||||
<button onclick="refreshAll()" class="info-card-button compact" id="refreshBtn" title="Refresh">
|
||||
<svg style="width:1.1rem;height:1.1rem" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" id="refreshIcon">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
@@ -751,8 +795,10 @@
|
||||
</div>
|
||||
|
||||
<div class="conn-layout" id="connDisplay">
|
||||
<div class="qr-box" id="lndQrBox">
|
||||
<div style="color:#999;font-size:12px;text-align:center;padding:2rem">Loading…</div>
|
||||
<div class="qr-panel">
|
||||
<div class="qr-box" id="lndQrBox">
|
||||
<div style="color:#999;font-size:12px;text-align:center;padding:2rem">Loading…</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conn-fields">
|
||||
<div>
|
||||
@@ -795,8 +841,10 @@
|
||||
<h2 class="text-xl font-semibold text-white mb-1">Node ID</h2>
|
||||
<p class="text-white-70 text-sm mb-4">Share this so other nodes can open a channel with you.</p>
|
||||
<div class="conn-layout">
|
||||
<div class="qr-box" id="nodeIdQrBox">
|
||||
<div style="color:#999;font-size:12px;text-align:center;padding:2rem">—</div>
|
||||
<div class="qr-panel">
|
||||
<div class="qr-box" id="nodeIdQrBox">
|
||||
<div style="color:#999;font-size:12px;text-align:center;padding:2rem">—</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conn-fields">
|
||||
<div>
|
||||
@@ -896,7 +944,7 @@
|
||||
<div class="glass-card p-6">
|
||||
<div class="flex justify-between items-center gap-3 mb-3">
|
||||
<h3 class="text-lg font-semibold text-white">Node Logs</h3>
|
||||
<button onclick="loadLogs()" class="glass-button px-3 py-1-5 rounded-lg text-xs font-medium">Refresh</button>
|
||||
<button onclick="loadLogs()" class="info-card-button compact" style="font-size:0.75rem;padding:0.45rem 0.8rem">Refresh</button>
|
||||
</div>
|
||||
<div class="logs-box" id="logsContent">Open this tab to load logs.</div>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
Reference in New Issue
Block a user