Feature(custom): optimize image process setting page

This commit is contained in:
Kuingsmile
2026-01-25 12:58:38 +08:00
parent 31d72676a8
commit d0853cca1f
11 changed files with 810 additions and 943 deletions

View File

@@ -53,21 +53,31 @@
</div> </div>
<div class="flex justify-end gap-1.5"> <div class="flex justify-end gap-1.5">
<button v-if="currentStep > 0" class="guide-btn secondary" @click="handlePrevious"> <CustomButton
<ChevronLeftIcon :size="16" /> v-if="currentStep > 0"
{{ t('guide.previous') }} type="secondary"
</button> :icon="ChevronLeftIcon"
<button class="guide-btn outline" @click="handleSkip"> :text="t('guide.previous')"
{{ t('guide.skip') }} class="p-2!"
</button> @click="handlePrevious"
<button v-if="currentStep < steps.length - 1" class="guide-btn primary" @click="handleNext"> />
{{ t('guide.next') }} <CustomButton class="p-2!" type="secondary" :text="t('guide.skip')" @click="handleSkip" />
<ChevronRightIcon :size="16" /> <CustomButton
</button> v-if="currentStep < steps.length - 1"
<button v-else class="guide-btn success" @click="handleFinish"> type="primary"
<CheckCircleIcon :size="16" /> class="p-2!"
{{ t('guide.finish') }} :icon="ChevronRightIcon"
</button> :text="t('guide.next')"
@click="handleNext"
/>
<CustomButton
v-else
type="primary"
class="p-2!"
:icon="CheckCircleIcon"
:text="t('guide.finish')"
@click="handleFinish"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -93,6 +103,8 @@ import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import CustomButton from '@/components/common/CustomButton.vue'
const { t } = useI18n() const { t } = useI18n()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@@ -363,5 +375,3 @@ onMounted(async () => {
window.addEventListener('resize', updateSpotlight) window.addEventListener('resize', updateSpotlight)
}) })
</script> </script>
<style scoped src="./css/FirstTimeGuide.css"></style>

File diff suppressed because it is too large Load Diff

View File

