Messy but works

Signed-off-by: Marcus Noble <github@marcusnoble.co.uk>
This commit is contained in:
2025-08-22 17:00:01 +01:00
parent 84adae4c73
commit 0df8d48810
5 changed files with 618 additions and 2 deletions

View File

@@ -0,0 +1,3 @@
FROM nginx:latest
WORKDIR /usr/share/nginx/html
ADD index.html ./

View File

@@ -1,6 +1,6 @@
.DEFAULT_GOAL := default
IMAGE ?= rg.fr-par.scw.cloud/averagemarcus-private/bsky-screenshot:latest
IMAGE ?= rg.fr-par.scw.cloud/averagemarcus/bsky-screenshot:latest
.PHONY: test # Run all tests, linting and format checks
test: lint check-format run-tests
@@ -67,7 +67,7 @@ ci:
.PHONY: release # Release the latest version of the application
release:
@echo "⚠️ 'release' unimplemented"
@kubectl --namespace bsky-screenshot set image deployment bsky-screenshot web=docker.cluster.fun/averagemarcus/bsky-screenshot:$(SHA)
.PHONY: help # Show this list of commands
help:

158
index.html Normal file
View File

@@ -0,0 +1,158 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bsky Screenshot</title>
<meta property="og:title" content="Bsky Screenshot">
<meta property="og:site_name" content="Bsky Screenshot">
<meta property="og:url" content="https://bsky-screenshot.cluster.fun">
<meta property="og:description" content="Web app to generate screenshots of Bluesky posts">
<meta property="og:type" content="website">
<script tpye="application/javascript" src="https://cdn.githubraw.com/AverageMarcus/BindIt.js/refs/heads/master/BindIt.js"></script>
<script tpye="application/javascript" src="https://cdn.githubraw.com/tsayen/dom-to-image/refs/heads/master/dist/dom-to-image.min.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.css">
<link rel="stylesheet" href="https://githubraw.com/AverageMarcus/milligram/master/dist/milligram.min.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1 class="heading-fancy">
Bsky Screenshot
</h1>
<blockquote>
Web app to generate screenshots of Bluesky posts
</blockquote>
<div class="row" id="input-form">
<div class="column">
<div class="form">
<input type="text" id="post-url" bind-it-to="bskyPost.url" bind-it-with="value" />
<button id="submit">Fetch</button>
</div>
<!-- Config is still WiP -->
<!--
<details>
<summary>Advanced Config</summary>
<div>
<label>CORS Proxy: <input type="text" bind-it-to="config.corsProxy" bind-it-with="value" bind-it-two-way /></label>
<label>Width: <input type="number" bind-it-to="config.width" bind-it-with="value" bind-it-two-way /></label>
<label>Window Decoration: <input type="checkbox" bind-it-to="config.windowDecoration" bind-it-with="checked" bind-it-two-way /></label>
</div>
</details>
-->
</div>
</div>
<div class="row">
<div class="column post-wrapper">
<div id="post" bind-it-to="config.width" bind-it-with="setWidth">
<div class="post border" bind-it-to="config.width" bind-it-with="setWidth">
<div class="windowDecoration" bind-it-to="config.windowDecoration" bind-it-with="shouldShow">
<div class="buttons">
<div class="roundButton" style="background-color: #ff544d; border-color: #dd3a33"></div>
<div class="roundButton" style="background-color: #feb429; border-color: #da9211"></div>
<div class="roundButton" style="background-color: #d9d8d7; border-color: #b7b5b5"></div>
</div>
<div class="title" bind-it-to="bskyPost.handle"></div>
<div class="spacer"></div>
</div>
<div class="header">
<img id="avatar" class="avatar" bind-it-to="bskyPost.avatar" bind-it-with="imageSrc" />
<div>
<div class="displayName" bind-it-to="bskyPost.displayName"></div>
<div class="handle" bind-it-to="bskyPost.handle"></div>
</div>
</div>
<div class="body" bind-it-to="bskyPost.text" bind-it-with="asHTML"></div>
<div class="embeds">
<div class="images" bind-it-to="bskyPost.images.exists" bind-it-with="shouldShow">
<div bind-it-to="bskyPost.images.image1" bind-it-with="shouldShow">
<img bind-it-to="bskyPost.images.image1" bind-it-with="imageSrc" />
</div>
<div bind-it-to="bskyPost.images.image2" bind-it-with="shouldShow">
<img bind-it-to="bskyPost.images.image2" bind-it-with="imageSrc" />
</div>
<div bind-it-to="bskyPost.images.image3" bind-it-with="shouldShow">
<img bind-it-to="bskyPost.images.image3" bind-it-with="imageSrc" />
</div>
<div bind-it-to="bskyPost.images.image4" bind-it-with="shouldShow">
<img bind-it-to="bskyPost.images.image4" bind-it-with="imageSrc" />
</div>
</div>
<div class="externalLink" bind-it-to="bskyPost.externalLink.exists" bind-it-with="shouldShow">
<img id="externalLinkImage" bind-it-to="bskyPost.externalLink.image" bind-it-with="imageSrc" />
<div class="linkMeta">
<div class="linkTitle" bind-it-to="bskyPost.externalLink.title"></div>
<div class="linkDescription" bind-it-to="bskyPost.externalLink.description"></div>
<div class="linkDomain">
<svg fill="none" viewBox="0 0 24 24" width="12" height="12" style="transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-timing-function: cubic-bezier(0.17, 0.73, 0.14, 1); transition-duration: 100ms; color: rgb(140, 158, 178);"><path fill="hsl(211, 20%, 62.4%)" fill-rule="evenodd" clip-rule="evenodd" d="M4.4 9.493C4.14 10.28 4 11.124 4 12a8 8 0 1 0 10.899-7.459l-.953 3.81a1 1 0 0 1-.726.727l-3.444.866-.772 1.533a1 1 0 0 1-1.493.35L4.4 9.493Zm.883-1.84L7.756 9.51l.44-.874a1 1 0 0 1 .649-.52l3.306-.832.807-3.227a7.993 7.993 0 0 0-7.676 3.597ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm8.43.162a1 1 0 0 1 .77-.29l1.89.121a1 1 0 0 1 .494.168l2.869 1.928a1 1 0 0 1 .336 1.277l-.973 1.946a1 1 0 0 1-.894.553h-2.92a1 1 0 0 1-.831-.445L9.225 14.5a1 1 0 0 1 .126-1.262l1.08-1.076Zm.915 1.913.177-.177 1.171.074 1.914 1.286-.303.607h-1.766l-1.194-1.79Z"></path></svg>
<span bind-it-to="bskyPost.externalLink.domain"></span>
</div>
</div>
</div>
</div>
<div class="footer">
<div class="interactions" bind-it-to="config.interactions" bind-it-with="shouldShow">
<span class="replies">
<svg fill="none" width="18" viewBox="0 0 24 24" height="18" style="color: rgb(111, 134, 159); pointer-events: none;"><path fill="hsl(211, 20%, 53%)" fill-rule="evenodd" clip-rule="evenodd" d="M2.002 6a3 3 0 0 1 3-3h14a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H12.28l-4.762 2.858A1 1 0 0 1 6.002 21v-2h-1a3 3 0 0 1-3-3V6Zm3-1a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h2a1 1 0 0 1 1 1v1.234l3.486-2.092a1 1 0 0 1 .514-.142h7a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-14Z"></path></svg>
<span bind-it-to="bskyPost.replies"></span> replies
</span>
<span class="reposts">
<svg fill="none" width="18" viewBox="0 0 24 24" height="18" style="color: rgb(111, 134, 159); pointer-events: none;"><path fill="hsl(211, 20%, 53%)" fill-rule="evenodd" clip-rule="evenodd" d="M17.957 2.293a1 1 0 1 0-1.414 1.414L17.836 5H6a3 3 0 0 0-3 3v3a1 1 0 1 0 2 0V8a1 1 0 0 1 1-1h11.836l-1.293 1.293a1 1 0 0 0 1.414 1.414l2.47-2.47a1.75 1.75 0 0 0 0-2.474l-2.47-2.47ZM20 12a1 1 0 0 1 1 1v3a3 3 0 0 1-3 3H6.164l1.293 1.293a1 1 0 1 1-1.414 1.414l-2.47-2.47a1.75 1.75 0 0 1 0-2.474l2.47-2.47a1 1 0 0 1 1.414 1.414L6.164 17H18a1 1 0 0 0 1-1v-3a1 1 0 0 1 1-1Z"></path></svg>
<span bind-it-to="bskyPost.reposts"></span> reposts
</span>
<span class="likes">
<svg fill="none" width="18" viewBox="0 0 24 24" height="18" style="color: rgb(111, 134, 159); pointer-events: none;"><path fill="hsl(211, 20%, 53%)" fill-rule="evenodd" clip-rule="evenodd" d="M16.734 5.091c-1.238-.276-2.708.047-4.022 1.38a1 1 0 0 1-1.424 0C9.974 5.137 8.504 4.814 7.266 5.09c-1.263.282-2.379 1.206-2.92 2.556C3.33 10.18 4.252 14.84 12 19.348c7.747-4.508 8.67-9.168 7.654-11.7-.541-1.351-1.657-2.275-2.92-2.557Zm4.777 1.812c1.604 4-.494 9.69-9.022 14.47a1 1 0 0 1-.978 0C2.983 16.592.885 10.902 2.49 6.902c.779-1.942 2.414-3.334 4.342-3.764 1.697-.378 3.552.003 5.169 1.286 1.617-1.283 3.472-1.664 5.17-1.286 1.927.43 3.562 1.822 4.34 3.764Z"></path></svg>
<span bind-it-to="bskyPost.likes"></span> likes
</span>
</div>
<div class="createdAt" bind-it-to="bskyPost.createdAt"></div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="column">
<img src="" id="result-image" />
</div>
</div>
<div class="row">
<div class="column">
<button id="download">Download Image</button>
</div>
</div>
<div>
Source code available on <a href="https://github.com/AverageMarcus/bsky-screenshot" target="_blank" rel="noopener noreferrer">GitHub</a>, <a href="https://gitlab.com/AverageMarcus/bsky-screenshot" target="_blank" rel="noopener noreferrer">GitLab</a>, <a href="https://bitbucket.org/AverageMarcus/bsky-screenshot/" target="_blank" rel="noopener noreferrer">Bitbucket</a> & <a href="https://git.cluster.fun/AverageMarcus/bsky-screenshot" target="_blank" rel="noopener noreferrer">my own Gitea server</a>.
</div>
</div>
<div class="container">
<div class="row">
<div class="column column-60 column-offset-20">
<footer>
Made with
<svg height="20" class="fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 449.3 449.3" xmlns:xlink="http://www.w3.org/1999/xlink"><title>love</title><g><path d="M0 162.7c1.5-7.7 2.7-15.4 4.5-23A125.5 125.5 0 0132 88a136.3 136.3 0 0162.7-40.6c8.3-2.9 17.7-3.2 26.6-3.7a134 134 0 0155.6 6.6c14.9 5.7 30 11 41 23.6 17-20 36.4-36.3 60-46.4 12-5.2 25.7-6.9 38.7-9.4a79.4 79.4 0 0140.3 3.2 96.4 96.4 0 0143.2 26 209.8 209.8 0 0137.8 55.4 133.2 133.2 0 0111 65.7c-3.2 42.2-21 79-41.5 114.8a431.2 431.2 0 01-47.6 64.3c-19.6 23-39.7 45.7-59.6 68.5-3.7 4.3-7.2 9-11.7 12.4-7.3 5.4-15.9 4.9-23.8 1.5-21.9-9.2-43.8-18.5-65.3-28.5a520.1 520.1 0 01-98-58.7c-28.2-21.5-55.5-44.3-74-75.3a183.8 183.8 0 01-25-61.4c-1-6.2-1.6-12.6-2.4-18.8v-24.5zM138 281l2.5.1c0 2.1.3 4.3 0 6.3l-9 55.5c-.3 1.6 0 4.2 1 5 5.8 4.1 11.8 7.8 17.9 11.8l11-59.7 2.5.3c3.7 21.6-6.3 42-7.6 63.5l19.4 9.6c.5-18 2.2-35 6.9-51.6 1.3 4 1.5 8 1.2 12-.7 11.2-1 22.5-2.6 33.7-1 7.3 1.9 10.6 8 13.3 27 11.7 54.1 23.5 81 35.7 5.5 2.5 8.8 1.5 12.6-2.9 12.5-14.4 25.7-28.1 38-42.7 17.2-20.2 34.5-40.5 50.5-61.7 29.5-39.2 52.7-81.7 61-131 3.6-21.7 2-43-5.6-63.5a176 176 0 00-33.9-54.5 84.8 84.8 0 00-38-26 91.2 91.2 0 00-44.2-2.5c-13 2-25.6 5.1-37.2 12.3a208.6 208.6 0 00-45.9 37c-5.7 6.3-7.8 6.4-15 1.4-5.7-4-11-8.8-17.2-11.9-15.1-7.5-30.7-14.4-48-14.9-13.7-.4-28-2-41.2 1-36 8.4-62.7 30-80.3 62.8a111 111 0 00-11.7 56.6c.3 10 1.4 20 2.2 29.9 7-18.9 11-38.3 19.7-56.3.6 3 .6 6 0 8.7l-14.5 53.2c-.7 2.6-2 5.7-1.2 8 2.7 8.4 6.2 16.6 9.4 24.8 0-20 13.7-63.9 21.8-66.8 0 .6.3 1.1.2 1.6a11936 11936 0 01-17 64c-.3 1.4-1.6 2.6-2.9 4.5l9.7 17a573 573 0 0120-67.3l2.9.7c-1 5-1.5 10-2.8 14.9-4.7 17.2-9.6 34.3-14.2 51.5-.7 2.5-1.5 6-.3 7.8 3.6 5.6 8 10.7 12.4 16.1 1.8-8.6 3.1-16.6 5.2-24.4 3.1-11.5 6.6-22.9 10.2-34.3.8-2.6 2.7-5 4-7.4l2 .8c-.1 1.6-.1 3.4-.5 5l-12.2 47.4c-4.6 17.9-4.3 18.9 7.8 29.4 2.2 1.8 4.5 3.5 7 5.3 3.7-31 12.5-64.7 18.4-68-.3 3.5-.3 6.6-.9 9.6l-12.7 58.8c-.3 1.2-.7 3-.1 3.5 4.7 4.4 9.6 8.6 14.5 12.9 3.5-21.4 7.6-41.7 13.6-61.6 3.4 8.7.6 17-.8 25.2-1.9 11.6-4.5 23.1-6.5 34.7-.4 2.3-.4 6 1 7 4.8 4.2 10.3 7.4 16 11.3 3.9-21.5 5.5-42.6 12.6-62.5z"/><path d="M323.2 180.5a24 24 0 01-24.5-19.7c-2-9.7.7-20.2 15-27 16.6-7.8 38.3 2.3 41.6 19.5 1.2 6.6-1.6 12.1-6 16.3a35.1 35.1 0 01-26 11z"/><path d="M138.9 167.1a31 31 0 0125 12.7 22 22 0 01-13.6 34.9 29.9 29.9 0 01-31-9.9c-6-7-7.6-15.3-4.1-24.1 3.7-9.5 12-12 21-13.5.8-.2 1.8 0 2.7 0z"/><path d="M233.2 202c-18.5-.4-33.7-13.6-34-29.7 0-4.2.4-8 5.4-8.7 4.5-.7 6.6 2.3 8 6 2.3 6.6 5.5 12.4 12.4 15.4 7.5 3.2 14 1.5 20.4-3.3 6.2-4.7 6.4-11 4.7-17.5-1.4-5.2.4-8.4 4.7-10.2 4.3-1.7 9 1.3 10.9 6.2 7.2 19.8-5.7 34.6-22.8 40.2-3 1-6.5 1-9.7 1.6z"/><path d="M201.2 384.7c-.9-2-2.7-4.2-2.5-6.2 1.2-14 2.8-28.1 4.4-42.2 0-.9.7-1.7 1-2.6l2.6 1.1-3.1 49.1-2.4.8z"/><path d="M222 387.4c-4.8-5.7-5-15-1-37.8 3.1 3 4 30.5 1 37.8z"/><path d="M240.9 361.7l-1.4 20.2c-4.8-3.3-4.6-11.9-.2-20.4l1.6.2z"/><path d="M257.4 380.4c-4.1-5.2-3.7-9.7 1-14.7l-1 14.7z"/></g></svg>
by <a href="https://marcusnoble.co.uk" class="fancy-link">Marcus Noble</a>
</footer>
</div>
</div>
</div>
<script tpye="application/javascript" src="script.js"></script>
</body>
</html>

