Refine homepage layout, typography, and component polish

- Header: replaced 3-bar masthead with slim dark meta strip + 68px main bar
  (brand · centered nav · search/CTA actions). ~60% shorter than before.
- Home: added numbered SectionLabel chapter markers (01–07) between
  sections with red accent bar; alternated paper/paper-2 backgrounds
  for clearer section separation.
- Typography: wrapped base h1–h4 and p element styles in @layer base so
  Tailwind utilities (text-white, etc.) can override defaults. Previously
  unlayered p { color: var(--ink-3) } was overriding all carousel white
  titles, making them appear grey-brown.
- Carousel: removed pagination arrows; strengthened title gradient,
  added top centered red category badge, text-shadow + generous padding
  so titles never crash into card edges. Modal hero received same
  treatment with overflow-safe inset positioning.
- PlansSection: rewrote with compact shadcn-pricing pattern; price
  reduced from 42px to 26px; featured plan elevated with translateY
  and shadow; star badge for "Popular" plan.
- TeamSection: redesigned member cards more compact (avatar 60→44px,
  reports count moved to top-end, line-clamp on bio).
- Removed inline padding overrides across all sections so Tailwind
  max-md:px-5 mobile responsiveness actually works.
This commit is contained in:
alireza 2026-05-25 17:17:16 +03:30
parent 2dd08650cf
commit 8c5d2a9739
20 changed files with 1406 additions and 1505 deletions

View File

