mirror of
https://github.com/Dvorinka/Portfolio.git
synced 2026-10-28 07:41:22 +00:00
Add spotlight cards: glow border, image zoom, reveal stagger
Cards now carry a mouse-tracked radial border glow and inner spotlight, a blurred arrow badge over the image, saturate/zoom on hover, deeper shadow, and IntersectionObserver-driven staggered reveal. Hidden state gated behind .js so no-JS still renders. Authored By: TDvorak <[email protected]> Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
b672dc1a5b
commit
a73979f0d1
2 files changed
+68
-4
No files matched your search
@@ -1,5 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
document.documentElement.classList.add("js");
|
||||||
|
|
||||||
// Theme: persisted in sessionStorage, dark default.
|
// Theme: persisted in sessionStorage, dark default.
|
||||||
const root = document.documentElement;
|
const root = document.documentElement;
|
||||||
root.dataset.theme = sessionStorage.getItem("theme") || "dark";
|
root.dataset.theme = sessionStorage.getItem("theme") || "dark";
|
||||||
@@ -12,6 +14,28 @@ document.querySelector("[data-theme-btn]")?.addEventListener("click", () => {
|
|||||||
// Footer year
|
// Footer year
|
||||||
document.getElementById("year").textContent = new Date().getFullYear();
|
document.getElementById("year").textContent = new Date().getFullYear();
|
||||||
|
|
||||||
|
// Card spotlight: track cursor into --mx/--my
|
||||||
|
for (const card of document.querySelectorAll(".card")) {
|
||||||
|
card.addEventListener("mousemove", (e) => {
|
||||||
|
const r = card.getBoundingClientRect();
|
||||||
|
card.style.setProperty("--mx", `${e.clientX - r.left}px`);
|
||||||
|
card.style.setProperty("--my", `${e.clientY - r.top}px`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Staggered reveal on scroll
|
||||||
|
const reveal = new IntersectionObserver((entries) => {
|
||||||
|
for (const en of entries) {
|
||||||
|
if (en.isIntersecting) {
|
||||||
|
const cards = [...en.target.parentElement.children];
|
||||||
|
en.target.style.transitionDelay = `${cards.indexOf(en.target) * 60}ms`;
|
||||||
|
en.target.classList.add("in");
|
||||||
|
reveal.unobserve(en.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { threshold: 0.15 });
|
||||||
|
document.querySelectorAll(".card").forEach((c) => reveal.observe(c));
|
||||||
|
|
||||||
// Contact form -> sendmail.tdvorak.dev
|
// Contact form -> sendmail.tdvorak.dev
|
||||||
const form = document.getElementById("contact-form");
|
const form = document.getElementById("contact-form");
|
||||||
const status = document.getElementById("form-status");
|
const status = document.getElementById("form-status");
|
||||||
|
|||||||
@@ -152,14 +152,54 @@ a { color: inherit; text-decoration: none; }
|
|||||||
margin-top: 40px;
|
margin-top: 40px;
|
||||||
}
|
}
|
||||||
.card {
|
.card {
|
||||||
|
--glow: rgba(255, 255, 255, 0.35);
|
||||||
|
position: relative;
|
||||||
display: flex; flex-direction: column;
|
display: flex; flex-direction: column;
|
||||||
border: 1px solid var(--border); border-radius: var(--radius);
|
border: 1px solid var(--border); border-radius: var(--radius);
|
||||||
background: var(--bg-raised); overflow: hidden;
|
background: var(--bg-raised); overflow: hidden;
|
||||||
transition: border-color 0.15s, transform 0.15s;
|
transition: border-color 0.2s, transform 0.2s, box-shadow 0.2s;
|
||||||
}
|
}
|
||||||
.card:hover { border-color: var(--border-strong); transform: translateY(-2px); }
|
.js .card { opacity: 0; transform: translateY(14px); }
|
||||||
.card figure { aspect-ratio: 16/9; overflow: hidden; border-bottom: 1px solid var(--border); }
|
[data-theme="light"] .card { --glow: rgba(0, 0, 0, 0.16); }
|
||||||
.card figure img { width: 100%; height: 100%; object-fit: cover; object-position: top; }
|
.card.in { opacity: 1; transform: none; transition: opacity 0.5s ease, transform 0.5s ease, border-color 0.2s, box-shadow 0.2s; }
|
||||||
|
.card:hover { border-color: var(--border-strong); transform: translateY(-3px); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35); }
|
||||||
|
[data-theme="light"] .card:hover { box-shadow: 0 12px 32px rgba(0, 0, 0, 0.1); }
|
||||||
|
/* mouse-tracked border glow */
|
||||||
|
.card::before {
|
||||||
|
content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
|
||||||
|
border-radius: var(--radius); padding: 1px;
|
||||||
|
background: radial-gradient(240px circle at var(--mx, 50%) var(--my, 50%), var(--glow), transparent 70%);
|
||||||
|
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
||||||
|
-webkit-mask-composite: xor; mask-composite: exclude;
|
||||||
|
opacity: 0; transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
/* soft inner spotlight */
|
||||||
|
.card::after {
|
||||||
|
content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
|
||||||
|
background: radial-gradient(420px circle at var(--mx, 50%) var(--my, 50%), rgba(255, 255, 255, 0.05), transparent 70%);
|
||||||
|
opacity: 0; transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
[data-theme="light"] .card::after { background: radial-gradient(420px circle at var(--mx, 50%) var(--my, 50%), rgba(0, 0, 0, 0.04), transparent 70%); }
|
||||||
|
.card:hover::before, .card:hover::after { opacity: 1; }
|
||||||
|
.card > * { position: relative; z-index: 1; }
|
||||||
|
.card figure { position: relative; aspect-ratio: 16/9; overflow: hidden; border-bottom: 1px solid var(--border); }
|
||||||
|
.card figure img {
|
||||||
|
width: 100%; height: 100%; object-fit: cover; object-position: top;
|
||||||
|
filter: saturate(0.9); transition: transform 0.5s ease, filter 0.5s ease;
|
||||||
|
}
|
||||||
|
.card:hover figure img { transform: scale(1.03); filter: saturate(1.05); }
|
||||||
|
/* ↗ badge over the image */
|
||||||
|
.card figure::after {
|
||||||
|
content: "↗"; position: absolute; top: 10px; right: 10px;
|
||||||
|
display: grid; place-items: center; width: 26px; height: 26px;
|
||||||
|
font-family: var(--font-mono); font-size: 13px; color: var(--fg);
|
||||||
|
background: color-mix(in srgb, var(--bg) 75%, transparent);
|
||||||
|
border: 1px solid var(--border-strong); border-radius: 999px;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
opacity: 0; transform: translate(-6px, 6px);
|
||||||
|
transition: opacity 0.2s, transform 0.2s;
|
||||||
|
}
|
||||||
|
.card:hover figure::after { opacity: 1; transform: none; }
|
||||||
.card-body { padding: 20px; }
|
.card-body { padding: 20px; }
|
||||||
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
||||||
.card-head h3 { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; }
|
.card-head h3 { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; }
|
||||||
|
|||||||
Reference in new issue
Block a user