Skip to content

Commit b4a2bc9

Browse files
committed
1 parent 6966ac7 commit b4a2bc9

File tree

2 files changed

+197
-0
lines changed

2 files changed

+197
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Prompts used are linked to from [the commit messages](https://github.com/simonw/
2222
- [MDN Browser Support Timelines](https://tools.simonwillison.net/mdn-timelines) - search for web features and view the browser support timeline pulled from [MDN](https://developer.mozilla.org/)
2323
- [Timestamp Converter](https://tools.simonwillison.net/unix-timestamp) - convert between Unix timestamps and human-readable dates
2424
- [Timezones](https://tools.simonwillison.net/timezones) - select two timezones to see a table comparing their times for the next 48 hours
25+
- [Social media cropper](https://tools.simonwillison.net/social-media-cropper) - open or paste in an image, crop it to 2x1 and download a compressed JPEG for use as a social media card
2526

2627
## LLM playgrounds and debuggers
2728

social-media-cropper.html

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Social Media Card Cropper</title>
5+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css">
6+
<style>
7+
body {
8+
font-family: system-ui, -apple-system, sans-serif;
9+
max-width: 800px;
10+
margin: 0 auto;
11+
padding: 20px;
12+
background: #f5f5f5;
13+
}
14+
.container {
15+
background: white;
16+
padding: 20px;
17+
border-radius: 8px;
18+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
19+
}
20+
.drop-zone {
21+
border: 2px dashed #ccc;
22+
border-radius: 4px;
23+
padding: 20px;
24+
text-align: center;
25+
margin-bottom: 20px;
26+
background: #fafafa;
27+
cursor: pointer;
28+
}
29+
.drop-zone.dragover {
30+
border-color: #666;
31+
background: #eee;
32+
}
33+
.img-container {
34+
max-width: 100%;
35+
margin-bottom: 20px;
36+
}
37+
#image {
38+
display: block;
39+
max-width: 100%;
40+
}
41+
.preview-container {
42+
margin-top: 20px;
43+
padding: 10px;
44+
background: #fafafa;
45+
border-radius: 4px;
46+
}
47+
#preview {
48+
width: 100%;
49+
max-width: 600px;
50+
margin: 0 auto;
51+
display: block;
52+
}
53+
.download-btn {
54+
display: inline-block;
55+
padding: 10px 20px;
56+
background: #0066cc;
57+
color: white;
58+
text-decoration: none;
59+
border-radius: 4px;
60+
margin-top: 10px;
61+
}
62+
.download-btn:hover {
63+
background: #0052a3;
64+
}
65+
.hidden {
66+
display: none;
67+
}
68+
</style>
69+
</head>
70+
<body>
71+
<div class="container">
72+
<h1>Social Media Card Cropper</h1>
73+
<p>Drop an image or click to select. The crop area is fixed to 2:1 ratio.</p>
74+
75+
<div class="drop-zone" id="dropZone">
76+
Drop image here, click to select, or paste from clipboard
77+
<input type="file" id="fileInput" accept="image/*" class="hidden">
78+
</div>
79+
80+
<div class="img-container">
81+
<img id="image" class="hidden">
82+
</div>
83+
84+
<div class="preview-container hidden" id="previewContainer">
85+
<h3>Preview (0.7 quality JPEG)</h3>
86+
<img id="preview">
87+
<div style="text-align: center; margin-top: 10px;">
88+
<a href="#" id="downloadBtn" class="download-btn">Download Social Media Card</a>
89+
</div>
90+
</div>
91+
</div>
92+
93+
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js"></script>
94+
<script>
95+
let cropper = null;
96+
const dropZone = document.getElementById('dropZone');
97+
const fileInput = document.getElementById('fileInput');
98+
99+
// Handle paste events
100+
document.addEventListener('paste', (e) => {
101+
e.preventDefault();
102+
const items = (e.clipboardData || e.originalEvent.clipboardData).items;
103+
for (const item of items) {
104+
if (item.type.indexOf('image') === 0) {
105+
const file = item.getAsFile();
106+
handleFile(file);
107+
break;
108+
}
109+
}
110+
});
111+
const image = document.getElementById('image');
112+
const preview = document.getElementById('preview');
113+
const previewContainer = document.getElementById('previewContainer');
114+
const downloadBtn = document.getElementById('downloadBtn');
115+
116+
// Handle drag and drop
117+
dropZone.addEventListener('dragover', (e) => {
118+
e.preventDefault();
119+
dropZone.classList.add('dragover');
120+
});
121+
122+
dropZone.addEventListener('dragleave', () => {
123+
dropZone.classList.remove('dragover');
124+
});
125+
126+
dropZone.addEventListener('drop', (e) => {
127+
e.preventDefault();
128+
dropZone.classList.remove('dragover');
129+
handleFile(e.dataTransfer.files[0]);
130+
});
131+
132+
// Handle click to select
133+
dropZone.addEventListener('click', () => {
134+
fileInput.click();
135+
});
136+
137+
fileInput.addEventListener('change', (e) => {
138+
if (e.target.files.length) {
139+
handleFile(e.target.files[0]);
140+
}
141+
});
142+
143+
function handleFile(file) {
144+
if (!file.type.startsWith('image/')) {
145+
alert('Please select an image file.');
146+
return;
147+
}
148+
149+
const reader = new FileReader();
150+
reader.onload = (e) => {
151+
image.src = e.target.result;
152+
image.classList.remove('hidden');
153+
initCropper();
154+
};
155+
reader.readAsDataURL(file);
156+
}
157+
158+
function initCropper() {
159+
if (cropper) {
160+
cropper.destroy();
161+
}
162+
163+
cropper = new Cropper(image, {
164+
aspectRatio: 2,
165+
viewMode: 1,
166+
dragMode: 'move',
167+
autoCropArea: 1,
168+
restore: false,
169+
guides: true,
170+
center: true,
171+
highlight: false,
172+
cropBoxMovable: true,
173+
cropBoxResizable: false,
174+
toggleDragModeOnDblclick: false,
175+
crop: updatePreview
176+
});
177+
}
178+
179+
function updatePreview() {
180+
if (!cropper) return;
181+
182+
const canvas = cropper.getCroppedCanvas();
183+
if (!canvas) return;
184+
185+
// Convert to JPEG with 0.7 quality
186+
const previewUrl = canvas.toDataURL('image/jpeg', 0.7);
187+
preview.src = previewUrl;
188+
previewContainer.classList.remove('hidden');
189+
190+
// Update download link
191+
downloadBtn.href = previewUrl;
192+
downloadBtn.download = 'social-media-card.jpg';
193+
}
194+
</script>
195+
</body>
196+
</html>

0 commit comments

Comments
 (0)