Skip to content

Commit 1754faf

Browse files
committed
perf+seo: optimize portfolio rendering, remove Google Fonts blocking, add SEO metadata
1 parent 3c708a5 commit 1754faf

6 files changed

Lines changed: 110 additions & 24 deletions

File tree

index.html

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,58 @@
2020
</script>
2121
<meta charset="UTF-8" />
2222
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
23+
<meta name="robots" content="index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1" />
24+
<meta name="googlebot" content="index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1" />
25+
<meta name="theme-color" content="#061018" />
26+
<meta name="author" content="CsFan" />
2327
<meta
2428
name="description"
2529
content="CsFan的作品化简历网站,聚焦 Java 全栈、微服务、云原生与 AI 工程化。"
2630
/>
31+
<meta property="og:type" content="website" />
32+
<meta property="og:site_name" content="Flowfolio" />
33+
<meta property="og:title" content="CsFan | Flowfolio" />
34+
<meta property="og:description" content="CsFan的作品化简历网站,聚焦 Java 全栈、微服务、云原生与 AI 工程化。" />
35+
<meta property="og:url" content="https://alleyf.github.io/Flowfolio/" />
36+
<meta property="og:image" content="https://alleyf.github.io/Flowfolio/resume/poster.webp" />
37+
<meta property="og:locale" content="zh_CN" />
38+
<meta name="twitter:card" content="summary_large_image" />
39+
<meta name="twitter:title" content="CsFan | Flowfolio" />
40+
<meta name="twitter:description" content="CsFan的作品化简历网站,聚焦 Java 全栈、微服务、云原生与 AI 工程化。" />
41+
<meta name="twitter:image" content="https://alleyf.github.io/Flowfolio/resume/poster.webp" />
42+
<link rel="canonical" href="https://alleyf.github.io/Flowfolio/" />
43+
<link rel="alternate" hreflang="zh-CN" href="https://alleyf.github.io/Flowfolio/" />
44+
<link rel="sitemap" type="application/xml" href="/sitemap.xml" />
2745
<title>CsFan | Flowfolio</title>
2846
<link rel="icon" type="image/webp" href="/resume/favicon.webp" />
2947
<link rel="apple-touch-icon" href="/resume/favicon.webp" />
3048
<link rel="preload" as="image" href="/resume/poster.webp" />
3149
<script defer src="https://api.busuanzi.cc/static/3.6.9/busuanzi.min.js"></script>
32-
<link rel="preconnect" href="https://fonts.googleapis.com" />
33-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
34-
<link
35-
href="https://fonts.googleapis.com/css2?family=Azeret+Mono:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;700;900&display=swap"
36-
rel="stylesheet"
37-
/>
50+
<script type="application/ld+json">
51+
{
52+
"@context": "https://schema.org",
53+
"@graph": [
54+
{
55+
"@type": "WebSite",
56+
"name": "Flowfolio",
57+
"url": "https://alleyf.github.io/Flowfolio/",
58+
"description": "CsFan的作品化简历网站,聚焦 Java 全栈、微服务、云原生与 AI 工程化。",
59+
"inLanguage": "zh-CN"
60+
},
61+
{
62+
"@type": "Person",
63+
"name": "CsFan",
64+
"url": "https://alleyf.github.io/Flowfolio/",
65+
"email": "mailto:alleyf@qq.com",
66+
"jobTitle": "Java 全栈工程师",
67+
"sameAs": [
68+
"https://github.com/Alleyf",
69+
"https://alleyf.github.io/"
70+
]
71+
}
72+
]
73+
}
74+
</script>
3875
</head>
3976
<body>
4077
<div id="root"></div>

public/robots.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
User-agent: *
2+
Allow: /
3+
4+
Sitemap: https://alleyf.github.io/Flowfolio/sitemap.xml

public/sitemap.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3+
<url>
4+
<loc>https://alleyf.github.io/Flowfolio/</loc>
5+
<lastmod>2026-03-21</lastmod>
6+
<changefreq>weekly</changefreq>
7+
<priority>1.0</priority>
8+
</url>
9+
</urlset>

