feat(stage-ui): update colors of card

Co-authored-by: 藍+85CD <50108258+kwaa@users.noreply.github.com>
This commit is contained in:
Neko Ayaka
2025-04-13 13:15:59 +08:00
co-authored by 藍+85CD
parent b15a66d1af
commit 455dea4484
5 changed files with 317 additions and 34 deletions
@@ -1,6 +1,18 @@
<script setup lang="ts">
import { ref } from 'vue'
import CharacterCardColorControls from '../../../stories/CharacterCardColorControls.vue'
import CharacterCard from './CharacterCard.vue'
import characterImage from './relu.png'
const primaryColor = ref('#f0f9fe')
const secondaryColor = ref('#fef1c9')
const backgroundColor = ref('#ffffff')
const textColor = ref('#607aa1')
const textShadowColor = ref('#f9fdff')
const textShadowSize = ref('0px 0px 1px')
const descriptionTextColor = ref('#474c57')
const subtitleTextColor = ref('#6693b6')
</script>
<template>
@@ -11,22 +23,59 @@ import characterImage from './relu.png'
>
<template #controls>
<ThemeColorsHueControl />
<CharacterCardColorControls
v-model:primary-color="primaryColor"
v-model:secondary-color="secondaryColor"
v-model:background-color="backgroundColor"
v-model:text-color="textColor"
v-model:text-shadow-color="textShadowColor"
v-model:text-shadow-size="textShadowSize"
v-model:description-text-color="descriptionTextColor"
v-model:subtitle-text-color="subtitleTextColor"
/>
</template>
<Variant
id="global"
title="CharacterCard"
id="default"
title="Character Card"
>
<div p-20>
<div p-4>
<CharacterCard
title="ReLU"
subtitle="ReLU-chan"
description="First awaken cyber consciousness in Project AIRI, quite a bit of a prankster"
:image="characterImage"
primary-color="6, 182, 212"
secondary-color="14, 165, 233"
background-color="23, 23, 23"
text-color="6, 182, 212"
:primary-color="primaryColor"
:secondary-color="secondaryColor"
:background-color="backgroundColor"
:text-color="textColor"
:text-shadow-color="textShadowColor"
:text-shadow-size="textShadowSize"
:description-text-color="descriptionTextColor"
:subtitle-text-color="subtitleTextColor"
:card-height="150"
:card-width="90"
/>
</div>
</Variant>
<Variant
id="default-without-image"
title="Character Card without image"
>
<div p-4>
<CharacterCard
title="ReLU"
subtitle="ReLU-chan"
description="First awaken cyber consciousness in Project AIRI, quite a bit of a prankster"
:primary-color="primaryColor"
:secondary-color="secondaryColor"
:background-color="backgroundColor"
:text-color="textColor"
:text-shadow-color="textShadowColor"
:text-shadow-size="textShadowSize"
:description-text-color="descriptionTextColor"
:subtitle-text-color="subtitleTextColor"
:card-height="150"
:card-width="90"
/>
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { filterBrightness, formatRgb, rgb } from 'culori'
import { computed } from 'vue'
const props = withDefaults(defineProps<{
@@ -6,7 +7,7 @@ const props = withDefaults(defineProps<{
subtitle: string
backgroundLabel?: string
description: string
image: string
image?: string
cardHeight?: number
cardWidth?: number
primaryColor: string
@@ -14,13 +15,23 @@ const props = withDefaults(defineProps<{
secondaryColor: string
backgroundColor?: string
dividerColor?: string
dividerOpacity?: number
textColor: string
textShadowColor?: string
textShadowSize?: string
descriptionTextColor?: string
subtitleTextColor?: string
barWidth?: number
barcodeCount?: number
}>(), {
backgroundLabel: 'Character',
cardHeight: 130,
cardWidth: 80,
dividerOpacity: 0.3,
textShadowColor: '#71717a',
textShadowSize: '0px 0px 3px',
descriptionTextColor: '#a1a1aa',
subtitleTextColor: '#ffffff',
barcodeCount: 8,
barWidth: 10,
})
@@ -37,32 +48,57 @@ const barcodeLines = computed(() => {
return lines
})
const dimmer50 = filterBrightness(0.5)
// CSS variables for the component
const cssVars = computed(() => {
const backgroundLabelColor = dimmer50(rgb(props.primaryColor)!)
backgroundLabelColor.alpha = 0.1
const backgroundMaskBackgroundColor = rgb(props.backgroundColor)!
backgroundMaskBackgroundColor.alpha = 1
const backgroundDotGridColor = dimmer50(rgb(props.primaryColor)!)
backgroundDotGridColor.alpha = 0.1
const backgroundColor = rgb(props.backgroundColor)!
backgroundColor.alpha = 1
const dividerColor = dimmer50(rgb(props.primaryColor)!)
dividerColor.alpha = props.dividerOpacity
return {
'--card-height': `${props.cardHeight * 4}px`,
'--card-width': `${props.cardWidth * 4}px`,
'--bar-width': `${props.barWidth * 4}px`,
// RGB values for colors
'--primary-rgb': props.primaryColor,
'--primary-dark-rgb': props.primaryColorDark || props.primaryColor,
'--secondary-rgb': props.secondaryColor,
'--background-rgb': props.backgroundColor,
'--text-rgb': props.textColor,
'--primary-rgb': `${formatRgb(props.primaryColor)}`,
'--secondary-rgb': `${formatRgb(props.secondaryColor)}`,
'--text-rgb': `${formatRgb(props.textColor)}`,
'--text-shadow-color': props.textShadowColor,
'--text-shadow-size': props.textShadowSize,
'--description-text-color': props.descriptionTextColor,
'--subtitle-text-color': props.subtitleTextColor,
// Opacity values
'--divider-opacity': props.dividerColor,
'--divider-color': `${formatRgb(dividerColor)}`,
'--divider-opacity': `${dividerColor.alpha}`,
// Components
'--background-color': `${formatRgb(backgroundColor)}`,
'--background-dot-grid-color': `${formatRgb(backgroundDotGridColor)}`,
'--background-mask-color': `${formatRgb(backgroundMaskBackgroundColor)}`,
'--background-label-color': `${formatRgb(backgroundLabelColor)}`,
}
})
</script>
<template>
<div
class="character-card [&_.gradient-image_img]:hover:translate-y-2 [&_.gradient-image_img]:hover:scale-102"
class="character-card [&_.card-cover_img]:hover:translate-y-2 [&_.card-cover_img]:hover:scale-102"
:style="cssVars"
w-fit
cursor-pointer
w-fit cursor-pointer rounded-xl shadow-sm
>
<div flex="~ items-center justify-center" w-fit bg="inherit">
<div class="card-container">
@@ -79,7 +115,7 @@ const cssVars = computed(() => {
rounded-l-xl
>
<span
class="background-label mt-2"
class="background-label mt-2" font-quicksand
leading="[1]"
absolute
inline-block
@@ -91,7 +127,7 @@ const cssVars = computed(() => {
</div>
<!-- Title section -->
<div font-jura relative z-3 pb-4 pl-4 pt-2>
<div relative z-4 text-white text-shadow-md text-shadow-color-neutral-500>
<div relative z-4 class="subtitle-text">
<!-- Subtitle section -->
<div text-base font-semibold>
<span text-base>{{ subtitle }}</span>
@@ -105,7 +141,6 @@ const cssVars = computed(() => {
font-italic
text-stroke-1
:style="{ paintOrder: 'stroke fill' }"
text-shadow="[0px_0px_1px]"
leading="[0.75]"
>
<span>{{ title }}</span>
@@ -121,14 +156,17 @@ const cssVars = computed(() => {
/>
</div>
<!-- Cover section -->
<div absolute bottom-0 left-0 right-0 top--20 z-2 overflow-hidden class="gradient-image">
<img :src="image" h="300" object-cover mix-blend-screen transition-all duration-500 ease-in-out>
<div absolute bottom-0 left-0 right-0 top--20 z-2 overflow-hidden class="card-cover">
<slot name="cover">
<img v-if="image" :src="image" h="300" object-cover mix-blend-screen transition-all duration-500 ease-in-out>
<div v-else h="300" w-full object-cover mix-blend-screen transition-all duration-500 ease-in-out />
</slot>
</div>
</div>
<!-- Divider section -->
<div mx-5 h-0.5 rounded-full class="divider" />
<!-- Info section -->
<div class="description" max-h="[4.5rem]" mx-5 mb-4 mt-2 h-full text="neutral-400">
<div class="description" max-h="[4.5rem]" font-quicksand mx-5 mb-4 mt-2 h-full>
<div h-full text-base>
{{ description }}
</div>
@@ -168,35 +206,44 @@ const cssVars = computed(() => {
}
.main-container {
background-color: rgb(var(--background-rgb));
background-color: var(--background-color);
}
.background-section-container {
background-image: linear-gradient(to bottom, rgb(var(--primary-rgb)) 0%, rgba(var(--primary-rgb), 1) 50%);
background-image: linear-gradient(to bottom, var(--primary-rgb) 0%, var(--primary-rgb) 50%);
}
.background-label {
font-size: 4rem;
color: rgba(var(--primary-dark-rgb), 0.1);
color: var(--background-label-color);
}
.title-gradient {
background-image: linear-gradient(to bottom, transparent 0%, rgb(var(--background-rgb)) 100%);
background-image: linear-gradient(to bottom, transparent 0%, var(--background-mask-color) 100%);
}
.subtitle-text {
color: var(--subtitle-text-color);
text-shadow: var(--text-shadow-size) var(--text-shadow-color);
}
.title-text {
color: rgb(var(--text-rgb));
text-shadow: 0px 0px 3px rgb(var(--text-rgb));
-webkit-text-stroke: 1px rgb(var(--text-rgb));
color: var(--text-rgb);
text-shadow: var(--text-shadow-size) var(--text-rgb);
-webkit-text-stroke: 1px var(--text-rgb);
}
.description {
color: var(--description-text-color);
}
.divider {
background-color: rgba(var(--primary-dark-rgb), var(--divider-opacity));
background-color: var(--divider-color);
}
.bar-container {
width: var(--bar-width);
background-image: linear-gradient(to bottom, rgb(var(--secondary-rgb)) 0%, rgb(var(--primary-rgb)) 100%);
background-image: linear-gradient(to bottom, var(--secondary-rgb) 0%, var(--primary-rgb) 100%);
}
.background-section-container::after {
@@ -206,13 +253,13 @@ const cssVars = computed(() => {
z-index: 10;
width: 100%;
height: 100%;
background-image: radial-gradient(circle at 2px 2px, rgba(var(--primary-dark-rgb), 0.1) 2px, transparent 0);
background-image: radial-gradient(circle at 2px 2px, var(--background-dot-grid-color) 2px, transparent 0);
background-size: 10px 10px;
transition: all 0.4s ease-in-out;
mask-image: linear-gradient(164deg, white 0%, transparent 24%);
}
.gradient-image {
.card-cover {
mask-image: linear-gradient(11deg, rgba(0, 0, 0, 0) 5%, rgba(0, 0, 0, 1) 13%),
radial-gradient(#fff 57%, #ffffff00 86%);
}
@@ -0,0 +1,144 @@
<script setup lang="ts">
import ColorPickerControl from './ColorPickerControl.vue'
const DEFAULT_COLORS: Record<string, string> = {
primaryColor: '#f0f9fe',
secondaryColor: '#fef1c9',
backgroundColor: '#ffffff',
textColor: '#607aa1',
textShadowColor: '#f9fdff',
textShadowSize: '0px 0px 1px',
descriptionTextColor: '#474c57',
subtitleTextColor: '#6693b6',
}
const DEFAULT_TEXT_SHADOW_SIZE = '0px 0px 3px'
const primaryColor = defineModel<string>('primaryColor', { required: true })
const secondaryColor = defineModel<string>('secondaryColor', { required: true })
const backgroundColor = defineModel<string>('backgroundColor', { required: true })
const textColor = defineModel<string>('textColor', { required: true })
const textShadowColor = defineModel<string>('textShadowColor', { required: true })
const textShadowSize = defineModel<string>('textShadowSize', { required: true })
const descriptionTextColor = defineModel<string>('descriptionTextColor', { required: true })
const subtitleTextColor = defineModel<string>('subtitleTextColor', { required: true })
// Text shadow size options
const textShadowSizeOptions = [
{ value: '0px 0px 1px', label: 'Small' },
{ value: '0px 0px 3px', label: 'Medium' },
{ value: '0px 0px 5px', label: 'Large' },
{ value: '1px 1px 3px', label: 'Offset' },
]
function resetToDefault() {
primaryColor.value = DEFAULT_COLORS.primaryColor
secondaryColor.value = DEFAULT_COLORS.secondaryColor
backgroundColor.value = DEFAULT_COLORS.backgroundColor
textColor.value = DEFAULT_COLORS.textColor
textShadowColor.value = DEFAULT_COLORS.textShadowColor
textShadowSize.value = DEFAULT_TEXT_SHADOW_SIZE
descriptionTextColor.value = DEFAULT_COLORS.descriptionTextColor
subtitleTextColor.value = DEFAULT_COLORS.subtitleTextColor
}
</script>
<template>
<div px-4 py-2 flex="~ col">
<div mb-4>
<h4 mb-2 font-medium>
Card Colors
</h4>
<ColorPickerControl
v-model:color="primaryColor"
label="Primary Color"
name="primaryColor"
/>
<ColorPickerControl
v-model:color="secondaryColor"
label="Secondary Color"
name="secondaryColor"
/>
<ColorPickerControl
v-model:color="backgroundColor"
label="Background Color"
name="backgroundColor"
/>
<h4 mb-2 mt-4 font-medium>
Text Colors
</h4>
<ColorPickerControl
v-model:color="textColor"
label="Title Text Color"
name="textColor"
/>
<ColorPickerControl
v-model:color="subtitleTextColor"
label="Subtitle Text Color"
name="subtitleTextColor"
/>
<ColorPickerControl
v-model:color="descriptionTextColor"
label="Description Text Color"
name="descriptionTextColor"
/>
<h4 mb-2 mt-4 font-medium>
Text Shadow
</h4>
<ColorPickerControl
v-model:color="textShadowColor"
label="Shadow Color"
name="textShadowColor"
/>
<div mb-3>
<div mb-1>
<label text-sm>Shadow Size</label>
</div>
<select
v-model="textShadowSize"
class="shadow-size-select"
w-full
rounded-lg
border="1 solid neutral-300 dark:neutral-700"
p-2
bg="white dark:neutral-800"
text="neutral-800 dark:neutral-200"
>
<option
v-for="option in textShadowSizeOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</option>
</select>
</div>
</div>
<button
class="rounded-md px-2 py-1 text-xs transition-colors"
bg="neutral-200 dark:neutral-800 hover:neutral-200 dark:hover:neutral-700"
text="neutral-700 dark:neutral-300"
@click="resetToDefault"
>
Reset to Default
</button>
</div>
</template>
<style scoped>
.shadow-size-select:focus {
outline: none;
border-color: rgb(var(--color-primary-500));
}
</style>
@@ -0,0 +1,42 @@
<script setup lang="ts">
interface Props {
label: string
name: string
}
const props = defineProps<Props>()
const color = defineModel<string>('color', { required: true })
</script>
<template>
<div class="color-picker-control" mb-3>
<div mb-1 flex items-center gap-2>
<label text-sm>{{ props.label }}</label>
</div>
<input
v-model="color"
type="color"
class="color-input"
w-full px-0.5
>
</div>
</template>
<style scoped>
.color-input {
--at-apply: appearance-none rounded-lg h-8 w-full cursor-pointer;
border: 1px solid rgb(var(--color-neutral-300));
}
.htw-dark .color-input {
border-color: rgb(var(--color-neutral-700));
}
.color-input::-webkit-color-swatch {
--at-apply: rounded-md border-0;
}
.color-input::-moz-color-swatch {
--at-apply: rounded-md border-0;
}
</style>
+1
View File
@@ -44,6 +44,7 @@ export default defineConfig({
cuteen: 'Sniglet',
jura: 'Jura',
gugi: 'Gugi',
quicksand: 'Quicksand',
},
timeouts: {
warning: 5000,