Files
douwco_website/public/assets/js/project-card.js
T
Douwe 2eef84eb03 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
2026-09-24 14:22:54 +00:00

102 lines
3.3 KiB
JavaScript

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, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;');
}
}
customElements.define('project-card', ProjectCard);