Compare commits

..
6 Commits
Author SHA1 Message Date
moh 0b513551ca POLISHED - Soften dock hover magnification (92 to 66)
CI / quality (push) Canceled after 0s
2026-09-20 20:49:25 +02:00
moh 57423b1baf IMPROVED - Remove dock shadows, fix bounce artifact, mac-radius icons, gallery icon
- Drop all shadows (dock, icons, tooltip, corner pills); this also removes the
  square that showed behind an icon mid-bounce.
- Bounce now moves the whole tile (logo bg lives on the animated element), so
  nothing is left behind.
- Icons use a ~22% macOS-like corner radius (art carries its own shape).
- Portfolio gets an original photo/gallery icon (not Apple's system icon).
2026-09-20 20:48:23 +02:00
moh c41a2e38c9 IMPROVED - Make dock feel like the real macOS dock
- Stronger magnification (50 to 92) with snappier spring tracking.
- macOS-style launch bounce on click; running-dot indicator for the active
  page (works with fixed app-icon images, no recolor).
- Per-item icon paths in navItems so real macOS app icons can be dropped in.
- Drop the now-unused -active icon variants.
2026-09-20 20:38:35 +02:00
moh 9fd928cce6 REMOVED - Trim home page: drop projects/capabilities/process, full-bleed tools marquee
- Remove the Selected Projects, Capabilities and Process sections from the
  home page (placeholder-heavy content).
- Take the tools marquee out of its card and let it span the full site width
  (full-bleed band) instead.
2026-09-20 20:31:35 +02:00
moh 1e211c1201 REVERTED - Remove macOS shell window; restore full-bleed content 2026-09-20 20:17:33 +02:00
moh 2fc0641512 ADDED - Frame site content in a macOS-style shell window on an ambient desktop
- Wrap #smooth-wrapper in an .app-shell: a fixed, inset, rounded, bordered
  panel. Its transform makes it the containing block for the fixed wrapper, so
  ScrollSmoother sizes the scroll viewport to the shell (content scrolls inside
  it) without fighting ScrollSmoother's inline styles.
- Dock and corner controls stay outside the shell, on a brand-tinted 'desktop'
  wallpaper painted on body.
- Reduced-motion path: the shell scrolls its content natively.
2026-09-20 20:16:49 +02:00
13 changed files with 104 additions and 205 deletions
+1 -86
View File
@@ -5,18 +5,11 @@ import { Container } from "@/components/layout/container";
import { HomeHero } from "@/components/layout/home-hero";
import { MotionFade } from "@/components/motion-fade";
import { MagicBentoSection } from "@/components/home/magic-bento-section";
import { CapabilitiesSection } from "@/components/home/capabilities-section";
import { ContactCtaSection } from "@/components/home/contact-cta-section";
import { MarqueeSection } from "@/components/home/marquee-section";
import { ProcessSection } from "@/components/home/process-section";
import { ProjectsSection } from "@/components/home/projects-section";
import type {
CapabilitiesSectionCopy,
ContactCtaSectionCopy,
MarqueeSectionCopy,
ProcessSectionCopy,
ProjectCardCopy,
ProjectsSectionCopy,
} from "@/components/home/types";
import { buildLocalizedMetadata } from "@/lib/metadata";
import { getLocalizedPath, resolveLocale } from "@/lib/locale";
@@ -25,10 +18,6 @@ import {
getSiteSettings,
splitMarqueeRowItems,
} from "@/lib/app-config";
import {
getLocalizedValue,
getPublishedPortfolioProjects,
} from "@/lib/portfolio";
import { type MarqueeRow } from "@/components/layout/stacked-marquee-section";
type HomePageProps = {
@@ -65,10 +54,9 @@ export default async function HomePage({ params }: HomePageProps) {
siteSettings.defaultLocale,
);
const [t, marqueeSettings, publishedProjects] = await Promise.all([
const [t, marqueeSettings] = await Promise.all([
getTranslations({ locale: localeKey, namespace: "homepage" }),
getMarqueeSettings(),
getPublishedPortfolioProjects(),
]);
const bentoCards = [
@@ -112,28 +100,6 @@ export default async function HomePage({ params }: HomePageProps) {
description: t("marquee.description"),
};
const projectsCopy: ProjectsSectionCopy = {
eyebrow: t("projects.eyebrow"),
title: t("projects.title"),
description: t("projects.description"),
stackLabel: t("projects.stackLabel"),
fallbackItems: t.raw("projects.fallbackItems") as ProjectsSectionCopy["fallbackItems"],
};
const capabilitiesCopy: CapabilitiesSectionCopy = {
eyebrow: t("capabilities.eyebrow"),
title: t("capabilities.title"),
description: t("capabilities.description"),
items: t.raw("capabilities.items") as CapabilitiesSectionCopy["items"],
};
const processCopy: ProcessSectionCopy = {
eyebrow: t("process.eyebrow"),
title: t("process.title"),
description: t("process.description"),
steps: t.raw("process.steps") as ProcessSectionCopy["steps"],
};
const contactCtaCopy: ContactCtaSectionCopy = {
eyebrow: t("contactCta.eyebrow"),
title: t("contactCta.title"),
@@ -157,45 +123,6 @@ export default async function HomePage({ params }: HomePageProps) {
const portfolioHref = getLocalizedPath(localeKey, "/portfolio", siteSettings.defaultLocale);
const contactHref = getLocalizedPath(localeKey, "/contact", siteSettings.defaultLocale);
const stackSets = t.raw("projects.stackSets") as string[][];
const selectedProjects = [
...publishedProjects.filter((project) => project.isFeatured),
...publishedProjects.filter((project) => !project.isFeatured),
].slice(0, 3);
const projectCards: ProjectCardCopy[] = selectedProjects.map((project, index) => ({
title: getLocalizedValue(project.title, localeKey),
summary: getLocalizedValue(project.summary, localeKey),
stack: stackSets[index] ?? stackSets[stackSets.length - 1] ?? [],
href: getLocalizedPath(
localeKey,
`/portfolio/${project.slug}`,
siteSettings.defaultLocale,
),
cta: t("projects.cta"),
meta: [
getLocalizedValue(project.category.name, localeKey),
getLocalizedValue(project.serviceLabel, localeKey),
String(project.projectYear),
],
featured: index === 0,
coverImagePath: project.coverImagePath,
}));
const fallbackProjectCards: ProjectCardCopy[] = projectsCopy.fallbackItems.map((item) => ({
title: item.title,
summary: item.summary,
stack: item.stack,
href: item.href === "/portfolio" ? portfolioHref : item.href === "/contact" ? contactHref : item.href,
cta: t("projects.cta"),
meta: [t("projects.fallbackMeta")],
}));
const mergedProjectCards = [
...projectCards,
...fallbackProjectCards.slice(0, Math.max(0, 3 - projectCards.length)),
].slice(0, 3);
return (
<>
@@ -226,19 +153,7 @@ export default async function HomePage({ params }: HomePageProps) {
<MarqueeSection copy={marqueeCopy} rows={marqueeRows} />
</MotionFade>
<MotionFade delay={0.08}>
<ProjectsSection copy={projectsCopy} items={mergedProjectCards} />
</MotionFade>
<MotionFade delay={0.1}>
<CapabilitiesSection copy={capabilitiesCopy} />
</MotionFade>
<MotionFade delay={0.12}>
<ProcessSection copy={processCopy} />
</MotionFade>
<MotionFade delay={0.14}>
<ContactCtaSection copy={contactCtaCopy} contactHref={contactHref} />
</MotionFade>
</Container>
+6 -7
View File
@@ -1,5 +1,4 @@
import { StackedMarqueeSection, type MarqueeRow } from "@/components/layout/stacked-marquee-section";
import { AppCard } from "@/components/ui/app-card";
import { SectionHeading } from "./section-heading";
import type { MarqueeSectionCopy } from "./types";
@@ -10,18 +9,18 @@ type MarqueeSectionProps = {
export function MarqueeSection({ copy, rows }: MarqueeSectionProps) {
return (
<section className="space-y-8 overflow-hidden">
<section className="space-y-8">
<SectionHeading
eyebrow={copy.eyebrow}
title={copy.title}
description={copy.description}
/>
<AppCard className="overflow-hidden">
<div className="rounded-nested border border-border/70 bg-background py-6">
<StackedMarqueeSection rows={rows} className="py-0" />
</div>
</AppCard>
{/* Full-bleed band: breaks out of the centered container to span the
entire site width instead of sitting inside a card. */}
<div className="relative left-1/2 w-screen -translate-x-1/2 overflow-hidden border-y border-border/60 bg-background/40 py-8">
<StackedMarqueeSection rows={rows} className="py-0" />
</div>
</section>
);
}
+1 -1
View File
@@ -19,7 +19,7 @@ type FloatingControlsProps = {
};
const pill =
"flex h-11 w-11 items-center justify-center rounded-full border border-border/60 bg-background/85 text-foreground/80 shadow-panel backdrop-blur-chrome transition-colors hover:bg-accent hover:text-foreground";
"flex h-11 w-11 items-center justify-center rounded-full border border-border/60 bg-background/85 text-foreground/80 backdrop-blur-chrome transition-colors hover:bg-accent hover:text-foreground";
/** Radius (px) of the arc the items fan out along. */
const RADIUS = 92;
+70 -43
View File
@@ -1,9 +1,11 @@
"use client";
import { motion, useReducedMotion } from "framer-motion";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useLocale, useTranslations } from "next-intl";
import { useState } from "react";
import { FloatingControls } from "@/components/layout/floating-controls";
import { Dock, DockIcon } from "@/components/ui/dock";
@@ -26,15 +28,17 @@ type NavKey = "about" | "portfolio" | "products" | "contact";
type NavItem = {
key: NavKey;
path: string;
/** Icon file under /public. Swap these for real macOS app icons anytime. */
icon: string;
disabled?: boolean;
};
// "Home" is intentionally omitted — the logo (first dock icon) is the home link.
const navItems: NavItem[] = [
{ key: "about", path: "/about" },
{ key: "portfolio", path: "/portfolio" },
{ key: "products", path: "/products", disabled: true },
{ key: "contact", path: "/contact" },
{ key: "about", path: "/about", icon: "/dock/about.svg" },
{ key: "portfolio", path: "/portfolio", icon: "/dock/portfolio.svg" },
{ key: "products", path: "/products", icon: "/dock/products.svg", disabled: true },
{ key: "contact", path: "/contact", icon: "/dock/contact.svg" },
];
function isNavItemActive(currentPath: string, itemPath: string) {
@@ -48,78 +52,92 @@ export function SiteDock({ defaultLocale, isSuperAdmin = false }: SiteDockProps)
const locale = useLocale();
const pathname = usePathname();
const t = useTranslations("navigation");
const reducedMotion = useReducedMotion();
const [bouncing, setBouncing] = useState<string | null>(null);
const currentPath = stripLocalePrefix(pathname);
const homeHref = getLocalizedPath(locale, "/", defaultLocale);
// macOS-style launch hop on click.
const bounceProps = (id: string) =>
reducedMotion
? {}
: {
onClick: () => setBouncing(id),
animate: bouncing === id ? { y: [0, -18, 0, -6, 0] } : { y: 0 },
transition: { duration: 0.5, ease: "easeOut" as const },
onAnimationComplete: () => setBouncing((cur) => (cur === id ? null : cur)),
};
return (
<>
{/* ===== Bottom dock (Magic UI) — primary navigation ===== */}
{/* ===== Bottom dock — primary navigation ===== */}
<div
className="pointer-events-none fixed inset-x-0 bottom-0 z-40 flex justify-center px-4"
style={{ paddingBottom: "calc(0.9rem + env(safe-area-inset-bottom, 0px))" }}
>
<Dock
direction="bottom"
iconSize={52}
iconMagnification={72}
iconDistance={150}
className="pointer-events-auto h-[68px] gap-2.5 border-border/60 bg-background/70 shadow-panel"
iconSize={50}
iconMagnification={66}
iconDistance={140}
className="pointer-events-auto h-[66px] gap-3 border-border/60 bg-background/70"
>
{/* ── Logo (fixed src → no refresh flash) ── */}
<DockIcon className="group relative overflow-visible rounded-[26%] bg-white shadow-[0_6px_14px_-6px_rgba(0,0,0,0.5)] ring-1 ring-black/10">
<DockIcon className="group relative overflow-visible">
<Link
href={homeHref}
aria-label="mohfarawati — Home"
className="flex h-full w-full items-center justify-center rounded-[inherit]"
className="flex h-full w-full items-center justify-center"
>
<Image
src="/logos/light-primary.svg"
alt="mohfarawati"
width={104}
height={104}
sizes="104px"
priority
className="h-full w-full rounded-[inherit] object-contain"
/>
<motion.span
className="flex h-full w-full items-center justify-center overflow-hidden rounded-[22%] bg-white ring-1 ring-black/10"
{...bounceProps("logo")}
>
<Image
src="/logos/light-primary.svg"
alt="mohfarawati"
width={104}
height={104}
sizes="104px"
priority
className="h-full w-full object-contain"
/>
</motion.span>
</Link>
<DockTip label="mohfarawati" />
</DockIcon>
<div className="mx-0.5 h-9 w-px self-center bg-border/70" aria-hidden />
{/* ── Pages ── */}
{navItems.map(({ key, path, disabled }) => {
{/* ── Pages (icon art carries its own squircle shape) ── */}
{navItems.map(({ key, path, icon: iconSrc, disabled }) => {
const itemPath = path || "/";
const isActive = isNavItemActive(currentPath, itemPath);
const label = t(key);
const src = `/dock/${key}${isActive ? "-active" : ""}.svg`;
const icon = (
<Image
src={src}
alt={label}
width={104}
height={104}
sizes="104px"
className={cn("h-full w-full rounded-[inherit] object-contain", disabled && "opacity-55")}
/>
<motion.span
className="flex h-full w-full items-center justify-center"
{...(disabled ? {} : bounceProps(key))}
>
<Image
src={iconSrc}
alt={label}
width={104}
height={104}
sizes="104px"
className={cn("h-full w-full object-contain", disabled && "opacity-55")}
/>
</motion.span>
);
return (
<DockIcon
key={key}
className={cn(
"group relative overflow-visible rounded-[26%] transition-shadow duration-200",
isActive
? "shadow-[0_12px_26px_-8px_hsl(var(--primary)/0.65)]"
: "shadow-[0_6px_14px_-6px_rgba(0,0,0,0.5)] hover:shadow-[0_10px_22px_-8px_rgba(0,0,0,0.6)]",
)}
>
<DockIcon key={key} className="group relative overflow-visible">
{disabled ? (
<span
aria-disabled="true"
className="flex h-full w-full cursor-not-allowed items-center justify-center rounded-[inherit]"
className="flex h-full w-full cursor-not-allowed items-center justify-center"
>
{icon}
</span>
@@ -128,11 +146,20 @@ export function SiteDock({ defaultLocale, isSuperAdmin = false }: SiteDockProps)
href={getLocalizedPath(locale, itemPath, defaultLocale)}
aria-label={label}
aria-current={isActive ? "page" : undefined}
className="flex h-full w-full items-center justify-center rounded-[inherit]"
className="flex h-full w-full items-center justify-center"
>
{icon}
</Link>
)}
{/* macOS running indicator */}
{isActive ? (
<span
aria-hidden
className="pointer-events-none absolute -bottom-[7px] left-1/2 h-[5px] w-[5px] -translate-x-1/2 rounded-full bg-foreground/70"
/>
) : null}
<DockTip label={label} soon={disabled ? t("soon") : undefined} />
</DockIcon>
);
@@ -150,7 +177,7 @@ function DockTip({ label, soon }: { label: string; soon?: string }) {
return (
<span
role="tooltip"
className="pointer-events-none absolute bottom-[calc(100%+0.85rem)] left-1/2 -translate-x-1/2 translate-y-1 whitespace-nowrap rounded-lg border border-border/60 bg-background/90 px-3 py-1.5 text-sm font-semibold text-foreground opacity-0 shadow-md backdrop-blur-chrome transition-all duration-150 group-hover:translate-y-0 group-hover:opacity-100"
className="pointer-events-none absolute bottom-[calc(100%+0.85rem)] left-1/2 -translate-x-1/2 translate-y-1 whitespace-nowrap rounded-lg border border-border/60 bg-background/90 px-3 py-1.5 text-sm font-semibold text-foreground opacity-0 backdrop-blur-chrome transition-all duration-150 group-hover:translate-y-0 group-hover:opacity-100"
>
{label}
{soon ? <span className="ms-1.5 text-xs tracking-wide text-primary">{soon}</span> : null}
+4 -2
View File
@@ -123,10 +123,12 @@ const DockIcon = ({
[size, targetSize, size],
);
// Snappy, near-instant tracking with a touch of smoothing — closer to how the
// real macOS dock follows the cursor than a soft/bouncy spring.
const scaleSize = useSpring(sizeTransform, {
mass: 0.1,
stiffness: 150,
damping: 12,
stiffness: 280,
damping: 22,
});
return (
-12
View File
@@ -1,12 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ff8a5f"/><stop offset="1" stop-color="#e5431f"/></linearGradient>
<linearGradient id="gl" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fff" stop-opacity="0.26"/><stop offset="0.5" stop-color="#fff" stop-opacity="0"/></linearGradient>
</defs>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#bg)"/>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#gl)"/>
<rect x="0.75" y="0.75" width="98.5" height="98.5" rx="27.25" fill="none" stroke="#fff" stroke-opacity="0.14" stroke-width="1.5"/>
<g fill="none" stroke="#fff" stroke-width="6" stroke-linecap="round" stroke-linejoin="round">
<circle cx="50" cy="41" r="11"/><path d="M30 72 C30 57, 70 57, 70 72"/></g>
</svg>

Before

Width:  |  Height:  |  Size: 853 B

+3 -3
View File
@@ -4,9 +4,9 @@
<linearGradient id="gl" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fff" stop-opacity="0.26"/><stop offset="0.5" stop-color="#fff" stop-opacity="0"/></linearGradient>
</defs>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#bg)"/>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#gl)"/>
<rect x="0.75" y="0.75" width="98.5" height="98.5" rx="27.25" fill="none" stroke="#fff" stroke-opacity="0.14" stroke-width="1.5"/>
<rect x="0" y="0" width="100" height="100" rx="22" fill="url(#bg)"/>
<rect x="0" y="0" width="100" height="100" rx="22" fill="url(#gl)"/>
<rect x="0.75" y="0.75" width="98.5" height="98.5" rx="21.25" fill="none" stroke="#fff" stroke-opacity="0.14" stroke-width="1.5"/>
<g fill="none" stroke="#fff" stroke-width="6" stroke-linecap="round" stroke-linejoin="round">
<circle cx="50" cy="41" r="11"/><path d="M30 72 C30 57, 70 57, 70 72"/></g>
</svg>

Before

Width:  |  Height:  |  Size: 853 B

After

Width:  |  Height:  |  Size: 853 B

-12
View File
@@ -1,12 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ff8a5f"/><stop offset="1" stop-color="#e5431f"/></linearGradient>
<linearGradient id="gl" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fff" stop-opacity="0.26"/><stop offset="0.5" stop-color="#fff" stop-opacity="0"/></linearGradient>
</defs>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#bg)"/>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#gl)"/>
<rect x="0.75" y="0.75" width="98.5" height="98.5" rx="27.25" fill="none" stroke="#fff" stroke-opacity="0.14" stroke-width="1.5"/>
<g fill="none" stroke="#fff" stroke-width="6" stroke-linecap="round" stroke-linejoin="round">
<rect x="27" y="35" width="46" height="30" rx="6"/><path d="M30 40 L50 54 L70 40"/></g>
</svg>

Before

Width:  |  Height:  |  Size: 865 B

+3 -3
View File
@@ -4,9 +4,9 @@
<linearGradient id="gl" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fff" stop-opacity="0.26"/><stop offset="0.5" stop-color="#fff" stop-opacity="0"/></linearGradient>
</defs>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#bg)"/>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#gl)"/>
<rect x="0.75" y="0.75" width="98.5" height="98.5" rx="27.25" fill="none" stroke="#fff" stroke-opacity="0.14" stroke-width="1.5"/>
<rect x="0" y="0" width="100" height="100" rx="22" fill="url(#bg)"/>
<rect x="0" y="0" width="100" height="100" rx="22" fill="url(#gl)"/>
<rect x="0.75" y="0.75" width="98.5" height="98.5" rx="21.25" fill="none" stroke="#fff" stroke-opacity="0.14" stroke-width="1.5"/>
<g fill="none" stroke="#fff" stroke-width="6" stroke-linecap="round" stroke-linejoin="round">
<rect x="27" y="35" width="46" height="30" rx="6"/><path d="M30 40 L50 54 L70 40"/></g>
</svg>

Before

Width:  |  Height:  |  Size: 865 B

After

Width:  |  Height:  |  Size: 865 B

-13
View File
@@ -1,13 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ff8a5f"/><stop offset="1" stop-color="#e5431f"/></linearGradient>
<linearGradient id="gl" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fff" stop-opacity="0.26"/><stop offset="0.5" stop-color="#fff" stop-opacity="0"/></linearGradient>
</defs>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#bg)"/>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#gl)"/>
<rect x="0.75" y="0.75" width="98.5" height="98.5" rx="27.25" fill="none" stroke="#fff" stroke-opacity="0.14" stroke-width="1.5"/>
<g fill="#fff">
<rect x="32" y="32" width="15" height="15" rx="4"/><rect x="53" y="32" width="15" height="15" rx="4"/>
<rect x="32" y="53" width="15" height="15" rx="4"/><rect x="53" y="53" width="15" height="15" rx="4"/></g>
</svg>

Before

Width:  |  Height:  |  Size: 909 B

+13 -8
View File
@@ -1,13 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#454b57"/><stop offset="1" stop-color="#1c2027"/></linearGradient>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#4aa8ff"/><stop offset="1" stop-color="#7b5cff"/></linearGradient>
<linearGradient id="gl" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fff" stop-opacity="0.26"/><stop offset="0.5" stop-color="#fff" stop-opacity="0"/></linearGradient>
<stop offset="0" stop-color="#fff" stop-opacity="0.30"/><stop offset="0.5" stop-color="#fff" stop-opacity="0"/></linearGradient>
<clipPath id="card"><rect x="27" y="31" width="46" height="38" rx="7"/></clipPath>
</defs>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#bg)"/>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#gl)"/>
<rect x="0.75" y="0.75" width="98.5" height="98.5" rx="27.25" fill="none" stroke="#fff" stroke-opacity="0.14" stroke-width="1.5"/>
<g fill="#fff">
<rect x="32" y="32" width="15" height="15" rx="4"/><rect x="53" y="32" width="15" height="15" rx="4"/>
<rect x="32" y="53" width="15" height="15" rx="4"/><rect x="53" y="53" width="15" height="15" rx="4"/></g>
<rect x="0" y="0" width="100" height="100" rx="22" fill="url(#bg)"/>
<rect x="0" y="0" width="100" height="100" rx="22" fill="url(#gl)"/>
<rect x="0.75" y="0.75" width="98.5" height="98.5" rx="21.25" fill="none" stroke="#fff" stroke-opacity="0.18" stroke-width="1.5"/>
<rect x="27" y="31" width="46" height="38" rx="7" fill="#ffffff"/>
<g clip-path="url(#card)">
<circle cx="40" cy="44" r="5.5" fill="#ffc23d"/>
<path d="M27 69 L43 52 L53 61 L61 51 L73 69 Z" fill="#37b877"/>
<path d="M55 69 L66 57 L73 64 L73 69 Z" fill="#2c9c86"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 909 B

After

Width:  |  Height:  |  Size: 1.0 KiB

-12
View File
@@ -1,12 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ff8a5f"/><stop offset="1" stop-color="#e5431f"/></linearGradient>
<linearGradient id="gl" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fff" stop-opacity="0.26"/><stop offset="0.5" stop-color="#fff" stop-opacity="0"/></linearGradient>
</defs>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#bg)"/>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#gl)"/>
<rect x="0.75" y="0.75" width="98.5" height="98.5" rx="27.25" fill="none" stroke="#fff" stroke-opacity="0.14" stroke-width="1.5"/>
<g fill="none" stroke="#fff" stroke-width="6" stroke-linecap="round" stroke-linejoin="round">
<path d="M50 28 L71 40 L71 60 L50 72 L29 60 L29 40 Z"/><path d="M29 40 L50 52 L71 40"/><path d="M50 52 L50 72"/></g>
</svg>

Before

Width:  |  Height:  |  Size: 894 B

+3 -3
View File
@@ -4,9 +4,9 @@
<linearGradient id="gl" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fff" stop-opacity="0.26"/><stop offset="0.5" stop-color="#fff" stop-opacity="0"/></linearGradient>
</defs>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#bg)"/>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#gl)"/>
<rect x="0.75" y="0.75" width="98.5" height="98.5" rx="27.25" fill="none" stroke="#fff" stroke-opacity="0.14" stroke-width="1.5"/>
<rect x="0" y="0" width="100" height="100" rx="22" fill="url(#bg)"/>
<rect x="0" y="0" width="100" height="100" rx="22" fill="url(#gl)"/>
<rect x="0.75" y="0.75" width="98.5" height="98.5" rx="21.25" fill="none" stroke="#fff" stroke-opacity="0.14" stroke-width="1.5"/>
<g fill="none" stroke="#fff" stroke-width="6" stroke-linecap="round" stroke-linejoin="round">
<path d="M50 28 L71 40 L71 60 L50 72 L29 60 L29 40 Z"/><path d="M29 40 L50 52 L71 40"/><path d="M50 52 L50 72"/></g>
</svg>

Before

Width:  |  Height:  |  Size: 894 B

After

Width:  |  Height:  |  Size: 894 B