Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"prettier": "^3.5.3",
"prettier-plugin-astro": "^0.14.1",
"tsx": "^4.19.3",
"puppeteer": "^24.7.2",
"typescript": "^5.8.3"
},
"prettier": {
Expand Down
593 changes: 593 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

Binary file added public/social/bg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/social/circle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/social/color.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/social/star.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/social/web.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions scripts/download_social.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const puppeteer = require("puppeteer");

(async () => {
const browser = await puppeteer.launch({
executablePath: process.env.CHROME_BIN,
args: ["--no-sandbox", "--disable-setuid-sandbox"],
});
const page = await browser.newPage();
await page.goto("http://localhost:4321/media/social_media_cards");

const elements = await page.$$(".social");

for (let i = 0; i < elements.length; i++) {
const el = elements[i];

// Get the slug from the element
const slug = await page.evaluate((el) => el.getAttribute("data-slug"), el);

// Fallback if slug is missing
const filename = slug ? `social-${slug}.png` : `social-${i}.png`;

await el.screenshot({ path: filename });
}

await browser.close();
})();
110 changes: 110 additions & 0 deletions src/components/SocialMediaCard.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
import { getCollection, getEntries, type CollectionEntry } from "astro:content";
import { Image } from "astro:assets";

const { entry } = Astro.props;

const sessions = await getEntries(entry.data.submissions);
---
<svg width="900" height="900" class="h-full w-full -z-10">
<image href="http://localhost:4321/social/bg.png" width="900" height="900" />
</svg>
{
entry.data.avatar ? (
<svg width="900" height="900">
<defs>
<clipPath id="curvedCornerClip">
<path
d="
m 885 885 l -240 0 a 240 240 0 0 1 -240 -240 a 240 240 0 0 1 240 -240 a 240 240 0 0 1 240 240 z
"
/>
</clipPath>
</defs>
<image
href={entry.data.avatar}
x="400"
y="400"
width="500"
height="560"
clip-path="url(#curvedCornerClip)"
preserveAspectRatio="xMidYMid slice"
/>
</svg>
<p
class="box fit-text"
>
{sessions.map((session) => <>{session.data.title} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</>)}
</p>

<p lang="en" class="box2 fit-text">
{entry.data.name}
</p>

):(

<p
class="box3 fit-text"
>
{sessions.map((session) => <>{session.data.title} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</>)}
</p>

<p lang="en" class="box4 fit-text">
{entry.data.name}
</p>

)
}



<style>
.box {
margin-top:240px;
width: 430px;
height: 300px;
font-size: 100px;
color: rgb(239, 215, 123);
padding: 1rem;
}

.box2 {

width: 420px;
height: 200px;
font-size: 70px;
color:white;
padding: 2rem;
}

.box3 {
margin-top:280px;
width: 800px;
height: 300px;
font-size: 100px;
color: rgb(239, 215, 123);
padding: 1rem;
}

.box4 {

width: 580px;
height: 200px;
font-size: 70px;
color:white;
padding: 2rem;
}


.box, .box2, .box3, .box4 {
box-sizing: border-box;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-family: Inter, sans-serif !important;
font-weight: bold;
line-height: 1em;
}
</style>
130 changes: 130 additions & 0 deletions src/pages/media/card/[slug].astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
---
import { getCollection, getEntries, type CollectionEntry } from "astro:content";
import SocialMediaCard from "@components/SocialMediaCard.astro";

export async function getStaticPaths() {
const entries = await getCollection("speakers");
return entries.map((entry) => ({
params: { slug: entry.id },
props: { entry },
}));
}

const { entry } = Astro.props;
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<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:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/html2canvas.min.js"></script>
</head>
<body class="overflow-auto">
{
entry &&
<div class="social relative w-[900px] h-[900px] overflow-hidden">
<SocialMediaCard entry={entry} />
</div>
}

</body>
</html>

<style is:global>

.social {
width: 900px;
height: 900px;
}

.social svg {
position: absolute;
top:0;
left:0;
width: 100%;
height: 100%;
object-fit: contain;
}

body * {
font-family: Inter, sans-serif;
}

.avatar {
object-position:50% 25%;
}

</style>

<script is:inline>
function fitText(container) {
let fontSize = 100; // Start big
container.style.fontSize = fontSize + 'px';

while (
(container.scrollWidth > container.clientWidth || container.scrollHeight > container.clientHeight)
&& fontSize > 5
) {
fontSize -= 1;
container.style.fontSize = fontSize + 'px';
}
}

function fitAllText() {
const boxes = document.querySelectorAll('.fit-text');
boxes.forEach(box => fitText(box));
}

fitAllText();

window.addEventListener('resize', fitAllText);
</script>

<script define:vars={{slug: entry.data.slug}}>
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.social').forEach((socialDiv, index) => {

socialDiv.addEventListener('click', () => {
const svgs = socialDiv.querySelectorAll('svg');

if (svgs.length === 0) {
alert('No SVGs found!');
return;
}

const xmlns = "http://www.w3.org/2000/svg";
const combinedSvg = document.createElementNS(xmlns, "svg");
combinedSvg.setAttribute("xmlns", xmlns);
combinedSvg.setAttribute("width", "900");
combinedSvg.setAttribute("height", "900");
combinedSvg.setAttribute("viewBox", "0 0 900 900");

svgs.forEach(svg => {
const g = document.createElementNS(xmlns, "g");
g.innerHTML = svg.innerHTML;
combinedSvg.appendChild(g);
});

const serializer = new XMLSerializer();
const svgString = serializer.serializeToString(combinedSvg);

const blob = new Blob([svgString], {type: "image/svg+xml"});
const url = URL.createObjectURL(blob);

const a = document.createElement('a');
a.href = url;
a.download = slug ? `social-${slug}.svg` : `social-${index +1}.svg`;
a.style.display = "none";
document.body.appendChild(a);
a.click();

URL.revokeObjectURL(url);
document.body.removeChild(a);
});
});
});
</script>
82 changes: 82 additions & 0 deletions src/pages/media/social_media_cards.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
import { getCollection, getEntries, type CollectionEntry } from "astro:content";
import SocialMediaCard from "@components/SocialMediaCard.astro";


