archy/neode-ui/src/components/ScreensaverRing.vue
Dorian 79ae14a127 release(v1.7.28-alpha): reboot progress overlay + VPS default primary
- 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>
2026-04-21 15:06:37 -04:00

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>