Justin Parsell 1 月之前
當前提交
7bf5499833
共有 9 個文件被更改,包括 854 次插入0 次删除
  1. 147 0
      error/403.html
  2. 147 0
      error/404.html
  3. 143 0
      error/503.html
  4. 二進制
      error/background.jpg
  5. 130 0
      error/default.html
  6. 二進制
      external/background.jpg
  7. 128 0
      external/index.html
  8. 二進制
      internal/background.jpg
  9. 159 0
      internal/index.html

+ 147 - 0
error/403.html

@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Access Blocked — parsell.me</title>
+<link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAzMiAzMic+PHJlY3Qgd2lkdGg9JzMyJyBoZWlnaHQ9JzMyJyByeD0nNycgZmlsbD0nIzFiMTgzMCcvPjxwYXRoIGQ9J00xNiA2IEwyNiAxNC41IEwyMy41IDE0LjUgTDIzLjUgMjUgTDE4LjUgMjUgTDE4LjUgMTguNSBMMTMuNSAxOC41IEwxMy41IDI1IEw4LjUgMjUgTDguNSAxNC41IEw2IDE0LjUgWicgZmlsbD0nI2ZmY2NiOCcvPjwvc3ZnPg==">
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
+<style>
+  /* Self-contained: error pages must render even if no other request succeeds.
+     Font + background tile are progressive-enhancement — system fonts and a
+     solid dark fill take over if they fail to load. */
+  * { box-sizing: border-box; margin: 0; padding: 0; }
+
+  html, body {
+    height: 100%;
+    font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
+    color: #fff;
+    overflow: hidden;
+  }
+
+  body {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 1.5rem;
+    background:
+      linear-gradient(rgba(20, 18, 32, 0.55), rgba(20, 18, 32, 0.55)),
+      #14121f url('/background.jpg') 0 0 / 960px repeat fixed;
+  }
+
+  .card {
+    max-width: 480px;
+    width: 100%;
+    background: rgba(255, 255, 255, 0.06);
+    backdrop-filter: blur(24px) saturate(160%);
+    -webkit-backdrop-filter: blur(24px) saturate(160%);
+    border: 1px solid rgba(255, 255, 255, 0.12);
+    border-radius: 24px;
+    padding: 2.5rem 2.25rem;
+    text-align: center;
+    box-shadow:
+      0 14px 50px rgba(0, 0, 0, 0.4),
+      inset 0 1px 0 rgba(255, 255, 255, 0.1);
+  }
+
+  .icon-circle {
+    width: 56px;
+    height: 56px;
+    margin: 0 auto 1.25rem;
+    border-radius: 50%;
+    background: rgba(247, 129, 102, 0.18);
+    color: #ffb199;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 1.75rem;
+    font-weight: 600;
+  }
+
+  .status {
+    font-family: "SF Mono", Menlo, Consolas, monospace;
+    font-size: 0.75rem;
+    color: #f78166;
+    letter-spacing: 0.1em;
+    text-transform: uppercase;
+    margin-bottom: 0.6rem;
+  }
+
+  h1 {
+    font-size: 1.6rem;
+    font-weight: 600;
+    letter-spacing: -0.02em;
+    margin-bottom: 0.75rem;
+  }
+
+  p {
+    color: rgba(255, 255, 255, 0.72);
+    line-height: 1.55;
+    font-size: 0.95rem;
+    margin-bottom: 0.75rem;
+  }
+
+  .host {
+    font-family: "SF Mono", Menlo, Consolas, monospace;
+    color: #fff;
+    background: rgba(255, 255, 255, 0.08);
+    padding: 0.15rem 0.4rem;
+    border-radius: 4px;
+    font-size: 0.88em;
+    word-break: break-all;
+  }
+
+  .btn {
+    display: inline-block;
+    margin-top: 1.25rem;
+    background: rgba(255, 255, 255, 0.05);
+    border: 1px solid rgba(255, 255, 255, 0.15);
+    color: #fff;
+    padding: 0.5rem 1.1rem;
+    border-radius: 8px;
+    font-size: 0.88rem;
+    cursor: pointer;
+    font-family: inherit;
+    text-decoration: none;
+    transition: border-color 0.15s, background 0.15s;
+  }
+
+  .btn:hover {
+    border-color: rgba(255, 255, 255, 0.3);
+    background: rgba(255, 255, 255, 0.12);
+  }
+
+  .btn + .btn { margin-left: 0.5rem; }
+
+  .footer {
+    margin-top: 1.75rem;
+    padding-top: 1.25rem;
+    border-top: 1px solid rgba(255, 255, 255, 0.1);
+    font-size: 0.8rem;
+    color: rgba(255, 255, 255, 0.55);
+    font-family: "SF Mono", Menlo, Consolas, monospace;
+  }
+</style>
+</head>
+<body>
+  <div class="card">
+    <div class="icon-circle">✕</div>
+    <div class="status">403 · Access Blocked</div>
+    <h1>Access blocked</h1>
+    <p>Your request to <span class="host" id="host">this address</span> was blocked by our security layer.</p>
+    <p>This is usually temporary. Wait a moment, then try again. If it keeps happening, your address may have been flagged.</p>
+    <a class="btn" href="https://parsell.me">Go home</a>
+    <button class="btn" onclick="location.reload()">Try again</button>
+    <div class="footer">parsell.me</div>
+  </div>
+<script>
+  (function () {
+    var loc = window.location.host + window.location.pathname;
+    if (loc.length > 80) loc = loc.slice(0, 77) + '...';
+    document.getElementById('host').textContent = loc;
+  })();
+</script>
+</body>
+</html>

