Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/assets/images/SunilKumar.JPG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
120 changes: 120 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@ a:hover {
margin-bottom: 36px;
}

.page-header {
display: grid;
gap: 0;
}

.page-header.page-header-left {
text-align: left;
}
Expand All @@ -243,6 +248,9 @@ a:hover {
color: var(--text-color);
font-size: clamp(2rem, 5vw, 3.5rem);
margin-bottom: 14px;
line-height: 1.04;
letter-spacing: -0.05em;
max-width: 14ch;
}

.page-header p,
Expand All @@ -258,13 +266,70 @@ a:hover {
margin-left: 0;
}

.page-header-showcase h1,
.page-header-library h1,
.page-header-dashboard h1,
.page-header-journey h1,
.page-header-achievements h1,
.page-header-about h1,
.page-header-contact h1 {
max-width: 15ch;
}

.page-header-actions {
display: flex;
flex-wrap: wrap;
gap: 14px;
margin-top: 22px;
}

.page-meta-strip {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14px;
margin: -6px 0 26px;
}

.page-meta-strip.compact {
grid-template-columns: repeat(4, minmax(0, 1fr));
}

.meta-strip-card {
padding: 16px 18px;
border-radius: 20px;
background: color-mix(in srgb, var(--card-bg) 82%, transparent);
border: 1px solid color-mix(in srgb, var(--input-border) 88%, transparent);
box-shadow: var(--shadow-light);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
display: grid;
gap: 6px;
}

.meta-strip-card span {
color: var(--text-secondary);
font-size: 0.82rem;
font-weight: 700;
}

.meta-strip-card strong {
color: var(--text-color);
font-size: clamp(1.45rem, 2.2vw, 2rem);
line-height: 1;
letter-spacing: -0.04em;
}

.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 18px;
}

.widget-card,
.showcase-panel .problem-card {
min-height: 100%;
}

.problem-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
Expand Down Expand Up @@ -412,6 +477,27 @@ a:hover {
align-items: stretch;
}

.filter-shell {
display: grid;
gap: 18px;
margin-bottom: 26px;
}

.filter-shell-copy {
display: grid;
gap: 8px;
}

.filter-shell-copy h3 {
color: var(--text-color);
font-size: 1.02rem;
}

.filter-shell-copy p {
color: var(--text-secondary);
line-height: 1.7;
}

