Skip to content

Commit 6694708

Browse files
committed
Add social media cards.
1 parent 0ea0c63 commit 6694708

File tree

11 files changed

+856
-3
lines changed

11 files changed

+856
-3
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
"devDependencies": {
4747
"@types/js-yaml": "^4.0.9",
4848
"prettier": "^3.5.3",
49-
"prettier-plugin-astro": "^0.14.1"
49+
"prettier-plugin-astro": "^0.14.1",
50+
"puppeteer": "^24.7.2"
5051
},
5152
"prettier": {
5253
"proseWrap": "always"

pnpm-lock.yaml

Lines changed: 594 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/social/bg.png

108 KB
Loading

public/social/circle.png

8.49 KB
Loading

public/social/color.png

73.3 KB
Loading

public/social/star.svg

Lines changed: 3 additions & 0 deletions
Loading

public/social/web.png

65.4 KB
Loading

scripts/download_social.cjs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const puppeteer = require("puppeteer");
2+
3+
(async () => {
4+
const browser = await puppeteer.launch({
5+
executablePath: process.env.CHROME_BIN,
6+
});
7+
const page = await browser.newPage();
8+
await page.goto("http://localhost:4321/social/");
9+
10+
const elements = await page.$$(".social");
11+
12+
for (let i = 0; i < elements.length; i++) {
13+
const el = elements[i];
14+
await el.screenshot({ path: `social-${i}.png` });
15+
}
16+
17+
await browser.close();
18+
})();
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
---
2+
import { getCollection, getEntries, type CollectionEntry } from "astro:content";
3+
import { Image } from "astro:assets";
4+
5+
const { entry } = Astro.props;
6+
7+
const sessions = await getEntries(entry.data.submissions);
8+
---
9+
10+
<svg width="900" height="900" class="h-full w-full -z-10">
11+
<image href="http://localhost:4321/social/bg.png" width="900" height="900" />
12+
</svg>
13+
{
14+
entry.data.avatar ? (
15+
<svg width="900" height="900">
16+
<defs>
17+
<clipPath id="curvedCornerClip">
18+
<path
19+
d="
20+
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
21+
"
22+
/>
23+
</clipPath>
24+
</defs>
25+
<image
26+
href={entry.data.avatar}
27+
x="400"
28+
y="400"
29+
width="500"
30+
height="560"
31+
clip-path="url(#curvedCornerClip)"
32+
preserveAspectRatio="xMidYMid slice"
33+
/>
34+
</svg>
35+
<p
36+
class="box fit-text"
37+
>
38+
{sessions.map((session) => <>{session.data.title} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</>)}
39+
</p>
40+
41+
<p lang="en" class="box2 fit-text">
42+
{entry.data.name}
43+
</p>
44+
45+
):(
46+
47+
<p
48+
class="box3 fit-text"
49+
>
50+
{sessions.map((session) => <>{session.data.title} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</>)}
51+
</p>
52+
53+
<p lang="en" class="box4 fit-text">
54+
{entry.data.name}
55+
</p>
56+
57+
)
58+
}
59+
60+
61+
62+
<style>
63+
.box {
64+
margin-top:240px;
65+
width: 430px;
66+
height: 300px;
67+
font-size: 100px;
68+
color: rgb(239, 215, 123);
69+
padding: 1rem;
70+
}
71+
72+
.box2 {
73+
74+
width: 420px;
75+
height: 200px;
76+
font-size: 70px;
77+
color:white;
78+
padding: 2rem;
79+
}
80+
81+
.box3 {
82+
margin-top:280px;
83+
width: 800px;
84+
height: 300px;
85+
font-size: 100px;
86+
color: rgb(239, 215, 123);
87+
padding: 1rem;
88+
}
89+
90+
.box4 {
91+
92+
width: 580px;
93+
height: 200px;
94+
font-size: 70px;
95+
color:white;
96+
padding: 2rem;
97+
}
98+
99+
100+
.box, .box2, .box3, .box4 {
101+
box-sizing: border-box;
102+
overflow: hidden;
103+
display: flex;
104+
align-items: center;
105+
justify-content: center;
106+
text-align: center;
107+
font-family: Inter, sans-serif !important;
108+
font-weight: bold;
109+
line-height: 1em;
110+
}
111+
112+
</style>
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
---
2+
import { getCollection, getEntries, type CollectionEntry } from "astro:content";
3+
import SocialMediaCard from "@components/SocialMediaCard.astro";
4+
5+
6+
const speakers = await getCollection("speakers");
7+
type Speaker = CollectionEntry<"speakers">;
8+
---
9+
<!DOCTYPE html>
10+
<html lang="en">
11+
<head>
12+
<meta charset="utf-8" />
13+
<meta name="viewport" content="width=device-width,initial-scale=1" />
14+
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
15+
<link rel="preconnect" href="https://fonts.googleapis.com">
16+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
17+
<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">
18+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/html2canvas.min.js"></script>
19+
</head>
20+
<body class="overflow-auto">
21+
{
22+
speakers.map((entry: Speaker) => (
23+
<div class="social relative w-[900px] h-[900px] overflow-hidden">
24+
<SocialMediaCard entry={entry} />
25+
</div>
26+
))
27+
}
28+
29+
</body>
30+
</html>
31+
32+
<style is:global>
33+
34+
.social {
35+
width: 900px;
36+
height: 900px;
37+
}
38+
39+
.social svg {
40+
position: absolute;
41+
top:0;
42+
left:0;
43+
width: 100%;
44+
height: 100%;
45+
object-fit: contain;
46+
}
47+
48+
body * {
49+
font-family: Inter, sans-serif;
50+
}
51+
52+
.avatar {
53+
object-position:50% 25%;
54+
}
55+
56+
</style>
57+
58+
<script is:inline>
59+
function fitText(container) {
60+
let fontSize = 100; // Start big
61+
container.style.fontSize = fontSize + 'px';
62+
63+
while (
64+
(container.scrollWidth > container.clientWidth || container.scrollHeight > container.clientHeight)
65+
&& fontSize > 5
66+
) {
67+
fontSize -= 1;
68+
container.style.fontSize = fontSize + 'px';
69+
}
70+
}
71+
72+
function fitAllText() {
73+
const boxes = document.querySelectorAll('.fit-text');
74+
boxes.forEach(box => fitText(box));
75+
}
76+
77+
fitAllText();
78+
79+
window.addEventListener('resize', fitAllText);
80+
</script>
81+
82+
83+
<script>
84+
document.addEventListener('DOMContentLoaded', () => {
85+
document.querySelectorAll('.social').forEach((socialDiv, index) => {
86+
87+
socialDiv.addEventListener('click', () => {
88+
const svgs = socialDiv.querySelectorAll('svg');
89+
90+
if (svgs.length === 0) {
91+
alert('No SVGs found!');
92+
return;
93+
}
94+
95+
const xmlns = "http://www.w3.org/2000/svg";
96+
const combinedSvg = document.createElementNS(xmlns, "svg");
97+
combinedSvg.setAttribute("xmlns", xmlns);
98+
combinedSvg.setAttribute("width", "900");
99+
combinedSvg.setAttribute("height", "900");
100+
combinedSvg.setAttribute("viewBox", "0 0 900 900");
101+
102+
svgs.forEach(svg => {
103+
const g = document.createElementNS(xmlns, "g");
104+
g.innerHTML = svg.innerHTML;
105+
combinedSvg.appendChild(g);
106+
});
107+
108+
const serializer = new XMLSerializer();
109+
const svgString = serializer.serializeToString(combinedSvg);
110+
111+
const blob = new Blob([svgString], {type: "image/svg+xml"});
112+
const url = URL.createObjectURL(blob);
113+
114+
const a = document.createElement('a');
115+
a.href = url;
116+
a.download = `social_media_${index +1}.svg`;
117+
a.style.display = "none";
118+
document.body.appendChild(a);
119+
a.click();
120+
121+
URL.revokeObjectURL(url);
122+
document.body.removeChild(a);
123+
});
124+
});
125+
});
126+
</script>

0 commit comments

Comments
 (0)