+ 147 - 0
error/404.html

@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Not Found — parsell.me</title>
+<link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAzMiAzMic+PHJlY3Qgd2lkdGg9JzMyJyBoZWlnaHQ9JzMyJyByeD0nNycgZmlsbD0nIzFiMTgzMCcvPjxwYXRoIGQ9J00xNiA2IEwyNiAxNC41IEwyMy41IDE0LjUgTDIzLjUgMjUgTDE4LjUgMjUgTDE4LjUgMTguNSBMMTMuNSAxOC41IEwxMy41IDI1IEw4LjUgMjUgTDguNSAxNC41IEw2IDE0LjUgWicgZmlsbD0nI2ZmY2NiOCcvPjwvc3ZnPg==">
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
+<style>
+  /* Self-contained: error pages must render even if no other request succeeds.
+     Font + background tile are progressive-enhancement — system fonts and a
+     solid dark fill take over if they fail to load. */
+  * { box-sizing: border-box; margin: 0; padding: 0; }
+
+  html, body {
+    height: 100%;
+    font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
+    color: #fff;
+    overflow: hidden;
+  }
+
+  body {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 1.5rem;
+    background:
+      linear-gradient(rgba(20, 18, 32, 0.55), rgba(20, 18, 32, 0.55)),
+      #14121f url('/background.jpg') 0 0 / 960px repeat fixed;
+  }
+
+  .card {
+    max-width: 480px;
+    width: 100%;
+    background: rgba(255, 255, 255, 0.06);
+    backdrop-filter: blur(24px) saturate(160%);
+    -webkit-backdrop-filter: blur(24px) saturate(160%);
+    border: 1px solid rgba(255, 255, 255, 0.12);
+    border-radius: 24px;
+    padding: 2.5rem 2.25rem;
+    text-align: center;
+    box-shadow:
+      0 14px 50px rgba(0, 0, 0, 0.4),
+      inset 0 1px 0 rgba(255, 255, 255, 0.1);
+  }
+
+  .icon-circle {
+    width: 56px;
+    height: 56px;
+    margin: 0 auto 1.25rem;
+    border-radius: 50%;
+    background: rgba(247, 129, 102, 0.18);
+    color: #ffb199;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 1.75rem;
+    font-weight: 600;
+  }
+
+  .status {
+    font-family: "SF Mono", Menlo, Consolas, monospace;
+    font-size: 0.75rem;
+    color: #f78166;
+    letter-spacing: 0.1em;
+    text-transform: uppercase;
+    margin-bottom: 0.6rem;
+  }
+
+  h1 {
+    font-size: 1.6rem;
+    font-weight: 600;
+    letter-spacing: -0.02em;
+    margin-bottom: 0.75rem;
+  }
+
+  p {
+    color: rgba(255, 255, 255, 0.72);
+    line-height: 1.55;
+    font-size: 0.95rem;
+    margin-bottom: 0.75rem;
+  }
+
+  .host {
+    font-family: "SF Mono", Menlo, Consolas, monospace;
+    color: #fff;
+    background: rgba(255, 255, 255, 0.08);
+    padding: 0.15rem 0.4rem;
+    border-radius: 4px;
+    font-size: 0.88em;
+    word-break: break-all;
+  }
+
+  .btn {
+    display: inline-block;
+    margin-top: 1.25rem;
+    background: rgba(255, 255, 255, 0.05);
+    border: 1px solid rgba(255, 255, 255, 0.15);
+    color: #fff;
+    padding: 0.5rem 1.1rem;
+    border-radius: 8px;
+    font-size: 0.88rem;
+    cursor: pointer;
+    font-family: inherit;
+    text-decoration: none;
+    transition: border-color 0.15s, background 0.15s;
+  }
+
+  .btn:hover {
+    border-color: rgba(255, 255, 255, 0.3);
+    background: rgba(255, 255, 255, 0.12);
+  }
+
+  .btn + .btn { margin-left: 0.5rem; }
+
+  .footer {
+    margin-top: 1.75rem;
+    padding-top: 1.25rem;
+    border-top: 1px solid rgba(255, 255, 255, 0.1);
+    font-size: 0.8rem;
+    color: rgba(255, 255, 255, 0.55);
+    font-family: "SF Mono", Menlo, Consolas, monospace;
+  }
+</style>
+</head>
+<body>
+  <div class="card">
+    <div class="icon-circle">?</div>
+    <div class="status">404 · Not Found</div>
+    <h1>Nothing here</h1>
+    <p>There's nothing at <span class="host" id="host">this address</span>.</p>
+    <p>The link may be incorrect, or the page may have moved.</p>
+    <button class="btn" onclick="history.back()">Go back</button>
+    <a class="btn" href="https://parsell.me">Go home</a>
+    <div class="footer">parsell.me</div>
+  </div>
+<script>
+  (function () {
+    var loc = window.location.host + window.location.pathname;
+    if (loc.length > 80) loc = loc.slice(0, 77) + '...';
+    document.getElementById('host').textContent = loc;
+  })();
+</script>
+</body>
+</html>

