Futuristic Card card
A clean, practical sci-fi style information container.
I finally made the component of my dreams! ◝(≧∀≦)◟
My college major was in mechatronics and automatic control, so I've always had a soft spot for sci-fi style components.
My closest past works were:
Due to limited technical skills back then, the interfaces were very basic -- barely touching the sci-fi aesthetic. (́⊙◞౪◟⊙‵)
After all these years, I've finally realized my vision through web technologies! ✧⁑。٩(ˊᗜˋ*)و✧⁕。
Usage Examples
Basic Usage
View example source code
<template>
<div class="w-full flex flex-col gap-10 border border-gray-200 rounded-xl p-6 pb-16">
<base-checkbox
v-model="visible"
:label="t('show')"
class="border rounded p-4"
/>
<div class="h-full flex justify-center">
<card-futuristic
v-on-click-outside="() => toggleSelect(false)"
:visible
:selected
class="font-orbitron"
@click="toggleSelect(true)"
>
<div class="flex flex-col gap-4">
<div class="text-xl font-bold">
Title
</div>
<div>
The best things in life are actually really expensive.
</div>
</div>
</card-futuristic>
</div>
</div>
</template>
<script setup lang="ts">
import { vOnClickOutside } from '@vueuse/components'
import { useToggle } from '@vueuse/core'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseCheckbox from '../../base-checkbox.vue'
import CardFuturistic from '../card-futuristic.vue'
const visible = ref(true)
const [selected, toggleSelect] = useToggle(false)
const { t } = useI18n()
</script>
<style lang="sass">
</style>Mix Parts
Combine different parts to create a variety of styles.
View example source code
<template>
<div class="w-full flex flex-col gap-4 border border-gray-200 rounded-xl p-6">
<div class="flex flex-wrap items-center justify-center gap-10">
<card-futuristic
v-for="item, i in list"
:key="i"
v-on-click-outside="() => item.selected = false"
v-bind="item"
class="font-orbitron"
@click="item.selected = true"
>
<div class="flex flex-col">
<div
v-if="item.title"
:class="item.titleClass"
>
{{ item.title }}
</div>
<div
v-if="item.text"
:class="item.textClass"
>
{{ item.text }}
</div>
</div>
</card-futuristic>
</div>
<base-checkbox
v-model="visible"
:label="t('show')"
class="checkbox sticky bottom-4 z-10 border rounded p-4 md:relative md:bottom-0"
/>
</div>
</template>
<script setup lang="ts">
import type { Writable } from 'type-fest'
import type { ExtractComponentProps } from '../../../types'
import { vOnClickOutside } from '@vueuse/components'
import { promiseTimeout } from '@vueuse/core'
import { map, pipe } from 'remeda'
import { ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseCheckbox from '../../base-checkbox.vue'
import CardFuturistic from '../card-futuristic.vue'
type CardProp = Writable<ExtractComponentProps<typeof CardFuturistic>> & {
title?: string;
titleClass?: string;
text?: string;
textClass?: string;
}
const list = ref(pipe(
[
{
title: 'COD-00',
titleClass: 'text-xl font-bold',
text: 'FUTURISTIC CARD',
bg: {
type: 'halftone',
color: 'light-dark(#FAFAFA, #2b2b2b)',
},
border: {
type: 'specific',
selectedColor: '#FFF',
strokeWidth: 1,
side: {
t: {},
l: {},
b: {},
r: {},
},
},
animeSequence: {
visible: {
border: { duration: 400 },
},
},
},
{
title: 'CARD-01',
titleClass: 'text-xl font-bold',
text: 'QUOTE CORNER',
corner: { type: 'quote' },
content: {
type: 'scale',
class: 'p-4',
},
bg: {
type: 'halftone',
color: 'light-dark(#FAFAFA, #2b2b2b)',
},
border: null,
animeSequence: {
visible: {
content: { delay: 200 },
bg: { delay: 400 },
},
},
},
{
title: 'CARD-02',
titleClass: 'text-xl font-bold ',
text: 'SIDE BORDER CLIP',
textClass: '',
corner: null,
content: {
type: 'clip',
class: 'p-4',
},
bg: {
type: 'typical',
margin: '0',
color: 'light-dark(#FAFAFA, #2b2b2b)',
},
border: { type: 'side' },
animeSequence: {
normal: {
border: { delay: 0 },
},
visible: {
border: { delay: 0 },
bg: { delay: 200 },
content: { delay: 300 },
},
hidden: {
border: { delay: 300 },
bg: { delay: 0 },
content: { delay: 0 },
},
},
},
{
title: 'CARD-03',
titleClass: 'text-xl font-bold text-white',
text: 'SOLID BACKGROUND',
textClass: 'text-white',
corner: null,
bg: {
type: 'solid',
selectedColor: '#444',
},
content: {
type: 'typical',
class: 'p-4 pl-6',
},
border: { type: 'specific' },
animeSequence: {
visible: {
border: { delay: 400 },
},
},
},
{
title: 'ERROR',
titleClass: 'text-2xl font-bold text-[#ba2507] ',
text: 'FISH OVERWEIGHT',
textClass: 'text-[#ba2507]',
corner: null,
bg: {
type: 'typical',
margin: '4px 0px',
color: 'light-dark(#ffe8e8, #451717)',
},
content: {
type: 'typical',
class: 'py-4 px-8',
},
border: {
type: 'specific',
color: '#ba2507',
selectedColor: '#f07860',
strokeWidth: 2,
side: {
t: {},
b: {},
},
},
animeSequence: {
normal: {
border: { delay: 0 },
},
visible: {
border: { delay: 0 },
bg: { delay: 400 },
content: { delay: 500 },
},
hidden: {
border: { delay: 300 },
bg: { delay: 0 },
content: { delay: 0 },
},
// null 表示停用動畫
selected: { content: null },
hover: { content: null },
},
},
{
title: 'COD',
corner: {
type: 'square',
size: 2,
},
bg: null,
content: {
type: 'typical',
class: 'p-1 px-2',
},
border: {
type: 'typical',
color: '#BBB',
},
animeSequence: {
visible: {
corner: { delay: 0 },
border: { delay: 400 },
content: { delay: 500 },
},
hidden: {
corner: { delay: 400 },
border: { delay: 0 },
content: { delay: 0 },
},
},
},
] as CardProp[],
map((data) => ({
...data,
visible: false,
selected: false,
})),
))
const visible = ref(false)
watch(visible, async (value) => {
for (const data of list.value) {
data.visible = value
await promiseTimeout(100)
}
})
const { t } = useI18n()
</script>
<style lang="sass">
.checkbox
background: light-dark(#FFF, #222)
</style>Text Animation
Use the text component to create sci-fi text effects.
Futuristic Card
This component is composed of border, bg, corner, content and ornament sub-components, with the card parent component orchestrating animations.
Sub-components can be freely combined to produce N kinds of interesting style designs.
Uses Vue-bound SVG attributes for rendering and anime.js for animations.
View example source code
<template>
<div class="w-full flex flex-col gap-4 border border-gray-200 rounded-xl p-6">
<base-checkbox
v-model="visible"
:label="t('show')"
class="sticky top-20 z-10 border rounded p-4 md:relative md:top-0"
/>
<div class="flex flex-wrap items-center justify-center gap-20">
<card-futuristic
v-for="item, i in list"
:key="i"
v-on-click-outside="() => item.selected = false"
v-bind="item"
class="font-orbitron"
@click="item.selected = true"
>
<div class="flex flex-col gap-2">
<card-futuristic-text
v-if="item.title"
:text="item.title"
:class="item.titleClass"
class="!m-0"
/>
<card-futuristic-text
v-for="line, j in item.textList"
:key="j"
:text="line"
:class="item.textClass"
class="!m-0"
/>
</div>
</card-futuristic>
</div>
</div>
</template>
<script setup lang="ts">
import type { Writable } from 'type-fest'
import type { ExtractComponentProps } from '../../../types'
import { vOnClickOutside } from '@vueuse/components'
import { promiseTimeout } from '@vueuse/core'
import { map, pipe } from 'remeda'
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseCheckbox from '../../base-checkbox.vue'
import CardFuturisticText from '../card-futuristic-text.vue'
import CardFuturistic from '../card-futuristic.vue'
type CardProp = Writable<ExtractComponentProps<typeof CardFuturistic>> & {
title?: string;
titleClass?: string;
textList?: string[];
textClass?: string;
}
const { t } = useI18n()
const list = computed(() => pipe(
[
{
title: 'Futuristic Card',
titleClass: 'text-xl font-bold',
textList: [
t('textLine1'),
t('textLine2'),
t('textLine3'),
],
bg: {
type: 'halftone',
color: '#0001',
dotSize: '1px',
size: '10px',
},
corner: {
type: 'quote',
strokeWidth: 8,
},
border: {
type: 'typical',
color: '#777',
},
animeSequence: {
visible: {
corner: { delay: 0 },
bg: { delay: 400 },
border: { delay: 400 },
content: { delay: 500 },
},
hidden: {
corner: { delay: 700 },
bg: { delay: 200 },
border: { delay: 100 },
content: { delay: 100 },
},
hover: {
corner: null,
border: null,
content: null,
},
selected: {
corner: null,
border: null,
content: null,
},
},
},
] as CardProp[],
map((data) => ({
...data,
visible: false,
selected: false,
})),
))
const visible = ref(false)
watch(visible, async (value) => {
for (const data of list.value) {
data.visible = value
await promiseTimeout(100)
}
})
</script>
<style lang="sass">
</style>How It Works
The component is composed of sub-components: border, bg, corner, and content, with the card parent component orchestrating the animations.
Sub-components can be freely combined to produce N kinds of interesting style designs.
The sub-components use Vue-bound SVG attributes for rendering and anime.js for animations.
Source Code
API
Props
interface Props {
/** 動畫序列,可自定義動畫參數 */
animeSequence?: Partial<AnimeSequence>;
visible?: boolean;
selected?: boolean;
/** 為空則自動處理,有提供則以參數數值為主 */
hover?: boolean;
/** null 表示不使用此元件 */
border?: BorderParam | null;
bg?: BgParam | null;
corner?: CornerParam | null;
content?: ContentParam | null;
}Methods
defineExpose({
/** 執行特定 part 狀態動畫 */
execute,
})Slots
interface Slots {
default?: () => unknown;
}