Compare commits
9 Commits
6387e2e5a5
...
06474f69b4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06474f69b4 | ||
|
|
b68869b9d3 | ||
|
|
ef2fef4d37 | ||
|
|
bb4e7227f6 | ||
|
|
819e55a652 | ||
|
|
927051b3b4 | ||
|
|
9fb6db09a4 | ||
|
|
a51c1ed1f3 | ||
|
|
87f6114807 |
@@ -7,17 +7,23 @@
|
||||
"contact": "Contact"
|
||||
},
|
||||
"Hero": {
|
||||
"greeting": "Hi, I'm",
|
||||
"name": "Yolando Manullang.",
|
||||
"greeting": "Ohh Hiii !!",
|
||||
"iAm": "I'm ",
|
||||
"name": "Yolando Manullang",
|
||||
"badge": "Available for opportunities",
|
||||
"role": "Senior Backend Developer · 3+ Years in Enterprise Banking",
|
||||
"titlePart1": "Building",
|
||||
"titleHighlight": "Secure, Scalable",
|
||||
"titlePart2": "Enterprise-Grade Systems",
|
||||
"yearsExp": "3+ Years",
|
||||
"subtitle": "in Banking Technology. Backend Developer specializing in Java Spring Boot, Microservices Architecture, and Enterprise Security.",
|
||||
"taglineRest": "Backend Developer · 3+ years building secure, scalable systems in Banking Technology — Java Spring Boot, Microservices Architecture, and Enterprise Security.",
|
||||
"taglineRest": "Senior Backend Developer · 3+ years building secure, scalable systems in Banking Technology — Java Spring Boot, Microservices Architecture, and Enterprise Security.",
|
||||
"description": "A Senior Backend Developer with 3+ years of hands-on experience, well-versed in enterprise banking technology. I enjoy building systems that are reliable, secure, and built to scale — using the kind of stack that banks actually trust.",
|
||||
"ctaContact": "Get in Touch",
|
||||
"ctaProjects": "View Projects",
|
||||
"ctaDownloadCV": "Download CV",
|
||||
"ctaMore": "More",
|
||||
"findMeOn": "Find Me On",
|
||||
"scroll": "Scroll"
|
||||
},
|
||||
"Experience": {
|
||||
|
||||
@@ -7,17 +7,23 @@
|
||||
"contact": "Kontak"
|
||||
},
|
||||
"Hero": {
|
||||
"greeting": "Hai, saya",
|
||||
"name": "Yolando Manullang.",
|
||||
"greeting": "Ohh Hiii !!",
|
||||
"iAm": "Saya ",
|
||||
"name": "Yolando Manullang",
|
||||
"badge": "Tersedia untuk peluang baru",
|
||||
"role": "Senior Backend Developer · 3+ Tahun di Perbankan Enterprise",
|
||||
"titlePart1": "Membangun Sistem",
|
||||
"titleHighlight": "Aman & Skalabel",
|
||||
"titlePart2": "Skala Enterprise",
|
||||
"yearsExp": "3+ Tahun",
|
||||
"subtitle": "di Teknologi Perbankan. Backend Developer dengan spesialisasi Java Spring Boot, Arsitektur Microservices, dan Keamanan Enterprise.",
|
||||
"taglineRest": "Backend Developer · 3+ tahun membangun sistem aman & skalabel di industri teknologi perbankan — Java Spring Boot, Arsitektur Microservices, dan Keamanan Enterprise.",
|
||||
"taglineRest": "Senior Backend Developer · 3+ tahun membangun sistem aman & skalabel di industri teknologi perbankan — Java Spring Boot, Arsitektur Microservices, dan Keamanan Enterprise.",
|
||||
"description": "Senior Backend Developer dengan 3+ tahun pengalaman kerja nyata, terbiasa dengan teknologi enterprise perbankan. Saya senang membangun sistem yang handal, aman, dan scalable — menggunakan stack yang memang dipercaya industri keuangan.",
|
||||
"ctaContact": "Hubungi Saya",
|
||||
"ctaProjects": "Lihat Proyek",
|
||||
"ctaDownloadCV": "Unduh CV",
|
||||
"ctaMore": "Selengkapnya",
|
||||
"findMeOn": "Temukan Saya Di",
|
||||
"scroll": "Scroll"
|
||||
},
|
||||
"Experience": {
|
||||
|
||||
BIN
public/brand/foto-1.jpg
Normal file
BIN
public/brand/foto-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 164 KiB |
BIN
public/brand/foto-2.jpeg
Normal file
BIN
public/brand/foto-2.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
BIN
public/brand/foto-3.jpeg
Normal file
BIN
public/brand/foto-3.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 344 KiB |
@@ -60,7 +60,7 @@ export async function EducationSection() {
|
||||
index % 2 === 0 ? "md:text-right" : "md:text-left"
|
||||
}`}
|
||||
>
|
||||
<span className="inline-block font-mono text-xs text-sky-500 font-semibold tracking-wider uppercase mb-2">
|
||||
<span className="inline-block font-mono text-xs text-accent font-semibold tracking-wider uppercase mb-2">
|
||||
{item.period}
|
||||
</span>
|
||||
<h3 className="text-xl font-bold mb-1">
|
||||
@@ -71,7 +71,7 @@ export async function EducationSection() {
|
||||
index % 2 === 0 ? "md:justify-end" : "md:justify-start"
|
||||
}`}
|
||||
>
|
||||
<School size={14} className="text-sky-500/70" />
|
||||
<School size={14} className="text-accent/70" />
|
||||
<span>{item.institution}</span>
|
||||
</div>
|
||||
|
||||
@@ -88,7 +88,7 @@ export async function EducationSection() {
|
||||
</span>
|
||||
)}
|
||||
{item.gpa && (
|
||||
<span className="inline-flex items-center gap-1.5 rounded-full bg-sky-500/10 px-3 py-1 text-xs text-sky-600 dark:text-sky-400">
|
||||
<span className="inline-flex items-center gap-1.5 rounded-full bg-accent/10 px-3 py-1 text-xs text-accent">
|
||||
{t("gpaLabel")}: {item.gpa}
|
||||
</span>
|
||||
)}
|
||||
@@ -102,8 +102,8 @@ export async function EducationSection() {
|
||||
)}
|
||||
|
||||
{item.finalProjectUrl && (
|
||||
<div className="inline-flex max-w-md flex-col gap-3 rounded-2xl border border-sky-500/15 bg-sky-500/5 p-4">
|
||||
<span className="text-[10px] font-mono font-bold uppercase tracking-[0.2em] text-sky-600 dark:text-sky-400">
|
||||
<div className="inline-flex max-w-md flex-col gap-3 rounded-2xl border border-accent/15 bg-accent/5 p-4">
|
||||
<span className="text-[10px] font-mono font-bold uppercase tracking-[0.2em] text-accent">
|
||||
{t("finalProjectLabel")}
|
||||
</span>
|
||||
{item.finalProjectTitle && (
|
||||
@@ -115,7 +115,7 @@ export async function EducationSection() {
|
||||
href={item.finalProjectUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 text-sm font-medium text-sky-600 hover:text-sky-700 dark:text-sky-400 dark:hover:text-sky-300 transition-colors"
|
||||
className="inline-flex items-center gap-2 text-sm font-medium text-accent hover:text-accent/80 transition-colors"
|
||||
>
|
||||
{t("viewFinalProject")}
|
||||
<ExternalLink size={14} />
|
||||
@@ -124,7 +124,7 @@ export async function EducationSection() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="absolute left-0 md:left-1/2 md:-translate-x-1/2 top-0 w-12 h-12 rounded-xl bg-card border-2 border-sky-500/30 flex items-center justify-center text-sky-500 shadow-lg shadow-sky-500/10 z-10">
|
||||
<div className="absolute left-0 md:left-1/2 md:-translate-x-1/2 top-0 w-12 h-12 rounded-xl bg-card border-2 border-accent/30 flex items-center justify-center text-accent shadow-lg shadow-accent/10 z-10">
|
||||
{item.icon}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,15 +2,56 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion, PanInfo } from "framer-motion";
|
||||
import { ArrowDown, FileText, Send, Hand } from "lucide-react";
|
||||
import { ArrowDown, FileText, Download, Plus } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const PROFILE_IMAGES = [
|
||||
"https://images.unsplash.com/photo-1556157382-97eda2d62296?w=600&auto=format&fit=crop&q=80",
|
||||
"https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=600&auto=format&fit=crop&q=80",
|
||||
"https://images.unsplash.com/photo-1605379399642-870262d3d051?w=600&auto=format&fit=crop&q=80",
|
||||
"/brand/foto-1.jpg",
|
||||
"/brand/foto-2.jpeg",
|
||||
"/brand/foto-3.jpeg",
|
||||
];
|
||||
|
||||
const SOCIAL_LINKS = [
|
||||
{
|
||||
name: "LinkedIn",
|
||||
href: "https://www.linkedin.com/in/yolando-asri-e-g-manullang/",
|
||||
icon: (
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Instagram",
|
||||
href: "https://instagram.com/yolando_20",
|
||||
icon: (
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "WhatsApp",
|
||||
href: "https://wa.me/6282267852521",
|
||||
icon: (
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Email",
|
||||
href: "mailto:yolandomanullang@gmail.com",
|
||||
icon: (
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect width="20" height="16" x="2" y="4" rx="2" />
|
||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
export function HeroSection() {
|
||||
const t = useTranslations("Hero");
|
||||
const tTech = useTranslations("TechStack");
|
||||
@@ -44,49 +85,86 @@ export function HeroSection() {
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] rounded-full bg-indigo-500/5 blur-[100px] pointer-events-none" />
|
||||
|
||||
<div className="relative z-10 w-full max-w-6xl mx-auto px-6">
|
||||
<div className="grid lg:grid-cols-2 gap-12 lg:gap-8 items-center">
|
||||
<div className="grid lg:grid-cols-[1.1fr_0.9fr] gap-12 lg:gap-8 items-center">
|
||||
|
||||
{/* LEFT: Text Content */}
|
||||
<div className="text-center lg:text-left flex flex-col items-center lg:items-start order-2 lg:order-1">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 0.3 }}
|
||||
className="max-w-xl mb-10"
|
||||
{/* Greeting - ekspresif & casual */}
|
||||
<motion.p
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className="text-lg sm:text-xl font-bold text-accent mb-3 tracking-wide"
|
||||
>
|
||||
<p className="text-sm sm:text-base text-muted-foreground font-medium mb-1 tracking-wide">
|
||||
{t("greeting")}
|
||||
</p>
|
||||
<h1 className="text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight mb-5">
|
||||
{t("greeting")} 👋
|
||||
</motion.p>
|
||||
|
||||
{/* Name Heading */}
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.7, delay: 0.3 }}
|
||||
className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight mb-2"
|
||||
>
|
||||
{t("iAm")}
|
||||
<span className="gradient-text">{t("name")}</span>
|
||||
</h1>
|
||||
<p className="text-base sm:text-lg text-muted-foreground font-normal leading-relaxed">
|
||||
{t("taglineRest")}
|
||||
</p>
|
||||
</motion.h1>
|
||||
|
||||
{/* Role / Subtitle */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.45 }}
|
||||
className="flex flex-wrap items-center gap-2 mb-6"
|
||||
>
|
||||
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-bold border border-accent/40 text-accent bg-accent/10 tracking-wide">
|
||||
{t("role")}
|
||||
</span>
|
||||
</motion.div>
|
||||
|
||||
{/* Horizontal Divider */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
initial={{ opacity: 0, scaleX: 0 }}
|
||||
animate={{ opacity: 1, scaleX: 1 }}
|
||||
transition={{ duration: 0.5, delay: 0.55 }}
|
||||
className="w-16 h-0.5 bg-accent/60 mb-6 origin-left"
|
||||
/>
|
||||
|
||||
{/* Description */}
|
||||
<motion.p
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.7, delay: 0.6 }}
|
||||
className="text-sm text-muted-foreground font-normal leading-relaxed max-w-md mb-10"
|
||||
>
|
||||
{t("description")}
|
||||
</motion.p>
|
||||
|
||||
{/* CTA Buttons */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 0.7 }}
|
||||
transition={{ duration: 0.7, delay: 0.75 }}
|
||||
className="flex flex-col sm:flex-row items-center gap-4 w-full sm:w-auto"
|
||||
>
|
||||
<a
|
||||
href="#contact"
|
||||
className="w-full sm:w-auto group inline-flex items-center justify-center gap-2 px-7 py-3.5 rounded-xl bg-gradient-to-r from-accent to-purple-500 text-white font-semibold text-sm shadow-lg shadow-accent/25 hover:shadow-accent/40 hover:scale-105 transition-all duration-300"
|
||||
href="/cv.pdf"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-full sm:w-auto group inline-flex items-center justify-center gap-2 px-7 py-3 rounded-full bg-gradient-to-r from-accent to-purple-500 text-white font-semibold text-sm shadow-lg shadow-accent/25 hover:shadow-accent/40 hover:scale-105 transition-all duration-300"
|
||||
>
|
||||
<Send size={16} />
|
||||
{t("ctaContact")}
|
||||
<Download size={16} />
|
||||
{t("ctaDownloadCV")}
|
||||
</a>
|
||||
<a
|
||||
href="#projects"
|
||||
className="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-7 py-3.5 rounded-xl font-semibold text-sm border border-border hover:bg-muted/50 transition-all duration-300 hover:scale-105"
|
||||
className="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-10 py-3 rounded-full font-semibold text-sm border border-border hover:bg-muted/50 transition-all duration-300 hover:scale-105"
|
||||
>
|
||||
<FileText size={16} />
|
||||
{t("ctaProjects")}
|
||||
{t("ctaMore")}
|
||||
</a>
|
||||
</motion.div>
|
||||
|
||||
{/* Tech Stack */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
@@ -120,11 +198,33 @@ export function HeroSection() {
|
||||
|
||||
{/* RIGHT: Swipeable Card Deck */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
initial={{ opacity: 0, x: 40 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.4 }}
|
||||
className="order-1 lg:order-2 flex justify-center lg:justify-end relative w-full perspective-1000 h-[380px] sm:h-[450px]"
|
||||
>
|
||||
{/* Decorative elements behind card deck */}
|
||||
<div className="absolute -bottom-10 -left-10 w-64 h-64 rounded-full bg-purple-600/30 blur-[80px] animate-pulse-glow pointer-events-none" />
|
||||
<div className="absolute -top-6 -right-6 w-40 h-40 rounded-full bg-indigo-500/20 blur-[60px] animate-pulse-glow pointer-events-none" style={{ animationDelay: "1.5s" }} />
|
||||
|
||||
{/* Plus icon decoration */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.5, delay: 0.9 }}
|
||||
className="absolute -top-2 right-4 sm:right-0 z-20 text-accent pointer-events-none"
|
||||
>
|
||||
<Plus size={24} strokeWidth={2.5} />
|
||||
</motion.div>
|
||||
|
||||
{/* Circle decoration */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0 }}
|
||||
animate={{ opacity: 0.5, scale: 1 }}
|
||||
transition={{ duration: 0.5, delay: 1.1 }}
|
||||
className="absolute top-16 -right-2 sm:right-[-12px] w-8 h-8 rounded-full border-2 border-accent/50 pointer-events-none z-20"
|
||||
/>
|
||||
|
||||
<div className="relative w-full max-w-[280px] sm:max-w-[320px] h-full mx-auto lg:mx-0 lg:mr-8 xl:mr-16">
|
||||
{cards.map((imgUrl, index) => {
|
||||
const isTop = index === 0;
|
||||
@@ -178,8 +278,38 @@ export function HeroSection() {
|
||||
</motion.div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Social Media Bar */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 1.2 }}
|
||||
className="mt-16 flex flex-col sm:flex-row items-center justify-end gap-4 sm:gap-6"
|
||||
>
|
||||
<span className="text-sm font-semibold text-muted-foreground tracking-wide">
|
||||
{t("findMeOn")}
|
||||
</span>
|
||||
<div className="flex items-center gap-3">
|
||||
{SOCIAL_LINKS.map((social, i) => (
|
||||
<motion.a
|
||||
key={social.name}
|
||||
href={social.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, delay: 1.3 + i * 0.1 }}
|
||||
className="w-10 h-10 rounded-full border border-border/60 flex items-center justify-center text-muted-foreground hover:border-accent hover:text-accent hover:bg-accent/10 hover:scale-110 transition-all duration-300"
|
||||
aria-label={social.name}
|
||||
>
|
||||
{social.icon}
|
||||
</motion.a>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Scroll Indicator */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
|
||||
@@ -104,7 +104,7 @@ export function SkillForm({ initialData, skillId }: { initialData?: any; skillId
|
||||
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-semibold">
|
||||
Devicon Slug <span className="text-muted-foreground font-normal">(opsional — untuk ikon di Hero)</span>
|
||||
Devicon Slug / Custom URL <span className="text-muted-foreground font-normal">(contoh: spring, atau /icons/logo.svg)</span>
|
||||
</label>
|
||||
<input
|
||||
name="iconName"
|
||||
@@ -117,7 +117,9 @@ export function SkillForm({ initialData, skillId }: { initialData?: any; skillId
|
||||
{iconPreview && (
|
||||
<div className="flex items-center gap-3 p-3 rounded-xl border border-border bg-muted/20">
|
||||
<img
|
||||
src={`https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/${iconPreview}/${iconPreview}-original.svg`}
|
||||
src={iconPreview.startsWith("http") || iconPreview.startsWith("/")
|
||||
? iconPreview
|
||||
: `https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/${iconPreview}/${iconPreview}-original.svg`}
|
||||
alt={iconPreview}
|
||||
className="w-10 h-10 object-contain"
|
||||
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
|
||||
|
||||
@@ -14,9 +14,13 @@ export function SkillIcon({ iconName, name }: { iconName: string | null; name: s
|
||||
);
|
||||
}
|
||||
|
||||
const iconSrc = iconName.startsWith("http") || iconName.startsWith("/")
|
||||
? iconName
|
||||
: `https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/${iconName}/${iconName}-original.svg`;
|
||||
|
||||
return (
|
||||
<img
|
||||
src={`https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/${iconName}/${iconName}-original.svg`}
|
||||
src={iconSrc}
|
||||
alt={name}
|
||||
className="w-9 h-9 object-contain"
|
||||
onError={() => setErrored(true)}
|
||||
|
||||
Reference in New Issue
Block a user