+ 143 - 0
error/503.html

@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Service Unavailable — parsell.me</title>
+<link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAzMiAzMic+PHJlY3Qgd2lkdGg9JzMyJyBoZWlnaHQ9JzMyJyByeD0nNycgZmlsbD0nIzFiMTgzMCcvPjxwYXRoIGQ9J00xNiA2IEwyNiAxNC41IEwyMy41IDE0LjUgTDIzLjUgMjUgTDE4LjUgMjUgTDE4LjUgMTguNSBMMTMuNSAxOC41IEwxMy41IDI1IEw4LjUgMjUgTDguNSAxNC41IEw2IDE0LjUgWicgZmlsbD0nI2ZmY2NiOCcvPjwvc3ZnPg==">
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
+<style>
+  /* Self-contained: error pages must render even if no other request succeeds.
+     Font + background tile are progressive-enhancement — system fonts and a
+     solid dark fill take over if they fail to load. */
+  * { box-sizing: border-box; margin: 0; padding: 0; }
+
+  html, body {
+    height: 100%;
+    font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
+    color: #fff;
+    overflow: hidden;
+  }
+
+  body {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 1.5rem;
+    background:
+      linear-gradient(rgba(20, 18, 32, 0.55), rgba(20, 18, 32, 0.55)),
+      #14121f url('/background.jpg') 0 0 / 960px repeat fixed;
+  }
+
+  .card {
+    max-width: 480px;
+    width: 100%;
+    background: rgba(255, 255, 255, 0.06);
+    backdrop-filter: blur(24px) saturate(160%);
+    -webkit-backdrop-filter: blur(24px) saturate(160%);
+    border: 1px solid rgba(255, 255, 255, 0.12);
+    border-radius: 24px;
+    padding: 2.5rem 2.25rem;
+    text-align: center;
+    box-shadow:
+      0 14px 50px rgba(0, 0, 0, 0.4),
+      inset 0 1px 0 rgba(255, 255, 255, 0.1);
+  }
+
+  .icon-circle {
+    width: 56px;
+    height: 56px;
+    margin: 0 auto 1.25rem;
+    border-radius: 50%;
+    background: rgba(247, 129, 102, 0.18);
+    color: #ffb199;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 1.75rem;
+    font-weight: 600;
+  }
+
+  .status {
+    font-family: "SF Mono", Menlo, Consolas, monospace;
+    font-size: 0.75rem;
+    color: #f78166;
+    letter-spacing: 0.1em;
+    text-transform: uppercase;
+    margin-bottom: 0.6rem;
+  }
+
+  h1 {
+    font-size: 1.6rem;
+    font-weight: 600;
+    letter-spacing: -0.02em;
+    margin-bottom: 0.75rem;
+  }
+
+  p {
+    color: rgba(255, 255, 255, 0.72);
+    line-height: 1.55;
+    font-size: 0.95rem;
+    margin-bottom: 0.75rem;
+  }
+
+  .host {
+    font-family: "SF Mono", Menlo, Consolas, monospace;
+    color: #fff;
+    background: rgba(255, 255, 255, 0.08);
+    padding: 0.15rem 0.4rem;
+    border-radius: 4px;
+    font-size: 0.88em;
+    word-break: break-all;
+  }
+
+  .btn {
+    display: inline-block;
+    margin-top: 1.25rem;
+    background: rgba(255, 255, 255, 0.05);
+    border: 1px solid rgba(255, 255, 255, 0.15);
+    color: #fff;
+    padding: 0.5rem 1.1rem;
+    border-radius: 8px;
+    font-size: 0.88rem;
+    cursor: pointer;
+    font-family: inherit;
+    text-decoration: none;
+    transition: border-color 0.15s, background 0.15s;
+  }
+
+  .btn:hover {
+    border-color: rgba(255, 255, 255, 0.3);
+    background: rgba(255, 255, 255, 0.12);
+  }
+
+  .btn + .btn { margin-left: 0.5rem; }
+
+  .footer {
+    margin-top: 1.75rem;
+    padding-top: 1.25rem;
+    border-top: 1px solid rgba(255, 255, 255, 0.1);
+    font-size: 0.8rem;
+    color: rgba(255, 255, 255, 0.55);
+    font-family: "SF Mono", Menlo, Consolas, monospace;
+  }
+</style>
+</head>
+<body>
+  <div class="card">
+    <div class="icon-circle">⚠</div>
+    <div class="status">503 · Service Unavailable</div>
+    <h1>Service is offline</h1>
+    <p>The service at <span class="host" id="host">this address</span> isn't responding right now.</p>
+    <p>It may be restarting, undergoing maintenance, or not currently deployed.</p>
+    <a class="btn" href="https://parsell.me">Go home</a>
+    <button class="btn" onclick="location.reload()">Retry</button>
+    <div class="footer">parsell.me</div>
+  </div>
+<script>
+  document.getElementById('host').textContent = window.location.hostname;
+</script>
+</body>
+</html>

