harb/landing/src/components/CountdownTimer.vue
johba 2acb619a11 feat(landing): add strict ESLint + Prettier with pre-commit hooks (#50)
- Install ESLint 9 with flat config, TypeScript, Vue plugins
- Configure Prettier (140 char, 2-space indent, single quotes)
- Add pre-commit hooks via husky + lint-staged for auto-fix
- Rename components to multi-word (Countdown → CountdownTimer, etc.)
- Add explicit TypeScript prop/emit interfaces
- Remove all console.log statements
- Fix all ESLint violations and type errors
- Verify type-check, build, and HMR working

resolves #43

Co-authored-by: johba <johba@harb.eth>
Reviewed-on: https://codeberg.org/johba/harb/pulls/50
2025-10-03 13:19:20 +02:00

127 lines
3.2 KiB
Vue

<template>
<div class="countdown">
<div class="countdown__title">Time until launch</div>
<div class="countdown__time">
<slot :days="differenceDays" :hours="differenceHours" :minutes="differenceMinutes">
<div>{{ differenceDays }}d {{ differenceHours }}h {{ differenceMinutes }}m {{ differenceSeconds }}s</div>
</slot>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue';
const seconds = ref(0);
let _timerId: ReturnType<typeof setTimeout>;
interface Props {
modelValue: number;
end: Date;
}
interface Emits {
(e: 'update:modelValue', value: number): void;
}
const emit = defineEmits<Emits>();
const props = withDefaults(defineProps<Props>(), {
modelValue: 0,
});
const differenceDays = computed(() => {
if (seconds.value <= 0) {
return 0;
}
return Math.floor(seconds.value / (3600 * 24));
});
//tage abziehen
const differenceHours = computed(() => {
if (seconds.value <= 0) {
return 0;
}
return Math.floor((seconds.value - differenceDays.value * 3600 * 24) / 3600);
});
//tage + stunden abziehen
const differenceMinutes = computed(() => {
if (seconds.value <= 0) {
return 0;
}
return Math.floor((seconds.value - differenceDays.value * 3600 * 24 - differenceHours.value * 3600) / 60);
});
const differenceSeconds = computed(() => {
if (seconds.value <= 0) {
return 0;
}
return seconds.value - differenceDays.value * 86400 - differenceHours.value * 3600 - differenceMinutes.value * 60;
});
onMounted(() => {
seconds.value = Math.round((props.end.getTime() - new Date().getTime()) / 1000);
if (seconds.value > 0) {
_timerId = setInterval(() => {
seconds.value--;
emit('update:modelValue', seconds.value);
if (seconds.value <= 0) {
clearInterval(_timerId);
}
}, 1000);
}
emit('update:modelValue', seconds.value);
});
onUnmounted(() => clearInterval(_timerId));
</script>
<style lang="sass">
@keyframes borderAnimation
0%
background-position: 0% 50%
50%
background-position: 100% 50%
100%
background-position: 0% 50%
.countdown
z-index: 10
align-self: center
font-weight: bold
width: calc( 100vw - 48px)
@media (min-width: 768px)
width: 500px
.countdown__title
text-align: left
font-size: 18px
font-weight: 400
margin: 8px
@media (min-width: 768px)
font-size: 20px
.countdown__time
box-sizing: border-box
padding: 12px 24px
font-size: 32px
position: relative
border-radius: 12px
@media (min-width: 768px)
font-size: 40px
&>div
font-family: 'orbitron', sans-serif
font-weight: 400
&::before
content: ""
position: absolute
inset: 0
border-radius: inherit
/* Erbt die Abrundung */
padding: 2px
/* Dicke des Borders */
background: linear-gradient(10deg, #BF62B2, black)
-webkit-mask: linear-gradient(white, white) content-box, linear-gradient(white, white)
-webkit-mask-composite: destination-out
mask-composite: exclude
z-index: -1
</style>