Restructure to classic static-site layout, add project-card component, fix bugs
- Move wwwroot/ to public/ with assets/ grouping (css, js, img, fonts, vid) - Inline section includes into index.html, drop runtime fetch loading - Add project-card web component to replace repeated project markup - Fix IntersectionObserver options placement bug in nav highlight - Fix @font-face syntax, invalid 5-digit hex colors, 75x border-radius typo - Remove dead css link (home.css), unused purple/orange color vars - Fix typos, replace inline styles with classes, serve favicon
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
setupNavigationHighlight();
|
||||
setupNavigationClicks();
|
||||
setupCopyright();
|
||||
});
|
||||
|
||||
function setupNavigationHighlight() {
|
||||
const sections = document.querySelectorAll('.scroll_section');
|
||||
const navOptions = document.querySelectorAll('.nav_option');
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const sectionId = entry.target.id;
|
||||
window.history.replaceState(null, null, `#${sectionId}`);
|
||||
navOptions.forEach(link => {
|
||||
link.classList.toggle('active', link.getAttribute('href') === `#${sectionId}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.5
|
||||
});
|
||||
|
||||
sections.forEach(section => observer.observe(section));
|
||||
}
|
||||
|
||||
function setupNavigationClicks() {
|
||||
document.querySelectorAll('.nav_option').forEach(link => {
|
||||
link.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const targetId = link.getAttribute('href');
|
||||
const targetSection = document.querySelector(targetId);
|
||||
if (targetSection) {
|
||||
targetSection.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setupCopyright() {
|
||||
document.getElementById('copyright').textContent = `© ${new Date().getFullYear()} - douwco.be`;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
class ProjectCard extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ['image', 'image-alt', 'title', 'description'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
this._rendered = false;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this._render();
|
||||
}
|
||||
|
||||
attributeChangedCallback() {
|
||||
if (this._rendered) {
|
||||
this._render();
|
||||
}
|
||||
}
|
||||
|
||||
_render() {
|
||||
this._rendered = true;
|
||||
const alt = this.getAttribute('image-alt') || '';
|
||||
const title = this.getAttribute('title') || '';
|
||||
const description = this.getAttribute('description') || '';
|
||||
|
||||
this.shadowRoot.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
.content-box {
|
||||
background-color: var(--background-accent-clr, #324f4f);
|
||||
margin: 50px 20px 20px;
|
||||
padding: 20px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
.content-box p {
|
||||
margin: 0;
|
||||
}
|
||||
.intro-text {
|
||||
font-weight: bolder;
|
||||
font-size: 28px;
|
||||
color: var(--green-clr, #6ede9a);
|
||||
}
|
||||
.project-thumb {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
object-fit: cover;
|
||||
margin-right: 15px;
|
||||
border-radius: 75px;
|
||||
float: left;
|
||||
}
|
||||
::slotted(p) { margin: 0; }
|
||||
</style>
|
||||
<div class="content-box">
|
||||
<img class="project-thumb" src="${this._escapeAttribute(this.getAttribute('image') || '')}" alt="${this._escapeAttribute(alt)}">
|
||||
<p><span class="intro-text"></span><slot></slot></p>
|
||||
</div>`;
|
||||
this.shadowRoot.querySelector('.intro-text').textContent = title;
|
||||
this._renderDescription(description);
|
||||
}
|
||||
|
||||
_renderDescription(description) {
|
||||
const slot = this.shadowRoot.querySelector('slot');
|
||||
slot.textContent = '';
|
||||
const linkPattern = /\[\[([^|\]]+)\|([^\]]+)\]\]/g;
|
||||
let lastIndex = 0;
|
||||
let match;
|
||||
while ((match = linkPattern.exec(description)) !== null) {
|
||||
if (match.index > lastIndex) {
|
||||
slot.appendChild(document.createTextNode(description.slice(lastIndex, match.index)));
|
||||
}
|
||||
const link = document.createElement('a');
|
||||
link.href = match[2];
|
||||
link.textContent = match[1];
|
||||
if (match[2].startsWith('http')) {
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener';
|
||||
}
|
||||
slot.appendChild(link);
|
||||
lastIndex = match.index + match[0].length;
|
||||
if (linkPattern.lastIndex === match.index) {
|
||||
linkPattern.lastIndex++;
|
||||
}
|
||||
}
|
||||
if (lastIndex < description.length) {
|
||||
slot.appendChild(document.createTextNode(description.slice(lastIndex)));
|
||||
}
|
||||
}
|
||||
|
||||
_escapeAttribute(value) {
|
||||
return value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<');
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('project-card', ProjectCard);
|
||||
Reference in New Issue
Block a user