Website Map
svg { width:100%; height:auto; }
text, g[id$=”_Label”], g[id$=”_label”] { cursor: pointer; }
path { pointer-events: none; } /* make sure paths don’t block label events */
#train { pointer-events: none; } /* train shouldn’t capture pointer events */
Website
.cls-1, .cls-2, .cls-3, .cls-4 {
fill: none;
}
.cls-2 {
stroke: #000;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 6px;
}
.cls-5, .cls-6, .cls-3 {
fill-rule: evenodd;
}
.cls-5, .cls-7 {
fill: #7abce8;
}
.cls-6, .cls-8 {
fill: #fff;
}
.cls-9 {
isolation: isolate;
}
.cls-10 {
font-family: AvenirNext-Bold, ‘Avenir Next’;
font-size: 40px;
font-weight: 700;
}
.cls-4 {
stroke: #fff;
stroke-miterlimit: 100;
stroke-width: 2px;
}
Research
document.addEventListener(“DOMContentLoaded”, () => {
const svg = document.querySelector(“svg”);
if (!svg) {
console.error(“No found on the page.”);
return;
}
// — Map your label IDs → path/polyline IDs (from your SVG) —
const labelToPathMap = {
“Journalistic_Writing_Label”: “Journalistic_Writing”,
“Graphic_Design_Label”: “Graphic_Design”,
“Audio_Label”: “Audio”,
“Contact_Label”: “Contact”,
“About_Me_Label”: “About_Me”,
“Research_Label”: “Research-2”,
“Photography_Label”: “Photography”,
“Videography_Label”: “Videography”
};
// — Remove any leftover inline tram/train groups in the SVG (safety) —
[“tramGroup”, “train”].forEach(id => {
const leftover = svg.querySelector(`#${CSS.escape(id)}`);
if (leftover) leftover.remove();
});
// — Create the tram container (outer group is what we animate) —
const tramGroup = document.createElementNS(“http://www.w3.org/2000/svg”, “g”);
tramGroup.setAttribute(“id”, “tramGroup”);
// Don’t let the tram block hover/clicks on labels/paths
tramGroup.style.pointerEvents = “none”;
svg.appendChild(tramGroup);
// Inner group holds the tram artwork; we offset & scale it so the
// tram’s CENTER rides on the path (instead of its top-left).
const tramInner = document.createElementNS(“http://www.w3.org/2000/svg”, “g”);
tramInner.setAttribute(“id”, “tramInner”);
tramGroup.appendChild(tramInner);
// — Sanitized tram artwork: all styles inlined, no classes/styles/defs —
// Also add vector-effect=”non-scaling-stroke” wherever there’s a stroke
tramInner.innerHTML = `
`;
// — Scale & center tram so its middle sits on the path —
const tramW = 112.94, tramH = 40; // original tram artbox (from viewBox)
const targetLengthPx = 90; // ← tweak overall tram size here
const scale = targetLengthPx / tramW;
const liftY = -2; // ← nudge upward (+ down, – up) to sit on the line
tramInner.setAttribute(
“transform”,
`translate(${-tramW / 2}, ${-tramH / 2 + 3}) scale(${scale})`
);
// — Build animateMotion for a path/polyline —
function buildAnimateMotionFor(elem) {
// clear previous motion so hover restarts cleanly
const oldAM = tramGroup.querySelector(“animateMotion”);
if (oldAM) oldAM.remove();
const am = document.createElementNS(“http://www.w3.org/2000/svg”, “animateMotion”);
am.setAttribute(“dur”, “2s”);
am.setAttribute(“fill”, “freeze”);
am.setAttribute(“rotate”, “auto”);
am.setAttribute(“begin”, “indefinite”);
const tag = elem.tagName.toLowerCase();
if (tag === “path”) {
am.setAttribute(“path”, elem.getAttribute(“d”));
} else if (tag === “polyline” || tag === “polygon”) {
const pts = (elem.getAttribute(“points”) || “”).trim();
const coords = pts.split(/\s+|,/).filter(Boolean).map(Number);
if (coords.length >= 4) {
let d = `M ${coords[0]} ${coords[1]}`;
for (let i = 2; i {
label.style.cursor = “pointer”;
label.addEventListener(“mouseenter”, () => {
const pathId = labelToPathMap[label.id];
const elem = pathId ? svg.querySelector(`#${CSS.escape(pathId)}`) : null;
if (!elem) {
console.warn(“No matching path/polyline for label:”, label.id, “→”, pathId);
return;
}
const am = buildAnimateMotionFor(elem);
if (am) am.beginElement();
});
});
// — Hover directly on the tracks too (optional) —
Object.values(labelToPathMap).forEach(pid => {
const elem = svg.querySelector(`#${CSS.escape(pid)}`);
if (!elem) return;
elem.style.cursor = “pointer”;
elem.addEventListener(“mouseenter”, () => {
const am = buildAnimateMotionFor(elem);
if (am) am.beginElement();
});
});
});
document.addEventListener(“DOMContentLoaded”, () => {
const svg = document.querySelector(“svg”);
if (!svg) return;
// Map label IDs → path IDs
const labelToPathMap = {
“Journalistic_Writing_Label”: “Journalistic_Writing”,
“Graphic_Design_Label”: “Graphic_Design”,
“Audio_Label”: “Audio”,
“Contact_Label”: “Contact”,
“About_Me_Label”: “About_Me”,
“Research_Label”: “Research-2”,
“Photography_Label”: “Photography”,
“Videography_Label”: “Videography”
};
// Map label IDs → URLs
const labelToUrlMap = {
“Research_Label”: “https://liammays.com/projects/”,
“Videography_Label”: “https://liammays.com/videography/”,
“Photography_Label”: “https://liammays.com/photography/”,
“Journalistic_Writing_Label”: “https://liammays.com/writing-samples/”,
“Graphic_Design_Label”: “https://liammays.com/graphic-design/”,
“Audio_Label”: “https://liammays.com/audio/”,
“Contact_Label”: “https://liammays.com/contact/”,
“About_Me_Label”: “https://liammays.com/about-me-2/”
};
// Add hover + click listeners
const labels = svg.querySelectorAll(“[id$=’_Label’]”);
labels.forEach(label => {
label.style.cursor = “pointer”;
label.addEventListener(“mouseenter”, () => {
const pathId = labelToPathMap[label.id];
const elem = pathId ? svg.querySelector(`#${CSS.escape(pathId)}`) : null;
if (!elem) return;
const am = buildAnimateMotionFor(elem);
if (am) am.beginElement();
});
label.addEventListener(“click”, () => {
const url = labelToUrlMap[label.id];
if (url) window.location.href = url;
});
});
// helper for animating tram (your existing buildAnimateMotionFor function should stay here!)
});
