首页星空大背景 + 品牌改为视锥/Logo + 联系方式改为可扩展手机号
All checks were successful
Build and Deploy / build-deploy (push) Successful in 25s

This commit is contained in:
2026-06-11 19:10:50 +08:00
parent de0b5d22a8
commit 32baae33ed
8 changed files with 237 additions and 40 deletions

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@@ -6,7 +6,8 @@ const year = new Date().getFullYear();
<footer class="site-footer">
<div class="container inner">
<div>
<span class="mono">{SITE.title}</span>
<span class="brand-cn">{SITE.title}</span>
<span class="mono muted"> {SITE.titleEn}</span>
<span class="muted"> · {SITE.subtitle}</span>
</div>
<div class="links">
@@ -17,6 +18,11 @@ const year = new Date().getFullYear();
</footer>
<style>
.brand-cn {
color: var(--text);
font-weight: 600;
letter-spacing: 0.06em;
}
.site-footer {
border-top: 1px solid var(--border);
margin-top: 4rem;

View File

@@ -8,7 +8,7 @@ const isActive = (href: string) =>
<header class="site-header">
<div class="container bar">
<a class="brand" href="/">
<span class="brand-mark">&gt;_</span>
<img class="brand-logo" src={SITE.logo} alt={SITE.title} width="28" height="32" />
<span class="brand-name">{SITE.title}</span>
</a>
<nav class="nav">
@@ -48,12 +48,15 @@ const isActive = (href: string) =>
color: var(--text);
font-weight: 700;
}
.brand-mark {
font-family: var(--font-mono);
color: var(--accent);
.brand-logo {
display: block;
height: 32px;
width: auto;
object-fit: contain;
}
.brand-name {
letter-spacing: 0.02em;
letter-spacing: 0.08em;
font-size: 1.05rem;
}
.nav {
display: flex;

View File

@@ -0,0 +1,130 @@
---
// 星空背景Canvas 绘制的星点 + 缓慢漂移 + 偶发流星,纯前端、轻量。
---
<canvas id="starfield" aria-hidden="true"></canvas>
<style>
#starfield {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
z-index: 0;
pointer-events: none;
}
</style>
<script>
const canvas = document.getElementById('starfield') as HTMLCanvasElement | null;
if (canvas) {
const ctx = canvas.getContext('2d')!;
let w = 0;
let h = 0;
let dpr = Math.min(window.devicePixelRatio || 1, 2);
type Star = { x: number; y: number; r: number; a: number; tw: number; vx: number };
type Meteor = { x: number; y: number; len: number; speed: number; life: number; angle: number };
let stars: Star[] = [];
let meteors: Meteor[] = [];
function resize() {
const parent = canvas.parentElement!;
w = parent.clientWidth;
h = parent.clientHeight;
dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = w * dpr;
canvas.height = h * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
initStars();
}
function initStars() {
const count = Math.floor((w * h) / 6000);
stars = [];
for (let i = 0; i < count; i++) {
stars.push({
x: Math.random() * w,
y: Math.random() * h,
r: Math.random() * 1.3 + 0.2,
a: Math.random() * 0.5 + 0.3,
tw: Math.random() * 0.02 + 0.005,
vx: Math.random() * 0.06 + 0.01,
});
}
}
function spawnMeteor() {
const startX = Math.random() * w * 0.8 + w * 0.1;
meteors.push({
x: startX,
y: Math.random() * h * 0.4,
len: Math.random() * 120 + 80,
speed: Math.random() * 4 + 6,
life: 1,
angle: Math.PI / 5,
});
}
let frame = 0;
let twDir = 1;
function draw() {
ctx.clearRect(0, 0, w, h);
// 星点
for (const s of stars) {
s.a += s.tw * twDir;
if (s.a > 0.9 || s.a < 0.25) twDir *= 1; // 单独翻转下面处理
s.x += s.vx;
if (s.x > w) s.x = 0;
const flicker = 0.6 + Math.abs(Math.sin((frame + s.x) * s.tw)) * 0.4;
ctx.beginPath();
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
ctx.fillStyle = `rgba(200, 225, 255, ${s.a * flicker})`;
ctx.fill();
}
// 流星
for (let i = meteors.length - 1; i >= 0; i--) {
const m = meteors[i];
const dx = Math.cos(m.angle) * m.len;
const dy = Math.sin(m.angle) * m.len;
const grad = ctx.createLinearGradient(m.x, m.y, m.x - dx, m.y - dy);
grad.addColorStop(0, `rgba(140, 230, 240, ${m.life})`);
grad.addColorStop(1, 'rgba(140, 230, 240, 0)');
ctx.strokeStyle = grad;
ctx.lineWidth = 1.6;
ctx.beginPath();
ctx.moveTo(m.x, m.y);
ctx.lineTo(m.x - dx, m.y - dy);
ctx.stroke();
m.x += Math.cos(m.angle) * m.speed;
m.y += Math.sin(m.angle) * m.speed;
m.life -= 0.012;
if (m.life <= 0 || m.x > w || m.y > h) meteors.splice(i, 1);
}
if (Math.random() < 0.006 && meteors.length < 3) spawnMeteor();
frame++;
requestAnimationFrame(draw);
}
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
resize();
window.addEventListener('resize', resize);
if (!reduce) {
draw();
} else {
// 静态绘制一帧
for (const s of stars) {
ctx.beginPath();
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
ctx.fillStyle = `rgba(200, 225, 255, ${s.a})`;
ctx.fill();
}
}
}
</script>

View File

@@ -20,7 +20,7 @@ const canonical = new URL(Astro.url.pathname, Astro.site ?? SITE.url).toString()
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" href="/logo.png" />
<meta name="generator" content={Astro.generator} />
<title>{pageTitle}</title>
@@ -36,6 +36,7 @@ const canonical = new URL(Astro.url.pathname, Astro.site ?? SITE.url).toString()
</head>
<body>
<Header />
<slot name="hero" />
<main class="container">
<slot />
</main>

View File

@@ -1,6 +1,6 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import { SITE, SOCIAL } from '../site.config';
import { CONTACTS } from '../site.config';
---
<BaseLayout title="联系" description="联系方式">
@@ -12,17 +12,20 @@ import { SITE, SOCIAL } from '../site.config';
</section>
<section class="contact">
<a class="card row" href={`mailto:${SITE.email}`}>
<span class="label mono">Email</span>
<span class="value">{SITE.email}</span>
</a>
{
SOCIAL.map((s) => (
<a class="card row" href={s.href} target="_blank" rel="noopener">
<span class="label mono">{s.label}</span>
<span class="value">{s.href}</span>
CONTACTS.map((c) =>
c.href ? (
<a class="card row" href={c.href}>
<span class="label mono">{c.label}</span>
<span class="value">{c.value}</span>
</a>
))
) : (
<div class="card row">
<span class="label mono">{c.label}</span>
<span class="value">{c.value}</span>
</div>
)
)
}
</section>
</BaseLayout>
@@ -56,8 +59,10 @@ import { SITE, SOCIAL } from '../site.config';
}
.value {
color: var(--text-dim);
font-size: 1.05rem;
letter-spacing: 0.02em;
}
.row:hover .value {
a.row:hover .value {
color: var(--text);
}
</style>

View File

@@ -2,6 +2,7 @@
import { getCollection } from 'astro:content';
import BaseLayout from '../layouts/BaseLayout.astro';
import ProjectCard from '../components/ProjectCard.astro';
import Starfield from '../components/Starfield.astro';
import { SITE } from '../site.config';
const allProjects = (await getCollection('projects')).sort(
@@ -17,16 +18,20 @@ const posts = (await getCollection('blog'))
---
<BaseLayout>
<section class="hero">
<section class="hero" slot="hero">
<Starfield />
<div class="hero-inner container">
<span class="tag">{SITE.subtitle}</span>
<h1>构建电子、软件与机器人系统</h1>
<p class="lead muted">
<p class="lead">
{SITE.description}
</p>
<div class="actions">
<a class="btn btn-primary" href="/projects">查看项目 →</a>
<a class="btn" href="/blog">技术博客</a>
</div>
</div>
<div class="hero-fade"></div>
</section>
<section class="block">
@@ -81,26 +86,56 @@ const posts = (await getCollection('blog'))
<style>
.hero {
padding: 4rem 0 3rem;
border-bottom: 1px solid var(--border);
position: relative;
min-height: 74vh;
display: flex;
align-items: center;
overflow: hidden;
background:
radial-gradient(1100px 600px at 50% -5%, rgba(57, 208, 216, 0.12), transparent 65%),
linear-gradient(180deg, #04060c 0%, #070b14 55%, var(--bg) 100%);
}
.hero-inner {
position: relative;
z-index: 2;
padding: 5rem 1.25rem;
width: 100%;
}
.hero h1 {
font-size: clamp(2rem, 5vw, 3rem);
margin: 1rem 0 0.75rem;
max-width: 18ch;
font-size: clamp(2.2rem, 6vw, 4rem);
margin: 1.1rem 0 1rem;
max-width: 20ch;
background: linear-gradient(180deg, #ffffff 0%, #aeb9cc 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 40px rgba(57, 208, 216, 0.15);
}
.lead {
.hero .lead {
max-width: 56ch;
font-size: 1.05rem;
font-size: 1.1rem;
color: #c2cde0;
}
.actions {
display: flex;
gap: 0.75rem;
margin-top: 1.75rem;
margin-top: 2rem;
}
.hero-fade {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 120px;
background: linear-gradient(180deg, transparent, var(--bg));
z-index: 1;
}
.block {
padding: 2.5rem 0 0;
}
.block:first-of-type {
padding-top: 1rem;
}
.block-head {
display: flex;
align-items: baseline;

View File

@@ -1,12 +1,13 @@
// 站点全局配置 —— 改这里即可调整站点信息、导航、社交链接
export const SITE = {
title: 'ShiZhui',
title: '视锥',
titleEn: 'ShiZhui',
subtitle: '电子 · 软件 · 通信 · 机器人',
description:
'ShiZhui —— 电子工程、软件、通信与机器人方向的项目展示与技术博客。',
author: 'ShiZhui',
'视锥 —— 电子工程、软件、通信与机器人方向的项目展示与技术博客。',
author: '视锥',
url: 'https://shizhui.xyz',
email: 'hello@shizhui.xyz',
logo: '/logo.png',
};
export const NAV = [
@@ -17,6 +18,22 @@ export const NAV = [
{ label: '联系', href: '/contact' },
];
// 联系方式可自由增删条目。type 用于图标/语义label 为显示名value 为内容,
// href 为可点击链接(可选,如电话用 tel:、邮箱用 mailto:、网址用 https
export const CONTACTS: {
type: string;
label: string;
value: string;
href?: string;
}[] = [
{ type: 'phone', label: '电话', value: '+86 152 8353 5287', href: 'tel:+8615283535287' },
// 在下面继续添加更多联系方式,例如:
// { type: 'wechat', label: '微信', value: 'your-wechat-id' },
// { type: 'email', label: '邮箱', value: 'you@example.com', href: 'mailto:you@example.com' },
// { type: 'github', label: 'GitHub', value: 'github.com/yourname', href: 'https://github.com/yourname' },
];
// 页脚社交链接(可选)
export const SOCIAL: { label: string; href: string }[] = [
// { label: 'GitHub', href: 'https://github.com/yourname' },
];