941 lines
23 KiB
Vue
941 lines
23 KiB
Vue
<script setup lang="ts">
|
||
import { computed, onMounted, ref, watch } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import PageSkeleton from '@/components/ui/PageSkeleton.vue'
|
||
import IconChevronBack from '@/components/icons/IconChevronBack.vue'
|
||
import IconChevronLeft from '@/components/icons/IconChevronLeft.vue'
|
||
import IconShare from '@/components/icons/IconShare.vue'
|
||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||
import { useCatalogStore } from '@/stores/catalog'
|
||
import { useCrmStore } from '@/stores/crm'
|
||
import { useCartStore } from '@/stores/cart'
|
||
import { useFavoritesStore } from '@/stores/favorites'
|
||
import { useGoBack } from '@/composables/useGoBack'
|
||
import type { Catalog } from '@/types'
|
||
import type { Product } from '@/types'
|
||
import type { CatalogProductCard } from '@/types/crm'
|
||
import {
|
||
SEASON_LABEL,
|
||
catalogDisplayTitle,
|
||
catalogLatinName,
|
||
groupCatalogPatterns,
|
||
type PatternKind,
|
||
} from '@/utils/catalogGallery'
|
||
import { formatNumber } from '@/utils/format'
|
||
import { colorSwatchStyle } from '@/utils/colorSwatch'
|
||
import { demoProductsFor, findMockCalite } from '@/data/caliteCollections'
|
||
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
const { goBack } = useGoBack('/catalogs')
|
||
const catalogStore = useCatalogStore()
|
||
const crm = useCrmStore()
|
||
const cartStore = useCartStore()
|
||
const favorites = useFavoritesStore()
|
||
|
||
const catalogId = computed(() => String(route.params.catalogId ?? ''))
|
||
const catalog = ref<Catalog | null>(null)
|
||
const products = ref<CatalogProductCard[]>([])
|
||
const loading = ref(true)
|
||
const error = ref<string | null>(null)
|
||
const adding = ref(false)
|
||
const toast = ref('')
|
||
const tab = ref<'all' | PatternKind>('all')
|
||
|
||
const title = computed(() => catalogDisplayTitle(catalog.value?.name || 'کالیته'))
|
||
const heroImage = computed(
|
||
() =>
|
||
catalog.value?.imageUrl
|
||
|| (catalog.value ? findMockCalite(catalog.value.id)?.imageUrl : undefined)
|
||
|| products.value.find((item) => item.imageUrl)?.imageUrl,
|
||
)
|
||
|
||
const latinName = computed(() => {
|
||
const mock = catalog.value ? findMockCalite(catalog.value.id) : undefined
|
||
if (mock?.latinName) return mock.latinName
|
||
return catalog.value ? catalogLatinName(catalog.value) : ''
|
||
})
|
||
|
||
const usageLine = computed(() => {
|
||
const mock = catalog.value ? findMockCalite(catalog.value.id) : undefined
|
||
if (mock?.subtitle) return mock.subtitle
|
||
const id = products.value[0]?.categoryId
|
||
if (id === 'trousers') return 'پارچههای شلواری'
|
||
if (id === 'shirt') return 'پارچههای پیراهنی'
|
||
return 'پارچههای کتوشلواری'
|
||
})
|
||
|
||
const patterns = computed(() => groupCatalogPatterns(products.value))
|
||
const visiblePatterns = computed(() =>
|
||
tab.value === 'all' ? patterns.value : patterns.value.filter((item) => item.kind === tab.value),
|
||
)
|
||
|
||
const seasonLabel = computed(() => {
|
||
const mock = catalog.value ? findMockCalite(catalog.value.id) : undefined
|
||
if (mock) return SEASON_LABEL[mock.season]
|
||
const counts: Record<string, number> = {}
|
||
for (const product of products.value) {
|
||
counts[product.season] = (counts[product.season] ?? 0) + 1
|
||
}
|
||
const top = Object.entries(counts).sort((a, b) => b[1] - a[1])[0]?.[0] as keyof typeof SEASON_LABEL | undefined
|
||
return top ? SEASON_LABEL[top] : SEASON_LABEL.all_season
|
||
})
|
||
|
||
const tabs = computed(() => [
|
||
{ id: 'all' as const, label: 'همه طرحها', count: patterns.value.length },
|
||
{
|
||
id: 'plain' as const,
|
||
label: 'ساده',
|
||
count: patterns.value.filter((item) => item.kind === 'plain').length,
|
||
},
|
||
{
|
||
id: 'melange' as const,
|
||
label: 'ملانژ',
|
||
count: patterns.value.filter((item) => item.kind === 'melange').length,
|
||
},
|
||
{
|
||
id: 'patterned' as const,
|
||
label: 'طرحدار',
|
||
count: patterns.value.filter((item) => item.kind === 'patterned').length,
|
||
},
|
||
])
|
||
|
||
async function load() {
|
||
loading.value = true
|
||
error.value = null
|
||
catalog.value = null
|
||
products.value = []
|
||
tab.value = 'all'
|
||
try {
|
||
const companies = await catalogStore.loadCompanies()
|
||
if (!catalogStore.getCatalog(catalogId.value)) {
|
||
await Promise.all(companies.map((company) => catalogStore.loadCatalogs(company.id)))
|
||
}
|
||
catalog.value = catalogStore.getCatalog(catalogId.value) ?? null
|
||
const mock = findMockCalite(catalogId.value)
|
||
if (!catalog.value && mock) {
|
||
catalog.value = {
|
||
id: mock.id,
|
||
companyId: '',
|
||
name: mock.name,
|
||
code: mock.latinName || mock.name,
|
||
description: 'ترکیبی از اصالت، کیفیت و استایل مدرن',
|
||
displayOrder: 0,
|
||
isActive: true,
|
||
}
|
||
products.value = demoProductsFor(mock)
|
||
return
|
||
}
|
||
if (!catalog.value) {
|
||
error.value = 'کالیته مورد نظر یافت نشد.'
|
||
return
|
||
}
|
||
products.value = await crm.loadBrowseProducts({
|
||
catalogId: catalog.value.id,
|
||
companyId: catalog.value.companyId,
|
||
})
|
||
if (!products.value.length && mock) {
|
||
products.value = demoProductsFor(mock)
|
||
}
|
||
} catch (e) {
|
||
error.value = e instanceof Error ? e.message : 'بارگذاری کالیته ناموفق بود.'
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
onMounted(async () => {
|
||
await favorites.hydrate()
|
||
await load()
|
||
})
|
||
watch(catalogId, load)
|
||
|
||
function openProduct(id: string) {
|
||
if (id.startsWith('demo-')) return
|
||
router.push({ path: '/product', query: { id } })
|
||
}
|
||
|
||
function toggleFavorite(id: string) {
|
||
void favorites.toggle(id)
|
||
}
|
||
|
||
function requestPhysical() {
|
||
router.push({
|
||
path: '/catalog-request',
|
||
query: { catalogId: catalogId.value },
|
||
})
|
||
}
|
||
|
||
function shareCatalog() {
|
||
const url = window.location.href
|
||
if (navigator.share) {
|
||
void navigator.share({ title: title.value, url }).catch(() => undefined)
|
||
return
|
||
}
|
||
void navigator.clipboard?.writeText(url)
|
||
}
|
||
|
||
function toCartProduct(item: CatalogProductCard): Product {
|
||
return {
|
||
id: item.id,
|
||
companyId: item.companyId || catalog.value?.companyId || '',
|
||
catalogId: item.catalogId || catalogId.value,
|
||
name: item.name,
|
||
code: item.code,
|
||
color: item.color || item.colors[0] || '—',
|
||
minMeterage: item.minMeterage ?? 30,
|
||
orderMultiplier: item.orderMultiplier ?? 10,
|
||
basePrice: item.basePrice,
|
||
isActive: true,
|
||
}
|
||
}
|
||
|
||
async function addAllToInquiry() {
|
||
if (!products.value.length || adding.value) return
|
||
adding.value = true
|
||
toast.value = ''
|
||
let added = 0
|
||
let failed = ''
|
||
try {
|
||
for (const item of products.value) {
|
||
const product = toCartProduct(item)
|
||
const result = await cartStore.addItem(
|
||
product,
|
||
catalog.value?.code || item.catalogCode || item.code,
|
||
product.minMeterage,
|
||
)
|
||
if (result.success) added += 1
|
||
else failed = result.message || failed
|
||
}
|
||
toast.value = added
|
||
? `${formatNumber(added)} طرح به سبد استعلام اضافه شد.`
|
||
: failed || 'افزودن به سبد ناموفق بود.'
|
||
if (added) {
|
||
window.setTimeout(() => router.push('/cart'), 650)
|
||
}
|
||
} finally {
|
||
adding.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="page calite-detail">
|
||
<div class="page__scroll page__scroll--with-nav calite-detail__scroll">
|
||
<section class="calite-hero">
|
||
<div
|
||
class="calite-hero__photo"
|
||
:style="heroImage ? { backgroundImage: `url(${heroImage})` } : undefined"
|
||
/>
|
||
<header class="calite-hero__nav">
|
||
<button type="button" class="icon-btn" aria-label="بازگشت" @click="goBack">
|
||
<IconChevronBack :size="22" color="#ffffff" />
|
||
</button>
|
||
<h1>{{ loading ? 'کالیته' : title }}</h1>
|
||
<button type="button" class="icon-btn" aria-label="اشتراکگذاری" @click="shareCatalog">
|
||
<IconShare :size="20" color="#ffffff" />
|
||
</button>
|
||
</header>
|
||
<div v-if="!loading && !error" class="calite-hero__copy">
|
||
<h2>{{ title }}</h2>
|
||
<p class="calite-hero__usage">{{ usageLine }}</p>
|
||
<strong v-if="latinName" class="calite-hero__latin">{{ latinName }}</strong>
|
||
<span class="calite-hero__tagline">{{
|
||
catalog?.description || 'ترکیبی از اصالت، کیفیت و استایل مدرن'
|
||
}}</span>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="calite-detail__body">
|
||
<PageSkeleton v-if="loading" variant="cards" :rows="5" />
|
||
<p v-else-if="error" class="calite-detail__error">{{ error }}</p>
|
||
<template v-else>
|
||
|
||
<div class="attr-row">
|
||
<span class="attr">
|
||
<svg class="attr__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||
<path
|
||
d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"
|
||
stroke="currentColor"
|
||
stroke-width="1.5"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
/>
|
||
<path
|
||
d="M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12"
|
||
stroke="currentColor"
|
||
stroke-width="1.5"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
/>
|
||
<path
|
||
d="M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17"
|
||
stroke="currentColor"
|
||
stroke-width="1.5"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
/>
|
||
</svg>
|
||
{{ formatNumber(patterns.length) }} طرح
|
||
</span>
|
||
<span class="attr">
|
||
<svg class="attr__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||
<circle cx="12" cy="12" r="4" stroke="currentColor" stroke-width="1.5" />
|
||
<path
|
||
d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"
|
||
stroke="currentColor"
|
||
stroke-width="1.5"
|
||
stroke-linecap="round"
|
||
/>
|
||
</svg>
|
||
{{ seasonLabel }}
|
||
</span>
|
||
<span class="attr">
|
||
<svg class="attr__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||
<path
|
||
d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"
|
||
stroke="currentColor"
|
||
stroke-width="1.5"
|
||
stroke-linejoin="round"
|
||
/>
|
||
</svg>
|
||
پرطرفدار
|
||
</span>
|
||
<span class="attr">
|
||
<svg class="attr__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||
<path
|
||
d="M2.7 10.3a2.41 2.41 0 0 0 0 3.41l7.59 7.59a2.41 2.41 0 0 0 3.41 0l7.59-7.59a2.41 2.41 0 0 0 0-3.41L13.7 2.71a2.41 2.41 0 0 0-3.41 0z"
|
||
stroke="currentColor"
|
||
stroke-width="1.5"
|
||
stroke-linejoin="round"
|
||
/>
|
||
</svg>
|
||
کیفیت ممتاز
|
||
</span>
|
||
</div>
|
||
|
||
<button type="button" class="physical-cta" @click="requestPhysical">
|
||
<span class="physical-cta__icon" aria-hidden="true">
|
||
<BasicIcon icon="vuesax-linear:truck-time" :size="20" color="#ffffff" />
|
||
</span>
|
||
درخواست دریافت فیزیکی کالیته
|
||
<span class="physical-cta__chevron" aria-hidden="true">
|
||
<IconChevronLeft :size="18" color="#ffffff" />
|
||
</span>
|
||
</button>
|
||
|
||
<div class="pattern-tabs" role="tablist" aria-label="نوع طرح">
|
||
<button
|
||
v-for="item in tabs"
|
||
:key="item.id"
|
||
type="button"
|
||
class="pattern-tab"
|
||
:class="{ 'is-on': tab === item.id }"
|
||
@click="tab = item.id"
|
||
>
|
||
{{ item.label }}<template v-if="item.id === 'all'"> ({{ formatNumber(item.count) }})</template>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="pattern-grid">
|
||
<article
|
||
v-for="pattern in visiblePatterns"
|
||
:key="pattern.id"
|
||
class="pattern-card"
|
||
@click="openProduct(pattern.id)"
|
||
>
|
||
<div
|
||
class="pattern-card__photo"
|
||
:style="pattern.imageUrl ? { backgroundImage: `url(${pattern.imageUrl})` } : undefined"
|
||
>
|
||
<button
|
||
type="button"
|
||
class="pattern-card__fav"
|
||
:aria-pressed="favorites.isFavorite(pattern.id)"
|
||
:aria-label="
|
||
favorites.isFavorite(pattern.id) ? 'حذف از علاقهمندیها' : 'افزودن به علاقهمندیها'
|
||
"
|
||
@click.stop="toggleFavorite(pattern.id)"
|
||
>
|
||
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" aria-hidden="true">
|
||
<path
|
||
d="M12.1 20.3s-6.6-4.1-8.4-8.1c-1.4-3.1.1-6.7 3.4-7.5 1.9-.5 3.7.3 4.9 1.8 1.2-1.5 3-2.3 4.9-1.8 3.3.8 4.8 4.4 3.4 7.5-1.8 4-8.2 8.1-8.2 8.1Z"
|
||
:fill="favorites.isFavorite(pattern.id) ? 'var(--color-primary)' : 'none'"
|
||
stroke="var(--color-primary)"
|
||
stroke-width="1.5"
|
||
stroke-linejoin="round"
|
||
/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
<div class="pattern-card__meta">
|
||
<strong>{{ pattern.name }}</strong>
|
||
<div class="pattern-card__colors">
|
||
<span>{{ formatNumber(pattern.colorCount) }} رنگ</span>
|
||
<div class="swatches">
|
||
<i
|
||
v-for="(color, idx) in pattern.colors"
|
||
:key="idx"
|
||
:style="colorSwatchStyle(color, pattern.colorImages[idx])"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
<p v-if="!visiblePatterns.length" class="calite-detail__error">طرحی در این دسته نیست.</p>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="!loading && products.length" class="sticky-footer calite-detail__footer">
|
||
<button type="button" class="inquiry-bar" :disabled="adding" @click="addAllToInquiry">
|
||
{{ adding ? 'در حال افزودن...' : 'افزودن همه به سبد استعلام' }}
|
||
<small>{{ formatNumber(patterns.length) }} طرح انتخاب میشود</small>
|
||
</button>
|
||
</div>
|
||
<p v-if="toast" class="toast">{{ toast }}</p>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.calite-detail {
|
||
background: var(--color-bg);
|
||
color: var(--color-text);
|
||
}
|
||
|
||
.calite-detail__nav,
|
||
.calite-hero__nav {
|
||
display: grid;
|
||
grid-template-columns: 44px 1fr 44px;
|
||
align-items: center;
|
||
padding: calc(6px + env(safe-area-inset-top, 0px)) 8px 2px;
|
||
}
|
||
|
||
.calite-hero__nav {
|
||
position: absolute;
|
||
inset-inline: 0;
|
||
top: 0;
|
||
z-index: 2;
|
||
background: transparent;
|
||
}
|
||
|
||
.calite-hero__nav h1 {
|
||
margin: 0;
|
||
text-align: center;
|
||
font-size: 1.0625rem;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
}
|
||
|
||
.icon-btn {
|
||
width: 44px;
|
||
height: 44px;
|
||
border: none;
|
||
background: transparent;
|
||
color: #fff;
|
||
display: grid;
|
||
place-items: center;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.calite-detail__scroll {
|
||
padding: 0 0 112px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0;
|
||
}
|
||
|
||
.calite-hero {
|
||
position: relative;
|
||
width: 100%;
|
||
min-height: 340px;
|
||
height: min(52vh, 440px);
|
||
overflow: hidden;
|
||
border-radius: 0;
|
||
color: #fff;
|
||
}
|
||
|
||
.calite-hero__photo {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: linear-gradient(160deg, #3d4a55, #151b22);
|
||
background-size: cover;
|
||
background-position: center;
|
||
}
|
||
|
||
.calite-hero__photo::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
background: linear-gradient(
|
||
180deg,
|
||
rgba(12, 10, 8, 0.28) 0%,
|
||
rgba(12, 10, 8, 0.08) 38%,
|
||
rgba(12, 10, 8, 0.55) 100%
|
||
);
|
||
}
|
||
|
||
.calite-hero__copy {
|
||
position: absolute;
|
||
inset-inline: 20px 20px;
|
||
bottom: 22px;
|
||
z-index: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
justify-content: flex-end;
|
||
gap: 2px;
|
||
min-height: 0;
|
||
padding: 0;
|
||
text-align: start;
|
||
}
|
||
|
||
.calite-hero__copy h2 {
|
||
margin: 0;
|
||
font-size: 1.5rem;
|
||
font-weight: 700;
|
||
line-height: 1.35;
|
||
text-shadow: 0 1px 8px rgba(0, 0, 0, 0.25);
|
||
}
|
||
|
||
.calite-hero__usage {
|
||
margin: 0;
|
||
font-size: 0.8125rem;
|
||
font-weight: 400;
|
||
opacity: 0.95;
|
||
}
|
||
|
||
.calite-hero__latin {
|
||
margin: 2px 0 0;
|
||
font-family: Georgia, "Times New Roman", serif;
|
||
font-size: 1.75rem;
|
||
font-style: italic;
|
||
font-weight: 400;
|
||
letter-spacing: 0.02em;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.calite-hero__tagline {
|
||
margin-top: 4px;
|
||
font-size: 0.75rem;
|
||
font-weight: 400;
|
||
opacity: 0.92;
|
||
}
|
||
|
||
.calite-detail__body {
|
||
padding: 8px 16px 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.attr-row {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 0;
|
||
padding: 4px 0 8px;
|
||
}
|
||
|
||
.attr {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
min-height: 0;
|
||
padding: 10px 8px;
|
||
border-radius: 0;
|
||
background: transparent;
|
||
color: #6f6b66;
|
||
font-size: 0.8125rem;
|
||
font-weight: 500;
|
||
text-align: center;
|
||
line-height: 1.4;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.attr:not(:last-child)::after {
|
||
content: '';
|
||
position: absolute;
|
||
inset-inline-end: 0;
|
||
top: 16%;
|
||
bottom: 16%;
|
||
width: 1px;
|
||
background: var(--color-border);
|
||
}
|
||
|
||
.attr__icon {
|
||
width: 26px;
|
||
height: 26px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.physical-cta {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
min-height: 52px;
|
||
padding: 0 44px;
|
||
border: none;
|
||
border-radius: 999px;
|
||
background: var(--color-primary);
|
||
color: #fff;
|
||
font: inherit;
|
||
font-size: 0.875rem;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.physical-cta__icon {
|
||
display: grid;
|
||
place-items: center;
|
||
}
|
||
|
||
.physical-cta__chevron {
|
||
position: absolute;
|
||
inset-inline-end: 16px;
|
||
display: grid;
|
||
place-items: center;
|
||
color: #fff;
|
||
}
|
||
|
||
.pattern-tabs {
|
||
display: flex;
|
||
gap: 8px;
|
||
overflow-x: auto;
|
||
padding: 2px 0;
|
||
}
|
||
|
||
.pattern-tab {
|
||
flex-shrink: 0;
|
||
height: 34px;
|
||
padding: 0 14px;
|
||
border: none;
|
||
border-radius: 999px;
|
||
background: var(--color-tab-inactive-bg);
|
||
color: #6a645e;
|
||
font: inherit;
|
||
font-size: 0.8125rem;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.pattern-tab.is-on {
|
||
background: var(--color-primary);
|
||
color: #fff;
|
||
}
|
||
|
||
.pattern-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 10px;
|
||
}
|
||
|
||
.pattern-card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
padding: 0 0 10px;
|
||
overflow: hidden;
|
||
border: none;
|
||
border-radius: 20px;
|
||
background: var(--color-surface);
|
||
text-align: start;
|
||
cursor: pointer;
|
||
color: #1c1c1c;
|
||
box-shadow: none;
|
||
border: 1px solid var(--color-border);
|
||
}
|
||
|
||
.pattern-card__photo {
|
||
position: relative;
|
||
width: 100%;
|
||
aspect-ratio: 1.08;
|
||
border-radius: 0;
|
||
background: linear-gradient(145deg, #2a3544, #111827);
|
||
background-size: cover;
|
||
background-position: center;
|
||
}
|
||
|
||
.pattern-card__fav {
|
||
position: absolute;
|
||
top: 8px;
|
||
left: 8px;
|
||
width: 28px;
|
||
height: 28px;
|
||
border: none;
|
||
background: transparent;
|
||
padding: 0;
|
||
display: grid;
|
||
place-items: center;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.pattern-card__meta {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
padding: 0 10px 2px;
|
||
}
|
||
|
||
.pattern-card strong {
|
||
font-size: 0.875rem;
|
||
font-weight: 700;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.pattern-card__colors {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
}
|
||
|
||
.pattern-card__colors span {
|
||
color: #8a847c;
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.swatches {
|
||
display: flex;
|
||
gap: 4px;
|
||
}
|
||
|
||
.swatches i {
|
||
width: 10px;
|
||
height: 10px;
|
||
border-radius: 999px;
|
||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||
background-size: cover;
|
||
background-position: center;
|
||
}
|
||
|
||
.inquiry-bar {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 2px;
|
||
width: 100%;
|
||
min-height: 56px;
|
||
border: none;
|
||
border-radius: 20px;
|
||
background: var(--color-primary);
|
||
color: #fff;
|
||
font: inherit;
|
||
font-size: 0.9375rem;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.inquiry-bar small {
|
||
font-size: 0.6875rem;
|
||
font-weight: 500;
|
||
opacity: 0.78;
|
||
}
|
||
|
||
.inquiry-bar:disabled {
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.calite-detail__error {
|
||
margin: 8px 0 0;
|
||
text-align: center;
|
||
color: #8a847c;
|
||
}
|
||
|
||
.toast {
|
||
position: fixed;
|
||
left: 50%;
|
||
bottom: calc(var(--nav-height) + 84px);
|
||
z-index: 20;
|
||
transform: translateX(-50%);
|
||
padding: 8px 14px;
|
||
border-radius: 999px;
|
||
background: var(--color-primary);
|
||
color: #fff;
|
||
font-size: 0.75rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
html.dark .calite-detail {
|
||
background: #0b0f14;
|
||
color: #eef2f8;
|
||
}
|
||
|
||
html.dark .calite-hero__nav h1,
|
||
html.dark .icon-btn {
|
||
color: #fff;
|
||
}
|
||
|
||
html.dark .attr {
|
||
background: transparent;
|
||
color: #c5d0de;
|
||
box-shadow: none;
|
||
}
|
||
|
||
html.dark .attr:not(:last-child)::after {
|
||
background: #2a3444;
|
||
}
|
||
|
||
html.dark .physical-cta {
|
||
background: var(--color-primary);
|
||
color: #fff;
|
||
}
|
||
|
||
html.dark .pattern-tab {
|
||
background: #1a2230;
|
||
color: #c5d0de;
|
||
}
|
||
|
||
html.dark .pattern-tab.is-on {
|
||
background: #e8e4df;
|
||
color: #121821;
|
||
}
|
||
|
||
html.dark .pattern-card {
|
||
background: #121821;
|
||
color: #eef2f8;
|
||
box-shadow: none;
|
||
}
|
||
|
||
html.dark .pattern-card__colors span,
|
||
html.dark .calite-detail__error {
|
||
color: #9aa8bc;
|
||
}
|
||
|
||
html.dark .inquiry-bar {
|
||
background: var(--color-primary);
|
||
color: #0b0f14;
|
||
}
|
||
|
||
html.dark .calite-detail__footer.sticky-footer {
|
||
background: #0b0f14;
|
||
}
|
||
|
||
@media (min-width: 768px) {
|
||
.calite-detail {
|
||
padding: 0;
|
||
max-width: none;
|
||
margin: 0;
|
||
overflow: visible;
|
||
min-height: auto;
|
||
}
|
||
|
||
.calite-detail__scroll {
|
||
padding-bottom: 0;
|
||
}
|
||
|
||
.calite-hero {
|
||
min-height: 300px;
|
||
height: 340px;
|
||
}
|
||
|
||
.calite-hero__nav {
|
||
padding-top: 10px;
|
||
}
|
||
|
||
.calite-hero__copy {
|
||
inset-inline: 36px 36px;
|
||
bottom: 28px;
|
||
gap: 4px;
|
||
}
|
||
|
||
.calite-hero__copy h2 {
|
||
font-size: 2rem;
|
||
}
|
||
|
||
.calite-hero__usage {
|
||
font-size: 0.9375rem;
|
||
}
|
||
|
||
.calite-hero__latin {
|
||
font-size: 2.125rem;
|
||
}
|
||
|
||
.calite-hero__tagline {
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.calite-detail__body {
|
||
padding: 18px 28px 28px;
|
||
max-width: 1120px;
|
||
margin-inline: auto;
|
||
width: 100%;
|
||
gap: 18px;
|
||
}
|
||
|
||
.attr-row {
|
||
max-width: 720px;
|
||
margin-inline: auto;
|
||
padding: 8px 0 4px;
|
||
}
|
||
|
||
.attr {
|
||
font-size: 0.875rem;
|
||
padding: 12px 12px;
|
||
}
|
||
|
||
.attr__icon {
|
||
width: 28px;
|
||
height: 28px;
|
||
}
|
||
|
||
.physical-cta {
|
||
max-width: 720px;
|
||
margin-inline: auto;
|
||
min-height: 56px;
|
||
}
|
||
|
||
.pattern-grid {
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 14px;
|
||
}
|
||
|
||
.calite-detail__footer.sticky-footer {
|
||
position: sticky;
|
||
bottom: 0;
|
||
inset-inline: auto;
|
||
width: 100%;
|
||
max-width: none;
|
||
padding: 12px 28px 24px;
|
||
background: var(--color-bg);
|
||
}
|
||
|
||
.inquiry-bar {
|
||
max-width: 1120px;
|
||
margin-inline: auto;
|
||
border-radius: 999px;
|
||
}
|
||
|
||
.toast {
|
||
bottom: 32px;
|
||
}
|
||
}
|
||
|
||
@media (min-width: 1200px) {
|
||
.calite-hero {
|
||
min-height: 360px;
|
||
height: 400px;
|
||
}
|
||
|
||
.calite-detail__body {
|
||
padding: 24px 40px 36px;
|
||
}
|
||
|
||
.pattern-grid {
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 16px;
|
||
}
|
||
|
||
.calite-detail__footer.sticky-footer {
|
||
padding-inline: 40px;
|
||
}
|
||
}
|
||
</style>
|