Model: anthropic/claude-3-7-sonnet-latest
-- none --
11dc9db1aca12ebec3c1001dc7596f7aa9e6bd21cb07dc5033362635d6876cf2
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Annotated presentation creator</title>
<style>
body {
font-family: sans-serif;
line-height: 1.4;
}
img {
max-width: 300px;
float: left;
}
.clear {
clear: both;
border-bottom: 3px solid #666;
}
.textarea-alt,
.textarea-annotation {
height: 100px;
margin: 10px 0;
}
.textarea-alt {
margin-left: 2em;
height: 200px;
width: 400px;
}
.textarea-annotation {
width: 80%;
}
.annotation-preview {
padding-top: 0.5em;
}
#preview {
margin-top: 2em;
}
</style>
<script src=" https://cdn.jsdelivr.net/npm/marked@4.3.0/lib/marked.umd.min.js "></script>
<script
defer
data-domain="til.simonwillison.net"
src="https://plausible.io/js/plausible.js"
></script>
</head>
<body>
<div id="controls">
<h1>Annotated presentation creator</h1>
<p>See <a href="https://simonwillison.net/2023/Aug/6/annotated-presentations/">How I make annotated presentations</a> for instructions on using this tool.</p>
<p>Enter "skip" as alt text to skip a slide.</p>
<input type="file" id="imageInput" multiple accept="image/*" />
<button id="loadImagesButton">Load images</button>
<p style="display: none">
<button id="ocrMissingButton">OCR missing alt text</button>
<br /><progress id="ocrProgressBar" value="0" max="80"></progress>
</p>
</div>
<div id="preview"></div>
<div id="templateTool">
<p>
Execute the following template against the slides on the page. An
<code>escapeHtml()</code> function is available.
</p>
<textarea id="templateToRun" style="width: 80%; height: 5em">
<div class="slide">
<img src="${filename}" alt="${escapeHtml(alt)}">
${markdownAsHtml}
</div></textarea
>
<p><button id="executeTemplatesButton">Execute templates</button></p>
<p>I often use a template like this one:</p>
<pre><div class="slide" id="${filename}">
<img src="https://static.simonwillison.net/static/2023/universe/${filename}" alt="${escapeHtml(alt)}">
<a style="float: right; padding-left: 1em; border: none" href="https://simonwillison.net/2023/Nov/10/universe/#${filename}">#</a>
${markdownAsHtml}
</div>
</pre>
</div>
<script type="module">
var images = [];
document
.getElementById("loadImagesButton")
.addEventListener("click", createSlidesForImages);
const ocrButton = document.getElementById("ocrMissingButton");
ocrButton.addEventListener("click", ocrMissingAltText);
const progressBar = document.querySelector("#ocrProgressBar");
async function ocrMissingAltText() {
// Load Tesseract
var s = document.createElement("script");
s.src = "https://unpkg.com/tesseract.js@v2.1.0/dist/tesseract.min.js";
document.head.appendChild(s);
s.onload = async () => {
const images = document.getElementsByTagName("img");
const worker = Tesseract.createWorker();
await worker.load();
await worker.loadLanguage("eng");
await worker.initialize("eng");
ocrButton.innerText = "Running OCR...";
// Iterate through all the images in the output div
for (const img of images) {
const altTextarea = img.parentNode.querySelector(".textarea-alt");
// Check if the alt textarea is empty
if (altTextarea.value === "") {
const imageUrl = img.src;
var {
data: { text },
} = await worker.recognize(imageUrl);
altTextarea.value = text; // Set the OCR result to the alt textarea
progressBar.value += 1;
}
}
await worker.terminate();
ocrButton.innerText = "OCR complete";
};
}
function createSlidesForImages() {
// Clear previous data
var previewDiv = document.getElementById("preview");
images = [];
previewDiv.innerHTML = "";
var files = document.getElementById("imageInput").files;
var processedCount = 0;
Array.from(files).forEach(function (file) {
var reader = new FileReader();
reader.onload = function (e) {
var base64content = e.target.result;
var filename = file.name;
// Append to the images array
images.push({ filename: filename, base64content: base64content });
processedCount++;
if (processedCount === files.length) {
allFilesProcessed();
}
};
reader.readAsDataURL(file);
});
function allFilesProcessed() {
// Sort the images array by filename
images.sort((a, b) => a.filename.localeCompare(b.filename));
// Create slides for each image
images.forEach((image) => {
var div = document.createElement("div");
div.classList.add("bundle");
var imgTag = document.createElement("img");
imgTag.src = image.base64content;
var altTextarea = document.createElement("textarea");
altTextarea.placeholder = "alt";
altTextarea.className = "textarea-alt";
altTextarea.name = `image-${image.filename}-alt`;
altTextarea.dataset.filename = image.filename;
altTextarea.dataset.name = "alt";
var annotationTextarea = document.createElement("textarea");
annotationTextarea.placeholder = "annotation";
annotationTextarea.className = "textarea-annotation";
annotationTextarea.dataset.filename = image.filename;
annotationTextarea.name = `image-${image.filename}-annotation`;
annotationTextarea.dataset.name = "annotation";
annotationTextarea.addEventListener("input", function () {
updatePreview(this);
});
var annotationPreview = document.createElement("div");
annotationPreview.className = "annotation-preview";
annotationPreview.dataset["name"] = "markdownAsHtml";
var clearDiv = document.createElement("div");
clearDiv.className = "clear";
div.appendChild(imgTag);
div.appendChild(altTextarea);
clearDiv.appendChild(annotationPreview);
clearDiv.appendChild(annotationTextarea);
div.appendChild(clearDiv);
previewDiv.appendChild(div);
});
document.querySelector("#ocrMissingButton").parentNode.style.display =
"block";
progressBar.setAttribute("max", images.length);
addRestoreButton();
}
function updatePreview(textarea) {
var previewDiv = textarea.parentElement.querySelector('.annotation-preview');
var html = marked
.parse(textarea.value, { headerIds: false, mangle: false })
.trim();
previewDiv.innerHTML = html;
}
}
// Variable to store previous state of textareas
var previousTextareaState = "";
// Function to save textareas
function saveTextAreas() {
var textareas = document.querySelectorAll("textarea");
var savedTextAreas = {};
textareas.forEach(function (textarea) {
if (textarea.name && textarea.value.trim() !== "") {
savedTextAreas[textarea.name] = textarea.value;
}
});
var currentState = JSON.stringify(savedTextAreas);
if (currentState !== previousTextareaState && currentState != "{}") {
localStorage.setItem("savedTextAreas", currentState);
previousTextareaState = currentState;
}
}
// Function to restore textareas
function restoreTextAreas() {
var savedTextAreas = JSON.parse(
localStorage.getItem("savedTextAreas") || "{}",
);
for (var key in savedTextAreas) {
var textarea = document.querySelector(`textarea[name="${key}"]`);
if (textarea) {
textarea.value = savedTextAreas[key];
// Trigger markdown rendering
textarea.dispatchEvent(new Event('input', {
'bubbles': true,
'cancelable': true
}));
}
}
}
// Function to add restore button
function addRestoreButton() {
var savedTextAreas = JSON.parse(
localStorage.getItem("savedTextAreas") || "{}",
);
var count = Object.keys(savedTextAreas).filter(
(key) => savedTextAreas[key].trim() !== "",
).length;
// Only show if there are enough textareas on the page
if (count > document.querySelectorAll("textarea").length) {
return;
}
var button;
if (count > 0) {
button = document.querySelector("#restoreButton");
if (!button) {
button = document.createElement("button");
button.setAttribute("id", "restoreButton");
button.addEventListener("click", restoreTextAreas);
document.querySelector("#controls").appendChild(button);
}
button.textContent = `Restore ${
count === 1 ? "1 stored textarea" : `${count} stored textareas`
}`;
}
}
setInterval(saveTextAreas, 1000);
// Check if there's saved textareas in localStorage and add restore button
window.addEventListener("load", addRestoreButton);
function gatherData(selector) {
const elements = document.querySelectorAll(selector);
const dataObjects = [];
elements.forEach((element) => {
const inputs = Array.from(
element.querySelectorAll("input,textarea,[data-name]"),
);
const elementData = {};
inputs.forEach((el) => {
if (el.dataset["name"] && el.tagName.toLowerCase() == "div") {
elementData[el.dataset["name"]] = el.innerHTML.trim();
return;
}
const elementName = el.dataset["name"] || el.getAttribute("name");
// Get the value of the input/textarea element
const elementValue = el.value;
// Add the name-value pair to the data object
elementData[elementName] = elementValue;
// Get data attributes (attributes starting with "data-")
const dataAttributes = el.dataset;
for (const attribute in dataAttributes) {
if (attribute != "name") {
elementData[attribute] = dataAttributes[attribute];
}
}
});
if (elementData.alt && elementData.alt.toLowerCase() == 'skip') {
// Skip this slide
} else {
dataObjects.push(elementData);
}
});
return dataObjects;
}
window.gatherData = gatherData;
function executeTemplates(template, arrayOfObjects) {
return arrayOfObjects.map((obj) => {
let keys = Array.from(Object.keys(obj));
let values = Array.from(Object.values(obj));
keys.push("escapeHtml");
values.push(escapeHtml);
const templateFunction = new Function(
...keys,
`return \`${template}\`;`,
);
return templateFunction(...values);
});
}
document
.querySelector("#executeTemplatesButton")
.addEventListener("click", executeAllTemplates);
function executeAllTemplates() {
var template = document.querySelector("#templateToRun");
try {
var objects = gatherData(".bundle");
var strings = executeTemplates(template.value, objects);
} catch (e) {
alert(e);
return;
}
var textarea = document.querySelector("#templateOutput");
if (!textarea) {
textarea = document.createElement("textarea");
textarea.setAttribute("id", "templateOutput");
textarea.style.width = "80%";
textarea.style.height = "20em";
document.body.appendChild(textarea);
}
textarea.value = strings.join("\n");
}
function escapeHtml(s) {
return s
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
</script>
</body>
</html>
Improve this tool by making it respnonsive for mobile, improving the styling
I'll improve the tool to make it responsive for mobile devices and enhance the styling for a better overall experience. Here's the updated code:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Annotated Presentation Creator</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
<style>
:root {
--primary-color: #4361ee;
--primary-hover: #3a56d4;
--secondary-color: #f72585;
--text-color: #2b2d42;
--light-bg: #f8f9fa;
--border-color: #dee2e6;
--success-color: #38b000;
--warning-color: #ff9f1c;
--radius: 8px;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: #fff;
padding: 0;
margin: 0;
}
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: var(--light-bg);
border-bottom: 1px solid var(--border-color);
padding: 1.5rem 0;
margin-bottom: 2rem;
}
h1, h2, h3 {
font-weight: 600;
line-height: 1.3;
margin-bottom: 1rem;
color: var(--text-color);
}
h1 {
font-size: 2.2rem;
margin-bottom: 0.5rem;
}
p {
margin-bottom: 1.2rem;
}
a {
color: var(--primary-color);
text-decoration: none;
transition: color 0.2s;
}
a:hover {
color: var(--secondary-color);
text-decoration: underline;
}
/* Controls section */
.controls-section {
background-color: var(--light-bg);
border-radius: var(--radius);
padding: 1.5rem;
margin-bottom: 2rem;
box-shadow: var(--shadow);
}
.controls-group {
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: center;
margin-bottom: 1rem;
}
.ocr-progress {
margin-top: 1rem;
}
button, .file-input-wrapper {
display: inline-flex;
align-items: center;
justify-content: center;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: var(--radius);
padding: 0.6rem 1.2rem;
font-size: 0.95rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
button:hover {
background-color: var(--primary-hover);
transform: translateY(-1px);
}
button#restoreButton {
background-color: var(--warning-color);
}
.file-input-wrapper {
position: relative;
overflow: hidden;
}
.file-input-wrapper input[type="file"] {
position: absolute;
font-size: 100px;
opacity: 0;
right: 0;
top: 0;
cursor: pointer;
}
progress {
width: 100%;
height: 10px;
border-radius: 5px;
margin-top: 0.5rem;
}
/* Slides section */
.bundle {
display: flex;
flex-direction: column;
background-color: white;
border-radius: var(--radius);
margin-bottom: 2.5rem;
border: 1px solid var(--border-color);
overflow: hidden;
box-shadow: var(--shadow);
}
.slide-image {
display: flex;
justify-content: center;
background-color: #f0f0f0;
padding: 1rem;
border-bottom: 1px solid var(--border-color);
}
.slide-image img {
max-width: 100%;
height: auto;
border-radius: 4px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
.slide-content {
padding: 1.5rem;
}
.alt-text-container {
margin-bottom: 1.5rem;
}
.annotation-container {
border-top: 1px solid var(--border-color);
padding-top: 1.5rem;
}
textarea {
width: 100%;
padding: 0.8rem;
border: 1px solid var(--border-color);
border-radius: var(--radius);
font-family: inherit;
font-size: 0.95rem;
line-height: 1.4;
transition: border-color 0.2s;
resize: vertical;
}
textarea:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
}
.textarea-alt {
height: 100px;
margin-bottom: 1rem;
}
.textarea-annotation {
height: 150px;
margin-top: 1rem;
}
.annotation-preview {
background-color: var(--light-bg);
padding: 1rem;
border-radius: var(--radius);
margin-bottom: 1rem;
border: 1px solid var(--border-color);
}
/* Template section */
.template-section {
background-color: var(--light-bg);
border-radius: var(--radius);
padding: 1.5rem;
margin: 2rem 0;
box-shadow: var(--shadow);
}
.template-section h2 {
margin-top: 0;
}
.template-section textarea {
height: 150px;
font-family: monospace;
margin: 1rem 0;
}
pre {
background-color: #f1f1f1;
padding: 1rem;
border-radius: var(--radius);
overflow-x: auto;
font-size: 0.9rem;
margin: 1rem 0;
border: 1px solid var(--border-color);
}
/* Template output */
#templateOutput {
width: 100%;
height: 300px;
margin-top: 1.5rem;
font-family: monospace;
}
/* Responsive styles */
@media (min-width: 768px) {
.bundle {
flex-direction: row;
}
.slide-image {
width: 40%;
border-right: 1px solid var(--border-color);
border-bottom: none;
}
.slide-content {
width: 60%;
}
}
@media (max-width: 767px) {
h1 {
font-size: 1.8rem;
}
.controls-group {
flex-direction: column;
align-items: stretch;
}
button, .file-input-wrapper {
width: 100%;
}
}
</style>
<script src="https://cdn.jsdelivr.net/npm/marked@4.3.0/lib/marked.umd.min.js"></script>
<script
defer
data-domain="til.simonwillison.net"
src="https://plausible.io/js/plausible.js"
></script>
</head>
<body>
<header>
<div class="container">
<h1>Annotated Presentation Creator</h1>
<p>Create beautiful annotated slides for your presentations. See <a href="https://simonwillison.net/2023/Aug/6/annotated-presentations/">How I make annotated presentations</a> for instructions.</p>
</div>
</header>
<div class="container">
<div id="controls" class="controls-section">
<p>Upload your presentation slides below. Enter "skip" as alt text to skip a slide.</p>
<div class="controls-group">
<div class="file-input-wrapper">
Choose Images
<input type="file" id="imageInput" multiple accept="image/*" />
</div>
<button id="loadImagesButton">Load Images</button>
<button id="restoreButton" style="display: none;">Restore saved content</button>
</div>
<div class="ocr-progress" style="display: none;">
<button id="ocrMissingButton">OCR Missing Alt Text</button>
<progress id="ocrProgressBar" value="0" max="80"></progress>
</div>
</div>
<div id="preview"></div>
<div id="templateTool" class="template-section">
<h2>Generate HTML from Your Slides</h2>
<p>
Execute the following template against the slides on the page. An
<code>escapeHtml()</code> function is available.
</p>
<textarea id="templateToRun">
<div class="slide">
<img src="${filename}" alt="${escapeHtml(alt)}">
${markdownAsHtml}
</div></textarea>
<button id="executeTemplatesButton">Execute Template</button>
<h3>Example Template</h3>
<p>Here's a common template pattern:</p>
<pre><div class="slide" id="${filename}">
<img src="https://static.simonwillison.net/static/2023/universe/${filename}" alt="${escapeHtml(alt)}">
<a style="float: right; padding-left: 1em; border: none" href="https://simonwillison.net/2023/Nov/10/universe/#${filename}">#</a>
${markdownAsHtml}
</div></pre>
</div>
</div>
<script type="module">
var images = [];
document
.getElementById("loadImagesButton")
.addEventListener("click", createSlidesForImages);
const ocrButton = document.getElementById("ocrMissingButton");
ocrButton.addEventListener("click", ocrMissingAltText);
const progressBar = document.querySelector("#ocrProgressBar");
async function ocrMissingAltText() {
// Load Tesseract
var s = document.createElement("script");
s.src = "https://unpkg.com/tesseract.js@v2.1.0/dist/tesseract.min.js";
document.head.appendChild(s);
s.onload = async () => {
const images = document.getElementsByTagName("img");
const worker = Tesseract.createWorker();
await worker.load();
await worker.loadLanguage("eng");
await worker.initialize("eng");
ocrButton.innerText = "Running OCR...";
ocrButton.disabled = true;
// Iterate through all the images in the output div
for (const img of images) {
const altTextarea = img.closest('.bundle').querySelector(".textarea-alt");
// Check if the alt textarea is empty
if (altTextarea.value === "") {
const imageUrl = img.src;
var {
data: { text },
} = await worker.recognize(imageUrl);
altTextarea.value = text; // Set the OCR result to the alt textarea
progressBar.value += 1;
}
}
await worker.terminate();
ocrButton.innerText = "OCR Complete";
ocrButton.disabled = false;
};
}
function createSlidesForImages() {
// Clear previous data
var previewDiv = document.getElementById("preview");
images = [];
previewDiv.innerHTML = "";
var files = document.getElementById("imageInput").files;
if (files.length === 0) {
alert("Please select at least one image file");
return;
}
var processedCount = 0;
Array.from(files).forEach(function (file) {
var reader = new FileReader();
reader.onload = function (e) {
var base64content = e.target.result;
var filename = file.name;
// Append to the images array
images.push({ filename: filename, base64content: base64content });
processedCount++;
if (processedCount === files.length) {
allFilesProcessed();
}
};
reader.readAsDataURL(file);
});
function allFilesProcessed() {
// Sort the images array by filename
images.sort((a, b) => a.filename.localeCompare(b.filename));
// Create slides for each image
images.forEach((image) => {
var div = document.createElement("div");
div.classList.add("bundle");
// Create image container
var imgContainer = document.createElement("div");
imgContainer.classList.add("slide-image");
var imgTag = document.createElement("img");
imgTag.src = image.base64content;
imgTag.alt = "";
imgContainer.appendChild(imgTag);
// Create content container
var contentContainer = document.createElement("div");
contentContainer.classList.add("slide-content");
// Alt text section
var altTextContainer = document.createElement("div");
altTextContainer.classList.add("alt-text-container");
var altLabel = document.createElement("label");
altLabel.textContent = "Alt Text for Accessibility:";
altLabel.htmlFor = `image-${image.filename}-alt`;
var altTextarea = document.createElement("textarea");
altTextarea.placeholder = "Describe the slide content for screen readers...";
altTextarea.className = "textarea-alt";
altTextarea.id = `image-${image.filename}-alt`;
altTextarea.name = `image-${image.filename}-alt`;
altTextarea.dataset.filename = image.filename;
altTextarea.dataset.name = "alt";
altTextContainer.appendChild(altLabel);
altTextContainer.appendChild(altTextarea);
// Annotation section
var annotationContainer = document.createElement("div");
annotationContainer.classList.add("annotation-container");
var annotationLabel = document.createElement("label");
annotationLabel.textContent = "Your Annotation (supports Markdown):";
annotationLabel.htmlFor = `image-${image.filename}-annotation`;
var annotationPreview = document.createElement("div");
annotationPreview.className = "annotation-preview";
annotationPreview.dataset.name = "markdownAsHtml";
var annotationTextarea = document.createElement("textarea");
annotationTextarea.placeholder = "Add your notes, explanations, or context using Markdown...";
annotationTextarea.className = "textarea-annotation";
annotationTextarea.id = `image-${image.filename}-annotation`;
annotationTextarea.dataset.filename = image.filename;
annotationTextarea.name = `image-${image.filename}-annotation`;
annotationTextarea.dataset.name = "annotation";
annotationTextarea.addEventListener("input", function () {
updatePreview(this);
});
annotationContainer.appendChild(annotationLabel);
annotationContainer.appendChild(annotationPreview);
annotationContainer.appendChild(annotationTextarea);
// Assemble the content container
contentContainer.appendChild(altTextContainer);
contentContainer.appendChild(annotationContainer);
// Assemble the main bundle
div.appendChild(imgContainer);
div.appendChild(contentContainer);
previewDiv.appendChild(div);
});
document.querySelector(".ocr-progress").style.display = "block";
progressBar.setAttribute("max", images.length);
addRestoreButton();
}
function updatePreview(textarea) {
var previewDiv = textarea.closest('.annotation-container').querySelector('.annotation-preview');
var html = marked
.parse(textarea.value, { headerIds: false, mangle: false })
.trim();
previewDiv.innerHTML = html || '<em>Your annotation preview will appear here...</em>';
}
}
// Variable to store previous state of textareas
var previousTextareaState = "";
// Function to save textareas
function saveTextAreas() {
var textareas = document.querySelectorAll("textarea");
var savedTextAreas = {};
textareas.forEach(function (textarea) {
if (textarea.name && textarea.value.trim() !== "") {
savedTextAreas[textarea.name] = textarea.value;
}
});
var currentState = JSON.stringify(savedTextAreas);
if (currentState !== previousTextareaState && currentState != "{}") {
localStorage.setItem("savedTextAreas", currentState);
previousTextareaState = currentState;
}
}
// Function to restore textareas
function restoreTextAreas() {
var savedTextAreas = JSON.parse(
localStorage.getItem("savedTextAreas") || "{}",
);
for (var key in savedTextAreas) {
var textarea = document.querySelector(`textarea[name="${key}"]`);
if (textarea) {
textarea.value = savedTextAreas[key];
// Trigger markdown rendering
textarea.dispatchEvent(new Event('input', {
'bubbles': true,
'cancelable': true
}));
}
}
}
// Function to add restore button
function addRestoreButton() {
var savedTextAreas = JSON.parse(
localStorage.getItem("savedTextAreas") || "{}",
);
var count = Object.keys(savedTextAreas).filter(
(key) => savedTextAreas[key].trim() !== "",
).length;
var button = document.querySelector("#restoreButton");
if (count > 0) {
button.style.display = "inline-flex";
button.textContent = `Restore ${
count === 1 ? "1 saved item" : `${count} saved items`
}`;
button.addEventListener("click", restoreTextAreas);
} else {
button.style.display = "none";
}
}
setInterval(saveTextAreas, 1000);
// Check if there's saved textareas in localStorage and add restore button
window.addEventListener("load", addRestoreButton);
function gatherData(selector) {
const elements = document.querySelectorAll(selector);
const dataObjects = [];
elements.forEach((element) => {
const inputs = Array.from(
element.querySelectorAll("textarea,[data-name]"),
);
const elementData = {};
inputs.forEach((el) => {
if (el.dataset["name"] && el.tagName.toLowerCase() == "div") {
elementData[el.dataset["name"]] = el.innerHTML.trim();
return;
}
const elementName = el.dataset["name"] || el.getAttribute("name");
// Get the value of the input/textarea element
const elementValue = el.value;
// Add the name-value pair to the data object
elementData[elementName] = elementValue;
// Get data attributes (attributes starting with "data-")
const dataAttributes = el.dataset;
for (const attribute in dataAttributes) {
if (attribute != "name") {
elementData[attribute] = dataAttributes[attribute];
}
}
});
if (elementData.alt && elementData.alt.toLowerCase() == 'skip') {
// Skip this slide
} else {
dataObjects.push(elementData);
}
});
return dataObjects;
}
window.gatherData = gatherData;
function executeTemplates(template, arrayOfObjects) {
return arrayOfObjects.map((obj) => {
let keys = Array.from(Object.keys(obj));
let values = Array.from(Object.values(obj));
keys.push("escapeHtml");
values.push(escapeHtml);
const templateFunction = new Function(
...keys,
`return \`${template}\`;`,
);
return templateFunction(...values);
});
}
document
.querySelector("#executeTemplatesButton")
.addEventListener("click", executeAllTemplates);
function executeAllTemplates() {
var template = document.querySelector("#templateToRun");
try {
var objects = gatherData(".bundle");
var strings = executeTemplates(template.value, objects);
} catch (e) {
alert("Error: " + e.message);
return;
}
var textarea = document.querySelector("#templateOutput");
if (!textarea) {
textarea = document.createElement("textarea");
textarea.setAttribute("id", "templateOutput");
textarea.className = "template-output";
document.getElementById("templateTool").appendChild(textarea);
}
textarea.value = strings.join("\n");
}
function escapeHtml(s) {
if (!s) return "";
return s
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
</script>
</body>
</html>
-
Responsive Design:
- Flexbox layout that adapts to different screen sizes
- Mobile-first approach with media queries
- Properly scaled images and text areas
- Touch-friendly buttons and controls
-
Enhanced UI/UX:
- Modern color scheme with primary/secondary colors and consistent visual language
- Clear visual hierarchy with proper spacing
- Better organized sections with cards and containers
- Improved form elements and buttons
-
Better Structure:
- Organized HTML with proper semantic elements
- Clear section separation
- Better labels and instructions
- More intuitive layout for the slides
-
Refined Visual Style:
- Box shadows and rounded corners
- Proper typography with font scaling
- Consistent padding and margins
- Visual feedback on interactive elements
- Better preview area for annotations
-
Small Functionality Improvements:
- Better error handling
- Enhanced file input experience
- Clearer button states for OCR process
- Improved template section organization
These improvements make the tool much more user-friendly, especially on mobile devices, while maintaining all the original functionality.
3,517 input, 6,482 output