// Skills grid — same award-show language as Services (hairline grid,
// giant faded monogram, cursor spotlight, numbered index) plus one new
// touch: the tag pills cascade in with a staggered pop the first time
// each card scrolls into view, via Motion's inView()+stagger() — the
// first use of either in this project (everywhere else uses animate()
// directly on a single value/element).
//
// Tag is a shared design-system component (also used by About/Work) that
// sets border/background/color as INLINE styles, which stylesheet
// :hover rules can't override without !important. Each tag is wrapped in
// its own span here instead, and the WRAPPER gets the hover lift/glow —
// Tag itself is untouched.
function SkillTile({ g, i }) {
const tileRef = React.useRef(null);
const tagsRef = React.useRef(null);
const { Tag } = window.ArnelBiscarraDesignSystem_2766cb;
const onMove = (e) => {
const el = e.currentTarget;
const r = el.getBoundingClientRect();
el.style.setProperty('--mx', ((e.clientX - r.left) / r.width) * 100 + '%');
el.style.setProperty('--my', ((e.clientY - r.top) / r.height) * 100 + '%');
};
React.useEffect(() => {
const M = window.Motion;
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const wraps = tagsRef.current ? tagsRef.current.querySelectorAll('.skill-tag-wrap') : [];
if (!M || !M.inView || reduced || !wraps.length) return;
const stop = M.inView(tileRef.current, () => {
M.animate(wraps, { opacity: [0, 1], y: [8, 0], scale: [0.92, 1] }, {
delay: M.stagger(0.035),
duration: 0.4,
easing: 'ease-out',
});
}, { amount: 0.3 });
return () => { if (typeof stop === 'function') stop(); };
}, []);
// Cursor-tracking conic-gradient border glow (js/glow-border.js) —
// registers this tile with the shared controller, which drives every
// registered tile off ONE global pointermove/scroll listener rather
// than each tile owning its own.
React.useEffect(() => {
if (!window.GlowBorder || !tileRef.current) return;
return window.GlowBorder.register(tileRef.current, {
proximity: 110,
inactiveZone: 0.01,
movementDuration: 0.7,
});
}, []);
const col = i % 3;
const row = Math.floor(i / 3);
const delay = (col + row) * 55;
return (