init #8

Merged
imelon merged 1 commits from codex/frontend-ui-design-migration into master 2026-08-05 08:45:42 +08:00
49 changed files with 2382 additions and 296 deletions
@@ -0,0 +1,47 @@
# Design migration matrix
`design/src-web` is the implementation source for component structure, CSS, assets and visual
interaction states. Migration means adapting that implementation to Vue and Reka UI; it does not
mean recreating the effect from screenshots.
## Rules
- Port the existing DOM anatomy, CSS values and raster assets before optimizing them.
- Keep pixel-accurate atomic dimensions, but expose component width and density through props or
CSS custom properties.
- Move reusable colors and metrics into semantic `--ui-*` tokens after visual parity is established.
- Keep page data, routing and stores outside `src/components/ui`.
- Delete a legacy `calc-*` component only after all of its call sites have migrated.
## Matrix
| Design source | Current target | Status | Migration notes |
| ----------------------- | --------------------------------------------------------- | ----------- | -------------------------------------------------------- |
| `foundations`, `shared` | `style/tokens`, `style/themes`, shared UI contract | Migrated | Semantic color and overridable geometry contracts |
| `button` | `ui/button` | Migrated | Preserved three-slice states; exposed density and width |
| `input` | `ui/input`, `ui/number-field` | Migrated | Removed forced inline width; normalized text metrics |
| `form-controls` | `ui/checkbox`, `ui/radio-group`, `ui/switch`, `ui/rating` | Migrated | Tokenized state colors and aligned label/control anatomy |
| `select` | `ui/select` | Migrated | Preserved popup anatomy and Reka keyboard behavior |
| `tabs` | `ui/tabs` | Migrated | Preserved list-owned animated marker |
| `slider` | `ui/slider` | Migrated | Preserved the Reka coordinate system |
| `data-table` | `ui/data-table` | Migrated | Ported hover, selected and density behavior |
| `dialog` | `ui/dialog` | Migrated | Preserved the focused title effect and focus management |
| `menu` | `ui/dropdown-menu` | Migrated | Ported source menu spacing and state tokens |
| `rarity` | `ui/item-slot` | Migrated | Retained rarity presentation as data-semantic tokens |
| `autocomplete` | `ui/autocomplete` | Migrated | Reka Combobox composition with source popup anatomy |
| `navigation` | `ui/navigation` | Migrated | Selectable navigation primitive with source assets |
| `scrollbar` | `ui/scroll-area` | Migrated | Reusable Reka viewport/scrollbar composition |
| `data-section` | `ui/data-section` | Migrated | Collapsible section and vertically centered data rows |
| `shop-item-row` | `ui/shop-item-row` | Migrated | Ported source row structure and selectable states |
| `complex-dialog` | `ui/dialog` + migrated primitives | Composition | Kept domain-independent by composing existing primitives |
| `settings` | `ui/settings` | Migrated | Responsive settings group, section and row primitives |
## Legacy replacement order
Deferred: this component-library pass does not change business call sites.
1. Shared form controls and overlays used by DNF Helper and import dialogs.
2. Forge controls (`select`, `checkbox`, `slider`, tabs and panels).
3. Equipment dictionary filters, tree, cascader and pagination.
4. Skill tree, skill detail and VP controls.
5. Result tables and remaining domain-specific components.
@@ -0,0 +1,234 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import {
ComboboxAnchor,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxItemIndicator,
ComboboxPortal,
ComboboxRoot,
ComboboxTrigger,
ComboboxViewport,
} from 'reka-ui'
import selectArrow from '../../../../../design/src-web/shared/img/sprite_interface.NPK/windowcommon_2x.img/0.PNG'
import { cn } from '@/lib/utils'
export interface UiAutocompleteOption {
value: string
label: string
disabled?: boolean
tone?: 'normal' | 'rare' | 'artifact' | 'legend' | 'epic'
}
const props = withDefaults(
defineProps<{
class?: HTMLAttributes['class']
options: UiAutocompleteOption[]
placeholder?: string
emptyText?: string
disabled?: boolean
}>(),
{ emptyText: '没有匹配项' },
)
const model = defineModel<string>()
const open = ref(false)
function displayValue(value: string | undefined) {
return props.options.find((option) => option.value === value)?.label ?? value ?? ''
}
</script>
<template>
<ComboboxRoot
v-model="model"
v-model:open="open"
:disabled="disabled"
open-on-click
open-on-focus
:class="cn('ui-autocomplete', props.class)"
>
<ComboboxAnchor class="ui-autocomplete__anchor">
<ComboboxInput
class="ui-autocomplete__input"
:display-value="displayValue"
:placeholder="placeholder"
/>
<ComboboxTrigger class="ui-autocomplete__trigger" aria-label="展开候选项">
<img :src="selectArrow" alt="" />
</ComboboxTrigger>
</ComboboxAnchor>
<ComboboxPortal>
<ComboboxContent class="ui-autocomplete__content" position="popper" :side-offset="2">
<ComboboxViewport class="ui-autocomplete__viewport">
<ComboboxEmpty class="ui-autocomplete__empty">{{ emptyText }}</ComboboxEmpty>
<ComboboxItem
v-for="option in options"
:key="option.value"
:value="option.value"
:disabled="option.disabled"
class="ui-autocomplete__item"
:class="option.tone && `quality-${option.tone}`"
>
<span>{{ option.label }}</span>
<ComboboxItemIndicator class="ui-autocomplete__indicator">◆</ComboboxItemIndicator>
</ComboboxItem>
</ComboboxViewport>
</ComboboxContent>
</ComboboxPortal>
</ComboboxRoot>
</template>
<style lang="scss">
.ui-autocomplete {
display: block;
min-width: 0;
}
.ui-autocomplete__anchor {
box-sizing: border-box;
display: grid;
width: 100%;
height: var(--ui-control-height-default);
grid-template-columns: minmax(0, 1fr) var(--ui-select-icon-inline-size);
align-items: center;
gap: var(--ui-autocomplete-trigger-gap);
padding: 0 4px 0 var(--ui-field-padding-inline);
border: 1px solid var(--ui-input);
background: var(--ui-field-background);
box-shadow: inset 0 0 0 1px var(--ui-field-inset);
&:focus-within,
&[data-state='open'] {
border-color: var(--ui-ring);
box-shadow:
inset 0 0 0 1px var(--ui-field-focus-inset),
0 0 0 1px var(--ui-field-focus-edge);
}
}
.ui-autocomplete__input {
min-width: 0;
height: 100%;
padding: 0;
border: 0;
outline: 0;
background: transparent;
color: var(--ui-primary-foreground);
font: inherit;
font-size: var(--ui-control-font-size-default);
line-height: var(--ui-control-line-height);
&::placeholder {
color: var(--ui-muted-foreground);
}
}
.ui-autocomplete__trigger {
display: inline-flex;
width: var(--ui-select-icon-inline-size);
height: var(--ui-select-icon-block-size);
align-items: center;
justify-content: center;
padding: 0;
border: 1px solid var(--ui-control-border);
outline: none;
background: var(--ui-control-background);
box-shadow: 0 0 0 1px var(--ui-select-icon-shadow);
cursor: pointer;
&:hover,
&:focus-visible {
border-color: var(--ui-control-border-active);
background: var(--ui-control-background-hover);
}
&:active img {
transform: translateY(1px);
}
img {
width: var(--ui-select-glyph-inline-size);
height: var(--ui-select-glyph-block-size);
image-rendering: pixelated;
}
}
.ui-autocomplete__content {
z-index: 80;
min-width: var(--reka-combobox-trigger-width);
max-height: min(
var(--ui-autocomplete-max-block-size),
var(--reka-combobox-content-available-height)
);
overflow: hidden;
border: 1px solid var(--ui-popup-border);
background: var(--ui-popup-background);
box-shadow: 0 8px 18px var(--ui-popup-shadow);
color: var(--ui-popover-foreground);
}
.ui-autocomplete__viewport {
max-height: inherit;
overflow-y: auto;
}
.ui-autocomplete__item,
.ui-autocomplete__empty {
box-sizing: border-box;
display: flex;
height: var(--ui-option-height);
align-items: center;
justify-content: space-between;
padding: 0 var(--ui-field-padding-inline);
border-bottom: 1px solid var(--ui-option-divider);
outline: none;
color: var(--ui-primary-foreground);
font-size: var(--ui-control-font-size-default);
line-height: var(--ui-control-line-height);
}
.ui-autocomplete__item {
cursor: pointer;
&[data-highlighted] {
outline: 1px solid var(--ui-ring);
outline-offset: -2px;
background: var(--ui-option-highlight-background);
color: var(--ui-option-highlight-foreground);
}
&[data-state='checked'] {
background: var(--ui-disclosure-row-selected);
color: var(--ui-selected);
}
&[data-disabled] {
pointer-events: none;
color: var(--ui-control-disabled-foreground);
}
}
.ui-autocomplete__empty {
color: var(--ui-muted-foreground);
}
.ui-autocomplete__indicator {
font-size: var(--ui-autocomplete-indicator-font-size);
}
.quality-rare {
color: var(--dcalc-quality-rare);
}
.quality-artifact {
color: var(--dcalc-quality-artifact);
}
.quality-legend {
color: var(--dcalc-quality-legend);
}
.quality-epic {
color: var(--dcalc-quality-epic);
}
</style>
@@ -0,0 +1,2 @@
export { default as UiAutocomplete } from './UiAutocomplete.vue'
export type { UiAutocompleteOption } from './UiAutocomplete.vue'
@@ -1,20 +1,17 @@
import { cva, type VariantProps } from 'class-variance-authority'
export const badgeVariants = cva(
'inline-flex h-[18px] items-center rounded-none border px-[7px] text-[10px] font-normal leading-none',
{
variants: {
variant: {
default: 'border-border bg-primary text-primary-foreground',
secondary: 'border-border bg-secondary text-secondary-foreground',
outline: 'border-border bg-transparent text-foreground',
destructive: 'border-destructive bg-destructive text-destructive-foreground',
},
},
defaultVariants: {
variant: 'default',
export const badgeVariants = cva('ui-badge', {
variants: {
variant: {
default: 'border-border bg-primary text-primary-foreground',
secondary: 'border-border bg-secondary text-secondary-foreground',
outline: 'border-border bg-transparent text-foreground',
destructive: 'border-destructive bg-destructive text-destructive-foreground',
},
},
)
defaultVariants: {
variant: 'default',
},
})
export type BadgeVariants = VariantProps<typeof badgeVariants>
@@ -1,7 +1,7 @@
import { cva, type VariantProps } from 'class-variance-authority'
export const buttonVariants = cva(
'ui-button inline-flex items-center justify-center gap-2 whitespace-nowrap outline-none transition-[color,background,border-color,transform] disabled:pointer-events-none [&_[data-icon]]:pointer-events-none [&_[data-icon]]:shrink-0',
'ui-button inline-flex items-center justify-center whitespace-nowrap text-center leading-none outline-none transition-[color,background,border-color,transform] disabled:pointer-events-none [&_[data-icon]]:pointer-events-none [&_[data-icon]]:shrink-0',
{
variants: {
variant: {
@@ -12,10 +12,10 @@ export const buttonVariants = cva(
ghost: 'ui-button--ghost',
},
size: {
default: 'h-[30px] min-w-[110px] px-[14px] text-[13px]',
sm: 'h-6 min-w-20 px-2.5 text-xs',
lg: 'h-11 min-w-[168px] px-5 text-base',
icon: 'size-[30px] min-w-[30px] p-0',
default: 'ui-button--size-default',
sm: 'ui-button--size-compact',
lg: 'ui-button--size-large',
icon: 'ui-button--size-icon',
},
},
defaultVariants: {
@@ -25,7 +25,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
v-bind="forwarded"
:class="
cn(
'ui-checkbox peer inline-flex size-4 shrink-0 items-center justify-center border outline-none disabled:cursor-not-allowed',
'ui-checkbox peer inline-flex shrink-0 items-center justify-center border outline-none disabled:cursor-not-allowed',
props.class,
)
"
@@ -36,27 +36,29 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<style scoped>
.ui-checkbox {
--check-edge-top: #635036;
--check-edge-bottom: #3c2e1b;
--check-top: #242424;
--check-bottom: #0f0f0f;
--check-edge-top: var(--ui-check-edge-top);
--check-edge-bottom: var(--ui-check-edge-bottom);
--check-top: var(--ui-check-surface-top);
--check-bottom: var(--ui-check-surface-bottom);
position: relative;
width: var(--ui-checkbox-size);
height: var(--ui-checkbox-size);
border-color: transparent;
border-radius: 0;
background:
linear-gradient(180deg, var(--check-top), var(--check-bottom)) padding-box,
linear-gradient(180deg, var(--check-edge-top), var(--check-edge-bottom)) border-box;
box-shadow:
0 0 0 1px rgb(0 0 0 / 30%),
inset 0 1px 0 rgb(255 255 255 / 2.5%);
0 0 0 1px var(--ui-check-outer-shadow),
inset 0 1px 0 var(--ui-control-inner-highlight);
color: var(--ui-primary-foreground);
}
.ui-checkbox:hover:not(:disabled) {
--check-edge-top: #bf9d6a;
--check-edge-bottom: #947853;
--check-top: #3a3a3a;
--check-bottom: #181818;
--check-edge-top: var(--ui-check-hover-edge-top);
--check-edge-bottom: var(--ui-check-hover-edge-bottom);
--check-top: var(--ui-check-hover-surface-top);
--check-bottom: var(--ui-check-hover-surface-bottom);
}
.ui-checkbox:focus-visible {
@@ -69,19 +71,19 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
.ui-checkbox__indicator {
position: absolute;
width: 18px;
height: 14px;
background: linear-gradient(180deg, #dfb142, #eac75f);
width: var(--ui-checkbox-mark-inline-size);
height: var(--ui-checkbox-mark-block-size);
background: linear-gradient(180deg, var(--ui-check-mark-top), var(--ui-check-mark-bottom));
clip-path: polygon(0 42%, 7% 34%, 39% 66%, 94% 4%, 100% 10%, 39% 82%);
filter: drop-shadow(0 0 1px rgb(230 192 86 / 45%));
filter: drop-shadow(0 0 1px var(--ui-check-mark-shadow));
animation: check-in 70ms ease-out;
}
.ui-checkbox:disabled {
--check-edge-top: #414141;
--check-edge-bottom: #252525;
--check-top: #1f1f1f;
--check-bottom: #0d0d0d;
--check-edge-top: var(--ui-check-disabled-edge-top);
--check-edge-bottom: var(--ui-check-disabled-edge-bottom);
--check-top: var(--ui-check-disabled-surface-top);
--check-bottom: var(--ui-check-disabled-surface-bottom);
}
@keyframes check-in {
from {
@@ -0,0 +1,52 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = withDefaults(
defineProps<{
class?: HTMLAttributes['class']
depth?: number
selected?: boolean
disabled?: boolean
}>(),
{ depth: 0 },
)
</script>
<template>
<div
:class="cn('ui-data-row', selected && 'is-selected', disabled && 'is-disabled', props.class)"
:style="{ '--ui-data-row-depth': props.depth }"
>
<slot />
</div>
</template>
<style scoped>
.ui-data-row {
display: flex;
height: var(--ui-disclosure-row-block-size);
min-width: 0;
align-items: center;
gap: var(--ui-disclosure-row-gap);
padding-left: calc(var(--ui-data-row-depth) * var(--ui-disclosure-depth-step));
border-bottom: 1px solid var(--ui-disclosure-row-divider);
color: var(--ui-disclosure-row-foreground);
line-height: var(--ui-control-line-height);
}
.ui-data-row:focus-within {
outline: 1px solid var(--ui-ring);
outline-offset: -2px;
background: var(--ui-disclosure-row-focus);
}
.ui-data-row.is-selected {
background: var(--ui-disclosure-row-selected);
color: var(--ui-disclosure-row-selected-foreground);
}
.ui-data-row.is-disabled {
color: var(--ui-control-disabled-foreground);
}
</style>
@@ -0,0 +1,102 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { CollapsibleContent, CollapsibleRoot, CollapsibleTrigger } from 'reka-ui'
import expandedIcon from '../../../../../design/src-web/data-section/img/sprite_interface.NPK/windowcommon.chn.img/565.PNG'
import collapsedIcon from '../../../../../design/src-web/data-section/img/sprite_interface.NPK/windowcommon.chn.img/561.PNG'
import { cn } from '@/lib/utils'
const props = withDefaults(
defineProps<{
class?: HTMLAttributes['class']
title: string
disabled?: boolean
expandedLabel?: string
collapsedLabel?: string
}>(),
{ expandedLabel: '已展开', collapsedLabel: '未展开' },
)
const open = defineModel<boolean>('open', { default: true })
</script>
<template>
<CollapsibleRoot
v-model:open="open"
:disabled="disabled"
:class="cn('ui-data-section', props.class)"
>
<CollapsibleTrigger class="ui-data-section__trigger">
<img :src="open ? expandedIcon : collapsedIcon" alt="" aria-hidden="true" />
<span class="ui-data-section__title">{{ title }}</span>
<span class="ui-data-section__state">{{ open ? expandedLabel : collapsedLabel }}</span>
</CollapsibleTrigger>
<CollapsibleContent class="ui-data-section__content">
<slot />
</CollapsibleContent>
</CollapsibleRoot>
</template>
<style scoped lang="scss">
.ui-data-section {
border: 1px solid var(--ui-disclosure-border);
background: var(--ui-disclosure-background);
color: var(--ui-secondary-foreground);
&[data-state='closed'] .ui-data-section__trigger {
border-bottom-color: transparent;
}
&[data-disabled] {
opacity: 0.48;
}
}
.ui-data-section__trigger {
box-sizing: border-box;
display: flex;
width: 100%;
min-height: var(--ui-disclosure-header-min-block-size);
align-items: center;
gap: var(--ui-control-gap);
padding: 0 var(--ui-field-padding-inline);
border: 0;
border-bottom: 1px solid var(--ui-disclosure-divider);
outline: none;
background: var(--ui-disclosure-header-background);
color: inherit;
font: inherit;
line-height: var(--ui-control-line-height);
text-align: left;
cursor: pointer;
&:focus-visible {
outline: 1px solid var(--ui-ring);
outline-offset: -2px;
}
&:active > * {
transform: translateY(1px);
}
img {
width: var(--ui-disclosure-icon-size);
height: var(--ui-disclosure-icon-size);
object-fit: contain;
image-rendering: pixelated;
}
}
.ui-data-section__title {
min-width: 0;
flex: 1;
}
.ui-data-section__state {
color: var(--ui-disclosure-state);
font-size: var(--ui-disclosure-state-font-size);
}
.ui-data-section__content {
padding: var(--ui-disclosure-content-padding-block) var(--ui-field-padding-inline);
}
</style>
@@ -0,0 +1,2 @@
export { default as UiDataRow } from './UiDataRow.vue'
export { default as UiDataSection } from './UiDataSection.vue'
@@ -75,26 +75,26 @@ const gridTemplate = computed(() => props.columns.map((column) => column.width ?
.ui-data-table {
overflow-x: auto;
overflow-y: hidden;
border: 1px solid #4a3c26;
border: 1px solid var(--ui-table-border);
border-radius: 0;
background: #030403;
color: #d2c78f;
background: var(--ui-table-background);
color: var(--ui-table-foreground);
}
.ui-data-table__row {
display: grid;
width: 100%;
min-height: 38px;
min-height: var(--ui-table-row-min-block-size);
align-items: center;
padding: 0;
border: 0;
border-bottom: 1px solid #2c2b26;
border-bottom: 1px solid var(--ui-table-row-divider);
border-radius: 0;
background: transparent;
color: inherit;
font: 12px/1 inherit;
text-align: left;
min-width: 560px;
min-width: var(--ui-table-scroll-inline-size);
}
button.ui-data-table__row {
@@ -108,43 +108,45 @@ button.ui-data-table__row:nth-child(odd) {
background: var(--ui-row-background);
}
button.ui-data-table__row:hover {
background: linear-gradient(90deg, #132b43, #0b1824 58%, #070a0d);
outline: 1px solid #3b91bd;
background: var(--ui-table-row-hover);
outline: 1px solid var(--ui-table-row-hover-outline);
outline-offset: -1px;
}
button.ui-data-table__row.is-selected {
background: linear-gradient(90deg, #352b11, #161207 60%, #090a08);
outline: 1px solid #d6ad37;
background: var(--ui-table-row-selected);
outline: 1px solid var(--ui-table-row-selected-outline);
outline-offset: -1px;
box-shadow: inset 0 0 0 1px #6d5218;
box-shadow: inset 0 0 0 1px var(--ui-table-row-selected-inset);
}
.ui-data-table__row > * {
box-sizing: border-box;
display: flex;
min-width: 0;
height: 100%;
align-items: center;
padding: 0 9px;
overflow: hidden;
border-right: 1px solid #292720;
border-right: 1px solid var(--ui-table-cell-divider);
text-overflow: ellipsis;
white-space: nowrap;
}
.ui-data-table__header {
min-height: 30px;
height: 30px;
min-height: var(--ui-table-header-block-size);
height: var(--ui-table-header-block-size);
column-gap: 1px;
border-bottom: 0;
background: #030403;
background: var(--ui-table-background);
}
.ui-data-table__header > * {
display: flex;
align-items: center;
border: 1px solid #070806;
border: 1px solid var(--ui-table-header-border);
background: var(--ui-table-header);
box-shadow:
inset 0 0 0 1px #4a3c26,
inset 0 1px 0 #65502a;
inset 0 0 0 1px var(--ui-table-header-inset),
inset 0 1px 0 var(--ui-table-header-highlight);
color: var(--ui-table-header-foreground);
font: 11px/1 inherit;
}
@@ -24,7 +24,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
v-bind="forwarded"
:class="
cn(
'ui-dialog-content fixed left-1/2 top-1/2 z-50 grid w-[min(92vw,620px)] -translate-x-1/2 -translate-y-1/2 outline-none data-[state=closed]:animate-[dialog-out_120ms_ease-in] data-[state=open]:animate-[dialog-in_160ms_ease-out]',
'ui-dialog-content fixed left-1/2 top-1/2 z-50 grid -translate-x-1/2 -translate-y-1/2 outline-none data-[state=closed]:animate-[dialog-out_120ms_ease-in] data-[state=open]:animate-[dialog-in_160ms_ease-out]',
props.class,
)
"
@@ -35,13 +35,14 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<style scoped>
.ui-dialog-content {
width: min(var(--ui-dialog-viewport-inline-size), var(--ui-dialog-max-inline-size));
overflow: hidden;
border: 1px solid #6c5a37;
border: 1px solid var(--ui-dialog-border);
border-radius: 0;
background: #050606;
background: var(--ui-dialog-background);
box-shadow:
0 18px 38px rgb(0 0 0 / 80%),
inset 0 0 0 1px #0e1418;
0 18px 38px var(--ui-dialog-shadow),
inset 0 0 0 1px var(--ui-dialog-inset);
color: var(--ui-surface-foreground);
}
</style>
@@ -7,12 +7,12 @@
<style scoped>
.ui-dialog-footer {
display: flex;
min-height: 38px;
min-height: var(--ui-dialog-footer-min-block-size);
align-items: center;
justify-content: flex-end;
gap: 8px;
padding: 4px 8px;
border-top: 1px solid #332c22;
background: linear-gradient(#20201d, #11110f);
border-top: 1px solid var(--ui-dialog-footer-border);
background: var(--ui-dialog-footer-background);
}
</style>
@@ -52,19 +52,19 @@ onBeforeUnmount(() => clearInterval(timer))
isolation: isolate;
display: grid;
grid-template-columns: 1fr auto 1fr;
height: 28px;
height: var(--ui-dialog-header-block-size);
align-items: center;
padding: 0 8px;
overflow: hidden;
border-bottom: 1px solid #1d2735;
background: linear-gradient(#303b50, #263247 56%, #1d2839);
border-bottom: 1px solid var(--ui-dialog-titlebar-border);
background: var(--ui-dialog-titlebar-background);
color: var(--ui-titlebar-foreground);
text-shadow: 0 1px 1px #000;
text-shadow: 0 1px 1px var(--ui-dialog-titlebar-text-shadow);
}
.ui-dialog-header.is-focused {
background: linear-gradient(#6079a5, #435a80 55%, #30425f);
color: #f4f6f2;
background: var(--ui-dialog-titlebar-focused-background);
color: var(--ui-dialog-titlebar-focused-foreground);
}
.ui-dialog-header__effect {
@@ -72,7 +72,7 @@ onBeforeUnmount(() => clearInterval(timer))
z-index: 1;
top: 0;
left: 50%;
width: 1100px;
width: var(--ui-dialog-effect-inline-size);
height: 100%;
max-width: none;
object-fit: fill;
@@ -101,8 +101,8 @@ onBeforeUnmount(() => clearInterval(timer))
.ui-dialog-header__close {
appearance: none;
width: 20px;
height: 20px;
width: var(--ui-dialog-close-size);
height: var(--ui-dialog-close-size);
padding: 0;
display: inline-flex;
align-items: center;
@@ -114,8 +114,8 @@ onBeforeUnmount(() => clearInterval(timer))
}
.ui-dialog-header__close img {
width: 10px;
height: 9px;
width: var(--ui-dialog-close-glyph-inline-size);
height: var(--ui-dialog-close-glyph-block-size);
filter: brightness(1);
}
@@ -124,7 +124,7 @@ onBeforeUnmount(() => clearInterval(timer))
filter: brightness(1.28);
}
.ui-dialog-header__close:focus-visible {
outline: 1px solid rgb(225 204 135 / 72%);
outline: 1px solid var(--ui-dialog-close-ring);
outline-offset: -2px;
}
.ui-dialog-header__close:active img {
@@ -11,10 +11,16 @@ const delegatedProps = computed(() => {
</script>
<template>
<DialogTitle
v-bind="delegatedProps"
:class="cn('text-[13px] font-normal text-[#f0f2ef]', props.class)"
>
<DialogTitle v-bind="delegatedProps" :class="cn('ui-dialog-title', props.class)">
<slot />
</DialogTitle>
</template>
<style scoped>
.ui-dialog-title {
color: var(--ui-titlebar-foreground);
font-size: var(--ui-control-font-size-default);
font-weight: 400;
line-height: var(--ui-control-line-height);
}
</style>
@@ -37,12 +37,12 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
.ui-dropdown-content {
position: relative;
z-index: 80;
min-width: 190px;
min-width: var(--ui-menu-min-inline-size);
padding: 3px 3px 4px;
border: 1px solid rgb(19 17 13 / 92%);
border: 1px solid var(--ui-menu-border);
border-radius: 0;
background: rgb(48 46 38 / 72%);
box-shadow: 0 10px 20px rgb(0 0 0 / 78%);
background: var(--ui-menu-background);
box-shadow: 0 10px 20px var(--ui-menu-shadow);
backdrop-filter: blur(1px);
color: var(--ui-popover-foreground);
transform-origin: var(--reka-dropdown-menu-content-transform-origin);
@@ -31,21 +31,23 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<style scoped>
.ui-dropdown-item {
display: flex;
height: 28px;
height: var(--ui-menu-item-block-size);
align-items: center;
padding: 0 7px;
border: 1px solid transparent;
outline: none;
color: var(--ui-primary-foreground);
font-size: var(--ui-control-font-size-default);
line-height: var(--ui-control-line-height);
cursor: pointer;
user-select: none;
}
.ui-dropdown-item[data-highlighted] {
border-color: #000;
background: rgb(15 14 12 / 28%);
color: #ffefad;
box-shadow: inset 0 0 0 1px rgb(132 113 76 / 13%);
border-color: var(--ui-menu-highlight-border);
background: var(--ui-menu-highlight-background);
color: var(--ui-menu-highlight-foreground);
box-shadow: inset 0 0 0 1px var(--ui-menu-highlight-inset);
}
.ui-dropdown-item[data-disabled] {
@@ -54,6 +56,6 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
}
.ui-dropdown-item--danger {
color: #eb7c79;
color: var(--ui-menu-danger);
}
</style>
@@ -25,15 +25,18 @@ const model = defineModel<string | number>()
.ui-input {
box-sizing: border-box;
width: 100%;
height: 30px;
padding: 0 9px;
min-width: 0;
height: var(--ui-control-height-default);
padding: 0 var(--ui-field-padding-inline);
border: 1px solid var(--ui-input);
border-radius: 0;
outline: none;
background: linear-gradient(#0b0c0d, #000100);
box-shadow: inset 0 0 0 1px #050505;
background: var(--ui-field-background);
box-shadow: inset 0 0 0 1px var(--ui-field-inset);
color: var(--ui-foreground);
font: inherit;
font-size: var(--ui-control-font-size-default);
line-height: var(--ui-control-line-height);
}
.ui-input::placeholder {
@@ -44,14 +47,14 @@ const model = defineModel<string | number>()
.ui-input:focus-visible {
border-color: var(--ui-ring);
box-shadow:
inset 0 0 0 1px #071722,
0 0 0 1px #24617c;
inset 0 0 0 1px var(--ui-field-focus-inset),
0 0 0 1px var(--ui-field-focus-edge);
}
.ui-input:disabled {
cursor: not-allowed;
border-color: var(--ui-control-disabled-border);
background: #101110;
background: var(--ui-field-disabled-background);
color: var(--ui-control-disabled-foreground);
}
</style>
@@ -42,18 +42,18 @@ const frames: Record<Rarity, string> = {
.ui-item-slot {
position: relative;
display: flex;
width: 40px;
height: 40px;
width: var(--ui-item-slot-size);
height: var(--ui-item-slot-size);
align-items: center;
justify-content: center;
border: 0;
background: #030404;
background: var(--ui-item-slot-background);
}
.ui-item-slot__item,
.ui-item-slot__frame {
width: 28px;
height: 28px;
width: var(--ui-item-image-size);
height: var(--ui-item-image-size);
image-rendering: pixelated;
}
@@ -64,7 +64,7 @@ const frames: Record<Rarity, string> = {
.ui-item-slot__frame {
position: absolute;
z-index: 2;
inset: 5px;
inset: var(--ui-item-frame-inset);
}
.ui-item-slot.is-emphasized::before,
@@ -76,22 +76,22 @@ const frames: Record<Rarity, string> = {
.ui-item-slot.is-emphasized::before {
z-index: 0;
inset: 4px;
border: 1px solid #ffa800;
border: 1px solid var(--ui-item-slot-emphasis);
box-shadow:
0 0 2px 1px rgb(255 168 0 / 88%),
0 0 5px 4px rgb(255 168 0 / 46%),
inset 0 0 2px rgb(255 168 0 / 70%);
0 0 2px 1px var(--ui-item-slot-emphasis-shadow-strong),
0 0 5px 4px var(--ui-item-slot-emphasis-shadow),
inset 0 0 2px var(--ui-item-slot-emphasis-inset);
}
.ui-item-slot.is-emphasized::after {
z-index: 3;
inset: 5px;
border: 1px solid #fffc2a;
inset: var(--ui-item-frame-inset);
border: 1px solid var(--ui-item-slot-emphasis-strong);
}
.ui-item-slot.is-locked::after {
content: '';
position: absolute;
z-index: 4;
inset: 0;
background: rgb(0 0 0 / 68%);
background: var(--ui-item-slot-lock-overlay);
}
</style>
@@ -0,0 +1,112 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
export interface UiNavigationItem {
value: string
label: string
icon?: string
indicator?: string
disabled?: boolean
}
const props = defineProps<{
class?: HTMLAttributes['class']
items: UiNavigationItem[]
label?: string
}>()
const model = defineModel<string>()
</script>
<template>
<nav :class="cn('ui-navigation', props.class)" :aria-label="label">
<button
v-for="item in items"
:key="item.value"
type="button"
class="ui-navigation__item"
:class="{ 'is-selected': model === item.value }"
:disabled="item.disabled"
:aria-current="model === item.value ? 'page' : undefined"
@click="model = item.value"
>
<img v-if="item.icon" :src="item.icon" alt="" aria-hidden="true" />
<span>{{ item.label }}</span>
<img v-if="item.indicator" :src="item.indicator" alt="" aria-hidden="true" />
<i v-else aria-hidden="true" />
</button>
</nav>
</template>
<style scoped lang="scss">
.ui-navigation {
width: var(--ui-navigation-width, min(100%, var(--ui-navigation-inline-size)));
overflow: hidden;
border: 1px solid var(--ui-navigation-border);
background: var(--ui-item-slot-background);
}
.ui-navigation__item {
box-sizing: border-box;
display: grid;
width: 100%;
height: var(--ui-navigation-row-block-size);
grid-template-columns:
var(--ui-navigation-icon-size) minmax(0, 1fr)
var(--ui-navigation-indicator-inline-size);
align-items: center;
gap: var(--ui-disclosure-row-gap);
padding: 0 var(--ui-navigation-padding-inline);
border: 0;
border-bottom: 1px solid var(--ui-navigation-divider);
outline: none;
background: transparent;
color: var(--ui-navigation-foreground);
font: inherit;
line-height: var(--ui-control-line-height);
text-align: left;
cursor: pointer;
&:last-child {
border-bottom: 0;
}
&:hover:not(:disabled),
&:focus-visible:not(:disabled) {
background: var(--ui-disclosure-header-background);
}
&:focus-visible {
outline: 1px solid var(--ui-ring);
outline-offset: -2px;
}
&:active:not(:disabled) > * {
transform: translateY(1px);
}
&.is-selected {
background: var(--ui-navigation-selected-background);
color: var(--ui-navigation-selected-foreground);
}
&:disabled {
cursor: not-allowed;
color: var(--ui-control-disabled-foreground);
}
img {
max-width: var(--ui-navigation-icon-size);
max-height: var(--ui-navigation-icon-size);
object-fit: contain;
image-rendering: pixelated;
}
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>
@@ -0,0 +1,2 @@
export { default as UiNavigation } from './UiNavigation.vue'
export type { UiNavigationItem } from './UiNavigation.vue'
@@ -65,41 +65,45 @@ function fastChange(direction: 1 | -1) {
<style scoped>
.ui-number-field-shell {
display: inline-flex;
width: var(--ui-number-field-width, 100%);
min-width: 0;
align-items: center;
}
.ui-number-field {
box-sizing: border-box;
display: grid;
width: 210px;
height: 30px;
width: 100%;
min-width: 0;
height: var(--ui-control-height-default);
grid-template-columns: minmax(0, 1fr) 23px;
border: 1px solid var(--ui-input);
background: linear-gradient(#0b0c0d, #000100);
box-shadow: inset 0 0 0 1px #050505;
background: var(--ui-field-background);
box-shadow: inset 0 0 0 1px var(--ui-field-inset);
}
.ui-number-field:focus-within {
border-color: var(--ui-ring);
box-shadow:
inset 0 0 0 1px #071722,
0 0 0 1px #24617c;
inset 0 0 0 1px var(--ui-field-focus-inset),
0 0 0 1px var(--ui-field-focus-edge);
}
.ui-number-field__input {
min-width: 0;
padding: 0 9px;
padding: 0 var(--ui-field-padding-inline);
border: 0;
outline: 0;
background: transparent;
color: var(--ui-primary-foreground);
font: 13px/1 inherit;
font: var(--ui-control-font-size-default) / var(--ui-control-line-height) inherit;
text-align: inherit;
}
.ui-number-field__spinner {
display: grid;
grid-template-rows: 1fr 1fr;
border-left: 1px solid #4b412d;
border-left: 1px solid var(--ui-number-spinner-divider);
}
.ui-number-field__step {
@@ -114,29 +118,29 @@ function fastChange(direction: 1 | -1) {
}
.ui-number-field__step + .ui-number-field__step {
border-top: 1px solid #2e2d28;
border-top: 1px solid var(--ui-number-step-divider);
}
.ui-number-field__step:hover {
background: rgb(255 255 255 / 3.5%);
background: var(--ui-number-step-hover);
}
.ui-number-field__step:active {
background: rgb(0 0 0 / 35%);
background: var(--ui-number-step-active);
}
.ui-number-field__step i {
width: 0;
height: 0;
border-right: 7px solid transparent;
border-bottom: 8px solid #b99550;
border-bottom: 8px solid var(--ui-number-step-arrow);
border-left: 7px solid transparent;
}
.ui-number-field__step:last-child i {
border-top: 8px solid #b99550;
border-top: 8px solid var(--ui-number-step-arrow);
border-bottom: 0;
}
.ui-number-field__fast-step {
width: 26px;
height: 28px;
width: var(--ui-number-fast-inline-size);
height: var(--ui-number-fast-block-size);
display: flex;
flex-direction: column;
margin-left: 3px;
@@ -148,18 +152,14 @@ function fastChange(direction: 1 | -1) {
--fast-b: 0;
appearance: none;
position: relative;
flex: 0 0 14px;
width: 26px;
height: 14px;
flex: 0 0 var(--ui-number-fast-step-block-size);
width: var(--ui-number-fast-inline-size);
height: var(--ui-number-fast-step-block-size);
padding: 0;
border: 0;
outline: none;
background:
linear-gradient(90deg, #3c66b4 0 1px, transparent 1px 23px, #3c66b4 23px) 1px 4px / 24px 9px
no-repeat,
linear-gradient(180deg, #325596, #1d2f53 46% 72%, #2b4378) 1px 4px / 24px 9px no-repeat,
linear-gradient(#000, #000) 0 3px / 26px 11px no-repeat;
color: #b69260;
background: var(--ui-number-fast-up-background);
color: var(--ui-number-fast-text);
cursor: pointer;
}
@@ -170,31 +170,17 @@ function fastChange(direction: 1 | -1) {
left: 9px;
width: 7px;
height: 3px;
background:
linear-gradient(#000, #000) center 0 / 3px 1px no-repeat,
linear-gradient(90deg, #000 0 1px, #080d17 1px 2px, #2f518f 2px 3px, #080d17 3px 4px, #000 4px)
center 1px / 5px 1px no-repeat,
linear-gradient(90deg, #000 0 1px, #080d17 1px 2px, #2f518f 2px 5px, #080d17 5px 6px, #000 6px)
center 2px / 7px 1px no-repeat;
background: var(--ui-number-fast-chevron-up);
}
.ui-number-field__fast-step button + button {
background:
linear-gradient(90deg, #3e6cbe 0 1px, transparent 1px 23px, #3e6cbe 23px) 1px 1px / 24px 9px
no-repeat,
linear-gradient(180deg, #345a9e, #1d2f53 46% 72%, #2d4782) 1px 1px / 24px 9px no-repeat,
linear-gradient(#000, #000) 0 0 / 26px 11px no-repeat;
background: var(--ui-number-fast-down-background);
}
.ui-number-field__fast-step button + button::before {
top: auto;
bottom: 0;
background:
linear-gradient(90deg, #000 0 1px, #080d17 1px 2px, #2f518f 2px 5px, #080d17 5px 6px, #000 6px)
center 0 / 7px 1px no-repeat,
linear-gradient(90deg, #000 0 1px, #080d17 1px 2px, #2f518f 2px 3px, #080d17 3px 4px, #000 4px)
center 1px / 5px 1px no-repeat,
linear-gradient(#000, #000) center 2px / 3px 1px no-repeat;
background: var(--ui-number-fast-chevron-down);
}
.ui-number-field__fast-step button:hover {
@@ -33,7 +33,7 @@ const props = defineProps<{
.ui-panel__heading {
box-sizing: border-box;
display: flex;
height: 46px;
height: var(--ui-panel-heading-block-size);
align-items: center;
justify-content: space-between;
gap: clamp(8px, 1.2cqi, 16px);
@@ -65,7 +65,7 @@ const props = defineProps<{
.ui-panel__heading {
align-items: flex-start;
height: auto;
min-height: 46px;
min-height: var(--ui-panel-heading-block-size);
padding-block: 10px;
}
.ui-panel__heading span {
@@ -28,16 +28,16 @@ const percentage = computed(() =>
.ui-progress {
position: relative;
width: 100%;
height: 14px;
height: var(--ui-progress-block-size);
overflow: hidden;
border: 1px solid #3c3425;
background: #0a0b0a;
border: 1px solid var(--ui-progress-border);
background: var(--ui-progress-background);
}
.ui-progress__indicator {
height: 100%;
background: linear-gradient(180deg, #9abe23, #63820a);
box-shadow: inset 0 0 0 1px #cce43c;
background: var(--ui-progress-fill);
box-shadow: inset 0 0 0 1px var(--ui-progress-fill-inset);
transition: width 180ms ease-out;
}
@@ -46,7 +46,7 @@ const percentage = computed(() =>
top: -5px;
width: 0;
height: 0;
border-top: 5px solid #d7bd61;
border-top: 5px solid var(--ui-progress-marker);
border-right: 4px solid transparent;
border-left: 4px solid transparent;
transform: translateX(-50%);
@@ -27,20 +27,20 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<style scoped>
.ui-radio-item {
box-sizing: border-box;
width: 14px;
height: 14px;
width: var(--ui-radio-size);
height: var(--ui-radio-size);
padding: 0;
border: 1px solid #614f36;
border: 1px solid var(--ui-radio-border);
border-radius: 50%;
outline: none;
background: linear-gradient(#2e2e2e, #171717);
box-shadow: inset 0 1px 0 rgb(255 255 255 / 4%);
background: var(--ui-radio-surface);
box-shadow: inset 0 1px 0 var(--ui-control-inner-highlight);
cursor: pointer;
}
.ui-radio-item:hover:not(:disabled) {
border-color: #d6c889;
background: linear-gradient(#4a4a4a, #252525);
border-color: var(--ui-radio-hover-border);
background: var(--ui-radio-hover-surface);
}
.ui-radio-item:focus-visible {
@@ -55,10 +55,15 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
.ui-radio-item:disabled {
cursor: not-allowed;
border-color: #3b3b3b;
border-color: var(--ui-radio-disabled-border);
background:
linear-gradient(135deg, transparent 43%, #313131 46% 54%, transparent 57%),
linear-gradient(#252525, #121212);
linear-gradient(
135deg,
transparent 43%,
var(--ui-radio-disabled-mark) 46% 54%,
transparent 57%
),
var(--ui-radio-disabled-surface);
opacity: 0.45;
}
@@ -69,8 +74,8 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
border: 1px solid transparent;
border-radius: inherit;
background:
linear-gradient(#dbaa3a, #edd067) padding-box,
linear-gradient(#f0dfa0, #a17e50) border-box;
box-shadow: inset 0 0 0 1px #272727;
var(--ui-radio-mark) padding-box,
var(--ui-radio-mark-border) border-box;
box-shadow: inset 0 0 0 1px var(--ui-radio-mark-inset);
}
</style>
@@ -39,14 +39,14 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
.ui-rating {
display: flex;
width: max-content;
min-height: 46px;
min-height: var(--ui-rating-min-block-size);
align-items: center;
gap: 2px;
padding: 8px 12px;
border-radius: 22px;
outline: none;
background: #1a1917;
box-shadow: inset 0 1px 0 rgb(255 255 255 / 3%);
background: var(--ui-rating-background);
box-shadow: inset 0 1px 0 var(--ui-rating-highlight);
}
.ui-rating:focus-visible {
@@ -54,8 +54,8 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
}
.ui-rating__item {
position: relative;
width: 24px;
height: 24px;
width: var(--ui-rating-item-size);
height: var(--ui-rating-item-size);
padding: 0;
border: 0;
background: transparent;
@@ -63,15 +63,15 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
}
.ui-rating__item > img,
.ui-rating :deep(.ui-rating__indicator img) {
width: 24px;
width: var(--ui-rating-item-size);
height: auto;
image-rendering: pixelated;
}
.ui-rating :deep(.ui-rating__indicator) {
position: absolute;
inset: 0;
width: 24px;
height: 24px;
width: var(--ui-rating-item-size);
height: var(--ui-rating-item-size);
padding: 0;
overflow: hidden;
border: 0;
@@ -0,0 +1,98 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import {
ScrollAreaCorner,
ScrollAreaRoot,
ScrollAreaScrollbar,
ScrollAreaThumb,
ScrollAreaViewport,
} from 'reka-ui'
import { cn } from '@/lib/utils'
const props = withDefaults(
defineProps<{
class?: HTMLAttributes['class']
type?: 'auto' | 'always' | 'scroll' | 'hover'
}>(),
{ type: 'auto' },
)
</script>
<template>
<ScrollAreaRoot :type="type" :class="cn('ui-scroll-area', props.class)">
<ScrollAreaViewport class="ui-scroll-area__viewport">
<slot />
</ScrollAreaViewport>
<ScrollAreaScrollbar class="ui-scroll-area__scrollbar" orientation="vertical">
<span class="ui-scroll-area__arrow ui-scroll-area__arrow--up" aria-hidden="true" />
<span class="ui-scroll-area__track"><ScrollAreaThumb class="ui-scroll-area__thumb" /></span>
<span class="ui-scroll-area__arrow ui-scroll-area__arrow--down" aria-hidden="true" />
</ScrollAreaScrollbar>
<ScrollAreaCorner />
</ScrollAreaRoot>
</template>
<style lang="scss">
.ui-scroll-area {
position: relative;
height: var(--ui-scroll-area-height, var(--ui-scroll-area-block-size));
overflow: hidden;
background: var(--ui-composition-background);
}
.ui-scroll-area__viewport {
width: 100%;
height: 100%;
}
.ui-scroll-area__scrollbar {
display: grid;
width: var(--ui-scrollbar-inline-size);
grid-template-rows: var(--ui-scrollbar-arrow-block-size) 1fr var(--ui-scrollbar-arrow-block-size);
border: 1px solid var(--ui-scrollbar-border);
background: var(--ui-scrollbar-background);
user-select: none;
touch-action: none;
}
.ui-scroll-area__arrow {
display: flex;
align-items: center;
justify-content: center;
background: var(--ui-scrollbar-arrow-background);
&::before {
content: '';
width: 0;
height: 0;
border-right: var(--ui-scrollbar-arrow-inline-size) solid transparent;
border-bottom: var(--ui-scrollbar-arrow-glyph-block-size) solid var(--ui-scrollbar-arrow);
border-left: var(--ui-scrollbar-arrow-inline-size) solid transparent;
filter: drop-shadow(0 1px 0 var(--ui-tooltip-shadow));
}
&--down::before {
border-top: var(--ui-scrollbar-arrow-glyph-block-size) solid var(--ui-scrollbar-arrow);
border-bottom: 0;
}
}
.ui-scroll-area__track {
position: relative;
background: var(--ui-scrollbar-track);
}
.ui-scroll-area__thumb {
position: relative;
width: calc(100% - var(--ui-scrollbar-thumb-gutter) * 2);
margin-inline: var(--ui-scrollbar-thumb-gutter);
border: 1px solid var(--ui-scrollbar-thumb-border);
background: var(--ui-scrollbar-thumb);
&::before {
content: '';
position: absolute;
inset: -8px 0;
}
}
</style>
@@ -0,0 +1 @@
export { default as UiScrollArea } from './UiScrollArea.vue'
@@ -46,10 +46,10 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
z-index: 80;
max-height: min(18rem, var(--reka-select-content-available-height));
overflow: hidden;
border: 1px solid #5a4828;
border: 1px solid var(--ui-popup-border);
border-radius: 0;
background: #050606;
box-shadow: 0 8px 18px #000;
background: var(--ui-popup-background);
box-shadow: 0 8px 18px var(--ui-popup-shadow);
color: var(--ui-popover-foreground);
transform-origin: var(--reka-select-content-transform-origin);
}
@@ -30,12 +30,14 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
.ui-select-item {
position: relative;
display: flex;
height: 27px;
height: var(--ui-option-height);
align-items: center;
padding: 0 10px;
border-bottom: 1px solid #25231f;
border-bottom: 1px solid var(--ui-option-divider);
outline: none;
color: var(--ui-primary-foreground);
font-size: var(--ui-control-font-size-default);
line-height: var(--ui-control-line-height);
cursor: pointer;
user-select: none;
}
@@ -43,8 +45,8 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
.ui-select-item[data-highlighted] {
outline: 1px solid var(--ui-ring);
outline-offset: -2px;
background: #091731;
color: #f3d777;
background: var(--ui-option-highlight-background);
color: var(--ui-option-highlight-foreground);
}
.ui-select-item[data-state='checked'] {
@@ -57,8 +59,8 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
}
.ui-select-item:active {
background: #07121b;
color: #fff0a4;
background: var(--ui-option-active-background);
color: var(--ui-option-active-foreground);
}
.ui-select-item__indicator {
display: none;
@@ -25,18 +25,20 @@ const delegatedProps = computed(() => {
box-sizing: border-box;
display: inline-flex;
width: 100%;
height: 30px;
height: var(--ui-control-height-default);
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 0 4px 0 9px;
padding: 0 4px 0 var(--ui-field-padding-inline);
border: 1px solid var(--ui-input);
border-radius: 0;
outline: none;
background: linear-gradient(#0b0c0d, #000100);
box-shadow: inset 0 0 0 1px #050505;
background: var(--ui-field-background);
box-shadow: inset 0 0 0 1px var(--ui-field-inset);
color: var(--ui-primary-foreground);
font: inherit;
font-size: var(--ui-control-font-size-default);
line-height: var(--ui-control-line-height);
cursor: pointer;
}
@@ -44,8 +46,8 @@ const delegatedProps = computed(() => {
.ui-select-trigger[data-state='open'] {
border-color: var(--ui-ring);
box-shadow:
inset 0 0 0 1px #071722,
0 0 0 1px #24617c;
inset 0 0 0 1px var(--ui-field-focus-inset),
0 0 0 1px var(--ui-field-focus-edge);
}
.ui-select-trigger:disabled {
@@ -55,25 +57,25 @@ const delegatedProps = computed(() => {
.ui-select-trigger__icon {
display: inline-flex;
width: 21px;
height: 18px;
width: var(--ui-select-icon-inline-size);
height: var(--ui-select-icon-block-size);
align-items: center;
justify-content: center;
border: 1px solid var(--ui-control-border);
background: var(--ui-control-background);
box-shadow: 0 0 0 1px rgb(0 0 0 / 60%);
box-shadow: 0 0 0 1px var(--ui-select-icon-shadow);
}
.ui-select-trigger__icon img {
width: 11px;
height: 6px;
width: var(--ui-select-glyph-inline-size);
height: var(--ui-select-glyph-block-size);
image-rendering: pixelated;
}
.ui-select-trigger:hover .ui-select-trigger__icon,
.ui-select-trigger:focus-visible .ui-select-trigger__icon {
border-color: #725f4e;
background: linear-gradient(180deg, #29457f, #24386a);
border-color: var(--ui-control-border-active);
background: var(--ui-control-background-hover);
}
.ui-select-trigger:active .ui-select-trigger__icon,
@@ -0,0 +1,92 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = withDefaults(
defineProps<{
class?: HTMLAttributes['class']
label: string
alt?: boolean
focused?: boolean
disabled?: boolean
indent?: 0 | 1 | 2
}>(),
{ indent: 0 },
)
</script>
<template>
<div
:class="
cn(
'ui-setting-row',
alt && 'is-alt',
focused && 'is-focused',
disabled && 'is-disabled',
props.class,
)
"
>
<div class="ui-setting-row__label" :style="{ '--ui-setting-indent': props.indent }">
<slot name="label">{{ label }}</slot>
</div>
<div class="ui-setting-row__control"><slot /></div>
</div>
</template>
<style scoped lang="scss">
.ui-setting-row {
box-sizing: border-box;
display: grid;
min-height: var(--ui-settings-row-min-block-size);
grid-template-columns:
minmax(var(--ui-settings-label-min-inline-size), 1fr)
minmax(var(--ui-settings-control-min-inline-size), 1.1fr);
align-items: center;
padding: var(--ui-settings-row-padding-block) var(--ui-settings-row-padding-inline);
border-bottom: 1px solid var(--ui-settings-row-divider);
color: var(--ui-settings-row-foreground);
line-height: var(--ui-control-line-height);
&.is-alt {
background: var(--ui-settings-row-alt);
}
&.is-focused,
&:focus-within {
outline: 1px solid var(--ui-ring);
outline-offset: -2px;
background: var(--ui-settings-row-focus);
}
&.is-disabled {
color: var(--ui-control-disabled-foreground);
}
}
.ui-setting-row__label {
display: flex;
min-width: 0;
align-items: center;
gap: var(--ui-disclosure-row-gap);
padding-left: calc(var(--ui-setting-indent) * var(--ui-settings-indent-step));
}
.ui-setting-row__control {
display: flex;
min-width: 0;
align-items: center;
justify-content: flex-end;
}
@container (max-width: 520px) {
.ui-setting-row {
grid-template-columns: 1fr;
gap: 8px;
}
.ui-setting-row__control {
justify-content: stretch;
}
}
</style>
@@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{ class?: HTMLAttributes['class'] }>()
</script>
<template>
<section :class="cn('ui-settings-group', props.class)"><slot /></section>
</template>
<style scoped>
.ui-settings-group {
border: 1px solid var(--ui-settings-border);
background: var(--ui-settings-background);
}
</style>
@@ -0,0 +1,27 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{ class?: HTMLAttributes['class'] }>()
</script>
<template>
<h3 :class="cn('ui-settings-section', props.class)"><slot /></h3>
</template>
<style scoped>
.ui-settings-section {
box-sizing: border-box;
display: flex;
height: var(--ui-settings-section-block-size);
align-items: center;
margin: 0;
padding: 0 10px;
border: 1px solid var(--ui-settings-section-border);
background: var(--ui-settings-section-background);
color: var(--ui-settings-section-foreground);
font-size: var(--ui-control-font-size-default);
font-weight: 400;
line-height: var(--ui-control-line-height);
}
</style>
@@ -0,0 +1,3 @@
export { default as UiSettingRow } from './UiSettingRow.vue'
export { default as UiSettingsGroup } from './UiSettingsGroup.vue'
export { default as UiSettingsSection } from './UiSettingsSection.vue'
@@ -0,0 +1,310 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import inspectIcon from '../../../../../design/src-web/shop-item-row/img/sprite_interface.NPK/windowcommon.chn.img/652.PNG'
import { cn } from '@/lib/utils'
export interface UiShopItem {
id: string
image: string
reinforce: string
amplify: string
name: string
status: string
owner: string
rarity?: 'normal' | 'rare' | 'artifact' | 'legend' | 'epic'
statusTone?: 'default' | 'info' | 'success' | 'danger'
disabled?: boolean
}
const props = defineProps<{
class?: HTMLAttributes['class']
rows: UiShopItem[]
inspectLabel?: string
}>()
const selected = defineModel<string>('selected')
const emits = defineEmits<{ inspect: [row: UiShopItem] }>()
function selectRow(row: UiShopItem) {
if (!row.disabled) selected.value = row.id
}
</script>
<template>
<div :class="cn('ui-shop-list', props.class)" role="listbox" aria-label="物品列表">
<div class="ui-shop-list__surface">
<div
v-for="(row, index) in rows"
:key="row.id"
class="ui-shop-row"
:class="{
'is-alt': index % 2 === 1,
'is-selected': selected === row.id,
'is-disabled': row.disabled,
}"
role="option"
:aria-selected="selected === row.id"
:aria-disabled="row.disabled || undefined"
:tabindex="row.disabled ? -1 : 0"
@click="selectRow(row)"
@keydown.enter.prevent="selectRow(row)"
@keydown.space.prevent="selectRow(row)"
>
<i class="ui-shop-row__frame" aria-hidden="true" />
<div class="ui-shop-row__icon"><img :src="row.image" alt="" /></div>
<div>{{ row.reinforce }}</div>
<div>{{ row.amplify }}</div>
<div :class="row.rarity && `quality-${row.rarity}`">{{ row.name }}</div>
<div>
<button
type="button"
class="ui-shop-row__inspect"
:aria-label="inspectLabel ?? `查看${row.name}`"
@click.stop="emits('inspect', row)"
>
<img :src="inspectIcon" alt="" />
</button>
</div>
<div :class="row.statusTone && `status-${row.statusTone}`">{{ row.status }}</div>
<div class="ui-shop-row__owner">{{ row.owner }}</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.ui-shop-list {
width: 100%;
overflow-x: auto;
overflow-y: hidden;
}
.ui-shop-list__surface {
display: flex;
min-width: var(--ui-shop-row-scroll-inline-size);
flex-direction: column;
gap: var(--ui-shop-row-gap);
}
.ui-shop-row {
--shop-frame-inset: 5px;
--shop-frame-corner: 7px;
--shop-surface-inset: 8px;
--shop-surface-corner: 5px;
--shop-outer: var(--ui-shop-frame-rest-outer);
--shop-middle: var(--ui-shop-frame-rest-middle);
--shop-accent: var(--ui-shop-frame-rest-accent);
--shop-inner: transparent;
--shop-surface: var(--ui-shop-frame-rest-surface);
--shop-shadow: transparent;
--shop-frame-filter: drop-shadow(0 0 4px var(--ui-shop-frame-shadow));
--shop-surface-filter: none;
box-sizing: border-box;
position: relative;
isolation: isolate;
display: grid;
min-height: var(--ui-shop-row-min-block-size);
grid-template-columns: var(--ui-shop-row-grid);
align-items: center;
padding-inline: var(--ui-shop-row-padding-inline);
outline: none;
color: var(--ui-shop-foreground);
font-size: var(--ui-shop-font-size);
line-height: 1.25;
cursor: pointer;
&.is-alt {
--shop-outer: var(--ui-shop-frame-alt-outer);
--shop-middle: var(--ui-shop-frame-alt-middle);
--shop-surface: var(--ui-shop-frame-alt-surface);
}
&:hover:not(.is-disabled),
&:focus-visible:not(.is-disabled) {
--shop-frame-inset: 3px;
--shop-frame-corner: 9px;
--shop-surface-inset: 7px;
--shop-outer: var(--ui-shop-frame-hover-outer);
--shop-middle: var(--ui-shop-frame-hover-middle);
--shop-accent: var(--ui-shop-frame-hover-accent);
--shop-inner: var(--ui-shop-frame-hover-inner);
--shop-surface: var(--ui-shop-frame-hover-surface);
--shop-shadow: var(--ui-tooltip-shadow);
--shop-frame-filter: none;
--shop-surface-filter: drop-shadow(1px 0 0 var(--shop-inner))
drop-shadow(-1px 0 0 var(--shop-inner)) drop-shadow(0 1px 0 var(--shop-inner))
drop-shadow(0 -1px 0 var(--shop-inner));
}
&.is-selected {
--shop-frame-inset: 3px;
--shop-frame-corner: 9px;
--shop-surface-inset: 7px;
--shop-outer: var(--ui-shop-frame-selected-outer);
--shop-middle: var(--ui-shop-frame-selected-middle);
--shop-accent: var(--ui-shop-frame-selected-accent);
--shop-inner: var(--ui-shop-frame-selected-inner);
--shop-surface: var(--ui-shop-frame-selected-surface);
--shop-shadow: var(--ui-tooltip-shadow);
--shop-frame-filter: none;
--shop-surface-filter: drop-shadow(1px 0 0 var(--shop-inner))
drop-shadow(-1px 0 0 var(--shop-inner)) drop-shadow(0 1px 0 var(--shop-inner))
drop-shadow(0 -1px 0 var(--shop-inner));
}
&.is-disabled {
color: var(--ui-control-disabled-foreground);
cursor: not-allowed;
opacity: 0.58;
}
> *:not(.ui-shop-row__frame) {
display: flex;
min-width: 0;
height: 100%;
align-items: center;
padding: 0 var(--ui-shop-cell-padding-inline);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&::before,
&::after,
.ui-shop-row__frame,
.ui-shop-row__frame::before,
.ui-shop-row__frame::after {
content: '';
position: absolute;
pointer-events: none;
}
&::before {
z-index: -3;
inset: 0;
background: var(--shop-shadow);
}
&::after {
z-index: -1;
inset: var(--shop-surface-inset);
background: var(--shop-surface);
clip-path: polygon(
var(--shop-surface-corner) 0,
calc(100% - var(--shop-surface-corner)) 0,
100% var(--shop-surface-corner),
100% calc(100% - var(--shop-surface-corner)),
calc(100% - var(--shop-surface-corner)) 100%,
var(--shop-surface-corner) 100%,
0 calc(100% - var(--shop-surface-corner)),
0 var(--shop-surface-corner)
);
filter: var(--shop-surface-filter);
}
}
.ui-shop-row__frame {
z-index: -2;
inset: var(--shop-frame-inset);
background: var(--shop-outer);
clip-path: polygon(
var(--shop-frame-corner) 0,
calc(100% - var(--shop-frame-corner)) 0,
100% var(--shop-frame-corner),
100% calc(100% - var(--shop-frame-corner)),
calc(100% - var(--shop-frame-corner)) 100%,
var(--shop-frame-corner) 100%,
0 calc(100% - var(--shop-frame-corner)),
0 var(--shop-frame-corner)
);
filter: var(--shop-frame-filter);
&::before {
inset: 1px;
background: var(--shop-middle);
}
&::after {
inset: 2px;
background: var(--shop-accent);
}
&::before,
&::after {
clip-path: inherit;
}
}
.ui-shop-row__icon {
width: var(--ui-shop-icon-inline-size);
height: var(--ui-shop-icon-block-size);
justify-content: center;
padding: 0 !important;
img {
max-width: var(--ui-item-image-size);
max-height: var(--ui-item-image-size);
image-rendering: pixelated;
}
}
.ui-shop-row__inspect {
appearance: none;
display: inline-flex;
width: var(--ui-shop-action-inline-size);
height: var(--ui-shop-action-block-size);
align-items: center;
justify-content: center;
padding: 0;
border: 2px solid var(--ui-input);
border-radius: 0;
background: var(--ui-shop-action-background);
box-shadow:
0 0 0 1px var(--ui-check-outer-shadow),
inset 0 0 0 1px var(--ui-shop-action-inset);
cursor: pointer;
&:hover,
&:focus-visible {
border-color: var(--ui-control-border-active);
outline: none;
background: var(--ui-shop-action-hover-background);
}
&:active {
transform: translateY(1px);
}
img {
max-width: var(--ui-shop-action-icon-size);
max-height: var(--ui-shop-action-icon-size);
image-rendering: pixelated;
}
}
.ui-shop-row__owner {
white-space: pre-line !important;
}
.quality-rare {
color: var(--dcalc-quality-rare);
}
.quality-artifact {
color: var(--dcalc-quality-artifact);
}
.quality-legend {
color: var(--dcalc-quality-legend);
}
.quality-epic {
color: var(--dcalc-quality-epic);
}
.status-info {
color: var(--ui-info);
}
.status-success {
color: var(--ui-success);
}
.status-danger {
color: var(--ui-destructive);
}
</style>
@@ -0,0 +1,2 @@
export { default as UiShopItemList } from './UiShopItemList.vue'
export type { UiShopItem } from './UiShopItemList.vue'
@@ -53,8 +53,8 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
position: relative;
display: flex;
width: 100%;
min-width: 160px;
height: 34px;
min-width: var(--ui-slider-min-inline-size);
height: var(--ui-slider-block-size);
align-items: center;
gap: 10px;
}
@@ -64,7 +64,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
display: flex;
min-width: 0;
flex: 1;
height: 17px;
height: var(--ui-slider-control-size);
align-items: center;
touch-action: none;
user-select: none;
@@ -82,28 +82,28 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
.ui-slider__track {
position: relative;
flex: 1;
height: 3px;
height: var(--ui-slider-track-size);
overflow: visible;
background: #515351;
box-shadow: 0 1px 0 #020202;
background: var(--ui-slider-track);
box-shadow: 0 1px 0 var(--ui-slider-track-shadow);
}
.ui-slider__range {
position: absolute;
inset-block: 0;
background: #f3d367;
box-shadow: 0 0 3px rgb(243 211 103 / 25%);
background: var(--ui-slider-range);
box-shadow: 0 0 3px var(--ui-slider-range-shadow);
}
.ui-slider__thumb {
--thumb-edge-top: #86693f;
--thumb-edge-bottom: #76593a;
--thumb-top: #21365e;
--thumb-bottom: #1b2b4d;
--thumb-edge-top: var(--ui-slider-thumb-edge-top);
--thumb-edge-bottom: var(--ui-slider-thumb-edge-bottom);
--thumb-top: var(--ui-slider-thumb-top);
--thumb-bottom: var(--ui-slider-thumb-bottom);
box-sizing: border-box;
display: block;
width: 17px;
height: 17px;
width: var(--ui-slider-control-size);
height: var(--ui-slider-control-size);
border: 1px solid transparent;
border-radius: 50%;
outline: none;
@@ -111,37 +111,37 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
radial-gradient(circle at 50% 28%, var(--thumb-top), var(--thumb-bottom) 78%) padding-box,
linear-gradient(180deg, var(--thumb-edge-top), var(--thumb-edge-bottom)) border-box;
box-shadow:
0 0 0 1px rgb(0 0 0 / 40%),
inset 0 1px 0 rgb(255 255 255 / 2.5%);
0 0 0 1px var(--ui-control-outer-shadow),
inset 0 1px 0 var(--ui-control-inner-highlight);
}
.ui-slider__control:hover .ui-slider__thumb,
.ui-slider__control:active .ui-slider__thumb,
.ui-slider__thumb:hover,
.ui-slider__thumb:focus-visible {
--thumb-edge-top: #907960;
--thumb-edge-bottom: #80695b;
--thumb-top: #2b467f;
--thumb-bottom: #253b6e;
box-shadow: 0 0 0 1px rgb(0 0 0 / 40%);
--thumb-edge-top: var(--ui-slider-thumb-hover-edge-top);
--thumb-edge-bottom: var(--ui-slider-thumb-hover-edge-bottom);
--thumb-top: var(--ui-slider-thumb-hover-top);
--thumb-bottom: var(--ui-slider-thumb-hover-bottom);
box-shadow: 0 0 0 1px var(--ui-control-outer-shadow);
}
.ui-slider__control[data-disabled] .ui-slider__range {
background: #686868;
background: var(--ui-slider-disabled-range);
box-shadow: none;
}
.ui-slider__control[data-disabled] .ui-slider__thumb {
--thumb-edge-top: #454545;
--thumb-edge-bottom: #3e3e3e;
--thumb-top: #2c2c2c;
--thumb-bottom: #242424;
--thumb-edge-top: var(--ui-slider-disabled-thumb-edge-top);
--thumb-edge-bottom: var(--ui-slider-disabled-thumb-edge-bottom);
--thumb-top: var(--ui-slider-disabled-thumb-top);
--thumb-bottom: var(--ui-slider-disabled-thumb-bottom);
}
.ui-slider:has(.ui-slider__control[data-disabled]) .ui-slider__value {
color: var(--ui-control-disabled-foreground);
}
.ui-slider__value {
min-width: 40px;
min-width: var(--ui-slider-value-min-inline-size);
color: var(--ui-primary-foreground);
font-size: 12px;
text-align: right;
@@ -29,16 +29,16 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
.ui-switch {
box-sizing: border-box;
position: relative;
width: 28px;
height: 16px;
width: var(--ui-switch-inline-size);
height: var(--ui-switch-block-size);
padding: 0;
border: 1px solid transparent;
border-radius: 8px / 50%;
outline: none;
background:
linear-gradient(#141414, #0f0f0f) padding-box,
linear-gradient(#846a48, #453520) border-box;
box-shadow: inset 0 1px 3px #090909;
var(--ui-switch-surface) padding-box,
var(--ui-switch-border) border-box;
box-shadow: inset 0 1px 3px var(--ui-switch-inset);
cursor: pointer;
}
@@ -54,14 +54,14 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
transform: translate(1px, 1px);
}
.ui-switch[data-state='checked']:active:not(:disabled) .ui-switch__thumb {
transform: translate(13px, 1px);
transform: translate(var(--ui-switch-thumb-travel), 1px);
}
.ui-switch[data-state='checked'] {
background:
linear-gradient(#eccb65, #eccb65) padding-box,
linear-gradient(#846a48, #453520) border-box;
box-shadow: inset 0 0 0 1px #0d0d0d;
var(--ui-switch-checked-surface) padding-box,
var(--ui-switch-border) border-box;
box-shadow: inset 0 0 0 1px var(--ui-switch-checked-inset);
}
.ui-switch:disabled {
@@ -77,16 +77,16 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
.ui-switch__thumb {
display: block;
width: 12px;
height: 12px;
width: var(--ui-switch-thumb-size);
height: var(--ui-switch-thumb-size);
border-radius: 50%;
background: var(--ui-control-background);
box-shadow: inset 0 1px 0 rgb(255 255 255 / 4%);
box-shadow: inset 0 1px 0 var(--ui-control-inner-highlight);
transition: transform 90ms ease-out;
transform: translateX(1px);
}
.ui-switch__thumb[data-state='checked'] {
transform: translateX(13px);
transform: translateX(var(--ui-switch-thumb-travel));
}
</style>
@@ -22,7 +22,7 @@ const delegatedProps = computed(() => {
border: 1px solid var(--ui-border);
border-top: 0;
outline: none;
background: rgb(0 0 0 / 22%);
background: var(--ui-tabs-content-background);
color: var(--ui-foreground);
}
@@ -85,10 +85,10 @@ onBeforeUnmount(() => {
position: relative;
display: flex;
width: 100%;
height: 40px;
align-items: flex-start;
height: var(--ui-tabs-height);
align-items: center;
padding: 5px 7px;
background: #090709;
background: var(--ui-tab-list-background);
}
.ui-tabs-list::after {
@@ -98,15 +98,15 @@ onBeforeUnmount(() => {
top: 35px;
left: 7px;
height: 1px;
background: #584840;
background: var(--ui-tab-divider);
}
.ui-tabs-list__marker {
position: absolute;
z-index: 4;
bottom: 0;
width: 39px;
height: 5px;
width: var(--ui-tab-marker-inline-size);
height: var(--ui-tab-marker-block-size);
opacity: 0;
transform: translateX(-50%);
pointer-events: none;
@@ -117,7 +117,7 @@ onBeforeUnmount(() => {
position: absolute;
inset: 0 0 auto;
height: 1px;
background: linear-gradient(90deg, transparent, #d0a56a 50%, transparent);
background: linear-gradient(90deg, transparent, var(--ui-tab-marker) 50%, transparent);
}
.ui-tabs-list__marker::after {
@@ -125,9 +125,14 @@ onBeforeUnmount(() => {
position: absolute;
top: 1px;
left: 50%;
width: 9px;
height: 4px;
background: linear-gradient(180deg, #9d7a35, #a7823f 46%, #d0a56a);
width: var(--ui-tab-marker-arrow-inline-size);
height: var(--ui-tab-marker-arrow-block-size);
background: linear-gradient(
180deg,
var(--ui-tab-marker-start),
var(--ui-tab-marker-middle) 46%,
var(--ui-tab-marker)
);
clip-path: polygon(0 0, 100% 0, 50% 100%);
transform: translateX(-50%);
}
@@ -18,38 +18,26 @@ const delegatedProps = computed(() => {
<style scoped>
.ui-tabs-trigger {
--tab-hover-glow: linear-gradient(
90deg,
transparent,
rgb(48 24 8 / 18%) 10%,
rgb(48 24 8 / 72%) 28%,
#301808 50%,
rgb(48 24 8 / 72%) 72%,
rgb(48 24 8 / 18%) 90%,
transparent
);
--tab-selected-glow: radial-gradient(
ellipse 58% 46% at 50% 100%,
rgb(114 97 30 / 94%),
rgb(88 74 23 / 74%) 34%,
rgb(52 44 14 / 38%) 66%,
transparent
);
--tab-hover-glow: var(--ui-tab-hover-glow);
--tab-selected-glow: var(--ui-tab-selected-glow);
position: relative;
z-index: 1;
height: 30px;
display: inline-flex;
height: var(--ui-tab-height);
align-items: center;
justify-content: center;
min-width: 0;
flex: 1 1 0;
padding: 0 12px;
border: 0;
outline: none;
background: #080808;
color: #9c815a;
background: var(--ui-tab-background);
color: var(--ui-tab-foreground);
font:
16px/1 'DCalc',
16px / var(--ui-control-line-height) 'DCalc',
SimSun,
sans-serif;
text-shadow: 1px 1px 0 #000;
text-shadow: 1px 1px 0 var(--ui-dialog-titlebar-text-shadow);
cursor: pointer;
}
@@ -60,17 +48,17 @@ const delegatedProps = computed(() => {
right: 0;
width: 1px;
height: 19px;
background: linear-gradient(180deg, transparent, #342f2a 22% 78%, transparent);
background: linear-gradient(180deg, transparent, var(--ui-tab-separator) 22% 78%, transparent);
}
.ui-tabs-trigger:hover,
.ui-tabs-trigger:focus-visible {
color: #e4ca90;
color: var(--ui-tab-active-foreground);
background-image: var(--tab-hover-glow);
}
.ui-tabs-trigger[data-state='active'] {
color: #e4ca90;
color: var(--ui-tab-active-foreground);
background-image: var(--tab-selected-glow), var(--tab-hover-glow);
}
@@ -84,5 +72,7 @@ const delegatedProps = computed(() => {
position: relative;
z-index: 1;
display: inline-block;
line-height: var(--ui-control-line-height);
transform: translateY(var(--ui-control-text-offset-y));
}
</style>
@@ -37,13 +37,13 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
.ui-tooltip-content {
position: relative;
z-index: 90;
max-width: 260px;
max-width: var(--ui-tooltip-max-inline-size);
padding: 5px 7px;
border: 1px solid #6c5c3d;
border: 1px solid var(--ui-tooltip-border);
border-radius: 0;
background: #020302;
box-shadow: 0 6px 14px #000;
color: #f5f5ef;
background: var(--ui-tooltip-background);
box-shadow: 0 6px 14px var(--ui-tooltip-shadow);
color: var(--ui-tooltip-foreground);
font-size: 12px;
line-height: 1.5;
transform-origin: var(--reka-tooltip-content-transform-origin);
@@ -1,8 +1,10 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { UiAutocomplete, type UiAutocompleteOption } from '@/components/ui/autocomplete'
import { UiBadge } from '@/components/ui/badge'
import { buttonVariants, UiButton } from '@/components/ui/button'
import { UiCheckbox } from '@/components/ui/checkbox'
import { UiDataRow, UiDataSection } from '@/components/ui/data-section'
import {
UiDataTable,
type UiDataTableColumn,
@@ -29,10 +31,12 @@ import {
import { UiInput } from '@/components/ui/input'
import { UiItemSlot } from '@/components/ui/item-slot'
import { UiNumberField } from '@/components/ui/number-field'
import { UiNavigation, type UiNavigationItem } from '@/components/ui/navigation'
import { UiPanel } from '@/components/ui/panel'
import { UiProgress } from '@/components/ui/progress'
import { UiRadioGroup, UiRadioGroupItem } from '@/components/ui/radio-group'
import { UiRating } from '@/components/ui/rating'
import { UiScrollArea } from '@/components/ui/scroll-area'
import {
SelectRoot,
SelectValue,
@@ -41,6 +45,8 @@ import {
UiSelectTrigger,
} from '@/components/ui/select'
import { UiSlider } from '@/components/ui/slider'
import { UiSettingRow, UiSettingsGroup, UiSettingsSection } from '@/components/ui/settings'
import { UiShopItemList, type UiShopItem } from '@/components/ui/shop-item-row'
import { UiSwitch } from '@/components/ui/switch'
import { TabsRoot, UiTabsContent, UiTabsList, UiTabsTrigger } from '@/components/ui/tabs'
import {
@@ -55,6 +61,10 @@ import itemArtifact from '../../../../design/src-web/rarity/img/sprite_item.NPK/
import itemLegend from '../../../../design/src-web/rarity/img/sprite_item.NPK/equipmentskill.img/2.PNG'
import itemEpic from '../../../../design/src-web/rarity/img/sprite_item.NPK/equipmentskill.img/3.PNG'
import itemPrimeval from '../../../../design/src-web/rarity/img/sprite_item.NPK/equipmentskill.img/4.PNG'
import navHome from '../../../../design/src-web/navigation/img/sprite_interface2_option.NPK/option.img/82.PNG'
import navDisplay from '../../../../design/src-web/navigation/img/sprite_interface2_option.NPK/option.img/85.PNG'
import navEffect from '../../../../design/src-web/navigation/img/sprite_interface2_option.NPK/option.img/91.PNG'
import navArrow from '../../../../design/src-web/navigation/img/sprite_interface2_option.NPK/option.img/103.PNG'
const { activeTheme, themes, setTheme } = useTheme()
@@ -71,6 +81,10 @@ const activeTab = ref('equipment')
const activeLineTab = ref('detail')
const lastAction = ref('尚未执行菜单操作')
const selectedRow = ref('sword')
const autocompleteValue = ref('dnf-one')
const navigationValue = ref('effects')
const dataSectionOpen = ref(true)
const shopSelected = ref('shop-rare')
const sortKey = ref('reinforce')
const sortDirection = ref<'ascending' | 'descending'>('ascending')
@@ -95,6 +109,62 @@ const componentNames = [
'Tooltip',
'DropdownMenu',
'Dialog',
'Autocomplete',
'Navigation',
'DataSection',
'ScrollArea',
'Settings',
'ShopItemRow',
]
const autocompleteOptions: UiAutocompleteOption[] = [
{ value: 'dnf-one', label: 'DNF Toonies(1天)' },
{ value: 'dnf-three', label: 'DNF Toonies(3天)' },
{ value: 'poison', label: 'DNF毒战者', tone: 'epic' },
{ value: 'sage', label: 'DNF智者', tone: 'rare' },
{ value: 'warrior', label: '闪耀之DNF勇士', tone: 'artifact' },
]
const navigationItems: UiNavigationItem[] = [
{ value: 'home', label: '首页', icon: navHome },
{ value: 'display', label: '画面', icon: navDisplay, indicator: navArrow },
{ value: 'effects', label: '特效', icon: navEffect, indicator: navArrow },
]
const shopRows: UiShopItem[] = [
{
id: 'shop-artifact',
image: itemArtifact,
reinforce: '+0',
amplify: '+0',
name: '人造神 3 号纳波尔宝珠',
status: '购买完成',
statusTone: 'info',
owner: '湖北 4\n绝唱',
rarity: 'artifact',
},
{
id: 'shop-epic',
image: itemEpic,
reinforce: '+0',
amplify: '+0',
name: '果味整夏收集箱宝珠自选',
status: '出售完成',
statusTone: 'success',
owner: '湖北 4\n绝唱',
rarity: 'epic',
},
{
id: 'shop-rare',
image: itemRare,
reinforce: '+2',
amplify: '+0',
name: '魔法封印转换灵魂',
status: '购买完成',
statusTone: 'info',
owner: '湖北 4\n蛋猪超人',
rarity: 'rare',
},
]
const raritySamples = [
@@ -385,8 +455,62 @@ function handleSort(key: string) {
</UiPanel>
</div>
<div class="two-column migrated-row">
<UiPanel title="Autocomplete / DataSection" note="Reka Combobox · Collapsible">
<div class="migration-stack">
<label class="field-stack">
<span>Autocomplete · keyboard filtering</span>
<UiAutocomplete
v-model="autocompleteValue"
:options="autocompleteOptions"
placeholder="搜索物品名称"
/>
</label>
<UiDataSection v-model:open="dataSectionOpen" title="基本信息">
<UiDataRow><span class="grow">魔法暴击</span><b>+113.5%</b></UiDataRow>
<UiDataRow><span class="grow">技能等级</span><b>46</b></UiDataRow>
<UiDataRow disabled><span class="grow">精神</span><span>不可用</span></UiDataRow>
</UiDataSection>
</div>
</UiPanel>
<UiPanel title="Navigation / SettingsRow" note="Source composition · responsive rows">
<div class="navigation-settings-demo">
<UiNavigation v-model="navigationValue" :items="navigationItems" label="设置分类" />
<UiSettingsGroup>
<UiSettingsSection>显示特效</UiSettingsSection>
<UiSettingRow label="阴影效果">
<label class="ui-control-row"><UiCheckbox v-model="accepted" />开启</label>
</UiSettingRow>
<UiSettingRow label="角色轮廓" alt focused>
<UiSlider v-model="volume" label="角色轮廓" />
</UiSettingRow>
<UiSettingRow label="自动同步" :indent="1">
<label class="ui-control-row"><UiSwitch v-model="syncEnabled" />开启</label>
</UiSettingRow>
</UiSettingsGroup>
</div>
</UiPanel>
</div>
<UiPanel title="ShopItemRow / ScrollArea" note="Source frame layers · real scrolling">
<div class="shop-scroll-demo">
<UiShopItemList
v-model:selected="shopSelected"
:rows="shopRows"
@inspect="lastAction = `查看 ${$event.name}`"
/>
<UiScrollArea class="catalog-scroll-area">
<div class="scroll-demo-content">
<b>SCROLLABLE SOURCE CONTENT</b>
<span v-for="index in 12" :key="index">设置记录 {{ index }}</span>
</div>
</UiScrollArea>
</div>
</UiPanel>
<UiPanel
title="Tooltip / DropdownMenu / WindowDialog"
title="Tooltip / DropdownMenu / WindowDialog / ComplexDialog"
note="Portal · Escape · focus restoration"
>
<div class="overlay-showcase">
@@ -434,6 +558,59 @@ function handleSort(key: string) {
></DialogPortal
></DialogRoot
>
<DialogRoot
><DialogTrigger as-child
><UiButton variant="outline">打开组合设置窗口</UiButton></DialogTrigger
><DialogPortal
><UiDialogOverlay /><UiDialogContent class="complex-dialog-demo"
><UiDialogHeader
><UiDialogTitle>管理设置</UiDialogTitle>
<UiDialogDescription
>组合展示已迁移的导航、设置行、滚动区与表单控件。</UiDialogDescription
></UiDialogHeader
>
<div class="complex-dialog-body">
<p>※ 直接组合迁移后的 Navigation、SettingsRow、ScrollArea 与表单控件。</p>
<div class="complex-dialog-main">
<UiNavigation
v-model="navigationValue"
:items="navigationItems"
label="设置分类"
/>
<UiScrollArea class="complex-dialog-scroll">
<UiSettingsGroup>
<UiSettingsSection>当前预设差异</UiSettingsSection>
<UiSettingRow label="装备信息对比">
<label class="ui-control-row"
><UiCheckbox v-model="accepted" />开启</label
>
</UiSettingRow>
<UiSettingRow label="角色技能效果透明" alt>
<UiSlider v-model="volume" label="技能透明度" />
</UiSettingRow>
<UiSettingRow label="自动同步">
<label class="ui-control-row"
><UiSwitch v-model="syncEnabled" />开启</label
>
</UiSettingRow>
<UiSettingRow label="界面分类" alt>
<UiAutocomplete
v-model="autocompleteValue"
:options="autocompleteOptions"
/>
</UiSettingRow>
</UiSettingsGroup>
</UiScrollArea>
</div>
</div>
<UiDialogFooter
><DialogClose :class="buttonVariants({ variant: 'outline' })"
>返回</DialogClose
><DialogClose :class="buttonVariants()">应用</DialogClose></UiDialogFooter
></UiDialogContent
></DialogPortal
></DialogRoot
>
<output>{{ lastAction }}</output>
</div>
</UiPanel>
@@ -750,6 +927,67 @@ label > span,
margin-top: 10px;
color: var(--ui-primary-foreground);
}
.migration-stack {
display: grid;
gap: 12px;
}
.navigation-settings-demo {
display: grid;
grid-template-columns: minmax(150px, 0.42fr) minmax(0, 1fr);
gap: 12px;
}
.navigation-settings-demo :deep(.ui-navigation) {
--ui-navigation-width: 100%;
}
.shop-scroll-demo {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(150px, 0.22fr);
gap: 12px;
}
.catalog-scroll-area {
--ui-scroll-area-height: 158px;
border: 1px solid var(--ui-composition-border);
}
.scroll-demo-content {
display: grid;
padding: 8px 20px 8px 8px;
gap: 7px;
color: var(--ui-muted-foreground);
}
.scroll-demo-content b {
color: var(--ui-primary-foreground);
font-size: 10px;
font-weight: 400;
}
.complex-dialog-demo {
--ui-dialog-max-inline-size: 980px;
}
.complex-dialog-body {
display: grid;
gap: 10px;
padding: 10px;
background: var(--ui-dialog-background);
}
.complex-dialog-body p {
margin: 0;
padding: 10px;
border: 1px solid var(--ui-composition-border);
color: var(--ui-muted-foreground);
text-align: center;
}
.complex-dialog-main {
display: grid;
grid-template-columns: minmax(160px, 0.32fr) minmax(0, 1fr);
gap: 10px;
min-height: 0;
}
.complex-dialog-main :deep(.ui-navigation) {
--ui-navigation-width: 100%;
}
.complex-dialog-scroll {
--ui-scroll-area-height: 270px;
border: 1px solid var(--ui-composition-border);
}
.overlay-showcase {
display: flex;
flex-wrap: wrap;
@@ -816,6 +1054,10 @@ footer {
.control-grid {
grid-template-columns: 1fr 1fr;
}
.navigation-settings-demo,
.shop-scroll-demo {
grid-template-columns: 1fr;
}
}
@media (max-width: 520px) {
@@ -838,6 +1080,21 @@ footer {
.slider-line {
grid-template-columns: 1fr;
}
.complex-dialog-main {
grid-template-columns: 1fr;
}
.complex-dialog-main :deep(.ui-navigation) {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.complex-dialog-main :deep(.ui-navigation__item) {
height: var(--ui-control-height-large);
grid-template-columns: 18px minmax(0, 1fr);
padding-inline: 7px;
}
.complex-dialog-main :deep(.ui-navigation__item > :last-child) {
display: none;
}
.overlay-showcase output {
width: 100%;
margin-left: 0;
@@ -0,0 +1,35 @@
@use 'sass:map';
/*
* Static component recipes belong in SCSS; runtime theme values remain CSS custom properties.
* Add a density here only when it changes component anatomy rather than a single page layout.
*/
$badge-density: (
height: var(--ui-badge-height),
padding-inline: var(--ui-badge-padding-inline),
font-size: var(--ui-badge-font-size),
);
@layer components {
.ui-badge {
display: inline-flex;
height: map.get($badge-density, height);
align-items: center;
padding-inline: map.get($badge-density, padding-inline);
border-width: 1px;
border-style: solid;
border-radius: 0;
font-size: map.get($badge-density, font-size);
font-weight: 400;
line-height: var(--ui-control-line-height);
}
/* Shared composition helper for a control and its visible label. */
.ui-control-row {
display: inline-flex;
min-width: 0;
align-items: center;
gap: var(--ui-control-gap);
line-height: var(--ui-control-line-height);
}
}
+39 -11
View File
@@ -1,21 +1,21 @@
.ui-button {
gap: var(--ui-control-gap);
border-width: 1px;
border-style: solid;
border-radius: 0;
color: var(--ui-primary-foreground);
font-family: inherit;
font-weight: 400;
line-height: var(--ui-control-line-height);
text-decoration: none;
text-shadow:
1px 1px 0 #000,
0 0 2px #000;
text-shadow: var(--ui-control-text-shadow);
box-shadow: none;
}
.ui-button:focus-visible {
outline: 1px solid var(--ui-ring);
outline-offset: 1px;
color: #eee2aa;
color: var(--ui-control-focus-foreground);
}
.ui-button:active:not(:disabled) {
@@ -44,14 +44,14 @@
}
.ui-button--secondary {
border-color: #4f5128;
background: linear-gradient(180deg, #014f22, #013f1b);
color: #b7d895;
border-color: var(--ui-control-secondary-border);
background: var(--ui-control-secondary-background);
color: var(--ui-control-secondary-foreground);
}
.ui-button--outline {
border-color: var(--ui-border);
background: #101211;
background: var(--ui-control-outline-background);
color: var(--ui-foreground);
}
@@ -62,9 +62,9 @@
}
.ui-button--destructive {
border-color: #6a4829;
background: linear-gradient(180deg, #522d01, #412401);
color: #e2b46e;
border-color: var(--ui-control-destructive-border);
background: var(--ui-control-destructive-background);
color: var(--ui-control-destructive-foreground);
}
.ui-button--ghost {
@@ -83,3 +83,31 @@
color: var(--ui-control-disabled-foreground);
opacity: 1;
}
.ui-button--size-compact {
height: var(--ui-control-height-compact);
min-width: var(--ui-button-min-inline-size, 80px);
padding-inline: var(--ui-control-padding-inline-compact);
font-size: var(--ui-control-font-size-compact);
}
.ui-button--size-default {
height: var(--ui-control-height-default);
min-width: var(--ui-button-min-inline-size, 110px);
padding-inline: var(--ui-control-padding-inline-default);
font-size: var(--ui-control-font-size-default);
}
.ui-button--size-large {
height: var(--ui-control-height-large);
min-width: var(--ui-button-min-inline-size, 168px);
padding-inline: var(--ui-control-padding-inline-large);
font-size: var(--ui-control-font-size-large);
}
.ui-button--size-icon {
width: var(--ui-control-height-default);
min-width: var(--ui-control-height-default);
height: var(--ui-control-height-default);
padding: 0;
}
+1
View File
@@ -1,3 +1,4 @@
@use './app.scss';
@use './icon.scss';
@use './char-img.scss';
@use './components/control';
+261
View File
@@ -2,6 +2,7 @@
[data-theme='dcalc'] {
color-scheme: dark;
/* Global surfaces and readable foreground hierarchy. */
--ui-background: var(--dcalc-black-950);
--ui-foreground: var(--dcalc-text-secondary);
--ui-surface: var(--dcalc-panel);
@@ -22,11 +23,17 @@
--ui-input: #5b472a;
--ui-ring: var(--dcalc-focus);
--ui-overlay: rgb(0 0 0 / 66%);
--ui-info: var(--dcalc-info); /* Informational status text. */
--ui-success: var(--dcalc-success); /* Successful operation status text. */
--ui-warning: var(--dcalc-warning); /* Recoverable warning status text. */
--ui-selected: var(--dcalc-selected); /* Selected item text/marker, independent of focus. */
/* DNF controls intentionally keep near-square corners. */
--ui-radius-sm: 2px;
--ui-radius-md: 2px;
--ui-radius-lg: 2px;
/* Application chrome: panels, title bars, rows and data headers. */
--ui-titlebar-background: linear-gradient(180deg, #5a7099, #415272 55%, #313e58);
--ui-titlebar-foreground: #f0f2ef;
--ui-panel-heading: #111313;
@@ -36,6 +43,7 @@
--ui-table-header: linear-gradient(180deg, #352817 0%, #1b170d 48%, #090a07 100%);
--ui-table-header-foreground: #bc9e58;
/* Shared button/control state contract ported from design/src-web/button. */
--ui-control-background: linear-gradient(180deg, #223768, #122438);
--ui-control-background-hover: linear-gradient(180deg, #2b477f, #213766);
--ui-control-background-active: linear-gradient(180deg, #2b477f, #213766);
@@ -44,6 +52,259 @@
--ui-control-disabled-background: linear-gradient(180deg, #2c2c2c, #222);
--ui-control-disabled-border: #343434;
--ui-control-disabled-foreground: #4d4b4d;
--ui-control-text-shadow: 1px 1px 0 #000, 0 0 2px #000;
--ui-control-focus-foreground: #eee2aa;
--ui-control-secondary-background: linear-gradient(180deg, #014f22, #013f1b);
--ui-control-secondary-border: #4f5128;
--ui-control-secondary-foreground: #b7d895;
--ui-control-outline-background: #101211;
--ui-control-destructive-background: linear-gradient(180deg, #522d01, #412401);
--ui-control-destructive-border: #6a4829;
--ui-control-destructive-foreground: #e2b46e;
/* Text fields, number fields and select triggers. */
--ui-field-background: linear-gradient(#0b0c0d, #000100);
--ui-field-inset: #050505;
--ui-field-focus-inset: #071722;
--ui-field-focus-edge: #24617c;
--ui-field-disabled-background: #101110;
/* Select/list option states. */
--ui-option-divider: #25231f;
--ui-option-highlight-background: #091731;
--ui-option-highlight-foreground: #f3d777;
--ui-option-active-background: #07121b;
--ui-option-active-foreground: #fff0a4;
/* Tabs, including the list-owned animated arrow/line marker. */
--ui-tab-list-background: #090709;
--ui-tab-divider: #584840;
--ui-tab-background: #080808;
--ui-tab-foreground: #9c815a;
--ui-tab-active-foreground: #e4ca90;
--ui-tab-separator: #342f2a;
--ui-tab-marker: #d0a56a;
--ui-tab-marker-start: #9d7a35;
--ui-tab-marker-middle: #a7823f;
--ui-tab-hover-glow: linear-gradient(
90deg,
transparent,
rgb(48 24 8 / 18%) 10%,
rgb(48 24 8 / 72%) 28%,
#301808 50%,
rgb(48 24 8 / 72%) 72%,
rgb(48 24 8 / 18%) 90%,
transparent
);
--ui-tab-selected-glow: radial-gradient(
ellipse 58% 46% at 50% 100%,
rgb(114 97 30 / 94%),
rgb(88 74 23 / 74%) 34%,
rgb(52 44 14 / 38%) 66%,
transparent
);
/* Checkbox visual layers and state transitions. */
--ui-check-edge-top: #635036;
--ui-check-edge-bottom: #3c2e1b;
--ui-check-surface-top: #242424;
--ui-check-surface-bottom: #0f0f0f;
--ui-check-hover-edge-top: #bf9d6a;
--ui-check-hover-edge-bottom: #947853;
--ui-check-hover-surface-top: #3a3a3a;
--ui-check-hover-surface-bottom: #181818;
--ui-check-mark-top: #dfb142;
--ui-check-mark-bottom: #eac75f;
--ui-check-disabled-edge-top: #414141;
--ui-check-disabled-edge-bottom: #252525;
--ui-check-disabled-surface-top: #1f1f1f;
--ui-check-disabled-surface-bottom: #0d0d0d;
/* Radio control shell, disabled slash and selected indicator. */
--ui-radio-border: #614f36;
--ui-radio-surface: linear-gradient(#2e2e2e, #171717);
--ui-radio-hover-border: #d6c889;
--ui-radio-hover-surface: linear-gradient(#4a4a4a, #252525);
--ui-radio-disabled-border: #3b3b3b;
--ui-radio-disabled-mark: #313131;
--ui-radio-disabled-surface: linear-gradient(#252525, #121212);
--ui-radio-mark: linear-gradient(#dbaa3a, #edd067);
--ui-radio-mark-border: linear-gradient(#f0dfa0, #a17e50);
--ui-radio-mark-inset: #272727;
/* Switch track and checked state. Thumb reuses the generic control surface. */
--ui-switch-surface: linear-gradient(#141414, #0f0f0f);
--ui-switch-border: linear-gradient(#846a48, #453520);
--ui-switch-inset: #090909;
--ui-switch-checked-surface: linear-gradient(#eccb65, #eccb65);
--ui-switch-checked-inset: #0d0d0d;
/* Slider coordinate visuals. Geometry stays in the component because Reka owns the track. */
--ui-slider-track: #515351;
--ui-slider-track-shadow: #020202;
--ui-slider-range: #f3d367;
--ui-slider-range-shadow: rgb(243 211 103 / 25%);
--ui-slider-thumb-edge-top: #86693f;
--ui-slider-thumb-edge-bottom: #76593a;
--ui-slider-thumb-top: #21365e;
--ui-slider-thumb-bottom: #1b2b4d;
--ui-slider-thumb-hover-edge-top: #907960;
--ui-slider-thumb-hover-edge-bottom: #80695b;
--ui-slider-thumb-hover-top: #2b467f;
--ui-slider-thumb-hover-bottom: #253b6e;
--ui-slider-disabled-range: #686868;
--ui-slider-disabled-thumb-edge-top: #454545;
--ui-slider-disabled-thumb-edge-bottom: #3e3e3e;
--ui-slider-disabled-thumb-top: #2c2c2c;
--ui-slider-disabled-thumb-bottom: #242424;
/* Reusable one-pixel control depth; kept separate from panel elevation. */
--ui-control-outer-shadow: rgb(0 0 0 / 40%);
--ui-control-inner-highlight: rgb(255 255 255 / 2.5%);
--ui-select-icon-shadow: rgb(0 0 0 / 60%); /* Outer pixel edge around the raster arrow button. */
--ui-check-outer-shadow: rgb(0 0 0 / 30%);
--ui-check-mark-shadow: rgb(230 192 86 / 45%);
/* NumberField spinner and the source-derived +/-10 raster-like surfaces. */
--ui-number-spinner-divider: #4b412d;
--ui-number-step-divider: #2e2d28;
--ui-number-step-hover: rgb(255 255 255 / 3.5%);
--ui-number-step-active: rgb(0 0 0 / 35%);
--ui-number-step-arrow: #b99550;
--ui-number-fast-text: #b69260;
--ui-number-fast-up-background:
linear-gradient(90deg, #3c66b4 0 1px, transparent 1px 23px, #3c66b4 23px) 1px 4px / 24px 9px
no-repeat,
linear-gradient(180deg, #325596, #1d2f53 46% 72%, #2b4378) 1px 4px / 24px 9px no-repeat,
linear-gradient(#000, #000) 0 3px / 26px 11px no-repeat;
--ui-number-fast-down-background:
linear-gradient(90deg, #3e6cbe 0 1px, transparent 1px 23px, #3e6cbe 23px) 1px 1px / 24px 9px
no-repeat,
linear-gradient(180deg, #345a9e, #1d2f53 46% 72%, #2d4782) 1px 1px / 24px 9px no-repeat,
linear-gradient(#000, #000) 0 0 / 26px 11px no-repeat;
--ui-number-fast-chevron-up:
linear-gradient(#000, #000) center 0 / 3px 1px no-repeat,
linear-gradient(90deg, #000 0 1px, #080d17 1px 2px, #2f518f 2px 3px, #080d17 3px 4px, #000 4px)
center 1px / 5px 1px no-repeat,
linear-gradient(90deg, #000 0 1px, #080d17 1px 2px, #2f518f 2px 5px, #080d17 5px 6px, #000 6px)
center 2px / 7px 1px no-repeat;
--ui-number-fast-chevron-down:
linear-gradient(90deg, #000 0 1px, #080d17 1px 2px, #2f518f 2px 5px, #080d17 5px 6px, #000 6px)
center 0 / 7px 1px no-repeat,
linear-gradient(90deg, #000 0 1px, #080d17 1px 2px, #2f518f 2px 3px, #080d17 3px 4px, #000 4px)
center 1px / 5px 1px no-repeat,
linear-gradient(#000, #000) center 2px / 3px 1px no-repeat;
/* Progress and rating feedback surfaces. */
--ui-progress-border: #3c3425;
--ui-progress-background: #0a0b0a;
--ui-progress-fill: linear-gradient(180deg, #9abe23, #63820a);
--ui-progress-fill-inset: #cce43c;
--ui-progress-marker: #d7bd61;
--ui-rating-background: #1a1917;
--ui-rating-highlight: rgb(255 255 255 / 3%);
/* Tooltip, select popup and menu layers. */
--ui-tooltip-border: #6c5c3d;
--ui-tooltip-background: #020302;
--ui-tooltip-shadow: #000;
--ui-tooltip-foreground: #f5f5ef;
--ui-popup-border: #5a4828;
--ui-popup-background: #050606;
--ui-popup-shadow: #000;
--ui-menu-border: rgb(19 17 13 / 92%);
--ui-menu-background: rgb(48 46 38 / 72%);
--ui-menu-shadow: rgb(0 0 0 / 78%);
--ui-menu-highlight-border: #000;
--ui-menu-highlight-background: rgb(15 14 12 / 28%);
--ui-menu-highlight-foreground: #ffefad;
--ui-menu-highlight-inset: rgb(132 113 76 / 13%);
--ui-menu-danger: #eb7c79;
/* Dialog window chrome. The focused frame animation remains an original raster asset. */
--ui-dialog-border: #6c5a37;
--ui-dialog-background: #050606;
--ui-dialog-shadow: rgb(0 0 0 / 80%);
--ui-dialog-inset: #0e1418;
--ui-dialog-titlebar-border: #1d2735;
--ui-dialog-titlebar-background: linear-gradient(#303b50, #263247 56%, #1d2839);
--ui-dialog-titlebar-focused-background: linear-gradient(#6079a5, #435a80 55%, #30425f);
--ui-dialog-titlebar-focused-foreground: #f4f6f2;
--ui-dialog-titlebar-text-shadow: #000;
--ui-dialog-close-ring: rgb(225 204 135 / 72%);
--ui-dialog-footer-border: #332c22;
--ui-dialog-footer-background: linear-gradient(#20201d, #11110f);
--ui-tabs-content-background: rgb(0 0 0 / 22%);
/* Item-slot emphasis and lock overlays. Rarity frame colors remain asset/data semantics. */
--ui-item-slot-background: #030404;
--ui-item-slot-emphasis: #ffa800;
--ui-item-slot-emphasis-strong: #fffc2a;
--ui-item-slot-emphasis-shadow-strong: rgb(255 168 0 / 88%);
--ui-item-slot-emphasis-shadow: rgb(255 168 0 / 46%);
--ui-item-slot-emphasis-inset: rgb(255 168 0 / 70%);
--ui-item-slot-lock-overlay: rgb(0 0 0 / 68%);
/* Dense table anatomy and selectable-row state colors. */
--ui-table-border: #4a3c26;
--ui-table-background: #030403;
--ui-table-foreground: #d2c78f;
--ui-table-row-divider: #2c2b26;
--ui-table-cell-divider: #292720;
--ui-table-row-hover: linear-gradient(90deg, #132b43, #0b1824 58%, #070a0d);
--ui-table-row-hover-outline: #3b91bd;
--ui-table-row-selected: linear-gradient(90deg, #352b11, #161207 60%, #090a08);
--ui-table-row-selected-outline: #d6ad37;
--ui-table-row-selected-inset: #6d5218;
--ui-table-header-border: #070806;
--ui-table-header-inset: #4a3c26;
--ui-table-header-highlight: #65502a;
/* Autocomplete, navigation and collapsible data-section compositions. */
--ui-composition-border: #292b27;
--ui-composition-background: #070807;
--ui-navigation-border: #20251f;
--ui-navigation-divider: #25221a;
--ui-navigation-foreground: #c5b573;
--ui-navigation-selected-background: linear-gradient(90deg, #80601b, #1b160b 72%);
--ui-navigation-selected-foreground: #f1d66e;
--ui-disclosure-border: #4b3d26;
--ui-disclosure-background: #070807;
--ui-disclosure-header-background: linear-gradient(#2a2925, #10110f);
--ui-disclosure-divider: #25221b;
--ui-disclosure-row-foreground: #c9bb7e;
--ui-disclosure-row-divider: #24231f;
--ui-disclosure-row-focus: #07131b;
--ui-disclosure-row-selected: #302711;
--ui-disclosure-row-selected-foreground: #f1d667;
--ui-disclosure-state: #857a57;
/* Reka ScrollArea skin ported from design/src-web/scrollbar. */
--ui-scrollbar-border: #2b2924;
--ui-scrollbar-background: #050606;
--ui-scrollbar-arrow-background: linear-gradient(#24241f, #10110f);
--ui-scrollbar-arrow: #a98b46;
--ui-scrollbar-track: linear-gradient(90deg, #080908, #151513, #070807);
--ui-scrollbar-thumb-border: #68532d;
--ui-scrollbar-thumb: linear-gradient(90deg, #1b1d1c, #363530, #161817);
/* Settings group and row states. */
--ui-settings-border: #2c2c28;
--ui-settings-background: #050606;
--ui-settings-section-border: #4c402a;
--ui-settings-section-background: linear-gradient(#22231f, #0e0f0e);
--ui-settings-section-foreground: #e4cc7c;
--ui-settings-row-divider: #23231f;
--ui-settings-row-foreground: #bcae73;
--ui-settings-row-alt: #171817;
--ui-settings-row-focus: #071017;
/* ShopItemRow frame layers. Values are a direct port of the source component. */
--ui-shop-foreground: #cabd82;
--ui-shop-frame-rest-outer: #2e2e2e;
--ui-shop-frame-rest-middle: #191919;
--ui-shop-frame-rest-accent: #050505;
--ui-shop-frame-rest-surface: linear-gradient(180deg, #070707 0%, #080808 100%);
--ui-shop-frame-alt-outer: #60503a;
--ui-shop-frame-alt-middle: #342b1f;
--ui-shop-frame-alt-surface: linear-gradient(180deg, #0b0b0b 0%, #0e0e0e 100%);
--ui-shop-frame-hover-outer: #000820;
--ui-shop-frame-hover-middle: #082068;
--ui-shop-frame-hover-accent: #1888b8;
--ui-shop-frame-hover-inner: #082040;
--ui-shop-frame-hover-surface: #000810;
--ui-shop-frame-selected-outer: #201808;
--ui-shop-frame-selected-middle: #604818;
--ui-shop-frame-selected-accent: #e8a828;
--ui-shop-frame-selected-inner: #302008;
--ui-shop-frame-selected-surface: #100800;
--ui-shop-frame-shadow: rgb(0 0 0 / 42%);
--ui-shop-action-background: linear-gradient(180deg, #1261a4, #071f3b);
--ui-shop-action-hover-background: linear-gradient(180deg, #1d9bdb, #0b325e);
--ui-shop-action-inset: #0f0f0f;
/* Panel elevation, intentionally separate from popup and dialog elevation. */
--ui-panel-background: linear-gradient(180deg, rgb(20 22 22 / 98%), rgb(7 8 8 / 98%));
--ui-panel-shadow: inset 0 1px 0 rgb(214 196 135 / 6%), 0 12px 28px rgb(0 0 0 / 32%);
}
+281
View File
@@ -1,6 +1,8 @@
[data-theme='midnight'] {
color-scheme: dark;
/* Midnight implements the same semantic contract as dcalc; components must not branch by theme. */
/* Global surfaces and readable foreground hierarchy. */
--ui-background: #080b12;
--ui-foreground: #d9e3f2;
--ui-surface: #111827;
@@ -21,7 +23,12 @@
--ui-input: #354b6e;
--ui-ring: #6fb5e6;
--ui-overlay: rgb(2 6 14 / 72%);
--ui-info: #79c7f2; /* Informational status text. */
--ui-success: #6fc58a; /* Successful operation status text. */
--ui-warning: #d4a66d; /* Recoverable warning status text. */
--ui-selected: #9ac8ef; /* Selected item text/marker, independent of focus. */
/* Shared button/control states. */
--ui-control-background: linear-gradient(180deg, #294d80, #172b4b);
--ui-control-background-hover: linear-gradient(180deg, #376aa7, #1e3b64);
--ui-control-background-active: linear-gradient(180deg, #244e82, #142b4a);
@@ -30,6 +37,280 @@
--ui-control-disabled-background: linear-gradient(180deg, #303846, #202733);
--ui-control-disabled-border: #3f4958;
--ui-control-disabled-foreground: #748094;
--ui-control-text-shadow: 1px 1px 0 #020611, 0 0 2px #020611;
--ui-control-focus-foreground: #ffffff;
--ui-control-secondary-background: linear-gradient(180deg, #185d55, #10433e);
--ui-control-secondary-border: #3f817b;
--ui-control-secondary-foreground: #d4fff8;
--ui-control-outline-background: #101827;
--ui-control-destructive-background: linear-gradient(180deg, #733f49, #4d2830);
--ui-control-destructive-border: #9c5c68;
--ui-control-destructive-foreground: #ffe2e7;
/* Fields and option states. */
--ui-field-background: linear-gradient(#0b1220, #070b13);
--ui-field-inset: #050914;
--ui-field-focus-inset: #101e33;
--ui-field-focus-edge: #6fb5e6;
--ui-field-disabled-background: #151b26;
--ui-option-divider: #26364f;
--ui-option-highlight-background: #19365d;
--ui-option-highlight-foreground: #e4f2ff;
--ui-option-active-background: #102845;
--ui-option-active-foreground: #ffffff;
/* Tabs and animated marker. */
--ui-tab-list-background: #0b111d;
--ui-tab-divider: #40577a;
--ui-tab-background: #0a101b;
--ui-tab-foreground: #8da0bc;
--ui-tab-active-foreground: #dcecff;
--ui-tab-separator: #2b3b55;
--ui-tab-marker: #6fb5e6;
--ui-tab-marker-start: #315c97;
--ui-tab-marker-middle: #6fb5e6;
--ui-tab-hover-glow: linear-gradient(
90deg,
transparent,
rgb(31 67 111 / 18%) 10%,
rgb(31 67 111 / 72%) 28%,
#16375f 50%,
rgb(31 67 111 / 72%) 72%,
rgb(31 67 111 / 18%) 90%,
transparent
);
--ui-tab-selected-glow: radial-gradient(
ellipse 58% 46% at 50% 100%,
rgb(69 120 176 / 94%),
rgb(49 92 151 / 74%) 34%,
rgb(31 63 105 / 38%) 66%,
transparent
);
/* Checkbox, radio and switch state layers. */
--ui-check-edge-top: #526f98;
--ui-check-edge-bottom: #354b6e;
--ui-check-surface-top: #202b3b;
--ui-check-surface-bottom: #101722;
--ui-check-hover-edge-top: #9ac8ef;
--ui-check-hover-edge-bottom: #658db8;
--ui-check-hover-surface-top: #34475f;
--ui-check-hover-surface-bottom: #1b2a3e;
--ui-check-mark-top: #8dc9f0;
--ui-check-mark-bottom: #dcecff;
--ui-check-disabled-edge-top: #465163;
--ui-check-disabled-edge-bottom: #303846;
--ui-check-disabled-surface-top: #29313d;
--ui-check-disabled-surface-bottom: #1b222d;
--ui-radio-border: #526f98;
--ui-radio-surface: linear-gradient(#29384d, #151f2e);
--ui-radio-hover-border: #9ac8ef;
--ui-radio-hover-surface: linear-gradient(#405b7b, #263b57);
--ui-radio-disabled-border: #3f4958;
--ui-radio-disabled-mark: #465163;
--ui-radio-disabled-surface: linear-gradient(#303846, #202733);
--ui-radio-mark: linear-gradient(#6fb5e6, #dcecff);
--ui-radio-mark-border: linear-gradient(#c5e7ff, #526f98);
--ui-radio-mark-inset: #17243a;
--ui-switch-surface: linear-gradient(#1a2435, #101722);
--ui-switch-border: linear-gradient(#526f98, #354b6e);
--ui-switch-inset: #080d16;
--ui-switch-checked-surface: linear-gradient(#6fb5e6, #6fb5e6);
--ui-switch-checked-inset: #17243a;
/* Slider, number-field, progress and rating feedback. */
--ui-slider-track: #40516a;
--ui-slider-track-shadow: #050914;
--ui-slider-range: #86c8f2;
--ui-slider-range-shadow: rgb(111 181 230 / 28%);
--ui-slider-thumb-edge-top: #6686ad;
--ui-slider-thumb-edge-bottom: #456487;
--ui-slider-thumb-top: #315c97;
--ui-slider-thumb-bottom: #1d3d68;
--ui-slider-thumb-hover-edge-top: #9ac8ef;
--ui-slider-thumb-hover-edge-bottom: #658db8;
--ui-slider-thumb-hover-top: #477cb5;
--ui-slider-thumb-hover-bottom: #295987;
--ui-slider-disabled-range: #596474;
--ui-slider-disabled-thumb-edge-top: #505b6b;
--ui-slider-disabled-thumb-edge-bottom: #3f4958;
--ui-slider-disabled-thumb-top: #384352;
--ui-slider-disabled-thumb-bottom: #29313d;
--ui-control-outer-shadow: rgb(0 0 0 / 42%);
--ui-control-inner-highlight: rgb(255 255 255 / 4%);
--ui-select-icon-shadow: rgb(0 0 0 / 60%); /* Keeps the arrow button legible on blue fields. */
--ui-check-outer-shadow: rgb(0 0 0 / 34%);
--ui-check-mark-shadow: rgb(111 181 230 / 42%);
--ui-number-spinner-divider: #40577a;
--ui-number-step-divider: #2b3b55;
--ui-number-step-hover: rgb(255 255 255 / 5%);
--ui-number-step-active: rgb(0 0 0 / 38%);
--ui-number-step-arrow: #9ac8ef;
--ui-number-fast-text: #c9d8ef;
--ui-number-fast-up-background:
linear-gradient(90deg, #5786bd 0 1px, transparent 1px 23px, #5786bd 23px) 1px 4px / 24px 9px
no-repeat,
linear-gradient(180deg, #376aa7, #1e3b64 46% 72%, #294e80) 1px 4px / 24px 9px no-repeat,
linear-gradient(#050914, #050914) 0 3px / 26px 11px no-repeat;
--ui-number-fast-down-background:
linear-gradient(90deg, #6b9ace 0 1px, transparent 1px 23px, #6b9ace 23px) 1px 1px / 24px 9px
no-repeat,
linear-gradient(180deg, #477cb5, #1e3b64 46% 72%, #315c97) 1px 1px / 24px 9px no-repeat,
linear-gradient(#050914, #050914) 0 0 / 26px 11px no-repeat;
--ui-number-fast-chevron-up:
linear-gradient(#050914, #050914) center 0 / 3px 1px no-repeat,
linear-gradient(
90deg,
#050914 0 1px,
#101b2d 1px 2px,
#5786bd 2px 3px,
#101b2d 3px 4px,
#050914 4px
)
center 1px / 5px 1px no-repeat,
linear-gradient(
90deg,
#050914 0 1px,
#101b2d 1px 2px,
#5786bd 2px 5px,
#101b2d 5px 6px,
#050914 6px
)
center 2px / 7px 1px no-repeat;
--ui-number-fast-chevron-down:
linear-gradient(
90deg,
#050914 0 1px,
#101b2d 1px 2px,
#5786bd 2px 5px,
#101b2d 5px 6px,
#050914 6px
)
center 0 / 7px 1px no-repeat,
linear-gradient(
90deg,
#050914 0 1px,
#101b2d 1px 2px,
#5786bd 2px 3px,
#101b2d 3px 4px,
#050914 4px
)
center 1px / 5px 1px no-repeat,
linear-gradient(#050914, #050914) center 2px / 3px 1px no-repeat;
--ui-progress-border: #40577a;
--ui-progress-background: #0b111d;
--ui-progress-fill: linear-gradient(180deg, #4d9ac6, #315c97);
--ui-progress-fill-inset: #9ac8ef;
--ui-progress-marker: #dcecff;
--ui-rating-background: #151d2a;
--ui-rating-highlight: rgb(255 255 255 / 4%);
/* Tooltip, select popup and dropdown menu layers. */
--ui-tooltip-border: #526f98;
--ui-tooltip-background: #080d16;
--ui-tooltip-shadow: #020611;
--ui-tooltip-foreground: #edf5ff;
--ui-popup-border: #40577a;
--ui-popup-background: #0c1220;
--ui-popup-shadow: #020611;
--ui-menu-border: rgb(34 49 72 / 94%);
--ui-menu-background: rgb(20 31 48 / 90%);
--ui-menu-shadow: rgb(0 0 0 / 76%);
--ui-menu-highlight-border: #40577a;
--ui-menu-highlight-background: rgb(49 92 151 / 34%);
--ui-menu-highlight-foreground: #ffffff;
--ui-menu-highlight-inset: rgb(143 196 241 / 14%);
--ui-menu-danger: #f08b94;
/* Dialog window chrome and focused title bar. */
--ui-dialog-border: #526f98;
--ui-dialog-background: #080d16;
--ui-dialog-shadow: rgb(0 0 0 / 82%);
--ui-dialog-inset: #17243a;
--ui-dialog-titlebar-border: #263b57;
--ui-dialog-titlebar-background: linear-gradient(#34475f, #263b57 56%, #1b2a3e);
--ui-dialog-titlebar-focused-background: linear-gradient(#5e83ad, #405f86 55%, #2d4869);
--ui-dialog-titlebar-focused-foreground: #f2f8ff;
--ui-dialog-titlebar-text-shadow: #020611;
--ui-dialog-close-ring: rgb(154 200 239 / 78%);
--ui-dialog-footer-border: #2b3b55;
--ui-dialog-footer-background: linear-gradient(#1a2435, #101722);
--ui-tabs-content-background: rgb(2 6 14 / 30%);
/* Item and dense data-display states. */
--ui-item-slot-background: #080d16;
--ui-item-slot-emphasis: #6fb5e6;
--ui-item-slot-emphasis-strong: #dcecff;
--ui-item-slot-emphasis-shadow-strong: rgb(111 181 230 / 84%);
--ui-item-slot-emphasis-shadow: rgb(111 181 230 / 44%);
--ui-item-slot-emphasis-inset: rgb(154 200 239 / 68%);
--ui-item-slot-lock-overlay: rgb(2 6 14 / 72%);
--ui-table-border: #40577a;
--ui-table-background: #080d16;
--ui-table-foreground: #c9d8ef;
--ui-table-row-divider: #26364f;
--ui-table-cell-divider: #26364f;
--ui-table-row-hover: linear-gradient(90deg, #19365d, #102845 58%, #0a1524);
--ui-table-row-hover-outline: #6fb5e6;
--ui-table-row-selected: linear-gradient(90deg, #294d80, #172b4b 60%, #0d1929);
--ui-table-row-selected-outline: #9ac8ef;
--ui-table-row-selected-inset: #315c97;
--ui-table-header-border: #111c2c;
--ui-table-header-inset: #40577a;
--ui-table-header-highlight: #658db8;
/* Autocomplete, navigation and collapsible data-section compositions. */
--ui-composition-border: #26364f;
--ui-composition-background: #0b111d;
--ui-navigation-border: #26364f;
--ui-navigation-divider: #26364f;
--ui-navigation-foreground: #c9d8ef;
--ui-navigation-selected-background: linear-gradient(90deg, #315c97, #172b4b 72%);
--ui-navigation-selected-foreground: #ffffff;
--ui-disclosure-border: #40577a;
--ui-disclosure-background: #0b111d;
--ui-disclosure-header-background: linear-gradient(#29384d, #151f2e);
--ui-disclosure-divider: #26364f;
--ui-disclosure-row-foreground: #c9d8ef;
--ui-disclosure-row-divider: #26364f;
--ui-disclosure-row-focus: #102845;
--ui-disclosure-row-selected: #19365d;
--ui-disclosure-row-selected-foreground: #ffffff;
--ui-disclosure-state: #8da0bc;
/* Reka ScrollArea skin. */
--ui-scrollbar-border: #26364f;
--ui-scrollbar-background: #080d16;
--ui-scrollbar-arrow-background: linear-gradient(#29384d, #151f2e);
--ui-scrollbar-arrow: #9ac8ef;
--ui-scrollbar-track: linear-gradient(90deg, #080d16, #172131, #080d16);
--ui-scrollbar-thumb-border: #526f98;
--ui-scrollbar-thumb: linear-gradient(90deg, #1b2a3e, #405b7b, #17243a);
/* Settings group and row states. */
--ui-settings-border: #26364f;
--ui-settings-background: #080d16;
--ui-settings-section-border: #40577a;
--ui-settings-section-background: linear-gradient(#29384d, #151f2e);
--ui-settings-section-foreground: #dcecff;
--ui-settings-row-divider: #26364f;
--ui-settings-row-foreground: #c9d8ef;
--ui-settings-row-alt: #111827;
--ui-settings-row-focus: #102845;
/* ShopItemRow frame layers. */
--ui-shop-foreground: #c9d8ef;
--ui-shop-frame-rest-outer: #303846;
--ui-shop-frame-rest-middle: #202733;
--ui-shop-frame-rest-accent: #080d16;
--ui-shop-frame-rest-surface: linear-gradient(180deg, #0b111d 0%, #0d1420 100%);
--ui-shop-frame-alt-outer: #526f98;
--ui-shop-frame-alt-middle: #29384d;
--ui-shop-frame-alt-surface: linear-gradient(180deg, #111827 0%, #151d2a 100%);
--ui-shop-frame-hover-outer: #0a1c34;
--ui-shop-frame-hover-middle: #1f4b7b;
--ui-shop-frame-hover-accent: #6fb5e6;
--ui-shop-frame-hover-inner: #234e79;
--ui-shop-frame-hover-surface: #08182b;
--ui-shop-frame-selected-outer: #172b4b;
--ui-shop-frame-selected-middle: #315c97;
--ui-shop-frame-selected-accent: #9ac8ef;
--ui-shop-frame-selected-inner: #1d3d68;
--ui-shop-frame-selected-surface: #0d1929;
--ui-shop-frame-shadow: rgb(0 0 0 / 44%);
--ui-shop-action-background: linear-gradient(180deg, #315c97, #172b4b);
--ui-shop-action-hover-background: linear-gradient(180deg, #477cb5, #295987);
--ui-shop-action-inset: #080d16;
/* Panel elevation. */
--ui-panel-background: linear-gradient(180deg, rgb(17 24 39 / 98%), rgb(7 12 22 / 98%));
--ui-panel-shadow: inset 0 1px 0 rgb(143 196 241 / 8%), 0 12px 30px rgb(0 0 0 / 40%);
}
+117
View File
@@ -1,4 +1,5 @@
:root {
/* Captured DCalc palette. Theme files map these physical colors to semantic roles. */
--dcalc-black-950: #050505;
--dcalc-black-900: #090808;
--dcalc-panel: #131515;
@@ -24,6 +25,7 @@
--dcalc-warning: #c28954;
--dcalc-danger: #bb3331;
/* Item rarity colors are data semantics, not generic foreground colors. */
--dcalc-quality-normal: #ffffff;
--dcalc-quality-advanced: #68d5ed;
--dcalc-quality-rare: #9879da;
@@ -31,4 +33,119 @@
--dcalc-quality-legend: #ff7700;
--dcalc-quality-epic: #fcb600;
--dcalc-quality-chronicle: #ff6666;
/*
* Shared component metrics.
* Heights keep the source pixel grid; callers control inline width through normal layout or
* component-specific CSS variables instead of overriding internal selectors.
*/
--ui-control-height-compact: 24px;
--ui-control-height-default: 30px;
--ui-control-height-large: 44px;
--ui-control-padding-inline-compact: 10px;
--ui-control-padding-inline-default: 14px;
--ui-control-padding-inline-large: 20px;
--ui-control-font-size-compact: 12px;
--ui-control-font-size-default: 13px;
--ui-control-font-size-large: 16px;
--ui-control-line-height: 1;
--ui-control-gap: 7px;
--ui-control-text-offset-y: 0px; /* Optical font correction only; never use for layout. */
--ui-field-padding-inline: 9px;
--ui-option-height: 27px;
--ui-tabs-height: 40px;
--ui-tab-height: 30px;
/*
* Component geometry defaults. Every value is consumed through var(), so a component instance
* can opt into a different density without reaching into scoped internals.
*/
--ui-badge-height: 18px;
--ui-badge-padding-inline: 7px;
--ui-badge-font-size: 10px;
--ui-checkbox-size: 16px;
--ui-checkbox-mark-inline-size: 18px;
--ui-checkbox-mark-block-size: 14px;
--ui-radio-size: 14px;
--ui-switch-inline-size: 28px;
--ui-switch-block-size: 16px;
--ui-switch-thumb-size: 12px;
--ui-switch-thumb-travel: 13px;
--ui-rating-min-block-size: 46px;
--ui-rating-item-size: 24px;
--ui-slider-min-inline-size: 160px;
--ui-slider-block-size: 34px;
--ui-slider-control-size: 17px;
--ui-slider-track-size: 3px;
--ui-slider-value-min-inline-size: 40px;
--ui-progress-block-size: 14px;
--ui-tooltip-max-inline-size: 260px;
--ui-menu-min-inline-size: 190px;
--ui-menu-item-block-size: 28px;
--ui-select-icon-inline-size: 21px;
--ui-select-icon-block-size: 18px;
--ui-select-glyph-inline-size: 11px;
--ui-select-glyph-block-size: 6px;
--ui-item-slot-size: 40px;
--ui-item-image-size: 28px;
--ui-item-frame-inset: 5px;
--ui-table-row-min-block-size: 38px;
--ui-table-header-block-size: 30px;
--ui-table-scroll-inline-size: 560px;
--ui-panel-heading-block-size: 46px;
--ui-dialog-viewport-inline-size: 92vw;
--ui-dialog-max-inline-size: 620px;
--ui-dialog-header-block-size: 28px;
--ui-dialog-footer-min-block-size: 38px;
--ui-dialog-effect-inline-size: 1100px;
--ui-dialog-close-size: 20px;
--ui-dialog-close-glyph-inline-size: 10px;
--ui-dialog-close-glyph-block-size: 9px;
--ui-tab-marker-inline-size: 39px;
--ui-tab-marker-block-size: 5px;
--ui-tab-marker-arrow-inline-size: 9px;
--ui-tab-marker-arrow-block-size: 4px;
--ui-number-fast-inline-size: 26px;
--ui-number-fast-block-size: 28px;
--ui-number-fast-step-block-size: 14px;
--ui-autocomplete-max-block-size: 240px;
--ui-autocomplete-trigger-gap: 4px;
--ui-autocomplete-indicator-font-size: 8px;
--ui-navigation-inline-size: 250px;
--ui-navigation-row-block-size: 48px;
--ui-navigation-icon-size: 24px;
--ui-navigation-indicator-inline-size: 12px;
--ui-navigation-padding-inline: 13px;
--ui-disclosure-header-min-block-size: 32px;
--ui-disclosure-row-block-size: 25px;
--ui-disclosure-icon-size: 13px;
--ui-disclosure-state-font-size: 10px;
--ui-disclosure-content-padding-block: 7px;
--ui-disclosure-row-gap: 6px;
--ui-disclosure-depth-step: 16px;
--ui-scrollbar-inline-size: 15px;
--ui-scrollbar-arrow-block-size: 18px;
--ui-scroll-area-block-size: 250px;
--ui-scrollbar-arrow-inline-size: 4px;
--ui-scrollbar-arrow-glyph-block-size: 5px;
--ui-scrollbar-thumb-gutter: 2px;
--ui-settings-section-block-size: 34px;
--ui-settings-row-min-block-size: 38px;
--ui-settings-label-min-inline-size: 190px;
--ui-settings-control-min-inline-size: 210px;
--ui-settings-row-padding-block: 5px;
--ui-settings-row-padding-inline: 10px;
--ui-settings-indent-step: 14px;
--ui-shop-row-min-block-size: 48px;
--ui-shop-row-scroll-inline-size: 760px;
--ui-shop-row-gap: 4px;
--ui-shop-row-grid: 42px 36px 48px minmax(130px, 1.55fr) 72px 0.92fr 1.1fr;
--ui-shop-row-padding-inline: 18px;
--ui-shop-cell-padding-inline: 7px;
--ui-shop-font-size: 11px;
--ui-shop-icon-inline-size: 36px;
--ui-shop-icon-block-size: 38px;
--ui-shop-action-inline-size: 28px;
--ui-shop-action-block-size: 24px;
--ui-shop-action-icon-size: 16px;
}