@@ -55,20 +55,14 @@
</div> </div>
</div> </div>
<div class="flex flex-wrap gap-3 p-4"> <div class="flex flex-wrap justify-center gap-3 p-2">
<button <CustomButton type="secondary" :text="t('common.cancel')" @click="handleInputBoxCancel" />
class="flex-1 rounded-sm border-none bg-danger/50 p-2.5 text-sm font-semibold text-secondary transition-colors duration-fast ease-apple hover:bg-danger/70" <CustomButton
@click="handleInputBoxCancel" type="primary"
>
{{ t('common.cancel') }}
</button>
<button
class="flex-1 rounded-sm border-none bg-accent p-2.5 text-sm font-semibold text-main transition-colors duration-fast ease-apple hover:bg-accent-hover disabled:cursor-not-allowed disabled:opacity-60"
:disabled="!inputBoxValue.trim()" :disabled="!inputBoxValue.trim()"
:text="t('common.confirm')"
@click="handleInputBoxConfirm" @click="handleInputBoxConfirm"
> />
{{ t('common.confirm') }}
</button>
</div> </div>
</div> </div>
</Transition> </Transition>
@@ -82,6 +76,7 @@ import { XIcon } from 'lucide-vue-next'
import { nextTick, onBeforeMount, onBeforeUnmount, reactive, ref, useTemplateRef } from 'vue' import { nextTick, onBeforeMount, onBeforeUnmount, reactive, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import CustomButton from '@/components/common/CustomButton.vue'
import $bus from '@/utils/bus' import $bus from '@/utils/bus'
import { SHOW_INPUT_BOX, SHOW_INPUT_BOX_RESPONSE } from '@/utils/constant' import { SHOW_INPUT_BOX, SHOW_INPUT_BOX_RESPONSE } from '@/utils/constant'
import { getConfig } from '@/utils/dataSender' import { getConfig } from '@/utils/dataSender'

View File

@@ -26,20 +26,22 @@
class="flex flex-wrap items-center justify-between rounded-sm border border-border bg-bg p-3 transition-all duration-fast ease-apple hover:border-accent hover:bg-surface" class="flex flex-wrap items-center justify-between rounded-sm border border-border bg-bg p-3 transition-all duration-fast ease-apple hover:border-accent hover:bg-surface"
> >
<label class="m-0 flex-1 text-sm font-medium text-main">{{ picbed.name }}</label> <label class="m-0 flex-1 text-sm font-medium text-main">{{ picbed.name }}</label>
<!-- Checkbox input --> <!-- Checkbox input -->
<label v-if="inputType === 'checkbox'" class="m-0 flex cursor-pointer items-center p-0 select-none"> <div v-if="inputType === 'checkbox'" class="flex items-center rounded-xl">
<input <label
:checked="getMapValue(mapField, picbed.type, defaultValue)" class="flex cursor-pointer items-center gap-4 rounded-lg border border-border transition-all duration-200 ease-apple hover:border-accent"
type="checkbox" >
class="peer hidden" <input
@change="e => handleMapChange(picbed.type, (e.target as HTMLInputElement).checked)" :checked="getMapValue(mapField, picbed.type, defaultValue)"
/> type="checkbox"
<span class="peer hidden"
class="relative h-[24px] w-[44px] shrink-0 rounded-lg bg-gray-300 bg-linear-to-r transition-all duration-medium ease-standard peer-checked:bg-accent peer-checked:shadow-lg before:absolute before:top-[2px] before:left-[2px] before:h-[20px] before:w-[20px] before:rounded-full before:bg-linear-to-r before:from-white before:to-gray-100 before:shadow-md before:transition-all before:duration-medium before:ease-standard peer-checked:before:translate-x-[20px]" @change="e => handleMapChange(picbed.type, (e.target as HTMLInputElement).checked)"
/> />
</label> <span
class="relative h-[21px] w-[44px] shrink-0 rounded-full bg-gray-400/80 shadow-sm transition-all duration-medium ease-standard peer-checked:bg-accent peer-checked:shadow-[inset_0_1px_3px_rgba(0,0,0,0.1),0_2px_8px_color-mix(in_srgb,var(--color-accent),transparent_30%)] before:absolute before:top-[2px] before:left-[2px] before:h-[17px] before:w-[17px] before:rounded-full before:bg-white before:shadow-sm before:transition-all before:duration-200 before:ease-apple before:content-[''] peer-checked:before:translate-x-[24px]"
/>
</label>
</div>
<!-- Range input --> <!-- Range input -->
<div v-else-if="inputType === 'range'" class="flex flex-wrap items-center gap-2 rounded-sm shadow-sm"> <div v-else-if="inputType === 'range'" class="flex flex-wrap items-center gap-2 rounded-sm shadow-sm">
<input <input

View File

@@ -9,7 +9,7 @@
class="my-3 h-[8px] w-full appearance-none rounded-sm bg-linear-to-l from-accent transition-colors duration-150 ease-apple outline-none [&::--moz-range-thumb]:shadow-sm [&::-moz-range-thumb]:h-[22px] [&::-moz-range-thumb]:w-[22px] [&::-moz-range-thumb]:cursor-pointer [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border [&::-moz-range-thumb]:border-border [&::-moz-range-thumb]:bg-white [&::-webkit-slider-thumb]:h-[22px] [&::-webkit-slider-thumb]:w-[22px] [&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border [&::-webkit-slider-thumb]:border-border [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-sm [&::-webkit-slider-thumb]:transition-all [&::-webkit-slider-thumb]:duration-200 [&::-webkit-slider-thumb]:ease-apple [&::-webkit-slider-thumb:hover]:scale-105 [&::-webkit-slider-thumb:hover]:shadow-md" class="my-3 h-[8px] w-full appearance-none rounded-sm bg-linear-to-l from-accent transition-colors duration-150 ease-apple outline-none [&::--moz-range-thumb]:shadow-sm [&::-moz-range-thumb]:h-[22px] [&::-moz-range-thumb]:w-[22px] [&::-moz-range-thumb]:cursor-pointer [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border [&::-moz-range-thumb]:border-border [&::-moz-range-thumb]:bg-white [&::-webkit-slider-thumb]:h-[22px] [&::-webkit-slider-thumb]:w-[22px] [&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border [&::-webkit-slider-thumb]:border-border [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-sm [&::-webkit-slider-thumb]:transition-all [&::-webkit-slider-thumb]:duration-200 [&::-webkit-slider-thumb]:ease-apple [&::-webkit-slider-thumb:hover]:scale-105 [&::-webkit-slider-thumb:hover]:shadow-md"
/> />
<div <div
class="mt-2 inline-flex min-w-14 items-center justify-center rounded-md bg-accent px-2 py-1.5 text-sm font-semibold text-white shadow-sm" class="mt-2 inline-flex max-w-16 min-w-14 items-center justify-center rounded-md bg-accent px-2 py-1.5 text-sm font-semibold text-white shadow-sm"
> >
{{ showValue }} {{ showValue }}
</div> </div>

View File

@@ -1,12 +1,15 @@
<template> <template>
<div class="flex items-center rounded-xl hover:bg-surface hover:shadow-sm"> <div class="flex items-center rounded-xl" :class="noHover ? '' : 'hover:bg-surface hover:shadow-sm'">
<label <label
class="flex cursor-pointer items-center gap-4 rounded-lg border border-border p-4 transition-all duration-200 ease-apple hover:border-accent" class="flex cursor-pointer items-center gap-4 rounded-lg border border-border transition-all duration-200 ease-apple hover:border-accent"
:class="noBorder ? 'border-none' : ''" :class="{
'border-none': noBorder,
'p-4': !tighter,
}"
> >
<input v-model="modelValue" type="checkbox" class="peer hidden" @change.stop="emit('change', modelValue)" /> <input v-model="modelValue" type="checkbox" class="peer hidden" @change.stop="emit('change', modelValue)" />
<span <span
class="bg-linear-180-r relative shrink-0 rounded-full bg-gray-400/80 shadow-sm transition-all duration-medium ease-standard peer-checked:bg-accent peer-checked:shadow-[inset_0_1px_3px_rgba(0,0,0,0.1),0_2px_8px_color-mix(in_srgb,var(--color-accent),transparent_30%)] before:absolute before:rounded-full before:bg-white before:shadow-sm before:transition-all before:duration-200 before:ease-apple before:content-[''] peer-checked:before:translate-x-[24px]" class="relative shrink-0 rounded-full bg-gray-400/80 shadow-sm transition-all duration-medium ease-standard peer-checked:bg-accent peer-checked:shadow-[inset_0_1px_3px_rgba(0,0,0,0.1),0_2px_8px_color-mix(in_srgb,var(--color-accent),transparent_30%)] before:absolute before:rounded-full before:bg-white before:shadow-sm before:transition-all before:duration-200 before:ease-apple before:content-[''] peer-checked:before:translate-x-[24px]"
:class=" :class="
small small
? 'h-[21px] w-[44px] before:top-[2px] before:left-[2px] before:h-[17px] before:w-[17px]' ? 'h-[21px] w-[44px] before:top-[2px] before:left-[2px] before:h-[17px] before:w-[17px]'
@@ -59,6 +62,8 @@ const {
small = false, small = false,
tips = '', tips = '',
required = false, required = false,
noHover = false,
tighter = false,
} = defineProps<{ } = defineProps<{
noBorder?: boolean noBorder?: boolean
title?: string title?: string
@@ -66,6 +71,8 @@ const {
small?: boolean small?: boolean
tips?: string tips?: string
required?: boolean required?: boolean
noHover?: boolean
tighter?: boolean
}>() }>()
function toggleTooltip() { function toggleTooltip() {

View File

@@ -1,16 +0,0 @@
@import "tailwindcss" reference;
@import "../../assets/css/theme.css" reference;
@import "../../assets/css/utilities.css" reference;
.guide-btn {
@apply px-3 py-1.5 rounded-sm text-sm font-medium cursor-pointer border-none flex items-center gap-1 transition-all duration-200 ease-apple;
@apply [.primary]:bg-accent [.primary]:text-white;
@apply hover:[.primary]:bg-accent-hover hover:[.primary]:transform-[-translateY(1px)];
@apply [.secondary]:bg-bg-tertiary [.secondary]:text-main;
@apply hover:[.secondary]:bg-accent-hover;
@apply [.outline]:bg-transparent [.outline]:text-main [.outline]:border-border;
@apply hover:[.outline]:bg-accent hover:[.outline]:text-main;
@apply [.success]:bg-success [.success]:text-white;
@apply hover:[.success]:bg-success hover:[.success]:-translate-y-px;
@apply max-md:flex-1 max-md:justify-center max-md:min-w-[calc(50%-4px)]
}

View File

@@ -2,39 +2,6 @@
@import '../../assets/css/theme.css' reference; @import '../../assets/css/theme.css' reference;
@import '../../assets/css/utilities.css' reference; @import '../../assets/css/utilities.css' reference;
/* ==================== Settings Section ==================== */
.settings-section {
@apply rounded-xl border border-border bg-bg p-5 shadow-sm transition-all duration-200 ease-apple hover:shadow-md;
}
.section-header {
@apply mb-6 flex items-center gap-1;
}
.section-icon {
@apply flex h-[30px] w-[30px] shrink-0 items-center justify-center rounded-lg bg-bg-secondary text-accent;
}
.section-title-group {
@apply [&_h2]:mb-0 [&_h2]:text-lg [&_h2]:leading-0 [&_h2]:font-semibold [&_h2]:tracking-tight [&_h2]:text-main;
@apply [&_p]:mt-6 [&_p]:text-sm [&_p]:leading-0 [&_p]:text-secondary;
}
/* ==================== Form Elements ==================== */
.form-grid {
@apply grid grid-cols-[repeat(auto-fit,minmax(280px,1fr))] gap-4;
}
.form-group {
@apply mb-6;
@apply last:mb-0;
@apply [&>:label:not(.custom-switch,.radio-option)]:mb-2.5
[&>:label:not(.custom-switch,.radio-option)]:block
[&>:label:not(.custom-switch,.radio-option)]:text-sm
[&>:label:not(.custom-switch,.radio-option)]:font-semibold
[&>:label:not(.custom-switch,.radio-option)]:text-main;
}
.form-input, .form-input,
.form-textarea { .form-textarea {
@apply box-border w-full rounded-lg border border-border bg-bg-tertiary px-4 py-3.5 text-sm text-main transition-all duration-200 ease-apple; @apply box-border w-full rounded-lg border border-border bg-bg-tertiary px-4 py-3.5 text-sm text-main transition-all duration-200 ease-apple;
@@ -45,7 +12,3 @@
.form-textarea { .form-textarea {
@apply min-h-[90px] resize-y font-[ui-monospace,SFMono-Regular,'SF_Mono',Menlo,Consolas,'Liberation_Mono',monospace]; @apply min-h-[90px] resize-y font-[ui-monospace,SFMono-Regular,'SF_Mono',Menlo,Consolas,'Liberation_Mono',monospace];
} }
.title-text {
@apply text-base font-semibold text-main;
}

View File

@@ -32,45 +32,40 @@
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-sm text-secondary">{{ t('pages.gallery.isAlwaysForceReload') }}</span> <span class="text-sm text-secondary">{{ t('pages.gallery.isAlwaysForceReload') }}</span>
<label class="relative inline-block h-[20px] w-[44px]"> <CustomSwitch
<input v-model="isAlwaysForceReload"
v-model="isAlwaysForceReload" small
class="peer/fir h-0 w-0 opacity-0" tighter
type="checkbox" no-border
@change="handleIsAlwaysForceReload" no-hover
/> @change="handleIsAlwaysForceReload"
<span />
class="switch-slider peer-checked/fir:bg-accent peer-checked/fir:bg-none! peer-checked/fir:before:translate-x-[22px]"
/>
</label>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-sm text-secondary">{{ t('pages.gallery.syncDelete') }}</span> <span class="text-sm text-secondary">{{ t('pages.gallery.syncDelete') }}</span>
<label class="relative inline-block h-[20px] w-[44px]"> <CustomSwitch v-model="deleteCloud" small tighter no-border no-hover @change="handleDeleteCloudFile" />
<input
v-model="deleteCloud"
class="peer/sec h-0 w-0 opacity-0"
type="checkbox"
@change="handleDeleteCloudFile"
/>
<span
class="switch-slider peer-checked/sec:bg-accent peer-checked/sec:bg-none! peer-checked/sec:before:translate-x-[22px]"
/>
</label>
</div> </div>
<button class="head-action-button relative" :title="getViewModeLabel()" @click="toggleViewMode"> <CustomButton
<component :is="getViewModeIcon()" :size="16" /> type="primary"
<span class="mt-0.5">{{ getViewModeLabel() }}</span> :text="getViewModeLabel()"
</button> :icon="getViewModeIcon()"
<button class="head-action-button" @click="toggleHandleBar"> class="px-2!"
<ChevronDownIcon v-if="!handleBarActive" :size="16" /> @click="toggleViewMode"
<ChevronUpIcon v-else :size="16" /> />
<span class="mt-0.5">{{ t('pages.gallery.hideFilters') }}</span> <CustomButton
</button> type="primary"
<button class="head-action-button" @click="refreshPage"> :text="t('pages.gallery.hideFilters')"
<RefreshCwIcon :size="16" /> :icon="handleBarActive ? ChevronUpIcon : ChevronDownIcon"
<span class="mt-0.5">{{ t('pages.gallery.refresh') }}</span> class="px-2!"
</button> @click="toggleHandleBar"
/>
<CustomButton
type="secondary"
:text="t('pages.gallery.refresh')"
:icon="RefreshCwIcon"
class="px-2!"
@click="refreshPage"
/>
</div> </div>
</div> </div>
@@ -90,7 +85,9 @@
</div> </div>
<div class="filter-group"> <div class="filter-group">
<label class="filter-label">{{ t('pages.gallery.dateRange') }}</label> <label class="mb-0 text-sm leading-[1.4] font-semibold text-secondary">{{
t('pages.gallery.dateRange')
}}</label>
<div class="flex w-full flex-wrap items-center gap-2 max-md:items-start"> <div class="flex w-full flex-wrap items-center gap-2 max-md:items-start">
<input v-model="dateRangeStart" type="date" class="date-input" placeholder="Start date" /> <input v-model="dateRangeStart" type="date" class="date-input" placeholder="Start date" />
<span class="shrink-0 font-medium text-secondary">-</span> <span class="shrink-0 font-medium text-secondary">-</span>
@@ -423,6 +420,7 @@ import { onBeforeRouteUpdate } from 'vue-router'
import ALLApi from '@/apis/allApi' import ALLApi from '@/apis/allApi'
import CustomButton from '@/components/common/CustomButton.vue' import CustomButton from '@/components/common/CustomButton.vue'
import CustomModal from '@/components/common/CustomModal.vue' import CustomModal from '@/components/common/CustomModal.vue'
import CustomSwitch from '@/components/common/CustomSwitch.vue'
import MultiSelect from '@/components/common/MultiSelect.vue' import MultiSelect from '@/components/common/MultiSelect.vue'
import PlaceholderTable from '@/components/common/PlaceholderTable.vue' import PlaceholderTable from '@/components/common/PlaceholderTable.vue'
import SingleSelect from '@/components/common/SingleSelect.vue' import SingleSelect from '@/components/common/SingleSelect.vue'
@@ -458,7 +456,7 @@ const imgInfo = reactive({
imgUrl: '', imgUrl: '',
}) })
const choosedList: IObjT<boolean> = reactive({}) const choosedList: IObjT<boolean> = reactive({})
const gallerySliderControl = reactive({ const gallerySliderControl = ref({
visible: false, visible: false,
index: 0, index: 0,
}) })
@@ -808,8 +806,8 @@ function clearChoosedList() {
} }
function zoomImage(index: number) { function zoomImage(index: number) {
gallerySliderControl.index = index gallerySliderControl.value.index = index
gallerySliderControl.visible = true gallerySliderControl.value.visible = true
} }
async function copy(item: ImgInfo) { async function copy(item: ImgInfo) {
@@ -863,18 +861,16 @@ function remove(item: ImgInfo, _: number) {
}) })
} }
function handleIsAlwaysForceReload(event: Event) { function handleIsAlwaysForceReload(value: boolean) {
const ev = (event.target as HTMLInputElement).checked
isAlwaysForceReload.value = ev
saveConfig({ saveConfig({
[configPaths.settings.isAlwaysForceReload]: ev, [configPaths.settings.isAlwaysForceReload]: value,
}) })
window.electron.sendRPC(IRPCActionType.REFRESH_SETTING_WINDOW) window.electron.sendRPC(IRPCActionType.REFRESH_SETTING_WINDOW)
} }
function handleDeleteCloudFile(event: Event) { function handleDeleteCloudFile(value: boolean) {
saveConfig({ saveConfig({
[configPaths.settings.deleteCloudFile]: (event.target as HTMLInputElement).checked, [configPaths.settings.deleteCloudFile]: value,
}) })
} }

