WaterBaby Serres — 婴幼儿游泳学校
4 周
|
设计与前端开发
WaterBaby Serres 在 Elpida Resort 的恒温室内泳池里,教四个月起的婴儿和十岁以下的儿童。这个网站要先对希腊家长说话,其次才是英语访客,但又不能变成两个网站。于是每一句面向访客的文字都在同一个文件里写了两遍,切换器只负责隐藏其中一份:每页一个网址,不重新加载,不发请求,没有第二套页面要同步,而且关掉 JavaScript 希腊语依然读得完整。六个页面撑起全部内容——首页、关于、两个课程、相册与联系——其中列出儿童的四个级别,从入门到高阶,还有家长需要准备的装备与证明文件,以及常见问答。GSAP 与 ScrollTrigger 放在本地而不是 CDN,所有动画都是叠加式的:CSS 不预先隐藏任何东西,脚本失败留下的是完整页面而不是空白页。首页的入场动画每次会话只放一遍,让一片水波沿视口退去,并由 CSS keyframes 自行收尾——即便 JavaScript 全数失效,也不会把访客困在遮罩后面。结构化数据覆盖课程、问答、相册和面包屑;llms.txt 为 AI 助手列出已核实的事实,并由测试与页面绑定,使两者不会走样。二十一个 Node 测试把一切钉住——页面结构、希腊语文案、跳转桩、加载动画的安全性质,以及每一块橙色表面的 WCAG 对比度。
技术栈
HTML5
CSS3
Vanilla JS (ES Modules)
GSAP 3
ScrollTrigger
JSON-LD
i18n (EL/EN)
Node Test Runner
WCAG 2.2 AA
SEO
挑战
双语网站通常意味着什么都要两份——两套页面、两个网址、两次“改了这边忘了那边”的机会。而这个站点由一位教练独自维护,每页多出一份副本是负担,不是功能。希腊语还必须是真正的默认语言,在任何脚本运行之前就能读,因为受众是本地家长。而游泳学校想要的那些点缀——入场动画、滚动显现、灯箱——恰恰是脚本在信号不好的手机上失败时,会留下一片空白的东西。
解决方案
两种语言都内联写在每个文件里,切换器只翻转一个 hidden 属性,于是只有一个页面要改、一个网址要分享、没有东西需要同步;语言偏好存在 localStorage,切换时派发一个自定义事件,相册会监听它。非文本内容——aria-label、占位符、meta 描述,乃至文档标题——通过成对的 data 属性在同一个函数里翻译,所以翻译过的页面不会留下没翻到的角落。动效按规矩只做叠加,并由测试强制执行:CSS 绝不允许预先隐藏参与动画的元素,因此 GSAP 加载失败只损失动画本身。入场遮罩以 hidden 出厂,只有 JavaScript 加上的类才会显示它,而它的退场是带 fill-mode forwards 的 CSS keyframe——脚本被关掉、被拦截或抛错,遮罩照样自行散去。库文件放在本地,CDN 挂掉不会连带把站点的动效一起带走;二十一个测试每一个都通过故意破坏它所保护的东西验证过。
图片集
两个课程——从四个月到十岁
让家长安心的三个数字——年龄、水温、班级人数
Baby Swimming——家长要一起下水的课程
同一个网址,两种语言——四个级别,切到英语
装备与文件——每一次第一堂课之前都会被问到的问题
相册——照片的大小由画面决定,而不是由网格决定
灯箱——原生 dialog 元素,键盘优先
联系——三种方式,联系一所没有前台的学校