Edrol97
Silver Coder
Is there any way to change the default pdf viewer. Ideally I want to make my own pdf viewer which emulates HTML5Flipbooks? When I ask AI as it's something I don't know how to do, it gives me the option to upload a pdf or it can't find my file, even with the filepath being correct. So what's going wrong in this code? AI has advised me to run it from a web-server, but I have no idea how to do this.
Thanks as always,
Eliot
Thanks as always,
Eliot
Code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ContextStack</title>
<link rel="stylesheet" href="Style.css" />
<link rel="icon" type="image/x-icon" href="/Fav.svg" />
<style>
.body {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 20px;
padding: 20px;
width: 100%;
box-sizing: border-box;
}
@media (max-width: 768px) {
.body {
grid-template-columns: 1fr;
}
}
body::-webkit-scrollbar {
width: 1em round;
overflow: scroll;
}
body::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px #012169;
}
body::-webkit-scrollbar-thumb {
background-color: #012169;
outline: 1px solid #012169;
width: 1em round;
border-radius: 10px;
}
.publication {
position: relative;
width: 100%;
max-width: 800px;
margin: 0 auto;
aspect-ratio: 4 / 3;
}
.publication embed {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.container {
background: white;
border-radius: 15px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 1000px;
width: 100%;
margin: 40px auto;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 20px;
font-size: 24px;
}
.viewer-wrapper {
position: relative;
width: 100%;
background: #f5f5f5;
border-radius: 10px;
overflow: hidden;
perspective: 2000px;
display: none;
}
.viewer-wrapper.active {
display: block;
}
.book {
position: relative;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
min-height: 600px;
}
.page-container {
position: relative;
width: 80%;
max-width: 700px;
margin: 40px auto;
}
canvas {
width: 100%;
height: auto;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
border-radius: 5px;
background: white;
transition: transform 0.6s ease;
transform-origin: left center;
}
canvas.flipping {
animation: flip 0.6s ease-in-out;
}
@keyframes flip {
0% {
transform: rotateY(0deg);
}
50% {
transform: rotateY(-90deg);
}
100% {
transform: rotateY(-180deg);
opacity: 0;
}
}
.controls {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin-top: 30px;
padding: 20px;
background: #f9f9f9;
border-radius: 10px;
}
button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 30px;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: all 0.3s;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
}
button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.page-info {
font-size: 18px;
color: #333;
font-weight: 600;
min-width: 100px;
text-align: center;
}
.loading {
text-align: center;
padding: 40px;
color: #666;
font-size: 18px;
}
.error {
text-align: center;
padding: 40px;
color: #d32f2f;
font-size: 16px;
}
.file-input-wrapper {
text-align: center;
padding: 20px;
background: #f9f9f9;
border-radius: 10px;
margin: 20px 0;
}
.file-input-wrapper input[type="file"] {
margin: 10px 0;
}
</style>
</head>
<body>
<main>
<header>
<div class="header-content">
<div class="logo-section">
<a href="Context.html" style="text-decoration: none; color: #000">
<img
style="margin: auto; display: block"
src="TheContextWeb.png"
width="50%"
alt="Logo"
/>
</a>
</div>
</div>
<button id="hamburger" class="hamburger" aria-label="Toggle menu">
<span></span>
<span></span>
<span></span>
</button>
<nav id="nav">
<a href="index.html">Home</a>
<a href="About.html">About</a>
<a href="People.html">People</a>
<a href="Articles.html">Articles</a>
<a href="Context.html" class="active">Context</a>
<a href="Substack.html">Substack</a>
</nav>
</header>
<div class="container">
<div class="file-input-wrapper">
<p>
If the PDF doesn't load automatically, please select it manually:
</p>
<input type="file" id="pdf-file-input" accept="application/pdf" />
</div>
<div class="viewer-wrapper" id="viewer-wrapper">
<div class="book">
<div class="page-container">
<div id="loading" class="loading">Loading PDF...</div>
<div id="error" class="error" style="display: none"></div>
<canvas id="pdf-canvas"></canvas>
</div>
</div>
<div class="controls">
<button id="prev-btn" disabled>← Previous</button>
<div class="page-info">
<span id="page-num">-</span> / <span id="page-count">-</span>
</div>
<button id="next-btn" disabled>Next →</button>
</div>
</div>
</div>
</main>
<footer>
<div class="footertop">
<a href="index.html">Home</a>
<a href="About.html">About</a>
<a href="People.html">People</a>
<a href="Articles.html">Articles</a>
<a href="Context.html" class="active">Context</a>
<a href="Substack.html">Substack</a>
<a href="mailto:[email protected]">Contact</a>
</div>
<br />
<h2>
© Context Cast News 2025. Design by
<a href="https://www.house-of-lord.co.uk">Eliot Lord</a>
</h2>
</footer>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<script>
pdfjsLib.GlobalWorkerOptions.workerSrc =
"https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js";
// Try multiple possible filenames
const POSSIBLE_FILENAMES = [
"The Context Magazine Edition 1.pdf",
"./The Context Magazine Edition 1.pdf",
"The Context Winter Edition 2025.pdf",
"./The Context Winter Edition 2025.pdf",
"The%20Context%20Magazine%20Edition%201.pdf",
"The%20Context%20Winter%20Edition%202025.pdf",
"the-context-winter-edition-2025.pdf",
];
let pdfDoc = null;
let pageNum = 1;
let pageIsRendering = false;
let pageNumIsPending = null;
const canvas = document.getElementById("pdf-canvas");
const ctx = canvas.getContext("2d");
const loading = document.getElementById("loading");
const error = document.getElementById("error");
const prevBtn = document.getElementById("prev-btn");
const nextBtn = document.getElementById("next-btn");
const pageNumSpan = document.getElementById("page-num");
const pageCountSpan = document.getElementById("page-count");
const viewerWrapper = document.getElementById("viewer-wrapper");
const fileInput = document.getElementById("pdf-file-input");
async function renderPage(num) {
pageIsRendering = true;
try {
const page = await pdfDoc.getPage(num);
const viewport = page.getViewport({ scale: 2 });
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderContext = {
canvasContext: ctx,
viewport: viewport,
};
await page.render(renderContext).promise;
pageIsRendering = false;
if (pageNumIsPending !== null) {
renderPage(pageNumIsPending);
pageNumIsPending = null;
}
} catch (err) {
console.error("Error rendering page:", err);
pageIsRendering = false;
}
}
function queueRenderPage(num) {
if (pageIsRendering) {
pageNumIsPending = num;
} else {
renderPage(num);
}
}
function showPage(num) {
canvas.classList.add("flipping");
setTimeout(() => {
pageNum = num;
queueRenderPage(num);
pageNumSpan.textContent = num;
prevBtn.disabled = num <= 1;
nextBtn.disabled = num >= pdfDoc.numPages;
canvas.classList.remove("flipping");
}, 300);
}
prevBtn.addEventListener("click", () => {
if (pageNum <= 1) return;
showPage(pageNum - 1);
});
nextBtn.addEventListener("click", () => {
if (pageNum >= pdfDoc.numPages) return;
showPage(pageNum + 1);
});
async function loadPDFFromData(data) {
try {
console.log("Loading PDF from data, size:", data.length, "bytes");
loading.style.display = "block";
error.style.display = "none";
canvas.style.display = "none";
viewerWrapper.classList.add("active");
const loadingTask = pdfjsLib.getDocument({ data: data });
pdfDoc = await loadingTask.promise;
console.log("PDF loaded successfully, pages:", pdfDoc.numPages);
pageCountSpan.textContent = pdfDoc.numPages;
pageNumSpan.textContent = pageNum;
loading.style.display = "none";
canvas.style.display = "block";
await renderPage(pageNum);
prevBtn.disabled = pageNum <= 1;
nextBtn.disabled = pageNum >= pdfDoc.numPages;
} catch (err) {
console.error("Error loading PDF:", err);
loading.style.display = "none";
error.style.display = "block";
error.textContent = "Error loading PDF: " + err.message;
}
}
// Handle manual file selection
fileInput.addEventListener("change", async (e) => {
const file = e.target.files[0];
if (file && file.type === "application/pdf") {
try {
const arrayBuffer = await file.arrayBuffer();
const typedArray = new Uint8Array(arrayBuffer);
await loadPDFFromData(typedArray);
} catch (err) {
console.error("Error loading selected file:", err);
error.style.display = "block";
error.textContent = "Error loading selected PDF: " + err.message;
}
}
});
async function loadPDF() {
try {
console.log("=== Starting PDF Load ===");
loading.style.display = "block";
viewerWrapper.classList.add("active");
// Try each possible filename
for (const filename of POSSIBLE_FILENAMES) {
try {
console.log("Trying to fetch:", filename);
const response = await fetch(filename, {
method: "GET",
cache: "no-cache",
});
if (response.ok) {
const arrayBuffer = await response.arrayBuffer();
const typedArray = new Uint8Array(arrayBuffer);
console.log("✓ PDF loaded successfully from:", filename);
await loadPDFFromData(typedArray);
return;
}
} catch (fetchErr) {
console.log("✗ Failed to load:", filename, fetchErr.message);
}
}
// If all attempts fail, show helpful error
throw new Error(
"Could not automatically load the PDF. Please use the file selector above to choose your PDF file manually."
);
} catch (err) {
console.error("All loading methods failed:", err);
loading.style.display = "none";
error.style.display = "block";
error.textContent = err.message;
}
}
canvas.style.display = "none";
// Start loading when page is ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", loadPDF);
} else {
loadPDF();
}
</script>
</body>
</html>
Last edited: