- New reboot progress overlay: full-screen black with the screensaver's pulsing ring, rebooting → reconnecting → back-online → stalled stages, elapsed counter, auto-reload on health-check success, manual reload button at 3 min stall. Mirrors the existing update overlay. - Ring extracted from Screensaver.vue into a reusable ScreensaverRing component so the reboot overlay reuses the same animation. - default_mirrors() now puts the VPS as Server 1 (primary) and tx1138 as Server 2 — new nodes fetch manifests from VPS first; existing nodes keep whatever mirror order they've customized. - What's New entry prepended for v1.7.28-alpha. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
115 lines
3.8 KiB
Vue
115 lines
3.8 KiB
Vue
<template>
|
|
<div class="viz-ring" :class="sizeClass">
|
|
<div
|
|
v-for="(_, i) in segmentCount"
|
|
:key="i"
|
|
class="viz-segment"
|
|
:style="getSegmentStyle(i)"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
|
|
const props = withDefaults(defineProps<{
|
|
/** Visual size: 'default' matches the screensaver; 'compact' drops the
|
|
* min-width breakpoints (useful inside overlays on narrower canvases). */
|
|
size?: 'default' | 'compact'
|
|
/** Override segment count. Defaults to 48 (screensaver standard). */
|
|
segmentCount?: number
|
|
}>(), { size: 'default', segmentCount: 48 })
|
|
|
|
const sizeClass = computed(() => props.size === 'compact' ? 'viz-ring-compact' : 'viz-ring-default')
|
|
|
|
function getSegmentStyle(i: number) {
|
|
const deg = (i / props.segmentCount) * 360
|
|
return {
|
|
'--segment-index': i,
|
|
'--segment-deg': `${deg}deg`,
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.viz-ring {
|
|
position: relative;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.viz-ring-default {
|
|
width: 280px;
|
|
height: 280px;
|
|
--viz-radius: 140px;
|
|
}
|
|
@media (min-width: 640px) {
|
|
.viz-ring-default {
|
|
width: 360px;
|
|
height: 360px;
|
|
--viz-radius: 180px;
|
|
}
|
|
}
|
|
@media (min-width: 768px) {
|
|
.viz-ring-default {
|
|
width: 400px;
|
|
height: 400px;
|
|
--viz-radius: 200px;
|
|
}
|
|
}
|
|
|
|
.viz-ring-compact {
|
|
width: 240px;
|
|
height: 240px;
|
|
--viz-radius: 120px;
|
|
}
|
|
@media (min-width: 768px) {
|
|
.viz-ring-compact {
|
|
width: 320px;
|
|
height: 320px;
|
|
--viz-radius: 160px;
|
|
}
|
|
}
|
|
|
|
.viz-segment {
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 50%;
|
|
width: 4px;
|
|
height: 24px;
|
|
margin-left: -2px;
|
|
margin-top: -12px;
|
|
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.1));
|
|
border-radius: 2px;
|
|
transform-origin: center center;
|
|
transform: rotate(var(--segment-deg)) translateY(calc(-1 * var(--viz-radius)));
|
|
animation: segment-pulse 14s ease-in-out infinite;
|
|
animation-delay: calc(var(--segment-index) * 0.02s);
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.viz-segment {
|
|
height: 28px;
|
|
margin-top: -14px;
|
|
}
|
|
}
|
|
|
|
/* 5 normal loops (10s) then stronger longer expression (4s) — total 14s */
|
|
@keyframes segment-pulse {
|
|
0% { opacity: 0.3; transform: rotate(var(--segment-deg)) translateY(calc(-1 * var(--viz-radius))) scaleY(0.4); }
|
|
7.1% { opacity: 0.9; transform: rotate(var(--segment-deg)) translateY(calc(-1 * var(--viz-radius))) scaleY(1); }
|
|
14.3%{ opacity: 0.3; transform: rotate(var(--segment-deg)) translateY(calc(-1 * var(--viz-radius))) scaleY(0.4); }
|
|
21.4%{ opacity: 0.9; transform: rotate(var(--segment-deg)) translateY(calc(-1 * var(--viz-radius))) scaleY(1); }
|
|
28.6%{ opacity: 0.3; transform: rotate(var(--segment-deg)) translateY(calc(-1 * var(--viz-radius))) scaleY(0.4); }
|
|
35.7%{ opacity: 0.9; transform: rotate(var(--segment-deg)) translateY(calc(-1 * var(--viz-radius))) scaleY(1); }
|
|
42.9%{ opacity: 0.3; transform: rotate(var(--segment-deg)) translateY(calc(-1 * var(--viz-radius))) scaleY(0.4); }
|
|
50% { opacity: 0.9; transform: rotate(var(--segment-deg)) translateY(calc(-1 * var(--viz-radius))) scaleY(1); }
|
|
57.1%{ opacity: 0.3; transform: rotate(var(--segment-deg)) translateY(calc(-1 * var(--viz-radius))) scaleY(0.4); }
|
|
64.3%{ opacity: 0.9; transform: rotate(var(--segment-deg)) translateY(calc(-1 * var(--viz-radius))) scaleY(1); }
|
|
71.4%{ opacity: 0.3; transform: rotate(var(--segment-deg)) translateY(calc(-1 * var(--viz-radius))) scaleY(0.4); }
|
|
78.6%{ opacity: 1; transform: rotate(var(--segment-deg)) translateY(calc(-1 * var(--viz-radius))) scaleY(1.5); }
|
|
85.7%{ opacity: 1; transform: rotate(var(--segment-deg)) translateY(calc(-1 * var(--viz-radius))) scaleY(1.5); }
|
|
92.9%{ opacity: 0.3; transform: rotate(var(--segment-deg)) translateY(calc(-1 * var(--viz-radius))) scaleY(0.4); }
|
|
100% { opacity: 0.3; transform: rotate(var(--segment-deg)) translateY(calc(-1 * var(--viz-radius))) scaleY(0.4); }
|
|
}
|
|
</style>
|