首页星空大背景 + 品牌改为视锥/Logo + 联系方式改为可扩展手机号
All checks were successful
Build and Deploy / build-deploy (push) Successful in 25s
All checks were successful
Build and Deploy / build-deploy (push) Successful in 25s
This commit is contained in:
BIN
public/logo.png
Normal file
BIN
public/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
@@ -6,7 +6,8 @@ const year = new Date().getFullYear();
|
|||||||
<footer class="site-footer">
|
<footer class="site-footer">
|
||||||
<div class="container inner">
|
<div class="container inner">
|
||||||
<div>
|
<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>
|
<span class="muted"> · {SITE.subtitle}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="links">
|
<div class="links">
|
||||||
@@ -17,6 +18,11 @@ const year = new Date().getFullYear();
|
|||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.brand-cn {
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
}
|
||||||
.site-footer {
|
.site-footer {
|
||||||
border-top: 1px solid var(--border);
|
border-top: 1px solid var(--border);
|
||||||
margin-top: 4rem;
|
margin-top: 4rem;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const isActive = (href: string) =>
|
|||||||
<header class="site-header">
|
<header class="site-header">
|
||||||
<div class="container bar">
|
<div class="container bar">
|
||||||
<a class="brand" href="/">
|
<a class="brand" href="/">
|
||||||
<span class="brand-mark">>_</span>
|
<img class="brand-logo" src={SITE.logo} alt={SITE.title} width="28" height="32" />
|
||||||
<span class="brand-name">{SITE.title}</span>
|
<span class="brand-name">{SITE.title}</span>
|
||||||
</a>
|
</a>
|
||||||
<nav class="nav">
|
<nav class="nav">
|
||||||
@@ -48,12 +48,15 @@ const isActive = (href: string) =>
|
|||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
.brand-mark {
|
.brand-logo {
|
||||||
font-family: var(--font-mono);
|
display: block;
|
||||||
color: var(--accent);
|
height: 32px;
|
||||||
|
width: auto;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
.brand-name {
|
.brand-name {
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.08em;
|
||||||
|
font-size: 1.05rem;
|
||||||
}
|
}
|
||||||
.nav {
|
.nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
130
src/components/Starfield.astro
Normal file
130
src/components/Starfield.astro
Normal 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>
|
||||||
@@ -20,7 +20,7 @@ const canonical = new URL(Astro.url.pathname, Astro.site ?? SITE.url).toString()
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<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} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
|
||||||
<title>{pageTitle}</title>
|
<title>{pageTitle}</title>
|
||||||
@@ -36,6 +36,7 @@ const canonical = new URL(Astro.url.pathname, Astro.site ?? SITE.url).toString()
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<Header />
|
<Header />
|
||||||
|
<slot name="hero" />
|
||||||
<main class="container">
|
<main class="container">
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
import { SITE, SOCIAL } from '../site.config';
|
import { CONTACTS } from '../site.config';
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title="联系" description="联系方式">
|
<BaseLayout title="联系" description="联系方式">
|
||||||
@@ -12,17 +12,20 @@ import { SITE, SOCIAL } from '../site.config';
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="contact">
|
<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) => (
|
CONTACTS.map((c) =>
|
||||||
<a class="card row" href={s.href} target="_blank" rel="noopener">
|
c.href ? (
|
||||||
<span class="label mono">{s.label}</span>
|
<a class="card row" href={c.href}>
|
||||||
<span class="value">{s.href}</span>
|
<span class="label mono">{c.label}</span>
|
||||||
|
<span class="value">{c.value}</span>
|
||||||
</a>
|
</a>
|
||||||
))
|
) : (
|
||||||
|
<div class="card row">
|
||||||
|
<span class="label mono">{c.label}</span>
|
||||||
|
<span class="value">{c.value}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
@@ -56,8 +59,10 @@ import { SITE, SOCIAL } from '../site.config';
|
|||||||
}
|
}
|
||||||
.value {
|
.value {
|
||||||
color: var(--text-dim);
|
color: var(--text-dim);
|
||||||
|
font-size: 1.05rem;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
}
|
}
|
||||||
.row:hover .value {
|
a.row:hover .value {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
import ProjectCard from '../components/ProjectCard.astro';
|
import ProjectCard from '../components/ProjectCard.astro';
|
||||||
|
import Starfield from '../components/Starfield.astro';
|
||||||
import { SITE } from '../site.config';
|
import { SITE } from '../site.config';
|
||||||
|
|
||||||
const allProjects = (await getCollection('projects')).sort(
|
const allProjects = (await getCollection('projects')).sort(
|
||||||
@@ -17,16 +18,20 @@ const posts = (await getCollection('blog'))
|
|||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
<section class="hero">
|
<section class="hero" slot="hero">
|
||||||
|
<Starfield />
|
||||||
|
<div class="hero-inner container">
|
||||||
<span class="tag">{SITE.subtitle}</span>
|
<span class="tag">{SITE.subtitle}</span>
|
||||||
<h1>构建电子、软件与机器人系统</h1>
|
<h1>构建电子、软件与机器人系统</h1>
|
||||||
<p class="lead muted">
|
<p class="lead">
|
||||||
{SITE.description}
|
{SITE.description}
|
||||||
</p>
|
</p>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a class="btn btn-primary" href="/projects">查看项目 →</a>
|
<a class="btn btn-primary" href="/projects">查看项目 →</a>
|
||||||
<a class="btn" href="/blog">技术博客</a>
|
<a class="btn" href="/blog">技术博客</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hero-fade"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="block">
|
<section class="block">
|
||||||
@@ -81,26 +86,56 @@ const posts = (await getCollection('blog'))
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.hero {
|
.hero {
|
||||||
padding: 4rem 0 3rem;
|
position: relative;
|
||||||
border-bottom: 1px solid var(--border);
|
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 {
|
.hero h1 {
|
||||||
font-size: clamp(2rem, 5vw, 3rem);
|
font-size: clamp(2.2rem, 6vw, 4rem);
|
||||||
margin: 1rem 0 0.75rem;
|
margin: 1.1rem 0 1rem;
|
||||||
max-width: 18ch;
|
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;
|
max-width: 56ch;
|
||||||
font-size: 1.05rem;
|
font-size: 1.1rem;
|
||||||
|
color: #c2cde0;
|
||||||
}
|
}
|
||||||
.actions {
|
.actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.75rem;
|
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 {
|
.block {
|
||||||
padding: 2.5rem 0 0;
|
padding: 2.5rem 0 0;
|
||||||
}
|
}
|
||||||
|
.block:first-of-type {
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
.block-head {
|
.block-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
// 站点全局配置 —— 改这里即可调整站点信息、导航、社交链接
|
// 站点全局配置 —— 改这里即可调整站点信息、导航、社交链接
|
||||||
export const SITE = {
|
export const SITE = {
|
||||||
title: 'ShiZhui',
|
title: '视锥',
|
||||||
|
titleEn: 'ShiZhui',
|
||||||
subtitle: '电子 · 软件 · 通信 · 机器人',
|
subtitle: '电子 · 软件 · 通信 · 机器人',
|
||||||
description:
|
description:
|
||||||
'ShiZhui —— 电子工程、软件、通信与机器人方向的项目展示与技术博客。',
|
'视锥 —— 电子工程、软件、通信与机器人方向的项目展示与技术博客。',
|
||||||
author: 'ShiZhui',
|
author: '视锥',
|
||||||
url: 'https://shizhui.xyz',
|
url: 'https://shizhui.xyz',
|
||||||
email: 'hello@shizhui.xyz',
|
logo: '/logo.png',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NAV = [
|
export const NAV = [
|
||||||
@@ -17,6 +18,22 @@ export const NAV = [
|
|||||||
{ label: '联系', href: '/contact' },
|
{ 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 }[] = [
|
export const SOCIAL: { label: string; href: string }[] = [
|
||||||
// { label: 'GitHub', href: 'https://github.com/yourname' },
|
// { label: 'GitHub', href: 'https://github.com/yourname' },
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user