View File

@@ -399,7 +399,7 @@
/> />
</SettingCard> </SettingCard>
<SettingCard p1> <SettingCard p1 class="flex flex-col justify-center">
<CustomSwitch <CustomSwitch
v-model="formOfSetting.deleteLocalFile" v-model="formOfSetting.deleteLocalFile"
small small
@@ -429,7 +429,7 @@
/> />
</SettingCard> </SettingCard>
<SettingCard p1> <SettingCard p1 class="flex flex-col justify-center">
<CustomSwitch <CustomSwitch
v-model="formOfSetting.autoCopy" v-model="formOfSetting.autoCopy"
small small

View File

@@ -2,24 +2,10 @@
@import "../../assets/css/theme.css" reference; @import "../../assets/css/theme.css" reference;
@import "../../assets/css/utilities.css" reference; @import "../../assets/css/utilities.css" reference;
.head-action-button {
@apply flex items-center border-none rounded-md px-2 py-1.5 text-sm font-medium text-white bg-accent font-[inherit] gap-2 cursor-pointer transition-all duration-fast ease-apple;
@apply hover:bg-accent-hover hover:-translate-y-px hover:shadow-md;
}
.switch-slider {
@apply absolute inset-0 cursor-pointer rounded-2xl bg-[linear-gradient(180deg,#d0d3d9_0%,#c0c4cc_100%)] shadow-sm transition-all duration-medium ease-standard;
@apply before:absolute before:bottom-[2px] before:left-[2px] before:h-[17px] before:w-[17px] before:rounded-full before:shadow-sm before:transition-all before:ease-standard before:duration-medium before:content-[""] before:bg-[linear-gradient(180deg,#ffffff_0%,#f5f5f5_100%)];
}
.filter-group { .filter-group {
@apply flex flex-col gap-1 flex-1 min-w-[140px]; @apply flex flex-col gap-1 flex-1 min-w-[140px];
} }
.filter-label {
@apply mb-0 text-sm font-semibold text-secondary leading-[1.4];
}
.date-input { .date-input {
@apply border border-border-secondary rounded-md px-2 py-1.5 min-w-[20px] h-[28px] text-xs text-main transition-all duration-fast ease-apple flex-1 leading-[1.2]; @apply border border-border-secondary rounded-md px-2 py-1.5 min-w-[20px] h-[28px] text-xs text-main transition-all duration-fast ease-apple flex-1 leading-[1.2];
@apply focus:border-accent-hover focus:outline-none focus:shadow-md; @apply focus:border-accent-hover focus:outline-none focus:shadow-md;
@@ -54,48 +40,7 @@
@apply [.delete-icon]:hover:text-error [.delete-icon]:hover:bg-error/10; @apply [.delete-icon]:hover:text-error [.delete-icon]:hover:bg-error/10;
} }
.nav-button {
@apply absolute flex z-10 justify-center items-center border-none rounded-full w-[48px] h-[48px] text-white bg-[rgb(0_0_0_/50%)] transition-all duration-fast ease-apple cursor-pointer;
@apply not-disabled:hover:bg-accent;
@apply disabled:opacity-50 disabled:cursor-not-allowed;
@apply [.prev]:left-4 [.next]:right-4;
}
.form-input { .form-input {
@apply border border-border rounded-md p-3 w-full text-sm text-main bg-bg-secondary transition-all duration-fast ease-apple box-border; @apply border border-border rounded-md p-3 w-full text-sm text-main bg-bg-secondary transition-all duration-fast ease-apple box-border;
@apply focus:border-accent focus:outline-none focus:shadow-md; @apply focus:border-accent focus:outline-none focus:shadow-md;
} }
.placeholder-grid {
@apply grid grid-cols-[repeat(auto-fit,minmax(150px,1fr))] gap-0 py-2;
}
.placeholder-item {
@apply gap-2 flex items-center m-0 rounded-none py-2 px-4 text-sm leading-[1.4] flex-col;
@apply hover:bg-accent/30 hover:border hover:border-accent;
@apply [&_code]:mr-3.5 [&_code]:border [&_code]:border-white/20 [&_code]:rounded-md [&_code]:px-2.5 [&_code]:py-1 [&_code]:min-w-[80px] [&_code]:text-base [&_code]:font-['SF_Mono',Monaco,'Cascadia_Code','Roboto_Mono',Consolas,'Courier_New',monospace] [&_code]:font-semibold [&_code]:text-center [&_code]:text-main [&_code]:bg-bg-secondary [&_code]:shadow-sm [&_code]:tracking-[0.02em] [&_code]:shrink-0;
@apply [&_span]:text-xs [&_span]:font-medium [&_span]:text-main [&_span]:flex-1;
}
/* Buttons */
.btn-primary {
@apply flex items-center border-none rounded-md px-6 py-3 text-sm font-medium font-[inherit] text-white bg-accent gap-2 cursor-pointer transition-all duration-fast ease-apple;
@apply hover:bg-accent-hover hover:-translate-y-px hover:shadow-sm;
}
.btn-secondary {
@apply flex items-center border border-border rounded-md px-6 py-3 text-sm font-medium font-[inherit] text-main bg-bg-secondary gap-2 cursor-pointer transition-all duration-fast ease-apple;
@apply hover:bg-border-secondary hover:-translate-y-px hover:shadow-sm;
}
.zoom-btn {
@apply flex justify-center items-center border-none rounded-md w-[32px] h-[32px] text-sm font-semibold text-white bg-[rgb(255_255_255_/10%)] transition-all duration-fast ease-apple cursor-pointer;
@apply not-disabled:hover:bg-[rgb(255_255_255_/20%)];
@apply disabled:opacity-50 disabled:cursor-not-allowed;
@apply [.reset-btn]:py-0 [.reset-btn]:w-auto [.reset-btn]:px-3 [.reset-btn]:text-xs;
}
.category-title {
@apply m-0 border-b border-border pt-3.5 px-4 pb-2 text-sm font-semibold text-secondary bg-[linear-gradient(180deg,var(--color-background-secondary)_0%,var(--color-background-tertiary)_100%)];
}