feat: switch to IRANSans FaNum fonts, add product images, rebuild out
Co-authored-by: Cursor <cursoragent@cursor.com>
@@ -3,85 +3,53 @@
|
|||||||
/* ---- local fonts ---- */
|
/* ---- local fonts ---- */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "IRANSans";
|
font-family: "IRANSans";
|
||||||
src: url("/fonts/IRANSansWeb_UltraLight.woff2") format("woff2");
|
src: url("/fonts/IRANSansWebFaNum_UltraLight.woff2") format("woff2");
|
||||||
font-weight: 200;
|
font-weight: 200;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "IRANSans";
|
font-family: "IRANSans";
|
||||||
src: url("/fonts/IRANSansWeb_Light.woff2") format("woff2");
|
src: url("/fonts/IRANSansWebFaNum_Light.woff2") format("woff2");
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "IRANSans";
|
font-family: "IRANSans";
|
||||||
src: url("/fonts/IRANSansWeb.woff2") format("woff2");
|
src: url("/fonts/IRANSansWebFaNum.woff2") format("woff2");
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "IRANSans";
|
font-family: "IRANSans";
|
||||||
src: url("/fonts/IRANSansWeb_Medium.woff2") format("woff2");
|
src: url("/fonts/IRANSansWebFaNum_Medium.woff2") format("woff2");
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "IRANSans";
|
font-family: "IRANSans";
|
||||||
src: url("/fonts/IRANSansWeb_Bold.woff2") format("woff2");
|
src: url("/fonts/IRANSansWebFaNum_Bold.woff2") format("woff2");
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "IRANSans Fallback";
|
font-family: "IRANSans";
|
||||||
src: local("Arial");
|
src: url("/fonts/IRANSansWebFaNum_Black.woff2") format("woff2");
|
||||||
ascent-override: 100%;
|
font-weight: 900;
|
||||||
descent-override: 35%;
|
|
||||||
line-gap-override: 0%;
|
|
||||||
size-adjust: 100%;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "ibmPlexMono";
|
|
||||||
src: url("/fonts/IBMPlexMono-Regular.woff2") format("woff2");
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
|
||||||
font-family: "ibmPlexMono";
|
|
||||||
src: url("/fonts/IBMPlexMono-Medium.woff2") format("woff2");
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "ibmPlexMono";
|
|
||||||
src: url("/fonts/IBMPlexMono-SemiBold.woff2") format("woff2");
|
|
||||||
font-weight: 600;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "ibmPlexMono Fallback";
|
|
||||||
src: local("Arial");
|
|
||||||
ascent-override: 77.95%;
|
|
||||||
descent-override: 20.91%;
|
|
||||||
line-gap-override: 0%;
|
|
||||||
size-adjust: 131.49%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--font-iransans: "IRANSans", "IRANSans Fallback", Tahoma, system-ui, sans-serif;
|
--font-iransans: "IRANSans", sans-serif;
|
||||||
--font-ibm-plex-mono: "ibmPlexMono", "ibmPlexMono Fallback", ui-monospace, monospace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--font-sans: var(--font-iransans), ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
--font-sans: var(--font-iransans);
|
||||||
--font-mono: var(--font-ibm-plex-mono), "IRANSans", ui-monospace, monospace;
|
--font-mono: var(--font-iransans);
|
||||||
|
|
||||||
--color-bg: #0b1224;
|
--color-bg: #0b1224;
|
||||||
--color-panel: #101a32;
|
--color-panel: #101a32;
|
||||||
|
|||||||
@@ -42,6 +42,17 @@ useSeoMeta({
|
|||||||
ogDescription: category.fullDesc,
|
ogDescription: category.fullDesc,
|
||||||
});
|
});
|
||||||
useJsonLd(categoryPageSchema(category), `category-${slug}`);
|
useJsonLd(categoryPageSchema(category), `category-${slug}`);
|
||||||
|
|
||||||
|
const activeGroup = ref<string | null>(null);
|
||||||
|
const visibleProducts = computed(() =>
|
||||||
|
activeGroup.value
|
||||||
|
? category.products.filter((p) => p.group === activeGroup.value)
|
||||||
|
: category.products,
|
||||||
|
);
|
||||||
|
const activeGroupDesc = computed(
|
||||||
|
() => category.groups?.find((g) => g.slug === activeGroup.value)?.desc,
|
||||||
|
);
|
||||||
|
const groupCount = (g: string) => category.products.filter((p) => p.group === g).length;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -106,20 +117,62 @@ useJsonLd(categoryPageSchema(category), `category-${slug}`);
|
|||||||
<section class="relative py-16 md:py-24">
|
<section class="relative py-16 md:py-24">
|
||||||
<div class="absolute inset-0 bg-grid-fine opacity-30" />
|
<div class="absolute inset-0 bg-grid-fine opacity-30" />
|
||||||
<div class="relative mx-auto max-w-[1480px] px-5 md:px-8">
|
<div class="relative mx-auto max-w-[1480px] px-5 md:px-8">
|
||||||
|
<div v-if="category.groups?.length" class="mb-10">
|
||||||
|
<div class="flex flex-wrap gap-2" role="tablist" aria-label="فیلتر زیردسته">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
:aria-selected="activeGroup === null"
|
||||||
|
class="rounded-full border px-4 py-2 text-sm transition-colors"
|
||||||
|
:class="
|
||||||
|
activeGroup === null
|
||||||
|
? 'border-accent bg-accent text-bg font-bold'
|
||||||
|
: 'border-line text-muted hover:border-accent/60 hover:text-accent'
|
||||||
|
"
|
||||||
|
@click="activeGroup = null"
|
||||||
|
>
|
||||||
|
همه
|
||||||
|
<span class="ms-1 font-mono text-xs opacity-70">{{ faNum(category.products.length) }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-for="g in category.groups"
|
||||||
|
:key="g.slug"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
:aria-selected="activeGroup === g.slug"
|
||||||
|
class="rounded-full border px-4 py-2 text-sm transition-colors"
|
||||||
|
:class="
|
||||||
|
activeGroup === g.slug
|
||||||
|
? 'border-accent bg-accent text-bg font-bold'
|
||||||
|
: 'border-line text-muted hover:border-accent/60 hover:text-accent'
|
||||||
|
"
|
||||||
|
@click="activeGroup = g.slug"
|
||||||
|
>
|
||||||
|
{{ g.name }}
|
||||||
|
<span class="ms-1 font-mono text-xs opacity-70">{{ faNum(groupCount(g.slug)) }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p v-if="activeGroupDesc" class="mt-4 text-sm text-muted">{{ activeGroupDesc }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
<Reveal v-for="(product, i) in category.products" :key="product.code" :delay="i * 0.06">
|
<Reveal v-for="(product, i) in visibleProducts" :key="product.code" :delay="i * 0.06">
|
||||||
<div
|
<div
|
||||||
class="group h-full overflow-hidden rounded-xl border border-line bg-panel/80 backdrop-blur transition-all duration-500 hover:border-accent/40 hover:shadow-[0_8px_40px_rgba(255,90,0,0.08)]"
|
class="group h-full overflow-hidden rounded-xl border border-line bg-panel/80 backdrop-blur transition-all duration-500 hover:border-accent/40 hover:shadow-[0_8px_40px_rgba(255,90,0,0.08)]"
|
||||||
>
|
>
|
||||||
<div class="relative aspect-[4/3] overflow-hidden">
|
<div
|
||||||
|
class="relative aspect-[4/3] overflow-hidden"
|
||||||
|
:class="product.image && 'bg-[radial-gradient(circle_at_center,rgba(255,255,255,0.14),transparent_70%)]'"
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
:src="CATEGORY_IMAGES[slug]"
|
:src="product.image ?? CATEGORY_IMAGES[slug]"
|
||||||
:alt="product.name"
|
:alt="product.name"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
class="size-full object-cover transition-transform duration-700 group-hover:scale-105"
|
class="size-full transition-transform duration-700 group-hover:scale-105"
|
||||||
|
:class="product.image ? 'object-contain p-8' : 'object-cover'"
|
||||||
/>
|
/>
|
||||||
<div class="absolute inset-0 bg-gradient-to-t from-bg/60 to-transparent" />
|
<div v-if="!product.image" class="absolute inset-0 bg-gradient-to-t from-bg/60 to-transparent" />
|
||||||
<div class="absolute inset-x-0 top-0 flex items-center justify-between p-4">
|
<div class="absolute inset-x-0 top-0 flex items-center justify-between p-4">
|
||||||
<span class="rounded-full bg-bg/60 px-3 py-1 font-mono text-[10px] tracking-widest text-ink/70 backdrop-blur">
|
<span class="rounded-full bg-bg/60 px-3 py-1 font-mono text-[10px] tracking-widest text-ink/70 backdrop-blur">
|
||||||
{{ product.code }}
|
{{ product.code }}
|
||||||
|
|||||||
@@ -24,21 +24,14 @@ export default defineNuxtConfig({
|
|||||||
link: [
|
link: [
|
||||||
{
|
{
|
||||||
rel: "preload",
|
rel: "preload",
|
||||||
href: "/fonts/IRANSansWeb.woff2",
|
href: "/fonts/IRANSansWebFaNum.woff2",
|
||||||
as: "font",
|
as: "font",
|
||||||
type: "font/woff2",
|
type: "font/woff2",
|
||||||
crossorigin: "",
|
crossorigin: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rel: "preload",
|
rel: "preload",
|
||||||
href: "/fonts/IRANSansWeb_Bold.woff2",
|
href: "/fonts/IRANSansWebFaNum_Bold.woff2",
|
||||||
as: "font",
|
|
||||||
type: "font/woff2",
|
|
||||||
crossorigin: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rel: "preload",
|
|
||||||
href: "/fonts/IBMPlexMono-Regular.woff2",
|
|
||||||
as: "font",
|
as: "font",
|
||||||
type: "font/woff2",
|
type: "font/woff2",
|
||||||
crossorigin: "",
|
crossorigin: "",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<!DOCTYPE html><html lang="fa" dir="rtl"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><script type="importmap">{"imports":{"#entry":"/_nuxt/Dfp1fBWi.js"}}</script><link rel="stylesheet" href="/_nuxt/entry.Dm-VTTRi.css" crossorigin><link rel="preload" href="/fonts/IRANSansWeb.woff2" as="font" type="font/woff2" crossorigin><link rel="preload" href="/fonts/IRANSansWeb_Bold.woff2" as="font" type="font/woff2" crossorigin><link rel="preload" href="/fonts/IBMPlexMono-Regular.woff2" as="font" type="font/woff2" crossorigin><link rel="modulepreload" as="script" crossorigin href="/_nuxt/Dfp1fBWi.js"><link rel="modulepreload" as="script" crossorigin href="/_nuxt/Bh52WncL.js"><script type="module" src="/_nuxt/Dfp1fBWi.js" crossorigin></script><link rel="icon" type="image/svg+xml" href="/favicon.svg"><link rel="manifest" href="/manifest.webmanifest"></head><body class="relative min-h-screen bg-bg font-sans text-ink antialiased"><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/",buildId:"c7ba7d9a-5f05-4176-9545-1689facbab14",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1791372518975,false]</script></body></html>
|
<!DOCTYPE html><html lang="fa" dir="rtl"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><script type="importmap">{"imports":{"#entry":"/_nuxt/CwuOmQRC.js"}}</script><link rel="stylesheet" href="/_nuxt/entry.DXyzAtZq.css" crossorigin><link rel="preload" href="/fonts/IRANSansWebFaNum.woff2" as="font" type="font/woff2" crossorigin><link rel="preload" href="/fonts/IRANSansWebFaNum_Bold.woff2" as="font" type="font/woff2" crossorigin><link rel="modulepreload" as="script" crossorigin href="/_nuxt/CwuOmQRC.js"><link rel="modulepreload" as="script" crossorigin href="/_nuxt/BLoJslU_.js"><script type="module" src="/_nuxt/CwuOmQRC.js" crossorigin></script><link rel="icon" type="image/svg+xml" href="/favicon.svg"><link rel="manifest" href="/manifest.webmanifest"></head><body class="relative min-h-screen bg-bg font-sans text-ink antialiased"><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/",buildId:"9bc20942-bdb1-40fa-859b-b1c16104da2f",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1791725332415,false]</script></body></html>
|
||||||
@@ -1 +1 @@
|
|||||||
{"id":"c7ba7d9a-5f05-4176-9545-1689facbab14","timestamp":1791372451268}
|
{"id":"9bc20942-bdb1-40fa-859b-b1c16104da2f","timestamp":1791725267318}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"id":"9bc20942-bdb1-40fa-859b-b1c16104da2f","timestamp":1791725267318,"prerendered":["/","/categories/respiratory-protection","/categories/safety-helmets","/categories/protective-gloves","/categories/hearing-protection","/__not-found","/categories/traffic-equipment","/categories/eye-protection","/categories/workwear","/categories/safety-footwear"]}
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"id":"c7ba7d9a-5f05-4176-9545-1689facbab14","timestamp":1791372451268,"prerendered":["/","/categories/hearing-protection","/categories/safety-helmets","/categories/traffic-equipment","/categories/protective-gloves","/categories/workwear","/categories/eye-protection","/__not-found","/categories/safety-footwear","/categories/respiratory-protection"]}
|
|
||||||
@@ -1 +1 @@
|
|||||||
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791372518597]
|
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791725331531]
|
||||||
@@ -1 +1 @@
|
|||||||
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791372518973]
|
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791725332414]
|
||||||
@@ -1 +1 @@
|
|||||||
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791372518974]
|
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791725332414]
|
||||||
@@ -1 +1 @@
|
|||||||
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791372518973]
|
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791725332414]
|
||||||
@@ -1 +1 @@
|
|||||||
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791372518974]
|
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791725332414]
|
||||||
@@ -1 +1 @@
|
|||||||
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791372518972]
|
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791725332413]
|
||||||
@@ -1 +1 @@
|
|||||||
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791372518973]
|
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791725332414]
|
||||||
@@ -1 +1 @@
|
|||||||
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791372518974]
|
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791725332415]
|
||||||
@@ -1 +1 @@
|
|||||||
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791372518972]
|
[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1791725332413]
|
||||||
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 65 KiB |