192
script.js Normal file
View File

@@ -0,0 +1,192 @@
function imageSrc(value) {
if (value) {
this.setAttribute('src', value);
} else {
this.setAttribute('src', "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==");
}
}
function asHTML(value) {
this.innerHTML = value;
}
function shouldShow(value) {
if (value == false || value == "") {
this.style.display = 'none';
} else {
this.style.display = '';
}
}
function setWidth(value) {
this.style.width = `${value}px`;
}
(() => {
let {bskyPost, config} = bindIt({
config: {
corsProxy: "https://corsproxy.io/?url=",
width: 600,
windowDecoration: true,
showInteractions: true
},
bskyPost: {
url: 'https://bsky.app/profile/averagemarcus.bsky.social/post/3lujs2efm5s2f',
handle: '',
displayName: '',
avatar: '',
text: '',
createdAt: '',
likes: 0,
reposts: 0,
replies: 0,
externalLink: {
exists: false,
title: "",
description: "",
image: "",
domain: ""
},
images: {
exists: false,
image1: "",
image2: "",
image3: "",
image4: "",
}
}
});
function isValidHttpUrl(string) {
let url;
try {
url = new URL(string);
} catch (_) {
return false;
}
return url.protocol === "http:" || url.protocol === "https:";
}
function formatDate(d) {
const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var d = new Date(d);
let month = months[d.getMonth()];
let ampm = "AM";
let hour = d.getHours();
if (hour > 12) {
ampm = "PM";
hour = hour - 12;
} else if (hour == 12) {
ampm = "PM";
}
return `${month} ${d.getDate()}, ${d.getFullYear()} at ${hour}:${d.getMinutes()} ${ampm}`;
}
async function loadPost() {
bskyPost.url = document.getElementById('post-url').value
if (!isValidHttpUrl(bskyPost.url)) {
return;
}
let urlParts = bskyPost.url.split('/');
let rKey = urlParts[6];
let handle = urlParts[4];
bskyPost.handle = `@${handle}`;
let profile = await (await fetch(`https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${handle}`)).json();
let did = profile.did;
bskyPost.displayName = profile.displayName;
bskyPost.avatar = config.corsProxy + profile.avatar;
let record = await (await fetch(`https://public.api.bsky.app/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=app.bsky.feed.post&rkey=${rKey}`)).json();
console.log(record);
let parts = [];
let body = unescape(encodeURIComponent(record.value.text));
let offset = 0;
if (record.value.facets) {
for (const facet of record.value.facets) {
parts.push(body.substring(0, facet.index.byteStart - offset));
body = body.substring(facet.index.byteStart - offset, body.length)
offset = facet.index.byteStart;
parts.push(body.substring(0, facet.index.byteEnd - offset));
body = body.substring(facet.index.byteEnd - offset, body.length)
offset = facet.index.byteEnd;
}
}
parts.push(body);
let postBody = "";
for (let index = 0; index < parts.length; index++) {
postBody += parts[index];
if (index == parts.length-1) {
} else if (index % 2 == 0) {
postBody += '<span class="link">'
} else {
postBody += '</span>'
}
}
bskyPost.text = decodeURIComponent(escape(postBody)).replaceAll("\n", "<br>");
bskyPost.createdAt = formatDate(record.value.createdAt);
let uri = record.uri;
let postLikes = await (await fetch(`https://public.api.bsky.app/xrpc/app.bsky.feed.getLikes?uri=${uri}&limit=100`)).json();
bskyPost.likes = postLikes.likes.length;
let postReposts = await (await fetch(`https://public.api.bsky.app/xrpc/app.bsky.feed.getRepostedBy?uri=${uri}&limit=100`)).json();
bskyPost.reposts = postReposts.repostedBy.length;
let thread = await (await fetch(`https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${uri}&parentHeight=1&depth=1000`)).json();
bskyPost.replies = thread.thread.replies.length
bskyPost.externalLink.exists = false;
bskyPost.images.exists = false;
bskyPost.images.image1 = "";
bskyPost.images.image2 = "";
bskyPost.images.image3 = "";
bskyPost.images.image4 = "";
if (record.value.embed && record.value.embed["$type"] == "app.bsky.embed.images") {
bskyPost.images.exists = true;
if (record.value.embed.images.length > 0) bskyPost.images.image1 = `${config.corsProxy}https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${record.value.embed.images[0].image.ref.$link}@jpeg`;
if (record.value.embed.images.length > 1) bskyPost.images.image2 = `${config.corsProxy}https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${record.value.embed.images[1].image.ref.$link}@jpeg`;
if (record.value.embed.images.length > 2) bskyPost.images.image3 = `${config.corsProxy}https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${record.value.embed.images[2].image.ref.$link}@jpeg`;
if (record.value.embed.images.length > 3) bskyPost.images.image4 = `${config.corsProxy}https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${record.value.embed.images[3].image.ref.$link}@jpeg`;
} else if (record.value.embed && record.value.embed["$type"] == "app.bsky.embed.external") {
bskyPost.externalLink.exists = true;
bskyPost.externalLink.title = record.value.embed.external.title;
bskyPost.externalLink.description = record.value.embed.external.description;
bskyPost.externalLink.domain = record.value.embed.external.uri.replaceAll("https://", "").split("/")[0];
bskyPost.externalLink.image = `${config.corsProxy}https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${record.value.embed.external.thumb.ref["$link"]}@jpeg`;
}
updateImage();
}
function updateImage() {
window.requestAnimationFrame(function() {
domtoimage.toPng(document.getElementById('post'), { cacheBust: false })
.then(function (dataUrl) {
document.getElementById('result-image').src = dataUrl;
});
});
}
[...document.querySelectorAll('img')].forEach(el => el.addEventListener("load", updateImage));
document.getElementById('submit').addEventListener('click', loadPost);
document.getElementById('post-url').addEventListener('change', loadPost);
document.getElementById('post-url').addEventListener('keyup', loadPost);
window.requestAnimationFrame(loadPost);
document.getElementById('download').addEventListener('click', function() {
let link = document.createElement('a');
link.download = 'post.png';
link.href = document.getElementById('result-image').src;
link.click();
});
})();

