Files
robinnetwork_website_new_cl…/src/components/software/TechStack.tsx
2026-04-29 12:01:53 +03:30

227 lines
9.3 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { useTranslations } from "next-intl";
const techItems = [
// ================= ORBIT 1 =================
{
key: "react",
orbit: 1,
startAngle: 0,
icon: (
<svg viewBox="-11.5 -10.2 23 20.4" className="w-6 h-6 sm:w-8 sm:h-8 text-[#61DAFB]">
<circle r="2.05" fill="currentColor" />
<g stroke="currentColor" strokeWidth="1" fill="none">
<ellipse rx="11" ry="4.2" />
<ellipse rx="11" ry="4.2" transform="rotate(60)" />
<ellipse rx="11" ry="4.2" transform="rotate(120)" />
</g>
</svg>
),
},
{
key: "nextjs",
orbit: 1,
startAngle: 180,
icon: (
<svg viewBox="0 0 100 100" className="w-6 h-6 sm:w-8 sm:h-8 text-foreground" fill="currentColor">
<path d="M50 0C22.4 0 0 22.4 0 50s22.4 50 50 50 50-22.4 50-50S77.6 0 50 0zm0 90c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40zm-4.9-57.5h6v32.1l-20.8-32.1h-6.6v41.7h6v-32.2l21 32.5h5.9V32.5h-6v..." />
<polygon points="63.2,32.5 57.2,32.5 57.2,74.2 63.2,74.2" />
<polygon points="36.8,32.5 30.2,32.5 30.2,74.2 36.8,74.2" />
<polygon points="36.8,32.5 57.2,64.2 63.2,64.2 42.8,32.5" />
</svg>
),
},
// ================= ORBIT 2 =================
{
key: "postgresql",
orbit: 2,
startAngle: 90,
icon: (
<svg viewBox="0 0 100 100" className="w-6 h-6 sm:w-8 sm:h-8 text-[#336791]" fill="currentColor">
<path d="M82.8 68.6c-1.3-4.7-5-9.2-10-12.2 4.1-1.3 7.8-3.4 10.9-6.3-4.6 1.8-9.8 2.6-15 2.2-5-4.5-12.1-7.1-19.5-6.5-12.8 1.1-23.4 11-25.5 23.7C20.6 88 35.8 100 50 100c14.2 0 26.6-9.5 30.6-22.7 1.1-3.6 1.8-7.3 2.2-8.7z" />
<path d="M47.7 5c-15.6 0-28.5 12.1-29.6 27.6-1 15 9.4 28.2 24 31.4 16.4 3.5 32.3-8.8 32.3-25.4 0-16-13-29-28.9-29-4.8 0-9.4 1.2-13.5 3.3 4.1-1.4 8.6-2.2 13.2-2.2 13.6 0 24.8 10.9 25.1 24.5.3 15-11.8 27.3-26.8 27.3-13.8 0-25.2-10.6-26.1-24.3-.9-14.4 10.5-26.6 24.9-26.6 5.8 0 11.2 1.9 15.6 5.2 0 0-5.8-11.8-10.4-11.8z" />
</svg>
),
},
{
key: "mongodb",
orbit: 2,
startAngle: 270,
icon: (
<svg viewBox="0 0 100 100" className="w-6 h-6 sm:w-8 sm:h-8 text-[#47A248]" fill="currentColor">
<path d="M49.9 8.7c0 0-17.8 24.3-17.8 45.4 0 17 8.3 29.8 15.6 37.1 1.2 1.2 3.1 1.2 4.3 0 7.3-7.3 15.6-20.1 15.6-37.1 0-21.1-17.7-45.4-17.7-45.4zM49.9 96.5c-2.4 0-4.3-1.9-4.3-4.3s1.9-4.3 4.3-4.3 4.3 1.9 4.3 4.3-1.9 4.3-4.3 4.3z" />
</svg>
),
},
// ================= ORBIT 3 =================
{
key: "docker",
orbit: 3,
startAngle: 45,
icon: (
<svg viewBox="0 0 100 100" className="w-6 h-6 sm:w-8 sm:h-8 text-[#2496ED]" fill="currentColor">
<path d="M12.4 62.6c0 10.6 19 19.3 42.4 19.3 23.4 0 42.4-8.6 42.4-19.3 0-5.1-4.4-9.7-11.7-13.4H12.4v13.4zM24.7 41.5h11v11h-11v-11zm15.4 0h11v11h-11v-11zm15.4 0h11v11h-11v-11zm-15.4-15h11v11h-11v-11zm15.4 0h11v11h-11v-11z" />
</svg>
),
},
{
key: "django",
orbit: 3,
startAngle: 225,
icon: (
<svg viewBox="0 0 100 100" className="w-6 h-6 sm:w-8 sm:h-8">
<rect width="100" height="100" rx="20" fill="#092E20" />
<text x="50" y="65" fontFamily="sans-serif" fontSize="45" fontWeight="bold" fill="#ffffff" textAnchor="middle">
dj
</text>
</svg>
),
},
// ================= ORBIT 4 =================
{
key: "nestjs",
orbit: 4,
startAngle: 135,
icon: (
<svg viewBox="0 0 100 100" className="w-6 h-6 sm:w-8 sm:h-8 text-[#E0234E]" fill="currentColor">
<path d="M49.3 5.2L5.2 30.6v40.3L49.3 96.3l44.1-25.4V30.6L49.3 5.2zm0 9.3l36.6 20.8v41.6L49.3 97.7 12.7 76.9V35.3L49.3 14.5z" />
<path d="M35.6 29.3L50.5 37.8l27.1-15.6v27.2l-27.1 15.6-27.2-15.6v-20.1z" />
</svg>
),
},
{
key: "typescript",
orbit: 4,
startAngle: 315,
icon: (
<svg viewBox="0 0 24 24" className="w-6 h-6 sm:w-8 sm:h-8 text-[#3178C6]" fill="currentColor">
<path d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zM11.734 18.906c-1.39 0-2.437-.36-3.14-.984l.875-1.516c.547.453 1.344.828 2.234.828 1.078 0 1.547-.468 1.547-1.078 0-1.844-4.578-.89-4.578-4.218 0-1.688 1.36-2.907 3.656-2.907 1.25 0 2.28.313 2.922.75l-.75 1.516c-.469-.328-1.188-.64-2.031-.64-.938 0-1.39.422-1.39.953 0 1.844 4.578.86 4.578 4.266 0 1.765-1.36 2.953-3.922 2.953zM16.14 8.781h5.813v2.094h-3.64v8.031h-2.172v-8.03h-3.64v-2.094h3.64z" />
</svg>
),
},
];
export default function TechStack() {
const t = useTranslations("software.techStack");
const [selected, setSelected] = useState(techItems[0]);
const [dimensions, setDimensions] = useState({
size: 800,
center: 400,
baseRadius: 120,
gap: 70,
});
useEffect(() => {
const updateDimensions = () => {
const width = window.innerWidth;
if (width < 640) {
setDimensions({ size: 400, center: 200, baseRadius: 60, gap: 35 });
} else if (width < 1024) {
setDimensions({ size: 500, center: 250, baseRadius: 80, gap: 45 });
} else {
setDimensions({ size: 800, center: 400, baseRadius: 120, gap: 70 });
}
};
updateDimensions();
window.addEventListener("resize", updateDimensions);
return () => window.removeEventListener("resize", updateDimensions);
}, []);
const { size, center, baseRadius, gap } = dimensions;
return (
<section className="flex flex-col items-center px-4 py-12 overflow-hidden sm:px-6 sm:py-16">
<div className="w-full mb-8 text-start sm:mb-12 max-w-7xl">
<h2 className="mb-2 text-2xl font-bold sm:text-3xl">{t("title")}</h2>
<p className="text-sm sm:text-base text-muted">{t("subtitle")}</p>
</div>
<div className="flex flex-col items-center justify-center w-full gap-8 lg:flex-row lg:flex-nowrap lg:gap-16 max-w-7xl">
{/* Orbital Tech System */}
<div
className="relative flex-shrink-0"
style={{
width: `${size}px`,
height: `${size}px`,
}}
>
{/* Sun (Center) */}
<div className="absolute text-center text-sm top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-10 flex items-center justify-center w-12 h-12 sm:w-16 sm:h-16 lg:w-24 lg:h-24 sm:text-base lg:text-lg rounded-full bg-accent shadow-[0_0_40px_rgba(249,115,22,0.6)] sm:shadow-[0_0_60px_rgba(249,115,22,0.6)]">
{t("center")}
</div>
{/* Orbits (Rings 1 to 4) */}
{[1, 2, 3, 4].map((orbit) => {
const radius = baseRadius + orbit * gap;
return (
<div
key={`ring-${orbit}`}
className="absolute -translate-x-1/2 -translate-y-1/2 border rounded-full pointer-events-none top-1/2 left-1/2 border-border/40"
style={{
width: `${radius * 2}px`,
height: `${radius * 2}px`,
}}
/>
);
})}
{/* Orbiting Items */}
{techItems.map((tech, i) => {
const radius = baseRadius + tech.orbit * gap;
const speed = 25 + tech.orbit * 10;
const angleRad = (tech.startAngle * Math.PI) / 180;
const x = center + radius * Math.cos(angleRad);
const y = center + radius * Math.sin(angleRad);
return (
<div
key={`item-${i}`}
className="absolute"
style={{
left: `${x}px`,
top: `${y}px`,
animation: `orbit-${tech.orbit} ${speed}s linear infinite`,
transformOrigin: `${center - x}px ${center - y}px`,
}}
>
<button
onClick={() => setSelected(tech)}
className={`pointer-events-auto cursor-pointer
-translate-x-1/2 -translate-y-1/2
w-10 h-10 sm:w-12 sm:h-12 lg:w-14 lg:h-14 rounded-full flex items-center justify-center transition-all z-20 bg-background
${
selected.key === tech.key
? "border-2 border-accent shadow-[0_0_15px_rgba(249,115,22,0.5)] sm:shadow-[0_0_20px_rgba(249,115,22,0.5)] scale-110"
: "border border-border hover:border-accent/50 hover:scale-110"
}
`}
style={{
animation: `counter-rotate ${speed}s linear infinite`,
}}
>
<div className="flex items-center justify-center w-full h-full">{tech.icon}</div>
</button>
</div>
);
})}
</div>
{/* Information Card */}
<div className="w-full max-w-md p-6 border text-start sm:p-8 bg-card border-border rounded-xl">
<div className="flex items-center justify-center w-12 h-12 mb-4 border sm:w-16 sm:h-16 sm:mb-6 bg-accent/10 border-accent/20 rounded-xl">
{selected.icon}
</div>
<h3 className="mb-3 text-xl font-bold sm:mb-4 sm:text-2xl">{t(`items.${selected.key}.name`)}</h3>
<p className="text-sm leading-relaxed sm:text-base text-muted">{t(`items.${selected.key}.description`)}</p>
</div>
</div>
</section>
);
}