- بازگشت به مقالات - + {titleEn} +
- {blog.categoryName ? ( -- {blog.categoryName} -
- ) : null} +- {blog.title} -
++ {blog.title} +
- {blog.abstract ? ( -- {blog.abstract} -
- ) : null} - -+ {blog.abstract} +
+ ) : null}
{post.title}
diff --git a/src/app/globals.css b/src/app/globals.css
index 0a76cc9..81e2623 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -16,6 +16,13 @@
--brand-gradient: linear-gradient(135deg, var(--accent-from), var(--accent-to));
--background: var(--paper);
--foreground: var(--ink);
+ --header-height: 9rem;
+}
+
+@media (min-width: 1024px) {
+ :root {
+ --header-height: 11rem;
+ }
}
@theme inline {
@@ -125,15 +132,45 @@ h4 {
}
.link-hover {
- transition: color 0.25s ease;
+ display: inline-block;
+ width: fit-content;
+ max-width: 100%;
+ background-image: var(--brand-gradient);
+ background-clip: text;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: currentColor;
+ color: inherit;
+ transition:
+ color 0.35s ease,
+ -webkit-text-fill-color 0.35s ease;
}
.link-hover:hover {
- color: var(--accent);
- background: var(--brand-gradient);
- background-clip: text;
- -webkit-background-clip: text;
-webkit-text-fill-color: transparent;
+ color: transparent;
+}
+
+/* Keep flex layout when the link is already a flex row (nav + icon) */
+a.link-hover.inline-flex,
+.link-hover.inline-flex {
+ display: inline-flex;
+}
+
+/* Pill / button-style links: solid accent, no gradient fill */
+a.link-hover.rounded-full,
+.link-hover.rounded-full,
+.link-hover.btn-primary {
+ background-image: none;
+ background-clip: unset;
+ -webkit-background-clip: unset;
+ -webkit-text-fill-color: unset;
+}
+
+a.link-hover.rounded-full:hover,
+.link-hover.rounded-full:hover,
+.link-hover.btn-primary:hover {
+ color: var(--accent);
+ -webkit-text-fill-color: var(--accent);
}
.border-hover {
@@ -165,11 +202,20 @@ a.group-border-hover:hover {
}
.group-text-hover {
- transition: color 0.25s ease;
+ display: inline;
+ background-image: var(--brand-gradient);
+ background-clip: text;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: currentColor;
+ color: inherit;
+ transition:
+ color 0.35s ease,
+ -webkit-text-fill-color 0.35s ease;
}
.group:hover .group-text-hover {
- color: var(--accent);
+ -webkit-text-fill-color: transparent;
+ color: transparent;
}
.carousel-mask {
@@ -207,6 +253,31 @@ a.group-border-hover:hover {
mask-image: radial-gradient(ellipse 70% 60% at 50% 30%, black 20%, transparent 75%);
}
+.page-title-band {
+ border-bottom: 1px solid rgba(14, 17, 22, 0.08);
+ background-color: #fafafa;
+ background-image:
+ radial-gradient(rgba(14, 17, 22, 0.05) 1px, transparent 1px),
+ linear-gradient(rgba(14, 17, 22, 0.02) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(14, 17, 22, 0.02) 1px, transparent 1px);
+ background-size: 20px 20px, 40px 40px, 40px 40px;
+}
+
+.project-link-bar {
+ position: fixed;
+ inset-inline: 0;
+ bottom: 0;
+ z-index: 40;
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
+ background-color: rgba(14, 17, 22, 0.94);
+ background-image:
+ radial-gradient(rgba(255, 255, 255, 0.09) 1px, transparent 1px),
+ linear-gradient(rgba(255, 255, 255, 0.045) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(255, 255, 255, 0.045) 1px, transparent 1px);
+ background-size: 18px 18px, 36px 36px, 36px 36px;
+ backdrop-filter: blur(10px);
+}
+
.mesh-glow {
background:
radial-gradient(ellipse 55% 45% at 78% 35%, var(--glow), transparent 70%),
@@ -325,8 +396,19 @@ a.group-border-hover:hover {
.prose-content a {
color: var(--accent);
- text-decoration: underline;
- text-underline-offset: 3px;
+ text-decoration: none;
+ background-image: var(--brand-gradient);
+ background-clip: text;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: currentColor;
+ transition:
+ color 0.35s ease,
+ -webkit-text-fill-color 0.35s ease;
+}
+
+.prose-content a:hover {
+ -webkit-text-fill-color: transparent;
+ color: transparent;
}
.prose-content img {
@@ -364,3 +446,90 @@ a.group-border-hover:hover {
padding: 0.55rem 0.75rem;
text-align: start;
}
+
+.footer-bottom-bar {
+ border-top: 1px solid #e5e5e5;
+ background: #f7f7f7;
+}
+
+.meshkee-credit {
+ position: relative;
+}
+
+.meshkee-credit__frame {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+}
+
+.meshkee-credit__line {
+ position: absolute;
+ background: #c8c8c8;
+ transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1);
+}
+
+.meshkee-credit__line--l {
+ top: 0;
+ left: 0;
+ width: 1px;
+ height: 100%;
+ transform: scaleY(0);
+ transform-origin: top;
+ transition-delay: 0s;
+}
+
+.meshkee-credit__line--b {
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 1px;
+ transform: scaleX(0);
+ transform-origin: left;
+ transition-delay: 0.14s;
+}
+
+.meshkee-credit__line--r {
+ top: 0;
+ right: 0;
+ width: 1px;
+ height: 100%;
+ transform: scaleY(0);
+ transform-origin: bottom;
+ transition-delay: 0.28s;
+}
+
+.meshkee-credit__line--t {
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 1px;
+ transform: scaleX(0);
+ transform-origin: right;
+ transition-delay: 0.42s;
+}
+
+.meshkee-credit:hover .meshkee-credit__line--l,
+.meshkee-credit:hover .meshkee-credit__line--r {
+ transform: scaleY(1);
+}
+
+.meshkee-credit:hover .meshkee-credit__line--t,
+.meshkee-credit:hover .meshkee-credit__line--b {
+ transform: scaleX(1);
+}
+
+.meshkee-credit:not(:hover) .meshkee-credit__line--l {
+ transition-delay: 0.42s;
+}
+
+.meshkee-credit:not(:hover) .meshkee-credit__line--b {
+ transition-delay: 0.28s;
+}
+
+.meshkee-credit:not(:hover) .meshkee-credit__line--r {
+ transition-delay: 0.14s;
+}
+
+.meshkee-credit:not(:hover) .meshkee-credit__line--t {
+ transition-delay: 0s;
+}
diff --git a/src/app/portfolio/[slug]/page.tsx b/src/app/portfolio/[slug]/page.tsx
index 7237295..8a36d65 100644
--- a/src/app/portfolio/[slug]/page.tsx
+++ b/src/app/portfolio/[slug]/page.tsx
@@ -4,6 +4,7 @@ import { notFound } from "next/navigation";
import { ContentPageShell } from "@/components/ContentPageShell";
import { HtmlContent } from "@/components/HtmlContent";
import { PortfolioGallery } from "@/components/PortfolioGallery";
+import { PortfolioProjectLink } from "@/components/PortfolioProjectLink";
import {
categoryLabelFa,
findPortfolioCategory,
@@ -103,58 +104,82 @@ export default async function PortfolioDetailPage({
(item) => item.url !== cover,
);
+ const titleEn =
+ portfolio.titleEn ??
+ (portfolio.titleFa && portfolio.title !== portfolio.titleFa
+ ? portfolio.title
+ : null);
+ const categoryHref = rootCategory
+ ? portfolioCategoryHref(rootCategory.slug)
+ : "/portfolio";
+
return (
-
+
-
-
-
- بازگشت به نمونهکارها
-
-
- {categoryFa ? (
- {categoryFa}
- ) : null}
-
-
- {title}
-
-
- {portfolio.titleEn ? (
-
- {portfolio.titleEn}
-
- ) : null}
-
- {portfolio.abstract ? (
-
- {portfolio.abstract}
-
- ) : null}
-
- {date ? (
-
+
+
+
({
id: item.mediaId,
url: item.url,
}))}
+ category={categoryFa}
+ date={date}
+ dateTime={portfolio.publishedAt}
/>
{portfolio.mainTextHtml ? (
@@ -163,6 +188,13 @@ export default async function PortfolioDetailPage({
) : null}
{categoryFa}
- ) : null} - -- {title} -
- - {portfolio.titleEn ? ( -- {portfolio.titleEn} -
- ) : null} - - {portfolio.abstract ? ( -- {portfolio.abstract} -
- ) : null} - - {date ? ( -- نمونهکارها -
-+
Portfolio
-- نگاهی به پروژهها و کارهای انجامشده توسط تیم مشکی. -
++ نمونهکارها +
++ نگاهی به پروژهها و کارهای انجامشده توسط تیم مشکی. +
+- {titleEn} -
- ) : null} -- {titleFa} -
-{categoryFa}
++ {categoryFa} +
+ ) : null} ++ {titleFa} +
+ {titleEn ? ( ++ {titleEn} +
) : null} {item.abstract ? (diff --git a/src/assets/logo/Meshkee Logo (No BG) - White -2.png b/src/assets/logo/Meshkee Logo - White.png similarity index 100% rename from src/assets/logo/Meshkee Logo (No BG) - White -2.png rename to src/assets/logo/Meshkee Logo - White.png diff --git a/src/components/BlogCoverImage.tsx b/src/components/BlogCoverImage.tsx new file mode 100644 index 0000000..3e466ed --- /dev/null +++ b/src/components/BlogCoverImage.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { useState } from "react"; + +type BlogCoverImageProps = { + src: string | null; + className?: string; +}; + +export function BlogCoverImage({ src, className = "" }: BlogCoverImageProps) { + const [failed, setFailed] = useState(false); + + if (!src || failed) { + return ( +