263
style.css Normal file
View File

@@ -0,0 +1,263 @@
body {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.form {
display: flex;
}
.form * {
margin: 2px;
}
.post-wrapper {
overflow: hidden;
position: relative;
}
#post {
background: transparent;
position: absolute;
left: -9999;
top: -9999;
width: 600px;
margin-left: -10px;
}
.link {
color: rgb(16, 131, 254);
}
.border {
background: #fff;
border-radius: 22px;
border-color: rgba(180, 180, 178, 0.56);
border-style: double;
border-width: 2px;
}
.border .windowDecoration {
height: 34px;
display: grid;
grid-template-columns: 1fr 2fr 1fr;
justify-content: space-around;
}
.border .windowDecoration .buttons {
display: flex;
gap: 6px;
padding: 7px;
}
.border .windowDecoration .buttons .roundButton {
border-radius: 9999px;
border-width: 1px;
border-style: solid;
background-color: rgba(38, 34, 23, 0.9);
opacity: 0.9;
width: 10px;
height: 10px;
}
.border .windowDecoration .title {
text-align: center;
font-size: 16px;
letter-spacing: 0px;
color: rgb(66, 87, 108);
line-height: 20px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post {
width: 600px;
padding: 8px;
}
.post .header {
width: 100%;
display: flex;
gap: 12px;
flex-direction: row;
padding-bottom: 12px;
}
.post .header .avatar {
width: 42px;
height: 42px;
border-radius: 21px;
}
.post .header .displayName {
font-size: 16.875px;
letter-spacing: 0px;
color: rgb(11, 15, 20);
flex-shrink: 1;
font-weight: 600;
line-height: 22px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post .header .handle {
font-size: 15px;
letter-spacing: 0px;
color: rgb(66, 87, 108);
line-height: 20px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post .body {
width: 100%;
font-size: 18.75px;
letter-spacing: 0px;
color: rgb(11, 15, 20);
line-height: 24px;
flex: 1 1 0%;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post .embeds {
display: flex;
}
.post .images {
gap: 6px;
display: flex;
border-radius: 12px;
overflow: hidden;
flex-direction: row;
align-content: flex-start;
align-items: stretch;
flex-wrap: wrap;
}
.post .images > div {
aspect-ratio: 1 / 1;
flex: 1 0 48%;
}
.post .images img {
vertical-align: middle;
width: 100%;
height: 100%;
object-fit: cover;
}
.post .images > div:nth-child(2) {
grid-column: 2;
}
.post .images > div:nth-child(3) {
grid-row: 2;
grid-column: 1;
}
.post .images > div:nth-child(4) {
grid-row: 2;
grid-column: 2;
}
.post .externalLink {
flex-direction: column;
border-radius: 12px;
overflow: hidden;
width: 100%;
border-width: 1px;
border-style: solid;
margin-top: 8px;
border-color: rgb(212, 219, 226);
}
.post .externalLink .linkMeta {
gap: 3px;
padding-bottom: 4px;
padding-left: 12px;
padding-right: 12px;
padding-top: 8px;
border-top-style: solid;
border-top-width: 1px;
border-color: rgb(212, 219, 226);
}
.post .externalLink .linkTitle {
font-size: 15px;
letter-spacing: 0px;
color: rgb(11, 15, 20);
font-weight: 600;
line-height: 20px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post .externalLink .linkDescription {
padding-top: 6px;
padding-bottom: 6px;
font-size: 13.125px;
letter-spacing: 0px;
color: rgb(11, 15, 20);
line-height: 17px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post .externalLink .linkDomain {
padding-bottom: 8px;
padding-top: 6px;
border-top-style: solid;
border-top-width: 1px;
border-color: rgb(212, 219, 226);
font-size: 11.25px;
letter-spacing: 0px;
color: rgb(66, 87, 108);
line-height: 15px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post .footer {
width: 100%;
padding-top: 12px;
display: flex;
justify-content: space-between;
border-color: rgb(212, 219, 226);
border-top-style: solid;
border-top-width: 1px;
margin-top: 12px;
padding-left: 4px;
padding-right: 8px;
padding-top: 12px;
padding-bottom: 12px;
}
.post .footer .createdAt {
font-size: 15px;
letter-spacing: 0px;
color: rgb(119, 129, 140);
line-height: 15px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post .footer .interactions {
display: flex;
flex-direction: row;
align-items: center;
gap: 16px;
}
.post .footer .replies,
.post .footer .reposts,
.post .footer .likes {
font-size: 15px;
letter-spacing: 0px;
color: rgb(119, 129, 140);
stroke: rgb(119, 129, 140);
line-height: 15px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
.post .footer .replies>svg,
.post .footer .reposts>svg,
.post .footer .likes>svg {
height: 20px;
margin-bottom: -5px;
}
.post .footer .replies>span,
.post .footer .reposts>span,
.post .footer .likes>span {
font-size: 15px;
letter-spacing: 0px;
font-weight: 600;
line-height: 15px;
font-family: InterVariable, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-variant: no-contextual;
}
#download, #result-image {
display: block;
margin: 30px auto;
}