<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Project Previewer</title>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(to bottom right, #280f34, #120417);
color: #ff6b00;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
/* Removed text-shadow */
}
/* Banner Header */
#banner-header {
background-color: rgba(0, 0, 0, 0.7);
padding: 0 10px; /* Reduced padding */
height: 40px; /* Fixed height */
display: flex;
align-items: center;
justify-content: center; /* Center the title */
backdrop-filter: blur(10px);
border-bottom: 1px solid #ff6b0044;
}
#banner-title {
font-family: 'Orbitron', sans-serif; /* Fancy Font */
font-size: 1.8em; /* Increased font size */
font-weight: 700;
margin: 0;
letter-spacing: 2px; /* Add letter spacing */
}
#header {
background-color: rgba(0, 0, 0, 0.5);
padding: 10px;
display: flex;
align-items: center;
justify-content: space-between;
backdrop-filter: blur(10px);
border-bottom: 1px solid #ff6b0044;
}
#file-input-container {
display: flex;
align-items: center;
position: relative;
}
#file-input {
position: absolute;
opacity: 0;
width: 0.1px;
height: 0.1px;
}
#open-folder-button, #save-file-button, #back-to-root, #fullscreen-button {
background-color: #ff6b00;
color: #120417;
padding: 8px 16px;
border: none;
border-radius: 20px;
cursor: pointer;
margin-right: 10px;
font-size: 14px;
font-weight: 900; /* Bolder button text */
box-shadow: 0 0 10px #ff6b00;
transition: transform 0.2s, box-shadow 0.2s;
}
#open-folder-button:hover, #save-file-button:hover, #back-to-root:hover, #fullscreen-button:hover{
transform: scale(1.05);
box-shadow: 0 0 15px #ff6b00;
}
#project-name {
font-size: 1.5em;
font-weight: bold;
margin: 0;
padding: 0 10px;
}
#file-list-container {
overflow-y: auto;
height: 150px;
border-bottom: 1px solid #ff6b0044;
padding: 10px;
}
#file-list {
list-style: none;
padding: 0;
margin: 0;
}
#file-list li {
padding: 8px 12px;
border-bottom: 1px solid #ff6b0022;
cursor: pointer;
transition: background-color: 0.2s;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border-radius: 5px;
}
#file-list li:last-child {
border-bottom: none;
}
#file-list li:hover {
background-color: rgba(255, 107, 0, 0.2);
}
#preview-container {
flex-grow: 1;
overflow: hidden;
position: relative;
padding: 10px;
}
#preview-iframe {
width: 100%;
height: 100%;
border: none;
background-color: white;
border-radius: 10px;
}
#status-bar {
background-color: rgba(0, 0, 0, 0.5);
padding: 5px 10px;
font-size: 0.8em;
text-align: right;
backdrop-filter: blur(10px);
border-top: 1px solid #ff6b0044;
}
/* Mobile Styles */
@media (max-width: 768px) {
#file-list-container {
max-height: 150px; /* Smaller on mobile */
width: 100%; /* Full width on small screen */
}
#preview-iframe, #editor-container{
width: 100%;
}
#file-list-toggle {
display: block; /* Show the toggle button */
}
#file-list.collapsed {
display: none;
}
#open-folder-button, #save-file-button, #back-to-root, #fullscreen-button {
padding: 6px 12px; /* Smaller padding */
font-size: 12px;
}
#project-name{
font-size: 1.2em;
}
#banner-header{
height: auto; /*Allow height to adjust on smaller screens*/
}
#banner-title{
font-size: 1.2em; /*Smaller Font size on mobile*/
}
}
#file-list-toggle {
display: none; /* Hidden by default */
background-color: #ff6b00;
color: #120417;
padding: 8px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-bottom: 5px; /* Space below the button */
font-weight: bold;
}
</style>
</head>
<body>
<!-- Banner Header -->
<div id="banner-header">
<h1 id="banner-title">V I e w b a d g e r 2 0 2 5</h1>
</div>
<div id="header">
<div id="file-input-container">
<input type="file" id="file-input" webkitdirectory directory style="position: absolute; opacity: 0; width: 0.1px; height: 0.1px;">
<button id="open-folder-button">Open Folder</button>
<button id="back-to-root" style="display: none;">Back to Root</button>
<button id="fullscreen-button">Fullscreen</button>
</div>
<h2 id="project-name"></h2> <!-- Project Name -->
</div>
<div id="file-list-toggle">Toggle File List</div> <!-- Toggle button for mobile -->
<div id="file-list-container">
<ul id="file-list"></ul>
</div>
<div id="preview-container">
<iframe id="preview-iframe" sandbox="allow-scripts allow-same-origin allow-popups allow-forms"></iframe>
</div>
<div id="status-bar">
Ready
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const fileInput = document.getElementById('file-input');
const openFolderButton = document.getElementById('open-folder-button');
const fileList = document.getElementById('file-list');
const backToRootButton = document.getElementById('back-to-root');
const previewIframe = document.getElementById('preview-iframe');
const projectNameDisplay = document.getElementById('project-name'); // Get element
const fullscreenButton = document.getElementById('fullscreen-button');
const fileListToggle = document.getElementById('file-list-toggle');
const statusBar = document.getElementById("status-bar")
let files = [];
let rootPath = "";
let currentPath = "";
// --- Fullscreen Toggle ---
fullscreenButton.addEventListener('click', () => {
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
previewIframe.requestFullscreen();
}
});
// ---Mobile file list toggle
fileListToggle.addEventListener('click', () => {
fileList.classList.toggle('collapsed');
});
openFolderButton.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', (event) => {
files = Array.from(event.target.files);
if (files.length > 0) {
rootPath = files[0].webkitRelativePath.split('/')[0] + "/";
currentPath = rootPath;
projectNameDisplay.textContent = rootPath; // Set project name
backToRootButton.style.display = 'inline-block';
renderFileList();
// Automatically load index.html if it exists
const indexFile = files.find(file => file.webkitRelativePath === rootPath + 'index.html');
if (indexFile) {
loadFile(indexFile);
} else if (files.length > 0) {
loadFile(files[0]);
}
}
});
backToRootButton.addEventListener('click', () => {
currentPath = rootPath;
projectNameDisplay.textContent = rootPath; // Update display!
renderFileList();
});
function getFileExtension(filename) {
return filename.split('.').pop().toLowerCase();
}
function getRelativePath(fullPath, basePath) {
if (fullPath.startsWith(basePath)) {
return fullPath.substring(basePath.length);
}
return fullPath;
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function loadFile(file) {
const reader = new FileReader();
const fileExtension = getFileExtension(file.name);
reader.onload = (event) => {
if (['html', 'htm'].includes(fileExtension)) {
const url = URL.createObjectURL(file);
previewIframe.src = url;
} else if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].includes(fileExtension)) {
const url = URL.createObjectURL(file);
previewIframe.src = url;
} else {
// Basic display for other file types
previewIframe.srcdoc = `<pre style="color: #ff6b00;">${escapeHtml(event.target.result)}</pre>`;
}
statusBar.textContent = `Loaded: ${file.webkitRelativePath}`;
};
if (['js', 'css', 'txt', 'html', 'htm'].includes(file.name.split('.').pop().toLowerCase())) {
reader.readAsText(file); //Crucial for editable text
} else {
reader.readAsArrayBuffer(file); //Still needed for image object URLs
}
}
function renderFileList() {
fileList.innerHTML = '';
const currentFiles = files.filter(file => file.webkitRelativePath.startsWith(currentPath));
const entries = new Set();
currentFiles.forEach(file => {
const relativePath = getRelativePath(file.webkitRelativePath, currentPath);
const parts = relativePath.split('/');
if (parts.length > 1) {
entries.add(parts[0] + "/");
} else if (parts[0]) {
entries.add(parts[0]);
}
});
entries.forEach(entry => {
const listItem = document.createElement('li');
listItem.textContent = entry;
listItem.dataset.path = currentPath + entry;
fileList.appendChild(listItem);
listItem.addEventListener('click', () => {
if (entry.endsWith("/")) {
currentPath = listItem.dataset.path;
projectNameDisplay.textContent = listItem.dataset.path; // Update on folder click
renderFileList();
} else {
const fileToLoad = files.find(f => f.webkitRelativePath === listItem.dataset.path);
if (fileToLoad) {
loadFile(fileToLoad);
}
}
});
});
}
// Prevent iframe navigation to external URLs
previewIframe.addEventListener('load', () => {
try {
if (previewIframe.contentWindow.location.origin !== window.location.origin) {
previewIframe.srcdoc = `<p style="color: #ff6b00;">Navigation to external URLs is blocked.</p>`;
statusBar.textContent = 'Blocked external navigation.';
}
} catch (error) {
// Cross-origin access error (expected)
}
});
});
</script>
</body>
</html>
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.