src/App.jsx

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,14 @@ useEcharts([RadarChart, RadarComponent, TooltipComponent, LegendComponent, Canva
1212

1313
const SLIDE_TRANSITION_MS = 820;
1414

15-
const artPhotoModules = import.meta.glob("../public/art/**/*.webp", {
16-
eager: true,
17-
import: "default",
18-
});
15+
const artPhotoModules = import.meta.glob("../public/art/**/*.webp");
1916

20-
const artPhotoCategoryMap = Object.entries(artPhotoModules).reduce((accumulator, [path, src]) => {
17+
const artPhotoCategoryMap = Object.keys(artPhotoModules).reduce((accumulator, path) => {
2118
const matched = path.match(/\/art\/([^/]+)\/[^/]+$/);
2219
if (!matched) return accumulator;
2320
const category = decodeURIComponent(matched[1]);
2421
if (!accumulator[category]) accumulator[category] = [];
25-
accumulator[category].push(src);
22+
accumulator[category].push(path.replace("../public", ""));
2623
return accumulator;
2724
}, {});
2825

@@ -398,6 +395,7 @@ export default function App() {
398395
const [form, setForm] = useState({ name: "", email: "", subject: contactConfig.defaultSubject, message: "" });
399396
const touchStartY = useRef(null);
400397
const touchStartSection = useRef(null);
398+
const artHoverRafRef = useRef(null);
401399
const toolboxRef = useRef(null);
402400
const unlockTimerRef = useRef(null);
403401
const transitionTimerRef = useRef(null);
@@ -565,6 +563,7 @@ export default function App() {
565563
useEffect(() => () => {
566564
window.clearTimeout(unlockTimerRef.current);
567565
window.clearTimeout(transitionTimerRef.current);
566+
if (artHoverRafRef.current) window.cancelAnimationFrame(artHoverRafRef.current);
568567
}, []);
569568

570569
useEffect(() => {
@@ -577,24 +576,35 @@ export default function App() {
577576
}, [artCategoryIndex, portfolioMode]);
578577

579578
useEffect(() => {
580-
if (portfolioMode !== "art" || !artPhotoCategories.length) return undefined;
579+
if (portfolioMode !== "art" || !artPhotoCategories.length || activeIndex !== portfolioSectionIndex) return undefined;
580+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return undefined;
581+
if ((navigator.hardwareConcurrency ?? 8) <= 4) return undefined;
581582

582583
const safeCategoryIndex = ((artCategoryIndex % artPhotoCategories.length) + artPhotoCategories.length) % artPhotoCategories.length;
583584
const currentPhotos = artPhotoCategories[safeCategoryIndex]?.photos ?? [];
584585
if (currentPhotos.length <= 1) return undefined;
585586

586587
const timer = window.setInterval(() => {
588+
if (document.hidden) return;
587589
setArtPhotoIndex((current) => (current + 1) % currentPhotos.length);
588-
}, 3500);
590+
}, 5500);
589591

590592
return () => window.clearInterval(timer);
591-
}, [artCategoryIndex, portfolioMode]);
593+
}, [activeIndex, artCategoryIndex, portfolioMode, portfolioSectionIndex]);
592594

593595
useEffect(() => {
594596
if (portfolioMode !== "art") return;
595597
const randomIndex = Math.floor(Math.random() * artCopyPool.length);
596598
setArtCopy(artCopyPool[randomIndex]);
597-
}, [portfolioMode, artCategoryIndex, artPhotoIndex]);
599+
}, [portfolioMode, artCategoryIndex]);
600+
601+
const handleArtTileHover = (index) => {
602+
if (index === artPhotoIndex || artHoverRafRef.current) return;
603+
artHoverRafRef.current = window.requestAnimationFrame(() => {
604+
setArtPhotoIndex(index);
605+
artHoverRafRef.current = null;
606+
});
607+
};
598608

599609
const preloadAround = (index) => {
600610
setLoadedIndexes((current) => {
@@ -1235,7 +1245,7 @@ export default function App() {
12351245
<Card className="glass-card portfolio-card">
12361246
<Grid container>
12371247
<Grid size={{ xs: 12, md: 7 }}>
1238-
<img src={work.image} alt={work.title} loading="lazy" className="portfolio-image portfolio-image-large" />
1248+
<img src={work.image} alt={work.title} loading="lazy" decoding="async" className="portfolio-image portfolio-image-large" />
12391249
</Grid>
12401250
<Grid size={{ xs: 12, md: 5 }}>
12411251
<CardContent className="portfolio-content">
@@ -1285,7 +1295,7 @@ export default function App() {
12851295
<Card className="glass-card portfolio-card art-matrix-shell">
12861296
<Grid container>
12871297
<Grid size={{ xs: 12, md: 7 }}>
1288-
{artLeadPhoto ? <img src={artLeadPhoto} alt={normalizeArtCategoryName(activeArtCategory.name)} loading="lazy" className="portfolio-image portfolio-image-large art-lead-image" /> : null}
1298+
{artLeadPhoto ? <img src={artLeadPhoto} alt={normalizeArtCategoryName(activeArtCategory.name)} loading="lazy" decoding="async" className="portfolio-image portfolio-image-large art-lead-image" /> : null}
12891299
</Grid>
12901300
<Grid size={{ xs: 12, md: 5 }}>
12911301
<CardContent className="portfolio-content">
@@ -1321,11 +1331,11 @@ export default function App() {
13211331
animate={{ opacity: 1, y: 0 }}
13221332
transition={{ duration: 0.35, delay: Math.min(index * 0.03, 0.28), ease: "easeOut" }}
13231333
whileHover={{ y: -8, scale: 1.02 }}
1324-
onMouseEnter={() => setArtPhotoIndex(index)}
1325-
onFocus={() => setArtPhotoIndex(index)}
1334+
onMouseEnter={() => handleArtTileHover(index)}
1335+
onFocus={() => handleArtTileHover(index)}
13261336
onClick={() => setArtPhotoIndex(index)}
13271337
>
1328-
<img src={photo} alt={`${normalizeArtCategoryName(activeArtCategory.name)}-${index + 1}`} loading="lazy" className="art-photo-image" />
1338+
<img src={photo} alt={`${normalizeArtCategoryName(activeArtCategory.name)}-${index + 1}`} loading="lazy" decoding="async" className="art-photo-image" />
13291339
<Box component="figcaption" className="art-photo-caption">FRAME {String(index + 1).padStart(2, "0")}</Box>
13301340
</motion.figure>
13311341
);

src/main.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const theme = createTheme({
1414
},
1515
shape: { borderRadius: 20 },
1616
typography: {
17-
fontFamily: '"Noto Sans SC", sans-serif',
17+
fontFamily: '"MiSans","HarmonyOS Sans SC","PingFang SC","Microsoft YaHei","Segoe UI",sans-serif',
1818
h1: { fontWeight: 900, letterSpacing: "-0.04em" },
1919
h2: { fontWeight: 800, letterSpacing: "-0.03em" },
2020
h3: { fontWeight: 700 },

src/styles.css

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
--gold: #ffb865;
55
--text-soft: #8fa8be;
66
--nav-height: 84px;
7+
--font-sans: "MiSans", "HarmonyOS Sans SC", "PingFang SC", "Microsoft YaHei", "Segoe UI", sans-serif;
8+
--font-mono: "JetBrains Mono", "Cascadia Mono", "Consolas", "Courier New", monospace;
79
}
810

911
:root[data-theme="dark"] {
@@ -105,7 +107,7 @@ button {
105107
.terminal-title,
106108
.terminal-prompt,
107109
.terminal-output {
108-
font-family: "Azeret Mono", monospace !important;
110+
font-family: var(--font-mono) !important;
109111
}
110112

111113
.brand {
@@ -1070,14 +1072,38 @@ button {
10701072
right: 10px;
10711073
bottom: 10px;
10721074
z-index: 2;
1073-
font-family: "Azeret Mono", monospace;
1075+
font-family: var(--font-mono);
10741076
font-size: 0.66rem;
10751077
letter-spacing: 0.12em;
10761078
text-transform: uppercase;
10771079
color: #daf4ff;
10781080
opacity: 0.92;
10791081
}
10801082

1083+
@media (prefers-reduced-motion: reduce), (max-width: 900px) {
1084+
.art-lead-image {
1085+
filter: none;
1086+
}
1087+
1088+
.art-photo-tile {
1089+
transition: border-color 0.2s ease, box-shadow 0.2s ease;
1090+
transform: none;
1091+
}
1092+
1093+
.art-photo-tile:hover,
1094+
.art-photo-tile.active {
1095+
transform: translateY(-2px);
1096+
}
1097+
1098+
.art-photo-image {
1099+
transition: none;
1100+
}
1101+
1102+
.art-photo-tile:hover .art-photo-image {
1103+
transform: none;
1104+
}
1105+
}
1106+
10811107
.tweet-card-wrap {
10821108
height: 100%;
10831109
}

0 commit comments

Comments
 (0)