Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ab7b458a4 |
@@ -1,99 +0,0 @@
|
|||||||
# Mobile readability fixes — `index.html`
|
|
||||||
|
|
||||||
Base: commit `3dcd822` (`bitcoinafterlife/bal-welist-website`, `main`).
|
|
||||||
|
|
||||||
**Scope:** header, filter bar and server list only. CSS-only — the JavaScript is
|
|
||||||
byte-for-byte unchanged. Desktop rendering is unaffected: every change sits
|
|
||||||
inside a `@media (max-width: ...)` block, except one line on `body` (item 7).
|
|
||||||
|
|
||||||
The "Add your Will Executor" panel, the invoice and the QR code were left
|
|
||||||
untouched on purpose, since payments are expected to be made from a desktop
|
|
||||||
browser.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Chain selector overflowed the viewport
|
|
||||||
|
|
||||||
**Before:** `.seg` was an `inline-flex` row of four buttons (Bitcoin, Testnet,
|
|
||||||
Testnet4, Regtest) with no wrapping — roughly 340px wide. On a 360–390px phone,
|
|
||||||
minus page padding, it pushed past the right edge and made the whole page scroll
|
|
||||||
horizontally.
|
|
||||||
|
|
||||||
**After:** below 600px the selector becomes a full-width 2×2 grid. Inner
|
|
||||||
dividers are recalculated with `:nth-child` so the borders stay correct in the
|
|
||||||
new arrangement.
|
|
||||||
|
|
||||||
## 2. Touch targets too small
|
|
||||||
|
|
||||||
Chain buttons were ~33px tall. Raised to `min-height: 44px` (the standard
|
|
||||||
minimum for a finger), applied to the chain buttons, the Tor toggle row and the
|
|
||||||
JSON link.
|
|
||||||
|
|
||||||
## 3. Nested scrolling in the server list
|
|
||||||
|
|
||||||
**Before:** `.mobile-list` carried `max-height: 350px; overflow: auto`, so on a
|
|
||||||
phone the list was a small window scrolling inside a page that also scrolls —
|
|
||||||
the two gestures fight each other.
|
|
||||||
|
|
||||||
**After:** the height cap is removed below 860px; the list flows with the page.
|
|
||||||
The desktop table (`.table-card`) keeps its 350px cap unchanged.
|
|
||||||
|
|
||||||
## 4. Long URLs broke out of the cards
|
|
||||||
|
|
||||||
Server URLs contain no spaces, so the browser cannot break them and they
|
|
||||||
overflowed the card. Added `overflow-wrap: anywhere`, plus `min-width: 0` on the
|
|
||||||
flex parent — without it a flex child defaults to `min-width: auto` and refuses
|
|
||||||
to shrink below its content width.
|
|
||||||
|
|
||||||
The JS truncation at 40 characters is unchanged.
|
|
||||||
|
|
||||||
## 5. Uneven card grid
|
|
||||||
|
|
||||||
The five fields (Score, Wins, Fee, Version, Height) sat in two fixed columns,
|
|
||||||
leaving Height alone on a half-empty row. Switched to
|
|
||||||
`repeat(auto-fit, minmax(84px, 1fr))`, which fits as many columns as the width
|
|
||||||
allows.
|
|
||||||
|
|
||||||
## 6. iOS auto-zoom on the search field
|
|
||||||
|
|
||||||
iOS Safari zooms the entire page when a focused input has a font size below
|
|
||||||
16px, and the user then has to pinch back out. The filter field is raised to
|
|
||||||
16px below 600px and made full width.
|
|
||||||
|
|
||||||
## 7. Text inflation in landscape
|
|
||||||
|
|
||||||
Added `-webkit-text-size-adjust: 100%` to `body`. iOS enlarges font sizes when a
|
|
||||||
phone is rotated to landscape, which distorted the header and the cards. This is
|
|
||||||
the only change outside a media query; it has no effect on desktop browsers.
|
|
||||||
|
|
||||||
## 8. Tighter spacing below 480px
|
|
||||||
|
|
||||||
New breakpoint reducing padding and type size where space is scarce:
|
|
||||||
|
|
||||||
| Element | Before | After |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `.wrap` side padding | 20px | 14px (+12px usable width per row) |
|
|
||||||
| `header.hero` padding | 48px 0 28px | 28px 0 20px |
|
|
||||||
| `.brand h1` | 24px | 20px |
|
|
||||||
| Logo mark | 46px | 40px |
|
|
||||||
| `.stat .n` | 26px | 22px |
|
|
||||||
| `.intro-card` padding | 28px 32px | 20px 18px |
|
|
||||||
| `.info-block` padding | 22px 24px | 18px 16px |
|
|
||||||
| `.srv` padding | 16px | 14px |
|
|
||||||
| Footer | space-between, wrapping | single column |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Verification performed
|
|
||||||
|
|
||||||
- CSS braces balanced (148 open / 148 close).
|
|
||||||
- All 32 element IDs referenced by the JavaScript still present in the markup.
|
|
||||||
- Tag balance checked for `div`, `section`, `header`, `footer`, `main`, `table`,
|
|
||||||
`style`, `script`.
|
|
||||||
- Media queries present in order: 860px, 600px, 480px, `prefers-reduced-motion`.
|
|
||||||
|
|
||||||
## Not yet verified
|
|
||||||
|
|
||||||
Rendering was not tested on a physical device. Worth a quick check on one iPhone
|
|
||||||
and one Android phone before merging, in particular the 2×2 chain selector at
|
|
||||||
320px width (iPhone SE, the narrowest screen still in common use).
|
|
||||||
153
README.md
153
README.md
@@ -1,153 +1,2 @@
|
|||||||
# Server Status: Complete Explanation and Reference Guide
|
welist html
|
||||||
|
|
||||||
This page explains all the **Will Executor server health statuses** used throughout the BAL Web Interface. Each status type provides critical information about server operation, health checks, and potential issues for administrators monitoring the Bitcoin After Life network.
|
|
||||||
|
|
||||||
## Status Code Overview
|
|
||||||
|
|
||||||
| Status | Description | When It Occurs |
|
|
||||||
|--------|-------------|----------------|
|
|
||||||
| `OK` | Health check passed | Server is online, functioning normally |
|
|
||||||
| `TIMEOUT: <message>` | Connection timeout | Server response took too long |
|
|
||||||
| `CONNECTION: <message>` | Connection error | Network connectivity issues |
|
|
||||||
| `STATUS: <code>` | HTTP error (from server) | Server returned non-200 status |
|
|
||||||
| `WRONG REPLY: <message>` | Invalid JSON response | Server response couldn't be parsed |
|
|
||||||
| `REQUEST: <message>` | Request error | Client-side request failed |
|
|
||||||
| `BODY: <message>` | Body read error | Failed to read response body |
|
|
||||||
| `DECODE: <message>` | Decode error | Failed to decode response content |
|
|
||||||
| `KO` | Generic error | Unknown or unspecified error |
|
|
||||||
|
|
||||||
## Status Types Explained
|
|
||||||
|
|
||||||
### 🟢 Good Statuses
|
|
||||||
**`OK`** - Everything is functioning correctly:
|
|
||||||
- Server is online and responding
|
|
||||||
- Health check completed successfully
|
|
||||||
- Ready for new registration/processing
|
|
||||||
|
|
||||||
### 🟡 Warning/Moderate Issues
|
|
||||||
**`TIMEOUT: <message>`** - Connection timed out:
|
|
||||||
- Server took too than expected to respond
|
|
||||||
- Could indicate server overload or network issues
|
|
||||||
- Requires attention but server may still be functional
|
|
||||||
|
|
||||||
**`CONNECTION: <message>`** - Connection error:
|
|
||||||
- Network connectivity problems
|
|
||||||
- Server might be reachable but cannot communicate
|
|
||||||
- Potential infrastructure or configuration issue
|
|
||||||
|
|
||||||
**`STATUS: <code>`** - HTTP error:
|
|
||||||
- Server responded with an error code (4xx or 5xx)
|
|
||||||
- Could indicate misconfiguration, maintenance, or issues |
|
|
||||||
- Different error codes mean different things
|
|
||||||
|
|
||||||
### 🔴 Critical Issues
|
|
||||||
**`WRONG REPLY`** - Invalid JSON response:
|
|
||||||
- Server response format is incorrect
|
|
||||||
- May indicate server software bug or corruption
|
|
||||||
- Prevents proper data processing
|
|
||||||
|
|
||||||
**`REQUEST`** - Request error:
|
|
||||||
- Client request failed
|
|
||||||
- Network or protocol issue
|
|
||||||
- Server may be unreachable
|
|
||||||
|
|
||||||
**`BODY`** - Body read error:
|
|
||||||
- Failed to read response body after successful connection
|
|
||||||
- Could indicate server processing issues
|
|
||||||
- Data corruption or transmission problems
|
|
||||||
|
|
||||||
**`DECODE`** - Decode error:
|
|
||||||
- Failed to decode response content
|
|
||||||
- Encoding mismatch or corrupted data
|
|
||||||
- Prevents proper data interpretation
|
|
||||||
|
|
||||||
**`KO`** - Generic error:
|
|
||||||
- Unknown or unspecified error
|
|
||||||
- Fallback for all other unidentified issues
|
|
||||||
- Indicates something went wrong but exact cause unknown
|
|
||||||
|
|
||||||
## Example Status Messages
|
|
||||||
|
|
||||||
### Normal Operations
|
|
||||||
```
|
|
||||||
Status: OK
|
|
||||||
Meaning: Health check passed
|
|
||||||
Display: Server is healthy and ready
|
|
||||||
```
|
|
||||||
|
|
||||||
### Network/Time Issues
|
|
||||||
```
|
|
||||||
Status: TIMEOUT: Connection timed out after 30 seconds
|
|
||||||
Meaning: Server took too long to respond
|
|
||||||
Display: "Connection timeout (Connection timed out after 30 seconds)"
|
|
||||||
```
|
|
||||||
|
|
||||||
### HTTP Errors
|
|
||||||
```
|
|
||||||
Status: STATUS: 500
|
|
||||||
Meaning: Server returned HTTP 500 Internal Server Error
|
|
||||||
Display: "HTTP error (HTTP error)"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Response Processing Issues
|
|
||||||
```
|
|
||||||
Status: WRONG REPLY: Invalid JSON
|
|
||||||
Meaning: Server response could not be parsed as JSON
|
|
||||||
Display: "Invalid JSON response (Invalid JSON)"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Practical Usage Guide
|
|
||||||
|
|
||||||
### For Server Administrators
|
|
||||||
- **OK**: Check server resources and logs if needed
|
|
||||||
- **TIMEOUT/CONNECTION**: Check server network connectivity
|
|
||||||
- **STATUS**: Review server logs and restart if needed
|
|
||||||
- **WRONG REPLY/DECODE/REQUEST**: Investigate server software issues
|
|
||||||
- **KO**: Review server errors and restart
|
|
||||||
|
|
||||||
### For Users
|
|
||||||
- **OK**: Server is working correctly
|
|
||||||
- **TIMEOUT/CONNECTION**: Temporary issue, try again later
|
|
||||||
- **STATUS**: Server may be down for maintenance
|
|
||||||
- **Other errors**: Server issue, contact support if persistent
|
|
||||||
|
|
||||||
## Monitoring Tips
|
|
||||||
|
|
||||||
1. **Continuous Monitoring**: Regular checks of status codes help detect issues early
|
|
||||||
2. **Alert Setup**: Configure alerts for non-OK statuses
|
|
||||||
3. **Log Analysis**: Correlate status codes with server logs
|
|
||||||
4. **Capacity Planning**: Address persistent timeouts/connections with infrastructure improvements
|
|
||||||
|
|
||||||
## Status Processing Logic
|
|
||||||
|
|
||||||
### Status Code Processing
|
|
||||||
```javascript
|
|
||||||
const statusMap = {
|
|
||||||
'OK': 'Health check passed',
|
|
||||||
'TIMEOUT': 'Connection timeout',
|
|
||||||
'CONNECTION': 'Connection error',
|
|
||||||
'STATUS': 'HTTP error',
|
|
||||||
'WRONG REPLY': 'Invalid JSON response',
|
|
||||||
'REQUEST': 'Request error',
|
|
||||||
'BODY': 'Body read error',
|
|
||||||
'DECODE': 'Decode error',
|
|
||||||
'KO': 'Generic error'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Extract base status from status message
|
|
||||||
const st = (data.status||'').split(':')[0];
|
|
||||||
|
|
||||||
// Display only the description (e.g., "Connection timeout" instead of "TIMEOUT: Connection timeout")
|
|
||||||
statusDisplay = statusMap[st] || '—';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Related Documentation
|
|
||||||
|
|
||||||
For more details on server setup, monitoring, and troubleshooting, refer to:
|
|
||||||
- [Server Administration Guide](./administration)
|
|
||||||
- [Status Monitoring Setup](./monitoring-setup)
|
|
||||||
- [Error Resolution Handbook](./error-resolve)
|
|
||||||
|
|
||||||
## Additional Information
|
|
||||||
|
|
||||||
This status system provides a clear, human-readable way to understand server health at a glance, making it easier for administrators to quickly identify and respond to issues.
|
|
||||||
42
bal.js
42
bal.js
@@ -76,40 +76,18 @@ async function fetchDataAndPopulateTable(url,params,fnPopulateTable,downloadOpt,
|
|||||||
function make_paginator(){
|
function make_paginator(){
|
||||||
document.getElementById("chainth").innerHTML = chain;
|
document.getElementById("chainth").innerHTML = chain;
|
||||||
const pag = document.getElementById("paginator");
|
const pag = document.getElementById("paginator");
|
||||||
pag.innerHTML = "";
|
|
||||||
const currentPage = paginator && paginator.page != null
|
pag.appendChild(document.createTextNode("_"));
|
||||||
? paginator.page
|
if (page > 0) {
|
||||||
: (Number(page) || 0);
|
pag.appendChild(createlink("<","?page="+paginator.prev));
|
||||||
const prevPage = paginator && paginator.prev != null
|
|
||||||
? paginator.prev
|
|
||||||
: Math.max(currentPage - 1, 0);
|
|
||||||
const nextPage = paginator && paginator.next != null
|
|
||||||
? paginator.next
|
|
||||||
: currentPage + 1;
|
|
||||||
const hasPrev = paginator && paginator.hasPrev != null
|
|
||||||
? paginator.hasPrev
|
|
||||||
: currentPage > 0;
|
|
||||||
const hasNext = paginator && paginator.hasNext != null
|
|
||||||
? paginator.hasNext
|
|
||||||
: true;
|
|
||||||
// prev (a plain, dimmed arrow on the first page), "page N", next
|
|
||||||
if (hasPrev) {
|
|
||||||
pag.appendChild(createlink("‹","?page="+prevPage));
|
|
||||||
} else {
|
|
||||||
const off = document.createElement("span");
|
|
||||||
off.className = "pg-off"; off.innerHTML = "‹";
|
|
||||||
pag.appendChild(off);
|
|
||||||
}
|
}
|
||||||
const lbl = document.createElement("span");
|
else{
|
||||||
lbl.className = "pg-label"; lbl.textContent = "page " + currentPage;
|
pag.appendChild(document.createTextNode("<"));
|
||||||
pag.appendChild(lbl);
|
|
||||||
if (hasNext) {
|
|
||||||
pag.appendChild(createlink("›","?page="+nextPage));
|
|
||||||
} else {
|
|
||||||
const off = document.createElement("span");
|
|
||||||
off.className = "pg-off"; off.innerHTML = "›";
|
|
||||||
pag.appendChild(off);
|
|
||||||
}
|
}
|
||||||
|
pag.appendChild(document.createTextNode("_"));
|
||||||
|
pag.appendChild(document.createTextNode(page));
|
||||||
|
pag.appendChild(document.createTextNode("_"));
|
||||||
|
pag.appendChild(createlink(">","?page="+paginator.next));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
660
index.html
660
index.html
@@ -4,21 +4,6 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>BAL Server List</title>
|
<title>BAL Server List</title>
|
||||||
<meta name="description" content="Public directory of Bitcoin After Life Will-Executor servers: the independent nodes that hold and broadcast time-locked Bitcoin inheritance transactions for the BAL Protocol.">
|
|
||||||
<link rel="canonical" href="https://welist.bitcoin-after.life/">
|
|
||||||
<meta property="og:type" content="website">
|
|
||||||
<meta property="og:site_name" content="Bitcoin After Life">
|
|
||||||
<meta property="og:title" content="Will-Executor Server List — Bitcoin After Life">
|
|
||||||
<meta property="og:description" content="Public directory of Bitcoin After Life Will-Executor servers: the independent nodes that hold and broadcast time-locked Bitcoin inheritance transactions for the BAL Protocol.">
|
|
||||||
<meta property="og:url" content="https://welist.bitcoin-after.life/">
|
|
||||||
<meta property="og:image" content="https://bitcoin-after.life/pitura/og-default.png">
|
|
||||||
<meta property="og:image:width" content="1200">
|
|
||||||
<meta property="og:image:height" content="630">
|
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
|
||||||
<meta name="twitter:site" content="@BitcoinAfterLif">
|
|
||||||
<meta name="twitter:title" content="Will-Executor Server List — Bitcoin After Life">
|
|
||||||
<meta name="twitter:description" content="Public directory of Bitcoin After Life Will-Executor servers: the independent nodes that hold and broadcast time-locked Bitcoin inheritance transactions for the BAL Protocol.">
|
|
||||||
<meta name="twitter:image" content="https://bitcoin-after.life/pitura/og-default.png">
|
|
||||||
<link rel="icon" href="Logo_nero.ico" type="image/x-icon">
|
<link rel="icon" href="Logo_nero.ico" type="image/x-icon">
|
||||||
<script src="qrcode.min.js"></script>
|
<script src="qrcode.min.js"></script>
|
||||||
<script src="purify.min.js"></script>
|
<script src="purify.min.js"></script>
|
||||||
@@ -55,10 +40,6 @@ body{
|
|||||||
font-family:var(--body);
|
font-family:var(--body);
|
||||||
line-height:1.5;
|
line-height:1.5;
|
||||||
-webkit-font-smoothing:antialiased;
|
-webkit-font-smoothing:antialiased;
|
||||||
/* WHY: iOS Safari inflates font sizes when a phone is rotated to landscape,
|
|
||||||
which breaks the header and the server cards. Locking the adjustment to
|
|
||||||
100% keeps the layout identical in both orientations. No effect on desktop. */
|
|
||||||
-webkit-text-size-adjust:100%;
|
|
||||||
}
|
}
|
||||||
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 20px}
|
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 20px}
|
||||||
|
|
||||||
@@ -78,15 +59,6 @@ header.hero{padding:48px 0 28px;border-bottom:1px solid var(--line)}
|
|||||||
}
|
}
|
||||||
.brand .sub{color:var(--muted);font-size:14px;margin-top:2px}
|
.brand .sub{color:var(--muted);font-size:14px;margin-top:2px}
|
||||||
.hero-stats{display:flex;gap:28px}
|
.hero-stats{display:flex;gap:28px}
|
||||||
/* Right side of the hero: the stats and the way to the Terms page (how to get
|
|
||||||
listed, yearly fee). Added 2026-11-06: the page was hard to find. */
|
|
||||||
.hero-right{display:flex;align-items:flex-end;gap:28px;flex-wrap:wrap}
|
|
||||||
.hero-cta{display:inline-flex;align-items:center;gap:10px;padding:10px 16px;border-radius:10px;
|
|
||||||
background:var(--accent);color:#fff;text-decoration:none;font-weight:600;font-size:14px;line-height:1.15;
|
|
||||||
box-shadow:0 4px 14px rgba(45,108,223,.25)}
|
|
||||||
.hero-cta:hover{filter:brightness(1.06)}
|
|
||||||
.hero-cta svg{width:22px;height:22px;flex:none}
|
|
||||||
.hero-cta small{display:block;font-weight:400;font-size:12px;opacity:.85;margin-top:2px}
|
|
||||||
.stat .n{font-family:var(--display);font-size:26px;font-weight:600;line-height:1}
|
.stat .n{font-family:var(--display);font-size:26px;font-weight:600;line-height:1}
|
||||||
.stat .l{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);margin-top:6px}
|
.stat .l{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);margin-top:6px}
|
||||||
|
|
||||||
@@ -145,8 +117,7 @@ tbody tr:hover{background:color-mix(in srgb,var(--accent-soft) 50%,transparent)}
|
|||||||
.num{font-family:var(--mono);font-variant-numeric:tabular-nums}
|
.num{font-family:var(--mono);font-variant-numeric:tabular-nums}
|
||||||
.score{font-family:var(--display);font-weight:600;font-size:15px}
|
.score{font-family:var(--display);font-weight:600;font-size:15px}
|
||||||
|
|
||||||
.add-grid{display:grid;grid-template-columns:minmax(0,1.9fr) minmax(0,1fr);gap:24px;margin:40px 0;align-items:stretch}
|
.add-grid{display:grid;grid-template-columns:1.3fr 1fr;gap:24px;margin:40px 0;align-items:start}
|
||||||
.add-grid>.panel{display:flex;flex-direction:column}
|
|
||||||
.panel{
|
.panel{
|
||||||
background:var(--panel);border:1px solid var(--line);
|
background:var(--panel);border:1px solid var(--line);
|
||||||
border-radius:var(--radius);padding:24px;box-shadow:var(--shadow);
|
border-radius:var(--radius);padding:24px;box-shadow:var(--shadow);
|
||||||
@@ -168,17 +139,6 @@ tbody tr:hover{background:color-mix(in srgb,var(--accent-soft) 50%,transparent)}
|
|||||||
.btn:disabled{opacity:.45;cursor:not-allowed;filter:none}
|
.btn:disabled{opacity:.45;cursor:not-allowed;filter:none}
|
||||||
.note{font-size:13px;color:var(--muted);margin-top:16px;line-height:1.55}
|
.note{font-size:13px;color:var(--muted);margin-top:16px;line-height:1.55}
|
||||||
.note strong{color:var(--ink)}
|
.note strong{color:var(--ink)}
|
||||||
.freshness-note{
|
|
||||||
font-size:12px;line-height:1.4;color:var(--muted);
|
|
||||||
background:var(--accent-soft);border:1px solid var(--line);border-radius:8px;
|
|
||||||
padding:7px 12px;margin:0 0 12px;
|
|
||||||
}
|
|
||||||
.freshness-note strong{color:var(--ink)}
|
|
||||||
.btn-share{
|
|
||||||
display:block;margin:0 auto 8px;font:inherit;font-size:13px;cursor:pointer;
|
|
||||||
padding:8px 16px;border-radius:8px;border:1px solid var(--line-strong);
|
|
||||||
background:var(--panel);color:var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.invoice{display:none}
|
.invoice{display:none}
|
||||||
.invoice.show{display:block}
|
.invoice.show{display:block}
|
||||||
@@ -186,18 +146,8 @@ tbody tr:hover{background:color-mix(in srgb,var(--accent-soft) 50%,transparent)}
|
|||||||
.invoice .row:last-child{border-bottom:0}
|
.invoice .row:last-child{border-bottom:0}
|
||||||
.invoice .row span:first-child{color:var(--muted)}
|
.invoice .row span:first-child{color:var(--muted)}
|
||||||
.invoice .row span:last-child{font-family:var(--mono);text-align:right;word-break:break-all}
|
.invoice .row span:last-child{font-family:var(--mono);text-align:right;word-break:break-all}
|
||||||
.invoice .row a{color:var(--accent);text-decoration:none}
|
#qrcode{margin:18px auto 4px;width:160px;height:160px}
|
||||||
.invoice .row a:hover{text-decoration:underline}
|
#qrcode img{width:160px;height:160px}
|
||||||
#qrcode{margin:18px auto 4px;width:160px}
|
|
||||||
#qrcode canvas,#qrcode img{width:160px!important;height:160px!important;max-width:100%}
|
|
||||||
#qr-overlay{
|
|
||||||
display:none;position:fixed;inset:0;z-index:999;
|
|
||||||
background:rgba(0,0,0,.7);cursor:pointer;
|
|
||||||
align-items:center;justify-content:center;
|
|
||||||
}
|
|
||||||
#qr-overlay.show{display:flex}
|
|
||||||
#qr-big{padding:24px;background:#fff;border-radius:12px}
|
|
||||||
#qr-big canvas,#qr-big img{width:280px!important;height:280px!important;max-width:90vw}
|
|
||||||
.ok-line{color:var(--ok);font-weight:600;font-size:14px;margin-bottom:14px}
|
.ok-line{color:var(--ok);font-weight:600;font-size:14px;margin-bottom:14px}
|
||||||
.err-line{color:#d32f2f;font-weight:600;font-size:14px;margin-bottom:14px}
|
.err-line{color:#d32f2f;font-weight:600;font-size:14px;margin-bottom:14px}
|
||||||
.terms-box{display:none;margin:16px 0;padding:20px;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius)}
|
.terms-box{display:none;margin:16px 0;padding:20px;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius)}
|
||||||
@@ -206,41 +156,6 @@ tbody tr:hover{background:color-mix(in srgb,var(--accent-soft) 50%,transparent)}
|
|||||||
.terms-box a.mono{font-size:14px;word-break:break-all}
|
.terms-box a.mono{font-size:14px;word-break:break-all}
|
||||||
.terms-box label{font-size:14px;cursor:pointer}
|
.terms-box label{font-size:14px;cursor:pointer}
|
||||||
.terms-box .btn{margin-top:14px}
|
.terms-box .btn{margin-top:14px}
|
||||||
.btn-row{display:flex;gap:8px;margin-top:8px}
|
|
||||||
.btn-row .btn{flex:1;margin-top:0}
|
|
||||||
[hidden]{display:none!important}
|
|
||||||
/* Add / Check tabs of the panel, and its two columns (form + side notes). */
|
|
||||||
.mode-seg{margin-bottom:18px;align-self:flex-start}
|
|
||||||
.mode-seg button{font-size:14px;padding:9px 16px;display:inline-flex;align-items:center;gap:8px}
|
|
||||||
.mode-seg button svg{width:16px;height:16px}
|
|
||||||
.mode-seg button[aria-selected="true"]{background:var(--accent-soft);color:var(--ink);font-weight:600}
|
|
||||||
.panel-body{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(0,1fr);gap:28px;align-items:start}
|
|
||||||
.panel-side{border-left:1px solid var(--line);padding-left:24px;font-size:13px;color:var(--muted);line-height:1.5}
|
|
||||||
.side-title{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--accent);font-weight:600;margin:6px 0 12px}
|
|
||||||
.mini-steps{list-style:none;margin:0 0 14px;padding:0;counter-reset:ms}
|
|
||||||
.mini-steps li{position:relative;padding:0 0 14px 34px;counter-increment:ms}
|
|
||||||
.mini-steps li::before{content:counter(ms);position:absolute;left:0;top:0;width:24px;height:24px;border-radius:50%;
|
|
||||||
background:var(--accent-soft);color:var(--accent);font-family:var(--mono);font-size:12px;display:flex;align-items:center;justify-content:center}
|
|
||||||
.mini-steps b{display:block;color:var(--ink);font-size:13.5px;margin-bottom:2px}
|
|
||||||
.side-link{color:var(--accent);text-decoration:none;font-weight:600}
|
|
||||||
/* Renewal status result */
|
|
||||||
.renewal-card{margin-top:14px;border:1px solid var(--line);border-radius:var(--radius);background:var(--bg);padding:18px 20px}
|
|
||||||
.rc-main{display:flex;flex-direction:column;gap:2px;margin-bottom:14px}
|
|
||||||
.rc-label{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}
|
|
||||||
.rc-date{font-family:var(--display);font-size:30px;font-weight:600;line-height:1.1;color:var(--ink)}
|
|
||||||
.rc-sub{font-size:13px;color:var(--muted)}
|
|
||||||
.rc-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px 18px;font-size:13px}
|
|
||||||
.rc-grid div{display:flex;flex-direction:column;gap:2px;border-top:1px solid var(--line);padding-top:8px}
|
|
||||||
.rc-grid span{color:var(--muted)}
|
|
||||||
.rc-grid b{font-family:var(--mono);font-weight:600}
|
|
||||||
.rc-link{display:inline-block;margin-top:14px;color:var(--accent);text-decoration:none;font-weight:600;font-size:13px}
|
|
||||||
.rc-link:hover{text-decoration:underline}
|
|
||||||
.rc-open{color:var(--ok)}
|
|
||||||
.side-link:hover{text-decoration:underline}
|
|
||||||
@media (max-width:1024px){
|
|
||||||
.panel-body{grid-template-columns:1fr}
|
|
||||||
.panel-side{border-left:0;padding-left:0;border-top:1px solid var(--line);padding-top:16px}
|
|
||||||
}
|
|
||||||
|
|
||||||
.intro-card{
|
.intro-card{
|
||||||
background:var(--panel);border:1px solid var(--line);
|
background:var(--panel);border:1px solid var(--line);
|
||||||
@@ -251,14 +166,6 @@ tbody tr:hover{background:color-mix(in srgb,var(--accent-soft) 50%,transparent)}
|
|||||||
.intro-card h2{font-family:var(--display);font-size:24px;margin:0 0 10px;letter-spacing:-.01em}
|
.intro-card h2{font-family:var(--display);font-size:24px;margin:0 0 10px;letter-spacing:-.01em}
|
||||||
.intro-card p{margin:0;color:var(--muted);font-size:15px;line-height:1.65;max-width:820px}
|
.intro-card p{margin:0;color:var(--muted);font-size:15px;line-height:1.65;max-width:820px}
|
||||||
|
|
||||||
.fee-disclaimer{margin:12px 0 0;color:var(--muted);font-size:13px;line-height:1.5;font-style:italic}
|
|
||||||
.fee-disclaimer a{color:var(--accent)}
|
|
||||||
.after-table{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-top:12px}
|
|
||||||
.after-table .fee-disclaimer{margin:0;flex:1 1 420px}
|
|
||||||
.add-cta{color:var(--accent);font-weight:600;white-space:nowrap}
|
|
||||||
.add-cta svg{width:16px;height:16px}
|
|
||||||
#we-panel{scroll-margin-top:16px}
|
|
||||||
|
|
||||||
.info-section{margin:48px 0}
|
.info-section{margin:48px 0}
|
||||||
.info-section h2{font-family:var(--display);font-size:22px;margin:0 0 8px}
|
.info-section h2{font-family:var(--display);font-size:22px;margin:0 0 8px}
|
||||||
.info-section .sub{color:var(--muted);font-size:15px;line-height:1.6;margin:0 0 28px;max-width:760px}
|
.info-section .sub{color:var(--muted);font-size:15px;line-height:1.6;margin:0 0 28px;max-width:760px}
|
||||||
@@ -275,11 +182,7 @@ footer a{color:var(--accent);text-decoration:none}
|
|||||||
.mobile-list{display:none}
|
.mobile-list{display:none}
|
||||||
@media (max-width:860px){
|
@media (max-width:860px){
|
||||||
.table-card{display:none}
|
.table-card{display:none}
|
||||||
/* WHY: the previous "max-height:350px;overflow:auto" turned the server list
|
.mobile-list{display:grid;gap:12px;max-height:350px;overflow:auto;padding-right:2px}
|
||||||
into a small scrollable window nested inside the page. On a touch screen
|
|
||||||
that inner scroll competes with the normal page scroll and makes the list
|
|
||||||
hard to read. On mobile the list now flows naturally with the page. */
|
|
||||||
.mobile-list{display:grid;gap:12px}
|
|
||||||
.add-grid{grid-template-columns:1fr}
|
.add-grid{grid-template-columns:1fr}
|
||||||
.info-grid{grid-template-columns:1fr}
|
.info-grid{grid-template-columns:1fr}
|
||||||
.hero-stats{gap:20px}
|
.hero-stats{gap:20px}
|
||||||
@@ -287,82 +190,9 @@ footer a{color:var(--accent);text-decoration:none}
|
|||||||
.srv{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:16px;box-shadow:var(--shadow)}
|
.srv{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:16px;box-shadow:var(--shadow)}
|
||||||
.srv .top{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;margin-bottom:12px}
|
.srv .top{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;margin-bottom:12px}
|
||||||
.srv .rk{font-family:var(--display);font-weight:600;color:var(--rank);font-size:18px}
|
.srv .rk{font-family:var(--display);font-weight:600;color:var(--rank);font-size:18px}
|
||||||
/* WHY: server URLs have no spaces, so a long URL cannot break by itself and
|
.srv .grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;font-size:13px}
|
||||||
would overflow the card. "anywhere" lets it wrap at any character.
|
|
||||||
"min-width:0" is required because a flex child defaults to min-width:auto,
|
|
||||||
which prevents it from shrinking below its content width. */
|
|
||||||
.srv .top>div{min-width:0}
|
|
||||||
.srv .top a{display:inline-block;max-width:100%;overflow-wrap:anywhere}
|
|
||||||
/* WHY: with two fixed columns the fifth field (Height) was left alone on a
|
|
||||||
half-empty row. auto-fit fills the available width with as many columns as
|
|
||||||
fit, so the five fields always line up tidily. */
|
|
||||||
.srv .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(84px,1fr));gap:10px;font-size:13px}
|
|
||||||
.srv .grid div span{display:block;color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.06em;margin-bottom:2px}
|
.srv .grid div span{display:block;color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.06em;margin-bottom:2px}
|
||||||
.srv .grid div b{font-family:var(--mono);font-weight:600;overflow-wrap:anywhere}
|
.srv .grid div b{font-family:var(--mono);font-weight:600}
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
|
||||||
Phone layout (<=600px).
|
|
||||||
Scope: header, filter bar and server list only. The "Add your Will Executor"
|
|
||||||
panel, the invoice and the QR code are intentionally left untouched, because
|
|
||||||
payments are expected to be made from a desktop browser.
|
|
||||||
--------------------------------------------------------------------------- */
|
|
||||||
@media (max-width:600px){
|
|
||||||
.controls{gap:10px}
|
|
||||||
|
|
||||||
/* WHY: the four chain buttons form a single non-wrapping row about 340px wide.
|
|
||||||
Inside a 360-390px phone screen (minus page padding) it overflowed the
|
|
||||||
viewport and produced horizontal scrolling. A 2x2 grid across the full
|
|
||||||
width removes the overflow. min-height:44px matches the recommended
|
|
||||||
minimum touch target size. */
|
|
||||||
.seg{display:grid;grid-template-columns:1fr 1fr;width:100%;border-radius:8px}
|
|
||||||
.seg button{font-size:14px;min-height:44px;padding:8px 10px;border-bottom:1px solid var(--line)}
|
|
||||||
.seg button:nth-child(2n){border-right:0} /* right column: no inner divider */
|
|
||||||
.seg button:nth-child(n+3){border-bottom:0} /* last row: no bottom divider */
|
|
||||||
|
|
||||||
/* Give the toggle and the JSON link a full-size touch area. */
|
|
||||||
.field{min-height:44px}
|
|
||||||
.btn-ghost{min-height:44px}
|
|
||||||
|
|
||||||
/* WHY: the search field takes the full width so it is comfortable to type in,
|
|
||||||
and the font is raised to 16px. Below 16px, iOS Safari automatically zooms
|
|
||||||
the whole page when the field receives focus, and the user then has to
|
|
||||||
pinch back out. 16px disables that behaviour. */
|
|
||||||
.search{flex:1 1 100%}
|
|
||||||
.search input{font-size:16px}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
|
||||||
Narrow phone layout (<=480px): tighter spacing to recover horizontal space.
|
|
||||||
--------------------------------------------------------------------------- */
|
|
||||||
@media (max-width:480px){
|
|
||||||
/* Recovers 12px of usable width on every row of the page. */
|
|
||||||
.wrap{padding:0 14px}
|
|
||||||
|
|
||||||
header.hero{padding:28px 0 20px}
|
|
||||||
.hero .wrap{gap:16px}
|
|
||||||
.brand{gap:12px}
|
|
||||||
.brand .mark{width:40px;height:40px;border-radius:10px}
|
|
||||||
.brand .mark .brand-logo{width:28px;height:28px}
|
|
||||||
.brand h1{font-size:20px}
|
|
||||||
.brand .sub{font-size:13px}
|
|
||||||
.stat .n{font-size:22px}
|
|
||||||
|
|
||||||
.controls{padding:16px 0 12px}
|
|
||||||
|
|
||||||
.srv{padding:14px}
|
|
||||||
|
|
||||||
.intro-card{padding:20px 18px;margin:22px 0 8px}
|
|
||||||
.intro-card h2{font-size:20px}
|
|
||||||
.intro-card p{font-size:14px}
|
|
||||||
|
|
||||||
.info-section{margin:32px 0}
|
|
||||||
.info-block{padding:18px 16px}
|
|
||||||
|
|
||||||
/* WHY: the four footer items were spread with space-between and wrapped into
|
|
||||||
an uneven, hard-to-read block. A single column reads cleanly on a phone. */
|
|
||||||
footer{margin-top:32px;padding:22px 0}
|
|
||||||
footer .wrap{flex-direction:column;gap:8px}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion:reduce){*{transition:none!important}}
|
@media (prefers-reduced-motion:reduce){*{transition:none!important}}
|
||||||
@@ -377,15 +207,13 @@ footer a{color:var(--accent);text-decoration:none}
|
|||||||
<div class="mark"><svg class="brand-logo" viewBox="0 0 1036 1036" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Bitcoin After Life logo"><g transform="translate(0.000000,1036.000000) scale(0.100000,-0.100000)" fill="currentColor" stroke="none"><path d="M6130 10203 c-153 -318 -283 -665 -344 -916 -61 -250 -71 -335 -71 -602 1 -223 3 -249 28 -370 52 -244 140 -460 260 -633 72 -106 225 -257 320 -316 179 -111 335 -154 777 -211 232 -30 367 -61 442 -102 76 -41 195 -164 248 -257 170 -302 311 -774 362 -1211 19 -167 16 -591 -6 -745 -68 -484 -201 -871 -436 -1270 -541 -917 -1492 -1525 -2549 -1630 -132 -13 -453 -13 -596 0 -268 24 -530 77 -757 151 -1063 350 -1871 1192 -2187 2280 -63 217 -111 495 -127 742 -7 104 -7 107 -30 107 l-24 0 0 -167 c0 -286 27 -508 95 -773 148 -587 427 -1083 854 -1523 231 -237 423 -391 703 -563 685 -420 1543 -589 2343 -462 1346 213 2458 1211 2814 2524 89 329 116 543 115 919 0 349 -23 544 -100 847 -68 273 10 632 176 799 91 92 165 104 491 77 320 -26 499 36 699 242 171 175 285 422 320 690 15 116 12 401 -6 535 -13 100 -32 200 -91 478 -3 15 -8 27 -12 27 -3 0 -21 -30 -40 -66 -20 -39 -63 -96 -105 -139 -127 -130 -287 -216 -616 -330 -297 -104 -420 -161 -551 -256 -159 -117 -268 -285 -325 -504 -36 -135 -37 -312 -4 -500 12 -71 24 -132 26 -134 2 -2 41 33 87 78 115 115 199 177 369 270 267 146 442 260 590 384 38 32 72 57 74 54 7 -6 -107 -108 -193 -173 -88 -67 -259 -175 -402 -256 -58 -34 -146 -88 -196 -122 -107 -73 -276 -237 -332 -323 -50 -76 -100 -193 -116 -274 l-12 -60 -87 173 c-194 389 -411 680 -731 980 -375 353 -563 553 -677 723 -54 79 -170 297 -170 318 0 7 21 -22 46 -63 179 -288 481 -616 885 -962 265 -227 363 -338 496 -560 32 -54 60 -97 63 -98 9 0 100 229 141 352 99 302 114 589 44 869 -105 419 -325 659 -955 1039 -333 201 -500 322 -642 465 -148 150 -254 318 -297 475 -11 40 -22 75 -25 77 -2 2 -27 -44 -56 -104z"/><path d="M4436 9470 c-1069 -83 -2060 -527 -2825 -1265 -567 -548 -975 -1198 -1195 -1909 -248 -796 -290 -1575 -130 -2365 213 -1049 795 -2006 1636 -2688 727 -590 1604 -940 2558 -1023 189 -16 648 -14 825 5 601 63 1114 212 1645 481 834 421 1518 1083 1967 1904 296 540 464 1079 540 1725 23 203 23 683 -1 908 -39 374 -122 744 -237 1062 l-31 84 -151 -53 c-141 -49 -152 -54 -151 -77 1 -13 20 -94 42 -179 303 -1165 101 -2402 -553 -3395 -410 -622 -943 -1098 -1600 -1431 -469 -237 -913 -369 -1450 -430 -202 -24 -622 -24 -830 -1 -739 82 -1409 331 -1990 741 -293 206 -640 542 -869 840 -474 616 -756 1323 -843 2111 -20 185 -22 593 -4 785 112 1186 708 2250 1662 2965 619 465 1347 745 2139 826 127 13 546 18 745 9 l110 -5 21 160 c12 88 21 161 20 162 -4 4 -149 25 -275 39 -161 18 -620 26 -775 14z"/><path d="M3870 6492 c-52 -26 -73 -71 -78 -169 l-5 -83 -98 0 -99 0 0 -225 0 -225 100 0 100 0 0 -355 0 -355 -100 0 -100 0 0 -230 0 -230 100 0 100 0 0 -355 0 -355 -100 0 -100 0 0 -225 0 -225 100 0 100 0 0 -80 c0 -97 17 -142 62 -170 29 -17 50 -20 169 -20 125 0 139 2 170 23 43 29 58 71 58 170 l1 77 64 0 64 0 4 -111 c3 -107 4 -112 31 -135 27 -23 34 -24 182 -24 221 0 235 10 243 172 l5 98 261 0 c227 0 272 3 345 20 264 64 482 254 576 505 119 313 36 650 -216 878 l-72 64 33 47 c43 63 94 176 116 256 25 91 25 299 0 395 -59 230 -220 428 -431 530 -141 69 -182 77 -412 82 l-203 5 0 77 c0 104 -14 147 -53 171 -28 18 -51 20 -185 20 l-154 0 -34 -34 -34 -34 0 -101 0 -101 -65 0 -65 0 0 85 c0 162 -28 185 -222 185 -97 0 -130 -4 -158 -18z m1307 -732 c74 -45 103 -184 85 -416 -12 -151 -31 -208 -85 -248 -28 -21 -39 -21 -478 -24 l-449 -3 0 356 0 355 448 0 c426 0 448 -1 479 -20z m150 -1172 c46 -30 93 -97 125 -177 18 -45 22 -77 22 -161 1 -98 -1 -110 -30 -171 -35 -73 -92 -142 -135 -165 -23 -12 -115 -14 -544 -14 l-515 0 0 360 0 360 518 -3 518 -2 41 -27z"/></g></svg></div>
|
<div class="mark"><svg class="brand-logo" viewBox="0 0 1036 1036" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Bitcoin After Life logo"><g transform="translate(0.000000,1036.000000) scale(0.100000,-0.100000)" fill="currentColor" stroke="none"><path d="M6130 10203 c-153 -318 -283 -665 -344 -916 -61 -250 -71 -335 -71 -602 1 -223 3 -249 28 -370 52 -244 140 -460 260 -633 72 -106 225 -257 320 -316 179 -111 335 -154 777 -211 232 -30 367 -61 442 -102 76 -41 195 -164 248 -257 170 -302 311 -774 362 -1211 19 -167 16 -591 -6 -745 -68 -484 -201 -871 -436 -1270 -541 -917 -1492 -1525 -2549 -1630 -132 -13 -453 -13 -596 0 -268 24 -530 77 -757 151 -1063 350 -1871 1192 -2187 2280 -63 217 -111 495 -127 742 -7 104 -7 107 -30 107 l-24 0 0 -167 c0 -286 27 -508 95 -773 148 -587 427 -1083 854 -1523 231 -237 423 -391 703 -563 685 -420 1543 -589 2343 -462 1346 213 2458 1211 2814 2524 89 329 116 543 115 919 0 349 -23 544 -100 847 -68 273 10 632 176 799 91 92 165 104 491 77 320 -26 499 36 699 242 171 175 285 422 320 690 15 116 12 401 -6 535 -13 100 -32 200 -91 478 -3 15 -8 27 -12 27 -3 0 -21 -30 -40 -66 -20 -39 -63 -96 -105 -139 -127 -130 -287 -216 -616 -330 -297 -104 -420 -161 -551 -256 -159 -117 -268 -285 -325 -504 -36 -135 -37 -312 -4 -500 12 -71 24 -132 26 -134 2 -2 41 33 87 78 115 115 199 177 369 270 267 146 442 260 590 384 38 32 72 57 74 54 7 -6 -107 -108 -193 -173 -88 -67 -259 -175 -402 -256 -58 -34 -146 -88 -196 -122 -107 -73 -276 -237 -332 -323 -50 -76 -100 -193 -116 -274 l-12 -60 -87 173 c-194 389 -411 680 -731 980 -375 353 -563 553 -677 723 -54 79 -170 297 -170 318 0 7 21 -22 46 -63 179 -288 481 -616 885 -962 265 -227 363 -338 496 -560 32 -54 60 -97 63 -98 9 0 100 229 141 352 99 302 114 589 44 869 -105 419 -325 659 -955 1039 -333 201 -500 322 -642 465 -148 150 -254 318 -297 475 -11 40 -22 75 -25 77 -2 2 -27 -44 -56 -104z"/><path d="M4436 9470 c-1069 -83 -2060 -527 -2825 -1265 -567 -548 -975 -1198 -1195 -1909 -248 -796 -290 -1575 -130 -2365 213 -1049 795 -2006 1636 -2688 727 -590 1604 -940 2558 -1023 189 -16 648 -14 825 5 601 63 1114 212 1645 481 834 421 1518 1083 1967 1904 296 540 464 1079 540 1725 23 203 23 683 -1 908 -39 374 -122 744 -237 1062 l-31 84 -151 -53 c-141 -49 -152 -54 -151 -77 1 -13 20 -94 42 -179 303 -1165 101 -2402 -553 -3395 -410 -622 -943 -1098 -1600 -1431 -469 -237 -913 -369 -1450 -430 -202 -24 -622 -24 -830 -1 -739 82 -1409 331 -1990 741 -293 206 -640 542 -869 840 -474 616 -756 1323 -843 2111 -20 185 -22 593 -4 785 112 1186 708 2250 1662 2965 619 465 1347 745 2139 826 127 13 546 18 745 9 l110 -5 21 160 c12 88 21 161 20 162 -4 4 -149 25 -275 39 -161 18 -620 26 -775 14z"/><path d="M3870 6492 c-52 -26 -73 -71 -78 -169 l-5 -83 -98 0 -99 0 0 -225 0 -225 100 0 100 0 0 -355 0 -355 -100 0 -100 0 0 -230 0 -230 100 0 100 0 0 -355 0 -355 -100 0 -100 0 0 -225 0 -225 100 0 100 0 0 -80 c0 -97 17 -142 62 -170 29 -17 50 -20 169 -20 125 0 139 2 170 23 43 29 58 71 58 170 l1 77 64 0 64 0 4 -111 c3 -107 4 -112 31 -135 27 -23 34 -24 182 -24 221 0 235 10 243 172 l5 98 261 0 c227 0 272 3 345 20 264 64 482 254 576 505 119 313 36 650 -216 878 l-72 64 33 47 c43 63 94 176 116 256 25 91 25 299 0 395 -59 230 -220 428 -431 530 -141 69 -182 77 -412 82 l-203 5 0 77 c0 104 -14 147 -53 171 -28 18 -51 20 -185 20 l-154 0 -34 -34 -34 -34 0 -101 0 -101 -65 0 -65 0 0 85 c0 162 -28 185 -222 185 -97 0 -130 -4 -158 -18z m1307 -732 c74 -45 103 -184 85 -416 -12 -151 -31 -208 -85 -248 -28 -21 -39 -21 -478 -24 l-449 -3 0 356 0 355 448 0 c426 0 448 -1 479 -20z m150 -1172 c46 -30 93 -97 125 -177 18 -45 22 -77 22 -161 1 -98 -1 -110 -30 -171 -35 -73 -92 -142 -135 -165 -23 -12 -115 -14 -544 -14 l-515 0 0 360 0 360 518 -3 518 -2 41 -27z"/></g></svg></div>
|
||||||
<div>
|
<div>
|
||||||
<h1>Will Executor — Server List</h1>
|
<h1>Will Executor — Server List</h1>
|
||||||
<div class="sub"><a href="https://bitcoin-after.life" target="_blank" rel="noopener">Bitcoin After Life</a> · Will-executor node leaderboard</div>
|
<div class="sub">Bitcoin After Life · Will-executor node leaderboard</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hero-right">
|
|
||||||
<div class="hero-stats">
|
<div class="hero-stats">
|
||||||
<div class="stat"><div class="n" id="s-online">0</div><div class="l">Online</div></div>
|
<div class="stat"><div class="n" id="s-online">0</div><div class="l">Online</div></div>
|
||||||
<div class="stat"><div class="n" id="s-total">0</div><div class="l">Servers</div></div>
|
<div class="stat"><div class="n" id="s-total">0</div><div class="l">Servers</div></div>
|
||||||
</div>
|
<div class="stat"><div class="n" id="s-block">—</div><div class="l">Block</div></div>
|
||||||
<a class="hero-cta" href="/terms.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><path d="M14 3v6h6"/><path d="M8 13h8M8 17h5"/></svg><span>How to get listed<small>Terms & yearly fee</small></span></a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -395,6 +223,7 @@ footer a{color:var(--accent);text-decoration:none}
|
|||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="seg" id="chainSeg" role="group" aria-label="Chain">
|
<div class="seg" id="chainSeg" role="group" aria-label="Chain">
|
||||||
<button data-c="bitcoin" aria-pressed="true">Bitcoin</button>
|
<button data-c="bitcoin" aria-pressed="true">Bitcoin</button>
|
||||||
|
<button data-c="testnet" aria-pressed="false">Testnet</button>
|
||||||
<button data-c="testnet4" aria-pressed="false">Testnet4</button>
|
<button data-c="testnet4" aria-pressed="false">Testnet4</button>
|
||||||
<button data-c="regtest" aria-pressed="false">Regtest</button>
|
<button data-c="regtest" aria-pressed="false">Regtest</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -414,19 +243,16 @@ footer a{color:var(--accent);text-decoration:none}
|
|||||||
<a class="btn-ghost" id="download" href="#" download="willexecutors.json">↓ JSON</a>
|
<a class="btn-ghost" id="download" href="#" download="willexecutors.json">↓ JSON</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="freshness-note">
|
|
||||||
<strong>Fee/description/version</strong> refresh about once a day (timing varies). <strong>Deposits</strong> are credited instantly after 3 confirmations (~30 min).
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="table-card">
|
<div class="table-card">
|
||||||
<table id="tbl">
|
<table id="tbl">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="num">#</th>
|
<th class="num">#</th>
|
||||||
<th>URL</th>
|
<th>URL</th>
|
||||||
<th>Info*</th>
|
<th>Info</th>
|
||||||
<th class="num">Fee*</th>
|
<th class="num">Fee</th>
|
||||||
<th>Version*</th>
|
<th>Version</th>
|
||||||
|
<th class="num">Balance</th>
|
||||||
<th class="num">Height</th>
|
<th class="num">Height</th>
|
||||||
<th class="num">Wins</th>
|
<th class="num">Wins</th>
|
||||||
<th class="num">Score</th>
|
<th class="num">Score</th>
|
||||||
@@ -438,71 +264,26 @@ footer a{color:var(--accent);text-decoration:none}
|
|||||||
|
|
||||||
<div class="mobile-list" id="mlist"></div>
|
<div class="mobile-list" id="mlist"></div>
|
||||||
|
|
||||||
<!-- Under the table: the disclaimer on the left and, on the right, the way to the
|
|
||||||
"Add your Will Executor" panel further down (owner, 2026-11-06: it was easy to miss). -->
|
|
||||||
<div class="after-table">
|
|
||||||
<p class="fee-disclaimer">* Info, Fee and Version data may not reflect actual values or may be outdated. Refer to the <a href="https://bitcoin-after.life" target="_blank" rel="noopener">Bitcoin After Life plugin</a> for up-to-date information.</p>
|
|
||||||
<a class="btn-ghost add-cta" href="#we-panel"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>Add your Will Executor</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section class="intro-card">
|
<section class="intro-card">
|
||||||
<h2>Will Executor Server List</h2>
|
<h2>Will Executor Server List</h2>
|
||||||
<p>This page displays all Will Executor servers that manage the inheritance processing for the Bitcoin After Life plugin on Electrum. Will Executors operate in a competitive environment, racing to broadcast inheritance transactions to network nodes and earn the associated transaction fees.</p>
|
<p>This page displays all Will Executor servers that manage the inheritance processing for the Bitcoin After Life plugin on Electrum. Will Executors operate in a competitive environment, racing to broadcast inheritance transactions to network nodes and earn the associated transaction fees.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="add-grid">
|
<div class="add-grid">
|
||||||
<!-- One panel, two jobs (owner, 2026-11-06): the tabs choose between adding a
|
<div class="panel">
|
||||||
server (pay, invoice) and checking a listed one (read-only). Elements marked
|
<h2>Add your Will Executor</h2>
|
||||||
data-add / data-check belong to one tab; the script hides the others.
|
<p class="lead">Enter your server URL. It will be checked online before it appears in the list.</p>
|
||||||
?mode=check in the address opens the Check tab (the Terms FAQ links to it). -->
|
|
||||||
<div class="panel" id="we-panel" data-mode="add">
|
|
||||||
<div class="seg mode-seg" role="tablist" aria-label="Add or check a server">
|
|
||||||
<button type="button" role="tab" aria-selected="true" data-mode="add">
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>Add a server
|
|
||||||
</button>
|
|
||||||
<button type="button" role="tab" aria-selected="false" data-mode="check">
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/></svg>Renewal status
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="panel-form">
|
|
||||||
<h2 data-add>Add your Will Executor</h2>
|
|
||||||
<h2 data-check hidden>Renewal status of your Will Executor</h2>
|
|
||||||
<p class="lead" data-add>Enter your server URL. It will appear in the list once your deposit is confirmed (typically about 30 minutes).</p>
|
|
||||||
<p class="lead" data-check hidden>Enter the exact URL of your server as it appears in the list: you see its expiry date and when you can renew. Nothing is charged.</p>
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<input type="text" id="url" placeholder="https://we.bitcoin-after.life">
|
<input type="text" id="url" placeholder="https://bitcoin-after.life:9137">
|
||||||
</div>
|
<button class="btn" id="submit">Add server</button>
|
||||||
<div style="margin-top:8px" data-add>
|
|
||||||
<input type="text" id="contact" placeholder="Email, npub or Telegram (optional)" style="width:100%;font:inherit;font-size:14px;padding:9px 12px;border:1px solid var(--line-strong);border-radius:8px;background:var(--panel);color:var(--ink)">
|
|
||||||
</div>
|
|
||||||
<div class="btn-row">
|
|
||||||
<button class="btn" id="submit" data-add>Add server</button>
|
|
||||||
<button class="btn" id="check" data-check hidden>Show renewal status</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ok-line" id="iv-status"></div>
|
<div class="ok-line" id="iv-status"></div>
|
||||||
|
|
||||||
<!-- Renewal status result: filled by the "Show renewal status" button. -->
|
|
||||||
<div class="renewal-card" id="renewal-card" hidden>
|
|
||||||
<div class="rc-main">
|
|
||||||
<span class="rc-label">Expiry date</span>
|
|
||||||
<b class="rc-date" id="rc-expiry">—</b>
|
|
||||||
<span class="rc-sub" id="rc-days">—</span>
|
|
||||||
</div>
|
|
||||||
<div class="rc-grid">
|
|
||||||
<div><span>Renewal window</span><b id="rc-window">—</b></div>
|
|
||||||
<div><span>Position</span><b id="rc-pos">—</b></div>
|
|
||||||
<div><span>Balance</span><b id="rc-balance">—</b></div>
|
|
||||||
<div><span>Donations</span><b id="rc-donations">—</b></div>
|
|
||||||
</div>
|
|
||||||
<a class="rc-link" id="rc-link" href="#">View the server page →</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="terms-box" id="terms-box">
|
<div class="terms-box" id="terms-box">
|
||||||
<p class="lead">You are about to add this server:</p>
|
<p class="lead">You are about to add this server:</p>
|
||||||
<p><a id="terms-url" href="#" target="_blank" rel="noopener" class="mono"></a></p>
|
<p><a id="terms-url" href="#" target="_blank" rel="noopener" class="mono"></a></p>
|
||||||
<p style="font-size:13px;margin-top:4px"><strong>Make sure the link above works correctly before paying. No refunds after payment. URL changes require proof of ownership — see the <a href="/terms.html" target="_blank">Terms</a>.</strong></p>
|
<p style="font-size:13px;margin-top:4px"><strong>Make sure the link above works correctly before paying. No refunds or URL changes after payment.</strong></p>
|
||||||
<p style="font-size:14px">
|
<p style="font-size:14px">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="terms-check">
|
<input type="checkbox" id="terms-check">
|
||||||
@@ -513,53 +294,26 @@ footer a{color:var(--accent);text-decoration:none}
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="invoice" id="invoice">
|
<div class="invoice" id="invoice">
|
||||||
<div class="row"><span>Annual fee</span><span id="iv-min">—</span></div>
|
<div class="row"><span>Minimum amount</span><span id="iv-min">—</span></div>
|
||||||
<div class="row"><span>Chain</span><span id="iv-chain">—</span></div>
|
<div class="row"><span>Chain</span><span id="iv-chain">—</span></div>
|
||||||
<div class="row"><span>Address</span><span id="iv-addr">—</span></div>
|
<div class="row"><span>Address</span><span id="iv-addr">—</span></div>
|
||||||
<div class="row"><span>Balance</span><span id="iv-balance">—</span></div>
|
<div class="row"><span>Balance</span><span id="iv-balance">—</span></div>
|
||||||
<div class="row"><span>Donations</span><span id="iv-donations">—</span></div>
|
<div class="row"><span>Status</span><span id="iv-status2">—</span></div>
|
||||||
<div class="row"><span>Contact</span><span id="iv-contact">—</span></div>
|
|
||||||
<div class="row"><span>Server</span><span id="iv-url">—</span></div>
|
<div class="row"><span>Server</span><span id="iv-url">—</span></div>
|
||||||
<div class="row"><span>Will Executor</span><span><a id="iv-weinfo" href="#" target="_blank">—</a></span></div>
|
|
||||||
<div class="row" id="iv-nextdep-row" style="display:none"><span>Next renewal</span><span id="iv-nextdep">—</span></div>
|
|
||||||
<div id="qrcode"></div>
|
<div id="qrcode"></div>
|
||||||
<button class="btn-share" id="share-btn">Share invoice</button>
|
</div>
|
||||||
|
|
||||||
<p class="note">
|
<p class="note">
|
||||||
<strong>Please make sure your server is online before you attempt to add it.</strong>
|
<strong>Please make sure your server is online before you attempt to add it.</strong>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
<aside class="panel-side">
|
|
||||||
<div data-add>
|
|
||||||
<p class="side-title">What happens next</p>
|
|
||||||
<ol class="mini-steps">
|
|
||||||
<li><b>Pay the yearly fee</b><span>50,000 sats, shown in the payment request</span></li>
|
|
||||||
<li><b>3 confirmations</b><span>about 30 minutes, then your server is listed for 365 days</span></li>
|
|
||||||
<li><b>Renew from 6 months before expiry</b><span>the new year is added on top of the time left</span></li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
<div data-check hidden>
|
|
||||||
<p class="side-title">What you get</p>
|
|
||||||
<ol class="mini-steps">
|
|
||||||
<li><b>Expiry date</b><span>shown only here and on the server page, not in the main table</span></li>
|
|
||||||
<li><b>Renewal window</b><span>it opens 6 months before expiry; you see whether it is open or when it opens</span></li>
|
|
||||||
<li><b>Position, balance, donations</b><span>and a link to the server page with its WeTime activity</span></li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
<a class="side-link" href="/terms.html">How listing works (15 s) →</a>
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<h2>How ranking works</h2>
|
<h2>How ranking works</h2>
|
||||||
<p class="lead">Ordered by excess donated, then total paid, then base fee (lower first), then WeTime points.</p>
|
<p class="lead">Bid-based ranking: higher contributions rank higher.</p>
|
||||||
<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--line);font-size:13px"><span style="color:var(--muted)">Yearly fee</span><b class="mono"><span id="thr-min">50,000</span> sats</b></div>
|
<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--line);font-size:13px"><span style="color:var(--muted)">Deposit threshold</span><b class="mono"><span id="thr-min">50,000</span> sats</b></div>
|
||||||
<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--line);font-size:13px"><span style="color:var(--muted)">Listing period</span><b>365 days</b></div>
|
<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--line);font-size:13px"><span style="color:var(--muted)">Plugin integration</span><b>automatic</b></div>
|
||||||
<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--line);font-size:13px"><span style="color:var(--muted)">Renewal window</span><b>from 6 months before expiry</b></div>
|
<div style="display:flex;justify-content:space-between;padding:8px 0;font-size:13px"><span style="color:var(--muted)">Early-adopter guarantee</span><b>12 months</b></div>
|
||||||
<div style="display:flex;justify-content:space-between;padding:8px 0;font-size:13px"><span style="color:var(--muted)">Plugin integration</span><b>automatic</b></div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -574,13 +328,13 @@ footer a{color:var(--accent);text-decoration:none}
|
|||||||
<div class="info-block">
|
<div class="info-block">
|
||||||
<span class="ic">02</span>
|
<span class="ic">02</span>
|
||||||
<h3>Ranking System</h3>
|
<h3>Ranking System</h3>
|
||||||
<h3>Ranking System</h3>
|
<p>This leaderboard operates on a bid-based ranking system where higher contributions result in better positioning.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info-block">
|
<div class="info-block">
|
||||||
<span class="ic">03</span>
|
<span class="ic">03</span>
|
||||||
<span class="ic">03</span>
|
<h3>Early Adopter Incentives</h3>
|
||||||
<h3>Listing & Renewal</h3>
|
<p>To support the project's initial launch phase, we plan to maintain the first Will Executor servers for a minimum of 12 months, rewarding early participants. Subsequently, premium positions will be available through an auction system based on protocol revenue generation.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info-block">
|
<div class="info-block">
|
||||||
@@ -596,21 +350,12 @@ footer a{color:var(--accent);text-decoration:none}
|
|||||||
<footer>
|
<footer>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<span>Bitcoin After Life — Will Executor List</span>
|
<span>Bitcoin After Life — Will Executor List</span>
|
||||||
<span>Bitcoin After Life — Will Executor List</span>
|
|
||||||
<a href="mailto:welist@bitcoin-after.life">welist@bitcoin-after.life</a>
|
<a href="mailto:welist@bitcoin-after.life">welist@bitcoin-after.life</a>
|
||||||
<a href="mailto:welist@bitcoin-after.life">welist@bitcoin-after.life</a>
|
|
||||||
<a class="mono" id="commit-link" href="https://bitcoin-after.life/gitea/bitcoinafterlife/bal-welist-website/commit/COMMIT_HASH_PLACEHOLDER">COMMIT_HASH_PLACEHOLDER</a>
|
<a class="mono" id="commit-link" href="https://bitcoin-after.life/gitea/bitcoinafterlife/bal-welist-website/commit/COMMIT_HASH_PLACEHOLDER">COMMIT_HASH_PLACEHOLDER</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
<script>
|
|
||||||
if (window.location.hostname !== 'localhost') {
|
|
||||||
const regtestBtn = document.querySelector('#chainSeg [data-c="regtest"]');
|
|
||||||
if (regtestBtn) {
|
|
||||||
regtestBtn.style.display = 'none';
|
|
||||||
regtestBtn.setAttribute('aria-pressed', 'false');
|
|
||||||
}
|
|
||||||
let allServers = [];
|
let allServers = [];
|
||||||
let withTor = true, q = "";
|
let withTor = true, q = "";
|
||||||
let minAmount = 50000;
|
let minAmount = 50000;
|
||||||
@@ -623,23 +368,11 @@ function getChain() {
|
|||||||
return btn ? btn.dataset.c : 'bitcoin';
|
return btn ? btn.dataset.c : 'bitcoin';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The selected chain lives in the address (?chain=testnet4), so a chain's list
|
|
||||||
// can be shared as a link. Bitcoin is the default and keeps the plain address.
|
|
||||||
function setChain(chain) {
|
|
||||||
const buttons = [...document.querySelectorAll('#chainSeg button')];
|
|
||||||
const target = buttons.find(b => b.dataset.c === chain && b.style.display !== 'none');
|
|
||||||
if (!target) return false;
|
|
||||||
buttons.forEach(b => b.setAttribute('aria-pressed', b === target ? 'true' : 'false'));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function chainFromUrl() {
|
|
||||||
return new URLSearchParams(window.location.search).get('chain') || 'bitcoin';
|
|
||||||
}
|
|
||||||
function getFiltered() {
|
function getFiltered() {
|
||||||
return allServers
|
return allServers
|
||||||
return allServers
|
.filter(d => withTor ? true : !d.onion)
|
||||||
|
.filter(d => (d.url + " " + (d.info||"")).toLowerCase().includes(q.toLowerCase()))
|
||||||
|
.sort((a, b) => (b.balance||0) - (a.balance||0) || (a.base_fee||0) - (b.base_fee||0) || (b.points||0) - (a.points||0));
|
||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
@@ -649,7 +382,7 @@ function render() {
|
|||||||
tb.innerHTML = ""; ml.innerHTML = "";
|
tb.innerHTML = ""; ml.innerHTML = "";
|
||||||
|
|
||||||
if (!list.length) {
|
if (!list.length) {
|
||||||
if (!list.length) {
|
tb.innerHTML = '<tr><td colspan="9" style="padding:28px;text-align:center;color:var(--muted)">No servers found.</td></tr>';
|
||||||
}
|
}
|
||||||
|
|
||||||
list.forEach((d, i) => {
|
list.forEach((d, i) => {
|
||||||
@@ -663,6 +396,7 @@ function render() {
|
|||||||
<td class="info">${DOMPurify.sanitize(d.info||'—')}</td>
|
<td class="info">${DOMPurify.sanitize(d.info||'—')}</td>
|
||||||
<td class="num">${fmt(d.base_fee)}</td>
|
<td class="num">${fmt(d.base_fee)}</td>
|
||||||
<td class="mono">${DOMPurify.sanitize(d.version||'—')}</td>
|
<td class="mono">${DOMPurify.sanitize(d.version||'—')}</td>
|
||||||
|
<td class="num">${fmt(d.balance)}</td>
|
||||||
<td class="num">${fmt(d.last_block)}</td>
|
<td class="num">${fmt(d.last_block)}</td>
|
||||||
<td class="num">${d.count_win != null ? d.count_win : '—'}</td>
|
<td class="num">${d.count_win != null ? d.count_win : '—'}</td>
|
||||||
<td class="num"><span class="score">${fmt(d.points)}</span></td>
|
<td class="num"><span class="score">${fmt(d.points)}</span></td>
|
||||||
@@ -677,15 +411,18 @@ function render() {
|
|||||||
<div><span>Score</span><b>${fmt(d.points)}</b></div>
|
<div><span>Score</span><b>${fmt(d.points)}</b></div>
|
||||||
<div><span>Wins</span><b>${d.count_win != null ? d.count_win : '—'}</b></div>
|
<div><span>Wins</span><b>${d.count_win != null ? d.count_win : '—'}</b></div>
|
||||||
<div><span>Fee</span><b>${fmt(d.base_fee)} sat</b></div>
|
<div><span>Fee</span><b>${fmt(d.base_fee)} sat</b></div>
|
||||||
|
<div><span>Balance</span><b>${fmt(d.balance)}</b></div>
|
||||||
<div><span>Version</span><b>${DOMPurify.sanitize(d.version||'—')}</b></div>
|
<div><span>Version</span><b>${DOMPurify.sanitize(d.version||'—')}</b></div>
|
||||||
<div><span>Height</span><b>${fmt(d.last_block)}</b></div>
|
<div><span>Height</span><b>${fmt(d.last_block)}</b></div>
|
||||||
</div>
|
</div>
|
||||||
</div>`);
|
</div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const online = allServers.filter(d => d.status === "online" || d.status === "OK").length;
|
||||||
document.getElementById('s-online').textContent = online;
|
document.getElementById('s-online').textContent = online;
|
||||||
document.getElementById('s-total').textContent = allServers.length;
|
document.getElementById('s-total').textContent = allServers.length;
|
||||||
|
const top = getFiltered()[0];
|
||||||
|
document.getElementById('s-block').textContent = top ? fmt(top.last_block) : '—';
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
@@ -693,14 +430,10 @@ async function fetchData() {
|
|||||||
const params = new URLSearchParams({ page: 0, limit: 100 });
|
const params = new URLSearchParams({ page: 0, limit: 100 });
|
||||||
if (!withTor) params.append("onion", "no");
|
if (!withTor) params.append("onion", "no");
|
||||||
try {
|
try {
|
||||||
try {
|
const response = await fetch(`/data/${chain}?${params}`);
|
||||||
// NOTE: /data/{chain} returns a HashMap, whose JSON key order is not
|
|
||||||
// guaranteed (it can change on every backend restart). /datalist/{chain}
|
|
||||||
// returns an ordered array (Vec) that preserves the server-side sort
|
|
||||||
// (donation DESC, balance DESC, base_fee ASC, wetime_points DESC) — see terms.html §3.
|
|
||||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
const obj = await response.json();
|
||||||
const list = await response.json();
|
allServers = Object.entries(obj).map(([key, s]) => ({ id: key, ...s }));
|
||||||
render();
|
render();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Fetch error:', err);
|
console.error('Fetch error:', err);
|
||||||
@@ -712,9 +445,8 @@ async function fetchData() {
|
|||||||
async function fetchMinAmount() {
|
async function fetchMinAmount() {
|
||||||
const chain = getChain();
|
const chain = getChain();
|
||||||
try {
|
try {
|
||||||
try {
|
const r = await fetch('/min_deposit/' + chain);
|
||||||
const r = await fetch('/ranking_info/' + chain);
|
const val = parseInt(await r.text(), 10);
|
||||||
const data = await r.json();
|
|
||||||
if (isNaN(val)) throw new Error('invalid');
|
if (isNaN(val)) throw new Error('invalid');
|
||||||
minAmount = val;
|
minAmount = val;
|
||||||
document.getElementById('thr-min').textContent = fmt(val);
|
document.getElementById('thr-min').textContent = fmt(val);
|
||||||
@@ -737,151 +469,13 @@ document.getElementById('filter').addEventListener('input', e => {
|
|||||||
document.getElementById('chainSeg').addEventListener('click', e => {
|
document.getElementById('chainSeg').addEventListener('click', e => {
|
||||||
const b = e.target.closest('button');
|
const b = e.target.closest('button');
|
||||||
if (!b) return;
|
if (!b) return;
|
||||||
if (!b) return;
|
[...e.currentTarget.children].forEach(x => x.setAttribute('aria-pressed', 'false'));
|
||||||
setChain(b.dataset.c);
|
b.setAttribute('aria-pressed', 'true');
|
||||||
const url = new URL(window.location.href);
|
|
||||||
if (b.dataset.c === 'bitcoin') url.searchParams.delete('chain');
|
|
||||||
else url.searchParams.set('chain', b.dataset.c);
|
|
||||||
fetchData();
|
fetchData();
|
||||||
fetchMinAmount();
|
fetchMinAmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Browser Back/Forward: follow the address, an unknown chain falls back to Bitcoin.
|
|
||||||
window.addEventListener('popstate', () => {
|
|
||||||
if (!setChain(chainFromUrl())) setChain('bitcoin');
|
|
||||||
fetchData();
|
|
||||||
fetchMinAmount();
|
|
||||||
});
|
|
||||||
|
|
||||||
// After an invoice is shown, ask the server page every 30 seconds whether the
|
|
||||||
// payment has been seen, so the visitor does not have to reload. The status only
|
|
||||||
// moves forward: unconfirmed_balance may go back to 0 before the deposit is
|
|
||||||
// credited, and that must not look as if the payment had disappeared.
|
|
||||||
const DEPOSIT_POLL_MS = 30000;
|
|
||||||
const DEPOSIT_POLL_MAX_MS = 24 * 60 * 60 * 1000;
|
|
||||||
let depositWatch = null;
|
|
||||||
|
|
||||||
function stopDepositWatch() {
|
|
||||||
if (depositWatch) clearInterval(depositWatch.timer);
|
|
||||||
depositWatch = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pollDeposit() {
|
|
||||||
const watch = depositWatch;
|
|
||||||
if (!watch) return;
|
|
||||||
if (Date.now() - watch.started > DEPOSIT_POLL_MAX_MS) return stopDepositWatch();
|
|
||||||
let data;
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/weinfodata/${watch.chain}/${watch.id}`);
|
|
||||||
if (!response.ok) return;
|
|
||||||
data = await response.json();
|
|
||||||
} catch (err) {
|
|
||||||
return; // temporary network problem: try again at the next round
|
|
||||||
}
|
|
||||||
if (watch !== depositWatch || !data) return;
|
|
||||||
const expiry = data.expiry_date || null;
|
|
||||||
// The first answer is the reference: listing or renewal changes the expiry date.
|
|
||||||
if (watch.expiry === undefined) watch.expiry = expiry;
|
|
||||||
const status = document.getElementById('iv-status');
|
|
||||||
const bal = data.balance != null ? fmt(data.balance) : null;
|
|
||||||
const unc = data.unconfirmed_balance != null ? fmt(data.unconfirmed_balance) : null;
|
|
||||||
if (bal) document.getElementById('iv-balance').textContent = unc ? bal + ' (' + unc + ')' : bal;
|
|
||||||
if (expiry && expiry !== watch.expiry) {
|
|
||||||
status.className = 'ok-line';
|
|
||||||
status.textContent = '✓ Payment confirmed: your server is now listed.';
|
|
||||||
stopDepositWatch();
|
|
||||||
fetchData();
|
|
||||||
} else if (Number(data.unconfirmed_balance) > 0 && !watch.detected) {
|
|
||||||
watch.detected = true;
|
|
||||||
status.className = 'ok-line';
|
|
||||||
status.textContent = 'Payment detected: ' + fmt(data.unconfirmed_balance) + ' sats, waiting for 3 confirmations (about 30 minutes). This page updates by itself, or use the "Follow your payment" link below.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function startDepositWatch(chain, id) {
|
|
||||||
stopDepositWatch();
|
|
||||||
if (!id) return;
|
|
||||||
depositWatch = { chain, id, started: Date.now(), detected: false, expiry: undefined };
|
|
||||||
depositWatch.timer = setInterval(pollDeposit, DEPOSIT_POLL_MS);
|
|
||||||
pollDeposit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Check server" is read-only: it looks the URL up in the list (GET /datalist) and
|
|
||||||
// shows the server's expiry and balance from /weinfodata. It never POSTs to /data,
|
|
||||||
// so checking a URL that is not listed creates nothing.
|
|
||||||
function normalizeUrl(u) {
|
|
||||||
u = u.trim();
|
|
||||||
if (!u) return '';
|
|
||||||
if (!u.startsWith('http://') && !u.startsWith('https://')) u = 'https://' + u;
|
|
||||||
return u.replace(/\/+$/, '');
|
|
||||||
}
|
|
||||||
function hidePaymentRows() {
|
|
||||||
document.getElementById('iv-min').parentElement.style.display = 'none';
|
|
||||||
document.getElementById('iv-addr').parentElement.style.display = 'none';
|
|
||||||
document.getElementById('qrcode').style.display = 'none';
|
|
||||||
document.getElementById('share-btn').style.display = 'none';
|
|
||||||
}
|
|
||||||
document.getElementById('check').addEventListener('click', async () => {
|
|
||||||
stopDepositWatch();
|
|
||||||
const urlInput = document.getElementById('url');
|
|
||||||
const url = normalizeUrl(urlInput.value);
|
|
||||||
if (!url) return;
|
|
||||||
urlInput.value = url;
|
|
||||||
const chain = getChain();
|
|
||||||
const status = document.getElementById('iv-status');
|
|
||||||
const inv = document.getElementById('invoice');
|
|
||||||
inv.classList.remove('show');
|
|
||||||
document.getElementById('terms-box').classList.remove('show');
|
|
||||||
document.getElementById('renewal-card').hidden = true;
|
|
||||||
status.className = '';
|
|
||||||
status.textContent = 'Checking…';
|
|
||||||
try {
|
|
||||||
const listResp = await fetch(`/datalist/${chain}?page=0&limit=100`);
|
|
||||||
if (!listResp.ok) throw new Error(`HTTP ${listResp.status}`);
|
|
||||||
const list = await listResp.json();
|
|
||||||
const idx = list.findIndex(d => normalizeUrl(d.url || '').toLowerCase() === url.toLowerCase());
|
|
||||||
if (idx < 0) {
|
|
||||||
status.className = 'err-line';
|
|
||||||
status.textContent = `Not in the ${chain} list: no server with this exact URL. To list it, use the "Add a server" tab.`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const d = list[idx];
|
|
||||||
const infoResp = await fetch(`/weinfodata/${chain}/${d.id}`);
|
|
||||||
const info = infoResp.ok ? await infoResp.json() : {};
|
|
||||||
const expiry = (info.expiry_date || '').slice(0, 10); // '2027-07-16 00:00:00' -> the date
|
|
||||||
const bal = info.balance != null ? fmt(info.balance) : null;
|
|
||||||
const unc = info.unconfirmed_balance != null ? fmt(info.unconfirmed_balance) : null;
|
|
||||||
// Renewal window: from 6 months before expiry (Terms, Section 1).
|
|
||||||
let daysText = '—', windowText = '—', windowOpen = false;
|
|
||||||
if (expiry) {
|
|
||||||
const exp = new Date(expiry + 'T00:00:00Z');
|
|
||||||
const win = new Date(exp); win.setUTCMonth(win.getUTCMonth() - 6);
|
|
||||||
const days = Math.ceil((exp - Date.now()) / 86400000);
|
|
||||||
daysText = days > 0 ? `${days} days left` : 'expired';
|
|
||||||
windowOpen = Date.now() >= win.getTime() && days > 0;
|
|
||||||
windowText = windowOpen ? 'open now: you can renew' : `opens on ${win.toISOString().slice(0, 10)}`;
|
|
||||||
}
|
|
||||||
document.getElementById('rc-expiry').textContent = expiry || '—';
|
|
||||||
document.getElementById('rc-days').textContent = daysText;
|
|
||||||
const winEl = document.getElementById('rc-window');
|
|
||||||
winEl.textContent = windowText;
|
|
||||||
winEl.className = windowOpen ? 'rc-open' : '';
|
|
||||||
document.getElementById('rc-pos').textContent = `#${idx + 1} of ${list.length}`;
|
|
||||||
document.getElementById('rc-balance').textContent = bal ? (unc ? bal + ' (' + unc + ')' : bal) : '—';
|
|
||||||
document.getElementById('rc-donations').textContent = info.donation != null ? fmt(info.donation) : '—';
|
|
||||||
document.getElementById('rc-link').href = `/weinfo/${chain}/${d.id}`;
|
|
||||||
status.className = 'ok-line';
|
|
||||||
status.textContent = windowOpen ? '✓ Listed. The renewal window is open.' : '✓ Listed.';
|
|
||||||
const card = document.getElementById('renewal-card');
|
|
||||||
card.hidden = false;
|
|
||||||
card.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
||||||
} catch (err) {
|
|
||||||
status.className = 'err-line';
|
|
||||||
status.textContent = 'Could not check the list right now. Please try again in a moment.';
|
|
||||||
}
|
|
||||||
document.getElementById('submit').addEventListener('click', async () => {
|
document.getElementById('submit').addEventListener('click', async () => {
|
||||||
document.getElementById('submit').addEventListener('click', async () => {
|
|
||||||
const urlInput = document.getElementById('url');
|
const urlInput = document.getElementById('url');
|
||||||
let url = urlInput.value.trim();
|
let url = urlInput.value.trim();
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
@@ -897,50 +491,18 @@ document.getElementById('submit').addEventListener('click', async () => {
|
|||||||
document.getElementById('iv-chain').textContent = '—';
|
document.getElementById('iv-chain').textContent = '—';
|
||||||
document.getElementById('iv-addr').textContent = '—';
|
document.getElementById('iv-addr').textContent = '—';
|
||||||
document.getElementById('iv-balance').textContent = '—';
|
document.getElementById('iv-balance').textContent = '—';
|
||||||
document.getElementById('iv-balance').textContent = '—';
|
document.getElementById('iv-status2').textContent = '—';
|
||||||
document.getElementById('iv-url').textContent = '—';
|
document.getElementById('iv-url').textContent = '—';
|
||||||
document.getElementById('iv-url').textContent = '—';
|
|
||||||
document.getElementById('iv-contact').textContent = '—';
|
|
||||||
document.getElementById('iv-weinfo').href = '#';
|
|
||||||
document.getElementById('iv-weinfo').textContent = '—';
|
|
||||||
document.getElementById('iv-nextdep-row').style.display = 'none';
|
|
||||||
document.getElementById('iv-nextdep').textContent = '—';
|
|
||||||
document.getElementById('iv-min').parentElement.style.display = '';
|
|
||||||
document.getElementById('iv-addr').parentElement.style.display = '';
|
|
||||||
document.getElementById('qrcode').style.display = '';
|
|
||||||
document.getElementById('qrcode').innerHTML = '';
|
document.getElementById('qrcode').innerHTML = '';
|
||||||
document.getElementById('qrcode').innerHTML = '';
|
|
||||||
document.getElementById('qrcode').style.cursor = '';
|
|
||||||
document.getElementById('qr-overlay').classList.remove('show');
|
|
||||||
document.getElementById('terms-box').classList.remove('show');
|
document.getElementById('terms-box').classList.remove('show');
|
||||||
document.getElementById('terms-box').classList.remove('show');
|
|
||||||
document.getElementById('terms-check').checked = false;
|
document.getElementById('terms-check').checked = false;
|
||||||
document.getElementById('terms-accept').disabled = true;
|
document.getElementById('terms-accept').disabled = true;
|
||||||
const chain = getChain();
|
const chain = getChain();
|
||||||
const chain = getChain();
|
|
||||||
const infoUrl = url + '/' + chain + '/info';
|
|
||||||
document.getElementById('terms-url').href = infoUrl;
|
|
||||||
document.getElementById('terms-url').textContent = url;
|
|
||||||
document.getElementById('terms-box').classList.add('show');
|
|
||||||
document.getElementById('terms-box').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('terms-check').addEventListener('change', function () {
|
|
||||||
document.getElementById('terms-accept').disabled = !this.checked;
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('terms-accept').addEventListener('click', async function () {
|
|
||||||
document.getElementById('terms-box').classList.remove('show');
|
|
||||||
const urlInput = document.getElementById('url');
|
|
||||||
let url = urlInput.value.trim();
|
|
||||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
||||||
url = 'https://' + url;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/data', {
|
const response = await fetch('/data', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
headers: { 'Content-Type': 'application/json' },
|
body: JSON.stringify({ url, chain })
|
||||||
});
|
});
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -951,116 +513,48 @@ document.getElementById('terms-accept').addEventListener('click', async function
|
|||||||
document.getElementById('iv-min').textContent = fmt(result.min_amount != null ? result.min_amount : minAmount) + ' sats';
|
document.getElementById('iv-min').textContent = fmt(result.min_amount != null ? result.min_amount : minAmount) + ' sats';
|
||||||
document.getElementById('iv-chain').textContent = result.chain || chain;
|
document.getElementById('iv-chain').textContent = result.chain || chain;
|
||||||
document.getElementById('iv-addr').textContent = result.address || '—';
|
document.getElementById('iv-addr').textContent = result.address || '—';
|
||||||
document.getElementById('iv-addr').textContent = result.address || '—';
|
document.getElementById('iv-balance').textContent = result.balance != null ? fmt(result.balance) : '—';
|
||||||
const balStr = result.balance != null ? fmt(result.balance) : null;
|
document.getElementById('iv-status2').textContent = result.status || '—';
|
||||||
const uncStr = result.unconfirmed_balance != null ? fmt(result.unconfirmed_balance) : null;
|
const invoiceuri = `bitcoin:${result.address}?label=BAL&message=Order+For+url+%26+${encodeURIComponent(url)}`;
|
||||||
document.getElementById('iv-balance').textContent = balStr ? (uncStr ? balStr + ' (' + uncStr + ')' : balStr) : '—';
|
|
||||||
document.getElementById('iv-donations').textContent = result.donation != null ? fmt(result.donation) : '—';
|
|
||||||
document.getElementById('iv-contact').textContent = document.getElementById('contact').value.trim() || '—';
|
|
||||||
const weLink = result.id ? `/weinfo/${result.chain || chain}/${result.id}` : null;
|
|
||||||
const weinfoEl = document.getElementById('iv-weinfo');
|
|
||||||
if (weLink) {
|
|
||||||
weinfoEl.href = weLink;
|
|
||||||
weinfoEl.textContent = 'View page';
|
|
||||||
} else {
|
|
||||||
weinfoEl.href = '#';
|
|
||||||
weinfoEl.textContent = '—';
|
|
||||||
}
|
|
||||||
const serverUrl = result.url || url;
|
|
||||||
document.getElementById('iv-url').textContent = serverUrl;
|
|
||||||
if (result.payment_needed === false) {
|
|
||||||
document.getElementById('iv-min').parentElement.style.display = 'none';
|
|
||||||
document.getElementById('iv-addr').parentElement.style.display = 'none';
|
|
||||||
document.getElementById('qrcode').style.display = 'none';
|
|
||||||
document.getElementById('share-btn').style.display = 'none';
|
|
||||||
document.getElementById('iv-nextdep').textContent = result.next_deposit_date || '—';
|
|
||||||
document.getElementById('iv-nextdep-row').style.display = 'flex';
|
|
||||||
document.getElementById('iv-status').className = 'ok-line';
|
|
||||||
document.getElementById('iv-status').textContent = 'Server is already listed. Next renewal: ' + (result.next_deposit_date || '—');
|
|
||||||
const inv = document.getElementById('invoice');
|
|
||||||
inv.classList.add('show');
|
|
||||||
inv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Fixed address: whoever pays can close this invoice and follow the deposit there.
|
|
||||||
if (weLink) weinfoEl.textContent = 'Follow your payment';
|
|
||||||
const amountSats = result.min_amount != null ? result.min_amount : minAmount;
|
|
||||||
const amountBtc = (amountSats / 1e8).toFixed(8).replace(/\.?0+$/, '');
|
|
||||||
const qrEl = document.getElementById('qrcode');
|
const qrEl = document.getElementById('qrcode');
|
||||||
qrEl.innerHTML = '';
|
qrEl.innerHTML = '';
|
||||||
qrEl.innerHTML = '';
|
new QRCode(qrEl, invoiceuri);
|
||||||
new QRCode(qrEl, { text: invoiceuri, width: 160, height: 160 });
|
const serverUrl = result.url || url;
|
||||||
qrEl.style.cursor = 'pointer';
|
const infoUrl = serverUrl + '/' + (result.chain || chain) + '/info';
|
||||||
qrEl.onclick = function () {
|
document.getElementById('iv-url').textContent = serverUrl;
|
||||||
const overlay = document.getElementById('qr-overlay');
|
document.getElementById('terms-url').href = infoUrl;
|
||||||
const big = document.getElementById('qr-big');
|
document.getElementById('terms-url').textContent = serverUrl;
|
||||||
big.innerHTML = '';
|
document.getElementById('terms-box').classList.add('show');
|
||||||
new QRCode(big, { text: invoiceuri, width: 280, height: 280 });
|
document.getElementById('terms-box').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
overlay.classList.add('show');
|
|
||||||
};
|
|
||||||
document.getElementById('qr-overlay').onclick = function () {
|
|
||||||
this.classList.remove('show');
|
|
||||||
};
|
|
||||||
const shareBtn = document.getElementById('share-btn');
|
|
||||||
shareBtn.textContent = 'Share invoice';
|
|
||||||
shareBtn.onclick = function () {
|
|
||||||
if (navigator.share) {
|
|
||||||
navigator.share({ title: 'BAL Invoice', text: invoiceuri });
|
|
||||||
} else {
|
|
||||||
navigator.clipboard.writeText(invoiceuri).then(() => {
|
|
||||||
this.textContent = 'Copied!';
|
|
||||||
setTimeout(() => this.textContent = 'Share invoice', 2000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.getElementById('iv-status').className = 'ok-line';
|
|
||||||
document.getElementById('iv-status').textContent = '✓ Server verified: ' + serverUrl + '. Pay the invoice — it will be listed after 3 confirmations.';
|
|
||||||
const inv = document.getElementById('invoice');
|
|
||||||
inv.classList.add('show');
|
|
||||||
inv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
document.getElementById('iv-status').className = 'err-line';
|
document.getElementById('iv-status').className = 'err-line';
|
||||||
document.getElementById('iv-status').textContent = 'Network error. Please try again.';
|
document.getElementById('iv-status').textContent = 'Network error. Please try again.';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('terms-check').addEventListener('change', function () {
|
||||||
// Add / Check tabs: show the elements of one tab, clear any previous result.
|
document.getElementById('terms-accept').disabled = !this.checked;
|
||||||
(function () {
|
|
||||||
const panel = document.getElementById('we-panel');
|
|
||||||
const tabs = Array.from(panel.querySelectorAll('.mode-seg button'));
|
|
||||||
function setMode(mode) {
|
|
||||||
panel.dataset.mode = mode;
|
|
||||||
tabs.forEach(b => b.setAttribute('aria-selected', b.dataset.mode === mode ? 'true' : 'false'));
|
|
||||||
panel.querySelectorAll('[data-add]').forEach(el => { el.hidden = mode !== 'add'; });
|
|
||||||
panel.querySelectorAll('[data-check]').forEach(el => { el.hidden = mode !== 'check'; });
|
|
||||||
stopDepositWatch();
|
|
||||||
const status = document.getElementById('iv-status');
|
|
||||||
status.className = ''; status.textContent = '';
|
|
||||||
document.getElementById('terms-box').classList.remove('show');
|
|
||||||
document.getElementById('invoice').classList.remove('show');
|
|
||||||
document.getElementById('renewal-card').hidden = true;
|
|
||||||
}
|
|
||||||
tabs.forEach(b => b.addEventListener('click', () => setMode(b.dataset.mode)));
|
|
||||||
if (new URLSearchParams(location.search).get('mode') === 'check') {
|
|
||||||
setMode('check');
|
|
||||||
// arriving from the Terms FAQ: land on the panel, not at the top of the page
|
|
||||||
panel.scrollIntoView({ block: 'start' });
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
document.getElementById('url').addEventListener('keydown', e => {
|
|
||||||
if (e.key !== 'Enter') return;
|
|
||||||
const mode = document.getElementById('we-panel').dataset.mode;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('terms-accept').addEventListener('click', function () {
|
||||||
(async function init() {
|
document.getElementById('terms-box').classList.remove('show');
|
||||||
setChain(chainFromUrl());
|
const inv = document.getElementById('invoice');
|
||||||
|
document.getElementById('iv-status').className = 'ok-line';
|
||||||
|
document.getElementById('iv-status').textContent = '✓ Server added: ' + document.getElementById('iv-url').textContent + '. Pay the invoice to complete.';
|
||||||
|
inv.classList.add('show');
|
||||||
|
inv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('url').addEventListener('keydown', e => {
|
||||||
|
if (e.key === 'Enter') document.getElementById('submit').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
(function init() {
|
||||||
const blob = new Blob([JSON.stringify(allServers, null, 2)], { type: 'application/json' });
|
const blob = new Blob([JSON.stringify(allServers, null, 2)], { type: 'application/json' });
|
||||||
document.getElementById('download').href = URL.createObjectURL(blob);
|
document.getElementById('download').href = URL.createObjectURL(blob);
|
||||||
|
fetchData();
|
||||||
|
fetchMinAmount();
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
User-agent: *
|
|
||||||
Allow: /
|
|
||||||
|
|
||||||
Sitemap: https://welist.bitcoin-after.life/sitemap.xml
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
||||||
<url><loc>https://welist.bitcoin-after.life/</loc><lastmod>2026-08-14</lastmod><priority>1.0</priority></url>
|
|
||||||
<url><loc>https://welist.bitcoin-after.life/terms.html</loc><lastmod>2026-08-14</lastmod><priority>0.4</priority></url>
|
|
||||||
</urlset>
|
|
||||||
456
terms.html
456
terms.html
@@ -4,36 +4,12 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>BAL Server List — Terms & Conditions</title>
|
<title>BAL Server List — Terms & Conditions</title>
|
||||||
<meta name="description" content="Terms and conditions for listing a Will-Executor server on the Bitcoin After Life WeList directory.">
|
|
||||||
<link rel="canonical" href="https://welist.bitcoin-after.life/terms.html">
|
|
||||||
<meta property="og:type" content="website">
|
|
||||||
<meta property="og:site_name" content="Bitcoin After Life">
|
|
||||||
<meta property="og:title" content="Terms & Conditions — Bitcoin After Life WeList">
|
|
||||||
<meta property="og:description" content="Terms and conditions for listing a Will-Executor server on the Bitcoin After Life WeList directory.">
|
|
||||||
<meta property="og:url" content="https://welist.bitcoin-after.life/terms.html">
|
|
||||||
<meta property="og:image" content="https://bitcoin-after.life/pitura/og-default.png">
|
|
||||||
<meta property="og:image:width" content="1200">
|
|
||||||
<meta property="og:image:height" content="630">
|
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
|
||||||
<meta name="twitter:site" content="@BitcoinAfterLif">
|
|
||||||
<meta name="twitter:title" content="Terms & Conditions — Bitcoin After Life WeList">
|
|
||||||
<meta name="twitter:description" content="Terms and conditions for listing a Will-Executor server on the Bitcoin After Life WeList directory.">
|
|
||||||
<meta name="twitter:image" content="https://bitcoin-after.life/pitura/og-default.png">
|
|
||||||
<link rel="icon" href="Logo_nero.ico" type="image/x-icon">
|
<link rel="icon" href="Logo_nero.ico" type="image/x-icon">
|
||||||
<!--
|
<!--
|
||||||
Sections 2-4 rewritten on 2026-07 for the yearly-listing model
|
Sections 2-4 rewritten on 2026-07 for the yearly-listing model
|
||||||
(flat yearly fee, renewal from 6 months before expiry). The list
|
(flat yearly fee, renewal from 6 months before expiry, list
|
||||||
is ordered by total excess donated, then total amount paid, then
|
ordered by points). Section 1, header, footer and the dynamic
|
||||||
base fee, then WeTime points as a tie-breaker (see Section 3),
|
min-deposit script are unchanged from the previous version.
|
||||||
matching the server-side get_list() ordering. Section 1, header,
|
|
||||||
footer and the dynamic min-deposit script are unchanged from the
|
|
||||||
previous version.
|
|
||||||
2026-11-06: the page opens with an animated SVG timeline (.lt) of the
|
|
||||||
listing lifecycle, played when it scrolls into view; the four-step
|
|
||||||
.lifecycle strip is kept for small screens only. Sections reordered
|
|
||||||
(Listing, Order, Contact, Changes) and renumbered; the "see Section N"
|
|
||||||
references follow. The yearly fee in the SVG (#thr-min-lt) is updated by
|
|
||||||
the same script as #thr-min.
|
|
||||||
-->
|
-->
|
||||||
<style>
|
<style>
|
||||||
:root{
|
:root{
|
||||||
@@ -86,12 +62,6 @@ header.hero{padding:48px 0 28px;border-bottom:1px solid var(--line)}
|
|||||||
font-size:30px;line-height:1.05;margin:0;letter-spacing:-.01em;
|
font-size:30px;line-height:1.05;margin:0;letter-spacing:-.01em;
|
||||||
}
|
}
|
||||||
.brand .sub{color:var(--muted);font-size:14px;margin-top:2px}
|
.brand .sub{color:var(--muted);font-size:14px;margin-top:2px}
|
||||||
/* Way back to the server list (added 2026-11-06: the page had no obvious exit). */
|
|
||||||
.back-btn{display:inline-flex;align-items:center;gap:8px;padding:10px 16px;border-radius:10px;
|
|
||||||
border:1px solid var(--line-strong);background:var(--panel);color:var(--accent);
|
|
||||||
font-weight:600;font-size:14px;text-decoration:none;box-shadow:var(--shadow)}
|
|
||||||
.back-btn:hover{border-color:var(--accent)}
|
|
||||||
.back-btn svg{width:18px;height:18px;flex:none}
|
|
||||||
|
|
||||||
.mono{font-family:var(--mono);font-size:13px}
|
.mono{font-family:var(--mono);font-size:13px}
|
||||||
|
|
||||||
@@ -123,71 +93,6 @@ header.hero{padding:48px 0 28px;border-bottom:1px solid var(--line)}
|
|||||||
.step h3{font-size:14px;margin:0 0 6px;letter-spacing:.01em}
|
.step h3{font-size:14px;margin:0 0 6px;letter-spacing:.01em}
|
||||||
.step p{font-size:13px;color:var(--muted);margin:0;line-height:1.55}
|
.step p{font-size:13px;color:var(--muted);margin:0;line-height:1.55}
|
||||||
|
|
||||||
/* --- Added 2026-11-06: animated timeline of the listing lifecycle (top of the page).
|
|
||||||
Inline SVG + CSS keyframes, runs once and ends on the complete drawing;
|
|
||||||
prefers-reduced-motion shows the complete drawing at once. On small
|
|
||||||
screens the SVG text would be too small, so the four-step .lifecycle
|
|
||||||
strip is shown instead (see the 860px media query). */
|
|
||||||
/* ---- Listing timeline (SVG + CSS animation) ---- */
|
|
||||||
.lt{position:relative;grid-column:1 / -1;background:var(--panel);border:1px solid var(--line);
|
|
||||||
border-radius:var(--radius);padding:18px 20px 6px;box-shadow:var(--shadow)}
|
|
||||||
.lt svg{display:block;width:100%;height:auto;font-family:var(--body)}
|
|
||||||
.lt .replay{position:absolute;right:14px;top:14px;font:12px var(--body);color:var(--accent);background:var(--accent-soft);
|
|
||||||
border:1px solid #cfe0fb;border-radius:999px;padding:4px 10px;cursor:pointer}
|
|
||||||
.lt text{fill:var(--ink);font-size:13px}
|
|
||||||
.lt .small{font-size:12px;fill:var(--muted)}
|
|
||||||
.lt .mono{font-family:var(--mono);font-size:12px}
|
|
||||||
.lt .lbl-ok{fill:var(--ok);font-weight:600}
|
|
||||||
.lt .lbl-acc{fill:var(--accent);font-weight:600}
|
|
||||||
.lt .lbl-off{fill:var(--off);font-weight:600}
|
|
||||||
|
|
||||||
/* every animated piece starts hidden/empty and ends in its final state; the
|
|
||||||
animation runs once (fill-mode both) so the diagram is complete when it stops */
|
|
||||||
.lt .bar-y1{transform-origin:120px 0}
|
|
||||||
.lt .bar-y2{transform-origin:520px 0}
|
|
||||||
.lt .dot{transform:translateX(400px)}
|
|
||||||
/* .play is added by the script when the timeline scrolls into view (and by
|
|
||||||
Replay); the keyframes start from the empty state, so until then the
|
|
||||||
complete drawing is shown */
|
|
||||||
.lt.play .pill{animation:fade .85s ease both}
|
|
||||||
.lt.play .bar-y1{animation:grow 5.5s linear both}
|
|
||||||
.lt.play .bar-y2{animation:grow 4s linear both}
|
|
||||||
.lt.play .dot{animation:travel 5.5s linear both}
|
|
||||||
.lt.play .win{animation:fade 1s ease both}
|
|
||||||
.lt.play .out{animation:fade 1s ease both}
|
|
||||||
.lt.play .p1{animation-delay:.4s}
|
|
||||||
.lt.play .p2{animation-delay:1.8s}
|
|
||||||
.lt.play .p3{animation-delay:3.4s}
|
|
||||||
.lt.play .bar-y1,.lt.play .dot{animation-delay:4s}
|
|
||||||
.lt.play .win{animation-delay:6.8s}
|
|
||||||
.lt.play .out{animation-delay:9.6s}
|
|
||||||
.lt.play .bar-y2{animation-delay:10.5s}
|
|
||||||
.lt.play .pay{animation:fade .5s ease both}
|
|
||||||
.lt.play .pay1{animation-delay:7.8s}
|
|
||||||
.lt.play .pay2{animation-delay:13.4s}
|
|
||||||
@keyframes fade{from{opacity:0}to{opacity:1}}
|
|
||||||
@keyframes grow{from{transform:scaleX(0)}to{transform:scaleX(1)}}
|
|
||||||
@keyframes travel{from{transform:translateX(0)}to{transform:translateX(400px)}}
|
|
||||||
@media (prefers-reduced-motion:reduce){
|
|
||||||
.lt.play .pill,.lt.play .win,.lt.play .out,.lt.play .pay,.lt.play .bar-y1,.lt.play .bar-y2,.lt.play .dot{animation:none}
|
|
||||||
}
|
|
||||||
.lifecycle{display:none}
|
|
||||||
|
|
||||||
/* --- FAQ (2026-11-06): native accordions in the page's card style --- */
|
|
||||||
.faq{display:grid;gap:10px;counter-reset:faq}
|
|
||||||
.faq details{counter-increment:faq}
|
|
||||||
.faq summary::before{content:counter(faq);flex:none;width:26px;height:26px;border-radius:50%;background:var(--accent);color:#fff;
|
|
||||||
font-family:var(--mono);font-size:12px;display:inline-flex;align-items:center;justify-content:center}
|
|
||||||
.faq summary span{flex:1}
|
|
||||||
.faq details{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:0 20px;box-shadow:var(--shadow)}
|
|
||||||
.faq summary{cursor:pointer;padding:16px 0;font-weight:600;font-size:15px;list-style:none;display:flex;justify-content:space-between;align-items:center;gap:16px}
|
|
||||||
.faq summary::-webkit-details-marker{display:none}
|
|
||||||
.faq summary::after{content:"+";flex:none;width:24px;height:24px;border-radius:50%;background:var(--accent-soft);color:var(--accent);
|
|
||||||
display:inline-flex;align-items:center;justify-content:center;font-size:18px;line-height:1}
|
|
||||||
.faq details[open] summary::after{content:"\2212"}
|
|
||||||
.faq details[open] summary{border-bottom:1px solid var(--line)}
|
|
||||||
.faq p{margin:14px 0 18px;color:var(--muted);font-size:14px;line-height:1.6}
|
|
||||||
|
|
||||||
footer{border-top:1px solid var(--line);margin-top:48px;padding:28px 0;color:var(--muted);font-size:13px}
|
footer{border-top:1px solid var(--line);margin-top:48px;padding:28px 0;color:var(--muted);font-size:13px}
|
||||||
footer .wrap{display:flex;justify-content:space-between;flex-wrap:wrap;gap:12px}
|
footer .wrap{display:flex;justify-content:space-between;flex-wrap:wrap;gap:12px}
|
||||||
footer a{color:var(--accent);text-decoration:none}
|
footer a{color:var(--accent);text-decoration:none}
|
||||||
@@ -195,9 +100,8 @@ footer a{color:var(--accent);text-decoration:none}
|
|||||||
@media (max-width:860px){
|
@media (max-width:860px){
|
||||||
.info-grid{grid-template-columns:1fr}
|
.info-grid{grid-template-columns:1fr}
|
||||||
.brand h1{font-size:24px}
|
.brand h1{font-size:24px}
|
||||||
/* On small screens the four-step strip replaces the SVG timeline and stacks vertically */
|
/* On small screens the timeline stacks vertically like the other blocks */
|
||||||
.lt-section{display:none}
|
.lifecycle{grid-template-columns:1fr}
|
||||||
.lifecycle{display:grid;grid-template-columns:1fr}
|
|
||||||
.step{border-top:none;border-left:3px solid var(--line-strong);padding:0 0 4px 16px}
|
.step{border-top:none;border-left:3px solid var(--line-strong);padding:0 0 4px 16px}
|
||||||
.step:first-child{border-left-color:var(--accent)}
|
.step:first-child{border-left-color:var(--accent)}
|
||||||
.step::before{top:2px;left:-7px}
|
.step::before{top:2px;left:-7px}
|
||||||
@@ -206,118 +110,6 @@ footer a{color:var(--accent);text-decoration:none}
|
|||||||
@media (prefers-reduced-motion:reduce){*{transition:none!important}}
|
@media (prefers-reduced-motion:reduce){*{transition:none!important}}
|
||||||
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
||||||
</style>
|
</style>
|
||||||
<script type="application/ld+json">
|
|
||||||
{
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "FAQPage",
|
|
||||||
"mainEntity": [
|
|
||||||
{
|
|
||||||
"@type": "Question",
|
|
||||||
"name": "How much does it cost to be listed?",
|
|
||||||
"acceptedAnswer": {
|
|
||||||
"@type": "Answer",
|
|
||||||
"text": "50,000 sats for 365 days, paid one year at a time. The exact amount in satoshi is shown in the payment request. The fee can be reviewed once a year, around 17 July; time you have already bought stays valid under the conditions in force when you paid."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "Question",
|
|
||||||
"name": "When does my server appear in the list?",
|
|
||||||
"acceptedAnswer": {
|
|
||||||
"@type": "Answer",
|
|
||||||
"text": "As soon as your payment reaches 3 confirmations on the blockchain, typically around 30 minutes. Your 365 days start at that moment."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "Question",
|
|
||||||
"name": "I paid less than the yearly fee. What happens?",
|
|
||||||
"acceptedAnswer": {
|
|
||||||
"@type": "Answer",
|
|
||||||
"text": "A payment below the yearly fee is treated as a donation: it does not add listing time."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "Question",
|
|
||||||
"name": "When and how do I renew?",
|
|
||||||
"acceptedAnswer": {
|
|
||||||
"@type": "Answer",
|
|
||||||
"text": "Any time from 6 months before your expiry date, by paying the yearly fee for the exact same server URL as published in the list. The new year is added on top of the time you have left, so renewing early never costs you days."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "Question",
|
|
||||||
"name": "What happens if I do not renew?",
|
|
||||||
"acceptedAnswer": {
|
|
||||||
"@type": "Answer",
|
|
||||||
"text": "Your server is removed from the list at expiry. Users who already added it to their plugin keep it, because the plugin's server list is add-only. Being listed matters for reaching new users."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "Question",
|
|
||||||
"name": "Where do I see my expiry date?",
|
|
||||||
"acceptedAnswer": {
|
|
||||||
"@type": "Answer",
|
|
||||||
"text": "On the home page, open the “Renewal status” tab, enter the exact URL of your server as it appears in the list and press “Show renewal status”. Nothing is charged: you see the expiry date, the days left, whether the renewal window is open (or when it opens), the position in the list, the balance and the donations, with a link to the server page. The date is not shown in the main table, so you need the exact URL. Knowing it also tells you when your renewal window opens (6 months before). Since the price per day is fixed and public, the expiry date shows how much a server has paid: this is intentional transparency."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "Question",
|
|
||||||
"name": "How is the order of the list decided?",
|
|
||||||
"acceptedAnswer": {
|
|
||||||
"@type": "Answer",
|
|
||||||
"text": "By four criteria, in this order: total excess donated (higher first), total amount paid (higher first), base fee charged (lower first) and, as a tie-breaker, WeTime points (higher first). Excess donated never decays, so paying more than the exact yearly fee is the fastest way to move up."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "Question",
|
|
||||||
"name": "What are WeTime points?",
|
|
||||||
"acceptedAnswer": {
|
|
||||||
"@type": "Answer",
|
|
||||||
"text": "Points earned in WeTime rounds: each executor periodically submits its block hash, and the faster the response within the same block, the more points. They are not transferable, have no monetary value and only break ties in this list. They measure presence and reactivity, not how a server will handle inheritance transactions."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "Question",
|
|
||||||
"name": "Can I change my server URL?",
|
|
||||||
"acceptedAnswer": {
|
|
||||||
"@type": "Answer",
|
|
||||||
"text": "Write to welist@bitcoin-after.life. Any change to a listing requires proof that you control both the domain and the server; we may ask for a message signed with the private key of the UTXO used for the payment."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "Question",
|
|
||||||
"name": "Can I get a refund?",
|
|
||||||
"acceptedAnswer": {
|
|
||||||
"@type": "Answer",
|
|
||||||
"text": "No. Once a payment is confirmed on the blockchain and the server is listed, no refunds are issued. Double-check that your server URL is correct and reachable before paying."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "Question",
|
|
||||||
"name": "Is there a limit to the number of servers in the list?",
|
|
||||||
"acceptedAnswer": {
|
|
||||||
"@type": "Answer",
|
|
||||||
"text": "No. There is no fixed number of slots: every server with remaining listing time is shown."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "Question",
|
|
||||||
"name": "I had a balance from before the yearly model. What happened to it?",
|
|
||||||
"acceptedAnswer": {
|
|
||||||
"@type": "Answer",
|
|
||||||
"text": "Balances deposited under the previous rules were converted into listing time at the same yearly rate (for example, 5,000 sats became 36.5 days). Those countdowns started on 17 July 2026; to stay listed beyond that time, renew as usual."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "Question",
|
|
||||||
"name": "Do I need to be in this list to run a Will Executor?",
|
|
||||||
"acceptedAnswer": {
|
|
||||||
"@type": "Answer",
|
|
||||||
"text": "No. This list is a convenience service: the BAL protocol does not depend on it. Users can add any Will Executor manually in the plugin, import custom lists or use other list services, and anyone is free to run one."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -419,119 +211,31 @@ l-49 -25 -880 0 -880 0 -3 704 c-2 557 0 706 10 713 7 4 407 7 888 5 818 -2
|
|||||||
<div class="sub">Bitcoin After Life · Will Executor List</div>
|
<div class="sub">Bitcoin After Life · Will Executor List</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a class="back-btn" href="/"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 17l-5-5 5-5"/><path d="M6 12h6"/><path d="M15 6h4M15 12h4M15 18h4"/></svg>Back to the server list</a>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="wrap">
|
<main class="wrap">
|
||||||
<!-- The listing lifecycle first: the animated timeline (desktop) with a
|
|
||||||
one-line intro. On small screens this section is hidden and the
|
|
||||||
four-step strip inside Section 1 takes its place. The animation
|
|
||||||
starts when the timeline scrolls into view (see the script). -->
|
|
||||||
<section class="info-section lt-section">
|
|
||||||
<h2>How a listing works</h2>
|
|
||||||
<p class="sub">From the payment to the renewal, in fifteen seconds. The details are in the sections below.</p>
|
|
||||||
<div class="lt" id="lt">
|
|
||||||
<button class="replay" type="button">↻ Replay</button>
|
|
||||||
<svg viewBox="0 0 1000 318" role="img" aria-labelledby="lt-title lt-desc">
|
|
||||||
<title id="lt-title">Listing timeline</title>
|
|
||||||
<desc id="lt-desc">Pay the yearly fee; after 3 confirmations the server is listed for 365 days. From 6 months before expiry you can renew: a new year is added on top. If you do not renew, the server is removed at expiry.</desc>
|
|
||||||
<defs>
|
|
||||||
<pattern id="hatch" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
|
|
||||||
<rect width="8" height="8" fill="#e6eefc"/><line x1="0" y1="0" x2="0" y2="8" stroke="#2d6cdf" stroke-opacity=".35" stroke-width="2"/>
|
|
||||||
</pattern>
|
|
||||||
</defs>
|
|
||||||
|
|
||||||
<!-- steps 1-3: pay, confirmations, listed -->
|
<section class="info-section">
|
||||||
<g class="pill p1">
|
<h2>1. Contact & Support</h2>
|
||||||
<rect x="20" y="22" width="180" height="40" rx="20" fill="#fff" stroke="#dfe4ea"/>
|
<p class="sub">For URL changes, refunds, or any issues related to your Will Executor listing, contact us at <a href="mailto:welist@bitcoin-after.life">welist@bitcoin-after.life</a>.</p>
|
||||||
<circle cx="42" cy="42" r="11" fill="#f7931a"/><text x="42" y="46.5" text-anchor="middle" font-size="13" font-weight="700" fill="#fff">₿</text>
|
<div class="info-grid">
|
||||||
<text x="62" y="38" font-weight="600">Pay the yearly fee</text>
|
<div class="info-block">
|
||||||
<text x="62" y="54" class="small mono"><tspan id="thr-min-lt">50,000</tspan> sats / 365 days</text>
|
<span class="ic">VERIFICATION</span>
|
||||||
</g>
|
<h3>Proof of Ownership</h3>
|
||||||
<g class="pill p2">
|
<p>Any request to modify a listing (including URL changes) requires proof that you control both the domain and the server. We reserve the right to request a message signed with the private key of the UTXO used for payment before processing any changes or refunds.</p>
|
||||||
<path d="M202 42 h26" stroke="#cbd2da" stroke-width="2"/>
|
</div>
|
||||||
<polygon points="226,37 234,42 226,47" fill="#cbd2da"/>
|
<div class="info-block">
|
||||||
<rect x="238" y="22" width="186" height="40" rx="20" fill="#fff" stroke="#dfe4ea"/>
|
<span class="ic">POLICY</span>
|
||||||
<circle cx="260" cy="42" r="11" fill="none" stroke="#2d6cdf" stroke-width="2"/><path d="M260 35 v7 l5 3" stroke="#2d6cdf" stroke-width="2" fill="none" stroke-linecap="round"/>
|
<h3>No Refunds After Payment</h3>
|
||||||
<text x="280" y="38" font-weight="600">3 confirmations</text>
|
<p>Once a payment is confirmed on the blockchain and the server is listed, no refunds will be issued. Always verify that your server URL is correct and reachable before submitting payment.</p>
|
||||||
<text x="280" y="54" class="small">about 30 minutes</text>
|
</div>
|
||||||
</g>
|
|
||||||
<g class="pill p3">
|
|
||||||
<path d="M426 42 h26" stroke="#cbd2da" stroke-width="2"/>
|
|
||||||
<polygon points="450,37 458,42 450,47" fill="#cbd2da"/>
|
|
||||||
<rect x="462" y="22" width="190" height="40" rx="20" fill="#e3f4ea" stroke="#bfe3cd"/>
|
|
||||||
<circle cx="484" cy="42" r="11" fill="#1f9d57"/><path d="M478 42 l4 4 l8 -8" stroke="#fff" stroke-width="2.4" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<text x="504" y="38" font-weight="600" fill="#1f9d57">Listed</text>
|
|
||||||
<text x="504" y="54" class="small">your 365 days start now</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<!-- axis: 24 months, 0 at x=120, 12 at x=520, 24 at x=920 -->
|
|
||||||
<line x1="120" y1="136" x2="940" y2="136" stroke="#cbd2da" stroke-width="2"/>
|
|
||||||
<polygon points="938,131 948,136 938,141" fill="#cbd2da"/>
|
|
||||||
<g class="small" text-anchor="middle">
|
|
||||||
<line x1="120" y1="130" x2="120" y2="142" stroke="#9aa4b0"/><text x="120" y="182">day 0</text>
|
|
||||||
<line x1="320" y1="130" x2="320" y2="142" stroke="#9aa4b0"/><text x="320" y="182">6 months</text>
|
|
||||||
<line x1="520" y1="130" x2="520" y2="142" stroke="#9aa4b0"/><text x="520" y="182" font-weight="600" fill="#1a2027">12 months · expiry</text>
|
|
||||||
<line x1="720" y1="130" x2="720" y2="142" stroke="#9aa4b0"/><text x="720" y="182">18 months</text>
|
|
||||||
<line x1="920" y1="130" x2="920" y2="142" stroke="#9aa4b0"/><text x="920" y="182">24 months</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<!-- year 1: listed (fills from day 0 to month 12) -->
|
|
||||||
<rect class="bar-y1" x="120" y="124" width="400" height="24" rx="6" fill="#1f9d57" fill-opacity=".85"/>
|
|
||||||
<text x="220" y="140" text-anchor="middle" font-size="13" font-weight="600" fill="#fff" style="pointer-events:none">Listed · year 1</text>
|
|
||||||
<circle class="dot" cx="120" cy="136" r="7" fill="#fff" stroke="#1f9d57" stroke-width="3"/>
|
|
||||||
|
|
||||||
<!-- renewal window: months 6-12, glued under the bar -->
|
|
||||||
<g class="win">
|
|
||||||
<!-- the window marked on the bar itself, and two guides down to its caption -->
|
|
||||||
<rect x="320" y="124" width="200" height="24" rx="6" fill="none" stroke="#2d6cdf" stroke-width="2" stroke-dasharray="5 4"/>
|
|
||||||
<rect x="320" y="150" width="200" height="16" fill="url(#hatch)" stroke="#2d6cdf" stroke-opacity=".5" rx="3"/>
|
|
||||||
<g class="pay pay1"><text x="420" y="114" text-anchor="middle" class="small" fill="#1a2027" font-weight="600">Renewal paid</text><circle cx="420" cy="136" r="9" fill="#f7931a" stroke="#fff" stroke-width="2"/><text x="420" y="140" text-anchor="middle" font-size="11" font-weight="700" fill="#fff">₿</text></g>
|
|
||||||
<text x="320" y="204" class="lbl-acc">Renewal window</text>
|
|
||||||
<text x="320" y="220" class="small">from 6 months before expiry: pay the yearly fee again</text>
|
|
||||||
<text x="320" y="235" class="small">for the same URL — the new year is added on top.</text>
|
|
||||||
<text x="320" y="250" class="small">Renewing early never costs you days.</text>
|
|
||||||
<text x="320" y="265" class="small">The same window opens every year.</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<!-- the two outcomes at month 12 -->
|
|
||||||
<g class="out">
|
|
||||||
<!-- renewed: the bar continues -->
|
|
||||||
<rect class="bar-y2" x="520" y="124" width="400" height="24" rx="6" fill="#1f9d57" fill-opacity=".55"/>
|
|
||||||
<text x="620" y="140" text-anchor="middle" font-size="13" font-weight="600" fill="#fff">Renewed · year 2</text>
|
|
||||||
<!-- the renewal window repeats every year: months 18-24 of year 2 -->
|
|
||||||
<rect x="720" y="124" width="200" height="24" rx="6" fill="none" stroke="#2d6cdf" stroke-width="2" stroke-dasharray="5 4"/>
|
|
||||||
<rect x="720" y="150" width="200" height="16" fill="url(#hatch)" stroke="#2d6cdf" stroke-opacity=".5" rx="3"/>
|
|
||||||
<g class="pay pay2"><text x="820" y="114" text-anchor="middle" class="small" fill="#1a2027" font-weight="600">Renewal paid</text><circle cx="820" cy="136" r="9" fill="#f7931a" stroke="#fff" stroke-width="2"/><text x="820" y="140" text-anchor="middle" font-size="11" font-weight="700" fill="#fff">₿</text></g>
|
|
||||||
<path d="M520 98 v18" stroke="#1f9d57" stroke-width="2"/>
|
|
||||||
<polygon points="515,116 520,124 525,116" fill="#1f9d57"/>
|
|
||||||
<text x="660" y="78" class="lbl-ok">Renewed in time</text>
|
|
||||||
<text x="660" y="93" class="small">+365 days added on top of the time left</text>
|
|
||||||
|
|
||||||
<!-- not renewed: removed at expiry -->
|
|
||||||
<path d="M524 156 h116 v58 h12" stroke="#9aa4b0" stroke-width="2" fill="none"/>
|
|
||||||
<polygon points="648,209 656,214 648,219" fill="#9aa4b0"/>
|
|
||||||
<rect x="660" y="200" width="292" height="28" rx="14" fill="#eceff3" stroke="#dfe4ea"/>
|
|
||||||
<line x1="676" y1="208" x2="688" y2="220" stroke="#9aa4b0" stroke-width="2.4" stroke-linecap="round"/>
|
|
||||||
<line x1="688" y1="208" x2="676" y2="220" stroke="#9aa4b0" stroke-width="2.4" stroke-linecap="round"/>
|
|
||||||
<text x="698" y="218" class="lbl-off">Not renewed → removed at expiry</text>
|
|
||||||
<text x="660" y="248" class="small">Users who already added your server keep it:</text>
|
|
||||||
<text x="660" y="263" class="small">the plugin list is add-only.</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<!-- note: the paid total never decays -->
|
|
||||||
<g>
|
|
||||||
<rect x="20" y="282" width="960" height="1" fill="#dfe4ea"/>
|
|
||||||
<text x="20" y="304" class="small"><tspan font-weight="600" fill="#1a2027">Your paid total never decays:</tspan> it is kept across renewals and sets your position in the list (Section 2). Payments below the yearly fee are donations and add no time.</text>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="info-section">
|
<section class="info-section">
|
||||||
<h2>1. Listing & Renewal</h2>
|
<h2>2. Listing & Renewal</h2>
|
||||||
<p class="sub">A listing works like a hosting plan: a flat yearly fee buys your server one year of presence in the list. Your paid amount accumulates and never decays — it is preserved across renewals and also determines your position in the list (see Section 2). Payments are non-refundable (see Section 3) — double-check your server URL before paying.</p>
|
<p class="sub">A listing works like a hosting plan: a flat yearly fee buys your server one year of presence in the list. No balances, no ranking by deposit, no daily decay. Payments are non-refundable (see Section 1) — double-check your server URL before paying.</p>
|
||||||
<div class="info-grid">
|
<div class="info-grid">
|
||||||
<div class="info-block">
|
<div class="info-block">
|
||||||
<span class="ic">01 · PAYMENT</span>
|
<span class="ic">01 · PAYMENT</span>
|
||||||
@@ -548,7 +252,7 @@ l-49 -25 -880 0 -880 0 -3 704 c-2 557 0 706 10 713 7 4 407 7 888 5 818 -2
|
|||||||
<div class="info-block">
|
<div class="info-block">
|
||||||
<span class="ic">03 · CAPACITY</span>
|
<span class="ic">03 · CAPACITY</span>
|
||||||
<h3>No Cap on Listings</h3>
|
<h3>No Cap on Listings</h3>
|
||||||
<p>There is no fixed number of slots: every server with remaining listing time is shown. Paying buys presence in the list; the total amount paid also affects your position in it (see Section 2).</p>
|
<p>There is no fixed number of slots: every server with remaining listing time is shown. Paying buys presence in the list, not a position in it (see Section 3).</p>
|
||||||
<p>Removal from the list does not affect users who already have your server in their plugin: the plugin's server list is add-only. Being listed matters for reaching new users.</p>
|
<p>Removal from the list does not affect users who already have your server in their plugin: the plugin's server list is add-only. Being listed matters for reaching new users.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-block">
|
<div class="info-block">
|
||||||
@@ -578,20 +282,14 @@ l-49 -25 -880 0 -880 0 -3 704 c-2 557 0 706 10 713 7 4 407 7 888 5 818 -2
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="info-section">
|
<section class="info-section">
|
||||||
<h2>2. List Order & Server Page</h2>
|
<h2>3. List Order & Server Page</h2>
|
||||||
<p class="sub">The list is ordered by four criteria: first by total excess donated (higher first), then by total amount paid in balance (higher first), then by the base fee charged (lower first), and finally by WeTime points (higher first) as a tie-breaker.</p>
|
<p class="sub">The list is ordered by points, earned by proving your server is online and synchronized. Payment does not affect position.</p>
|
||||||
<div class="info-grid">
|
<div class="info-grid">
|
||||||
<div class="info-block">
|
<div class="info-block">
|
||||||
<span class="ic">ORDER</span>
|
<span class="ic">ORDER</span>
|
||||||
<h3>How Position Is Determined</h3>
|
<h3>Points & WeTime</h3>
|
||||||
<p>Servers are sorted by four criteria, in order: first by the total excess donated (higher first), then by the total paid amount (higher first), then by the base fee they charge (lower first), and finally by WeTime points (higher first) to break any remaining ties.</p>
|
|
||||||
<p>Because excess donated comes first and never decays, over-paying or tipping more — on top of any payment — directly and permanently moves a server up the list. Paying more than the exact yearly fee is the fastest way to improve position.</p>
|
|
||||||
</div>
|
|
||||||
<div class="info-block">
|
|
||||||
<span class="ic">POINTS</span>
|
|
||||||
<h3>WeTime Points</h3>
|
|
||||||
<p>Points are earned by participating in WeTime rounds. Each executor periodically submits its block hash; the faster the response within the same block, the more points awarded. The win count tracks how many times an executor was the fastest responder.</p>
|
<p>Points are earned by participating in WeTime rounds. Each executor periodically submits its block hash; the faster the response within the same block, the more points awarded. The win count tracks how many times an executor was the fastest responder.</p>
|
||||||
<p>Points are not transferable, not redeemable and have no monetary value: within this list they act only as a tie-breaker. Points measure presence and reactivity — they do not certify how a server will handle inheritance transactions.</p>
|
<p>Points are not transferable, not redeemable and have no monetary value: they exist only to order this list. Points measure presence and reactivity — they do not certify how a server will handle inheritance transactions.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-block">
|
<div class="info-block">
|
||||||
<span class="ic">DETAILS</span>
|
<span class="ic">DETAILS</span>
|
||||||
@@ -601,23 +299,6 @@ l-49 -25 -880 0 -880 0 -3 704 c-2 557 0 706 10 713 7 4 407 7 888 5 818 -2
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="info-section">
|
|
||||||
<h2>3. Contact & Support</h2>
|
|
||||||
<p class="sub">For URL changes, refunds, or any issues related to your Will Executor listing, contact us at <a href="mailto:welist@bitcoin-after.life">welist@bitcoin-after.life</a>.</p>
|
|
||||||
<div class="info-grid">
|
|
||||||
<div class="info-block">
|
|
||||||
<span class="ic">VERIFICATION</span>
|
|
||||||
<h3>Proof of Ownership</h3>
|
|
||||||
<p>Any request to modify a listing (including URL changes) requires proof that you control both the domain and the server. We reserve the right to request a message signed with the private key of the UTXO used for payment before processing any changes or refunds.</p>
|
|
||||||
</div>
|
|
||||||
<div class="info-block">
|
|
||||||
<span class="ic">POLICY</span>
|
|
||||||
<h3>No Refunds After Payment</h3>
|
|
||||||
<p>Once a payment is confirmed on the blockchain and the server is listed, no refunds will be issued. Always verify that your server URL is correct and reachable before submitting payment.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="info-section">
|
<section class="info-section">
|
||||||
<h2>4. Changes to These Terms</h2>
|
<h2>4. Changes to These Terms</h2>
|
||||||
<p class="sub">We reserve the right to update these terms at any time. Material changes will be announced on this page with their effective date.</p>
|
<p class="sub">We reserve the right to update these terms at any time. Material changes will be announced on this page with their effective date.</p>
|
||||||
@@ -634,104 +315,27 @@ l-49 -25 -880 0 -880 0 -3 704 c-2 557 0 706 10 713 7 4 407 7 888 5 818 -2
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<!-- FAQ (added 2026-11-06): the questions a reader asks first, answered only
|
|
||||||
with what the sections above already say. Native <details>, no script;
|
|
||||||
the same questions are in the FAQPage JSON-LD in the head. -->
|
|
||||||
<section class="info-section" id="faq">
|
|
||||||
<h2>5. Frequently Asked Questions</h2>
|
|
||||||
<p class="sub">Short answers to the questions we get most often. The sections above remain the reference.</p>
|
|
||||||
<div class="faq">
|
|
||||||
<details open>
|
|
||||||
<summary><span>How much does it cost to be listed?</span></summary>
|
|
||||||
<p>50,000 sats for 365 days, paid one year at a time. The exact amount in satoshi is shown in the payment request. The fee can be reviewed once a year, around 17 July; time you have already bought stays valid under the conditions in force when you paid.</p>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><span>When does my server appear in the list?</span></summary>
|
|
||||||
<p>As soon as your payment reaches 3 confirmations on the blockchain, typically around 30 minutes. Your 365 days start at that moment.</p>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><span>I paid less than the yearly fee. What happens?</span></summary>
|
|
||||||
<p>A payment below the yearly fee is treated as a donation: it does not add listing time.</p>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><span>When and how do I renew?</span></summary>
|
|
||||||
<p>Any time from 6 months before your expiry date, by paying the yearly fee for the exact same server URL as published in the list. The new year is added on top of the time you have left, so renewing early never costs you days.</p>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><span>What happens if I do not renew?</span></summary>
|
|
||||||
<p>Your server is removed from the list at expiry. Users who already added it to their plugin keep it, because the plugin's server list is add-only. Being listed matters for reaching new users.</p>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><span>Where do I see my expiry date?</span></summary>
|
|
||||||
<p>On the home page, open the “Renewal status” tab, enter the exact URL of your server as it appears in the list and press “Show renewal status”. Nothing is charged: you see the expiry date, the days left, whether the renewal window is open (or when it opens), the position in the list, the balance and the donations, with a link to the server page. The date is not shown in the main table, so you need the exact URL. Knowing it also tells you when your renewal window opens (6 months before). Since the price per day is fixed and public, the expiry date shows how much a server has paid: this is intentional transparency. <a href="/?mode=check#we-panel">Open the Renewal status tab →</a></p>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><span>How is the order of the list decided?</span></summary>
|
|
||||||
<p>By four criteria, in this order: total excess donated (higher first), total amount paid (higher first), base fee charged (lower first) and, as a tie-breaker, WeTime points (higher first). Excess donated never decays, so paying more than the exact yearly fee is the fastest way to move up.</p>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><span>What are WeTime points?</span></summary>
|
|
||||||
<p>Points earned in WeTime rounds: each executor periodically submits its block hash, and the faster the response within the same block, the more points. They are not transferable, have no monetary value and only break ties in this list. They measure presence and reactivity, not how a server will handle inheritance transactions.</p>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><span>Can I change my server URL?</span></summary>
|
|
||||||
<p>Write to welist@bitcoin-after.life. Any change to a listing requires proof that you control both the domain and the server; we may ask for a message signed with the private key of the UTXO used for the payment.</p>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><span>Can I get a refund?</span></summary>
|
|
||||||
<p>No. Once a payment is confirmed on the blockchain and the server is listed, no refunds are issued. Double-check that your server URL is correct and reachable before paying.</p>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><span>Is there a limit to the number of servers in the list?</span></summary>
|
|
||||||
<p>No. There is no fixed number of slots: every server with remaining listing time is shown.</p>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><span>I had a balance from before the yearly model. What happened to it?</span></summary>
|
|
||||||
<p>Balances deposited under the previous rules were converted into listing time at the same yearly rate (for example, 5,000 sats became 36.5 days). Those countdowns started on 17 July 2026; to stay listed beyond that time, renew as usual.</p>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><span>Do I need to be in this list to run a Will Executor?</span></summary>
|
|
||||||
<p>No. This list is a convenience service: the BAL protocol does not depend on it. Users can add any Will Executor manually in the plugin, import custom lists or use other list services, and anyone is free to run one.</p>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<span>Bitcoin After Life — Will Executor List</span>
|
<span>Bitcoin After Life — Will Executor List</span>
|
||||||
<a href="/">← Back to the server list</a>
|
|
||||||
<a href="mailto:welist@bitcoin-after.life">welist@bitcoin-after.life</a>
|
<a href="mailto:welist@bitcoin-after.life">welist@bitcoin-after.life</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
|
||||||
// Listing timeline: play once when it scrolls into view; Replay restarts it.
|
|
||||||
var lt = document.getElementById('lt');
|
|
||||||
if (!lt || !('IntersectionObserver' in window)) { if (lt) lt.classList.add('play'); return; }
|
|
||||||
var play = function () { lt.classList.remove('play'); void lt.offsetWidth; lt.classList.add('play'); };
|
|
||||||
var io = new IntersectionObserver(function (entries) {
|
|
||||||
if (entries.some(function (e) { return e.isIntersecting; })) { play(); io.disconnect(); }
|
|
||||||
}, { threshold: 0.5 });
|
|
||||||
io.observe(lt);
|
|
||||||
lt.querySelector('.replay').addEventListener('click', play);
|
|
||||||
})();
|
|
||||||
(async function() {
|
(async function() {
|
||||||
// Unchanged from the previous version: fetches the current yearly fee
|
// Unchanged from the previous version: fetches the current yearly fee
|
||||||
// from the server so the number on this page always matches reality.
|
// from the server so the number on this page always matches reality.
|
||||||
// The #thr-min span is inside Section 1 (01 · PAYMENT).
|
// The #thr-min span is now inside Section 2 (01 · PAYMENT).
|
||||||
const chain = location.hash ? location.hash.slice(1) : 'bitcoin';
|
const chain = location.hash ? location.hash.slice(1) : 'bitcoin';
|
||||||
try {
|
try {
|
||||||
const r = await fetch('/ranking_info/' + chain);
|
const r = await fetch('/min_deposit/' + chain);
|
||||||
const data = await r.json();
|
const val = parseInt(await r.text(), 10);
|
||||||
const val = parseInt(data.amount, 10);
|
|
||||||
if (!isNaN(val)) {
|
if (!isNaN(val)) {
|
||||||
const txt = Number(val).toLocaleString('en-US');
|
document.getElementById('thr-min').textContent = Number(val).toLocaleString('en-US');
|
||||||
document.getElementById('thr-min').textContent = txt;
|
|
||||||
const svgFee = document.getElementById('thr-min-lt');
|
|
||||||
if (svgFee) svgFee.textContent = txt;
|
|
||||||
}
|
}
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
})();
|
})();
|
||||||
|
|||||||
428
weinfo.html
428
weinfo.html
@@ -1,367 +1,133 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-<|begin▁of▁sentence|>8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-w, initial-scale=1.0" />
|
||||||
<title>Will Executor — Server page</title>
|
<title>Bal Server List</title>
|
||||||
<meta name="robots" content="noindex">
|
<link rel="icon" href="Logo_nero.ico" type="image/x-icon">
|
||||||
<link rel="icon" href="/Logo_nero.ico" type="image/x-icon">
|
<script src="/qrcode.min.js"></script>
|
||||||
<script src="/purify.min.js"></script>
|
<script src="/purify.min.js"></script>
|
||||||
<script src="/bal.js"></script>
|
<script src="/bal.js"></script>
|
||||||
<!--
|
</head>
|
||||||
Server detail page, restyled 2026-11-06 in the look of index.html (same colour
|
<style>
|
||||||
variables and type). Deliberately light: inline CSS only, no new script
|
table {
|
||||||
(qrcode.min.js was loaded and never used, so it is gone). The data flow is
|
border-collapse: collapse;
|
||||||
unchanged: fetchDataAndPopulateTable (bal.js) reads /weinfodata/<chain>/<id>
|
width: 100%;
|
||||||
with ?page/?limit and draws the prev/next links into #paginator; the payment
|
margin-top: 20px;
|
||||||
banner polls the same endpoint until the deposit is credited.
|
}
|
||||||
-->
|
th, td {
|
||||||
<style>
|
border: 1px solid #ddd;
|
||||||
:root{
|
padding: 8px;
|
||||||
--maxw:1120px;--radius:10px;
|
text-align: left;
|
||||||
--bg:#eef1f4;--panel:#ffffff;--ink:#1a2027;--muted:#5b6672;
|
}
|
||||||
--line:#dfe4ea;--line-strong:#cbd2da;
|
th {
|
||||||
--accent:#2d6cdf;--accent-soft:#e6eefc;
|
background-color: #f2f2f2;
|
||||||
--ok:#1f9d57;--ok-soft:#e3f4ea;--off:#9aa4b0;--off-soft:#eceff3;
|
}
|
||||||
--display:"Inter","Segoe UI",system-ui,-apple-system,sans-serif;
|
tr:nth-child(even) {
|
||||||
--body:"Inter","Segoe UI",system-ui,-apple-system,sans-serif;
|
background-color: #f9f9f9;
|
||||||
--mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
|
}
|
||||||
--shadow:0 1px 2px rgba(20,30,50,.05);
|
span{
|
||||||
}
|
margin-left:10px;
|
||||||
*{box-sizing:border-box}
|
}
|
||||||
html,body{margin:0}
|
</style>
|
||||||
body{background:var(--bg);color:var(--ink);font-family:var(--body);font-size:15px;line-height:1.5}
|
<body>
|
||||||
a{color:var(--accent)}
|
<div align="center">
|
||||||
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 20px}
|
<a href="/"><img alt="BAL LOGO" src="/Logo_nero.png"></a>
|
||||||
.mono{font-family:var(--mono)}
|
<h2 id="wename"> </h2>
|
||||||
|
<h3 id="weinfo"> </h3>
|
||||||
header.hero{padding:28px 0 22px;border-bottom:1px solid var(--line);background:var(--panel)}
|
<h4 id="wechain"> </h4>
|
||||||
.hero .wrap{display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap}
|
|
||||||
.brand{display:flex;align-items:center;gap:14px;text-decoration:none;color:inherit}
|
|
||||||
.brand .mark{width:44px;height:44px;border-radius:12px;border:1px solid var(--line);display:flex;align-items:center;justify-content:center;background:#fff}
|
|
||||||
.brand .mark img{width:30px;height:30px;display:block}
|
|
||||||
.brand h1{font-family:var(--display);font-size:18px;font-weight:600;margin:0;letter-spacing:-.01em}
|
|
||||||
.brand .sub{color:var(--muted);font-size:13px}
|
|
||||||
.back-btn{display:inline-flex;align-items:center;gap:8px;padding:9px 14px;border-radius:10px;border:1px solid var(--line-strong);
|
|
||||||
background:var(--panel);color:var(--accent);font-weight:600;font-size:14px;text-decoration:none;box-shadow:var(--shadow)}
|
|
||||||
.back-btn:hover{border-color:var(--accent)}
|
|
||||||
.back-btn svg{width:18px;height:18px}
|
|
||||||
|
|
||||||
main{padding:28px 0 40px}
|
|
||||||
.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:22px 24px}
|
|
||||||
.server .url{font-family:var(--mono);font-size:22px;font-weight:600;word-break:break-all;margin:0 0 6px}
|
|
||||||
.server .info{color:var(--muted);margin:0 0 14px}
|
|
||||||
.badges{display:flex;flex-wrap:wrap;gap:8px}
|
|
||||||
.badge{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-family:var(--mono);padding:4px 10px;border-radius:999px;
|
|
||||||
background:var(--off-soft);color:var(--muted);border:1px solid var(--line)}
|
|
||||||
.badge.ok{background:var(--ok-soft);color:var(--ok);border-color:#bfe3cd}
|
|
||||||
.badge.ok::before{content:"";width:8px;height:8px;border-radius:50%;background:var(--ok)}
|
|
||||||
.badge.bad{background:#fdecec;color:#b42318;border-color:#f5c2c0}
|
|
||||||
.badge.bad::before{content:"";width:8px;height:8px;border-radius:50%;background:#b42318}
|
|
||||||
|
|
||||||
#wepayment{display:none;margin:16px 0 0;padding:10px 14px;border-radius:8px;font-weight:600;font-size:14px}
|
|
||||||
|
|
||||||
.grid2{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.4fr);gap:20px;margin-top:20px}
|
|
||||||
.sec-title{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--accent);font-weight:600;margin:0 0 12px}
|
|
||||||
.expiry .big{font-family:var(--display);font-size:32px;font-weight:600;line-height:1.1;margin:0}
|
|
||||||
.expiry .days{color:var(--muted);font-size:13px;margin:2px 0 14px}
|
|
||||||
.kv{display:grid;grid-template-columns:1fr 1fr;gap:10px 18px;font-size:13px}
|
|
||||||
.kv div{display:flex;flex-direction:column;gap:2px;border-top:1px solid var(--line);padding-top:8px}
|
|
||||||
.kv span{color:var(--muted)}
|
|
||||||
.kv b{font-family:var(--mono);font-weight:600}
|
|
||||||
.open{color:var(--ok)}
|
|
||||||
.tiles{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
|
|
||||||
.tile{border:1px solid var(--line);border-radius:8px;padding:12px 14px;background:var(--bg)}
|
|
||||||
.tile .n{font-family:var(--display);font-size:22px;font-weight:600;line-height:1.1}
|
|
||||||
.tile .l{font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-top:4px}
|
|
||||||
.tile.wide{grid-column:1 / -1;display:flex;justify-content:space-between;align-items:center;gap:12px}
|
|
||||||
.tile.wide .n{font-size:13px;font-family:var(--mono)}
|
|
||||||
|
|
||||||
.rounds{margin-top:20px;padding:0;overflow:hidden}
|
|
||||||
.rounds .head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;padding:16px 24px;border-bottom:1px solid var(--line)}
|
|
||||||
.rounds .head h2{font-family:var(--display);font-size:16px;margin:0}
|
|
||||||
.rounds .head .note{color:var(--muted);font-size:13px}
|
|
||||||
.pager{display:flex;align-items:center;gap:6px;font-family:var(--mono);font-size:13px;color:var(--muted)}
|
|
||||||
#paginator a{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border:1px solid var(--line-strong);
|
|
||||||
border-radius:8px;background:var(--panel);color:var(--ink);text-decoration:none;font-weight:600}
|
|
||||||
#paginator a:hover{border-color:var(--accent);color:var(--accent)}
|
|
||||||
.pg-off{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border:1px solid var(--line);border-radius:8px;color:var(--off)}
|
|
||||||
.pg-label{padding:0 4px}
|
|
||||||
.table-wrap{overflow-x:auto}
|
|
||||||
table{border-collapse:collapse;width:100%;font-size:13px}
|
|
||||||
th{font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);text-align:left;
|
|
||||||
padding:10px 10px;border-bottom:1px solid var(--line);background:#fafbfc;white-space:nowrap}
|
|
||||||
td{padding:10px 10px;border-bottom:1px solid var(--line);white-space:nowrap;vertical-align:top}
|
|
||||||
tr:last-child td{border-bottom:0}
|
|
||||||
td.num,th.num{text-align:right;font-family:var(--mono)}
|
|
||||||
td.hash{font-family:var(--mono);color:var(--muted);font-size:12px;letter-spacing:-.01em}
|
|
||||||
.pos-1{color:var(--ok);font-weight:600}
|
|
||||||
.foot{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;padding:14px 24px;border-top:1px solid var(--line);font-size:13px;color:var(--muted)}
|
|
||||||
.btn-ghost{font:inherit;font-size:13px;text-decoration:none;padding:8px 12px;border-radius:8px;border:1px solid var(--line-strong);
|
|
||||||
background:var(--panel);color:var(--ink);display:inline-flex;gap:7px;align-items:center}
|
|
||||||
.btn-ghost:hover{border-color:var(--accent);color:var(--accent)}
|
|
||||||
|
|
||||||
footer{border-top:1px solid var(--line);margin-top:32px;padding:24px 0;color:var(--muted);font-size:13px}
|
|
||||||
footer .wrap{display:flex;justify-content:space-between;flex-wrap:wrap;gap:12px}
|
|
||||||
footer a{color:var(--accent);text-decoration:none}
|
|
||||||
|
|
||||||
@media (max-width:760px){
|
|
||||||
.grid2{grid-template-columns:1fr}
|
|
||||||
.tiles{grid-template-columns:repeat(2,1fr)}
|
|
||||||
.server .url{font-size:18px}
|
|
||||||
.expiry .big{font-size:28px}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<header class="hero">
|
|
||||||
<div class="wrap">
|
|
||||||
<a class="brand" href="/">
|
|
||||||
<span class="mark"><img alt="Bitcoin After Life" src="/Logo_nero.png"></span>
|
|
||||||
<span><h1>Will Executor — Server List</h1><span class="sub">Bitcoin After Life · server page</span></span>
|
|
||||||
</a>
|
|
||||||
<a class="back-btn" href="/"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 17l-5-5 5-5"/><path d="M6 12h6"/><path d="M15 6h4M15 12h4M15 18h4"/></svg>Back to the server list</a>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
<div style="float:left;border:1;margin-right:50px;">
|
||||||
|
<div><span>Version:</span><span id="weversion"> </span></div>
|
||||||
<main class="wrap">
|
<div><span>Fees:</span><span id="wefees"> </span></div>
|
||||||
|
<div><span>Tld:</span><span id="wetld"> </span></div>
|
||||||
<section class="card server">
|
|
||||||
<p class="url" id="wename"> </p>
|
|
||||||
<p class="info" id="weinfo"> </p>
|
|
||||||
<div class="badges">
|
|
||||||
<span class="badge" id="wechain"> </span>
|
|
||||||
<span class="badge"><span>v</span><span id="weversion"> </span></span>
|
|
||||||
<span class="badge" id="westatus-badge"><span id="westatus"> </span></span>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="wepayment"></div>
|
<div style="float:left;border:1;margin-right:50px;">
|
||||||
</section>
|
<div><span>Height:</span><span id="weheight"> </span></div>
|
||||||
|
<div><span>Points:</span><span id="wepoints"> </span></div>
|
||||||
<div class="grid2">
|
<div><span>Win:</span><span id="wewin"> </span></div>
|
||||||
<section class="card expiry">
|
|
||||||
<p class="sec-title">Listing</p>
|
|
||||||
<p class="big" id="weexpiry"> </p>
|
|
||||||
<p class="days" id="wedays"> </p>
|
|
||||||
<div class="kv">
|
|
||||||
<div><span>Renewal window</span><b id="wewindow">—</b></div>
|
|
||||||
<div><span>Balance</span><b id="webalance">—</b></div>
|
|
||||||
<div><span>Donations</span><b id="wedonations">—</b></div>
|
|
||||||
<div><span>Domain</span><b id="wetld">—</b></div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div style="border:1;margin-right:50px;">
|
||||||
|
<div><span>Balance:</span><span id="webalance"> </span></div>
|
||||||
<section class="card">
|
<div><span>Status:</span><span id="westatus"> </span></div>
|
||||||
<p class="sec-title">Activity</p>
|
<div><span>Last Update:</span><span id="weupdate"> </span></div>
|
||||||
<div class="tiles">
|
|
||||||
<div class="tile"><div class="n" id="wepoints">—</div><div class="l">Points</div></div>
|
|
||||||
<div class="tile"><div class="n" id="wewin">—</div><div class="l">Wins</div></div>
|
|
||||||
<div class="tile"><div class="n" id="wefees">—</div><div class="l">Fee (sats)</div></div>
|
|
||||||
<div class="tile"><div class="n" id="weheight">—</div><div class="l">Height</div></div>
|
|
||||||
<div class="tile wide"><div class="l" style="margin:0">Last update</div><div class="n" id="weupdate">—</div></div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<section class="card rounds">
|
|
||||||
<div class="head">
|
|
||||||
<div><h2>WeTime rounds</h2><div class="note">Each round: the block hash sent by this server and how fast it was. Chain: <span class="mono" id="chainth"></span>. The full list is in the download below.</div></div>
|
|
||||||
<!-- The API supports page offsets; bal.js draws the prev/next links here. -->
|
|
||||||
<div class="pager" id="paginator"></div>
|
|
||||||
</div>
|
|
||||||
<div class="table-wrap">
|
|
||||||
<table id="wetimes">
|
<table id="wetimes">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="num">Height</th>
|
<th colspan=5><span id="chainth"><!-- --></span>: <span id="paginator"></span></th>
|
||||||
<th>Block hash</th>
|
</tr>
|
||||||
<th>Time</th>
|
<tr>
|
||||||
<th class="num">Delay</th>
|
<th>height</th>
|
||||||
<th class="num">Position</th>
|
<th>block hash</th>
|
||||||
<th class="num">Participants</th>
|
<th>time</th>
|
||||||
<th class="num">Points</th>
|
<th>diff</th>
|
||||||
|
<th>position</th>
|
||||||
|
<th>participants</th>
|
||||||
|
<th>points</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody><!-- rows from the script --></tbody>
|
<tbody>
|
||||||
|
<!-- -->
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="foot">
|
<a href="" id="download">Download</a>
|
||||||
<span>Points reward fast answers within the same block; they only break ties in the list.</span>
|
|
||||||
<a class="btn-ghost" href="" id="download">↓ Download JSON</a>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<div class="wrap">
|
|
||||||
<span>Bitcoin After Life — Will Executor List</span>
|
|
||||||
<a href="/">← Server list</a>
|
|
||||||
<a href="/terms.html">Terms</a>
|
|
||||||
<a href="mailto:welist@bitcoin-after.life">welist@bitcoin-after.life</a>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
// Payment banner: this page has a fixed address, so whoever paid can close the
|
|
||||||
// invoice and follow the deposit here. It asks the server data every 30 seconds
|
|
||||||
// until the server is listed. The status only moves forward, because
|
|
||||||
// unconfirmed_balance may go back to 0 before the deposit is credited.
|
|
||||||
const PAYMENT_POLL_MS = 30000;
|
|
||||||
const PAYMENT_POLL_MAX_MS = 24 * 60 * 60 * 1000;
|
|
||||||
const paymentStarted = Date.now();
|
|
||||||
let paymentTimer = null, paymentSeen = false, paymentAmount = 0, firstExpiry;
|
|
||||||
|
|
||||||
const fmt = n => (n != null ? Number(n).toLocaleString('en-US') : '—');
|
|
||||||
const pad = n => String(n).padStart(2, '0');
|
|
||||||
function fmtTime(d) {
|
|
||||||
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' +
|
|
||||||
pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expiry card: the date big, the days left, the renewal window (from 6 months
|
|
||||||
// before expiry, Terms Section 1).
|
|
||||||
function showExpiry(expiryRaw) {
|
|
||||||
const expiry = (expiryRaw || '').slice(0, 10);
|
|
||||||
document.getElementById('weexpiry').textContent = expiry ? 'Expires ' + expiry : 'Not listed';
|
|
||||||
const daysEl = document.getElementById('wedays');
|
|
||||||
const winEl = document.getElementById('wewindow');
|
|
||||||
if (!expiry) { daysEl.textContent = 'No listing time: see the Terms to get listed.'; winEl.textContent = '—'; winEl.className = ''; return; }
|
|
||||||
const exp = new Date(expiry + 'T00:00:00Z');
|
|
||||||
const win = new Date(exp); win.setUTCMonth(win.getUTCMonth() - 6);
|
|
||||||
const days = Math.ceil((exp - Date.now()) / 86400000);
|
|
||||||
daysEl.textContent = days > 0 ? days + ' days of listing left' : 'Listing expired';
|
|
||||||
const open = Date.now() >= win.getTime() && days > 0;
|
|
||||||
winEl.textContent = open ? 'open now' : 'opens on ' + win.toISOString().slice(0, 10);
|
|
||||||
winEl.className = open ? 'open' : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function showPayment(text, ok) {
|
|
||||||
const box = document.getElementById('wepayment');
|
|
||||||
box.textContent = text;
|
|
||||||
box.style.display = 'block';
|
|
||||||
box.style.color = ok ? '#14703a' : '#7a5200';
|
|
||||||
box.style.background = ok ? '#e6f6ec' : '#fdf3dc';
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopPayment() {
|
|
||||||
if (paymentTimer) clearInterval(paymentTimer);
|
|
||||||
paymentTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pollPayment() {
|
|
||||||
if (Date.now() - paymentStarted > PAYMENT_POLL_MAX_MS) return stopPayment();
|
|
||||||
const path = window.location.pathname.split('/').filter(p => p !== '');
|
|
||||||
let data;
|
|
||||||
try {
|
|
||||||
const response = await fetch('/weinfodata/' + path[1] + '/' + path[2]);
|
|
||||||
if (!response.ok) return;
|
|
||||||
data = await response.json();
|
|
||||||
} catch (err) {
|
|
||||||
return; // temporary network problem: try again at the next round
|
|
||||||
}
|
|
||||||
if (!data) return;
|
|
||||||
const expiry = data.expiry_date || null;
|
|
||||||
if (firstExpiry === undefined) firstExpiry = expiry;
|
|
||||||
const unconfirmed = Number(data.unconfirmed_balance) > 0;
|
|
||||||
const active = expiry && new Date(expiry.replace(' ', 'T') + 'Z') > new Date();
|
|
||||||
const bal = data.balance != null ? fmt(data.balance) : null;
|
|
||||||
const unc = data.unconfirmed_balance != null ? fmt(data.unconfirmed_balance) : null;
|
|
||||||
document.getElementById('webalance').textContent = bal ? (unc ? bal + ' (' + unc + ')' : bal) : '—';
|
|
||||||
showExpiry(expiry);
|
|
||||||
if (expiry && expiry !== firstExpiry) {
|
|
||||||
showPayment('✓ Payment confirmed: this server is now listed.', true);
|
|
||||||
stopPayment();
|
|
||||||
} else if (unconfirmed || paymentSeen) {
|
|
||||||
paymentSeen = true;
|
|
||||||
if (unconfirmed) paymentAmount = Number(data.unconfirmed_balance);
|
|
||||||
showPayment('Payment detected: ' + paymentAmount.toLocaleString('en-US') + ' sats, waiting for 3 confirmations (about 30 minutes). This page updates by itself.', false);
|
|
||||||
} else if (active) {
|
|
||||||
stopPayment(); // already listed and nothing incoming: no banner, nothing to follow
|
|
||||||
} else {
|
|
||||||
showPayment('Waiting for payment. This page updates by itself.', false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
paymentTimer = setInterval(pollPayment, PAYMENT_POLL_MS);
|
|
||||||
pollPayment();
|
|
||||||
});
|
|
||||||
|
|
||||||
function getWeInfo(){
|
function getWeInfo(){
|
||||||
const path = window.location.pathname.split('/').filter(p => p !== '');
|
const path = window.location.pathname.split('/').filter(p => p !== '');
|
||||||
const we_id = path[2];
|
const we_id = path[2];
|
||||||
const chain = path[1];
|
const chain = path[1];
|
||||||
const cdate=new Date().toISOString().replace(/[-:T.Z]/g, '').slice(0, 14);
|
const cdate=new Date().toISOString().replace(/[-:T.Z]/g, '').slice(0, 14);
|
||||||
fetchDataAndPopulateTable('/weinfodata/'+chain+"/"+we_id, new URLSearchParams(),(data) => {
|
fetchDataAndPopulateTable('/weinfodata/'+chain+"/"+we_id, new URLSearchParams(),(data) => {
|
||||||
document.getElementById('wename').textContent = data.url || '—';
|
document.getElementById('wename').innerHTML = DOMPurify.sanitize(data.url);
|
||||||
document.title = (data.url || 'Server') + ' — Will Executor';
|
document.getElementById('wechain').innerHTML = DOMPurify.sanitize(data.chain);
|
||||||
document.getElementById('wechain').textContent = data.chain || chain;
|
document.getElementById('weinfo').innerHTML = DOMPurify.sanitize(data.info);
|
||||||
document.getElementById('weinfo').textContent = data.info || '';
|
document.getElementById('wefees').innerHTML = data.base_fee;
|
||||||
document.getElementById('wefees').textContent = fmt(data.base_fee);
|
document.getElementById('weheight').innerHTML = data.last_block;
|
||||||
document.getElementById('weheight').textContent = fmt(data.last_block);
|
document.getElementById('wepoints').innerHTML = data.points;
|
||||||
document.getElementById('wepoints').textContent = fmt(data.points);
|
document.getElementById('wewin').innerHTML = data.count_win;
|
||||||
document.getElementById('wewin').textContent = fmt(data.count_win);
|
document.getElementById('wetld').innerHTML = DOMPurify.sanitize(data.tld);
|
||||||
document.getElementById('wetld').textContent = data.tld || '—';
|
document.getElementById('webalance').innerHTML = data.balance;
|
||||||
const bal = data.balance != null ? fmt(data.balance) : null;
|
document.getElementById('weversion').innerHTML = data.version;;
|
||||||
const unc = data.unconfirmed_balance != null ? fmt(data.unconfirmed_balance) : null;
|
document.getElementById('westatus').innerHTML = DOMPurify.sanitize(data.status);
|
||||||
document.getElementById('webalance').textContent = bal ? (unc ? bal + ' (' + unc + ')' : bal) : '—';
|
document.getElementById('weupdate').innerHTML = data.last_update;
|
||||||
document.getElementById('wedonations').textContent = fmt(data.donation);
|
|
||||||
document.getElementById('weversion').textContent = data.version || '—';
|
|
||||||
document.getElementById('weupdate').textContent = data.last_update || '—';
|
|
||||||
showExpiry(data.expiry_date);
|
|
||||||
const statusMap = {
|
|
||||||
'OK': 'Health check passed',
|
|
||||||
'TIMEOUT': 'Connection timeout',
|
|
||||||
'CONNECTION': 'Connection error',
|
|
||||||
'STATUS': 'HTTP error',
|
|
||||||
'WRONG REPLY': 'Invalid JSON response',
|
|
||||||
'REQUEST': 'Request error',
|
|
||||||
'BODY': 'Body read error',
|
|
||||||
'DECODE': 'Decode error',
|
|
||||||
'KO': 'Generic error'
|
|
||||||
};
|
|
||||||
const st = (data.status||'').split(':')[0];
|
|
||||||
document.getElementById('westatus').textContent = statusMap[st] || (data.status ? 'Unknown status' : '—');
|
|
||||||
document.getElementById('westatus-badge').className = 'badge ' + (st === 'OK' ? 'ok' : (st ? 'bad' : ''));
|
|
||||||
|
|
||||||
const tableBody = document.querySelector('#wetimes tbody');
|
const tableBody = document.querySelector('#wetimes tbody');
|
||||||
tableBody.innerHTML ='';
|
tableBody.innerHTML ='';
|
||||||
if (data.times && data.times.length) {
|
|
||||||
Object.entries(data.times).forEach(([key,wetime]) => {
|
Object.entries(data.times).forEach(([key,wetime]) => {
|
||||||
const row = document.createElement('tr');
|
const row = document.createElement('tr');
|
||||||
const d = new Date(wetime.time/1000);
|
const d = new Date(wetime.time/1000);
|
||||||
const hash = DOMPurify.sanitize(wetime.blockHash||'');
|
|
||||||
const pos = wetime.position != null ? wetime.position + 1 : null;
|
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<td class="num">${wetime.blockHeight != null ? fmt(wetime.blockHeight) : '—'}</td>
|
<td>${wetime.blockHeight}</td>
|
||||||
<td class="hash">${hash || '—'}</td>
|
<td>${DOMPurify.sanitize(wetime.blockHash)}</td>
|
||||||
<td>${fmtTime(d)}</td>
|
<td>${d}</td>
|
||||||
<td class="num">${wetime.diff != null ? (wetime.diff/1000000).toFixed(3) + ' s' : '—'}</td>
|
<td>${wetime.diff/1000000} s</td>
|
||||||
<td class="num ${pos === 1 ? 'pos-1' : ''}">${pos != null ? '#' + pos : '—'}</td>
|
<td>${wetime.position+1}</td>
|
||||||
<td class="num">${wetime.participants != null ? wetime.participants : '—'}</td>
|
<td>${wetime.participants}</td>
|
||||||
<td class="num">${wetime.points != null ? wetime.points : '—'}</td>
|
<td>${wetime.points}</td>
|
||||||
`;
|
`;
|
||||||
tableBody.appendChild(row);
|
tableBody.appendChild(row);
|
||||||
});
|
})
|
||||||
} else {
|
var prev=0;
|
||||||
tableBody.innerHTML = '<tr><td colspan="7" style="padding:24px;text-align:center;color:var(--muted)">No rounds yet.</td></tr>';
|
var next=0;
|
||||||
|
if (data.times.length > 0){
|
||||||
|
prev = data.times[0].blockHeight+data.times.length;
|
||||||
|
next = data.times[data.times.length-1].blockHeight;
|
||||||
|
if (prev < 0 ){
|
||||||
|
prev = 0;
|
||||||
}
|
}
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
}
|
||||||
const currentPage = Math.max(parseInt(urlParams.get('page') || '0', 10) || 0, 0);
|
|
||||||
const pageSize = Math.min(Number(limit) || 100, 100);
|
const paginator = {prev:prev,next:next};
|
||||||
const hasNext = !!data.times && data.times.length >= pageSize;
|
return paginator;
|
||||||
return {
|
|
||||||
page: currentPage,
|
|
||||||
prev: Math.max(currentPage - 1, 0),
|
|
||||||
next: currentPage + 1,
|
|
||||||
hasPrev: currentPage > 0,
|
|
||||||
hasNext: hasNext,
|
|
||||||
};
|
|
||||||
},{elementName:"download",fileName:"weinfo_"+cdate+".json",linkText:"download"})
|
},{elementName:"download",fileName:"weinfo_"+cdate+".json",linkText:"download"})
|
||||||
}
|
}
|
||||||
|
|
||||||
getWeInfo();
|
getWeInfo();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user