Compare commits

...

16 Commits

20 changed files with 3505 additions and 198 deletions
+107
View File
@@ -0,0 +1,107 @@
# 612 班级纪念站
这是一个用 Astro 搭建的班级纪念网站,用来保存毕业后的班级记忆:时间线、照片墙、同学近况、综合试卷和留言墙。
## 功能
- 首页:班级纪念入口、导航和基础统计。
- 三年时间线:记录高中阶段的重要片段。
- 照片墙:按专题整理课堂日常、班级活动、毕业当天和随手抓拍。
- 如今的我们:展示同学卡片和个人近况页面。
- 综合试卷:把纪念试卷做成网页,并保留 Word 原卷下载。
- 留言墙:接入 Twikoo 后可在页面中留言。
## 技术栈
- Astro 6
- TypeScript
- 原生 CSS
- Twikoo 评论系统
## 快速开始
```bash
npm install
npm run dev
```
本地开发服务启动后,按终端提示打开页面即可。
常用命令:
```bash
npm run dev # 启动开发服务器
npm run build # 构建静态站点到 dist
npm run preview # 预览构建结果
```
## 项目结构
```text
.
|-- public/
| `-- assets/ # 静态图片、试卷附件等资源
|-- scripts/ # GitHub Issue 自动更新数据脚本
|-- src/
| |-- data/ # 站点内容数据
| | |-- site.ts # 站点标题、导航、联系方式
| | |-- timeline.ts # 时间线内容
| | |-- photos.ts # 照片墙入口数据
| | |-- photo-topics/ # 各照片专题
| | |-- people.ts # 同学资料
| | |-- exam.ts # 综合试卷内容与评分配置
| | `-- messages.ts # 留言墙文案与 Twikoo 配置
| |-- layouts/ # 通用页面布局
| |-- pages/ # 路由页面
| `-- styles/ # 全局和页面样式
|-- astro.config.mjs
`-- package.json
```
## 内容维护
大部分网站内容都在 `src/data` 目录中维护。
- 修改站点名称、首页文案、导航和联系方式:编辑 `src/data/site.ts`
- 修改时间线:编辑 `src/data/timeline.ts`
- 修改照片墙专题:编辑 `src/data/photo-topics/*.ts`
- 修改同学资料:编辑 `src/data/people.ts`
- 修改试卷内容和答案:编辑 `src/data/exam.ts`
- 修改留言墙文案和 Twikoo 配置:编辑 `src/data/messages.ts`
新增静态资源时,放到 `public/assets` 下。页面中引用资源时使用以 `/assets/` 开头的路径,例如:
```ts
photo: "/assets/photos/example.jpg"
```
## 留言墙配置
留言墙使用 Twikoo。需要在本地或部署平台中配置环境变量:
```bash
PUBLIC_TWIKOO_ENV_ID=你的_Twikoo_envId
```
如果没有配置该变量,留言页会显示待配置提示,不会加载评论区。
## GitHub Issue 自动更新
仓库中包含用于收集内容更新的 Issue 模板和 GitHub Actions
- `people-update`:根据 Issue 表单更新 `src/data/people.ts`
- `photo-update`:根据 Issue 表单更新对应的照片专题数据
提交或编辑对应 Issue 后,工作流会运行脚本并自动创建更新 PR,方便审核后合并。
## 构建与部署
执行:
```bash
npm run build
```
构建产物会生成到 `dist/`。这个项目是静态站点,可以部署到 GitHub Pages、Vercel、Netlify 或任意静态资源服务器。
部署前建议检查 `astro.config.mjs` 中的 `site` 字段,把它改成真实站点地址。
+183 -179
View File
@@ -1,12 +1,12 @@
{
"name": "class-anniversary",
"version": "0.3.0",
"version": "1.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "class-anniversary",
"version": "0.3.0",
"version": "1.2.0",
"dependencies": {
"@astrojs/check": "^0.9.9",
"astro": "^6.2.1",
@@ -47,9 +47,9 @@
}
},
"node_modules/@astrojs/language-server": {
"version": "2.16.7",
"resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.16.7.tgz",
"integrity": "sha512-b64bWT74Vq/ORcSqW7TdIjjpB6hcl+Ei/lMANIUaAGlLPiYNtPTRI/j2tzvugT+LoVwfJtE2Ukq/t2OGCyEtfQ==",
"version": "2.16.8",
"resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.16.8.tgz",
"integrity": "sha512-yg1pZF6hs9FaKr2fgXMOGbW7pDLgFexFjuhWilPAc8VybTU+WSnbfbhYaUL1exm6dAK4sM3aKXGcfVwss+HXbg==",
"license": "MIT",
"dependencies": {
"@astrojs/compiler": "^2.13.1",
@@ -129,13 +129,12 @@
}
},
"node_modules/@astrojs/telemetry": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.1.tgz",
"integrity": "sha512-7fcIxXS9J4ls5tr8b3ww9rbAIz2+HrhNJYZdkAhhB4za/I5IZ/60g+Bs8q7zwG0tOIZfNB4JWhVJ1Qkl/OrNCw==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.2.tgz",
"integrity": "sha512-j8DNruA8ors99Al39RYZPJK4DC1bKkoNm93mAMuBhY9TCNC4R8n1q7ovFnJ5qhGh5Lsh7pa1gpQVpYpsJPeTHQ==",
"license": "MIT",
"dependencies": {
"ci-info": "^4.4.0",
"dlv": "^1.1.3",
"dset": "^3.1.4",
"is-docker": "^4.0.0",
"is-wsl": "^3.1.1",
@@ -1270,9 +1269,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz",
"integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz",
"integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==",
"cpu": [
"arm"
],
@@ -1283,9 +1282,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz",
"integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz",
"integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==",
"cpu": [
"arm64"
],
@@ -1296,9 +1295,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz",
"integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz",
"integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==",
"cpu": [
"arm64"
],
@@ -1309,9 +1308,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz",
"integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz",
"integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==",
"cpu": [
"x64"
],
@@ -1322,9 +1321,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz",
"integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz",
"integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==",
"cpu": [
"arm64"
],
@@ -1335,9 +1334,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz",
"integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz",
"integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==",
"cpu": [
"x64"
],
@@ -1348,9 +1347,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz",
"integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz",
"integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==",
"cpu": [
"arm"
],
@@ -1364,9 +1363,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz",
"integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz",
"integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==",
"cpu": [
"arm"
],
@@ -1380,9 +1379,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz",
"integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz",
"integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==",
"cpu": [
"arm64"
],
@@ -1396,9 +1395,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz",
"integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz",
"integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==",
"cpu": [
"arm64"
],
@@ -1412,9 +1411,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz",
"integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz",
"integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==",
"cpu": [
"loong64"
],
@@ -1428,9 +1427,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz",
"integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz",
"integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==",
"cpu": [
"loong64"
],
@@ -1444,9 +1443,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz",
"integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz",
"integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==",
"cpu": [
"ppc64"
],
@@ -1460,9 +1459,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz",
"integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz",
"integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==",
"cpu": [
"ppc64"
],
@@ -1476,9 +1475,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz",
"integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz",
"integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==",
"cpu": [
"riscv64"
],
@@ -1492,9 +1491,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz",
"integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz",
"integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==",
"cpu": [
"riscv64"
],
@@ -1508,9 +1507,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz",
"integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz",
"integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==",
"cpu": [
"s390x"
],
@@ -1524,9 +1523,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz",
"integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz",
"integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==",
"cpu": [
"x64"
],
@@ -1540,9 +1539,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz",
"integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz",
"integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==",
"cpu": [
"x64"
],
@@ -1556,9 +1555,9 @@
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz",
"integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz",
"integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==",
"cpu": [
"x64"
],
@@ -1569,9 +1568,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz",
"integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz",
"integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==",
"cpu": [
"arm64"
],
@@ -1582,9 +1581,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz",
"integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz",
"integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==",
"cpu": [
"arm64"
],
@@ -1595,9 +1594,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz",
"integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz",
"integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==",
"cpu": [
"ia32"
],
@@ -1608,9 +1607,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz",
"integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz",
"integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==",
"cpu": [
"x64"
],
@@ -1621,9 +1620,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz",
"integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz",
"integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==",
"cpu": [
"x64"
],
@@ -1743,9 +1742,9 @@
}
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
"license": "MIT"
},
"node_modules/@types/hast": {
@@ -1788,9 +1787,9 @@
"license": "MIT"
},
"node_modules/@ungap/structured-clone": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz",
"integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
"license": "ISC"
},
"node_modules/@volar/kit": {
@@ -1877,6 +1876,12 @@
"vscode-uri": "^3.0.8"
}
},
"node_modules/@vscode/emmet-helper/node_modules/jsonc-parser": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz",
"integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==",
"license": "MIT"
},
"node_modules/@vscode/l10n": {
"version": "0.0.18",
"resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz",
@@ -1988,15 +1993,15 @@
}
},
"node_modules/astro": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/astro/-/astro-6.2.1.tgz",
"integrity": "sha512-3g1sYNly+QAkuO5ErNEQBYvsxorNDSCUNIeStBs+kcXGchvKQl1Q9EuDNOvSg010XLlHJFLVFZs9LV18Jjp4Hg==",
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/astro/-/astro-6.3.1.tgz",
"integrity": "sha512-atz6dmkE3Gu24bDgb7g2RE/BYnKqPYIHd6hTUM1UXvu/i7qNZOKLAqEHvgYpv9PQVcgWsXpk4/OOXZ0E/FzvSQ==",
"license": "MIT",
"dependencies": {
"@astrojs/compiler": "^4.0.0",
"@astrojs/internal-helpers": "0.9.0",
"@astrojs/markdown-remark": "7.1.1",
"@astrojs/telemetry": "3.3.1",
"@astrojs/telemetry": "3.3.2",
"@capsizecss/unpack": "^4.0.0",
"@clack/prompts": "^1.1.0",
"@oslojs/encoding": "^1.1.0",
@@ -2014,10 +2019,12 @@
"esbuild": "^0.27.3",
"flattie": "^1.1.1",
"fontace": "~0.4.1",
"get-tsconfig": "5.0.0-beta.4",
"github-slugger": "^2.0.0",
"html-escaper": "3.0.3",
"http-cache-semantics": "^4.2.0",
"js-yaml": "^4.1.1",
"jsonc-parser": "^3.3.1",
"magic-string": "^0.30.21",
"magicast": "^0.5.2",
"mrmime": "^2.0.1",
@@ -2036,7 +2043,6 @@
"tinyclip": "^0.1.12",
"tinyexec": "^1.0.4",
"tinyglobby": "^0.2.15",
"tsconfck": "^3.1.6",
"ultrahtml": "^1.6.0",
"unifont": "~0.7.4",
"unist-util-visit": "^5.1.0",
@@ -2070,26 +2076,6 @@
"integrity": "sha512-eouss7G8ygdZqHuke033VMcVw5HTZUu+PXd/h06DGDUg/jt5btPYPqh66ENWw/mU78rBrf/oeC4oqoBwMtDMNA==",
"license": "MIT"
},
"node_modules/astro/node_modules/tsconfck": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz",
"integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==",
"license": "MIT",
"bin": {
"tsconfck": "bin/tsconfck.js"
},
"engines": {
"node": "^18 || >=20"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -2445,12 +2431,6 @@
"node": ">=0.3.1"
}
},
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"license": "MIT"
},
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
@@ -2669,9 +2649,9 @@
}
},
"node_modules/fast-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
"funding": [
{
"type": "github",
@@ -2763,6 +2743,21 @@
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-tsconfig": {
"version": "5.0.0-beta.4",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.4.tgz",
"integrity": "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==",
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"engines": {
"node": ">=20.20.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/github-slugger": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
@@ -3097,9 +3092,9 @@
"license": "MIT"
},
"node_modules/jsonc-parser": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz",
"integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==",
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
"integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
"license": "MIT"
},
"node_modules/kleur": {
@@ -3122,9 +3117,9 @@
}
},
"node_modules/lru-cache": {
"version": "11.3.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz",
"integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==",
"version": "11.3.6",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz",
"integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==",
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
@@ -4202,9 +4197,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.13",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz",
"integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==",
"version": "8.5.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
"funding": [
{
"type": "opencollective",
@@ -4472,6 +4467,15 @@
"node": ">=0.10.0"
}
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"license": "MIT",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/retext": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz",
@@ -4534,9 +4538,9 @@
}
},
"node_modules/rollup": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz",
"integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==",
"version": "4.60.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz",
"integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==",
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
@@ -4549,34 +4553,40 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.60.2",
"@rollup/rollup-android-arm64": "4.60.2",
"@rollup/rollup-darwin-arm64": "4.60.2",
"@rollup/rollup-darwin-x64": "4.60.2",
"@rollup/rollup-freebsd-arm64": "4.60.2",
"@rollup/rollup-freebsd-x64": "4.60.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.60.2",
"@rollup/rollup-linux-arm-musleabihf": "4.60.2",
"@rollup/rollup-linux-arm64-gnu": "4.60.2",
"@rollup/rollup-linux-arm64-musl": "4.60.2",
"@rollup/rollup-linux-loong64-gnu": "4.60.2",
"@rollup/rollup-linux-loong64-musl": "4.60.2",
"@rollup/rollup-linux-ppc64-gnu": "4.60.2",
"@rollup/rollup-linux-ppc64-musl": "4.60.2",
"@rollup/rollup-linux-riscv64-gnu": "4.60.2",
"@rollup/rollup-linux-riscv64-musl": "4.60.2",
"@rollup/rollup-linux-s390x-gnu": "4.60.2",
"@rollup/rollup-linux-x64-gnu": "4.60.2",
"@rollup/rollup-linux-x64-musl": "4.60.2",
"@rollup/rollup-openbsd-x64": "4.60.2",
"@rollup/rollup-openharmony-arm64": "4.60.2",
"@rollup/rollup-win32-arm64-msvc": "4.60.2",
"@rollup/rollup-win32-ia32-msvc": "4.60.2",
"@rollup/rollup-win32-x64-gnu": "4.60.2",
"@rollup/rollup-win32-x64-msvc": "4.60.2",
"@rollup/rollup-android-arm-eabi": "4.60.3",
"@rollup/rollup-android-arm64": "4.60.3",
"@rollup/rollup-darwin-arm64": "4.60.3",
"@rollup/rollup-darwin-x64": "4.60.3",
"@rollup/rollup-freebsd-arm64": "4.60.3",
"@rollup/rollup-freebsd-x64": "4.60.3",
"@rollup/rollup-linux-arm-gnueabihf": "4.60.3",
"@rollup/rollup-linux-arm-musleabihf": "4.60.3",
"@rollup/rollup-linux-arm64-gnu": "4.60.3",
"@rollup/rollup-linux-arm64-musl": "4.60.3",
"@rollup/rollup-linux-loong64-gnu": "4.60.3",
"@rollup/rollup-linux-loong64-musl": "4.60.3",
"@rollup/rollup-linux-ppc64-gnu": "4.60.3",
"@rollup/rollup-linux-ppc64-musl": "4.60.3",
"@rollup/rollup-linux-riscv64-gnu": "4.60.3",
"@rollup/rollup-linux-riscv64-musl": "4.60.3",
"@rollup/rollup-linux-s390x-gnu": "4.60.3",
"@rollup/rollup-linux-x64-gnu": "4.60.3",
"@rollup/rollup-linux-x64-musl": "4.60.3",
"@rollup/rollup-openbsd-x64": "4.60.3",
"@rollup/rollup-openharmony-arm64": "4.60.3",
"@rollup/rollup-win32-arm64-msvc": "4.60.3",
"@rollup/rollup-win32-ia32-msvc": "4.60.3",
"@rollup/rollup-win32-x64-gnu": "4.60.3",
"@rollup/rollup-win32-x64-msvc": "4.60.3",
"fsevents": "~2.3.2"
}
},
"node_modules/rollup/node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"license": "MIT"
},
"node_modules/sax": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
@@ -4587,9 +4597,9 @@
}
},
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -5197,9 +5207,9 @@
}
},
"node_modules/vite": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz",
"integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
"version": "7.3.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz",
"integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==",
"license": "MIT",
"dependencies": {
"esbuild": "^0.27.0",
@@ -5465,12 +5475,6 @@
"npm": ">=7.0.0"
}
},
"node_modules/vscode-json-languageservice/node_modules/jsonc-parser": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
"integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
"license": "MIT"
},
"node_modules/vscode-jsonrpc": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
@@ -5578,9 +5582,9 @@
}
},
"node_modules/yaml": {
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz",
"integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==",
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
"integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
@@ -5681,9 +5685,9 @@
}
},
"node_modules/zod": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.2.tgz",
"integrity": "sha512-IynmDyxsEsb9RKzO3J9+4SxXnl2FTFSzNBaKKaMV6tsSk0rw9gYw9gs+JFCq/qk2LCZ78KDwyj+Z289TijSkUw==",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "class-anniversary",
"version": "0.3.0",
"version": "1.2.0",
"private": true,
"type": "module",
"scripts": {
+1
View File
@@ -0,0 +1 @@
Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

+299
View File
@@ -0,0 +1,299 @@
export const examIntro = {
title: "综合素质检测",
eyebrow: "Class 612 Exam",
description:
"一份写给 2024 届 612 班的特别试卷。网页保留正文、插图与公式,原始版式可下载 Word 原卷查看。",
downloadHref: "https://s3.biss.click/OFFICE/exam-2024.pdf",
previewHref: "https://file.biss.click/onlinePreview?url=aHR0cHM6Ly9zMy5iaXNzLmNsaWNrL09GRklDRS9leGFtLTIwMjQucGRm&watermarkTxt=%E4%BB%85%E4%BE%9B%E9%A2%84%E8%A7%88"
};
export const examMath: Record<string, string> = {
setA:
'<math xmlns="http://www.w3.org/1998/Math/MathML" display="inline"><mi>A</mi><mo>=</mo><mo>{</mo><mn>6</mn><mo>,</mo><mn>1</mn><mo>,</mo><mn>2</mn><mo>,</mo><mn>9</mn><mo>,</mo><mn>8</mn><mo>,</mo><mn>5</mn><mo>,</mo><mn>2</mn><mo>,</mo><mn>1</mn><mo>,</mo><mn>1,6</mn><mo>,</mo><mn>6,6</mn><mo>}</mo></math>',
relation:
'<math xmlns="http://www.w3.org/1998/Math/MathML" display="inline"><mo>-</mo><mn>1</mn><mo>≤</mo><mi>a</mi><mo>+</mo><mi>b</mi><mo>≤</mo><mn>4</mn><mo>,</mo><mo>-</mo><mn>1</mn><mo>≤</mo><mi>a</mi><mo>-</mo><mi>b</mi><mo>≤</mo><mn>2</mn></math>',
expression:
'<math xmlns="http://www.w3.org/1998/Math/MathML" display="inline"><mn>4</mn><mi>a</mi><mo>-</mo><mn>2</mn><mi>b</mi></math>',
optionA:
'<math xmlns="http://www.w3.org/1998/Math/MathML" display="inline"><mrow><mo>[</mo><mrow><mo>-</mo><mn>2,10</mn></mrow><mo>]</mo></mrow></math>',
optionB:
'<math xmlns="http://www.w3.org/1998/Math/MathML" display="inline"><mrow><mo>(</mo><mrow><mo>-</mo><mn>2,10</mn></mrow><mo>]</mo></mrow></math>',
optionC:
'<math xmlns="http://www.w3.org/1998/Math/MathML" display="inline"><mi>R</mi></math>',
optionD:
'<math xmlns="http://www.w3.org/1998/Math/MathML" display="inline"><mi mathvariant="normal">∅</mi></math>',
doubleIntegral:
'<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><mi>I</mi><mo>=</mo><mrow><msubsup><mo stretchy="false">∫</mo><mrow><mn>0</mn></mrow><mrow><mn>1</mn></mrow></msubsup><mrow><mrow><msubsup><mo stretchy="false">∫</mo><mrow><mn>0</mn></mrow><mrow><mn>1</mn><mo>-</mo><mi>x</mi></mrow></msubsup><mrow><mrow><mo>(</mo><mrow><mi>x</mi><mo>+</mo><mi>y</mi></mrow><mo>)</mo></mrow></mrow></mrow></mrow></mrow><mo></mo><mi>d</mi><mi>y</mi><mo></mo><mi>d</mi><mi>x</mi></math>'
};
export const examScoring = {
choiceBonus: {
label: "选择题全对奖励",
points: 10,
appliesTo: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
},
choiceQuestions: [
{ number: 1, points: 5, answer: "B" },
{ number: 2, points: 5, answer: "D" },
{ number: 3, points: 5, answer: "B" },
{ number: 4, points: 5, answer: "A" },
{ number: 5, points: 5, answer: "C" },
{ number: 6, points: 5, answer: "C" },
{ number: 7, points: 5, answer: "D" },
{ number: 8, points: 5, answer: "D" },
{ number: 9, points: 5, answer: "C" },
{ number: 10, points: 5, answer: "A" },
{ number: 11, points: 5, answer: "B" },
{ number: 12, points: 5, answer: "" , freebie: true},
{ number: 18, points: 5, answer: "A" }
],
fillQuestions: [
{
number: 13,
points: 10,
mode: "ordered-blanks",
blankPoints: 2,
answers: ["既", "也|仍", "只有", "才", "如果|一旦"],
note: "按 ①▲ 到 ⑤▲ 的顺序填写答案。"
},
{
number: 14,
points: 30,
mode: "unordered-names",
blankCount: 56,
answers: [
"安梦伦",
"毕爽爽",
"陈可",
"陈昕楠",
"陈雨鑫",
"程怡钊",
"崔冬",
"崔欣钇",
"段星浩",
"冯梦雪",
"冯彦斌",
"郭梦帆",
"郭思涵",
"郭紫瑜",
"贺冰冰",
"贺秀芸",
"霍馨媛",
"姬锦琛",
"姜康坤",
"焦敬泽",
"琚泽浩",
"李超鹏",
"李琛",
"李佳琦",
"李丽桃",
"李顺利",
"李潇逸",
"李晓坤",
"李艳江",
"李怡颖",
"李宇川",
"李炤锦",
"路宗谕",
"马明宇",
"牛梦茹",
"牛润锋",
"牛温雅",
"秦培源",
"秦祎岚",
"秦泽宇",
"谭宇辉",
"王德杰",
"王琳柯",
"王雪婧",
"王宇锦",
"王子玉",
"卫宇坤",
"吴子涵",
"张晨雨",
"张帆",
"张妍忆",
"张中华",
"赵家祺",
"赵壬雨",
"周钰浩",
"朱学航"
],
penaltyPerMistake: 1,
note: "人名无先后顺序;全对 30 分,每错、漏一个扣 1 分,最低分可以为负分。"
},
{
number: 15,
points: 20,
mode: "image-titles",
blankPoints: 5,
answers: ["", "", "", ""],
freebie: true,
note: "按图片顺序填写标题。"
}
],
manualQuestions: [
{ number: 16, points: 5 },
{ number: 17, points: 5 },
{ number: 19, points: 5 },
{ number: 20, points: 10 },
{ number: 21, points: 10 },
{ number: 22, points: 10 },
{ number: 23, points: 10 },
{ number: 24, points: 40 },
{ number: 25, points: 30 }
]
};
export const examText = `
姓名:准考证号#########座位号##
2024届普通高中毕业班综合测试
综合素质检测
本试卷共8页,25题。全卷满分260分。考试用时150分钟。
注意事项:
考生要认真检查试题卷、答题卡有无缺印、漏印,监考员下发条形码上信息是否与本人相符,检查完毕后在试题卷、答题卡、草稿纸的相应位置填写信息。
在作答选择题时,用2B铅笔将答题卡上对应的答案标号涂黑,如需更改,请用橡皮擦净后改涂其他选项。在作答非选择题时,使用0.5mm 的黑色碳素笔作答;作答在试题卷、草稿纸以及答题卡非作答区域上的答案无效。
考试结束后,请将本试题卷和答题卡自行保管。
★祝考试顺利!★
一、选择题:在下面的小题中每题至少有一项符合题意,每小题5分,共12小题,共60分。漏选得2分,有选错的不得分,全部正确额外加10分。
如图为某班某次考试的成绩雷达图,读图并回答1-2小题:
[image:/assets/exam/image2.svg:某班某次考试成绩雷达图]
[q]下列说法中正确的有
①该班成绩中,英语学科总体成绩较好;
②该班所有同学英语科成绩一定高于平均分;
③没有足够证据表明语文学科与英语学科成绩的方差相同;
④在各科成绩中,平均水平最差的为物理科。
A.①④B.①③④C.③④D.①②③④
[q]请推断该班同学甲的英语科成绩
A. 优秀B. 良好C. 较差D. 无法推断
[q]集合 [math:setA] 中,元素的个数为
A. 9 B. 6 C. 7 D. 8
阅读本段材料,回答4-5 小题:
假定你正在参加你校毕业典礼,突然你想起数学教师讲的一道题,题目如下:已知不等关系 [math:relation],求 [math:expression] 的取值范围。
[q]解答材料中题目
A.[math:optionA] B.[math:optionB] C.[math:optionC] D.[math:optionD]
[q]在解答与材料中相类似题目时,往往会出错,其中有一种最典型的错误,原因是
A. 水平不足B. 题目错了
C. 在解答时扩大了𝑎, bD. 从没错过
你将阅读一段文字,并在文中的横线上选出恰当的成语。
在高中三年中,我们与老师和同学们▲,虽然历经风雨,但我们不轻言放弃、▲,我们始终相信阳光总在风雨后,▲,我们一定会拥有美好的明天!
[q]在文中的横线上选出恰当的成语
A. 戮力同心坚持不懈苦尽甘来B. 披荆斩棘戮力同心苦尽甘来
C. 坚持不懈苦尽甘来戮力同心D. 披荆斩棘坚持不懈戮力同心
高中三年,你是否还记得班主任对我们的教诲?回答7-8 小题。
[q]在高二年级,班主任来班级的频率
A. 全天都在B. 上午会在C. 下午会在D. 经常不在
[q]夏季,班主任经常在教室
A. 认真工作B. 玩手机C. 睡觉D. 吹空调
[q]At the graduation ceremony, we ▲uniform attire and celebrated graduation together.
A. take up B. make up C. dress up D. turn up.
[q]元旦晚会上,有一首必唱的歌,为
A.《高平一中赞歌》B.《高平一中校歌》C. 流行音乐D.《七律·长征》
[q]通过十二年的学习,我们收获了很多。下列说法错误的是
A. 我们参加了丰富的实践活动,学会了做人,学会了做事
B. 我们在学海中泛舟,懂得了书本知识比实践能力更重要
C. 我们结交一群志同道合的朋友,在成长道路上风雨同行
D. 我们不断地修炼着道德操守,坚持与德并进,与法同行
[q]在毕业后我们都各奔东西,不知你是否过上了自己想要的大学生活,现在请评价一下你的大学生活。
A.如鱼得水B.不尽人意C.勉强还行D.一塌糊涂
无论你的选择如何,都是满分答案。请记住生活中总有美好的事物。不要被当前的不如意所阻碍!
二、填空题:共3小题,共50分。
[q]在下列材料的空白处填入合适的关联词语(每空2分,共10分):
青年人富有理想和抱负,憧憬着美好的未来,这是青年人的特点,①▲是优点。②▲须懂得,个人的抱负不可能孤立地实现,③▲将个人理想同时代和人民的要求紧密结合起来,用自己的知识和本领为祖国、为人民服务,④▲能使自身价值得到充分体现。⑤▲脱离时代,脱离人民,必将一事无成。
[q]默写题:在本题中请你默写你班所有同学的姓名,全对得30分,每错、漏一个扣1分,最低分可以为负分,若为负分,则从总分中倒扣。
[q]在本题中你将看到几幅图片请为每幅图片编写一个标题,每处5分,共20分。
[gallery:/assets/exam/image3.jpg,/assets/exam/image4.jpeg,/assets/exam/image5.jpg,/assets/exam/image6.jpg]
三、读·思:共分3部分,共30分。
(一)阅读:本部分共两小题,每小题5分,共10分。
①白求恩同志毫不利己专门利人的精神,表现在他对工作的极端的负责任,对同志对人民的极端的热忱。每个共产党员都要学习他。不少的人对工作不负责任,拈轻怕重,把重担子推给人家,自己挑轻的。一事当前,先替自己打算,然后再替别人打算。出了一点力就觉得了不起,喜欢自吹,生怕人家不知道。对同志对人民不是满腔热忱,而是冷冷清清,漠不关心,麻木不仁。这种人其实不是共产党员,至少不能算一个纯粹的共产党员。从前线回来的人说到白求恩,没有一个不佩服,没有一个不为他的精神所感动。晋察冀边区的军民,凡亲身受过白求恩医生的治疗和亲眼看过白求恩医生的工作的,无不为之感动。每一个共产党员,一定要学习白求恩同志的这种真正共产主义者的精神。
②白求恩同志是个医生,他以医疗为职业,对技术精益求精;在整个八路军医务系统中,他的医术是很高明的。这对于一班见异思迁的人,对于一班鄙薄技术工作以为不足道、以为无出路的人,也是一个极好的教训。
[q]简述作者观点。(100字以内)
[q]这段话对你有什么启示?(150字以内)
(二)文言文阅读:本部分共两小题,每小题5分,共10分。
陆壹贰序【现】谭宇辉
三载韶光织锦,聚星芒于庠序;一室肝胆同辉,化春雨润青衿。望流云而思俊采,抚书卷以慕长风。敢竭鄙怀,恭疏短引:
观夫师道巍巍,各秉圭璋[1]。坤霖骋骏,驰骋绿茵振木铎;玉先雕龙,吞吐诗骚点碧霄。少南布算,谈笑间星河倒转;雪梅沥血,丹忱处桃李无言。永珍叱咤惊雷[2],口劈物理玄关;海燕呢喃细雨,手润化学幽微。英莉探生命之妙,王琼遗蕙兰之芳。至若张琴,申彤余音绕梁,诸师并耀,皆化春泥护新蕊。
至若男儿列岫[3],尽展峥嵘。陈可执牛耳而谐六艺,宇辉扫浊尘以净八方。培源擎纛引龙[4]骧虎步,昕楠振玉和鸾[5]凤清声。怡钊苦诣,似精卫填沧海。明宇逐风,若夸父追高日。子涵,泽浩掌戏乾坤,驭电驰霆[6]。宇锦绝笑如云外钟,琳柯诙谐似通灵鬼。宇川冠绝篮场,爽爽解构天机。晨雨,星浩倚案听蝉,枕流漱石鸣[7]。彦斌称恙犹戏谑,顺利蓄髯[8]自轩昂。宇坤扣篮惊鸿影,潇逸裂石遏云歌。超鹏吐凤嘲群彦,泽宇持正镇佞邪。宗谕戏谑藏锥颖,炤锦通达聚虹霓[9]。壬雨,家祺,润锋挟雷吐烈焰[10],融霜成笑;佳琦禅心映佛光,温婉为歌。李琛魁硕,力拔山兮盖世,梦帆易途,满腹玄鸡梗语。且看敬泽,晓坤,学航,思涵,德杰,雨鑫,崔冬,锦琛,祎岚渊默抱璞,伏枥藏[11]。百态峥嵘尽琳琅也。
尤慕娥眉璀璨,独占风华。欣钇执印[12],凛若秋霜裁玉律;子玉挥毫,灿如春日照朱阑[13]。冰冰怀瑾,璞玉终耀荆山彩;中华泼墨,才华直夺阆苑[14]色。梦伦渊默参星斗,怡颖凝脂掬月华。紫瑜拈花说岐黄,嫣然解尽三焦语;梦雪赧颊叱风雨[15],红妆偏胜七尺躯。馨媛眸转银汉,惊鸿一瞥倾人国;艳江笑漾梨涡,豆蔻初妆动帝畿[16]。丽桃译鸾通九译,妍忆容华羞洛神。梦茹凌波涉流芳,张帆莲步自生香。雪婧倩笑,云髻明灭半遮面;丹唇外朗,修眉联娟锁珠帘。温雅热忱,光照两性之友,众星拱月,皎若冰凌之心[17]。观其群芳:或如昆山片玉,或似赤水玄珠,皆钟造化之神秀也。
辉,三尺微命,一介农子,难忘师生之情,同窗之悦。既天赐文韬,唯以潘江陆海,铭此少年游——
教泽深兮沐春阳,同窗契兮凌雪霜。
男儿志在拏云手,女儿心凝韫玉光。
待乘长风破巨浪,犹记当时明月廊!
(选用时有删改)
注释:
[1]圭璋:一种玉制礼器,象征着美好的品质。
[2]叱咤惊雷:有震撼力和威严。
[3]列岫:排列的山峰,这里指依次介绍同学。
[4]擎纛引龙:举着旗引着龙,形容先锋作用。
[5]振玉和鸾:玉饰,车铃的声响,形容好听的声音。
[6]掌戏乾坤,驭电驰霆:戏指游戏,电指电子,总体意思就是玩电子游戏。
[7]倚案听蝉,枕流漱石鸣:即瞌睡说的雅称。
[8]蓄髯:留着胡须。
[9]聚虹霓:聚集光彩,指聚集目光,受同学们爱戴。
[10]挟雷吐烈焰:性情刚烈。
[11]渊默抱璞,伏枥藏锋:闷声干大事。
[12]执印:有官威,这里指能很好地管理班级。
[13]朱阑:红色的围栏,也指华丽的建筑。
[14]阆苑:神仙居住的地方,五光十色,色彩缤纷。
[15]赧颊叱风雨:赧颊指害羞,叱风雨指有气势,形容女汉子。
[16]帝畿:代指皇帝。
[17]冰凌之心:有柔情似水,也有冰凌刀锋,形容爱憎分明。
[q]文中描述“男儿列岫,尽展峥嵘”,这句话表达的意思最接近(请将答案填涂在答题卡上):
A. 男儿志向高远,展示出非凡的才华B.男儿在困境中展示自我
C. 男儿低调内敛,不张扬D. 男儿注重修身,心境高远
[q]文中多次提到“诸师并耀”,请结合文意解释这句话的含义,并谈谈其对作者的影响。
[q]阅读与改错:本部分中你将阅读五个名句,若有错误请使用修改符号改正,每处2分,共10 分。
①少无世俗韵,性本爱秋山。
②####难为听,今夜闻君琵琶语,如听仙乐耳暂明。
③故不积硅步无以致千里,不积小流无以成江海。
④奈何取之尽zi’zhu,用之如泥砂?
⑤寄浮游于天地,渺沧海之一粟。
四、解答题:共2小题,每小题10分,共20分。
[q]计算以下重积分:
[math:doubleIntegral]
[q]请你写出某个最令你印象深刻的事件,并简要评析。
五、综合操作题:共1小题,共10分。
[q]本题共五小题,每小题2分,共10分,请你写出正确且合理的操作:
(1)假定英语老师正在检查作业,而你恰好忘记写了,请你写出你的应对方式;
(2)牛坤霖老师正在检查迟到,而你姗姗来迟,你会怎么解释(辩解)?
(3)周练啦!你肯定会看到同学们互帮互助(抄),对此情况,你会怎么做?
(4)假定牛坤霖老师制定一些班规,你会有何反应?
5)毕业啦!你有何感想?
六、读·写:共1小题,共40分。
[q]请在以下A、B两题中选做一题,并将自己选做的题号填涂在答题卡的相应位置上:若有多做,按第一题计分:若考生多涂、漏涂选作标记,按A题计分。
A
在高中毕业前,我们总是憧憬着大学生活,但是到大学之后一切似乎和自己想的不太一样,老师也说大学才是开始学习的时候,是更苦更累的时候。一些人发出了“要回高中休息”的想法。
读了这则材料,你有什么感想?请你写一篇450字左右的作文。
要求:结合材料,选好角度,确定立意,明确文体,自拟标题;不要套作,不得抄袭;不得泄露个人信息;不少于450字。
B
2020年暑假,高考成绩676分,湖南省全省文科排名第四的留守女孩钟芳蓉因选择了北大考古专业而备受关注。钟芳蓉表示,选择该专业是因为自己从小喜欢历史和文物,希望将来能读研深造,做考古研究。对于她的选择,有人为她浪费了高分而感到惋惜,担忧她毕业后的就业前景和经济状况;也有人支持她的选择。
读了这则材料,你有什么感想?请你就未来人生规划写一篇450字左右的作文。
要求:结合材料,选好角度,确定立意,明确文体,自拟标题;不要套作,不得抄袭;不得泄露个人信息;不少于450字。
Ⅶ.Writing,Full mark:30.
Direction: in this section, you'll read a passage and then please you continue the story. The words are not limited.
From 612 to the Journey Ahead
The 612 class, consisting of fifty-six students, embarked on their journey from the first day of school until graduation. The class name "612" became synonymous (同义的) with unity and camaraderie (友情), as students from diverse backgrounds and families came together as a tightly-knit(紧密结合)group.
Over the past few years, the 612-class faced numerous challenges and experienced significant growth. They shared countless mornings under the rising sun, confronted the pressures of exams, and celebrated both successes and failures together. Guided by their teachers, they supported and uplifted one another, forging a unique bond.
As time passed, the students of 612 began to showcase(展示) their individual talents and potentials. Some excelled academically, becoming the class's academic achievers (学术佼佼者). Others displayed remarkable artistic talents, becoming the artistic pillars (艺术支柱) of the class. Some actively engaged in social activities, becoming the class's pioneers (先锋) in community service. Each person worked hard in their respective fields, adding vibrant colors to the class.
As graduation approached, the students of 612 started contemplating their futures. Some dreamed of becoming doctors, contributing to people's health and well-being. Others aspired to be scientists, exploring the mysteries of the unknown. And there were those who yearned to become artists, using their creativity to convey emotions. Regardless of their dreams, they carried with them confidence and passion for the future.
On the final day of 612, the classmates gathered to reminisce about their precious time together. They shared stories of personal growth and achievements, filled with deep emotions. Although parting ways was inevitable, they all understood that this class had provided them with endless support and encouragement, leaving an indelible mark (深刻的印记) in their hearts.
The future is an unknown realm, brimming with infinite possibilities. The students of 612 believe that no matter how winding (曲折) the path ahead may be, they will march forward with unwavering determination. They will use their efforts and talents to realize their dreams and make meaningful contributions to society.
In the closing chapter of their story, the 612 classmates bid farewell, knowing that it marks the beginning of a new journey. They carry their youthful dreams, venturing into a future that awaits them. They believe that, together, they will create a brighter tomorrow, armed with their dedication and aspirations.
[q]Continuation: Several years later, you find it while you're sorting through your things, and your mind flips back to that summer…
本试题卷到此结束,试题卷与答题卡均无需上交,请妥善保管
青春 席慕容
所有的结局都已写好
所有的泪水也都已启程
却忽然忘了是怎么样的一个开始
在那个古老的不再回来的夏日
无论我如何地去追索
年轻的你只如云影掠过
而你微笑的面容极浅极淡
逐渐隐没在日落后的群岚
遂翻开那发黄的扉页
命运将它装订的极为拙劣
含着泪我一读再读
却不得不承认
青春是一本太仓促的书
[注]那种对青春远逝的无限伤感、那种对生命短暂的无穷幽怨仿佛立刻遮蔽了天空,紧紧攫住了读者的心;就像那起程的泪水汹涌而来,打湿了每一个敏感而脆弱的生命。
`;
+1 -1
View File
@@ -5,7 +5,7 @@ export const messagesIntro = {
export const featuredMessage = {
text: "后来我们去了不同的地方,但那间教室像一个坐标,提醒我们曾经一起出发。",
author: "写给高三 X 班"
author: "写给612班"
};
export const messages = [
+14 -14
View File
@@ -7,29 +7,29 @@ export const classroomTopic: PhotoTopic = {
cover: "",
photos: [
{
title: "窗边的座位",
caption: "把真实照片放到 public/photos/classroom/window-seat.jpg 后,再把 image 改成对应路径。",
image: ""
title: "窗边",
caption: "走廊尽头的窗户",
image: "https://pic.biss.click/image/d4aa5275-9581-4d43-8d7e-ef3f44f8c497.jpg"
},
{
title: "黑板角落",
caption: "适合放倒计时、值日表、板书和课代表留下的提醒。",
image: ""
caption: "倒计时、值日表、板书和课代表留下的提醒。",
image: "https://pic.biss.click/image/049b8074-fde1-423c-b9b5-b89cfe4430e9.jpg"
},
{
title: "堆满书的桌面",
caption: "那些看起来很乱、后来又很想念的普通一天。",
image: ""
title: "黑板上的一句话",
caption: "",
image: "https://pic.biss.click/image/d37b224b-81ea-40cf-87aa-1dfa02312ba7.jpg"
},
{
title: "晚自习灯光",
caption: "可以放教室灯亮着、窗外天色暗下来的照片。",
image: ""
title: "高考加油",
caption: "",
image: "https://pic.biss.click/image/d06e370e-39a7-497e-a47d-eed7a759a67a.jpg"
},
{
title: "课间十分钟",
caption: "不用太正式,越像随手拍越有高中味道。",
image: ""
title: "询问老师",
caption: "课后向老师请教问题",
image: "https://pic.biss.click/image/95c03bd4-d0d3-4c38-9855-3342fd582080.jpg"
}
]
};
+2 -1
View File
@@ -21,7 +21,7 @@ export const contactLinks = [
},
{
label: "微信公众号",
href: "#",
href: "https://pic.biss.click/image/44a5e576-5cf3-4752-bae2-70d74619324f.webp",
icon: "fa-brands fa-weixin",
qrImage: "https://pic.biss.click/image/44a5e576-5cf3-4752-bae2-70d74619324f.webp"
}
@@ -32,6 +32,7 @@ export const navItems = [
{ label: "三年时间线", href: "/timeline/" },
{ label: "照片墙", href: "/gallery/" },
{ label: "如今的我们", href: "/people/" },
{ label: "综合试卷", href: "/exam/" },
{ label: "留言墙", href: "/messages/" }
];
+164
View File
@@ -0,0 +1,164 @@
---
import "../styles/anniversary.css";
const memoryCards = [
{
kicker: "2021",
title: "把名字写进同一张名单",
text: "从陌生的座位、军训的队列,到第一次一起等铃声响起,班级的故事从那时开始慢慢有了形状。"
},
{
kicker: "2022",
title: "普通日子也有回声",
text: "网课、考试、活动、晚自习,很多当时只觉得匆忙的片段,后来都变成了可以反复想起的证据。"
},
{
kicker: "2023",
title: "高三把我们推向同一个方向",
text: "倒计时、试卷、誓师和一次次模拟,把每个人的步伐压得很紧,也把彼此留得更深。"
},
{
kicker: "2024",
title: "毕业不是散场",
text: "离开校门以后,我们去了不同的地方,但那些一起走过的清晨和黄昏,仍然在这里被好好保存。"
}
];
const gallery = [
"/assets/campus-hero.png",
"/assets/exam/image1.png",
"/assets/exam/image3.jpg",
"/assets/exam/image4.jpeg",
"/assets/exam/image5.jpg",
"/assets/exam/image6.jpg"
];
const backgroundMusic = "/assets/anniversary/background-music.mp3";
const anniversaryVideo = "/assets/anniversary/anniversary-video.mp4";
const videoPoster = "/assets/campus-hero.png";
---
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>2024届12班周年专题</title>
<meta
name="description"
content="2024届12班周年专题页,收下那些毕业之后仍然清晰的瞬间。"
/>
</head>
<body>
<main class="anniversary-page">
<section class="hero" aria-labelledby="hero-title">
<img class="hero-media" src="/assets/campus-hero.png" alt="校园记忆" />
<div class="hero-shade" aria-hidden="true"></div>
<nav class="topbar" aria-label="周年专题导航">
<a class="home-link" href="/">2024届12班</a>
<div class="topbar-links">
<a href="#memory">时间切片</a>
<a href="#gallery">影像回放</a>
<a href="#letter">写给我们</a>
</div>
</nav>
<div class="hero-content">
<p class="eyebrow">毕业周年专题</p>
<h1 id="hero-title">我们仍在同一段青春里相遇</h1>
<p class="hero-copy">
从 2024 年夏天出发,到今天再次回望。这里不急着总结人生,只把一起经过的日子重新点亮。
</p>
<div class="hero-actions" aria-label="页面入口">
<a class="primary-action" href="#memory">开始回望</a>
<a class="secondary-action" href="/gallery/">去照片墙</a>
</div>
</div>
<aside class="music-player" aria-label="背景音乐">
<span class="music-mark" aria-hidden="true">♪</span>
<div class="music-copy">
<strong>背景乐</strong>
<span>替换 public/assets/anniversary/background-music.mp3</span>
</div>
<audio controls preload="metadata" loop>
<source src={backgroundMusic} type="audio/mpeg" />
</audio>
</aside>
<div class="hero-count">
<strong>2</strong>
<span>周年纪念</span>
</div>
</section>
<section class="ticker" aria-label="纪念短句">
<span>铃声响过</span>
<span>操场还亮着</span>
<span>试卷翻页</span>
<span>合照定格</span>
<span>名字仍被记得</span>
</section>
<section class="memory-section" id="memory" aria-labelledby="memory-title">
<div class="section-heading">
<p class="eyebrow">Time Slices</p>
<h2 id="memory-title">把三年拆成几束光</h2>
</div>
<div class="memory-grid">
{
memoryCards.map((card) => (
<article class="memory-card">
<span>{card.kicker}</span>
<h3>{card.title}</h3>
<p>{card.text}</p>
</article>
))
}
</div>
</section>
<section class="gallery-section" id="gallery" aria-labelledby="gallery-title">
<div class="section-heading">
<p class="eyebrow">Replay</p>
<h2 id="gallery-title">一些会自己发光的画面</h2>
</div>
<div class="video-feature" aria-label="周年视频">
<div class="video-frame">
<video controls preload="metadata" poster={videoPoster}>
<source src={anniversaryVideo} type="video/mp4" />
</video>
</div>
<div class="video-caption">
<p class="eyebrow">Film</p>
<h3>周年视频</h3>
<p>
替换 public/assets/anniversary/anniversary-video.mp4,这里会显示为专题视频播放区。
</p>
</div>
</div>
<div class="photo-strip">
{
gallery.map((src, index) => (
<figure class={`photo-card photo-card-${index + 1}`}>
<img src={src} alt={`周年纪念影像 ${index + 1}`} loading={index === 0 ? "eager" : "lazy"} />
</figure>
))
}
</div>
</section>
<section class="letter-section" id="letter" aria-labelledby="letter-title">
<div class="letter-panel">
<p class="eyebrow">For Us</p>
<h2 id="letter-title">写给毕业后的我们</h2>
<p>
后来的我们会遇见新的城市、新的课程、新的工作和新的自己。可只要有人再次提起那间教室、那次考试、那张合照,
2024届12班就会短暂地重新集合。
</p>
<p>
愿每一次回到这里,都不是为了停在过去,而是确认:那些一起认真生活过的日子,已经成为继续往前走的底气。
</p>
<a href="/messages/">留下新的近况</a>
</div>
</section>
</main>
</body>
</html>
+834
View File
@@ -0,0 +1,834 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import { examIntro, examMath, examScoring, examText } from "../data/exam";
import { site } from "../data/site";
const lines = examText
.trim()
.split("\n")
.map((line) => line.trim())
.filter(Boolean);
const isSectionLine = (line: string) =>
/^(一、|二、|三、|四、|五、|六、|Ⅶ\.)/.test(line);
const imagePattern = /^\[image:(.+?):(.+?)\]$/;
const galleryPattern = /^\[gallery:(.+)\]$/;
const questionPattern = /^\[q\](.+)$/;
const mathPattern = /\[math:([\w-]+)\]/g;
const optionPattern = /^[A-D]\./;
const renderLine = (line: string) =>
line.replace(mathPattern, (_, id: string) => examMath[id] ?? "");
const getOptions = (line: string) => {
if (!optionPattern.test(line)) {
return [];
}
return line.match(/[A-D]\.\s*.*?(?=\s*[A-D]\.|$)/g) ?? [];
};
const getOptionValue = (option: string) => option.match(/^([A-D])\./)?.[1] ?? "";
const fillQuestionMap = new Map(examScoring.fillQuestions.map((question) => [question.number, question]));
const circledNumbers = ["①", "②", "③", "④", "⑤"];
const renderQuestion13Line = (line: string) =>
line.replace(/([①②③④⑤])▲/g, (_, marker: string) => {
const blankIndex = circledNumbers.indexOf(marker);
return `${marker}<input class="inline-fill-input" data-fill-answer data-question-number="13" data-blank-index="${blankIndex}" aria-label="第 13 题第 ${blankIndex + 1} 空" />`;
});
const objectiveMax =
examScoring.choiceQuestions.reduce((sum, question) => sum + question.points, 0) +
examScoring.fillQuestions
.filter((question) => question.mode !== "unordered-names")
.reduce((sum, question) => sum + question.points, 0) +
examScoring.choiceBonus.points;
const nameQuestionMax =
examScoring.fillQuestions.find((question) => question.mode === "unordered-names")?.points ?? 0;
const manualMax = examScoring.manualQuestions.reduce((sum, question) => sum + question.points, 0);
const totalMax = objectiveMax + nameQuestionMax + manualMax;
const admissionNumberLength = 9;
const answerCardQuestions = [
...examScoring.choiceQuestions.map((question) => ({ ...question, kind: "choice" })),
...examScoring.fillQuestions.map((question) => ({ ...question, kind: "fill" })),
...examScoring.manualQuestions.map((question) => ({ ...question, kind: "manual" }))
].sort((left, right) => left.number - right.number);
let questionNumber = 0;
---
<BaseLayout title={`${examIntro.title} · ${site.className}`}>
<main class="page-main exam-page">
<section class="page-hero">
<div class="section-inner">
<a class="back-link" href="/">返回首页</a>
<p class="eyebrow">{examIntro.eyebrow}</p>
<h1>{examIntro.title}</h1>
<p>{examIntro.description}</p>
<div class="hero-actions">
<a class="button primary" href={examIntro.previewHref} target="_blank" rel="noreferrer">预览原卷</a>
</div>
</div>
</section>
<section class="exam-band" id="paper">
<div class="section-inner">
<div class="exam-workspace">
<article class="exam-paper" aria-label="试卷正文">
{
lines.map((line, index) => {
const image = line.match(imagePattern);
const gallery = line.match(galleryPattern);
const question = line.match(questionPattern);
const options = getOptions(line);
if (image) {
return (
<figure class="exam-figure">
<img src={image[1]} alt={image[2]} loading="lazy" />
<figcaption>{image[2]}</figcaption>
</figure>
);
}
if (gallery) {
const images = gallery[1].split(",");
return (
<div class="exam-image-grid" aria-label="图片标题题配图">
{images.map((src, imageIndex) => (
<figure>
<img src={src} alt={`图片标题题配图 ${imageIndex + 1}`} loading="lazy" />
<figcaption>图 {imageIndex + 1}</figcaption>
{
questionNumber === 15 && (
<label class="image-title-answer">
<span>标题</span>
<input
data-fill-answer
data-question-number="15"
data-blank-index={imageIndex}
aria-label={`第 15 题图 ${imageIndex + 1} 标题`}
/>
</label>
)
}
</figure>
))}
</div>
);
}
if (question) {
questionNumber += 1;
const currentQuestionNumber = questionNumber;
const fillQuestion = fillQuestionMap.get(currentQuestionNumber);
return (
<div
class="exam-question-block"
id={`question-${currentQuestionNumber}`}
data-question-block
data-question-number={currentQuestionNumber}
>
<p class="exam-question">
<span class="exam-question-number">{currentQuestionNumber}</span>
<span class="exam-question-text" set:html={renderLine(question[1])} />
</p>
{
fillQuestion?.mode === "unordered-names" ? (
<div class="name-fill-grid" aria-label="第 14 题姓名默写作答">
{Array.from({ length: fillQuestion.blankCount }, (_, blankIndex) => (
<label>
<span>{blankIndex + 1}</span>
<input
data-fill-answer
data-question-number={currentQuestionNumber}
data-blank-index={blankIndex}
aria-label={`第 ${currentQuestionNumber} 题第 ${blankIndex + 1} 个姓名`}
/>
</label>
))}
</div>
) : null
}
</div>
);
}
if (options.length > 0) {
return (
<div class="exam-options" aria-label="选项">
{options.map((option) => (
<button
class="exam-option"
type="button"
data-select-option
data-question-number={questionNumber}
data-option-value={getOptionValue(option)}
set:html={renderLine(option)}
/>
))}
</div>
);
}
if (index === 0) {
return (
<div class="exam-candidate-info" aria-label="考生信息">
<label>
<span>姓名</span>
<input id="candidate-name" autocomplete="name" />
</label>
<label>
<span>准考证号</span>
<input
id="admission-number"
inputmode="numeric"
maxlength={admissionNumberLength}
pattern={`\\d{${admissionNumberLength}}`}
placeholder={"#".repeat(admissionNumberLength)}
/>
</label>
<label>
<span>座位号</span>
<input id="seat-number" inputmode="numeric" maxlength="2" readonly placeholder="末两位" />
</label>
</div>
);
}
if (index < 3) {
return (
<p
class:list={["exam-title-line", index === 1 && "major"]}
set:html={renderLine(line)}
/>
);
}
if (isSectionLine(line)) {
return <h2>{line}</h2>;
}
if (line === "注意事项:" || line === "注释:") {
return <h3>{line}</h3>;
}
if (questionNumber === 13 && line.includes("▲")) {
return <p class="fill-inline-text" set:html={renderQuestion13Line(line)} />;
}
return <p set:html={renderLine(line)} />;
})
}
</article>
<button
class="score-fab"
type="button"
aria-controls="scorer"
aria-expanded="false"
aria-label="打开答题卡"
data-score-toggle
>
</button>
<aside class="score-sidebar" id="scorer" aria-label="答题卡">
<form class="score-panel" id="exam-scorer">
<div class="score-sidebar-head">
<p class="eyebrow">Answer Sheet</p>
<h2>答题卡</h2>
<p>题号颜色会提示是否作答,点击题号可跳到对应题目。</p>
</div>
<section class="score-group answer-card-profile" aria-label="考生信息">
<dl>
<div>
<dt>姓名</dt>
<dd id="card-candidate-name">未填写</dd>
</div>
<div>
<dt>准考证号</dt>
<dd id="card-admission-number">未填写</dd>
</div>
<div>
<dt>座位号</dt>
<dd id="card-seat-number">未填写</dd>
</div>
</dl>
</section>
<section class="score-group" aria-labelledby="answer-nav-title">
<h3 id="answer-nav-title">题号</h3>
<div class="answer-card-grid">
{
answerCardQuestions.map((question) => (
<button
class="answer-card-number"
type="button"
data-answer-jump
data-kind={question.kind}
data-question-number={question.number}
aria-label={`跳到第 ${question.number} 题`}
>
{question.number}
</button>
))
}
</div>
<div class="answer-card-legend" aria-label="答题状态说明">
<span><i class="is-unanswered"></i>未答</span>
<span><i class="is-answered"></i>已答</span>
<span><i class="is-manual"></i>手评</span>
</div>
</section>
<section class="score-group" aria-labelledby="manual-score-title">
<h3 id="manual-score-title">主观题</h3>
<div class="manual-score-grid">
{
examScoring.manualQuestions.map((question) => (
<label class="manual-score-item">
<span>{question.number} 题</span>
<input
data-manual-score
data-points={question.points}
type="number"
min="0"
max={question.points}
step="0.5"
value="0"
/>
<small>/ {question.points}</small>
</label>
))
}
</div>
</section>
<button class="score-submit" type="submit">提交</button>
<div class="score-total" aria-live="polite">
<div>
<span>客观题</span>
<strong><output id="objective-score">0</output><small> / {objectiveMax}</small></strong>
</div>
<div>
<span>姓名题</span>
<strong><output id="name-score">0</output><small> / {nameQuestionMax}</small></strong>
</div>
<div>
<span>主观题</span>
<strong><output id="manual-score">0</output><small> / {manualMax}</small></strong>
</div>
<div class="score-total-final">
<span>总分</span>
<strong><output id="total-score">0</output><small> / {totalMax}</small></strong>
</div>
</div>
<p class="score-total-note">14 题为姓名题,单独计算得分;罚分只从姓名题和总分中体现,不计入客观题。</p>
</form>
</aside>
</div>
</div>
</section>
</main>
<script is:inline>
window.MathJax = {
startup: {
typeset: true
},
chtml: {
matchFontHeight: false
}
};
</script>
<script
is:inline
src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/mml-chtml.js"
defer
></script>
<script
is:inline
define:vars={{
choiceBonus: examScoring.choiceBonus,
choiceQuestions: examScoring.choiceQuestions,
fillQuestions: examScoring.fillQuestions,
objectiveMax,
nameQuestionMax,
manualMax,
totalMax
}}
>
const scorer = document.querySelector("#exam-scorer");
const scoreSidebar = document.querySelector("#scorer");
const scoreToggle = document.querySelector("[data-score-toggle]");
const candidateName = document.querySelector("#candidate-name");
const admissionNumber = document.querySelector("#admission-number");
const seatNumber = document.querySelector("#seat-number");
const cardCandidateName = document.querySelector("#card-candidate-name");
const cardAdmissionNumber = document.querySelector("#card-admission-number");
const cardSeatNumber = document.querySelector("#card-seat-number");
const draftStorageKey = "examDraft";
const setScoreMenuOpen = (isOpen) => {
scoreSidebar?.classList.toggle("is-open", isOpen);
if (scoreToggle) {
scoreToggle.setAttribute("aria-expanded", String(isOpen));
scoreToggle.setAttribute("aria-label", isOpen ? "关闭答题卡" : "打开答题卡");
scoreToggle.textContent = isOpen ? "×" : "答";
}
};
const normalizeAnswer = (value) =>
String(value || "")
.trim()
.replace(/\s+/g, "")
.toLowerCase();
const getAcceptedAnswers = (answer) =>
(Array.isArray(answer) ? answer : String(answer || "").split("|"))
.map(normalizeAnswer)
.filter(Boolean);
const clampScore = (value, max) => {
const score = Number(value);
if (!Number.isFinite(score)) {
return 0;
}
return Math.min(Math.max(score, 0), max);
};
const clampManualScoreInput = (input) => {
const value = input.value.trim();
if (!value) {
return;
}
const max = Number(input.dataset.points || input.max || 0);
const score = Number(value);
if (!Number.isFinite(score)) {
return;
}
const clampedScore = clampScore(score, max);
if (score !== clampedScore) {
input.value = String(clampedScore);
}
};
const getDraft = () => ({
candidate: {
name: candidateName?.value ?? "",
admissionNumber: admissionNumber?.value ?? "",
seatNumber: seatNumber?.value ?? ""
},
choices: Object.fromEntries(
[...document.querySelectorAll("[data-select-option].is-selected")].map((option) => [
option.dataset.questionNumber,
option.dataset.optionValue
])
),
fills: Object.fromEntries(
[...document.querySelectorAll("[data-fill-answer]")].map((input) => [
`${input.dataset.questionNumber}-${input.dataset.blankIndex || 0}`,
input.value
])
),
manualScores: Object.fromEntries(
[...document.querySelectorAll("[data-manual-score]")].map((input) => {
const label = input.closest(".manual-score-item");
const numberText = label?.querySelector("span")?.textContent ?? "";
const questionNumber = Number(numberText.match(/\d+/)?.[0] ?? 0);
return [questionNumber, input.value];
})
)
});
const saveDraft = () => {
localStorage.setItem(draftStorageKey, JSON.stringify(getDraft()));
};
const restoreDraft = () => {
const draft = JSON.parse(localStorage.getItem(draftStorageKey) || "null");
if (!draft) {
return;
}
if (candidateName) {
candidateName.value = draft.candidate?.name || "";
}
if (admissionNumber) {
admissionNumber.value = draft.candidate?.admissionNumber || "";
}
if (seatNumber) {
seatNumber.value = draft.candidate?.seatNumber || admissionNumber?.value.slice(-2) || "";
}
Object.entries(draft.choices || {}).forEach(([questionNumber, optionValue]) => {
const option = document.querySelector(
`[data-select-option][data-question-number="${questionNumber}"][data-option-value="${optionValue}"]`
);
option?.classList.add("is-selected");
});
Object.entries(draft.fills || {}).forEach(([key, value]) => {
const [questionNumber, blankIndex] = key.split("-");
const input = document.querySelector(
`[data-fill-answer][data-question-number="${questionNumber}"][data-blank-index="${blankIndex}"]`
);
if (input) {
input.value = value;
}
});
document.querySelectorAll("[data-manual-score]").forEach((input) => {
const label = input.closest(".manual-score-item");
const numberText = label?.querySelector("span")?.textContent ?? "";
const questionNumber = Number(numberText.match(/\d+/)?.[0] ?? 0);
if (draft.manualScores?.[questionNumber] != null) {
input.value = draft.manualScores[questionNumber];
clampManualScoreInput(input);
}
});
};
const syncSelectedOption = (questionNumber, userAnswer) => {
document
.querySelectorAll(`[data-select-option][data-question-number="${questionNumber}"]`)
.forEach((option) =>
option.classList.toggle(
"is-selected",
Boolean(userAnswer) && option.dataset.optionValue === userAnswer
)
);
};
const syncCandidateInfo = () => {
if (cardCandidateName) {
cardCandidateName.textContent = candidateName?.value.trim() || "未填写";
}
if (cardAdmissionNumber) {
cardAdmissionNumber.textContent = admissionNumber?.value.trim() || "未填写";
}
if (cardSeatNumber) {
cardSeatNumber.textContent = seatNumber?.value.trim() || "未填写";
}
};
const setAnswerCardState = (questionNumber, isAnswered) => {
const cardNumber = document.querySelector(
`[data-answer-jump][data-question-number="${questionNumber}"]`
);
cardNumber?.classList.toggle("is-answered", Boolean(isAnswered));
};
const updateAnswerCardStates = () => {
choiceQuestions.forEach((question) => {
const selectedOption = document.querySelector(
`[data-select-option][data-question-number="${question.number}"].is-selected`
);
setAnswerCardState(question.number, Boolean(selectedOption));
});
fillQuestions.forEach((question) => {
const inputs = [...document.querySelectorAll(`[data-fill-answer][data-question-number="${question.number}"]`)];
setAnswerCardState(question.number, inputs.some((input) => input.value.trim()));
});
scorer?.querySelectorAll("[data-manual-score]").forEach((input) => {
const label = input.closest(".manual-score-item");
const numberText = label?.querySelector("span")?.textContent ?? "";
const questionNumber = Number(numberText.match(/\d+/)?.[0] ?? 0);
if (questionNumber) {
setAnswerCardState(questionNumber, Number(input.value) > 0);
}
});
};
const updateScores = () => {
if (!scorer) {
return null;
}
let objectiveScore = 0;
let manualScore = 0;
let choiceScore = 0;
let fillScore = 0;
let choiceAnswered = 0;
let fillAnswered = 0;
let manualAnswered = 0;
let namePenalty = 0;
let nameScore = 0;
let rawTotal = 0;
const correctChoiceNumbers = new Set();
choiceQuestions.forEach((question) => {
const selectedOption = document.querySelector(
`[data-select-option][data-question-number="${question.number}"].is-selected`
);
const userAnswer = selectedOption?.dataset.optionValue ?? "";
if (userAnswer) {
choiceAnswered += 1;
}
if (question.freebie && userAnswer) {
objectiveScore += question.points;
choiceScore += question.points;
correctChoiceNumbers.add(question.number);
return;
}
if (question.answer && userAnswer === question.answer) {
objectiveScore += question.points;
choiceScore += question.points;
correctChoiceNumbers.add(question.number);
}
});
fillQuestions.forEach((question) => {
const inputs = [...document.querySelectorAll(`[data-fill-answer][data-question-number="${question.number}"]`)]
.sort((left, right) => Number(left.dataset.blankIndex || 0) - Number(right.dataset.blankIndex || 0));
const answers = Array.isArray(question.answers) ? question.answers : [];
if (question.mode === "unordered-names") {
const standardNames = answers.map(normalizeAnswer).filter(Boolean);
const submittedNames = inputs.map((input) => normalizeAnswer(input.value)).filter(Boolean);
if (submittedNames.length > 0) {
fillAnswered += 1;
}
if (standardNames.length > 0) {
const standardSet = new Set(standardNames);
const submittedSet = new Set(submittedNames);
const correctCount = [...submittedSet].filter((name) => standardSet.has(name)).length;
const missingCount = Math.max(0, standardSet.size - correctCount);
const wrongCount = submittedNames.filter((name) => !standardSet.has(name)).length;
namePenalty = (missingCount + wrongCount) * (question.penaltyPerMistake ?? 1);
nameScore = question.points - namePenalty;
}
return;
}
let questionHasAnswer = false;
answers.forEach((answer, index) => {
const acceptedAnswers = getAcceptedAnswers(answer);
const normalizedUserAnswer = normalizeAnswer(inputs[index]?.value ?? "");
const blankPoints = question.blankPoints ?? question.points / Math.max(answers.length, 1);
if (normalizedUserAnswer) {
questionHasAnswer = true;
}
if (question.freebie && normalizedUserAnswer) {
objectiveScore += blankPoints;
fillScore += blankPoints;
return;
}
if (acceptedAnswers.length > 0 && acceptedAnswers.includes(normalizedUserAnswer)) {
objectiveScore += blankPoints;
fillScore += blankPoints;
}
});
if (questionHasAnswer) {
fillAnswered += 1;
}
});
const bonusApplies =
choiceBonus.appliesTo.length > 0 &&
choiceBonus.appliesTo.every((number) => correctChoiceNumbers.has(number));
if (bonusApplies) {
objectiveScore += choiceBonus.points;
choiceScore += choiceBonus.points;
}
scorer.querySelectorAll("[data-manual-score]").forEach((input) => {
const points = Number(input.dataset.points || 0);
const score = clampScore(input.value, points);
if (String(input.value) !== String(score)) {
input.value = String(score);
}
manualScore += score;
if (score > 0) {
manualAnswered += 1;
}
});
const objectiveOutput = document.querySelector("#objective-score");
const nameOutput = document.querySelector("#name-score");
const manualOutput = document.querySelector("#manual-score");
const totalOutput = document.querySelector("#total-score");
const subtotal = objectiveScore + nameScore + manualScore;
rawTotal = subtotal;
const displayTotal = Math.max(0, rawTotal);
if (objectiveOutput) {
objectiveOutput.value = objectiveScore;
objectiveOutput.textContent = objectiveScore;
}
if (nameOutput) {
nameOutput.value = nameScore;
nameOutput.textContent = nameScore;
}
if (manualOutput) {
manualOutput.value = manualScore;
manualOutput.textContent = manualScore;
}
if (totalOutput) {
totalOutput.value = displayTotal;
totalOutput.textContent = displayTotal;
}
return {
candidate: {
name: candidateName?.value.trim() || "未填写",
admissionNumber: admissionNumber?.value.trim() || "未填写",
seatNumber: seatNumber?.value.trim() || "未填写"
},
score: {
objective: objectiveScore,
name: nameScore,
manual: manualScore,
subtotal,
total: displayTotal,
rawTotal,
objectiveMax,
nameQuestionMax,
manualMax,
totalMax
},
breakdown: {
choiceScore,
fillScore,
choiceAnswered,
choiceTotal: choiceQuestions.length,
fillAnswered,
fillTotal: fillQuestions.length,
manualAnswered,
manualTotal: scorer.querySelectorAll("[data-manual-score]").length,
bonusApplied: bonusApplies,
namePenalty,
totalFloored: rawTotal < 0
},
submittedAt: new Date().toISOString()
};
};
scorer?.addEventListener("submit", (event) => {
event.preventDefault();
const result = updateScores();
if (result) {
sessionStorage.setItem("examResult", JSON.stringify(result));
window.location.href = "/exam/result/";
}
});
document.querySelectorAll("[data-select-option]").forEach((button) => {
button.addEventListener("click", () => {
const questionNumber = button.dataset.questionNumber;
const optionValue = button.dataset.optionValue;
if (!questionNumber || !optionValue) {
return;
}
document
.querySelectorAll(`[data-select-option][data-question-number="${questionNumber}"]`)
.forEach((option) => option.classList.toggle("is-selected", option === button));
updateAnswerCardStates();
saveDraft();
});
});
document.querySelectorAll("[data-fill-answer]").forEach((input) => {
input.addEventListener("input", () => {
updateAnswerCardStates();
saveDraft();
});
});
scorer?.querySelectorAll("[data-manual-score]").forEach((input) => {
input.addEventListener("input", () => {
clampManualScoreInput(input);
updateAnswerCardStates();
saveDraft();
});
});
document.querySelectorAll("[data-answer-jump]").forEach((button) => {
button.addEventListener("click", () => {
const question = document.querySelector(`#question-${button.dataset.questionNumber}`);
question?.scrollIntoView({
behavior: "smooth",
block: "start"
});
setScoreMenuOpen(false);
});
});
scoreToggle?.addEventListener("click", () => {
setScoreMenuOpen(!scoreSidebar?.classList.contains("is-open"));
});
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
setScoreMenuOpen(false);
}
});
admissionNumber?.addEventListener("input", () => {
admissionNumber.value = admissionNumber.value.replace(/\D/g, "").slice(0, admissionNumber.maxLength);
if (seatNumber) {
seatNumber.value = admissionNumber.value.slice(-2);
}
syncCandidateInfo();
saveDraft();
});
candidateName?.addEventListener("input", () => {
syncCandidateInfo();
saveDraft();
});
seatNumber?.addEventListener("input", () => {
syncCandidateInfo();
saveDraft();
});
restoreDraft();
syncCandidateInfo();
updateAnswerCardStates();
</script>
</BaseLayout>
+226
View File
@@ -0,0 +1,226 @@
---
import BaseLayout from "../../layouts/BaseLayout.astro";
import { site } from "../../data/site";
---
<BaseLayout title={`成绩分析 · ${site.className}`}>
<main class="page-main">
<section class="page-hero result-hero">
<div class="section-inner">
<a class="back-link" href="/exam/">返回试卷</a>
<p class="eyebrow">Result</p>
<h1>成绩分析</h1>
<p>这里展示本次答题的得分、完成情况和可下载的分享图。</p>
</div>
</section>
<section class="result-band">
<div class="section-inner">
<article class="result-card" id="result-card">
<div class="result-empty" id="result-empty">
<h2>还没有成绩</h2>
<p>请先完成试卷并点击答题卡里的“提交”。</p>
<a class="section-link" href="/exam/">去答题</a>
</div>
<div class="result-content" id="result-content" hidden>
<div class="result-head">
<div>
<p class="eyebrow">普通高中毕业班综合测试~综合素质检测</p>
<h2 id="result-title">成绩单</h2>
</div>
<button class="score-submit" type="button" id="download-share">下载分享图</button>
</div>
<dl class="result-profile">
<div>
<dt>姓名</dt>
<dd id="result-name">未填写</dd>
</div>
<div>
<dt>准考证号</dt>
<dd id="result-admission">未填写</dd>
</div>
<div>
<dt>座位号</dt>
<dd id="result-seat">未填写</dd>
</div>
</dl>
<div class="result-score-grid">
<div class="result-total">
<span>总分</span>
<strong><output id="result-total">0</output></strong>
<small id="result-total-max">/ 260</small>
</div>
<div>
<span>客观题</span>
<strong><output id="result-objective">0</output></strong>
<small id="result-objective-max">/ 135</small>
</div>
<div>
<span>主观题</span>
<strong><output id="result-manual">0</output></strong>
<small id="result-manual-max">/ 125</small>
</div>
<div class="result-name-score">
<span>姓名题</span>
<strong><output id="result-name-score">0</output></strong>
<small id="result-name-score-max">/ 30</small>
</div>
</div>
<p class="result-score-note">14 题姓名题单独计算,分数可低于 0;客观题不包含该题分数。</p>
<div class="result-analysis">
<h2>完成情况</h2>
<div class="analysis-grid">
<p><strong id="choice-progress">0 / 0</strong><span>选择题作答</span></p>
<p><strong id="fill-progress">0 / 0</strong><span>填空题作答</span></p>
<p><strong id="manual-progress">0 / 0</strong><span>主观题给分</span></p>
<p><strong id="bonus-status">未获得</strong><span>选择题奖励</span></p>
</div>
<div class="penalty-note" id="penalty-note" hidden>
<strong>14 题罚分:<span id="name-penalty">0</span> 分</strong>
<span id="floor-note" hidden>原始总分为负,最终总分已按 0 分记录。</span>
</div>
<p class="result-comment" id="result-comment"></p>
</div>
</div>
</article>
</div>
</section>
</main>
<script is:inline>
const result = JSON.parse(sessionStorage.getItem("examResult") || "null");
const empty = document.querySelector("#result-empty");
const content = document.querySelector("#result-content");
const setText = (selector, value) => {
const element = document.querySelector(selector);
if (element) {
element.textContent = String(value);
}
};
const getComment = (score, max) => {
const ratio = max > 0 ? score / max : 0;
if (ratio >= 0.9) return "状态拉满,很有当年考场上那股稳劲。";
if (ratio >= 0.75) return "整体发挥不错,很多题都稳稳拿住了。";
if (ratio >= 0.6) return "完成度还可以,主观题和填空题还能继续捞分。";
return "这张卷子本来就带点纪念性质,答完就已经很有参与感。";
};
const drawShareImage = (data) => {
const canvas = document.createElement("canvas");
const scale = window.devicePixelRatio || 1;
const width = 960;
const height = 1280;
canvas.width = width * scale;
canvas.height = height * scale;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
const ctx = canvas.getContext("2d");
ctx.scale(scale, scale);
ctx.fillStyle = "#fffdf7";
ctx.fillRect(0, 0, width, height);
ctx.fillStyle = "#24443a";
ctx.fillRect(0, 0, width, 240);
ctx.fillStyle = "#f3cf8b";
ctx.font = "700 28px Microsoft YaHei, sans-serif";
ctx.fillText("2024届612班", 72, 86);
ctx.fillStyle = "#fffdf7";
ctx.font = "800 54px Microsoft YaHei, sans-serif";
ctx.fillText("综合素质检测模拟卷", 72, 160);
ctx.font = "700 28px Microsoft YaHei, sans-serif";
ctx.fillText("成绩分析", 72, 208);
ctx.fillStyle = "#1f2b2a";
ctx.font = "700 30px Microsoft YaHei, sans-serif";
ctx.fillText(`姓名:${data.candidate.name}`, 72, 310);
ctx.fillText(`准考证号:${data.candidate.admissionNumber}`, 72, 360);
ctx.fillText(`座位号:${data.candidate.seatNumber}`, 72, 410);
ctx.fillStyle = "#376d5a";
ctx.font = "900 140px Microsoft YaHei, sans-serif";
ctx.fillText(String(data.score.total), 72, 610);
ctx.font = "800 38px Microsoft YaHei, sans-serif";
ctx.fillText(`/ ${data.score.totalMax}`, 340, 590);
ctx.fillStyle = "#62706f";
ctx.font = "700 28px Microsoft YaHei, sans-serif";
ctx.fillText(`客观题 ${data.score.objective} / ${data.score.objectiveMax}`, 72, 690);
ctx.fillText(`主观题 ${data.score.manual} / ${data.score.manualMax}`, 72, 740);
ctx.fillText(`姓名题 ${data.score.name ?? 0} / ${data.score.nameQuestionMax ?? 0}`, 72, 790);
ctx.fillStyle = "#f4f7ee";
ctx.fillRect(72, 820, 816, 230);
ctx.fillStyle = "#1f2b2a";
ctx.font = "800 30px Microsoft YaHei, sans-serif";
ctx.fillText("完成情况", 110, 880);
ctx.font = "700 24px Microsoft YaHei, sans-serif";
ctx.fillText(`选择题:${data.breakdown.choiceAnswered} / ${data.breakdown.choiceTotal}`, 110, 935);
ctx.fillText(`填空题:${data.breakdown.fillAnswered} / ${data.breakdown.fillTotal}`, 110, 985);
ctx.fillText(`主观题:${data.breakdown.manualAnswered} / ${data.breakdown.manualTotal}`, 500, 935);
ctx.fillText(`奖励分:${data.breakdown.bonusApplied ? "已获得" : "未获得"}`, 500, 985);
if (data.breakdown.namePenalty > 0 || data.breakdown.totalFloored) {
ctx.fillStyle = "#c96452";
ctx.font = "800 24px Microsoft YaHei, sans-serif";
ctx.fillText(`14题罚分:${data.breakdown.namePenalty || 0}分`, 110, 1035);
if (data.breakdown.totalFloored) {
ctx.fillText("原始总分为负,最终总分按0分记录", 500, 1035);
}
}
ctx.fillStyle = "#62706f";
ctx.font = "700 24px Microsoft YaHei, sans-serif";
ctx.fillText("青春是一本太仓促的书,但这张成绩单刚刚好。", 72, 1140);
const link = document.createElement("a");
link.download = `612-score-${Date.now()}.png`;
link.href = canvas.toDataURL("image/png");
link.click();
};
if (result) {
empty.hidden = true;
content.hidden = false;
setText("#result-title", `${result.candidate.name}的成绩单`);
setText("#result-name", result.candidate.name);
setText("#result-admission", result.candidate.admissionNumber);
setText("#result-seat", result.candidate.seatNumber);
setText("#result-total", result.score.total);
setText("#result-total-max", `/ ${result.score.totalMax}`);
setText("#result-objective", result.score.objective);
setText("#result-objective-max", `/ ${result.score.objectiveMax}`);
setText("#result-manual", result.score.manual);
setText("#result-manual-max", `/ ${result.score.manualMax}`);
setText("#result-name-score", result.score.name ?? 0);
setText("#result-name-score-max", `/ ${result.score.nameQuestionMax ?? 0}`);
setText("#choice-progress", `${result.breakdown.choiceAnswered} / ${result.breakdown.choiceTotal}`);
setText("#fill-progress", `${result.breakdown.fillAnswered} / ${result.breakdown.fillTotal}`);
setText("#manual-progress", `${result.breakdown.manualAnswered} / ${result.breakdown.manualTotal}`);
setText("#bonus-status", result.breakdown.bonusApplied ? "已获得" : "未获得");
setText("#result-comment", getComment(result.score.total, result.score.totalMax));
if (result.breakdown.namePenalty > 0 || result.breakdown.totalFloored) {
document.querySelector("#penalty-note").hidden = false;
setText("#name-penalty", result.breakdown.namePenalty || 0);
if (result.breakdown.totalFloored) {
document.querySelector("#floor-note").hidden = false;
}
}
}
document.querySelector("#download-share")?.addEventListener("click", () => {
if (result) {
drawShareImage(result);
}
});
</script>
</BaseLayout>
+1 -1
View File
@@ -2,7 +2,7 @@
import BaseLayout from "../layouts/BaseLayout.astro";
import { featuredMessage, messagesIntro, twikooConfig } from "../data/messages";
import { site } from "../data/site";
import twikooThemeUrl from "../styles/twikoo2.css";
import twikooThemeUrl from "../styles/twikoo2.css?url";
---
<BaseLayout title={`${messagesIntro.title} · ${site.className}`}>
+575
View File
@@ -0,0 +1,575 @@
:root {
color: #24170f;
background: #f7efe3;
font-family:
"Inter", "PingFang SC", "Microsoft YaHei", "Noto Sans SC", system-ui,
sans-serif;
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
min-width: 320px;
background:
linear-gradient(90deg, rgba(36, 23, 15, 0.05) 1px, transparent 1px),
linear-gradient(180deg, rgba(36, 23, 15, 0.05) 1px, transparent 1px),
#f7efe3;
background-size: 42px 42px;
}
a {
color: inherit;
text-decoration: none;
}
.anniversary-page {
overflow: hidden;
}
.hero {
position: relative;
min-height: 92vh;
display: grid;
align-items: end;
padding: 28px clamp(18px, 5vw, 72px) 64px;
color: #fff9ed;
isolation: isolate;
}
.hero-media,
.hero-shade {
position: absolute;
inset: 0;
z-index: -2;
}
.hero-media {
width: 100%;
height: 100%;
object-fit: cover;
filter: saturate(0.96) contrast(1.04);
}
.hero-shade {
z-index: -1;
background:
linear-gradient(90deg, rgba(22, 12, 7, 0.86), rgba(22, 12, 7, 0.34) 58%, rgba(22, 12, 7, 0.52)),
linear-gradient(0deg, rgba(22, 12, 7, 0.9), transparent 44%);
}
.topbar {
position: absolute;
top: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
gap: 24px;
align-items: center;
padding: 24px clamp(18px, 5vw, 72px);
}
.home-link,
.topbar-links a {
border: 1px solid rgba(255, 249, 237, 0.42);
background: rgba(255, 249, 237, 0.1);
backdrop-filter: blur(14px);
}
.home-link {
padding: 12px 16px;
font-weight: 800;
}
.topbar-links {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 8px;
}
.topbar-links a {
padding: 10px 13px;
font-size: 14px;
}
.hero-content {
width: min(900px, 100%);
padding-top: 96px;
}
.eyebrow {
margin: 0 0 14px;
color: #c75f2a;
font-size: 13px;
font-weight: 900;
letter-spacing: 0;
text-transform: uppercase;
}
.hero .eyebrow {
color: #ffd36c;
}
h1,
h2,
h3,
p {
margin-top: 0;
}
h1 {
max-width: 980px;
margin-bottom: 22px;
font-size: clamp(46px, 8vw, 118px);
line-height: 0.96;
letter-spacing: 0;
text-wrap: balance;
}
.hero-copy {
max-width: 720px;
margin-bottom: 32px;
color: rgba(255, 249, 237, 0.84);
font-size: clamp(18px, 2vw, 24px);
line-height: 1.7;
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.primary-action,
.secondary-action,
.letter-panel a {
display: inline-flex;
min-height: 48px;
align-items: center;
justify-content: center;
padding: 0 22px;
font-weight: 900;
}
.primary-action,
.letter-panel a {
background: #ffd36c;
color: #24170f;
}
.secondary-action {
border: 1px solid rgba(255, 249, 237, 0.5);
color: #fff9ed;
}
.music-player {
position: fixed;
right: clamp(14px, 3vw, 34px);
bottom: clamp(14px, 3vw, 34px);
z-index: 20;
width: min(420px, calc(100vw - 28px));
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: 12px 14px;
align-items: center;
padding: 14px;
border: 1px solid rgba(255, 249, 237, 0.34);
background: rgba(20, 35, 29, 0.46);
backdrop-filter: blur(18px);
box-shadow: 0 18px 50px rgba(22, 12, 7, 0.24);
}
.music-mark {
display: grid;
place-items: center;
width: 42px;
aspect-ratio: 1;
border-radius: 50%;
background: #ffd36c;
color: #24170f;
font-size: 24px;
font-weight: 900;
}
.music-copy {
display: grid;
gap: 3px;
min-width: 0;
}
.music-copy strong,
.music-copy span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.music-copy strong {
font-size: 15px;
}
.music-copy span {
color: rgba(255, 249, 237, 0.66);
font-size: 12px;
}
.music-player audio {
grid-column: 1 / -1;
width: 100%;
height: 38px;
}
.hero-count {
position: absolute;
right: clamp(18px, 5vw, 72px);
bottom: 42px;
display: grid;
place-items: center;
width: clamp(108px, 14vw, 172px);
aspect-ratio: 1;
border: 1px solid rgba(255, 249, 237, 0.4);
border-radius: 50%;
background: rgba(255, 249, 237, 0.12);
backdrop-filter: blur(16px);
}
.hero-count strong {
font-size: clamp(42px, 7vw, 82px);
line-height: 0.9;
}
.hero-count span {
font-size: 13px;
font-weight: 800;
}
.ticker {
display: flex;
gap: 12px;
overflow-x: auto;
padding: 18px clamp(18px, 5vw, 72px);
color: #fff9ed;
background: #24170f;
scrollbar-width: none;
}
.ticker span {
flex: 0 0 auto;
padding: 8px 16px;
border: 1px solid rgba(255, 249, 237, 0.25);
border-radius: 999px;
color: rgba(255, 249, 237, 0.82);
font-size: 14px;
}
.memory-section,
.gallery-section,
.letter-section {
padding: clamp(64px, 9vw, 120px) clamp(18px, 5vw, 72px);
}
.section-heading {
display: grid;
grid-template-columns: minmax(0, 0.42fr) minmax(0, 0.58fr);
gap: 28px;
align-items: end;
margin-bottom: 34px;
}
.section-heading h2,
.letter-panel h2 {
margin-bottom: 0;
color: #24170f;
font-size: clamp(34px, 5vw, 72px);
line-height: 1.04;
letter-spacing: 0;
}
.memory-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
border: 1px solid #24170f;
background: #24170f;
gap: 1px;
}
.memory-card {
min-height: 360px;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 24px;
background: #f7efe3;
}
.memory-card span {
color: #c75f2a;
font-size: 46px;
font-weight: 950;
line-height: 1;
}
.memory-card h3 {
margin-bottom: 16px;
font-size: 25px;
line-height: 1.18;
letter-spacing: 0;
}
.memory-card p,
.letter-panel p {
color: rgba(36, 23, 15, 0.72);
font-size: 16px;
line-height: 1.9;
}
.gallery-section {
color: #fff9ed;
background: #14231d;
}
.gallery-section .section-heading h2 {
color: #fff9ed;
}
.video-feature {
display: grid;
grid-template-columns: minmax(0, 0.68fr) minmax(260px, 0.32fr);
gap: clamp(18px, 4vw, 42px);
align-items: end;
margin-bottom: clamp(28px, 5vw, 58px);
}
.video-frame {
position: relative;
overflow: hidden;
aspect-ratio: 16 / 9;
border: 1px solid rgba(255, 249, 237, 0.18);
background:
linear-gradient(135deg, rgba(255, 211, 108, 0.22), transparent),
rgba(255, 249, 237, 0.08);
}
.video-frame video {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
.video-caption {
padding-bottom: 10px;
}
.video-caption h3 {
margin-bottom: 14px;
color: #fff9ed;
font-size: clamp(28px, 4vw, 52px);
line-height: 1.08;
}
.video-caption p:last-child {
margin-bottom: 0;
color: rgba(255, 249, 237, 0.7);
font-size: 16px;
line-height: 1.8;
}
.photo-strip {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-auto-rows: 86px;
gap: 14px;
}
.photo-card {
margin: 0;
min-height: 180px;
overflow: hidden;
background: rgba(255, 249, 237, 0.1);
}
.photo-card img {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
.photo-card-1 {
grid-column: span 5;
grid-row: span 5;
}
.photo-card-2 {
grid-column: span 3;
grid-row: span 3;
}
.photo-card-3 {
grid-column: span 4;
grid-row: span 4;
}
.photo-card-4 {
grid-column: span 4;
grid-row: span 3;
}
.photo-card-5 {
grid-column: span 3;
grid-row: span 4;
}
.photo-card-6 {
grid-column: span 5;
grid-row: span 3;
}
.letter-section {
display: grid;
min-height: 76vh;
align-items: center;
background:
linear-gradient(135deg, rgba(199, 95, 42, 0.16), transparent 36%),
#f7efe3;
}
.letter-panel {
width: min(820px, 100%);
margin-inline: auto;
border-left: 6px solid #c75f2a;
padding: 12px 0 12px clamp(24px, 5vw, 56px);
}
.letter-panel h2 {
margin-bottom: 24px;
}
.letter-panel a {
margin-top: 14px;
}
@media (max-width: 900px) {
.hero {
min-height: 88vh;
padding-bottom: 42px;
}
.topbar {
align-items: flex-start;
}
.topbar-links {
max-width: 190px;
}
.hero-count {
position: static;
margin-top: 38px;
}
.music-player {
width: min(420px, calc(100vw - 28px));
}
.section-heading {
grid-template-columns: 1fr;
gap: 8px;
}
.memory-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.photo-strip {
grid-template-columns: repeat(6, 1fr);
grid-auto-rows: 72px;
}
.video-feature {
grid-template-columns: 1fr;
}
.photo-card-1,
.photo-card-3,
.photo-card-6 {
grid-column: span 6;
}
.photo-card-2,
.photo-card-4,
.photo-card-5 {
grid-column: span 3;
}
}
@media (max-width: 560px) {
.topbar {
position: relative;
padding: 0 0 42px;
flex-direction: column;
}
.topbar-links {
max-width: none;
justify-content: flex-start;
}
.hero {
min-height: auto;
padding-top: 18px;
}
h1 {
font-size: 44px;
}
.hero-copy {
font-size: 17px;
}
.music-player {
grid-template-columns: auto minmax(0, 1fr);
padding: 12px;
}
.music-mark {
width: 38px;
}
.music-copy span {
display: none;
}
.memory-grid {
grid-template-columns: 1fr;
}
.memory-card {
min-height: 280px;
}
.photo-strip {
display: flex;
overflow-x: auto;
padding-bottom: 10px;
}
.photo-card {
flex: 0 0 78%;
height: 320px;
}
.letter-panel {
border-left-width: 4px;
}
}
+1096 -1
View File
File diff suppressed because it is too large Load Diff