二進制
error/background.jpg


+ 130 - 0
error/default.html

@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Error — parsell.me</title>
+<link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAzMiAzMic+PHJlY3Qgd2lkdGg9JzMyJyBoZWlnaHQ9JzMyJyByeD0nNycgZmlsbD0nIzFiMTgzMCcvPjxwYXRoIGQ9J00xNiA2IEwyNiAxNC41IEwyMy41IDE0LjUgTDIzLjUgMjUgTDE4LjUgMjUgTDE4LjUgMTguNSBMMTMuNSAxOC41IEwxMy41IDI1IEw4LjUgMjUgTDguNSAxNC41IEw2IDE0LjUgWicgZmlsbD0nI2ZmY2NiOCcvPjwvc3ZnPg==">
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
+<style>
+  /* Self-contained: error pages must render even if no other request succeeds.
+     Font + background tile are progressive-enhancement — system fonts and a
+     solid dark fill take over if they fail to load. */
+  * { box-sizing: border-box; margin: 0; padding: 0; }
+
+  html, body {
+    height: 100%;
+    font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
+    color: #fff;
+    overflow: hidden;
+  }
+
+  body {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 1.5rem;
+    background:
+      linear-gradient(rgba(20, 18, 32, 0.55), rgba(20, 18, 32, 0.55)),
+      #14121f url('/background.jpg') 0 0 / 960px repeat fixed;
+  }
+
+  .card {
+    max-width: 480px;
+    width: 100%;
+    background: rgba(255, 255, 255, 0.06);
+    backdrop-filter: blur(24px) saturate(160%);
+    -webkit-backdrop-filter: blur(24px) saturate(160%);
+    border: 1px solid rgba(255, 255, 255, 0.12);
+    border-radius: 24px;
+    padding: 2.5rem 2.25rem;
+    text-align: center;
+    box-shadow:
+      0 14px 50px rgba(0, 0, 0, 0.4),
+      inset 0 1px 0 rgba(255, 255, 255, 0.1);
+  }
+
+  .icon-circle {
+    width: 56px;
+    height: 56px;
+    margin: 0 auto 1.25rem;
+    border-radius: 50%;
+    background: rgba(247, 129, 102, 0.18);
+    color: #ffb199;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 1.75rem;
+    font-weight: 600;
+  }
+
+  .status {
+    font-family: "SF Mono", Menlo, Consolas, monospace;
+    font-size: 0.75rem;
+    color: #f78166;
+    letter-spacing: 0.1em;
+    text-transform: uppercase;
+    margin-bottom: 0.6rem;
+  }
+
+  h1 {
+    font-size: 1.6rem;
+    font-weight: 600;
+    letter-spacing: -0.02em;
+    margin-bottom: 0.75rem;
+  }
+
+  p {
+    color: rgba(255, 255, 255, 0.72);
+    line-height: 1.55;
+    font-size: 0.95rem;
+    margin-bottom: 0.75rem;
+  }
+
+  .btn {
+    display: inline-block;
+    margin-top: 1.25rem;
+    background: rgba(255, 255, 255, 0.05);
+    border: 1px solid rgba(255, 255, 255, 0.15);
+    color: #fff;
+    padding: 0.5rem 1.1rem;
+    border-radius: 8px;
+    font-size: 0.88rem;
+    cursor: pointer;
+    font-family: inherit;
+    text-decoration: none;
+    transition: border-color 0.15s, background 0.15s;
+  }
+
+  .btn:hover {
+    border-color: rgba(255, 255, 255, 0.3);
+    background: rgba(255, 255, 255, 0.12);
+  }
+
+  .btn + .btn { margin-left: 0.5rem; }
+
+  .footer {
+    margin-top: 1.75rem;
+    padding-top: 1.25rem;
+    border-top: 1px solid rgba(255, 255, 255, 0.1);
+    font-size: 0.8rem;
+    color: rgba(255, 255, 255, 0.55);
+    font-family: "SF Mono", Menlo, Consolas, monospace;
+  }
+</style>
+</head>
+<body>
+  <div class="card">
+    <div class="icon-circle">!</div>
+    <div class="status">Error</div>
+    <h1>Something went wrong</h1>
+    <p>The server ran into an unexpected problem and couldn't complete your request.</p>
+    <p>Try again in a moment. If it keeps happening, the service may be down.</p>
+    <a class="btn" href="https://parsell.me">Go home</a>
+    <button class="btn" onclick="location.reload()">Try again</button>
+    <div class="footer">parsell.me</div>
+  </div>
+</body>
+</html>

