feat(stage-ui): update colors of card
Co-authored-by: 藍+85CD <50108258+kwaa@users.noreply.github.com>
This commit is contained in:
@@ -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>
|
||||
@@ -44,6 +44,7 @@ export default defineConfig({
|
||||
cuteen: 'Sniglet',
|
||||
jura: 'Jura',
|
||||
gugi: 'Gugi',
|
||||
quicksand: 'Quicksand',
|
||||
},
|
||||
timeouts: {
|
||||
warning: 5000,
|
||||
|
||||
Reference in New Issue
Block a user