浅做了一下加入我们

This commit is contained in:
2026-03-05 21:51:37 +08:00
parent 9f001635b3
commit a683ab35d9
5 changed files with 2078 additions and 0 deletions
+475
View File
@@ -0,0 +1,475 @@
.join-page {
padding: 36px 0 72px;
}
.join-head {
margin-bottom: 26px;
}
.join-title {
margin: 0;
font-size: clamp(2.1rem, 1.9vw + 1.3rem, 3.2rem);
line-height: 1.08;
color: var(--color-primary);
}
.join-hero {
display: flex;
flex-direction: column;
gap: 16px;
margin-bottom: 34px;
}
.join-canvas-shell {
position: relative;
border: 1px solid var(--border-default);
border-radius: 18px;
overflow: hidden;
background: color-mix(in srgb, var(--bg-card) 94%, transparent);
box-shadow: var(--shadow-sm);
}
.join-stage-photo-frame {
position: relative;
overflow: hidden;
}
.join-stage-photo {
width: 100%;
height: clamp(220px, 28vw, 360px);
object-fit: cover;
display: block;
}
.join-wave-layer {
position: relative;
margin-top: clamp(-20px, -2.4vw, -12px);
z-index: 1;
}
.join-progress-canvas {
display: block;
width: 100%;
height: clamp(56px, 5.8vw, 82px);
}
.join-wave-progress {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 2;
}
.join-wave-track {
position: relative;
position: absolute;
left: clamp(18px, 2.2vw, 30px);
right: clamp(18px, 2.2vw, 30px);
top: 68%;
height: 4px;
border-radius: 999px;
transform: translateY(-50%);
background: color-mix(in srgb, var(--text-secondary) 26%, transparent);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--border-default) 60%, transparent);
}
.join-wave-fill {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 0;
border-radius: inherit;
background: linear-gradient(
90deg,
color-mix(in srgb, var(--color-primary) 56%, transparent) 0%,
color-mix(in srgb, var(--color-primary) 82%, #7ed7ff 18%) 100%
);
box-shadow: 0 0 10px color-mix(in srgb, var(--color-primary) 35%, transparent);
}
.join-wave-marks {
position: absolute;
inset: 0;
overflow: visible;
}
.join-wave-mark {
--mark-wave-offset: 0px;
--mark-tilt: 0deg;
position: absolute;
top: 50%;
width: clamp(20px, 1.9vw, 27px);
transform: translate(-50%, calc(-58% + var(--mark-wave-offset))) rotate(var(--mark-tilt));
transform-origin: 50% 78%;
filter: drop-shadow(0 2px 6px color-mix(in srgb, #00142b 50%, transparent));
will-change: transform;
}
.join-wave-mark.is-lighthouse {
width: clamp(22px, 2vw, 30px);
transform: translate(-50%, -80%);
transform-origin: 50% 96%;
}
.join-wave-mark.is-active {
filter: drop-shadow(0 0 10px color-mix(in srgb, var(--color-primary) 36%, transparent));
}
.join-wave-mark-icon {
display: block;
width: 100%;
height: auto;
transform-origin: 50% 80%;
will-change: transform, opacity;
}
.join-wave-mark svg {
display: block;
width: 100%;
height: auto;
}
.join-wave-mark .marker-buoy-ring {
fill: color-mix(in srgb, #5fa0df 72%, #8ec8ff 28%);
}
.join-wave-mark .marker-buoy-base {
fill: color-mix(in srgb, #fbfdff 92%, #d5e9ff 8%);
}
.join-wave-mark .marker-buoy-stripe {
fill: color-mix(in srgb, #f6814c 70%, #ec4d44 30%);
}
.join-wave-mark .marker-buoy-cap {
fill: color-mix(in srgb, #29497d 74%, #1c2e57 26%);
}
.join-wave-mark .marker-buoy-light {
fill: color-mix(in srgb, #ffe083 72%, #ffc74a 28%);
}
.join-wave-mark .marker-buoy-gloss {
fill: color-mix(in srgb, #ffffff 88%, transparent);
}
.join-wave-mark .marker-lh-base {
fill: color-mix(in srgb, var(--color-primary) 70%, #0d2e62 30%);
}
.join-wave-mark .marker-lh-tower {
fill: color-mix(in srgb, #f6fbff 88%, var(--color-primary) 12%);
}
.join-wave-mark .marker-lh-top {
fill: color-mix(in srgb, #d44545 72%, #a52f2f 28%);
}
.join-wave-mark .marker-lh-beam {
fill: color-mix(in srgb, #ffef9f 64%, transparent);
opacity: 0.28;
}
.join-wave-mark .marker-reef-rock {
fill: color-mix(in srgb, #365a7f 72%, #2a4868 28%);
}
.join-wave-mark .marker-reef-highlight {
fill: color-mix(in srgb, #5f82a6 64%, #486a8e 36%);
}
.join-wave-boat {
--boat-wave-offset: 0px;
--boat-tilt: 0deg;
--boat-waterline-adjust: -4px;
position: absolute;
top: 50%;
left: 0;
width: clamp(42px, 4.4vw, 58px);
transform: translate(-50%, calc(-62% + var(--boat-waterline-adjust) + var(--boat-wave-offset))) rotate(var(--boat-tilt));
color: color-mix(in srgb, var(--color-primary) 78%, #8ed5ff 22%);
filter: drop-shadow(0 3px 8px color-mix(in srgb, #00132f 60%, transparent));
will-change: transform, left;
}
.join-wave-boat-icon {
display: block;
transform-origin: 50% 60%;
}
.join-wave-boat svg {
display: block;
width: 100%;
height: auto;
}
.join-wave-boat .boat-hull {
fill: color-mix(in srgb, var(--color-primary) 64%, #102e5f 36%);
}
.join-wave-boat .boat-sail-main {
fill: color-mix(in srgb, #f2f9ff 90%, var(--color-primary) 10%);
}
.join-wave-boat .boat-sail-side {
fill: color-mix(in srgb, #cbe6ff 80%, var(--color-primary) 20%);
}
.join-wave-boat .boat-mast {
fill: color-mix(in srgb, var(--text-primary) 75%, #294b7a 25%);
}
.join-wave-boat .boat-porthole {
fill: color-mix(in srgb, #eef6ff 85%, var(--color-primary) 15%);
}
.join-canvas-overlay {
position: absolute;
top: 16px;
left: 16px;
right: 16px;
z-index: 2;
max-width: 540px;
padding: 14px 16px;
border-radius: 12px;
border: 1px solid color-mix(in srgb, var(--border-default) 72%, transparent);
background: color-mix(in srgb, var(--bg-body) 68%, transparent);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
}
.join-current-label {
margin: 0;
font-size: 0.84rem;
letter-spacing: 0.04em;
color: var(--text-secondary);
}
.join-current-stage {
margin: 4px 0 0;
font-size: clamp(1.2rem, 0.9vw + 1rem, 1.7rem);
line-height: 1.25;
color: var(--text-primary);
}
.join-current-range {
margin: 6px 0 0;
font-size: 0.92rem;
color: var(--text-secondary);
}
.join-stage-list {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 10px;
margin: 0;
padding: 0;
list-style: none;
}
.join-stage-item {
border: 1px solid var(--border-default);
border-radius: 10px;
background: color-mix(in srgb, var(--bg-card) 95%, transparent);
padding: 10px 11px;
min-height: 98px;
}
.join-stage-item.is-current {
border-color: color-mix(in srgb, var(--color-primary) 52%, var(--border-default) 48%);
box-shadow: var(--shadow-sm);
}
.join-stage-item.is-completed .join-stage-status {
color: #0f8a4f;
}
.join-stage-item.is-active .join-stage-status {
color: var(--color-primary);
}
.join-stage-item.is-upcoming .join-stage-status,
.join-stage-item.is-pending .join-stage-status {
color: var(--text-secondary);
}
.join-stage-title-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: start;
column-gap: 8px;
row-gap: 4px;
}
.join-stage-name {
margin: 0;
min-width: 0;
color: var(--text-primary);
font-size: 0.94rem;
line-height: 1.3;
overflow-wrap: anywhere;
word-break: break-word;
}
.join-stage-status {
flex-shrink: 0;
white-space: nowrap;
align-self: start;
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.02em;
}
.join-stage-time {
margin: 8px 0 0;
color: var(--text-secondary);
font-size: 0.8rem;
line-height: 1.45;
}
.join-forms-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
.join-form-card {
border: 1px solid var(--border-default);
border-radius: 14px;
background: color-mix(in srgb, var(--bg-card) 95%, transparent);
padding: 18px;
}
.join-form-card-full {
grid-column: 1 / -1;
}
.join-form-head h2 {
margin: 0;
color: var(--text-primary);
font-size: 1.22rem;
}
.join-form-head p {
margin: 6px 0 0;
color: var(--text-secondary);
font-size: 0.9rem;
}
.join-form-content {
margin-top: 14px;
}
.join-form-content form {
margin: 0;
}
.join-form-content .frm_forms {
margin: 0;
}
.join-form-content .frm_style_formidable-style.with_frm_style .frm_form_fields,
.join-form-content .frm_style_formidable-style.with_frm_style .frm_submit {
max-width: none;
}
.join-form-content .frm_style_formidable-style.with_frm_style input[type="text"],
.join-form-content .frm_style_formidable-style.with_frm_style input[type="email"],
.join-form-content .frm_style_formidable-style.with_frm_style input[type="number"],
.join-form-content .frm_style_formidable-style.with_frm_style input[type="tel"],
.join-form-content .frm_style_formidable-style.with_frm_style input[type="date"],
.join-form-content .frm_style_formidable-style.with_frm_style input[type="datetime-local"],
.join-form-content .frm_style_formidable-style.with_frm_style textarea,
.join-form-content .frm_style_formidable-style.with_frm_style select {
background: color-mix(in srgb, var(--bg-body) 90%, transparent);
border-color: var(--border-default);
color: var(--text-primary);
}
.join-form-content .frm_style_formidable-style.with_frm_style input[type="submit"],
.join-form-content .frm_style_formidable-style.with_frm_style button.frm_button_submit {
border-radius: 9px;
border: 1px solid color-mix(in srgb, var(--color-primary) 62%, var(--border-default) 38%);
background: color-mix(in srgb, var(--color-primary) 10%, transparent);
color: var(--color-primary);
}
.join-form-tip {
margin: 0;
padding: 14px;
border-radius: 10px;
border: 1px dashed var(--border-default);
color: var(--text-secondary);
font-size: 0.92rem;
line-height: 1.65;
}
.join-form-footnote {
margin-top: 12px;
color: var(--text-secondary);
font-size: 0.82rem;
}
@media (max-width: 1080px) {
.join-stage-list {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 900px) {
.join-page {
padding: 30px 0 60px;
}
.join-stage-list {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.join-forms-grid {
grid-template-columns: 1fr;
}
.join-form-card-full {
grid-column: auto;
}
}
@media (max-width: 640px) {
.join-head {
margin-bottom: 22px;
}
.join-hero {
margin-bottom: 24px;
}
.join-canvas-overlay {
top: 12px;
left: 12px;
right: 12px;
padding: 10px 11px;
}
.join-stage-photo {
height: 230px;
}
.join-current-stage {
margin-top: 2px;
font-size: 1.1rem;
}
.join-current-range {
margin-top: 5px;
font-size: 0.82rem;
}
.join-stage-list {
grid-template-columns: 1fr;
}
.join-stage-item {
min-height: 0;
}
}
+496
View File
@@ -0,0 +1,496 @@
(() => {
const canvas = document.getElementById('joinProgressCanvas');
if (!canvas) {
return;
}
const context = canvas.getContext('2d');
if (!context) {
return;
}
const animeLib = window.anime;
const hasAnime = typeof animeLib === 'function';
const waveTrack = document.getElementById('joinWaveTrack');
const waveFill = document.getElementById('joinWaveFill');
const waveBoat = document.getElementById('joinWaveBoat');
const waveMarks = document.getElementById('joinWaveMarks');
const joinData = window.itstudioJoinData && typeof window.itstudioJoinData === 'object'
? window.itstudioJoinData
: {};
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
let width = 0;
let height = 0;
let dpr = 1;
let rafId = 0;
let targetProgress = 0;
let displayedProgress = 0;
let trackLeft = 0;
let trackWidth = 0;
let stageMarkers = [];
const waveShape1 = { base: 0.16, amp: 7.5, len: 280, speed: 0.9 };
const waveShape2 = { base: 0.44, amp: 6, len: 340, speed: 0.75 };
const waveShape3 = { base: 0.68, amp: 6.5, len: 400, speed: 0.62 };
const palettes = {
light: {
wave1: 'rgba(198, 227, 255, 0.92)',
wave2: 'rgba(142, 193, 236, 0.4)',
wave3: 'rgba(84, 151, 214, 0.48)',
},
dark: {
wave1: 'rgba(132, 190, 242, 0.88)',
wave2: 'rgba(88, 151, 214, 0.4)',
wave3: 'rgba(56, 118, 183, 0.48)',
},
};
function clamp(value, min, max) {
return Math.min(max, Math.max(min, value));
}
function getTheme() {
return document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light';
}
function getNumber(value) {
const numeric = Number(value);
return Number.isFinite(numeric) ? numeric : null;
}
function getNavigationType() {
if (window.performance && typeof window.performance.getEntriesByType === 'function') {
const entries = window.performance.getEntriesByType('navigation');
if (entries && entries.length > 0 && entries[0].type) {
return entries[0].type;
}
}
if (window.performance && window.performance.navigation) {
if (window.performance.navigation.type === 1) {
return 'reload';
}
if (window.performance.navigation.type === 2) {
return 'back_forward';
}
}
return 'navigate';
}
function shouldAnimateEntry() {
if (prefersReducedMotion.matches) {
return false;
}
return getNavigationType() !== 'reload';
}
function resize() {
dpr = window.devicePixelRatio || 1;
width = canvas.clientWidth;
height = canvas.clientHeight;
canvas.width = Math.max(1, Math.floor(width * dpr));
canvas.height = Math.max(1, Math.floor(height * dpr));
context.setTransform(dpr, 0, 0, dpr, 0, 0);
updateTrackMetrics();
}
function updateTrackMetrics() {
if (!waveTrack) {
trackLeft = 0;
trackWidth = width;
return;
}
const canvasRect = canvas.getBoundingClientRect();
const trackRect = waveTrack.getBoundingClientRect();
trackLeft = clamp(trackRect.left - canvasRect.left, 0, width);
trackWidth = Number.isFinite(trackRect.width) && trackRect.width > 0 ? trackRect.width : width;
}
function drawWave(color, yBase, amplitude, wavelength, speed, time) {
const overscan = 24;
const step = 6;
context.beginPath();
context.moveTo(-overscan, height + 2);
for (let x = -overscan; x <= width + overscan; x += step) {
const theta = (x / wavelength) * Math.PI * 2 + time * speed;
const y = yBase + Math.sin(theta) * amplitude;
context.lineTo(x, y);
}
context.lineTo(width + overscan, height + 2);
context.lineTo(-overscan, height + 2);
context.closePath();
context.fillStyle = color;
context.fill();
}
function sampleWaveAtX(shape, x, time) {
const theta = (x / shape.len) * Math.PI * 2 + time * shape.speed;
const y = Math.sin(theta) * shape.amp;
const slope = Math.cos(theta) * (Math.PI * 2 / shape.len) * shape.amp;
return { y, slope };
}
function getWaveXFromProgress(progress) {
const safeProgress = clamp(progress, 0, 1);
const currentTrackWidth = trackWidth > 0 ? trackWidth : width;
return trackLeft + (safeProgress * currentTrackWidth);
}
function applyFloatingMotion(time) {
if (width <= 0) {
return;
}
const boatWave = sampleWaveAtX(waveShape3, getWaveXFromProgress(displayedProgress), time);
if (waveBoat) {
const boatTilt = clamp(boatWave.slope * 34, -7.2, 7.2);
waveBoat.style.setProperty('--boat-wave-offset', `${boatWave.y.toFixed(2)}px`);
waveBoat.style.setProperty('--boat-tilt', `${boatTilt.toFixed(2)}deg`);
}
stageMarkers.forEach((marker) => {
if (marker.type === 'lighthouse') {
marker.element.style.setProperty('--mark-wave-offset', '0px');
marker.element.style.setProperty('--mark-tilt', '0deg');
return;
}
const markerWave = sampleWaveAtX(waveShape3, getWaveXFromProgress(marker.progress), time);
const markerTilt = clamp(markerWave.slope * 34, -7.2, 7.2);
marker.element.style.setProperty('--mark-wave-offset', `${markerWave.y.toFixed(2)}px`);
marker.element.style.setProperty('--mark-tilt', `${markerTilt.toFixed(2)}deg`);
});
}
function renderWaves(timestamp) {
const theme = getTheme();
const palette = palettes[theme];
const time = timestamp * 0.001;
context.clearRect(0, 0, width, height);
drawWave(palette.wave1, height * waveShape1.base, waveShape1.amp, waveShape1.len, waveShape1.speed, time);
drawWave(palette.wave2, height * waveShape2.base, waveShape2.amp, waveShape2.len, waveShape2.speed, time);
drawWave(palette.wave3, height * waveShape3.base, waveShape3.amp, waveShape3.len, waveShape3.speed, time);
applyFloatingMotion(time);
rafId = requestAnimationFrame(renderWaves);
}
function stopWaves() {
if (rafId) {
cancelAnimationFrame(rafId);
rafId = 0;
}
}
function drawStaticWaves() {
const theme = getTheme();
const palette = palettes[theme];
context.clearRect(0, 0, width, height);
drawWave(palette.wave1, height * waveShape1.base, waveShape1.amp, waveShape1.len, waveShape1.speed, 0);
drawWave(palette.wave2, height * waveShape2.base, waveShape2.amp, waveShape2.len, waveShape2.speed, 0);
drawWave(palette.wave3, height * waveShape3.base, waveShape3.amp, waveShape3.len, waveShape3.speed, 0);
applyFloatingMotion(0);
}
function startWaves() {
stopWaves();
resize();
if (prefersReducedMotion.matches) {
drawStaticWaves();
return;
}
rafId = requestAnimationFrame(renderWaves);
}
function computeTargetProgress() {
const stages = Array.isArray(joinData.stages) ? joinData.stages : [];
if (!stages.length) {
return 0;
}
const denominator = stages.length > 1 ? (stages.length - 1) : 1;
const currentIndex = getNumber(joinData.currentStageIndex);
// 有进行中阶段时,船严格对齐对应浮标节点。
if (currentIndex !== null && currentIndex >= 0) {
return clamp(currentIndex / denominator, 0, 1);
}
// 无进行中阶段时,停在最近已完成阶段节点。
let lastCompletedIndex = -1;
for (let i = 0; i < stages.length; i += 1) {
if (stages[i] && stages[i].status === 'completed') {
lastCompletedIndex = i;
}
}
if (lastCompletedIndex >= 0) {
return clamp(lastCompletedIndex / denominator, 0, 1);
}
return 0;
}
function getBuoyMarkerSvg() {
return `
<svg viewBox="0 0 40 52" role="img" aria-hidden="true" focusable="false">
<ellipse class="marker-buoy-ring" cx="20" cy="40.5" rx="12.8" ry="5.4"></ellipse>
<path class="marker-buoy-base" d="M13.2 21.2C13.2 16.8 16.8 13.2 21.2 13.2C25.6 13.2 29.2 16.8 29.2 21.2V31.8C29.2 35.1 26.5 37.8 23.2 37.8H19.2C15.9 37.8 13.2 35.1 13.2 31.8V21.2Z"></path>
<path class="marker-buoy-stripe" d="M13.2 23.6H29.2V28.1H13.2Z"></path>
<path class="marker-buoy-cap" d="M16.6 12.6H25.8V15.4H16.6Z"></path>
<circle class="marker-buoy-light" cx="21.2" cy="10.2" r="2.4"></circle>
<path class="marker-buoy-gloss" d="M17 18.4C17.6 16.9 19 15.9 20.5 15.9V35.1C18.4 35.1 16.7 33.4 16.7 31.3V19.2C16.7 18.9 16.8 18.6 17 18.4Z"></path>
</svg>
`;
}
function getLighthouseMarkerSvg() {
return `
<svg viewBox="0 0 48 66" role="img" aria-hidden="true" focusable="false">
<path class="marker-reef-rock" d="M6 59L12 52L20 55L27 49L35 52L42 48L45 59L6 59Z"></path>
<path class="marker-reef-highlight" d="M13 56L19 54L24 55L31 52L36 54L33 57L20 58L13 56Z"></path>
<path class="marker-lh-base" d="M15 50H33L30 57H18L15 50Z"></path>
<path class="marker-lh-tower" d="M18 50L21 15H27L30 50H18Z"></path>
<path class="marker-lh-top" d="M18 15H30L27.5 10H20.5L18 15Z"></path>
<path class="marker-lh-top" d="M16 20H32V23H16Z"></path>
<path class="marker-lh-beam marker-lh-beam-right" d="M32 18L46 15V22L32 20Z"></path>
<path class="marker-lh-beam marker-lh-beam-left" d="M16 18L2 15V22L16 20Z"></path>
</svg>
`;
}
function renderStageMarks() {
if (!waveMarks) {
return;
}
stageMarkers = [];
waveMarks.innerHTML = '';
const stages = Array.isArray(joinData.stages) ? joinData.stages : [];
if (!stages.length) {
return;
}
const currentIndex = getNumber(joinData.currentStageIndex);
const safeCurrentIndex = currentIndex === null ? -1 : Math.round(currentIndex);
const denominator = stages.length > 1 ? (stages.length - 1) : 1;
stages.forEach((stage, index) => {
const marker = document.createElement('span');
marker.className = 'join-wave-mark';
const progress = denominator > 0 ? (index / denominator) : 0;
marker.style.left = `${(progress * 100).toFixed(3)}%`;
const isLighthouse = index === stages.length - 1 || (stage && stage.key === 'public_notice');
marker.classList.add(isLighthouse ? 'is-lighthouse' : 'is-buoy');
if (index === safeCurrentIndex) {
marker.classList.add('is-active');
}
const icon = document.createElement('span');
icon.className = 'join-wave-mark-icon';
icon.innerHTML = isLighthouse ? getLighthouseMarkerSvg() : getBuoyMarkerSvg();
marker.appendChild(icon);
const beams = isLighthouse ? Array.from(icon.querySelectorAll('.marker-lh-beam')) : [];
waveMarks.appendChild(marker);
stageMarkers.push({
element: marker,
icon,
beams,
progress,
type: isLighthouse ? 'lighthouse' : 'buoy',
});
});
}
function stopBoatAnimations() {
if (!hasAnime) {
return;
}
const targets = [waveFill, waveBoat];
stageMarkers.forEach((marker) => {
if (marker.icon) {
targets.push(marker.icon);
}
if (Array.isArray(marker.beams) && marker.beams.length) {
marker.beams.forEach((beam) => targets.push(beam));
}
});
animeLib.remove(targets);
}
function startLighthouseBeacon() {
const allBeams = [];
const leftBeams = [];
const rightBeams = [];
stageMarkers.forEach((marker) => {
if (marker.type !== 'lighthouse' || !Array.isArray(marker.beams)) {
return;
}
marker.beams.forEach((beam) => {
allBeams.push(beam);
beam.style.opacity = '0.28';
if (beam.classList.contains('marker-lh-beam-left')) {
leftBeams.push(beam);
} else if (beam.classList.contains('marker-lh-beam-right')) {
rightBeams.push(beam);
}
});
});
if (!allBeams.length || !hasAnime || prefersReducedMotion.matches) {
return;
}
const duration = 3600;
const easing = 'easeInOutSine';
if (rightBeams.length) {
animeLib({
targets: rightBeams,
opacity: [0.2, 0.86, 0.2],
duration,
easing,
loop: true,
});
}
if (leftBeams.length) {
animeLib({
targets: leftBeams,
opacity: [0.2, 0.82, 0.2],
duration,
delay: duration / 2,
easing,
loop: true,
});
} else if (!rightBeams.length) {
animeLib({
targets: allBeams,
opacity: [0.2, 0.84, 0.2],
duration,
easing,
loop: true,
});
}
}
function setProgress(value) {
const safe = clamp(value, 0, 1);
displayedProgress = safe;
const percent = `${(safe * 100).toFixed(3)}%`;
if (waveFill) {
waveFill.style.width = percent;
}
if (waveBoat) {
waveBoat.style.left = percent;
}
}
function animateBoatToTarget() {
if (!waveBoat || !waveFill) {
return;
}
stopBoatAnimations();
if (!hasAnime || !shouldAnimateEntry()) {
setProgress(targetProgress);
startLighthouseBeacon();
return;
}
const state = { value: 0 };
setProgress(0);
animeLib({
targets: state,
value: targetProgress,
duration: 1750,
easing: 'easeOutCubic',
update: () => {
setProgress(state.value);
},
complete: () => {
setProgress(targetProgress);
startLighthouseBeacon();
},
});
animeLib({
targets: waveFill,
opacity: [0.55, 1],
duration: 1100,
easing: 'easeOutSine',
});
const activeIcon = waveMarks ? waveMarks.querySelector('.join-wave-mark.is-active .join-wave-mark-icon') : null;
if (activeIcon) {
animeLib({
targets: activeIcon,
scale: [1, 1.24, 1],
opacity: [0.82, 1, 0.92],
duration: 850,
delay: 980,
easing: 'easeOutSine',
});
}
}
function handleMotionPreferenceChange() {
targetProgress = computeTargetProgress();
setProgress(targetProgress);
stopBoatAnimations();
startWaves();
startLighthouseBeacon();
}
const observer = new MutationObserver(() => {
if (prefersReducedMotion.matches) {
drawStaticWaves();
}
});
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
if (typeof prefersReducedMotion.addEventListener === 'function') {
prefersReducedMotion.addEventListener('change', handleMotionPreferenceChange);
} else if (typeof prefersReducedMotion.addListener === 'function') {
prefersReducedMotion.addListener(handleMotionPreferenceChange);
}
window.addEventListener('resize', () => {
startWaves();
setProgress(targetProgress);
});
window.addEventListener('orientationchange', () => {
startWaves();
setProgress(targetProgress);
});
window.addEventListener('beforeunload', () => {
stopWaves();
stopBoatAnimations();
});
targetProgress = computeTargetProgress();
renderStageMarks();
startWaves();
animateBoatToTarget();
})();