二進制
external/background.jpg


+ 128 - 0
external/index.html

@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Parsell Family</title>
+<link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAzMiAzMic+PHJlY3Qgd2lkdGg9JzMyJyBoZWlnaHQ9JzMyJyByeD0nNycgZmlsbD0nIzFiMTgzMCcvPjxwYXRoIGQ9J00xNiA2IEwyNiAxNC41IEwyMy41IDE0LjUgTDIzLjUgMjUgTDE4LjUgMjUgTDE4LjUgMTguNSBMMTMuNSAxOC41IEwxMy41IDI1IEw4LjUgMjUgTDguNSAxNC41IEw2IDE0LjUgWicgZmlsbD0nI2ZmY2NiOCcvPjwvc3ZnPg==">
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
+<style>
+  * { box-sizing: border-box; margin: 0; padding: 0; }
+
+  html, body {
+    height: 100%;
+    font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
+    color: #fff;
+    overflow: hidden;
+  }
+
+  body {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 1.5rem;
+    /* Layered: dark tint over the wave-pattern image so the frosted card has
+       reliable contrast regardless of which part of the pattern sits behind it.
+       background-tile.jpg is a 2x2 mirrored arrangement of the original — each
+       edge meets its own mirror, so the pattern tiles seamlessly. Displayed at
+       960px so each mirror panel is 480px (the original tile size). */
+    background:
+      linear-gradient(rgba(20, 18, 32, 0.45), rgba(20, 18, 32, 0.45)),
+      url('/background.jpg') 0 0 / 960px repeat fixed;
+  }
+
+  /* --- Frosted card --- */
+  .card {
+    max-width: 480px;
+    width: 100%;
+    background: rgba(255, 255, 255, 0.06);
+    backdrop-filter: blur(24px) saturate(160%);
+    -webkit-backdrop-filter: blur(24px) saturate(160%);
+    border: 1px solid rgba(255, 255, 255, 0.12);
+    border-radius: 24px;
+    padding: 2.75rem 2.25rem;
+    text-align: center;
+    box-shadow:
+      0 14px 50px rgba(0, 0, 0, 0.4),
+      inset 0 1px 0 rgba(255, 255, 255, 0.1);
+  }
+
+  h1 {
+    font-size: 2.15rem;
+    font-weight: 700;
+    letter-spacing: -0.02em;
+    margin-bottom: 1.5rem;
+    padding-bottom: 1.5rem;
+    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+    background: linear-gradient(135deg, #ffffff 0%, #ffd9c8 100%);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    background-clip: text;
+  }
+
+  .services {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: center;
+    gap: 0.75rem;
+  }
+
+  .service-btn {
+    width: 64px;
+    height: 64px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: rgba(255, 255, 255, 0.05);
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    border-radius: 50%;
+    text-decoration: none;
+    transition: background 0.2s, border-color 0.2s, transform 0.2s, box-shadow 0.2s;
+  }
+
+  .service-btn:hover {
+    background: rgba(255, 255, 255, 0.12);
+    border-color: rgba(255, 255, 255, 0.28);
+    transform: translateY(-2px);
+    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
+  }
+
+  .service-btn .icon {
+    width: 26px;
+    height: 26px;
+    background: rgba(255, 255, 255, 0.85);
+    mask: var(--mask) center / contain no-repeat;
+    -webkit-mask: var(--mask) center / contain no-repeat;
+    transition: background 0.15s;
+  }
+
+  .service-btn:hover .icon {
+    background: #fff;
+  }
+</style>
+</head>
+<body>
+  <div class="card">
+    <h1>Parsell Family</h1>
+    <div class="services">
+      <a class="service-btn" href="https://mail.proton.me" title="Email" aria-label="Email">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/mail.svg)"></span>
+      </a>
+      <a class="service-btn" href="https://watch.parsell.me" title="Media" aria-label="Media">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/circle-play.svg)"></span>
+      </a>
+      <a class="service-btn" href="https://photos.parsell.me" title="Photos" aria-label="Photos">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/images.svg)"></span>
+      </a>
+      <a class="service-btn" href="https://git.parsell.me" title="Git" aria-label="Git">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/git-branch.svg)"></span>
+      </a>
+      <a class="service-btn" href="https://login.parsell.me" title="Sign in" aria-label="Sign in">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/key-round.svg)"></span>
+      </a>
+    </div>
+  </div>
+</body>
+</html>

