zebra-report: grid-only layout — fix controls-eats-viewport bug, no more flex
This commit is contained in:
parent
03afea7dd6
commit
5d589d9986
3 changed files with 102 additions and 55 deletions
|
|
@ -53,11 +53,16 @@
|
|||
button:disabled { opacity: 0.3; cursor: default; }
|
||||
button.invert { background: #000; color: #fff; }
|
||||
button.invert:hover:not(:disabled) { background: #333; }
|
||||
.row { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.75rem; flex-wrap: wrap; }
|
||||
.row {
|
||||
display: grid; grid-auto-flow: column;
|
||||
grid-template-columns: max-content minmax(0, 1fr);
|
||||
grid-auto-columns: max-content;
|
||||
align-items: center; gap: 0.75rem; margin-bottom: 0.75rem;
|
||||
}
|
||||
.dot {
|
||||
width: 9px; height: 9px; border-radius: 50%;
|
||||
border: 1px solid #000; background: #fff;
|
||||
flex-shrink: 0; transition: background 0.15s;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.dot.on { background: #000; }
|
||||
.dot.warn { background: #888; }
|
||||
|
|
@ -71,8 +76,11 @@
|
|||
}
|
||||
input[type=text], input[type=password], select { width: 100%; }
|
||||
textarea { width: 100%; resize: vertical; min-height: 4.5rem; }
|
||||
.field-row { display: flex; gap: 0.5rem; margin-bottom: 0.5rem; }
|
||||
.field-row > input, .field-row > textarea { flex: 1; }
|
||||
.field-row {
|
||||
display: grid; grid-template-columns: minmax(0, 1fr) max-content;
|
||||
gap: 0.5rem; margin-bottom: 0.5rem;
|
||||
}
|
||||
.field-row > input, .field-row > textarea { width: 100%; min-width: 0; }
|
||||
.key-display {
|
||||
border: 1px solid #ccc; padding: 0.5rem;
|
||||
font-family: monospace; font-size: 0.7rem;
|
||||
|
|
@ -127,7 +135,8 @@
|
|||
}
|
||||
details[open] > summary { margin-bottom: 0.5rem; }
|
||||
.mode-toggle {
|
||||
display: inline-flex; border: 1px solid #000;
|
||||
display: inline-grid; grid-auto-flow: column; grid-auto-columns: max-content;
|
||||
border: 1px solid #000;
|
||||
font-size: 0.78rem; user-select: none;
|
||||
}
|
||||
.mode-toggle > label {
|
||||
|
|
@ -152,11 +161,14 @@
|
|||
.share-box .qr-canvas {
|
||||
width: 320px; height: 320px;
|
||||
border: 1px solid #ccc; background: #fff;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
display: grid; place-items: center;
|
||||
font-size: 0.65rem; color: #999;
|
||||
}
|
||||
.share-box input { width: 100%; font-size: 0.7rem; }
|
||||
.share-box .copy-row { display: flex; gap: 0.4rem; margin-top: 0.3rem; }
|
||||
.share-box .copy-row {
|
||||
display: grid; grid-template-columns: minmax(0, 1fr) max-content;
|
||||
gap: 0.4rem; margin-top: 0.3rem;
|
||||
}
|
||||
.share-box .copy-row button { font-size: 0.75rem; padding: 0.25rem 0.7rem; }
|
||||
@media (max-width: 600px) {
|
||||
.share-box { grid-template-columns: 1fr; }
|
||||
|
|
@ -250,7 +262,7 @@ try {
|
|||
<h2>identity</h2>
|
||||
<div class="row">
|
||||
<label for="handle-in">handle:</label>
|
||||
<input type="text" id="handle-in" style="flex:1; min-width:0" placeholder="your handle">
|
||||
<input type="text" id="handle-in" style="width:100%; min-width:0" placeholder="your handle">
|
||||
<span class="status-line" id="id-status"></span>
|
||||
</div>
|
||||
<div class="row" style="margin-top:0.5rem">
|
||||
|
|
@ -269,7 +281,7 @@ try {
|
|||
<h2>room (passphrase mode)</h2>
|
||||
<div class="row">
|
||||
<input type="password" id="passphrase-in" placeholder="room passphrase — everyone in the room types the same one"
|
||||
style="flex:1; min-width:0">
|
||||
style="width:100%; min-width:0">
|
||||
</div>
|
||||
<div class="row">
|
||||
<button id="btn-pp-join" class="invert">enter room</button>
|
||||
|
|
@ -307,12 +319,12 @@ try {
|
|||
</div>
|
||||
<div class="row" style="margin-top:0.4rem">
|
||||
<span class="note" style="min-width:3rem">tx</span>
|
||||
<div class="meter" style="flex:1"><div class="meter-fill" id="meter-tx"></div></div>
|
||||
<div class="meter" style="width:100%"><div class="meter-fill" id="meter-tx"></div></div>
|
||||
<span class="note" id="gain-label" style="min-width:3rem; text-align:right">0%</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="note" style="min-width:3rem">rx</span>
|
||||
<div class="meter" style="flex:1"><div class="meter-fill" id="meter-rx"></div></div>
|
||||
<div class="meter" style="width:100%"><div class="meter-fill" id="meter-rx"></div></div>
|
||||
<span class="note" id="rx-label" style="min-width:3rem; text-align:right">—</span>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -1720,8 +1732,8 @@ logLine('sys', 'chat content lives in encrypted SRTP audio. no IP packets carry
|
|||
</script>
|
||||
<footer style="max-width:820px;margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
|
||||
<span style="color:#777">page integrity</span> · built <span class="stamp-date">2026-06-02</span><br>
|
||||
md5 <span class="stamp-md5">0fb2dca42673efdde9164f5bc24d0200</span><br>
|
||||
sha256 <span class="stamp-sha">ba3b4ce8b96e2649f76f9b0338bde38ddcdbea148adbdfb0e23094a85999ee2b</span><br>
|
||||
md5 <span class="stamp-md5">5ae64bcf96a54da7ea55a0df88548920</span><br>
|
||||
sha256 <span class="stamp-sha">06a3c011ec9670d85c6ab27b8decbfcc4f78e8a9be8186407670fdf4e4cc8dfd</span><br>
|
||||
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
|
||||
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify it against these hashes; point it at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -39,14 +39,19 @@
|
|||
button:disabled { opacity: 0.3; cursor: default; }
|
||||
button.invert { background: #000; color: #fff; }
|
||||
button.invert:hover:not(:disabled) { background: #333; }
|
||||
.row { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.75rem; flex-wrap: wrap; }
|
||||
.row {
|
||||
display: grid; grid-auto-flow: column;
|
||||
grid-template-columns: max-content minmax(0, 1fr);
|
||||
grid-auto-columns: max-content;
|
||||
align-items: center; gap: 0.75rem; margin-bottom: 0.75rem;
|
||||
}
|
||||
input[type=text] {
|
||||
font-family: monospace; font-size: 0.9rem; border: 1px solid #000;
|
||||
padding: 0.45rem; background: #fff; color: #000; flex: 1; min-width: 0;
|
||||
padding: 0.45rem; background: #fff; color: #000; min-width: 0; width: 100%;
|
||||
}
|
||||
select {
|
||||
font-family: monospace; font-size: 0.9rem; border: 1px solid #000;
|
||||
padding: 0.45rem; background: #fff; color: #000; flex: 1; min-width: 0; cursor: pointer;
|
||||
padding: 0.45rem; background: #fff; color: #000; min-width: 0; width: 100%; cursor: pointer;
|
||||
}
|
||||
.dot { width: 10px; height: 10px; border-radius: 50%; border: 1px solid #000; background: #fff; flex-shrink: 0; }
|
||||
.dot.ok { background: #060; border-color: #060; }
|
||||
|
|
@ -703,7 +708,7 @@ else wirePuppet();
|
|||
<!-- secret: triple-click the footer "page integrity" seal -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="puppet" hidden style="max-width:560px;margin:1.8rem auto 0;border:1px dashed #000;padding:0.9rem 1rem">
|
||||
<h2 style="display:flex;align-items:center;justify-content:space-between">
|
||||
<h2 style="display:grid;grid-template-columns:1fr auto;align-items:center;gap:0.5rem">
|
||||
<span>puppet</span>
|
||||
<button id="puppet-close" style="font-size:0.7rem;padding:0.15rem 0.6rem">close</button>
|
||||
</h2>
|
||||
|
|
@ -727,8 +732,8 @@ else wirePuppet();
|
|||
|
||||
<footer style="max-width:560px;margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
|
||||
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> · built <span class="stamp-date">2026-06-02</span><br>
|
||||
md5 <span class="stamp-md5">97b7b9f30c280b37917e4cd94befc4ba</span><br>
|
||||
sha256 <span class="stamp-sha">1ed57fa7bca6be80d98ac418cccc30a6681b45341b36b6d263e9b090e7f3e8df</span><br>
|
||||
md5 <span class="stamp-md5">47f8c9b6c4c4e3d2dc0544cf59da1efc</span><br>
|
||||
sha256 <span class="stamp-sha">a9532ca9dbe91f11d63ece9073f558e3beef5e5ead524b85972c8f0436a8aa41</span><br>
|
||||
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
|
||||
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify it against these hashes; point it at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -101,23 +101,33 @@
|
|||
* Both columns get min-width:0 so they shrink without forcing a
|
||||
* horizontal scrollbar. */
|
||||
/* three columns: cameras (left) | timeline/screen (middle) | controls (right).
|
||||
* The 220px cameras track stays reserved even when no camera is live —
|
||||
* .hidden hides the section content but the column itself stays put so
|
||||
* the timeline (with the game iframe or a screen share) keeps its
|
||||
* middle position instead of sliding leftmost. */
|
||||
* Each child is pinned to its column with grid-column so any one of
|
||||
* them going display:none can't cause the others to slide. Controls
|
||||
* column is also clamped to ≤50vw so a very wide window doesn't let
|
||||
* the side panel eat the screen-share area. */
|
||||
.page {
|
||||
display: grid;
|
||||
grid-template-columns: 220px minmax(0, 1fr) minmax(0, 360px);
|
||||
grid-template-columns: 220px minmax(0, 1fr) minmax(0, min(360px, 50vw));
|
||||
gap: 1.25rem;
|
||||
align-items: start;
|
||||
}
|
||||
.cameras-col { grid-column: 1; }
|
||||
.timeline { grid-column: 2; }
|
||||
.controls { grid-column: 3; }
|
||||
.cameras-col {
|
||||
min-width: 0;
|
||||
border-right: 1px solid #ddd; padding-right: 1.25rem;
|
||||
display: flex; flex-direction: column;
|
||||
display: grid !important; /* beat the global .hidden util */
|
||||
grid-auto-rows: max-content;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
/* the cameras-col uses the .hidden class as a content-empty signal —
|
||||
* keep the column visible (track reserved) but hide its h2 + grid
|
||||
* and drop the right-border/padding so the empty column reads as
|
||||
* dead space rather than a divider for nothing */
|
||||
.cameras-col.hidden h2,
|
||||
.cameras-col.hidden #cameras { display: none; }
|
||||
.cameras-col.hidden { border-right: none; padding-right: 0; }
|
||||
/* hide-panel = focus mode: both side columns fold away, screen+timeline
|
||||
* fills the full viewport. Cameras and controls both go to display:none
|
||||
* so the screen-share / game iframe has nothing competing with it. */
|
||||
|
|
@ -138,10 +148,14 @@
|
|||
html.controls-collapsed-pref .page > .timeline { border-right: none; padding-right: 0; }
|
||||
.timeline {
|
||||
border-right: 1px solid #ddd; padding-right: 1.25rem; min-height: 60vh;
|
||||
min-width: 0; display: flex; flex-direction: column; position: relative;
|
||||
min-width: 0; display: grid; grid-auto-rows: max-content;
|
||||
position: relative;
|
||||
}
|
||||
.controls { min-width: 0; }
|
||||
.game-tabs { display: flex; gap: 0.4rem; margin-bottom: 0.5rem; flex-wrap: wrap; }
|
||||
.game-tabs {
|
||||
display: grid; grid-auto-flow: column; grid-auto-columns: max-content;
|
||||
gap: 0.4rem; margin-bottom: 0.5rem;
|
||||
}
|
||||
.game-tabs button {
|
||||
background: #fff; color: #000; border: 1px solid #000;
|
||||
padding: 0.3rem 0.8rem; font-family: monospace; font-size: 0.8rem; cursor: pointer;
|
||||
|
|
@ -149,19 +163,20 @@
|
|||
.game-tabs button.active { background: #000; color: #fff; }
|
||||
.game-tabs button:hover:not(.active) { background: #f0f0f0; }
|
||||
.timeline-frame {
|
||||
flex: 1; min-height: 80vh; width: 100%;
|
||||
min-height: 80vh; width: 100%;
|
||||
border: 1px solid #ddd; background: #fff;
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
body { padding: 1.25rem; }
|
||||
/* stack on mobile: controls first, then any live screen, cameras last */
|
||||
/* mobile: single column, all three sections stack to grid-column:1 */
|
||||
.page { grid-template-columns: minmax(0, 1fr); gap: 1rem; }
|
||||
.controls { order: 1; }
|
||||
.timeline { order: 2;
|
||||
border-right: none; border-top: 1px solid #ddd;
|
||||
padding-right: 0; padding-top: 1rem; min-height: 0; }
|
||||
.cameras-col { order: 3;
|
||||
width: 100%; border-right: none; border-top: 1px solid #ddd;
|
||||
.controls, .timeline, .cameras-col { grid-column: 1; }
|
||||
.controls { grid-row: 1; }
|
||||
.timeline { grid-row: 2;
|
||||
border-right: none; border-top: 1px solid #ddd;
|
||||
padding-right: 0; padding-top: 1rem; min-height: 0; }
|
||||
.cameras-col { grid-row: 3;
|
||||
border-right: none; border-top: 1px solid #ddd;
|
||||
padding-right: 0; padding-top: 1rem; }
|
||||
#cameras { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
|
||||
}
|
||||
|
|
@ -176,14 +191,14 @@
|
|||
#screens { display: grid; grid-template-columns: 1fr; gap: 0.75rem; }
|
||||
.screen-tile {
|
||||
border: 1px solid #000; background: #000; padding: 0;
|
||||
display: flex; flex-direction: column; position: relative;
|
||||
display: grid; grid-auto-rows: max-content; position: relative;
|
||||
}
|
||||
.screen-tile video {
|
||||
width: 100%; height: auto; max-height: 92vh; display: block; background: #000;
|
||||
}
|
||||
.screen-tile .screen-meta {
|
||||
background: #111; color: #ddd; font-size: 0.7rem; padding: 0.3rem 0.5rem;
|
||||
display: flex; justify-content: space-between;
|
||||
display: grid; grid-template-columns: 1fr auto; gap: 0.5rem; align-items: center;
|
||||
}
|
||||
/* Firefox Android rejects MediaStream <video> autoplay even when muted.
|
||||
* When play() rejects we surface a tap-to-play overlay over the video
|
||||
|
|
@ -191,11 +206,11 @@
|
|||
* gesture so the retry play() succeeds. */
|
||||
.screen-tile .tap-play {
|
||||
position: absolute; left: 0; right: 0; top: 0; bottom: 1.6rem;
|
||||
display: none; align-items: center; justify-content: center;
|
||||
display: none; place-items: center;
|
||||
background: rgba(0,0,0,0.65); color: #fff; cursor: pointer;
|
||||
font-family: monospace; font-size: 1rem; user-select: none;
|
||||
}
|
||||
.screen-tile.needs-tap .tap-play { display: flex; }
|
||||
.screen-tile.needs-tap .tap-play { display: grid; }
|
||||
/* sec-screens visible → push everything else in the timeline column down or hide */
|
||||
.timeline:has(> #sec-screens:not(.hidden)) > .game-tabs,
|
||||
.timeline:has(> #sec-screens:not(.hidden)) > #game-frame { display: none; }
|
||||
|
|
@ -205,7 +220,7 @@
|
|||
#cameras { display: grid; grid-template-columns: 1fr; gap: 0.45rem; }
|
||||
.camera-tile {
|
||||
border: 1px solid #000; background: #000; padding: 0;
|
||||
display: flex; flex-direction: column; position: relative;
|
||||
display: grid; grid-auto-rows: max-content; position: relative;
|
||||
}
|
||||
.camera-tile video {
|
||||
width: 100%; height: auto; display: block; background: #000;
|
||||
|
|
@ -213,16 +228,16 @@
|
|||
}
|
||||
.camera-tile .screen-meta {
|
||||
background: #111; color: #ddd; font-size: 0.7rem; padding: 0.2rem 0.4rem;
|
||||
display: flex; justify-content: space-between;
|
||||
display: grid; grid-template-columns: 1fr auto; gap: 0.4rem; align-items: center;
|
||||
}
|
||||
/* tap-to-play overlay applies to camera tiles too */
|
||||
.camera-tile .tap-play {
|
||||
position: absolute; left: 0; right: 0; top: 0; bottom: 1.2rem;
|
||||
display: none; align-items: center; justify-content: center;
|
||||
display: none; place-items: center;
|
||||
background: rgba(0,0,0,0.65); color: #fff; cursor: pointer;
|
||||
font-family: monospace; font-size: 0.9rem; user-select: none;
|
||||
}
|
||||
.camera-tile.needs-tap .tap-play { display: flex; }
|
||||
.camera-tile.needs-tap .tap-play { display: grid; }
|
||||
|
||||
/* latency panel: one row per live PC, fixed-width columns so the numbers
|
||||
* line up vertically as values bounce. lat-good < 50ms, mid < 150ms,
|
||||
|
|
@ -267,10 +282,22 @@
|
|||
button.invert { background: #000; color: #fff; }
|
||||
button.invert:hover:not(:disabled) { background: #333; }
|
||||
button.small { padding: 0.2rem 0.55rem; font-size: 0.75rem; }
|
||||
.row { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.6rem; flex-wrap: wrap; }
|
||||
/* form rows: inline strip with a single grow track for the input/select.
|
||||
* grid-template-columns is generous enough for the common shapes:
|
||||
* label input (1) label-stretchy
|
||||
* label input button (2) label-stretchy-button
|
||||
* button button span (3) buttons-stretchy
|
||||
* Items past the third just flow into the row via grid-auto-flow. */
|
||||
.row {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-template-columns: max-content minmax(0, 1fr);
|
||||
grid-auto-columns: max-content;
|
||||
align-items: center; gap: 0.6rem; margin-bottom: 0.6rem;
|
||||
}
|
||||
input[type=text], input[type=password] {
|
||||
font-family: monospace; font-size: 0.9rem; border: 1px solid #000;
|
||||
padding: 0.4rem; background: #fff; color: #000; flex: 1; min-width: 0;
|
||||
padding: 0.4rem; background: #fff; color: #000; min-width: 0; width: 100%;
|
||||
}
|
||||
textarea {
|
||||
font-family: monospace; font-size: 0.75rem; border: 1px solid #000;
|
||||
|
|
@ -279,9 +306,9 @@
|
|||
}
|
||||
select {
|
||||
font-family: monospace; font-size: 0.9rem; border: 1px solid #000;
|
||||
padding: 0.4rem; background: #fff; color: #000; flex: 1; min-width: 0; cursor: pointer;
|
||||
padding: 0.4rem; background: #fff; color: #000; min-width: 0; width: 100%; cursor: pointer;
|
||||
}
|
||||
.dot { width: 10px; height: 10px; border-radius: 50%; border: 1px solid #000; background: #fff; flex-shrink: 0; }
|
||||
.dot { width: 10px; height: 10px; border-radius: 50%; border: 1px solid #000; background: #fff; }
|
||||
.dot.ok { background: #060; border-color: #060; }
|
||||
.dot.warn { background: #888; }
|
||||
.status-line { font-size: 0.8rem; color: #555; }
|
||||
|
|
@ -314,9 +341,10 @@
|
|||
.mic { width: 18px; height: 18px; }
|
||||
.mic svg { width: 18px; height: 18px; display: block; }
|
||||
.mod-actions {
|
||||
display: flex; gap: 0.3rem; flex-wrap: wrap;
|
||||
display: grid; grid-auto-flow: column; grid-auto-columns: max-content;
|
||||
gap: 0.3rem;
|
||||
grid-column: 1 / -1; /* second row, full width */
|
||||
justify-content: flex-start;
|
||||
justify-content: start;
|
||||
padding-left: 6.05rem; /* line up under the handle (badge col + gap) */
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
|
@ -327,13 +355,15 @@
|
|||
.log-line.err { color: #b00; }
|
||||
.invite-banner {
|
||||
border: 2px solid #000; padding: 0.8rem; margin-bottom: 1rem; background: #ffd;
|
||||
display: flex; flex-direction: column; align-items: flex-start; gap: 0.6rem;
|
||||
display: grid; grid-auto-rows: max-content; gap: 0.6rem; justify-items: start;
|
||||
}
|
||||
.invite-banner .invite-actions {
|
||||
display: grid; grid-auto-flow: column; grid-auto-columns: max-content; gap: 0.5rem;
|
||||
}
|
||||
.invite-banner .invite-actions { display: flex; gap: 0.5rem; }
|
||||
.notice-banner {
|
||||
padding: 0.7rem 0.8rem; margin-bottom: 1rem;
|
||||
display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;
|
||||
font-size: 0.85rem;
|
||||
display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
|
||||
gap: 0.6rem; font-size: 0.85rem;
|
||||
}
|
||||
.notice-banner.warn { border: 2px solid #b00; background: #fee; color: #b00; }
|
||||
.notice-banner.info { border: 2px solid #060; background: #efe; color: #060; }
|
||||
|
|
@ -2255,8 +2285,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');
|
|||
|
||||
<footer style="margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
|
||||
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> · built <span class="stamp-date">2026-06-02</span><br>
|
||||
md5 <span class="stamp-md5">a6c36b06668308278c7645054fb5e432</span><br>
|
||||
sha256 <span class="stamp-sha">cc3fe5cbc7e83183066c4ea09d1bb9d261a1a75c1abe51edfd6f2898e44d5374</span><br>
|
||||
md5 <span class="stamp-md5">c58b4a17d9536b098d707ff650fbb9d3</span><br>
|
||||
sha256 <span class="stamp-sha">4a105138a9b6e25c913dbbf9217a94a65912eee678caaebb28a07e96877bf35f</span><br>
|
||||
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
|
||||
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify against these hashes; point at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
|
||||
</footer>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue