refactor css and add UI Standards class

This commit is contained in:
pa
2026-03-09 16:28:05 +09:00
parent 493713b79a
commit 3dadc84179
13 changed files with 141 additions and 129 deletions

View File

@@ -34,7 +34,6 @@
import VRCXUpdateDialog from './components/dialogs/VRCXUpdateDialog.vue';
import '@/styles/globals.css';
import '@/app.css';
console.log(`isLinux: ${LINUX}`);

View File

@@ -1,32 +0,0 @@
html {
overflow: hidden;
}
.lucide.is-loading {
animation: rotating 2s linear infinite;
}
.x-container {
position: relative;
padding: 8px;
overflow: hidden auto;
box-sizing: border-box;
min-width: 0;
background: var(--background);
height: calc(100% - 20px);
margin: 8px 0 8px 0;
border-radius: var(--radius);
border: 1px solid var(--border);
}
.aside-collapsed .x-container {
margin-right: 8px;
}
html.dark .x-container {
background: var(--sidebar);
}
[data-sonner-toast] [data-content] [data-description] {
white-space: pre-line;
}

View File

@@ -10,3 +10,39 @@
src: local('Times New Roman');
unicode-range: U+2026;
}
:root {
--font-western-primary: 'Inter Variable';
--font-western:
'ellipsis-font', -apple-system, var(--font-western-primary), 'Segoe UI',
'Roboto', 'Ubuntu', 'Cantarell', 'DejaVu Sans', sans-serif;
--font-symbol: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
--font-fallback-cjk: sans-serif;
--font-primary-cjk:
'Noto Sans JP Variable', 'Noto Sans SC Variable',
'Noto Sans KR Variable', 'Noto Sans TC Variable';
}
:root[lang='zh-CN'] {
--font-primary-cjk:
'Noto Sans SC Variable', 'Noto Sans JP Variable',
'Noto Sans KR Variable', 'Noto Sans TC Variable';
}
:root[lang='ja'] {
--font-primary-cjk:
'Noto Sans JP Variable', 'Noto Sans KR Variable',
'Noto Sans TC Variable', 'Noto Sans SC Variable';
}
:root[lang='ko'] {
--font-primary-cjk:
'Noto Sans KR Variable', 'Noto Sans JP Variable',
'Noto Sans TC Variable', 'Noto Sans SC Variable';
}
:root[lang='zh-TW'] {
--font-primary-cjk:
'Noto Sans TC Variable', 'Noto Sans JP Variable',
'Noto Sans KR Variable', 'Noto Sans SC Variable';
}

View File

@@ -16,30 +16,6 @@
@custom-variant dark (&:is(.dark *));
:root[lang='zh-CN'] {
--font-primary-cjk:
'Noto Sans SC Variable', 'Noto Sans JP Variable',
'Noto Sans KR Variable', 'Noto Sans TC Variable';
}
:root[lang='ja'] {
--font-primary-cjk:
'Noto Sans JP Variable', 'Noto Sans KR Variable',
'Noto Sans TC Variable', 'Noto Sans SC Variable';
}
:root[lang='ko'] {
--font-primary-cjk:
'Noto Sans KR Variable', 'Noto Sans JP Variable',
'Noto Sans TC Variable', 'Noto Sans SC Variable';
}
:root[lang='zh-TW'] {
--font-primary-cjk:
'Noto Sans TC Variable', 'Noto Sans JP Variable',
'Noto Sans KR Variable', 'Noto Sans SC Variable';
}
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
@@ -95,16 +71,6 @@
--visibility-public: #22c55e;
--visibility-friends: #0ea5e9;
--visibility-private: #ef4444;
--font-western-primary: 'Inter Variable';
--font-western:
'ellipsis-font', -apple-system, var(--font-western-primary), 'Segoe UI',
'Roboto', 'Ubuntu', 'Cantarell', 'DejaVu Sans', sans-serif;
--font-symbol: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
--font-fallback-cjk: sans-serif;
--font-primary-cjk:
'Noto Sans JP Variable', 'Noto Sans SC Variable',
'Noto Sans KR Variable', 'Noto Sans TC Variable';
}
.dark {
@@ -202,19 +168,51 @@
--color-visibility-private: var(--visibility-private);
}
@layer base {
* {
@apply border-border outline-ring/50;
scrollbar-width: thin;
scrollbar-color: var(--border) transparent;
}
body {
@apply bg-background text-foreground;
font-family:
var(--font-western), var(--font-symbol), var(--font-primary-cjk),
var(--font-fallback-cjk);
margin: 0;
}
html {
overflow: hidden;
}
.lucide.is-loading {
animation: rotating 2s linear infinite;
}
.x-container {
position: relative;
padding: 8px;
overflow: hidden auto;
box-sizing: border-box;
min-width: 0;
background: var(--background);
height: calc(100% - 20px);
margin: 8px 0 8px 0;
border-radius: var(--radius);
border: 1px solid var(--border);
}
.aside-collapsed .x-container {
margin-right: 8px;
}
html.dark .x-container {
background: var(--sidebar);
}
[data-sonner-toast] [data-content] [data-description] {
white-space: pre-line;
}
* {
@apply border-border outline-ring/50;
scrollbar-width: thin;
scrollbar-color: var(--border) transparent;
}
body {
@apply bg-background text-foreground;
font-family:
var(--font-western), var(--font-symbol), var(--font-primary-cjk),
var(--font-fallback-cjk);
margin: 0;
}
::-webkit-scrollbar {
@@ -233,17 +231,15 @@
background-clip: content-box;
}
@layer utilities {
.scrollbar-hidden {
scrollbar-width: none;
-ms-overflow-style: none;
}
.scrollbar-hidden {
scrollbar-width: none;
-ms-overflow-style: none;
}
.scrollbar-hidden::-webkit-scrollbar {
width: 0;
height: 0;
display: none;
}
.scrollbar-hidden::-webkit-scrollbar {
width: 0;
height: 0;
display: none;
}
[data-slot='table-header'],
@@ -255,3 +251,31 @@
width: 1em;
height: 1em;
}
/* ==========================================================================
* VRCX UI Standards
* These are project-wide conventions.
* Use them when building new components.
* ==========================================================================*/
/* Hover Transitions
* Smooth hover state changes to avoid harsh flickering.
* x-hover-card — Cards & panels (0.2s, bg + shadow)
* x-hover-list — List items (0.15s, bg). Skip for high-density lists (e.g. friend sidebar).
* x-hover-icon — Icon buttons (0.1s, bg + color)
*/
.x-hover-card {
transition:
background-color 0.2s ease,
box-shadow 0.2s ease;
}
.x-hover-list {
transition: background-color 0.15s ease;
}
.x-hover-icon {
transition:
background-color 0.1s ease,
color 0.1s ease;
}

View File

@@ -51,7 +51,7 @@
v-for="group in favoriteAvatarGroups"
:key="group.key"
:class="[
'group-item hover:shadow-sm',
'group-item x-hover-card hover:shadow-sm',
`group-item--${group.visibility}`,
{ 'is-active': !hasSearchInput && isGroupActive('remote', group.key) }
]"
@@ -123,7 +123,7 @@
v-for="group in avatarGroupPlaceholders"
:key="group.key"
:class="[
'group-item hover:shadow-sm',
'group-item x-hover-card hover:shadow-sm',
'pointer-events-none opacity-70',
{ 'is-active': !hasSearchInput && isGroupActive('remote', group.key) }
]">
@@ -162,7 +162,7 @@
v-for="group in localAvatarFavoriteGroups"
:key="group"
:class="[
'group-item hover:shadow-sm',
'group-item x-hover-card hover:shadow-sm',
{ 'is-active': !hasSearchInput && isGroupActive('local', group) }
]"
@click="handleGroupClick('local', group)">
@@ -211,7 +211,7 @@
:content="t('view.favorite.avatars.local_favorites')">
<div
:class="[
'group-item hover:shadow-sm',
'group-item x-hover-card hover:shadow-sm',
'border-dashed flex items-center justify-center gap-2 text-sm',
{ 'opacity-50 cursor-not-allowed': !isLocalUserVrcPlusSupporter }
]"
@@ -253,7 +253,7 @@
<div class="flex flex-col gap-2">
<div
:class="[
'group-item hover:shadow-sm',
'group-item x-hover-card hover:shadow-sm',
{ 'is-active': !hasSearchInput && isGroupActive('history', historyGroupKey) }
]"
@click="handleGroupClick('history', historyGroupKey)">
@@ -314,7 +314,7 @@
<div
v-for="favorite in avatarFavoriteSearchResults"
:key="favorite.id"
class="favorites-search-card hover:shadow-sm"
class="favorites-search-card x-hover-card hover:shadow-sm"
@click="showAvatarDialog(favorite.id)">
<div class="favorites-search-card__content">
<div

View File

@@ -51,7 +51,7 @@
v-for="group in favoriteFriendGroups"
:key="group.key"
:class="[
'group-item hover:shadow-sm',
'group-item x-hover-card hover:shadow-sm',
`group-item--${group.visibility}`,
{ 'is-active': !hasSearchInput && isGroupActive('remote', group.key) }
]"
@@ -140,7 +140,7 @@
v-for="group in localFriendFavoriteGroups"
:key="group"
:class="[
'group-item hover:shadow-sm',
'group-item x-hover-card hover:shadow-sm',
{ 'is-active': !hasSearchInput && isGroupActive('local', group) }
]"
@click="handleGroupClick('local', group)">
@@ -184,7 +184,7 @@
</div>
<div
v-if="!isCreatingLocalGroup"
class="group-item hover:shadow-sm border-dashed flex items-center justify-center gap-2 text-sm"
class="group-item x-hover-card hover:shadow-sm border-dashed flex items-center justify-center gap-2 text-sm"
@click="startLocalGroupCreation">
<Plus />
<span>{{ t('view.favorite.worlds.new_group') }}</span>
@@ -289,7 +289,7 @@
<div
v-for="favorite in friendFavoriteSearchResults"
:key="favorite.id"
class="favorites-search-card hover:shadow-sm"
class="favorites-search-card x-hover-card hover:shadow-sm"
@click="showUserDialog(favorite.id)">
<div class="favorites-search-card__content">
<div class="favorites-search-card__avatar">

View File

@@ -51,7 +51,7 @@
v-for="group in favoriteWorldGroups"
:key="group.key"
:class="[
'group-item hover:shadow-sm',
'group-item x-hover-card hover:shadow-sm',
`group-item--${group.visibility}`,
{ 'is-active': !hasSearchInput && isGroupActive('remote', group.key) }
]"
@@ -123,7 +123,7 @@
v-for="group in worldGroupPlaceholders"
:key="group.key"
:class="[
'group-item hover:shadow-sm',
'group-item x-hover-card hover:shadow-sm',
'pointer-events-none opacity-70',
{ 'is-active': !hasSearchInput && isGroupActive('remote', group.key) }
]">
@@ -160,7 +160,7 @@
v-for="group in localWorldFavoriteGroups"
:key="group"
:class="[
'group-item hover:shadow-sm',
'group-item x-hover-card hover:shadow-sm',
{ 'is-active': !hasSearchInput && isGroupActive('local', group) }
]"
@click="handleGroupClick('local', group)">
@@ -204,7 +204,7 @@
</div>
<div
v-if="!isCreatingLocalGroup"
class="group-item hover:shadow-sm border-dashed flex items-center justify-center gap-2 text-sm"
class="group-item x-hover-card hover:shadow-sm border-dashed flex items-center justify-center gap-2 text-sm"
@click="startLocalGroupCreation">
<Plus />
<span>{{ t('view.favorite.worlds.new_group') }}</span>
@@ -262,7 +262,7 @@
<div
v-for="favorite in worldFavoriteSearchResults"
:key="favorite.id"
class="favorites-search-card hover:shadow-sm"
class="favorites-search-card x-hover-card hover:shadow-sm"
@click="showWorldDialog(favorite.id)">
<div class="favorites-search-card__content">
<div