二進制
internal/background.jpg


+ 159 - 0
internal/index.html

@@ -0,0 +1,159 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Parsell Family</title>
+<link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAzMiAzMic+PHJlY3Qgd2lkdGg9JzMyJyBoZWlnaHQ9JzMyJyByeD0nNycgZmlsbD0nIzFiMTgzMCcvPjxwYXRoIGQ9J00xNiA2IEwyNiAxNC41IEwyMy41IDE0LjUgTDIzLjUgMjUgTDE4LjUgMjUgTDE4LjUgMTguNSBMMTMuNSAxOC41IEwxMy41IDI1IEw4LjUgMjUgTDguNSAxNC41IEw2IDE0LjUgWicgZmlsbD0nI2ZmY2NiOCcvPjwvc3ZnPg==">
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
+<style>
+  * { box-sizing: border-box; margin: 0; padding: 0; }
+
+  html, body {
+    height: 100%;
+    font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
+    color: #fff;
+    overflow: hidden;
+  }
+
+  body {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 1.5rem;
+    /* Layered: dark tint over the wave-pattern image so the frosted card has
+       reliable contrast regardless of which part of the pattern sits behind it.
+       background-tile.jpg is a 2x2 mirrored arrangement of the original — each
+       edge meets its own mirror, so the pattern tiles seamlessly. Displayed at
+       960px so each mirror panel is 480px (the original tile size). */
+    background:
+      linear-gradient(rgba(20, 18, 32, 0.45), rgba(20, 18, 32, 0.45)),
+      url('/background.jpg') 0 0 / 960px repeat fixed;
+  }
+
+  /* --- Frosted card --- */
+  .card {
+    max-width: 480px;
+    width: 100%;
+    background: rgba(255, 255, 255, 0.06);
+    backdrop-filter: blur(24px) saturate(160%);
+    -webkit-backdrop-filter: blur(24px) saturate(160%);
+    border: 1px solid rgba(255, 255, 255, 0.12);
+    border-radius: 24px;
+    padding: 2.75rem 2.25rem;
+    text-align: center;
+    box-shadow:
+      0 14px 50px rgba(0, 0, 0, 0.4),
+      inset 0 1px 0 rgba(255, 255, 255, 0.1);
+  }
+
+  h1 {
+    font-size: 2.15rem;
+    font-weight: 700;
+    letter-spacing: -0.02em;
+    margin-bottom: 1.5rem;
+    padding-bottom: 1.5rem;
+    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+    background: linear-gradient(135deg, #ffffff 0%, #ffd9c8 100%);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    background-clip: text;
+  }
+
+  .services {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: center;
+    gap: 0.75rem;
+  }
+
+  .service-btn {
+    width: 64px;
+    height: 64px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: rgba(255, 255, 255, 0.05);
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    border-radius: 50%;
+    text-decoration: none;
+    transition: background 0.2s, border-color 0.2s, transform 0.2s, box-shadow 0.2s;
+  }
+
+  .service-btn:hover {
+    background: rgba(255, 255, 255, 0.12);
+    border-color: rgba(255, 255, 255, 0.28);
+    transform: translateY(-2px);
+    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
+  }
+
+  .service-btn .icon {
+    width: 26px;
+    height: 26px;
+    background: rgba(255, 255, 255, 0.85);
+    mask: var(--mask) center / contain no-repeat;
+    -webkit-mask: var(--mask) center / contain no-repeat;
+    transition: background 0.15s;
+  }
+
+  .service-btn:hover .icon {
+    background: #fff;
+  }
+
+  /* Full-width hairline that forces a wrap, splitting the row into groups. */
+  .divider {
+    flex-basis: 100%;
+    height: 1px;
+    background: rgba(255, 255, 255, 0.1);
+    margin: 0.25rem 0;
+  }
+</style>
+</head>
+<body>
+  <div class="card">
+    <h1>Parsell Family</h1>
+    <div class="services">
+      <a class="service-btn" href="https://mail.proton.me" title="Email" aria-label="Email">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/mail.svg)"></span>
+      </a>
+      <a class="service-btn" href="https://watch.parsell.me" title="Media" aria-label="Media">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/circle-play.svg)"></span>
+      </a>
+      <a class="service-btn" href="https://photos.parsell.me" title="Photos" aria-label="Photos">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/images.svg)"></span>
+      </a>
+      <a class="service-btn" href="https://git.parsell.me" title="Git" aria-label="Git">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/git-branch.svg)"></span>
+      </a>
+      <a class="service-btn" href="https://login.parsell.me" title="Sign in" aria-label="Sign in">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/key-round.svg)"></span>
+      </a>
+      <div class="divider"></div>
+      <a class="service-btn" href="http://server.parsell.internal" title="Server" aria-label="Server">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/server.svg)"></span>
+      </a>
+      <a class="service-btn" href="http://dns.parsell.internal:5380" title="DNS" aria-label="DNS">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/network.svg)"></span>
+      </a>
+      <a class="service-btn" href="https://crowdsec.parsell.me" title="CrowdSec" aria-label="CrowdSec">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/shield.svg)"></span>
+      </a>
+      <a class="service-btn" href="https://adminer.parsell.me" title="Adminer" aria-label="Adminer">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/database.svg)"></span>
+      </a>
+      <div class="divider"></div>
+      <a class="service-btn" href="https://monitor.parsell.me" title="Monitor" aria-label="Monitor">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/activity.svg)"></span>
+      </a>
+      <a class="service-btn" href="https://health.parsell.me" title="Health" aria-label="Health">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/heart-pulse.svg)"></span>
+      </a>
+      <a class="service-btn" href="https://media.parsell.me" title="Medialyze" aria-label="Medialyze">
+        <span class="icon" aria-hidden="true" style="--mask: url(https://cdn.jsdelivr.net/npm/lucide-static/icons/scan-search.svg)"></span>
+      </a>
+    </div>
+  </div>
+</body>
+</html>