.filter-bar > * {
flex: 1 1 180px;
min-width: 0;
Expand Down Expand Up @@ -487,6 +573,11 @@ a:hover {
gap: 22px;
}

.timeline-panel .dashboard-grid,
.explorer-panel .dashboard-grid {
margin-top: 0;
}

.problem-detail-card {
background: var(--card-bg);
border: 1px solid color-mix(in srgb, var(--input-border) 88%, transparent);
Expand Down Expand Up @@ -604,6 +695,13 @@ a:hover {
animation: pageFadeUp 0.55s ease both;
}

.showcase-panel,
.explorer-panel,
.timeline-panel,
.contact-cta-panel {
position: relative;
}

.section-panel h2 {
font-size: clamp(1.9rem, 4vw, 3rem);
color: var(--text-color);
Expand Down Expand Up @@ -721,6 +819,10 @@ a:hover {
flex-wrap: wrap;
}

.contact-cta-panel .cta-panel > div:first-child {
max-width: 560px;
}

.copy-button {
border: 1px solid color-mix(in srgb, var(--input-border) 88%, transparent);
background: color-mix(in srgb, var(--card-bg) 88%, transparent);
Expand Down Expand Up @@ -896,6 +998,10 @@ textarea:focus-visible {
}
.timeline-item span { color: var(--accent-color); font-weight: 800; }

.widget-card p + p {
margin-top: 6px;
}

.state-page,
.project-detail-page {
width: min(1180px, calc(100% - 32px));
Expand Down Expand Up @@ -926,6 +1032,20 @@ textarea:focus-visible {
.section-panel { padding: 42px 18px; }
}

@media (max-width: 720px) {
.page-meta-strip,
.page-meta-strip.compact {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}

@media (max-width: 480px) {
.page-meta-strip,
.page-meta-strip.compact {
grid-template-columns: 1fr;
}
}

@keyframes pageFadeUp {
from {
opacity: 0;
Expand Down
67 changes: 56 additions & 11 deletions src/components/HeroSection.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useEffect, useMemo, useState } from "react";
import { motion } from "framer-motion";
import { Link } from "react-router-dom";
import HeroPic from "/assets/images/pic.png";
import HeroPic from "/assets/images/SunilKumar.JPG";
import { profile } from "../data/profile";
import DeveloperMetrics from "./DeveloperMetrics";
import {
Expand All @@ -18,6 +18,17 @@ import Badge from "./ui/Badge";
import "../styles/HeroSection.css";

function sortProjects(a, b) {
const preferredOrder = ["rainbowcode", "chessplay", "sunilcraft", "coding-journal"];
const normalizedA = String(a.name || "").toLowerCase();
const normalizedB = String(b.name || "").toLowerCase();
const preferredIndexA = preferredOrder.findIndex((item) => normalizedA.includes(item));
const preferredIndexB = preferredOrder.findIndex((item) => normalizedB.includes(item));
if (preferredIndexA !== preferredIndexB) {
if (preferredIndexA === -1) return 1;
if (preferredIndexB === -1) return -1;
return preferredIndexA - preferredIndexB;
}

const featuredRank = Number(Boolean(b.featured)) - Number(Boolean(a.featured));
if (featuredRank !== 0) return featuredRank;

Expand Down Expand Up @@ -62,6 +73,10 @@ export default function HeroSection() {

const featuredProjects = useMemo(() => [...projects].sort(sortProjects).slice(0, 3), [projects]);
const verifiedProblems = useMemo(() => problems.filter((problem) => problem.verified).slice(0, 3), [problems]);
const latestProject = useMemo(
() => [...projects].sort((a, b) => new Date(b.updatedAt || 0) - new Date(a.updatedAt || 0))[0] || null,
[projects]
);
const summary = useMemo(() => {
const verified = problems.filter((problem) => problem.verified).length;
const platforms = new Set(problems.map((problem) => problem.platform).filter(Boolean)).size;
Expand Down Expand Up @@ -173,12 +188,11 @@ export default function HeroSection() {
</section>

<div className="page-shell home-page-shell">
<DeveloperMetrics />

<SectionPanel
eyebrow="Selected Work"
title="Featured Projects"
description="Projects I’m actively building or maintaining, synced from my GitHub repositories."
className="showcase-panel"
>
{loading ? (
<LoadingState title="Loading featured projects" message="Fetching the latest work preview from coding-journal." />
Expand Down Expand Up @@ -221,10 +235,13 @@ export default function HeroSection() {
)}
</SectionPanel>

<DeveloperMetrics title="Developer Activity" />

<SectionPanel
eyebrow="Problem Solving"
title="Problem Solving Preview"
title="Verified Problem Solving"
description="A verified record of coding problems I’ve solved, tested, and documented."
className="explorer-panel"
>
{loading ? (
<LoadingState title="Loading problem preview" message="Preparing a live problem snapshot from coding-journal." />
Expand Down Expand Up @@ -275,6 +292,7 @@ export default function HeroSection() {
eyebrow="Verified Solutions"
title="Codebase Preview"
description="My personal solution library with source code, tests, explanations, and complexity notes."
className="explorer-panel"
>
{loading ? (
<LoadingState title="Loading codebase preview" message="Fetching verified solution entries from coding-journal." />
Expand Down Expand Up @@ -308,18 +326,45 @@ export default function HeroSection() {
</SectionPanel>

<SectionPanel
eyebrow="Next Step"
title="Move from portfolio view to engineering dashboard"
description="Use SunilCraft to review flagship work, inspect verified problem solving, and track progress across the coding-journal pipeline."
eyebrow="Building Journey"
title="What is actively taking shape"
description="Projects, verified solutions, and coding-journal activity keep moving together, so the portfolio reflects real work instead of static snapshots."
className="timeline-panel"
>
<div className="dashboard-grid">
<article className="glass-card widget-card">
<h3>Latest project motion</h3>
<p>{latestProject ? `${latestProject.name} is the most recently updated synced project in the portfolio feed.` : "Project updates appear here as soon as coding-journal data loads."}</p>
</article>
<article className="glass-card widget-card">
<h3>Verified momentum</h3>
<p>{summary.verified} verified solutions are already tracked in the live coding journal.</p>
</article>
<article className="glass-card widget-card">
<h3>Where to go next</h3>
<p>Open Journey for the narrative timeline, or jump into Dashboard for a broader view of developer activity.</p>
</article>
</div>
<div className="hero-actions hero-actions-inline">
<Link to="/journey" className="hero-button secondary">View Journey</Link>
<Link to="/dashboard" className="hero-button">Open Dashboard</Link>
</div>
</SectionPanel>

<SectionPanel
eyebrow="Contact"
title="Let’s talk through the work"
description="If the mix of product polish, verified coding work, and real GitHub activity looks like a fit, the next step is a simple conversation."
className="contact-cta-panel"
>
<div className="cta-panel">
<div>
<h3>Everything stays connected</h3>
<p>Projects, problems, journey milestones, and achievements stay synced from coding-journal without adding clutter back into the main navigation.</p>
<h3>Built in public, explained clearly</h3>
<p>SunilCraft is meant to show how I think, build, document, and improve products over time, not just list tools or numbers.</p>
</div>
<div className="hero-actions">
<Link to="/dashboard" className="hero-button">View Dashboard</Link>
<Link to="/contact" className="hero-button secondary">Contact</Link>
<Link to="/contact" className="hero-button">Contact</Link>
<a href={profile.github} className="hero-button secondary" target="_blank" rel="noreferrer">GitHub</a>
</div>
</div>
</SectionPanel>
Expand Down
5 changes: 3 additions & 2 deletions src/components/Navbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ThemeContext } from "../context/theme";
import { GiHamburgerMenu } from "react-icons/gi";
import { FaSun, FaMoon } from "react-icons/fa";
import { useNavigate, useLocation } from "react-router-dom";
import LogoMark from "./branding/LogoMark";
import "../styles/Navbar.css";

export default function Navbar() {
Expand Down Expand Up @@ -64,8 +65,8 @@ export default function Navbar() {
<nav className="navbar">
<div className="container">
{/* Logo */}
<button className="logo" type="button" onClick={() => handleNavClick("Home")} aria-label="Go to home">
<div className="logo-icon">S</div>
<button className="logo" type="button" onClick={() => handleNavClick("Home")} aria-label="SunilCraft home">
<LogoMark />
<div className="logo-text">SunilCraft</div>
</button>

Expand Down
11 changes: 11 additions & 0 deletions src/components/ProjectsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ import EmptyState from "./ui/EmptyState";
import Badge from "./ui/Badge";

function sortProjects(a, b) {
const preferredOrder = ["rainbowcode", "chessplay", "sunilcraft", "coding-journal"];
const normalizedA = String(a.name || "").toLowerCase();
const normalizedB = String(b.name || "").toLowerCase();
const preferredIndexA = preferredOrder.findIndex((item) => normalizedA.includes(item));
const preferredIndexB = preferredOrder.findIndex((item) => normalizedB.includes(item));
if (preferredIndexA !== preferredIndexB) {
if (preferredIndexA === -1) return 1;
if (preferredIndexB === -1) return -1;
return preferredIndexA - preferredIndexB;
}

const featuredRank = Number(Boolean(b.featured)) - Number(Boolean(a.featured));
if (featuredRank !== 0) return featuredRank;

Expand Down
36 changes: 36 additions & 0 deletions src/components/branding/LogoMark.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from "react";
import { motion } from "framer-motion";

export default function LogoMark() {
return (
<motion.span
className="logo-mark"
initial={{ opacity: 0, scale: 0.92, y: 4 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
transition={{ duration: 0.45, ease: "easeOut" }}
aria-hidden="true"
>
<svg viewBox="0 0 64 64" role="presentation" focusable="false">
<defs>
<linearGradient id="skGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="currentColor" stopOpacity="0.98" />
<stop offset="100%" stopColor="currentColor" stopOpacity="0.62" />
</linearGradient>
</defs>
<rect x="5" y="5" width="54" height="54" rx="18" className="logo-mark-frame" />
<path
d="M20 23c3-5 8-7 14-7 6 0 10 2 13 5"
className="logo-mark-stroke"
/>
<path
d="M20 31c0-4 3-7 8-7h9c4 0 7 2 7 6 0 4-3 6-7 6h-9c-5 0-8 3-8 8"
className="logo-mark-stroke"
/>
<path
d="M44 18 28 34l17 12"
className="logo-mark-stroke accent"
/>
</svg>
</motion.span>
);
}
Loading
Loading