<script lang="ts">
import { onMount } from "svelte";
import { resolve } from "$app/paths";
import { STATUS_LABEL, type Status } from "#lib/status.ts";
let status = $state<Status>("unknown");
onMount(() => {
const controller = new AbortController();
fetch(resolve("/_internal/status"), { signal: controller.signal })
.then((res) => (res.ok ? res.json() : { status: "unknown" }))
.then((body: { status: Status }) => (status = body.status))
.catch(() => {});
return () => controller.abort();
});
</script>
<span
class="dot"
data-status={status}
role="img"
aria-label={STATUS_LABEL[status]}
title={STATUS_LABEL[status]}
></span>
<style>
.dot {
--dot: var(--color-foreground-placeholder);
position: relative;
display: inline-block;
flex: none;
width: 0.5rem;
height: 0.5rem;
margin-inline-start: 0.4em;
vertical-align: middle;
border-radius: 9999px;
background: var(--dot);
}
.dot[data-status="up"] {
--dot: var(--color-foreground-success);
}
.dot[data-status="degraded"],
.dot[data-status="maintenance"] {
--dot: var(--color-foreground-warning);
}
.dot[data-status="down"] {
--dot: var(--color-foreground-danger);
}
.dot:not([data-status="unknown"])::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
background: var(--dot);
animation: ping 2s cubic-bezier(0, 0, 0.2, 1) infinite;
}
@keyframes ping {
75%,
100% {
transform: scale(2.4);
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.dot::after {
animation: none;
display: none;
}
}
</style>