const speakers = await getCollection("speakers");
type Speaker = CollectionEntry<"speakers">;
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<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:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/html2canvas.min.js"></script>
</head>
<body class="overflow-auto">
{
speakers.map((entry: Speaker) => (
<a href=`/media/card/${entry.data.slug}`>
<div class="social relative w-[900px] h-[900px] overflow-hidden" data-slug={entry.data.slug}>
<SocialMediaCard entry={entry} />
</div>
</a>
))
}

</body>
</html>

<style is:global>

.social {
width: 900px;
height: 900px;
}

.social svg {
position: absolute;
top:0;
left:0;
width: 100%;
height: 100%;
object-fit: contain;
}

body * {
font-family: Inter, sans-serif;
}

.avatar {
object-position:50% 25%;
}

</style>

<script is:inline>
function fitText(container) {
let fontSize = 100; // Start big
container.style.fontSize = fontSize + 'px';

while (
(container.scrollWidth > container.clientWidth || container.scrollHeight > container.clientHeight)
&& fontSize > 5
) {
fontSize -= 1;
container.style.fontSize = fontSize + 'px';
}
}

function fitAllText() {
const boxes = document.querySelectorAll('.fit-text');
boxes.forEach(box => fitText(box));
}

fitAllText();

window.addEventListener('resize', fitAllText);
</script>
1 change: 1 addition & 0 deletions src/pages/robots.txt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Disallow: /
const prodRobots = `
User-agent: *
Disallow: /_astro/
Disallow: /media/
Disallow: /*?
Allow: /

Expand Down