@ -89,7 +89,7 @@ export function Footer() {
style={{ backgroundColor: 'var(--ink)', color: 'rgba(255,255,255,0.6)' }}
>
{/* TOP GRID */}
<div style={{ padding: '64px 48px' }}>
<div style={{ padding: '64px 0' }} className="max-w-7xl mx-auto w-full px-12 max-md:px-5">
<div
style={{
display: 'grid',
@ -167,8 +167,12 @@ export function Footer() {
{/* BOTTOM ROW */}
<div
style={{
padding: '20px 48px',
borderTop: '1px solid rgba(255,255,255,0.08)',
}}
>
<div
style={{
padding: '20px 48px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
@ -177,6 +181,7 @@ export function Footer() {
flexWrap: 'wrap',
gap: 12,
}}
className="max-w-7xl mx-auto w-full px-12 max-md:px-5"
>
<span>© ۱۴۰۳ اندیشکده فولاد ایران تمام حقوق محفوظ است</span>
<div style={{ display: 'flex', gap: 18 }}>
@ -191,6 +196,7 @@ export function Footer() {
))}
</div>
</div>
</div>
</footer>
)
}

View File

@ -5,13 +5,12 @@ import { Search, Menu, X } from 'lucide-react'
import { motion, AnimatePresence } from 'framer-motion'
/*
DESIGN TOKENS (mirrors CSS variables for inline styles)
DESIGN TOKENS
*/
const T = {
paper: 'var(--paper)',
paper2: 'var(--paper-2)',
ink: 'var(--ink)',
ink2: 'var(--ink-2)',
ink3: 'var(--ink-3)',
ink4: 'var(--ink-4)',
ink5: 'var(--ink-5)',
@ -29,67 +28,15 @@ const NAV_ITEMS = [
{ label: 'رویدادها', to: '/events' },
{ label: 'اسکنر جهانی', to: '/scanner' },
{ label: 'تیم تحریریه', to: '/team' },
{ label: 'داشبورد بازار', to: '/market' },
{ label: 'آرشیو', to: '/archive' },
]
const COMPACT_NAV = [
{ label: 'گزارش‌ها', to: '/reports' },
{ label: 'بازار', to: '/market' },
{ label: 'رویدادها', to: '/events' },
{ label: 'ریسک', to: '/risks' },
]
const META_LEFT = ['درباره ما', 'کارشناسان', 'همکاری', 'رسانه']
/*
HELPERS
NAV LINK (used in main + drawer)
*/
/** Social icon box */
function SocialIcon({ label, href }: { label: string; href: string }) {
const [hovered, setHovered] = useState(false)
return (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
aria-label={label}
style={{
width: 28,
height: 28,
border: `1px solid ${T.ruleThin}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 10,
fontWeight: 700,
color: hovered ? T.paper : T.ink3,
background: hovered ? T.ink : 'transparent',
cursor: 'pointer',
textDecoration: 'none',
transition: 'background 120ms, color 120ms',
flexShrink: 0,
lineHeight: 1,
}}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
{label}
</a>
)
}
/** Full nav link (expanded navbar) */
function FullNavItem({
to,
label,
isLast,
}: {
to: string
label: string
isLast: boolean
}) {
function MainNavItem({ to, label }: { to: string; label: string }) {
const [hovered, setHovered] = useState(false)
return (
<NavLink
@ -97,18 +44,16 @@ function FullNavItem({
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
style={({ isActive }: { isActive: boolean }): CSSProperties => ({
padding: '12px 20px',
fontSize: 12,
fontSize: 13,
fontWeight: 600,
color: isActive || hovered ? T.paper : T.ink,
background: isActive ? T.red : hovered ? T.ink : 'transparent',
color: isActive ? T.red : hovered ? T.ink : T.ink3,
textDecoration: 'none',
borderRight: !isLast ? `1px solid ${T.ruleThin}` : 'none',
display: 'flex',
alignItems: 'center',
transition: 'background 150ms, color 150ms',
padding: '6px 0',
position: 'relative',
whiteSpace: 'nowrap',
letterSpacing: '0.2px',
transition: 'color 120ms',
borderBottom: isActive ? `2px solid ${T.red}` : '2px solid transparent',
letterSpacing: '0.1px',
})}
>
{label}
@ -116,44 +61,6 @@ function FullNavItem({
)
}
/** Compact nav link */
function CompactNavItem({
to,
label,
isFirst,
}: {
to: string
label: string
isFirst: boolean
}) {
const [hovered, setHovered] = useState(false)
return (
<NavLink
to={to}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
style={({ isActive }: { isActive: boolean }): CSSProperties => ({
fontSize: 12,
fontWeight: 600,
color: isActive || hovered ? T.paper : T.ink,
background: isActive ? T.ink : hovered ? T.paper2 : 'transparent',
textDecoration: 'none',
padding: '0 14px',
height: 56,
display: 'flex',
alignItems: 'center',
borderRight: `1px solid ${T.ruleThin}`,
borderLeft: isFirst ? `1px solid ${T.ruleThin}` : 'none',
whiteSpace: 'nowrap',
transition: 'background 120ms, color 120ms',
})}
>
{label}
</NavLink>
)
}
/** Drawer nav link */
function DrawerNavItem({
to,
label,
@ -191,21 +98,14 @@ function DrawerNavItem({
MAIN COMPONENT
*/
export default function Header() {
const [compact, setCompact] = useState(false)
const [mobileOpen, setMobileOpen] = useState(false)
const [isMobile, setIsMobile] = useState(false)
const searchRef = useRef<HTMLInputElement>(null)
/* scroll listener */
useEffect(() => {
const onScroll = () => setCompact(window.scrollY > 80)
window.addEventListener('scroll', onScroll, { passive: true })
return () => window.removeEventListener('scroll', onScroll)
}, [])
const [searchOpen, setSearchOpen] = useState(false)
const searchInputRef = useRef<HTMLInputElement>(null)
/* viewport listener */
useEffect(() => {
const mq = window.matchMedia('(max-width: 767px)')
const mq = window.matchMedia('(max-width: 1023px)')
const handler = (e: MediaQueryListEvent | MediaQueryList) => setIsMobile(e.matches)
handler(mq)
mq.addEventListener('change', handler)
@ -218,7 +118,10 @@ export default function Header() {
return () => { document.body.style.overflow = '' }
}, [mobileOpen])
const showCompact = compact || isMobile
/* focus search on open */
useEffect(() => {
if (searchOpen) searchInputRef.current?.focus()
}, [searchOpen])
return (
<header
@ -230,214 +133,233 @@ export default function Header() {
direction: 'rtl',
}}
>
{/*
EXPANDED STATE
*/}
<AnimatePresence initial={false}>
{!showCompact && (
<motion.div
key="expanded"
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.22, ease: 'easeInOut' }}
style={{ overflow: 'hidden' }}
>
{/* ── TOP META BAR ─────────────────────────── */}
{/*
TOP META STRIP slim, dark
*/}
<div
style={{
background: T.ink,
color: 'rgba(244,239,231,0.7)',
}}
className="max-md:hidden"
>
<div
style={{
borderBottom: `1px solid ${T.ruleThin}`,
padding: '8px 48px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
gap: 16,
height: 32,
fontSize: 11,
}}
className="max-w-7xl mx-auto px-12"
>
{/* Right: login / language */}
<ul
{/* Right: date */}
<span style={{ letterSpacing: '0.3px' }}>
شنبه ۲۱ بهمن ۱۴۰۳ · دوره دوازدهم
</span>
{/* Left: meta links + login */}
<div style={{ display: 'flex', alignItems: 'center', gap: 18 }}>
{META_LEFT.map((item) => (
<a
key={item}
href="#"
style={{
display: 'flex',
alignItems: 'center',
gap: 12,
listStyle: 'none',
fontSize: 11,
color: 'rgba(244,239,231,0.7)',
textDecoration: 'none',
transition: 'color 120ms',
}}
onMouseEnter={e => (e.currentTarget.style.color = 'var(--paper)')}
onMouseLeave={e => (e.currentTarget.style.color = 'rgba(244,239,231,0.7)')}
>
<li>
{item}
</a>
))}
<span style={{ color: 'rgba(244,239,231,0.2)' }}>·</span>
<a
href="/login"
style={{
fontSize: 11,
fontWeight: 500,
color: T.ink3,
color: 'rgba(244,239,231,0.85)',
textDecoration: 'none',
fontWeight: 600,
}}
>
ورود
</a>
</li>
<li style={{ fontSize: 11, color: T.ruleThin, userSelect: 'none' }}>|</li>
<li>
<a
href="#"
style={{ fontSize: 11, color: T.ink4, textDecoration: 'none' }}
>
فارسی
</a>
</li>
</ul>
{/* Center: date */}
<span
style={{
fontSize: 11,
color: T.ink4,
fontWeight: 400,
letterSpacing: '0.3px',
whiteSpace: 'nowrap',
color: 'rgba(244,239,231,0.7)',
textDecoration: 'none',
}}
>
شنبه ۲۱ بهمن ۱۴۰۳ · دوره دوازدهم
</span>
EN
</a>
</div>
</div>
</div>
{/* Left: secondary nav */}
<ul
{/*
MAIN BAR brand | nav | actions
*/}
<div
style={{
borderBottom: `2px solid ${T.ink}`,
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 20,
listStyle: 'none',
}}
>
{META_LEFT.map((item) => (
<li key={item}>
<a
href="#"
style={{ fontSize: 11, color: T.ink4, textDecoration: 'none' }}
>
{item}
</a>
</li>
))}
</ul>
</div>
{/* ── MASTHEAD MAIN ────────────────────────── */}
<div
style={{
padding: '28px 48px',
display: 'grid',
gridTemplateColumns: '1fr auto 1fr',
alignItems: 'center',
justifyContent: 'space-between',
height: 68,
gap: 24,
}}
className="max-w-7xl mx-auto px-12 max-md:px-5 max-md:h-[60px]"
>
{/* Left col */}
<div
{/* ── Brand ── */}
<NavLink
to="/"
style={{
display: 'flex',
flexDirection: 'column',
gap: 10,
}}
>
<div>
<p
style={{
fontSize: 9,
fontWeight: 700,
textTransform: 'uppercase',
letterSpacing: '2.5px',
color: T.ink5,
lineHeight: 1.4,
}}
>
تحلیل مستقل · سیاستگذاری
</p>
<p
style={{
fontSize: 11,
color: T.ink5,
lineHeight: 1.6,
marginTop: 4,
}}
>
۲۸۰ گزارش منتشرشده · ۴۵ متخصص
</p>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<SocialIcon label="in" href="https://linkedin.com" />
<SocialIcon label="tg" href="https://t.me" />
<SocialIcon label="tw" href="https://x.com" />
</div>
</div>
{/* Center col */}
<div
style={{
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<h1
style={{
fontSize: 'clamp(28px, 3.5vw, 48px)',
fontWeight: 900,
gap: 2,
textDecoration: 'none',
flexShrink: 0,
lineHeight: 1,
letterSpacing: '-1px',
}}
>
<span
style={{
fontSize: 19,
fontWeight: 900,
color: T.ink,
letterSpacing: '-0.5px',
whiteSpace: 'nowrap',
}}
className="max-md:text-[16px]"
>
اندیشکده فولاد ایران
</h1>
<hr
aria-hidden="true"
</span>
<span
style={{
width: 60,
height: 2,
background: T.red,
border: 'none',
margin: '8px 0',
flexShrink: 0,
}}
/>
<p
style={{
fontSize: 11,
letterSpacing: '5px',
color: T.ink4,
fontWeight: 400,
fontSize: 8,
letterSpacing: '2.5px',
color: T.ink5,
fontWeight: 600,
textTransform: 'uppercase',
whiteSpace: 'nowrap',
}}
className="max-md:hidden"
>
IRAN STEEL POLICY INSTITUTE
</p>
</div>
</span>
</NavLink>
{/* Right col */}
<div
{/* ── Center: Nav (desktop only) ── */}
{!isMobile && (
<nav
aria-label="ناوبری اصلی"
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-end',
gap: 8,
alignItems: 'center',
gap: 22,
flex: 1,
justifyContent: 'center',
}}
>
<div style={{ position: 'relative' }}>
<input
ref={searchRef}
type="search"
placeholder="جستجو در گزارش‌ها..."
{NAV_ITEMS.map((item) => (
<MainNavItem key={item.to} to={item.to} label={item.label} />
))}
</nav>
)}
{/* ── Left: Actions ── */}
<div style={{ display: 'flex', alignItems: 'center', gap: 10, flexShrink: 0 }}>
{/* Search icon button */}
<button
aria-label="جستجو"
onClick={() => setSearchOpen(s => !s)}
style={{
border: `1px solid ${T.ink}`,
padding: '8px 12px 8px 32px',
fontSize: 12,
width: 34,
height: 34,
border: `1px solid ${T.ruleThin}`,
background: searchOpen ? T.ink : 'transparent',
color: searchOpen ? T.paper : T.ink,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'background 120ms, color 120ms',
borderRadius: 0,
}}
>
<Search size={14} aria-hidden="true" />
</button>
{/* Subscribe CTA — desktop only */}
{!isMobile && <RedCtaButton>اشتراک</RedCtaButton>}
{/* Mobile menu */}
{isMobile && (
<button
aria-label="باز کردن منو"
onClick={() => setMobileOpen(true)}
style={{
width: 34,
height: 34,
border: `1px solid ${T.ruleThin}`,
background: 'transparent',
width: 200,
color: T.ink,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 0,
}}
>
<Menu size={18} />
</button>
)}
</div>
</div>
</div>
{/*
SEARCH DROPDOWN
*/}
<AnimatePresence>
{searchOpen && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.2 }}
style={{
overflow: 'hidden',
borderBottom: `1px solid ${T.ruleThin}`,
background: T.paper2,
}}
>
<div
style={{ padding: '20px 0' }}
className="max-w-7xl mx-auto px-12 max-md:px-5"
>
<input
ref={searchInputRef}
type="search"
placeholder="جستجو در گزارش‌ها، تحلیل‌ها، رویدادها..."
aria-label="جستجو"
onKeyDown={e => e.key === 'Escape' && setSearchOpen(false)}
style={{
width: '100%',
border: `1px solid ${T.ink}`,
padding: '12px 16px',
fontSize: 14,
background: T.paper,
color: T.ink,
outline: 'none',
borderRadius: 0,
@ -445,152 +367,17 @@ export default function Header() {
direction: 'rtl',
}}
/>
<Search
size={14}
aria-hidden="true"
style={{
position: 'absolute',
left: 10,
top: '50%',
transform: 'translateY(-50%)',
color: T.ink4,
pointerEvents: 'none',
}}
/>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<FilledButton>اشتراک سازمانی</FilledButton>
<OutlineButton>ورود</OutlineButton>
</div>
</div>
</div>
{/* ── NAV BAR ──────────────────────────────── */}
<nav
aria-label="ناوبری اصلی"
style={{
borderTop: `1px solid ${T.ruleThin}`,
borderBottom: `3px solid ${T.ink}`,
display: 'flex',
flexDirection: 'row',
overflowX: 'auto',
}}
>
{NAV_ITEMS.map((item, i) => (
<FullNavItem
key={item.to}
to={item.to}
label={item.label}
isLast={i === NAV_ITEMS.length - 1}
/>
))}
</nav>
</motion.div>
)}
</AnimatePresence>
{/*
COMPACT STATE
*/}
<AnimatePresence initial={false}>
{showCompact && (
<motion.div
key="compact"
initial={{ opacity: 0, y: -8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }}
transition={{ duration: 0.18, ease: 'easeOut' }}
>
<div
style={{
height: 56,
borderBottom: `2px solid ${T.ink}`,
padding: '0 48px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
gap: 24,
}}
>
{/* Brand */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 1, flexShrink: 0 }}>
<span
style={{
fontSize: 16,
fontWeight: 900,
lineHeight: 1,
color: T.ink,
whiteSpace: 'nowrap',
}}
>
اندیشکده فولاد ایران
</span>
<span
style={{
fontSize: 9,
letterSpacing: '3px',
color: T.ink5,
fontWeight: 400,
textTransform: 'uppercase',
lineHeight: 1,
whiteSpace: 'nowrap',
}}
>
IRAN STEEL POLICY INSTITUTE
</span>
</div>
{/* Desktop compact nav */}
{!isMobile ? (
<nav
aria-label="ناوبری فشرده"
style={{
display: 'flex',
alignItems: 'center',
flex: 1,
justifyContent: 'flex-end',
}}
>
{COMPACT_NAV.map((item, i) => (
<CompactNavItem
key={item.to}
to={item.to}
label={item.label}
isFirst={i === 0}
/>
))}
<RedCtaButton style={{ marginRight: 16 }}>اشتراک</RedCtaButton>
</nav>
) : (
/* Mobile hamburger */
<button
aria-label="باز کردن منو"
onClick={() => setMobileOpen(true)}
style={{
background: 'transparent',
border: 'none',
cursor: 'pointer',
color: T.ink,
padding: 4,
display: 'flex',
alignItems: 'center',
}}
>
<Menu size={22} />
</button>
)}
</div>
</motion.div>
)}
</AnimatePresence>
{/*
{/*
MOBILE DRAWER
*/}
*/}
<AnimatePresence>
{mobileOpen && (
<>
{/* Overlay */}
<motion.div
key="overlay"
initial={{ opacity: 0 }}
@ -605,20 +392,18 @@ export default function Header() {
zIndex: 100,
}}
/>
{/* Drawer panel */}
<motion.div
key="drawer"
initial={{ x: 280 }}
initial={{ x: 300 }}
animate={{ x: 0 }}
exit={{ x: 280 }}
exit={{ x: 300 }}
transition={{ duration: 0.25, ease: [0.32, 0, 0.18, 1] }}
style={{
position: 'fixed',
top: 0,
right: 0,
height: '100dvh',
width: 280,
width: 300,
background: T.paper,
zIndex: 101,
display: 'flex',
@ -634,11 +419,11 @@ export default function Header() {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
background: T.ink,
color: T.paper,
}}
>
<span
style={{ fontSize: 14, fontWeight: 700, color: T.ink }}
>
<span style={{ fontSize: 14, fontWeight: 800 }}>
اندیشکده فولاد ایران
</span>
<button
@ -648,7 +433,7 @@ export default function Header() {
background: 'transparent',
border: 'none',
cursor: 'pointer',
color: T.ink,
color: T.paper,
padding: 4,
display: 'flex',
alignItems: 'center',
@ -658,7 +443,7 @@ export default function Header() {
</button>
</div>
{/* Nav list */}
{/* Nav */}
<nav style={{ flex: 1, overflowY: 'auto' }}>
{NAV_ITEMS.map((item) => (
<DrawerNavItem
@ -670,23 +455,27 @@ export default function Header() {
))}
</nav>
{/* Footer CTAs */}
{/* Footer */}
<div
style={{
padding: '16px 20px',
borderTop: `1px solid ${T.ruleThin}`,
display: 'flex',
flexDirection: 'column',
gap: 8,
gap: 10,
}}
>
<RedCtaButton style={{ width: '100%' }}>اشتراک سازمانی</RedCtaButton>
<OutlineButton style={{ width: '100%' }}>ورود</OutlineButton>
<RedCtaButton style={{ width: '100%', padding: '10px 16px' }}>
اشتراک سازمانی
</RedCtaButton>
<OutlineButton style={{ width: '100%', padding: '10px 16px' }}>
ورود
</OutlineButton>
<div
style={{
display: 'flex',
gap: 12,
marginTop: 4,
gap: 14,
marginTop: 6,
flexWrap: 'wrap',
}}
>
@ -710,9 +499,9 @@ export default function Header() {
}
/*
MICRO BUTTON COMPONENTS (hover via useState clean)
BUTTONS
*/
function FilledButton({
function RedCtaButton({
children,
style: extraStyle,
}: {
@ -725,18 +514,21 @@ function FilledButton({
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
style={{
background: hovered ? T.red : T.ink,
background: hovered ? T.ink : T.red,
color: T.paper,
border: `1px solid ${hovered ? T.red : T.ink}`,
padding: '7px 16px',
border: 'none',
padding: '8px 16px',
fontSize: 12,
fontWeight: 600,
fontWeight: 700,
cursor: 'pointer',
fontFamily: 'inherit',
borderRadius: 0,
letterSpacing: '0.3px',
transition: 'background 120ms, border-color 120ms',
transition: 'background 120ms',
whiteSpace: 'nowrap',
textAlign: 'center',
flexShrink: 0,
height: 34,
...extraStyle,
}}
>
@ -761,7 +553,7 @@ function OutlineButton({
background: hovered ? T.ink : 'transparent',
color: hovered ? T.paper : T.ink,
border: `1px solid ${T.ink}`,
padding: '7px 14px',
padding: '8px 14px',
fontSize: 12,
fontWeight: 600,
cursor: 'pointer',
@ -778,38 +570,3 @@ function OutlineButton({
</button>
)
}
function RedCtaButton({
children,
style: extraStyle,
}: {
children: React.ReactNode
style?: CSSProperties
}) {
const [hovered, setHovered] = useState(false)
return (
<button
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
style={{
background: hovered ? T.ink : T.red,
color: T.paper,
border: 'none',
padding: '7px 16px',
fontSize: 12,
fontWeight: 700,
cursor: 'pointer',
fontFamily: 'inherit',
borderRadius: 0,
letterSpacing: '0.3px',
transition: 'background 120ms',
whiteSpace: 'nowrap',
textAlign: 'center',
flexShrink: 0,
...extraStyle,
}}
>
{children}
</button>
)
}

View File

@ -43,27 +43,14 @@ export function Carousel({
initialScroll?: number
}) {
const carouselRef = useRef<HTMLDivElement>(null)
const [canScrollLeft, setCanScrollLeft] = useState(false)
const [canScrollRight, setCanScrollRight] = useState(true)
const [currentIndex, setCurrentIndex] = useState(0)
useEffect(() => {
if (carouselRef.current) {
carouselRef.current.scrollLeft = initialScroll
checkScrollability()
}
}, [initialScroll])
function checkScrollability() {
if (!carouselRef.current) return
const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current
setCanScrollLeft(scrollLeft > 0)
setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 1)
}
function scrollLeft() { carouselRef.current?.scrollBy({ left: -340, behavior: 'smooth' }) }
function scrollRight() { carouselRef.current?.scrollBy({ left: 340, behavior: 'smooth' }) }
function handleCardClose(index: number) {
if (!carouselRef.current) return
const cardWidth = window.innerWidth < 768 ? 230 : 350
@ -77,7 +64,6 @@ export function Carousel({
{/* Scrollable track */}
<div
ref={carouselRef}
onScroll={checkScrollability}
className="flex w-full overflow-x-scroll overscroll-x-auto scroll-smooth py-10 md:py-14 [scrollbar-width:none]"
style={{ direction: 'ltr' }}
>
@ -96,27 +82,6 @@ export function Carousel({
</div>
</div>
{/* Arrow buttons */}
<div className="flex justify-end gap-2 px-10 pb-6">
<button
onClick={scrollLeft}
disabled={!canScrollLeft}
className="h-10 w-10 rounded-full bg-[var(--paper-3)] border border-[var(--rule-thin)] flex items-center justify-center disabled:opacity-30 transition-opacity hover:bg-[var(--paper-2)]"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} className="h-5 w-5 text-[var(--ink)]">
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
</svg>
</button>
<button
onClick={scrollRight}
disabled={!canScrollRight}
className="h-10 w-10 rounded-full bg-[var(--paper-3)] border border-[var(--rule-thin)] flex items-center justify-center disabled:opacity-30 transition-opacity hover:bg-[var(--paper-2)]"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} className="h-5 w-5 text-[var(--ink)]">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</CarouselContext.Provider>
)
@ -183,19 +148,31 @@ export function Card({
</button>
{/* Hero image strip */}
<div className="relative h-52 overflow-hidden" style={{ flexShrink: 0 }}>
<div className="relative h-56 overflow-hidden" style={{ flexShrink: 0 }}>
<img
src={card.src}
alt={card.title}
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent" />
{/* inset-x-0 ensures text never overflows past modal edges */}
<div className="absolute bottom-0 inset-x-0 p-6">
<p className="text-white/70 text-xs font-bold tracking-widest uppercase mb-1">
{/* Stronger gradient — fully opaque at bottom for readable text */}
<div className="absolute inset-0 bg-gradient-to-t from-black via-black/70 via-[40%] to-transparent" />
{/* Category badge — red, top */}
<div className="absolute top-6 right-8 z-10">
<span
className="inline-block text-white px-3 py-1.5 text-[11px] font-bold tracking-[2px] whitespace-nowrap"
style={{ background: 'var(--red)' }}
>
{card.category}
</p>
<p className="text-white text-lg font-bold leading-snug line-clamp-2">
</span>
</div>
{/* Title — bottom, bright white with shadow for clarity */}
<div className="absolute bottom-0 inset-x-0 px-8 pb-6 pt-10">
<p
className="text-white text-xl font-extrabold leading-tight line-clamp-2"
style={{ textShadow: '0 2px 8px rgba(0,0,0,0.6)' }}
>
{card.title}
</p>
</div>
@ -223,15 +200,25 @@ export function Card({
whileHover={{ scale: 1.015 }}
transition={{ duration: 0.2 }}
>
{/* Bottom-to-top gradient so text is readable — tall enough to cover 2 lines */}
<div className="absolute inset-0 bg-gradient-to-t from-black/95 via-black/65 via-[35%] to-transparent z-20 pointer-events-none" />
{/* Strong bottom-to-top gradient — fully opaque at bottom so title pops */}
<div className="absolute inset-0 bg-gradient-to-t from-black via-black/75 via-[45%] to-transparent z-20 pointer-events-none" />
{/* Text — bottom of card, small */}
<div className="relative z-30 p-5 w-full text-right overflow-hidden">
<p className="text-white/65 text-[10px] font-bold tracking-widest uppercase mb-1.5 truncate">
{/* Top centered category badge — prominent, eye-catching */}
<div className="absolute top-6 left-1/2 -translate-x-1/2 z-30">
<span
className="inline-block text-white px-4 py-1.5 text-[12px] font-bold tracking-[2px] whitespace-nowrap"
style={{ background: 'var(--red)' }}
>
{card.category}
</p>
<p className="text-white text-sm font-bold leading-snug line-clamp-2">
</span>
</div>
{/* Bottom: title — bright white, readable */}
<div className="relative z-30 px-7 pb-6 pt-10 w-full text-right overflow-hidden">
<p
className="text-white text-[16px] font-extrabold leading-snug line-clamp-3"
style={{ textShadow: '0 2px 8px rgba(0,0,0,0.6)' }}
>
{card.title}
</p>
</div>

View File

@ -85,31 +85,34 @@
--fs-sm: clamp(11px, 0.75vw, 13px);
--fs-xs: clamp(9px, 0.6vw, 11px);
/* Letter spacing */
--ls-display: -2.5px;
--ls-h1: -2px;
--ls-h2: -1px;
--ls-h3: -0.3px;
--ls-body: 0px;
--ls-label: 1.5px;
--ls-overline: 2.5px;
--ls-widest: 3px;
/* Letter spacing (Persian typography uses normal spacing to keep cursive script connected) */
--ls-display: normal;
--ls-h1: normal;
--ls-h2: normal;
--ls-h3: normal;
--ls-body: normal;
--ls-label: normal;
--ls-overline: normal;
--ls-widest: normal;
/* Line heights */
--lh-display: 1.04;
--lh-heading: 1.12;
--lh-subhead: 1.3;
/* Line heights (increased for Persian script to prevent accent collisions and overlap) */
--lh-display: 1.25;
--lh-heading: 1.3;
--lh-subhead: 1.4;
--lh-body: 1.8;
--lh-relaxed: 1.85;
--lh-label: 1.3;
--lh-label: 1.4;
}
/* ── Base element styles ──────────────────────── */
/* Base element styles
Inside @layer base so Tailwind utilities (text-white, text-red, etc.)
in the @utilities layer can override these defaults. */
@layer base {
h1 {
font-size: var(--fs-h1);
font-weight: 900;
line-height: var(--lh-heading);
letter-spacing: var(--ls-h1);
letter-spacing: normal;
color: var(--ink);
margin: 0;
}
@ -117,7 +120,7 @@ h2 {
font-size: var(--fs-h2);
font-weight: 900;
line-height: var(--lh-heading);
letter-spacing: var(--ls-h2);
letter-spacing: normal;
color: var(--ink);
margin: 0;
}
@ -125,7 +128,7 @@ h3 {
font-size: var(--fs-h3);
font-weight: 700;
line-height: var(--lh-subhead);
letter-spacing: var(--ls-h3);
letter-spacing: normal;
color: var(--ink);
margin: 0;
}
@ -133,22 +136,31 @@ h4 {
font-size: var(--fs-h4);
font-weight: 700;
line-height: var(--lh-subhead);
letter-spacing: normal;
color: var(--ink);
margin: 0;
}
p {
font-size: var(--fs-body);
font-weight: 400; /* was 300 — too thin on most screens */
font-weight: 400;
line-height: var(--lh-body);
letter-spacing: normal;
color: var(--ink-3);
margin: 0;
}
}
/*
BASE
*/
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* McKinsey centered container margins (physical override of standard logical properties) */
.mx-auto {
margin-left: auto !important;
margin-right: auto !important;
}
html { scroll-behavior: smooth; direction: rtl; }
body {
@ -157,6 +169,7 @@ body {
color: var(--ink);
font-size: var(--fs-body);
line-height: var(--lh-body);
letter-spacing: normal;
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
}
@ -180,7 +193,7 @@ body {
.t-overline {
font-size: var(--fs-xs);
font-weight: 700;
letter-spacing: var(--ls-overline);
letter-spacing: normal;
text-transform: uppercase;
line-height: var(--lh-label);
}

View File

@ -55,14 +55,10 @@ function ReportRow({ report }: { report: (typeof reports)[0] }) {
return (
<div
className="grid grid-cols-[64px_1fr_140px_52px_80px] max-md:grid-cols-[56px_1fr_70px] archive-row items-center border-b border-[var(--rule-thin)] min-h-[40px]"
style={{
display: 'grid',
gridTemplateColumns: '64px 1fr 140px 52px 80px',
alignItems: 'center',
borderBottom: '1px solid var(--rule-thin)',
background: hovered ? 'var(--paper-2)' : 'transparent',
transition: 'background 100ms',
minHeight: 40,
}}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
@ -78,7 +74,6 @@ function ReportRow({ report }: { report: (typeof reports)[0] }) {
style={{
fontSize: 9,
fontWeight: 700,
letterSpacing: '0.8px',
color: typeColor,
border: `1px solid ${typeColor}`,
padding: '2px 5px',
@ -116,6 +111,7 @@ function ReportRow({ report }: { report: (typeof reports)[0] }) {
borderLeft: '1px solid var(--rule-thin)',
overflow: 'hidden',
}}
className="max-md:hidden"
>
<span
style={{
@ -139,6 +135,7 @@ function ReportRow({ report }: { report: (typeof reports)[0] }) {
borderLeft: '1px solid var(--rule-thin)',
textAlign: 'center',
}}
className="max-md:hidden"
>
<span style={{ fontSize: 11, color: 'var(--ink-5)', fontWeight: 400 }}>
{report.pages.toLocaleString('fa-IR')} ص
@ -355,29 +352,20 @@ export default function Archive() {
{/* column headers */}
<div
style={{
display: 'grid',
gridTemplateColumns: '64px 1fr 140px 52px 80px',
alignItems: 'center',
borderTop: '2px solid var(--ink)',
borderBottom: '1px solid var(--ink)',
marginTop: 8,
background: 'var(--paper-2)',
}}
className="grid grid-cols-[64px_1fr_140px_52px_80px] max-md:grid-cols-[56px_1fr_70px] archive-row items-center border-t-2 border-b border-[var(--ink)] mt-2 bg-[var(--paper-2)]"
>
{[
{ label: 'نوع', style: { padding: '6px 12px', borderLeft: '1px solid var(--rule-thin)' } },
{ label: 'عنوان گزارش', style: { padding: '6px 16px', borderLeft: '1px solid var(--rule-thin)' } },
{ label: 'نویسنده', style: { padding: '6px 12px', borderLeft: '1px solid var(--rule-thin)' } },
{ label: 'صفحه', style: { padding: '6px 10px', borderLeft: '1px solid var(--rule-thin)', textAlign: 'center' as const } },
{ label: 'نویسنده', style: { padding: '6px 12px', borderLeft: '1px solid var(--rule-thin)' }, hideMobile: true },
{ label: 'صفحه', style: { padding: '6px 10px', borderLeft: '1px solid var(--rule-thin)', textAlign: 'center' as const }, hideMobile: true },
{ label: 'قیمت', style: { padding: '6px 12px', textAlign: 'left' as const } },
].map((col) => (
<div key={col.label} style={col.style}>
<div key={col.label} style={col.style} className={col.hideMobile ? "max-md:hidden" : ""}>
<span
style={{
fontSize: 9,
fontWeight: 700,
letterSpacing: '1.2px',
color: 'var(--ink-4)',
textTransform: 'uppercase',
}}

View File

@ -5,22 +5,119 @@ import ReportsCarouselSection from './sections/ReportsCarouselSection'
import LatestNewsBentoSection from './sections/LatestNewsBentoSection'
import EventsSection from './sections/EventsSection'
import RiskSection from './sections/RiskSection'
import PlansSection from './sections/PlansSection'
import TeamSection from './sections/TeamSection'
import PlansSection from './sections/PlansSection'
import NewsletterSection from './sections/NewsletterSection'
/* Section chapter marker
A thin full-bleed dark bar that stamps the start of every
major section creates instant, unmistakable boundaries.
*/
function SectionLabel({ n, title }: { n: string; title: string }) {
return (
<div
style={{
background: 'var(--ink)',
borderTop: '1px solid rgba(255,255,255,0.08)',
position: 'relative',
}}
>
{/* Red accent bar on the right edge (RTL → visual start) */}
<div style={{
position: 'absolute',
top: 0,
bottom: 0,
right: 0,
width: 4,
background: 'var(--red)',
}} />
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 24,
}}
className="max-w-7xl mx-auto w-full px-12 py-10 max-md:px-5 max-md:py-7 max-md:gap-4"
>
<span style={{
fontSize: 36,
fontWeight: 900,
color: 'var(--red)',
letterSpacing: '-1.5px',
lineHeight: 1,
direction: 'ltr',
fontVariantNumeric: 'tabular-nums',
flexShrink: 0,
}}
className="max-md:text-[26px]"
>
{n}
</span>
<div style={{
width: 40,
height: 2,
background: 'rgba(255,255,255,0.25)',
flexShrink: 0,
}}
className="max-md:w-6"
/>
<span style={{
fontSize: 17,
fontWeight: 800,
letterSpacing: '-0.3px',
color: 'var(--paper)',
}}
className="max-md:text-[14px]"
>
{title}
</span>
</div>
</div>
)
}
/* ─── Home page ──────────────────────────────────────── */
export default function Home() {
return (
<div>
{/* ── 00 · Hero ─────────────────────────────────── */}
<HeroSection />
{/* ── Ticker ────────────────────────────────────── */}
<SnapshotBar />
{/* ── 01 · اسکنر جهانی · bg: paper ─────────────── */}
<SectionLabel n="01" title="اسکنر جهانی · رصد ۲۴ ساعته" />
<GlobalScannerSection />
{/* ── 02 · تحلیل‌ها · bg: paper-2 ──────────────── */}
<SectionLabel n="02" title="تحلیل‌های اخیر" />
<ReportsCarouselSection />
{/* ── 03 · اخبار · bg: paper ────────────────────── */}
<SectionLabel n="03" title="آخرین اخبار بازار" />
<LatestNewsBentoSection />
{/* ── 04 · رویدادها · bg: paper-2 ──────────────── */}
<SectionLabel n="04" title="رویدادهای پیش‌رو" />
<EventsSection />
{/* ── 05 · ریسک · bg: paper ─────────────────────── */}
<SectionLabel n="05" title="پایش ریسک · لحظه‌ای" />
<RiskSection />
{/* ── 06 · تیم · bg: paper-2 ────────────────────── */}
<SectionLabel n="06" title="تیم تحریریه" />
<TeamSection />
{/* ── 07 · اشتراک · bg: paper ───────────────────── */}
<SectionLabel n="07" title="اشتراک و دسترسی" />
<PlansSection />
{/* ── خبرنامه (last, no label) ───────────────────── */}
<NewsletterSection />
</div>
)

View File

@ -27,32 +27,28 @@ export default function EventsSection() {
const displayed = events.slice(0, 4)
return (
<section style={{ borderBottom: '2px solid var(--ink)' }}>
<div className="px-12 py-20 max-md:px-5 max-md:py-14">
{/* Banner */}
<div style={{ display: 'flex', alignItems: 'center', borderBottom: '1px solid var(--rule-thin)', marginBottom: 0 }}>
<div style={{ background: 'var(--ink)', color: 'var(--paper)', padding: '8px 20px', fontSize: '9px', fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase' }}>رویدادهای پیشرو</div>
<div style={{ flex: 1 }} />
<Link to="/events" style={{ fontSize: '10px', fontWeight: 700, letterSpacing: '1.5px', textTransform: 'uppercase', color: 'var(--ink-4)', textDecoration: 'none', padding: '8px 0' }}>تقویم کامل </Link>
<section style={{ borderBottom: '2px solid var(--ink)', background: 'var(--paper-2)' }}>
<div className="max-w-7xl mx-auto px-12 py-20 max-md:px-5 max-md:py-14">
{/* Sleek top row */}
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 24 }}>
<Link to="/events" style={{ fontSize: '10px', fontWeight: 700, textTransform: 'uppercase', color: 'var(--ink-4)', textDecoration: 'none' }}>تقویم کامل </Link>
</div>
{/* 4-col grid */}
<div
style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)' }}
className="max-md:grid-cols-2 max-sm:grid-cols-1"
className="grid grid-cols-4 max-md:grid-cols-2 max-sm:grid-cols-1"
>
{displayed.map((event, idx) => (
{displayed.map((event) => (
<div
key={event.id}
style={{
padding: '32px 28px',
borderRight: idx === 0 ? 'none' : '1px solid var(--rule-thin)',
}}
className="max-md:border-b max-md:border-[var(--rule-thin)] max-md:last:border-b-0"
className="border-r border-[var(--rule-thin)] first:border-r-0 max-md:border-r-0 max-md:border-b max-md:border-[var(--rule-thin)] max-md:nth-child(even):border-r max-md:nth-child(even):border-[var(--rule-thin)] max-sm:border-r-0 max-sm:border-b max-sm:last:border-b-0 max-md:nth-child(3):border-b-0 max-md:nth-child(4):border-b-0"
>
{/* Date block */}
<div style={{ borderBottom: '2px solid var(--ink)', paddingBottom: 12, marginBottom: 16 }}>
<div style={{ fontSize: 52, fontWeight: 900, letterSpacing: '-2px', lineHeight: 1, color: 'var(--ink)' }}>
<div style={{ fontSize: 52, fontWeight: 900, lineHeight: 1, color: 'var(--ink)' }}>
{event.date.toLocaleString('fa-IR')}
</div>
<div style={{ fontSize: 12, color: 'var(--ink-4)', marginTop: 2 }}>
@ -70,7 +66,6 @@ export default function EventsSection() {
fontSize: 9,
fontWeight: 700,
textTransform: 'uppercase',
letterSpacing: '1px',
padding: '3px 8px',
lineHeight: 1.4,
}}
@ -87,11 +82,11 @@ export default function EventsSection() {
{/* CTA */}
{event.registrationOpen ? (
<Link to="#" style={{ fontSize: 10, fontWeight: 700, letterSpacing: '2px', textTransform: 'uppercase', color: 'var(--red)', textDecoration: 'none', cursor: 'pointer' }}>
<Link to="#" style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', color: 'var(--red)', textDecoration: 'none', cursor: 'pointer' }}>
ثبتنام
</Link>
) : (
<span style={{ fontSize: 10, color: 'var(--ink-5)', letterSpacing: '1px', textTransform: 'uppercase', fontWeight: 700 }}>
<span style={{ fontSize: 10, color: 'var(--ink-5)', textTransform: 'uppercase', fontWeight: 700 }}>
بهزودی
</span>
)}

View File

@ -56,33 +56,11 @@ export default function GlobalScannerSection() {
overflow: 'hidden',
}}
>
<div className="max-w-7xl mx-auto w-full px-12 max-md:px-5 pb-12">
{/* ── Text header ────────────────────────────────── */}
<div
style={{ textAlign: 'center' }}
className="px-12 pt-[72px] pb-12 max-md:px-5 max-md:pt-10 max-md:pb-8"
style={{ textAlign: 'center', paddingTop: 72, paddingBottom: 48 }}
>
{/* Overline */}
<div
style={{
display: 'inline-flex',
alignItems: 'center',
gap: 10,
marginBottom: 20,
}}
>
<div style={{ width: 20, height: 2, background: 'var(--red)' }} />
<span style={{
fontSize: 9,
fontWeight: 700,
letterSpacing: '3px',
textTransform: 'uppercase',
color: 'var(--red)',
}}>
اسکنر جهانی · پوشش ۲۴ ساعته
</span>
<div style={{ width: 20, height: 2, background: 'var(--red)' }} />
</div>
{/* Headline */}
<h2 style={{
fontSize: 'clamp(28px, 3.5vw, 52px)',
@ -119,8 +97,8 @@ export default function GlobalScannerSection() {
</p>
</div>
{/* ── World map (full width, no side padding) ─── */}
<div className="px-6 max-md:px-0">
{/* ── World map (full width within container) ─── */}
<div className="max-md:px-0">
<WorldMap
dots={ROUTES}
lineColor="#9b1c1c"
@ -132,10 +110,9 @@ export default function GlobalScannerSection() {
<div
style={{
borderTop: '1px solid var(--rule-thin)',
display: 'grid',
gridTemplateColumns: 'repeat(4, 1fr)',
marginTop: 48,
}}
className="max-md:grid-cols-2"
className="grid grid-cols-4 max-md:grid-cols-2 max-sm:grid-cols-1"
>
{[
{ n: '۱۸۰+', label: 'بازار رصد‌شده', sub: 'در ۵ قاره' },
@ -151,16 +128,14 @@ export default function GlobalScannerSection() {
transition={{ duration: 0.4, delay: i * 0.08 }}
style={{
padding: '28px 24px',
borderRight: i > 0 ? '1px solid var(--rule-thin)' : 'none',
textAlign: 'center',
}}
className="max-md:border-r-0 max-md:border-b max-md:border-[var(--rule-thin)]"
className="border-r border-[var(--rule-thin)] first:border-r-0 max-md:border-r-0 max-md:border-b max-md:border-[var(--rule-thin)] max-md:nth-child(even):border-r max-md:nth-child(even):border-[var(--rule-thin)] max-sm:border-r-0 max-sm:border-b max-sm:last:border-b-0 max-md:nth-child(3):border-b-0 max-md:nth-child(4):border-b-0"
>
<div style={{
fontSize: 32,
fontWeight: 900,
color: 'var(--ink)',
letterSpacing: '-1px',
lineHeight: 1,
marginBottom: 4,
}}>
@ -175,6 +150,7 @@ export default function GlobalScannerSection() {
</motion.div>
))}
</div>
</div>
</section>
)
}

View File

@ -82,15 +82,16 @@ export default function HeroSection() {
</div>
{/* ── Main content — anchored bottom ── */}
<div style={{
<div
style={{
position: 'absolute',
inset: 0,
display: 'grid',
gridTemplateColumns: '1fr 240px',
display: 'flex',
alignItems: 'flex-end',
gap: 48,
}}
className="px-14 pb-[52px] max-md:grid-cols-1 max-md:px-5 max-md:pb-8"
>
<div
className="max-w-7xl mx-auto w-full px-14 pb-[52px] grid grid-cols-1 md:grid-cols-[1fr_240px] items-end gap-12 max-md:px-5 max-md:pb-8 max-md:gap-6"
>
{/* ── Left: headline + meta ── */}
@ -106,8 +107,6 @@ export default function HeroSection() {
<span style={{
fontSize: 10,
fontWeight: 700,
letterSpacing: '2.5px',
textTransform: 'uppercase',
color: 'rgba(255,255,255,0.5)',
}}>
گزارش راهبردی · تحلیل سناریو
@ -120,10 +119,9 @@ export default function HeroSection() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1 }}
style={{
fontSize: 'clamp(36px, 5vw, 72px)',
fontSize: 'clamp(32px, 5vw, 64px)',
fontWeight: 900,
lineHeight: 1.04,
letterSpacing: '-2.5px',
lineHeight: 1.2,
color: 'white',
marginBottom: 24,
maxWidth: 700,
@ -195,14 +193,13 @@ export default function HeroSection() {
</div>
{/* Separator */}
<div style={{ width: 1, height: 32, background: 'rgba(255,255,255,0.15)' }} />
<div className="max-sm:hidden" style={{ width: 1, height: 32, background: 'rgba(255,255,255,0.15)' }} />
{/* Price */}
<div style={{ display: 'flex', alignItems: 'baseline', gap: 5 }}>
<span style={{
fontSize: 28,
fontWeight: 900,
letterSpacing: '-1px',
color: 'white',
lineHeight: 1,
}}>
@ -212,7 +209,7 @@ export default function HeroSection() {
</div>
{/* Buttons */}
<div style={{ display: 'flex', gap: 10 }}>
<div className="flex gap-2.5 max-sm:flex-col max-sm:w-full">
<button
onMouseEnter={() => setBuyHov(true)}
onMouseLeave={() => setBuyHov(false)}
@ -225,10 +222,10 @@ export default function HeroSection() {
fontWeight: 700,
fontFamily: 'Vazir, sans-serif',
cursor: 'pointer',
letterSpacing: '0.5px',
transition: 'background 160ms',
whiteSpace: 'nowrap',
}}
className="max-sm:w-full"
>
خرید گزارش
</button>
@ -244,10 +241,10 @@ export default function HeroSection() {
fontWeight: 400,
fontFamily: 'Vazir, sans-serif',
cursor: 'pointer',
letterSpacing: '0.5px',
transition: 'background 160ms',
whiteSpace: 'nowrap',
}}
className="max-sm:w-full"
>
دانلود خلاصه رایگان
</button>
@ -310,6 +307,7 @@ export default function HeroSection() {
</motion.div>
</div>
</div>
</section>
)
}

View File

@ -51,27 +51,12 @@ function Accent({ color = 'var(--red)' }: { color?: string }) {
/* ─── Section ────────────────────────────────────────────── */
export default function LatestNewsBentoSection() {
return (
<section style={{ background: 'var(--paper-2)', borderBottom: '2px solid var(--ink)' }}>
<section style={{ background: 'var(--paper)', borderBottom: '2px solid var(--ink)' }}>
{/* ── Header ── */}
<div className="px-12 pt-16 max-md:px-5 max-md:pt-10">
<div style={{
display: 'flex', alignItems: 'center', gap: 16,
marginBottom: 32, paddingBottom: 20, borderBottom: '2px solid var(--ink)',
}}>
<div style={{
background: 'var(--ink)', color: 'var(--paper)',
fontSize: 9, fontWeight: 700, letterSpacing: '3px',
textTransform: 'uppercase', padding: '6px 16px',
}}>
آخرین اخبار
</div>
<h2 style={{ fontSize: 'clamp(16px,2vw,22px)', fontWeight: 900, letterSpacing: '-0.5px' }}>
رصد روزانه بازار و صنعت
</h2>
<div style={{ flex: 1 }} />
<span style={{ fontSize: 10, fontWeight: 500, color: 'var(--ink-5)' }}>بهمن ۱۴۰۳</span>
</div>
<div className="max-w-7xl mx-auto px-12 pt-8 pb-4 max-md:px-5 max-md:pt-6 flex justify-between items-center text-xs text-[var(--ink-4)]">
<span>رصد روزانه بازار و صنعت</span>
<span>بهمن ۱۴۰۳</span>
</div>
{/* ── Bento grid ── */}
@ -80,7 +65,7 @@ export default function LatestNewsBentoSection() {
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="px-12 pb-14 max-md:px-5 max-md:pb-10"
className="max-w-7xl mx-auto w-full px-12 pb-14 max-md:px-5 max-md:pb-10"
>
{/*
3-col grid on desktop, 1-col on mobile.

View File

@ -13,11 +13,11 @@ export default function NewsletterSection() {
}
return (
<section style={{ borderBottom: '3px solid var(--ink)' }}>
<section style={{ borderBottom: '3px solid var(--ink)', background: 'var(--paper-2)' }}>
{/* Outer grid — strip inline padding, use Tailwind */}
<div
style={{ display: 'grid', gridTemplateColumns: '1fr 1px 1fr', minHeight: 180 }}
className="px-12 max-md:px-5 max-md:grid-cols-1"
className="max-w-7xl mx-auto w-full px-12 max-md:px-5 max-md:grid-cols-1"
>
{/* Left: description */}
<div

View File

@ -1,28 +1,39 @@
import { useState } from 'react'
import { plans } from '@/data/plans'
/* ─── Checkmark icon ─────────────────────────────────── */
/* ─── Star icon (for "popular" badge) ──────────────── */
function Star() {
return (
<svg width="11" height="11" viewBox="0 0 16 16" fill="currentColor" style={{ flexShrink: 0 }}>
<path d="M8 1l2.09 4.23L14.78 6l-3.39 3.31.8 4.69L8 11.77 3.81 14l.8-4.69L1.22 6l4.69-.77L8 1z" />
</svg>
)
}
/* ─── Check icon ──────────────────────────────────── */
function Check({ featured }: { featured: boolean }) {
return (
<svg
width="16" height="16" viewBox="0 0 16 16" fill="none"
style={{ flexShrink: 0, marginTop: 1 }}
width="14" height="14" viewBox="0 0 16 16" fill="none"
style={{ flexShrink: 0, marginTop: 3 }}
>
<circle
cx="8" cy="8" r="7.5"
stroke={featured ? 'rgba(255,255,255,0.35)' : 'var(--rule-thin)'}
fill={featured ? 'rgba(255,255,255,0.1)' : 'transparent'}
/>
<path
d="M5 8l2 2 4-4"
stroke={featured ? '#f4efe7' : 'var(--green-ink)'}
strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"
d="M3 8.5l3 3 7-7"
stroke={featured ? 'rgba(244,239,231,0.85)' : 'var(--ink)'}
strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"
/>
</svg>
)
}
/* ─── Individual plan card ───────────────────────────── */
/* ─── Plan tagline lookup ─────────────────────────── */
const TAGLINE: Record<string, string> = {
individual: 'برای کارشناسان و محققان',
organizational: 'برای شرکت‌ها و سازمان‌ها',
ministry: 'برای نهادهای دولتی',
}
/* ─── Individual plan card ────────────────────────── */
function PlanCard({ plan, isMiddle }: { plan: (typeof plans)[0]; isMiddle: boolean }) {
const [hovered, setHovered] = useState(false)
const featured = plan.featured
@ -30,99 +41,126 @@ function PlanCard({ plan, isMiddle }: { plan: (typeof plans)[0]; isMiddle: boole
return (
<div
style={{
background: featured ? 'var(--ink)' : 'white',
border: featured ? 'none' : '1px solid var(--rule-thin)',
borderTop: featured ? 'none' : '1px solid var(--rule-thin)',
padding: '32px 28px',
position: 'relative',
background: featured ? 'var(--ink)' : 'var(--paper)',
border: '1px solid var(--rule-thin)',
display: 'flex',
flexDirection: 'column',
position: 'relative',
boxShadow: isMiddle ? '0 8px 40px rgba(26,23,18,0.12)' : 'none',
boxShadow: isMiddle ? '0 12px 40px rgba(26,23,18,0.14)' : 'none',
zIndex: isMiddle ? 1 : 0,
transform: isMiddle ? 'translateY(-4px)' : 'none',
}}
>
{/* Badge */}
{/* "Popular" badge — top right */}
{plan.badge && (
<div style={{
position: 'absolute',
top: -12,
right: '50%',
transform: 'translateX(50%)',
top: 12,
right: 12,
background: 'var(--red)',
color: 'white',
fontSize: 9,
padding: '5px 10px',
fontSize: 10,
fontWeight: 700,
letterSpacing: '2px',
textTransform: 'uppercase',
padding: '4px 14px',
whiteSpace: 'nowrap',
letterSpacing: '1.5px',
display: 'flex',
alignItems: 'center',
gap: 5,
zIndex: 10,
}}>
<Star />
{plan.badge}
</div>
)}
{/* Plan name */}
{/* Header */}
<div style={{
fontSize: 11,
fontWeight: 700,
letterSpacing: '1.5px',
textTransform: 'uppercase',
color: featured ? 'rgba(244,239,231,0.5)' : 'var(--ink-5)',
marginBottom: 8,
background: featured ? 'rgba(255,255,255,0.04)' : 'var(--paper-2)',
borderBottom: featured ? '1px solid rgba(255,255,255,0.08)' : '1px solid var(--rule-thin)',
padding: '20px 22px',
}}>
<div style={{
fontSize: 15,
fontWeight: 800,
color: featured ? 'var(--paper)' : 'var(--ink)',
marginBottom: 4,
}}>
{plan.name}
</div>
{/* Tagline */}
<div style={{
fontSize: 13,
<p style={{
fontSize: 12,
color: featured ? 'rgba(244,239,231,0.55)' : 'var(--ink-4)',
marginBottom: 24,
fontWeight: 300,
marginBottom: 14,
fontWeight: 400,
lineHeight: 1.5,
}}>
{plan.id === 'individual' && 'برای کارشناسان و محققان'}
{plan.id === 'organizational' && 'برای شرکت‌ها و سازمان‌ها'}
{plan.id === 'ministry' && 'برای نهادهای دولتی'}
</div>
{/* Price */}
<div style={{ marginBottom: 6 }}>
{TAGLINE[plan.id]}
</p>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
<span style={{
fontSize: 42,
fontSize: 26,
fontWeight: 900,
letterSpacing: '-2px',
letterSpacing: '-0.8px',
lineHeight: 1,
color: featured ? 'var(--paper)' : 'var(--ink)',
fontVariantNumeric: 'tabular-nums',
}}>
{plan.price === 0 ? 'تماس' : plan.price.toLocaleString('fa-IR')}
</span>
{plan.price > 0 && (
{plan.price > 0 ? (
<span style={{
fontSize: 13,
fontWeight: 400,
color: featured ? 'rgba(244,239,231,0.5)' : 'var(--ink-4)',
marginRight: 6,
fontSize: 11,
color: featured ? 'rgba(244,239,231,0.55)' : 'var(--ink-4)',
fontWeight: 500,
}}>
تومان
تومان / {plan.period}
</span>
) : (
<span style={{
fontSize: 11,
color: featured ? 'rgba(244,239,231,0.55)' : 'var(--ink-4)',
fontWeight: 500,
}}>
· {plan.period}
</span>
)}
</div>
<div style={{
fontSize: 11,
color: featured ? 'rgba(244,239,231,0.4)' : 'var(--ink-5)',
marginBottom: 28,
}}>
{plan.period}
</div>
{/* CTA button */}
{/* Features */}
<div style={{
padding: '18px 22px',
display: 'flex',
flexDirection: 'column',
gap: 10,
flex: 1,
}}>
{plan.features.map(f => (
<div key={f} style={{ display: 'flex', alignItems: 'flex-start', gap: 9 }}>
<Check featured={featured} />
<span style={{
fontSize: 12,
lineHeight: 1.55,
color: featured ? 'rgba(244,239,231,0.8)' : 'var(--ink-3)',
fontWeight: 400,
}}>
{f}
</span>
</div>
))}
</div>
{/* CTA */}
<div style={{
borderTop: featured ? '1px solid rgba(255,255,255,0.08)' : '1px solid var(--rule-thin)',
padding: '12px 22px 14px',
}}>
<button
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
style={{
width: '100%',
padding: '12px',
padding: '10px',
border: featured
? 'none'
: hovered ? 'none' : '1px solid var(--ink)',
@ -137,41 +175,18 @@ function PlanCard({ plan, isMiddle }: { plan: (typeof plans)[0]; isMiddle: boole
fontFamily: 'Vazir, sans-serif',
cursor: 'pointer',
borderRadius: 0,
marginBottom: 28,
letterSpacing: '0.5px',
transition: 'background 180ms, color 180ms, border 180ms',
}}
>
{plan.ctaLabel}
</button>
{/* Divider */}
<div style={{
borderTop: featured ? '1px solid rgba(244,239,231,0.12)' : '1px solid var(--rule-thin)',
marginBottom: 24,
}} />
{/* Features */}
<ul style={{ listStyle: 'none', display: 'flex', flexDirection: 'column', gap: 12 }}>
{plan.features.map(f => (
<li key={f} style={{ display: 'flex', alignItems: 'flex-start', gap: 10, textAlign: 'right' }}>
<Check featured={featured} />
<span style={{
fontSize: 13,
lineHeight: 1.5,
color: featured ? 'rgba(244,239,231,0.75)' : 'var(--ink-3)',
fontWeight: 300,
}}>
{f}
</span>
</li>
))}
</ul>
</div>
</div>
)
}
/* ─── Enterprise bottom card ─────────────────────────── */
/* ─── Enterprise bottom card ─────────────────────── */
const enterpriseFeatures = [
'پروژه‌های نامحدود',
'پشتیبانی تیمی اختصاصی',
@ -190,34 +205,35 @@ const enterpriseFeatures = [
function EnterpriseCard() {
const [hov, setHov] = useState(false)
return (
<div style={{
<div
style={{
border: '1px solid var(--rule-thin)',
background: 'white',
padding: '36px 40px',
background: 'var(--paper)',
padding: '28px 32px',
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: '40px',
gap: '32px',
alignItems: 'start',
}}
className="max-md:grid-cols-1 max-md:gap-6"
className="max-md:grid-cols-1 max-md:gap-6 max-md:p-6"
>
{/* Left: description */}
<div>
<div style={{
fontSize: 11, fontWeight: 700, letterSpacing: '2px',
textTransform: 'uppercase', color: 'var(--ink-5)', marginBottom: 10,
fontSize: 10, fontWeight: 700, letterSpacing: '1.5px',
textTransform: 'uppercase', color: 'var(--ink-5)', marginBottom: 8,
}}>
وزارتخانه / سازمان دولتی
</div>
<h3 style={{
fontSize: 22, fontWeight: 900, letterSpacing: '-0.5px',
color: 'var(--ink)', marginBottom: 12,
fontSize: 18, fontWeight: 900, letterSpacing: '-0.4px',
color: 'var(--ink)', marginBottom: 10,
}}>
راهکار سازمانی کامل
</h3>
<p style={{
fontSize: 13, lineHeight: 1.8, color: 'var(--ink-3)',
fontWeight: 300, marginBottom: 24,
fontSize: 12, lineHeight: 1.75, color: 'var(--ink-3)',
fontWeight: 400, marginBottom: 20,
}}>
به راهکارهای سفارشی، پشتیبانی اختصاصی یا قیمتگذاری حجمی نیاز دارید؟
با کارشناسان ما درباره پلن منحصربهفرد سازمانتان صحبت کنید.
@ -226,7 +242,7 @@ function EnterpriseCard() {
onMouseEnter={() => setHov(true)}
onMouseLeave={() => setHov(false)}
style={{
padding: '13px 28px',
padding: '11px 24px',
border: '1px solid var(--ink)',
background: hov ? 'var(--ink)' : 'transparent',
color: hov ? 'var(--paper)' : 'var(--ink)',
@ -246,12 +262,12 @@ function EnterpriseCard() {
<div style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: '10px 20px',
gap: '8px 18px',
}}>
{enterpriseFeatures.map(f => (
<div key={f} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div key={f} style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
<Check featured={false} />
<span style={{ fontSize: 12, color: 'var(--ink-3)', lineHeight: 1.4, fontWeight: 300 }}>
<span style={{ fontSize: 11, color: 'var(--ink-3)', lineHeight: 1.4, fontWeight: 400 }}>
{f}
</span>
</div>
@ -261,39 +277,37 @@ function EnterpriseCard() {
)
}
/* ─── Section ────────────────────────────────────────── */
/* ─── Section ────────────────────────────────────── */
export default function PlansSection() {
const allThree = plans
return (
<section style={{
background: 'var(--paper-2)',
background: 'var(--paper)',
borderBottom: '2px solid var(--ink)',
}}>
<div className="px-12 py-20 max-md:px-5 max-md:py-14">
<div className="max-w-6xl mx-auto px-12 py-16 max-md:px-5 max-md:py-12">
{/* Header */}
<div style={{ textAlign: 'center', marginBottom: 56 }}>
<div style={{ textAlign: 'center', marginBottom: 40 }}>
<div style={{
fontSize: 9, fontWeight: 700, letterSpacing: '3px',
fontSize: 10, fontWeight: 700, letterSpacing: '2.5px',
textTransform: 'uppercase', color: 'var(--red)',
marginBottom: 14,
marginBottom: 12,
}}>
اشتراک و دسترسی
</div>
<h2 style={{
fontSize: 'clamp(26px, 3vw, 40px)',
fontSize: 'clamp(22px, 2.4vw, 32px)',
fontWeight: 900,
letterSpacing: '-1.5px',
letterSpacing: '-1px',
color: 'var(--ink)',
marginBottom: 12,
marginBottom: 10,
}}>
پلن مناسب خود را انتخاب کنید
</h2>
<p style={{
fontSize: 14, lineHeight: 1.8,
color: 'var(--ink-4)', fontWeight: 300,
maxWidth: 480, margin: '0 auto',
fontSize: 13, lineHeight: 1.7,
color: 'var(--ink-4)', fontWeight: 400,
maxWidth: 460, margin: '0 auto',
}}>
از دسترسی پایه برای پژوهشگران تا راهکار کامل برای وزارتخانهها
هر سطحی از نیاز را پوشش میدهیم.
@ -301,16 +315,17 @@ export default function PlansSection() {
</div>
{/* 3-col plan cards */}
<div style={{
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: 0,
marginBottom: 32,
position: 'relative',
gap: 16,
marginBottom: 20,
alignItems: 'stretch',
}}
className="max-md:grid-cols-1 max-md:gap-0"
className="max-md:grid-cols-1 max-md:gap-4"
>
{allThree.map((plan, idx) => (
{plans.map((plan, idx) => (
<PlanCard
key={plan.id}
plan={plan}
@ -325,10 +340,10 @@ export default function PlansSection() {
{/* Money-back note */}
<div style={{
textAlign: 'center',
marginTop: 20,
marginTop: 18,
fontSize: 11,
color: 'var(--ink-5)',
fontWeight: 300,
fontWeight: 400,
}}>
&nbsp; ضمانت بازگشت وجه ۱۴ روزه &nbsp;·&nbsp; بدون قرارداد اجباری
</div>

View File

@ -236,33 +236,9 @@ export default function ReportsCarouselSection() {
))
return (
<section style={{ borderBottom: '2px solid var(--ink)' }}>
{/* Header */}
<div className="px-12 max-md:px-5">
<div style={{
display: 'flex', alignItems: 'center',
borderBottom: '1px solid var(--rule-thin)', paddingTop: 52,
}}>
<div style={{
background: 'var(--ink)', color: 'var(--paper)',
padding: '8px 20px', fontSize: 9, fontWeight: 700,
letterSpacing: '3px', textTransform: 'uppercase',
}}>
تحلیلهای اخیر
</div>
<div style={{ flex: 1 }} />
<Link to="/reports" style={{
fontSize: 10, fontWeight: 700, letterSpacing: '1.5px',
textTransform: 'uppercase', color: 'var(--ink-4)',
textDecoration: 'none', padding: '8px 0',
}}>
همه گزارشها
</Link>
</div>
</div>
{/* Legend */}
<div className="px-12 pt-[14px] max-md:px-5">
<section style={{ borderBottom: '2px solid var(--ink)', background: 'var(--paper-2)', paddingBottom: 40 }}>
{/* Legend & Link Row */}
<div className="max-w-7xl mx-auto px-12 pt-10 max-md:px-5 flex items-center justify-between flex-wrap gap-4">
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap' }}>
{Object.entries(typeLabel).map(([key, label]) => (
<div key={key} style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
@ -271,9 +247,18 @@ export default function ReportsCarouselSection() {
</div>
))}
</div>
<Link to="/reports" style={{
fontSize: 10, fontWeight: 700, letterSpacing: '1.5px',
textTransform: 'uppercase', color: 'var(--ink-4)',
textDecoration: 'none',
}}>
همه گزارشها
</Link>
</div>
<div className="max-w-7xl mx-auto w-full px-12 max-md:px-5 mt-6">
<Carousel items={cardElements} />
</div>
</section>
)
}

View File

@ -90,51 +90,10 @@ export default function RiskSection() {
}, [])
return (
<section style={{ borderBottom: '2px solid var(--ink)', background: 'var(--paper-2)' }}>
<div className="px-12 pt-14 max-md:px-5 max-md:pt-10">
{/* ── Header bar ── */}
<div style={{
display: 'flex',
alignItems: 'center',
borderBottom: '1px solid var(--rule-thin)',
}}>
<div style={{
background: 'var(--red)',
color: 'white',
padding: '8px 20px',
fontSize: 9,
fontWeight: 700,
letterSpacing: '3px',
textTransform: 'uppercase',
}}>
پایش ریسک · لحظهای
</div>
<div style={{
marginRight: 16,
fontSize: 10,
color: 'var(--ink-5)',
fontWeight: 600,
letterSpacing: '0.5px',
}}>
{riskItems.length} سیگنال فعال
</div>
<div style={{ flex: 1 }} />
<Link to="/risks" style={{
fontSize: 10,
fontWeight: 700,
letterSpacing: '1.5px',
textTransform: 'uppercase',
color: 'var(--ink-4)',
textDecoration: 'none',
padding: '8px 0',
}}>
همه ریسکها
</Link>
</div>
{/* ── Legend ── */}
<div style={{ display: 'flex', gap: 20, flexWrap: 'wrap', padding: '14px 0 0' }}>
<section style={{ borderBottom: '2px solid var(--ink)', background: 'var(--paper)' }}>
{/* ── Legend, active count, and link row ── */}
<div className="max-w-7xl mx-auto px-12 pt-8 pb-2 max-md:px-5 max-md:pt-6 flex justify-between items-center flex-wrap gap-4 text-xs">
<div style={{ display: 'flex', gap: 20, flexWrap: 'wrap' }}>
{[
{ color: '#7f1d1d', label: 'بحرانی' },
{ color: '#9b1c1c', label: 'بالا' },
@ -148,6 +107,19 @@ export default function RiskSection() {
</div>
))}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<span style={{ color: 'var(--ink-5)', fontWeight: 600 }}>{riskItems.length} سیگنال فعال</span>
<Link to="/risks" style={{
fontSize: 10,
fontWeight: 700,
letterSpacing: '1.5px',
textTransform: 'uppercase',
color: 'var(--ink-4)',
textDecoration: 'none',
}}>
همه ریسکها
</Link>
</div>
</div>
{/* ── Auto-scroll ticker ── */}

View File

@ -13,11 +13,10 @@ export default function SnapshotBar() {
>
<div
style={{
padding: '0 48px',
display: 'grid',
gridTemplateColumns: 'repeat(4, 1fr)',
}}
className="max-md:grid-cols-2 max-md:px-6"
className="max-w-7xl mx-auto w-full px-12 max-md:px-6 max-md:grid-cols-2"
>
{prices.map((price, idx) => {
const isLast = idx === prices.length - 1

View File

@ -1,48 +1,215 @@
import { useState } from 'react'
import { Link } from 'react-router-dom'
import { team } from '@/data/team'
function MemberCard({ member, idx }: { member: (typeof team)[0]; idx: number }) {
const borderRight = idx % 2 === 0 ? '1px solid var(--rule-thin)' : 'none'
const borderTop = idx >= 2 ? '1px solid var(--rule-thin)' : 'none'
function MemberCard({ member }: { member: (typeof team)[0] }) {
const [hovered, setHovered] = useState(false)
return (
<div
style={{ padding: '40px', borderRight, borderTop, cursor: 'pointer', transition: 'background 200ms' }}
onMouseEnter={e => (e.currentTarget.style.background = 'var(--paper-2)')}
onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}
className="max-md:border-r-0 max-md:border-t max-md:border-[var(--rule-thin)] max-md:first:border-t-0 max-md:p-6"
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
style={{
background: hovered ? 'var(--paper-2)' : 'var(--paper)',
display: 'flex',
flexDirection: 'column',
transition: 'background 150ms ease',
position: 'relative',
}}
className="p-6 max-md:p-5 cursor-pointer border border-[var(--rule-thin)]"
>
{/* Top row: avatar + name/role + reports stat */}
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
{/* Avatar */}
<div
style={{
width: 44,
height: 44,
background: 'var(--ink)',
color: 'var(--paper)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 16,
fontWeight: 900,
borderRadius: '50%',
flexShrink: 0,
}}
>
<div style={{ width: 56, height: 56, background: 'var(--paper-3)', border: '1px solid var(--rule-thin)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 22, fontWeight: 900, color: 'var(--ink)', marginBottom: 16 }}>
{member.initial}
</div>
<div style={{ fontSize: 15, fontWeight: 800, color: 'var(--ink)', letterSpacing: '-0.3px', marginBottom: 3 }}>{member.name}</div>
<div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '1.5px', textTransform: 'uppercase', color: 'var(--red)', marginBottom: 12 }}>{member.role}</div>
<p style={{ fontSize: 13, color: 'var(--ink-3)', lineHeight: 1.75, fontWeight: 400, marginBottom: 14 }}>{member.bio}</p>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 5, marginBottom: 14 }}>
{member.expertise.slice(0, 4).map(tag => (
<span key={tag} style={{ fontSize: 9, padding: '2px 7px', background: 'var(--paper-3)', color: 'var(--ink-4)', fontWeight: 600, letterSpacing: '0.5px' }}>{tag}</span>
{/* Name + role */}
<div style={{ flex: 1, minWidth: 0 }}>
<h3
style={{
fontSize: 14,
fontWeight: 800,
color: 'var(--ink)',
lineHeight: 1.3,
marginBottom: 2,
letterSpacing: '-0.2px',
}}
>
{member.name}
</h3>
<span
style={{
fontSize: 10,
fontWeight: 700,
color: 'var(--red)',
letterSpacing: '1px',
textTransform: 'uppercase',
}}
>
{member.role}
</span>
</div>
{/* Report count — top left (RTL → visual end) */}
<div style={{ textAlign: 'left', flexShrink: 0 }}>
<div
style={{
fontSize: 18,
fontWeight: 900,
color: 'var(--ink)',
lineHeight: 1,
fontVariantNumeric: 'tabular-nums',
}}
>
{member.reportCount.toLocaleString('fa-IR')}
</div>
<div
style={{
fontSize: 9,
color: 'var(--ink-5)',
marginTop: 3,
fontWeight: 500,
}}
>
گزارش
</div>
</div>
</div>
{/* Bio */}
<p
style={{
fontSize: 12,
color: 'var(--ink-3)',
lineHeight: 1.7,
fontWeight: 400,
marginTop: 14,
marginBottom: 14,
display: '-webkit-box',
WebkitLineClamp: 3,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
}}
>
{member.bio}
</p>
{/* Tags — max 3 */}
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 5, marginBottom: 12 }}>
{member.expertise.slice(0, 3).map(tag => (
<span
key={tag}
style={{
fontSize: 10,
padding: '2px 8px',
color: 'var(--ink-4)',
border: '1px solid var(--rule-thin)',
fontWeight: 500,
whiteSpace: 'nowrap',
}}
>
{tag}
</span>
))}
</div>
<div style={{ paddingTop: 12, borderTop: '1px solid var(--rule-thin)', fontSize: 11, color: 'var(--ink-5)' }}>
<strong style={{ fontSize: 20, fontWeight: 900, color: 'var(--ink)', marginLeft: 4 }}>{member.reportCount}</strong>
گزارش منتشرشده
{/* Footer: email */}
{member.email && (
<div
style={{
paddingTop: 10,
borderTop: '1px solid var(--rule-thin)',
marginTop: 'auto',
}}
>
<a
href={`mailto:${member.email}`}
style={{
fontSize: 10,
color: 'var(--ink-5)',
textDecoration: 'none',
fontWeight: 500,
direction: 'ltr',
fontFamily: 'ui-monospace, monospace',
}}
>
{member.email}
</a>
</div>
)}
</div>
)
}
export default function TeamSection() {
const displayedMembers = team.slice(0, 4)
return (
<section style={{ borderBottom: '2px solid var(--ink)' }}>
<div className="px-12 py-20 max-md:px-5 max-md:py-14">
<div style={{ display: 'flex', alignItems: 'center', borderBottom: '1px solid var(--rule-thin)', marginBottom: '40px' }}>
<div style={{ background: 'var(--ink)', color: 'var(--paper)', padding: '8px 20px', fontSize: '9px', fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase' }}>تیم تحریریه</div>
<div style={{ flex: 1 }} />
<Link to="/team" style={{ fontSize: '10px', fontWeight: 700, letterSpacing: '1.5px', textTransform: 'uppercase', color: 'var(--ink-4)', textDecoration: 'none', padding: '8px 0' }}>همه کارشناسان </Link>
<section style={{ borderBottom: '2px solid var(--ink)', background: 'var(--paper-2)' }}>
<div className="max-w-6xl mx-auto px-12 py-16 max-md:px-5 max-md:py-12">
{/* Editorial sub-bar */}
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
borderBottom: '1px solid var(--rule-thin)',
paddingBottom: 14,
marginBottom: 24,
}}
className="flex-wrap gap-3"
>
<span
style={{
fontSize: 12,
fontWeight: 400,
color: 'var(--ink-4)',
fontStyle: 'italic',
}}
>
اعضای هیئت تحریریه و متخصصان راهبردی
</span>
<Link
to="/team"
style={{
fontSize: 11,
fontWeight: 700,
letterSpacing: '1px',
color: 'var(--red)',
textDecoration: 'none',
}}
>
همه کارشناسان
</Link>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }} className="max-md:grid-cols-1">
{team.slice(0, 4).map((member, idx) => (
<MemberCard key={member.id} member={member} idx={idx} />
{/* 2x2 grid on desktop, single column on mobile */}
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
gap: 12,
}}
className="max-md:grid-cols-1"
>
{displayedMembers.map(member => (
<MemberCard key={member.id} member={member} />
))}
</div>
</div>

View File

@ -161,21 +161,15 @@ function ComparisonTool() {
{/* Side-by-side comparison */}
<div
style={{
display: 'grid',
gridTemplateColumns: '1fr auto 1fr',
gap: 0,
border: '1px solid var(--rule-thin)',
}}
className="grid grid-cols-[1fr_auto_1fr] max-md:grid-cols-1 border border-[var(--rule-thin)]"
>
<div style={{ padding: 32, textAlign: 'center' }}>
<div className="max-md:border-b max-md:border-[var(--rule-thin)]" style={{ padding: 32, textAlign: 'center' }}>
<p style={{ fontSize: 13, color: 'var(--ink-3)', marginBottom: 8 }}>{itemA.name}</p>
<p
style={{
fontSize: 32,
fontWeight: 900,
color: 'var(--ink)',
letterSpacing: '-1px',
lineHeight: 1,
}}
>
@ -214,13 +208,9 @@ function ComparisonTool() {
{/* VS divider */}
<div
style={{
borderRight: '1px solid var(--rule-thin)',
borderLeft: '1px solid var(--rule-thin)',
width: 60,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
className="max-md:hidden border-r border-l border-[var(--rule-thin)] flex items-center justify-center"
>
<span
style={{
@ -242,7 +232,6 @@ function ComparisonTool() {
fontSize: 32,
fontWeight: 900,
color: 'var(--ink)',
letterSpacing: '-1px',
lineHeight: 1,
}}
>
@ -526,13 +515,7 @@ export default function Market() {
subtitle="قیمت بازار داخلی — تومان به کیلوگرم / دلار به تن"
/>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(4, 1fr)',
borderTop: '1px solid var(--rule-thin)',
borderRight: '1px solid var(--rule-thin)',
}}
className="price-grid"
className="grid grid-cols-4 max-lg:grid-cols-2 max-sm:grid-cols-1 border-t border-r border-[var(--rule-thin)] price-grid"
>
{marketPrices.map((item) => (
<PriceCard key={item.id} item={item} />

View File

@ -126,7 +126,8 @@ export default function Risks() {
ماتریس ریسکهای کلیدی
</h2>
<div style={{ border: '1px solid var(--ink)', overflow: 'hidden' }}>
<div className="overflow-x-auto border border-[var(--ink)]">
<div style={{ minWidth: 768 }}>
{/* Table Header */}
<div
style={{
@ -217,6 +218,7 @@ export default function Risks() {
})}
</div>
</div>
</div>
{/* Risk Reports Section */}
<div>
@ -244,12 +246,8 @@ export default function Risks() {
<Link
key={report.id}
to={`/reports/${report.id}`}
className="grid grid-cols-[90px_1fr_120px_140px_140px] max-md:grid-cols-1 max-md:p-5 max-md:gap-3 p-4 items-center"
style={{
display: 'grid',
gridTemplateColumns: '90px 1fr 120px 140px 140px',
gap: '0',
alignItems: 'center',
padding: '16px 0',
borderBottom: '1px solid var(--rule-thin)',
background: idx % 2 === 0 ? 'var(--paper)' : 'var(--paper-2)',
textDecoration: 'none',
@ -257,7 +255,7 @@ export default function Risks() {
}}
>
{/* Type badge */}
<div style={{ padding: '0 0 0 12px' }}>
<div style={{ padding: '0 0 0 12px' }} className="max-md:p-0">
<span
style={{
fontSize: '10px',
@ -265,7 +263,6 @@ export default function Risks() {
color: 'var(--paper)',
background: '#7f1d1d',
padding: '3px 8px',
letterSpacing: '0.06em',
}}
>
ریسک
@ -277,9 +274,8 @@ export default function Risks() {
fontSize: '14px',
fontWeight: 600,
color: 'var(--ink)',
borderRight: '1px solid var(--rule-thin)',
padding: '0 16px',
}}
className="border-r border-[var(--rule-thin)] px-4 max-md:border-r-0 max-md:p-0"
>
{report.title}
</div>
@ -288,9 +284,8 @@ export default function Risks() {
style={{
fontSize: '12px',
color: 'var(--ink-5)',
borderRight: '1px solid var(--rule-thin)',
padding: '0 16px',
}}
className="border-r border-[var(--rule-thin)] px-4 max-md:border-r-0 max-md:p-0"
>
{report.publishDate}
</div>
@ -299,9 +294,8 @@ export default function Risks() {
style={{
fontSize: '12px',
color: 'var(--ink-5)',
borderRight: '1px solid var(--rule-thin)',
padding: '0 16px',
}}
className="border-r border-[var(--rule-thin)] px-4 max-md:border-r-0 max-md:p-0"
>
{report.pages} صفحه
</div>
@ -312,9 +306,8 @@ export default function Risks() {
alignItems: 'center',
justifyContent: 'flex-end',
gap: '12px',
borderRight: '1px solid var(--rule-thin)',
padding: '0 16px',
}}
className="border-r border-[var(--rule-thin)] px-4 max-md:border-r-0 max-md:p-0 max-md:justify-start"
>
<span style={{ fontSize: '13px', fontWeight: 700, color: 'var(--ink)' }}>
{formatPrice(report.price)}

View File

@ -43,7 +43,7 @@ function formatNumber(n: number): string {
return n.toLocaleString('fa-IR');
}
function PriceTicker({ item, col, row, totalCols, totalRows }: { item: MarketPrice; col: number; row: number; totalCols: number; totalRows: number }) {
function PriceTicker({ item }: { item: MarketPrice }) {
const isUp = item.trend === 'up';
const isDown = item.trend === 'down';
const trendArrow = isUp ? '▲' : isDown ? '▼' : '—';
@ -54,8 +54,6 @@ function PriceTicker({ item, col, row, totalCols, totalRows }: { item: MarketPri
<div
style={{
padding: '24px 20px',
borderLeft: col < totalCols - 1 ? '1px solid var(--rule-thin)' : 'none',
borderBottom: row < totalRows - 1 ? '1px solid var(--rule-thin)' : 'none',
background: 'var(--paper)',
}}
>
@ -136,8 +134,6 @@ function PriceTicker({ item, col, row, totalCols, totalRows }: { item: MarketPri
export default function Scanner() {
// Use first 6 prices for the grid
const gridPrices = marketPrices.slice(0, 6);
const COLS = 3;
const ROWS = Math.ceil(gridPrices.length / COLS);
// For CSS bar chart: find max price in globalComparison for proportional width
// Filter to USD-only for a fair comparison (exclude IRR)
@ -237,25 +233,17 @@ export default function Scanner() {
<div
style={{
border: '2px solid var(--ink)',
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
background: 'var(--rule-thin)',
gap: 1,
}}
className="max-md:grid-cols-1"
className="grid grid-cols-3 max-lg:grid-cols-2 max-sm:grid-cols-1"
>
{gridPrices.map((item, idx) => {
const col = idx % COLS;
const row = Math.floor(idx / COLS);
return (
{gridPrices.map((item) => (
<PriceTicker
key={item.id}
item={item}
col={col}
row={row}
totalCols={COLS}
totalRows={ROWS}
/>
);
})}
))}
</div>
</div>
@ -364,8 +352,7 @@ export default function Scanner() {
</div>
<div
style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px' }}
className="max-md:grid-cols-1"
className="grid grid-cols-3 max-lg:grid-cols-2 max-sm:grid-cols-1 gap-4"
>
{riskAlerts.map((alert) => {
const level = alert.level as AlertLevel;

View File

@ -330,8 +330,7 @@ export default function Special() {
</div>
<div
className="max-md:grid-cols-1"
style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '24px' }}
className="grid grid-cols-2 max-md:grid-cols-1 gap-6"
>
{specialReports.map((report) => (
<SpecialCard key={report.id} report={report} />
@ -369,8 +368,7 @@ export default function Special() {
</div>
<div
className="max-md:grid-cols-1"
style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '20px' }}
className="grid grid-cols-3 max-lg:grid-cols-2 max-md:grid-cols-1 gap-5"
>
{otherReports.map((report) => (
<CompactCard key={report.id} report={report} />