View File

@@ -12,7 +12,6 @@
padding: var(--favorites-card-padding-y, 8px)
var(--favorites-card-padding-x, 8px);
cursor: pointer;
transition: background-color 0.15s ease;
width: 100%;
min-width: var(--favorites-card-min-width, 240px);
max-width: var(--favorites-card-target-width, 320px);
@@ -20,6 +19,7 @@
.favorites-search-card:hover {
background-color: var(--accent);
box-shadow: var(--shadow-sm);
}
.favorites-search-card__content {

View File

@@ -22,11 +22,11 @@
border: 1px solid var(--border);
padding: 8px;
cursor: pointer;
transition: background-color 0.15s ease;
}
.group-item:hover {
background-color: var(--accent);
box-shadow: var(--shadow-sm);
}
.group-item--public {

View File

@@ -1,7 +1,10 @@
<template>
<ContextMenu>
<ContextMenuTrigger as-child>
<Card class="friend-card p-0 gap-0" :style="cardStyle" @click="showUserDialog(friend.id)">
<Card
class="friend-card x-hover-card p-0 gap-0 hover:bg-accent hover:shadow-sm"
:style="cardStyle"
@click="showUserDialog(friend.id)">
<div class="friend-card__header">
<div>
<Avatar
@@ -241,16 +244,10 @@
display: grid;
gap: calc(14px * var(--card-scale) * var(--card-spacing));
border-radius: var(--radius-lg);
transition: background-color 0.15s ease;
width: 100%;
max-width: var(--friend-card-target-width, 220px);
min-width: var(--friend-card-min-width, 220px);
box-sizing: border-box;
&:hover {
background-color: var(--accent);
box-shadow: var(--shadow-sm);
}
}
.friend-card__header {

View File

@@ -5,7 +5,7 @@
<ContextMenuTrigger as="div">
<div class="avatar-card-wrapper rounded-lg" @click="$emit('click')">
<Card
class="avatar-card flex flex-col gap-0 p-0 cursor-pointer overflow-hidden rounded-lg relative transition-colors hover:bg-accent hover:shadow-md"
class="avatar-card x-hover-card flex flex-col gap-0 p-0 cursor-pointer overflow-hidden rounded-lg relative hover:bg-accent hover:shadow-sm"
:class="isActive ? 'border-2 border-primary' : 'border border-border/50'">
<div class="w-full aspect-5/2 overflow-hidden bg-muted relative">
<img

View File

@@ -12,7 +12,7 @@
<span class="category-title">{{ t('view.tools.pictures.header') }}</span>
</div>
<div class="tools-grid" v-show="!categoryCollapsed['image']">
<Card class="tool-card p-0 gap-0">
<Card class="tool-card x-hover-card p-0 gap-0 hover:bg-accent hover:shadow-sm">
<div class="tool-content text-2xl" @click="showScreenshotMetadataPage">
<div class="tool-icon">
<Camera />
@@ -554,18 +554,12 @@
}
.tool-card {
transition: background-color 0.15s ease;
position: relative;
overflow: visible;
border-radius: var(--radius-lg);
cursor: pointer;
width: 100%;
&:hover {
background-color: var(--accent);
box-shadow: var(--shadow-sm);
}
.tool-content {
display: flex;
align-items: center;

View File

@@ -2,7 +2,7 @@
<Popover :open="eventPopoverOpen">
<PopoverTrigger as-child>
<Card
class="event-card p-0 gap-0"
class="event-card x-hover-card p-0 gap-0 hover:bg-accent hover:shadow-sm"
:class="cardClass"
@mouseenter="openEventPopover"
@mouseleave="scheduleCloseEventPopover">
@@ -275,18 +275,12 @@
<style scoped>
.event-card {
transition: background-color 0.15s ease;
position: relative;
overflow: visible;
border-radius: var(--radius-lg);
width: 100%;
}
.event-card:hover {
background-color: var(--accent);
box-shadow: var(--shadow-sm);
}
.event-card.grouped-card {
margin-bottom: 0;
}