portfolio / ProjectViewr /ProjectViewr.html
Mohammedallyl's picture
Upload 71 files
3c83d6f verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Project Details</title>
<!-- Iconify -->
<script src="https://code.iconify.design/3/3.1.1/iconify.min.js"></script>
<!-- Marked.js -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
:root {
--bg: #0f0f0f;
--card: #1a1a1a;
--accent: #f5c542;
--text: #e5e5e5;
--muted: #9e9e9e;
--divider: #2a2a2a;
--error: #ff5252;
--radius: 20px;
--shadow: 0 10px 30px rgba(0,0,0,0.6);
--font: "Segoe UI", Roboto, sans-serif;
}
* { box-sizing: border-box; }
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: var(--font);
line-height: 1.7;
}
.page {
min-height: 100vh;
display: flex;
justify-content: center;
padding: 60px 16px;
}
.container {
width: 100%;
max-width: 1000px;
background: var(--card);
border-radius: var(--radius);
padding: 50px 40px;
box-shadow: var(--shadow);
animation: fadeIn 0.4s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
h1 {
margin: 0;
text-align: center;
font-size: 36px;
color: var(--accent);
}
.section {
margin-top: 50px;
}
.section-title {
text-align: center;
font-size: 14px;
letter-spacing: 2px;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 20px;
}
.divider {
height: 1px;
background: var(--divider);
margin: 40px 0;
opacity: 0.6;
}
.category {
text-align: center;
color: var(--muted);
}
.tools {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 30px;
}
.tool {
display: flex;
flex-direction: column;
align-items: center;
font-size: 14px;
color: var(--muted);
transition: transform 0.3s;
}
.tool:hover { transform: translateY(-4px); }
.tool .iconify {
font-size: 40px;
color: var(--accent);
margin-bottom: 8px;
}
.media {
display: flex;
justify-content: center;
}
.media video,
.media img {
width: 100%;
max-width: 750px;
border-radius: var(--radius);
box-shadow: var(--shadow);
}
.description {
max-width: 800px;
margin: auto;
}
.description h1,
.description h2,
.description h3 {
color: var(--accent);
}
.description pre {
background: #111;
padding: 15px;
border-radius: 10px;
overflow-x: auto;
}
.loading {
text-align: center;
color: var(--muted);
}
.error {
text-align: center;
color: var(--error);
}
@media (max-width: 768px) {
.container { padding: 30px 20px; }
h1 { font-size: 28px; }
}
</style>
</head>
<body>
<div class="page">
<div class="container">
<h1 id="title"></h1>
<div class="section">
<div class="section-title">Category</div>
<div class="category" id="category"></div>
</div>
<div class="divider"></div>
<div class="section">
<div class="section-title">Tools Used</div>
<div class="tools" id="tools"></div>
</div>
<div class="divider"></div>
<div class="section">
<div class="section-title">Preview</div>
<div class="media" id="media"></div>
</div>
<div class="divider"></div>
<div class="section">
<div class="section-title">Description</div>
<div id="description" class="description loading">
Loading README...
</div>
</div>
</div>
</div>
<script>
const toolIcons = {
python: "logos:python",
tensorflow: "logos:tensorflow",
opencv: "logos:opencv",
fastapi: "logos:fastapi-icon",
nextjs: "logos:nextjs-icon",
react: "logos:react",
javascript: "logos:javascript",
docker: "logos:docker-icon",
linux: "logos:linux-tux"
};
document.addEventListener("DOMContentLoaded", init);
async function init() {
const params = new URLSearchParams(window.location.search);
const dataFile = params.get("data"); // e.g., "data/Pro1.json"
if (!dataFile) return showError("No project file provided.");
let project;
try {
const res = await fetch(dataFile);
if (!res.ok) throw new Error();
project = await res.json();
} catch {
return showError("Failed to load project JSON.");
}
renderBasicInfo(project);
renderMedia(project);
renderTools(project.tools);
if (!project.description) return showError("README file path missing.");
const descPath = project.description.trim();
if (!descPath.endsWith(".md") && !descPath.endsWith(".readme")) {
return showError("Description must be a .md or .readme file.");
}
await loadReadme(descPath);
}
function renderBasicInfo(project) {
document.getElementById("title").textContent =
project.name || "Untitled Project";
document.getElementById("category").textContent =
project.category || "N/A";
}
function renderMedia(project) {
const media = document.getElementById("media");
media.innerHTML = "";
if (!project.filePath) return;
if (project.displayType === "video") {
const video = document.createElement("video");
video.src = project.filePath;
video.controls = true;
media.appendChild(video);
} else {
const img = document.createElement("img");
img.src = project.filePath;
img.alt = project.name || "Project preview";
media.appendChild(img);
}
}
function renderTools(toolsText = "") {
const container = document.getElementById("tools");
container.innerHTML = "";
toolsText
.split(/[, ]+/)
.filter(Boolean)
.forEach(tool => {
const key = tool.toLowerCase();
const icon = toolIcons[key] || "mdi:tools";
const el = document.createElement("div");
el.className = "tool";
el.innerHTML = `
<span class="iconify" data-icon="${icon}"></span>
<span>${tool}</span>
`;
container.appendChild(el);
});
}
async function loadReadme(path) {
const description = document.getElementById("description");
try {
const response = await fetch(path);
if (!response.ok) throw new Error();
const markdown = await response.text();
description.classList.remove("loading");
description.innerHTML = marked.parse(markdown);
} catch {
showError("Failed to load README file.");
}
}
function showError(message) {
const description = document.getElementById("description");
description.classList.remove("loading");
description.innerHTML = `<div class="error">${message}</div>`;
}
</script>
</body>
</html>