TheOrb/preview.html
2026-05-13 22:06:44 -04:00

390 lines
9 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Archive Status Preview</title>
<style>
:root {
color-scheme: dark;
--page: #1e1f22;
--channel: #313338;
--message: #2b2d31;
--text: #dbdee1;
--muted: #949ba4;
--link: #00a8fc;
--border: #3f4147;
--green: #10b981;
--orange: #f59e0b;
--red: #ef4444;
--gray: #6b7280;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
background: var(--page);
color: var(--text);
font: 14px/1.45 system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
}
main {
width: min(980px, calc(100vw - 32px));
margin: 0 auto;
padding: 32px 0;
}
h1 {
margin: 0 0 16px;
font-size: 20px;
font-weight: 650;
letter-spacing: 0;
}
.channel {
background: var(--channel);
border: 1px solid #26272b;
border-radius: 8px;
min-height: 560px;
overflow: hidden;
}
.channel-header {
height: 48px;
display: flex;
align-items: center;
padding: 0 16px;
border-bottom: 1px solid #26272b;
color: #f2f3f5;
font-weight: 650;
}
.message {
display: grid;
grid-template-columns: 40px 1fr;
gap: 12px;
padding: 20px 18px;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #5865f2;
display: grid;
place-items: center;
color: #fff;
font-weight: 700;
}
.message-meta {
display: flex;
align-items: baseline;
gap: 8px;
margin-bottom: 8px;
}
.author {
color: #f2f3f5;
font-weight: 650;
}
.bot-tag {
padding: 1px 4px;
border-radius: 3px;
background: #5865f2;
color: #fff;
font-size: 10px;
font-weight: 700;
line-height: 1.3;
}
.time {
color: var(--muted);
font-size: 12px;
}
.embeds {
display: grid;
gap: 8px;
max-width: 560px;
}
.embed {
position: relative;
background: var(--message);
border: 1px solid var(--border);
border-radius: 4px;
padding: 10px 12px 10px 16px;
overflow: hidden;
}
.embed::before {
content: "";
position: absolute;
inset: 0 auto 0 0;
width: 4px;
background: var(--embed-color, var(--gray));
}
.embed-title {
margin: 0 0 6px;
color: #f2f3f5;
font-size: 14px;
font-weight: 650;
}
.embed-description {
margin: 0;
white-space: pre-line;
color: var(--text);
}
.embed-description a {
color: var(--link);
text-decoration: none;
}
.embed-description a:hover {
text-decoration: underline;
}
.embed-footer {
margin-top: 8px;
color: var(--muted);
font-size: 12px;
}
.embed-fields {
display: grid;
grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
gap: 12px;
margin-top: 10px;
}
.embed-field-name {
color: #f2f3f5;
font-weight: 650;
margin-bottom: 4px;
}
.embed-field-value {
color: var(--text);
white-space: pre-line;
}
.embed-field-value a {
color: var(--link);
text-decoration: none;
}
.embed-field-value a:hover {
text-decoration: underline;
}
.controls {
display: grid;
gap: 8px;
margin-top: 18px;
max-width: 560px;
}
label {
color: var(--muted);
font-weight: 600;
}
textarea {
width: 100%;
min-height: 220px;
resize: vertical;
border: 1px solid #3f4147;
border-radius: 6px;
background: #232428;
color: var(--text);
padding: 10px;
font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
button {
justify-self: start;
border: 1px solid #4e5058;
border-radius: 6px;
background: #404249;
color: #fff;
padding: 8px 12px;
font-weight: 650;
cursor: pointer;
}
button:hover {
background: #4e5058;
}
.error {
color: #fca5a5;
min-height: 20px;
}
@media (max-width: 640px) {
main {
width: 100%;
padding: 0;
}
h1 {
padding: 16px;
margin: 0;
}
.channel {
border-left: 0;
border-right: 0;
border-radius: 0;
}
.embeds,
.controls {
max-width: 100%;
}
}
</style>
</head>
<body>
<main>
<h1>Archive Status Preview</h1>
<section class="channel" aria-label="Discord status channel preview">
<div class="channel-header"># status</div>
<article class="message">
<div class="avatar">A</div>
<div>
<div class="message-meta">
<span class="author">Archive Status</span>
<span class="bot-tag">BOT</span>
<span class="time">Today at 9:03 PM</span>
</div>
<div id="embeds" class="embeds"></div>
<div class="controls">
<label for="payload">Preview payload</label>
<textarea id="payload" spellcheck="false"></textarea>
<button type="button" id="render">Render payload</button>
<div id="error" class="error" role="status"></div>
</div>
</div>
</article>
</section>
</main>
<script>
const samplePayload = {
content: "",
embeds: [
{
title: "The Mithral Archive",
description: "🟡 Degraded · 5/6 online · 1 service needs attention",
color: 16096779,
fields: [
{
name: "Service",
value: "**Portal**\n**Jellyfin**\n**Navidrome**\n**Voting Hub**\n**Forgejo**\n**Support**",
inline: true
},
{
name: "Status",
value: "🟢 Online\n🟢 Online\n🟢 Online\n🟢 Online\n🟢 Online\n🔴 Issue",
inline: true
}
],
footer: { text: "Refreshes every 60s • Last checked 2026-05-14 01:56:29 UTC" }
}
]
};
const embedsEl = document.querySelector("#embeds");
const payloadEl = document.querySelector("#payload");
const errorEl = document.querySelector("#error");
function colorToHex(value) {
const number = Number(value);
if (!Number.isFinite(number)) return "#6b7280";
return `#${number.toString(16).padStart(6, "0").slice(-6)}`;
}
function renderMarkdownLinks(text) {
const escaped = text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
return escaped.replace(/\[([^\]]+)]\((https?:\/\/[^)\s]+)\)/g, '<a href="$2">$1</a>');
}
function render(payload) {
embedsEl.innerHTML = "";
const embeds = Array.isArray(payload.embeds) ? payload.embeds : [];
embeds.forEach((embed) => {
const article = document.createElement("section");
article.className = "embed";
article.style.setProperty("--embed-color", colorToHex(embed.color));
const title = document.createElement("h2");
title.className = "embed-title";
title.textContent = embed.title || "Untitled";
article.append(title);
if (embed.description) {
const description = document.createElement("p");
description.className = "embed-description";
description.innerHTML = renderMarkdownLinks(String(embed.description));
article.append(description);
}
if (Array.isArray(embed.fields) && embed.fields.length) {
const fields = document.createElement("div");
fields.className = "embed-fields";
embed.fields.forEach((field) => {
const item = document.createElement("div");
item.className = "embed-field";
const name = document.createElement("div");
name.className = "embed-field-name";
name.textContent = field.name || "\u200b";
const value = document.createElement("div");
value.className = "embed-field-value";
value.innerHTML = renderMarkdownLinks(String(field.value || "\u200b"));
item.append(name, value);
fields.append(item);
});
article.append(fields);
}
if (embed.footer?.text) {
const footer = document.createElement("div");
footer.className = "embed-footer";
footer.textContent = embed.footer.text;
article.append(footer);
}
embedsEl.append(article);
});
}
document.querySelector("#render").addEventListener("click", () => {
try {
const payload = JSON.parse(payloadEl.value);
errorEl.textContent = "";
render(payload);
} catch (error) {
errorEl.textContent = error.message;
}
});
payloadEl.value = JSON.stringify(samplePayload, null, 2);
render(samplePayload);
</script>
</body>
</html>