diff --git a/src/sections/Meshery/Features-section/features-section.style.js b/src/sections/Meshery/Features-section/features-section.style.js deleted file mode 100644 index 298ebcf3d9a6e6..00000000000000 --- a/src/sections/Meshery/Features-section/features-section.style.js +++ /dev/null @@ -1,306 +0,0 @@ -import styled from "styled-components"; - -const FeaturesSectionWrapper = styled.section` - - background-color: black; - transform: skew(0deg, -6deg); - padding-bottom: 5rem; - margin-bottom: 8rem; - overflow: hidden; - position: relative; - width: 100%; - max-width: 100%; - contain: layout style paint; - transform-origin: center; - z-index: 1; - height: auto; - isolate: isolate; - .skw{ - display: grid; - position: relative; - top: -5rem; - } - .rect-1-left { - background: #C9FCF6; - border-radius: 0 2.125rem 0 0; - grid-column: 1/ span 2; - height: 5rem; - grid-row: 1; - position: relative; - top: 0.5px; - } - .rect-2-left { - background: ${(props) => props.theme.secondaryColor}; - border-radius: 0 0 3.5rem 0; - grid-column: 1/2; - height: 5rem; - grid-row: 2; - } - .rect-3-right { - background: ${(props) => props.theme.secondaryColor}; - border-radius: 3.5rem 0 0 0; - grid-column: 10/11; - grid-row: 1; - height: 5rem; - } - .rect-4-right { - background: #C9FCF6; - border-radius: 0 0 0 2.125rem; - grid-column: 9/11; - grid-row: 2; - height: 5rem; - position: relative; - top: -0.75px - } - - .section-data { - transform: skew(0deg, 6deg); - padding: 0 1rem 3rem; - text-align: center; - position: relative; - z-index: 1; - h1, h2, p { - color: white; - } - } - - .meshery-logo { - img { - max-height: 8rem; - } - } - .section-header { - margin-top: 3rem; - h1 { - line-height: 3.125rem; - span { - color: ${(props) => props.theme.secondaryColor}; - } - } - h2 { - font-weight: 400; - } - p { - font-size: 1.25rem; - line-height: 1.25rem; - margin: 1rem 0 0; - } - } - .smp-section { - margin: 3rem auto; - max-width: 85%; - overflow: hidden; - - .smp-section-row { - margin: 4rem 0; - align-items: center; - } - h2, h4 { - color: white; - } - .smp-section-data { - h1, h2, p { - text-align: start; color: white; - } - h2 { - font-size: 1.75rem; - font-weight: 500; - } - } - - .smp-section-carousel { - .slick-slider { - max-height: 520px; - .slick-list { - max-width: 50rem; - max-height: 520px; - height: auto; - - .slick-track { - height: auto; - } - - .slick-slide { - height: auto; - img { - max-height: 20rem; - margin: auto; - } - } - } - - .slick-arrow{ - display: none !important; - } - - .slick-dots { - bottom: 0; - top: 0rem; - left: 52rem; - width: 8rem; - - li { - display: block; - width: 100%; - height: auto; - margin: 1rem auto; - opacity: 0.6; - - p { - display: block; - - img { - height: 5rem; - vertical-align: middle; - } - } - } - } - .slick-dots - li.slick-active { - opacity: 1; - } - } - } - } - } - - @media screen and (max-width: 1600px) { - .smp-section .smp-section-carousel .slick-slider { - .slick-list { - max-width: 38rem; - } - - .slick-dots { - left: 40rem; - width: 6rem; - - li p img { - height: 4rem; - } - } - } - } - - @media screen and (max-width: 1200px) { - padding-bottom: 2rem; - margin-bottom: 6rem; - - .smp-section { - .smp-section-row { - margin: 2.5rem 0; - } - - .smp-section-data { - h3, p { - text-align: center; - } - } - - .smp-section-carousel .slick-slider { - .slick-list { - max-width: 80%; - margin: 2rem auto; - } - - .slick-dots { - left: 92%; - } - } - } - } - - @media screen and (max-width: 960px) { - padding-bottom: 0rem; - margin-bottom: 4rem; - .smp-section .smp-section-carousel .slick-slider .slick-list { - .slick-slide { - img { - max-height: 16rem; - } - } - } - } - - @media screen and (max-width: 700px) { - .smp-section { - max-width: 95%; - - .smp-section-carousel .slick-slider { - .slick-list { - max-width: 85%; - margin: 2rem 0; - } - .slick-dots { - left: 90%; - width: 4rem; - - li p img { - height: 2.5rem; - } - } - } - } - } - - @media screen and (max-width: 500px) { - .smp-section { - max-width: 95%; - - .smp-section-carousel .slick-slider { - .slick-list { - max-width: 80%; - .slick-slide { - img { - max-height: 10rem; - } - } - } - .slick-dots { - left: 85%; - top: -1rem; - } - } - } - } - - .mesh-mngmnt { - h3 { - margin: 1rem 0; - color: rgba(255, 255, 255, 0.6); - font-size: 1.5rem; - } - p { - margin: 0.5rem 0; - color: rgba(255, 255, 255, 0.6); - } - - .mesh-mngmnt-btn { - margin: 2rem auto; - display: flex; - flex-wrap: wrap; - max-width: 75%; - - .mgmt_button { - margin: 0.5rem 0; - min-width: 15rem; - border-radius: 1rem; - } - } - } - - @media screen and (max-width: 992px) { - .mesh-mngmnt { - .mesh-mngmnt-btn { - justify-content: center; - - .mgmt_button { - margin: 0.5rem auto; - min-width: 11rem; - } - } - } - } -`; - -export default FeaturesSectionWrapper; diff --git a/src/sections/Meshery/GitHubStarButton/GitHubStarButtonBanner.js b/src/sections/Meshery/GitHubStarButton/GitHubStarButtonBanner.js new file mode 100644 index 00000000000000..a1ad0c008c334a --- /dev/null +++ b/src/sections/Meshery/GitHubStarButton/GitHubStarButtonBanner.js @@ -0,0 +1,228 @@ +import React, { useState, useEffect } from "react"; +import { FaGithub } from "@react-icons/all-files/fa/FaGithub"; +import { FaStar } from "@react-icons/all-files/fa/FaStar"; +import styled from "styled-components"; + +const BannerButton = styled.a` + box-sizing: border-box; + position: relative; + isolation: isolate; + + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + + min-width: 200px; + padding: 12px 30px; + + color: ${({ theme }) => theme.bannerText || "#f0f6fc"}; + + font-family: inherit; + font-size: 15px; + font-weight: 500; + line-height: 1.2; + + text-decoration: none; + text-transform: none; + + transition: 250ms ease; + + /* --- Hexagonal banner shape lives on a pseudo-element so clip-path never clips the focus ring --- */ + &::before { + content: ""; + position: absolute; + inset: 0; + z-index: -1; + + background: ${({ theme }) => theme.bannerBg || "#0d1117"}; + border: 1px solid ${({ theme }) => theme.bannerAccent || "#2bb77c"}; + clip-path: polygon(4% 0%, 96% 0%, 100% 50%, 96% 100%, 4% 100%, 0% 50%); + + transition: inherit; + } + + &:hover::before { + border-color: ${({ theme }) => theme.bannerAccentHover || "#3fd9a0"}; + background: ${({ theme }) => theme.bannerBgHover || "#131a22"}; + } + + &:active { + transform: scale(0.98); + } + + /* Retain visible focus indicator for accessibility */ + &:focus { + outline: 2px solid + ${({ theme }) => theme.activeColor || theme.bannerAccent || "#2bb77c"}; + outline-offset: 2px; + } + + &:focus:not(:focus-visible) { + outline: none; + } + + &:focus-visible { + outline: 2px solid + ${({ theme }) => theme.activeColor || theme.bannerAccent || "#2bb77c"}; + outline-offset: 3px; + } + + &:visited { + color: ${({ theme }) => theme.bannerText || "#f0f6fc"}; + } + + .banner-action, + .banner-count { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + } + + .banner-count { + padding-left: 16px; + margin-left: 16px; + border-left: 1px dashed ${({ theme }) => theme.bannerAccent || "#2bb77c"}; + /* Fixed min-width reserves space to prevent Cumulative Layout Shift (CLS) */ + min-width: 55px; + } + + .banner-icon { + flex-shrink: 0; + color: ${({ theme }) => theme.bannerAccent || "#2bb77c"}; + } + + @media (max-width: 768px) { + min-width: 180px; + padding: 12px 24px; + font-size: 14px; + + .banner-count { + padding-left: 12px; + margin-left: 12px; + min-width: 48px; + } + } +`; + +const GitHubStarButtonBanner = ({ + repo = "meshery/meshery", + url, + className, + ...props +}) => { + const [stars, setStars] = useState(null); + const [loading, setLoading] = useState(true); + + // Derive URL dynamically from repo to avoid mismatches + const targetUrl = url || `https://github.com/${repo}`; + + useEffect(() => { + let isMounted = true; + const controller = new AbortController(); + const cacheKey = `gh_stars_${repo.replace("/", "_")}`; + + // Reset loading state if repo prop changes + setStars(null); + setLoading(true); + + const checkCacheAndFetch = async () => { + let cached = null; + try { + cached = + typeof window !== "undefined" ? localStorage.getItem(cacheKey) : null; + } catch { + cached = null; + } + + if (cached) { + try { + const { count, timestamp } = JSON.parse(cached); + const age = Date.now() - timestamp; + if (typeof count === "number" && age >= 0 && age < 3600000) { + if (isMounted) { + setStars(count); + setLoading(false); + } + return; + } + } catch { + try { + localStorage.removeItem(cacheKey); + } catch { + // Ignore storage errors + } + } + } + + try { + const res = await fetch(`https://api.github.com/repos/${repo}`, { + signal: controller.signal, + }); + if (!res.ok) throw new Error("GitHub API error"); + const data = await res.json(); + + if (typeof data.stargazers_count === "number") { + const count = data.stargazers_count; + try { + localStorage.setItem( + cacheKey, + JSON.stringify({ count, timestamp: Date.now() }), + ); + } catch { + // Ignore storage errors + } + if (isMounted) { + setStars(count); + } + } + } catch (err) { + if (err.name !== "AbortError") { + // Silent catch for network errors + } + } finally { + if (isMounted) { + setLoading(false); + } + } + }; + + checkCacheAndFetch(); + + return () => { + isMounted = false; + controller.abort(); + }; + }, [repo]); + + const formatCount = (num) => { + if (typeof num !== "number") return "Star"; + if (num >= 1000) { + return (num / 1000).toFixed(1).replace(/\.0$/, "") + "k"; + } + return num.toString(); + }; + + return ( + + + + Star on GitHub + + + + {loading ? "…" : stars !== null ? formatCount(stars) : "Star"} + + + ); +}; + +export default GitHubStarButtonBanner; diff --git a/src/sections/Meshery/index.js b/src/sections/Meshery/index.js index ef159481dc370d..1ff3bfa384d302 100644 --- a/src/sections/Meshery/index.js +++ b/src/sections/Meshery/index.js @@ -4,7 +4,6 @@ import Button from "../../reusecore/Button"; import { FiDownloadCloud } from "@react-icons/all-files/fi/FiDownloadCloud"; import { GiClockwork } from "@react-icons/all-files/gi/GiClockwork"; -import { FaGithub } from "@react-icons/all-files/fa/FaGithub"; import FeaturesTable from "./Features-Col"; import mesheryDemo from "../../../static/video/meshery/dashboard.webm"; @@ -16,6 +15,7 @@ import Features from "./Meshery-features"; import InlineQuotes from "../../components/Inline-quotes"; import Maximiliano from "../../collections/members/maximiliano-churichi/Maximiliano-Churichi.webp"; import Nic from "../../collections/members/nicholas-jackson/nic-jackson.webp"; +import GitHubStarButtonBanner from "./GitHubStarButton/GitHubStarButtonBanner"; const MesheryPage = () => { return ( @@ -24,33 +24,39 @@ const MesheryPage = () => {
-

Wrangle your infrastructure

-

collaboratively

+

Wrangle your infrastructure

+

+ collaboratively +

{/* Meshery is the cloud native manager.
*/} - Confidently design, deploy, and operate your infrastructure and workloads with Meshery. + Confidently design, deploy, and operate your infrastructure and + workloads with Meshery.

- - -
@@ -65,7 +71,9 @@ const MesheryPage = () => { image={Nic} />
-

Manage your clusters with features you won't find anywhere else.

+

+ Manage your clusters with features you won't find anywhere else. +

@@ -79,7 +87,6 @@ const MesheryPage = () => { /> - ); }; diff --git a/src/sections/Meshery/meshery.style.js b/src/sections/Meshery/meshery.style.js index 04da4892759df7..dee11f68925eea 100644 --- a/src/sections/Meshery/meshery.style.js +++ b/src/sections/Meshery/meshery.style.js @@ -145,7 +145,8 @@ const MesheryWrapper = styled.section` margin-left: 1rem; } .banner-btn { - margin: 0 0.75rem 0 o.5rem; + margin: 0 ${({ theme }) => theme.spacing?.sm || "0.75rem"} 0 + ${({ theme }) => theme.spacing?.xs || "0.5rem"}; } } }