README: add portal thumbnail
Kim committed
Oct 30, 2025 at 11:36 UTC
d82bf216cfc92465a03c818a0c9cf9672763cb8c
3 files changed
+13
-7
README.md
+3
-1
@@ -1,5 +1,7 @@
1
# PORTAL — Publicly Open Relay To Access Localhost
2
-> Run locally, host everywhere!
2
+<p align="center">
3
+ <img src="portal.jpg" alt="Portal logo" width="540" />
4
+</p>
5
6
Portal is a secure, encrypted relay service that enables end-to-end encrypted communication between clients through a central relay server. It provides mutual authentication, forward secrecy, and secure connection management with cryptographic identity verification.
7
cmd/relay-server/view.go
+10
-6
@@ -333,7 +333,7 @@ var serverTmpl = template.Must(template.New("admin-index").Parse(`<!doctype html
333
body { margin:0; background:var(--bg); color:var(--ink); font-family:sans-serif; font-size:16px; line-height:1.6 }
334
.wrap { max-width: 980px; margin: 0 auto; padding: 32px 20px }
335
header { display:flex; align-items:center; gap:16px; justify-content:space-between; padding: 20px 24px; background:var(--panel); border:1px solid var(--line); border-radius: 14px }
336
- .brand { display:flex; align-items:center; height:36px; font-weight:800; font-size:18px; letter-spacing:.2px }
336
+ .brand { display:flex; align-items:center; height:36px; font-weight:800; font-size:22px; letter-spacing:.2px }
337
.status { color:var(--ok); font-weight:700 }
338
.app-left { display:flex; align-items:center; gap:12px }
339
main { margin-top: 22px }
@@ -342,6 +342,8 @@ var serverTmpl = template.Must(template.New("admin-index").Parse(`<!doctype html
342
.card { background:var(--panel); border:1px solid var(--line); border-radius:14px; padding:16px; display:flex; flex-direction:column; box-shadow: 0 1px 2px rgba(15,23,42,0.06); transition: box-shadow .2s ease, transform .2s ease; text-decoration:none; color:inherit; cursor:pointer; min-height: 180px }
343
.card:hover { box-shadow: 0 6px 20px rgba(15,23,42,0.12); transform: translateY(-2px) }
344
.card-head { display:flex; align-items:center; gap:10px; margin-bottom:8px }
345
+ .chevron { margin-left:auto; width:16px; height:16px; color:var(--muted); opacity:.9; transition: transform .2s ease, color .2s ease, opacity .2s ease }
346
+ .card:hover .chevron { transform: translateX(2px); color:var(--primary); opacity:1 }
347
.status-dot { width:10px; height:10px; border-radius:999px; background:var(--muted) }
348
.status-dot.ok { background: var(--ok) }
349
.status-dot.bad { background: var(--bad) }
@@ -357,6 +359,7 @@ var serverTmpl = template.Must(template.New("admin-index").Parse(`<!doctype html
359
.btn { display:inline-block; background:var(--primary); color:#fff; text-decoration:none; border-radius:8px; padding:6px 10px; font-weight:700; font-size:13px; line-height:1; margin-top:6px }
360
.btn.outline { background:transparent; color:var(--primary); border:1px solid var(--primary) }
361
.actions { margin-top:auto; display:flex; justify-content:flex-end }
362
+ .card:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px }
363
/* Google-like search bar */
364
.searchbar { width: 320px; max-width: 50vw; margin: 0; display:flex; align-items:center; gap:10px; height:36px; padding:0 12px; border:1px solid var(--line); border-radius:999px; background:#fff }
365
.searchbar:focus-within { box-shadow: 0 1px 6px rgba(32,33,36,.28); border-color:#dfe1e5 }
@@ -396,23 +399,24 @@ var serverTmpl = template.Must(template.New("admin-index").Parse(`<!doctype html
399
<path d="M12 .5C5.73.5.5 5.73.5 12c0 5.08 3.29 9.37 7.86 10.88.58.1.79-.25.79-.56 0-.27-.01-1.16-.02-2.11-3.2.69-3.88-1.39-3.88-1.39-.53-1.35-1.29-1.71-1.29-1.71-1.05-.72.08-.7.08-.7 1.16.08 1.78 1.19 1.78 1.19 1.03 1.77 2.7 1.26 3.36.96.1-.75.4-1.26.73-1.55-2.56-.29-5.26-1.28-5.26-5.72 0-1.26.45-2.3 1.19-3.11-.12-.29-.52-1.45.11-3.02 0 0 .98-.31 3.2 1.19.93-.26 1.94-.39 2.94-.39s2.01.13 2.95.39c2.22-1.5 3.2-1.19 3.2-1.19.63 1.57.23 2.73.12 3.02.74.81 1.19 1.85 1.19 3.11 0 4.45-2.7 5.43-5.28 5.72.41.36.77 1.07.77 2.16 0 1.56-.01 2.81-.01 3.19 0 .31.21.67.8.56C20.22 21.36 23.5 17.08 23.5 12 23.5 5.73 18.27.5 12 .5Z"/>
400
</svg>
401
</a>
399
- <div class="counter" aria-label="Active clients" title="Active clients">
402
+ <div class="counter" aria-label="Active devices" title="Active devices">
403
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
401
- <path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5s-3 1.34-3 3 1.34 3 3 3Zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5 5 6.34 5 8s1.34 3 3 3Zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5C18 14.17 13.33 13 11 13Zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h4v-2.5c0-2.33-4.67-3.5-7-3.5Z"/>
404
+ <circle cx="12" cy="12" r="6"/>
405
</svg>
406
<span>{{len .Rows}}</span>
407
</div>
408
</div>
409
</header>
410
<main>
408
-
409
-
411
<div class="grid">
412
{{range .Rows}}
412
- <a class="card {{if .Connected}}connected{{else}}disconnected{{end}}" id="peer-{{.Peer}}" data-peer="{{.Peer}}" data-name="{{.Name}}" href="{{.Link}}">
413
+ <a class="card {{if .Connected}}connected{{else}}disconnected{{end}}" id="peer-{{.Peer}}" data-peer="{{.Peer}}" data-name="{{.Name}}" href="{{.Link}}" title="{{if .Name}}{{.Name}}{{else}}(unnamed){{end}} 열기">
414
<div class="card-head">
415
<span class="status-dot {{if .Connected}}ok{{else}}bad{{end}}"></span>
416
<div class="title">{{if .Name}}{{.Name}}{{else}}(unnamed){{end}}</div>
417
+ <svg class="chevron" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
418
+ <path d="M9 6l6 6-6 6"/>
419
+ </svg>
420
</div>
421
<div class="muted">Last seen: {{.LastSeen}}</div>
422
</a>
portal.jpg
Binary files /dev/null and b/portal.jpg differ