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 { HomeHero } from "@/components/layout/home-hero";
import { MotionFade } from "@/components/motion-fade"; import { MotionFade } from "@/components/motion-fade";
import { MagicBentoSection } from "@/components/home/magic-bento-section"; import { MagicBentoSection } from "@/components/home/magic-bento-section";
import { CapabilitiesSection } from "@/components/home/capabilities-section";
import { ContactCtaSection } from "@/components/home/contact-cta-section"; import { ContactCtaSection } from "@/components/home/contact-cta-section";
import { MarqueeSection } from "@/components/home/marquee-section"; import { MarqueeSection } from "@/components/home/marquee-section";
import { ProcessSection } from "@/components/home/process-section";
import { ProjectsSection } from "@/components/home/projects-section";
import type { import type {
CapabilitiesSectionCopy,
ContactCtaSectionCopy, ContactCtaSectionCopy,
MarqueeSectionCopy, MarqueeSectionCopy,
ProcessSectionCopy,
ProjectCardCopy,
ProjectsSectionCopy,
} from "@/components/home/types"; } from "@/components/home/types";
import { buildLocalizedMetadata } from "@/lib/metadata"; import { buildLocalizedMetadata } from "@/lib/metadata";
import { getLocalizedPath, resolveLocale } from "@/lib/locale"; import { getLocalizedPath, resolveLocale } from "@/lib/locale";
@@ -25,10 +18,6 @@ import {
getSiteSettings, getSiteSettings,
splitMarqueeRowItems, splitMarqueeRowItems,
} from "@/lib/app-config"; } from "@/lib/app-config";
import {
getLocalizedValue,
getPublishedPortfolioProjects,
} from "@/lib/portfolio";
import { type MarqueeRow } from "@/components/layout/stacked-marquee-section"; import { type MarqueeRow } from "@/components/layout/stacked-marquee-section";
type HomePageProps = { type HomePageProps = {
@@ -65,10 +54,9 @@ export default async function HomePage({ params }: HomePageProps) {
siteSettings.defaultLocale, siteSettings.defaultLocale,
); );
const [t, marqueeSettings, publishedProjects] = await Promise.all([ const [t, marqueeSettings] = await Promise.all([
getTranslations({ locale: localeKey, namespace: "homepage" }), getTranslations({ locale: localeKey, namespace: "homepage" }),
getMarqueeSettings(), getMarqueeSettings(),
getPublishedPortfolioProjects(),
]); ]);
const bentoCards = [ const bentoCards = [
@@ -112,28 +100,6 @@ export default async function HomePage({ params }: HomePageProps) {
description: t("marquee.description"), 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 = { const contactCtaCopy: ContactCtaSectionCopy = {
eyebrow: t("contactCta.eyebrow"), eyebrow: t("contactCta.eyebrow"),
title: t("contactCta.title"), title: t("contactCta.title"),
@@ -157,45 +123,6 @@ export default async function HomePage({ params }: HomePageProps) {
const portfolioHref = getLocalizedPath(localeKey, "/portfolio", siteSettings.defaultLocale); const portfolioHref = getLocalizedPath(localeKey, "/portfolio", siteSettings.defaultLocale);
const contactHref = getLocalizedPath(localeKey, "/contact", 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 ( return (
<> <>
@@ -226,19 +153,7 @@ export default async function HomePage({ params }: HomePageProps) {
<MarqueeSection copy={marqueeCopy} rows={marqueeRows} /> <MarqueeSection copy={marqueeCopy} rows={marqueeRows} />
</MotionFade> </MotionFade>
<MotionFade delay={0.08}>
<ProjectsSection copy={projectsCopy} items={mergedProjectCards} />
</MotionFade>
<MotionFade delay={0.1}> <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} /> <ContactCtaSection copy={contactCtaCopy} contactHref={contactHref} />
</MotionFade> </MotionFade>
</Container> </Container>
+4 -5
View File
@@ -1,5 +1,4 @@
import { StackedMarqueeSection, type MarqueeRow } from "@/components/layout/stacked-marquee-section"; import { StackedMarqueeSection, type MarqueeRow } from "@/components/layout/stacked-marquee-section";
import { AppCard } from "@/components/ui/app-card";
import { SectionHeading } from "./section-heading"; import { SectionHeading } from "./section-heading";
import type { MarqueeSectionCopy } from "./types"; import type { MarqueeSectionCopy } from "./types";
@@ -10,18 +9,18 @@ type MarqueeSectionProps = {
export function MarqueeSection({ copy, rows }: MarqueeSectionProps) { export function MarqueeSection({ copy, rows }: MarqueeSectionProps) {
return ( return (
<section className="space-y-8 overflow-hidden"> <section className="space-y-8">
<SectionHeading <SectionHeading
eyebrow={copy.eyebrow} eyebrow={copy.eyebrow}
title={copy.title} title={copy.title}
description={copy.description} description={copy.description}
/> />
<AppCard className="overflow-hidden"> {/* Full-bleed band: breaks out of the centered container to span the
<div className="rounded-nested border border-border/70 bg-background py-6"> 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" /> <StackedMarqueeSection rows={rows} className="py-0" />
</div> </div>
</AppCard>
</section> </section>
); );
} }
+1 -1
View File
@@ -19,7 +19,7 @@ type FloatingControlsProps = {
}; };
const pill = 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. */ /** Radius (px) of the arc the items fan out along. */
const RADIUS = 92; const RADIUS = 92;
+56 -29
View File
@@ -1,9 +1,11 @@
"use client"; "use client";
import { motion, useReducedMotion } from "framer-motion";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { useLocale, useTranslations } from "next-intl"; import { useLocale, useTranslations } from "next-intl";
import { useState } from "react";
import { FloatingControls } from "@/components/layout/floating-controls"; import { FloatingControls } from "@/components/layout/floating-controls";
import { Dock, DockIcon } from "@/components/ui/dock"; import { Dock, DockIcon } from "@/components/ui/dock";
@@ -26,15 +28,17 @@ type NavKey = "about" | "portfolio" | "products" | "contact";
type NavItem = { type NavItem = {
key: NavKey; key: NavKey;
path: string; path: string;
/** Icon file under /public. Swap these for real macOS app icons anytime. */
icon: string;
disabled?: boolean; disabled?: boolean;
}; };
// "Home" is intentionally omitted — the logo (first dock icon) is the home link. // "Home" is intentionally omitted — the logo (first dock icon) is the home link.
const navItems: NavItem[] = [ const navItems: NavItem[] = [
{ key: "about", path: "/about" }, { key: "about", path: "/about", icon: "/dock/about.svg" },
{ key: "portfolio", path: "/portfolio" }, { key: "portfolio", path: "/portfolio", icon: "/dock/portfolio.svg" },
{ key: "products", path: "/products", disabled: true }, { key: "products", path: "/products", icon: "/dock/products.svg", disabled: true },
{ key: "contact", path: "/contact" }, { key: "contact", path: "/contact", icon: "/dock/contact.svg" },
]; ];
function isNavItemActive(currentPath: string, itemPath: string) { function isNavItemActive(currentPath: string, itemPath: string) {
@@ -48,30 +52,47 @@ export function SiteDock({ defaultLocale, isSuperAdmin = false }: SiteDockProps)
const locale = useLocale(); const locale = useLocale();
const pathname = usePathname(); const pathname = usePathname();
const t = useTranslations("navigation"); const t = useTranslations("navigation");
const reducedMotion = useReducedMotion();
const [bouncing, setBouncing] = useState<string | null>(null);
const currentPath = stripLocalePrefix(pathname); const currentPath = stripLocalePrefix(pathname);
const homeHref = getLocalizedPath(locale, "/", defaultLocale); 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 ( return (
<> <>
{/* ===== Bottom dock (Magic UI) — primary navigation ===== */} {/* ===== Bottom dock — primary navigation ===== */}
<div <div
className="pointer-events-none fixed inset-x-0 bottom-0 z-40 flex justify-center px-4" 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))" }} style={{ paddingBottom: "calc(0.9rem + env(safe-area-inset-bottom, 0px))" }}
> >
<Dock <Dock
direction="bottom" direction="bottom"
iconSize={52} iconSize={50}
iconMagnification={72} iconMagnification={66}
iconDistance={150} iconDistance={140}
className="pointer-events-auto h-[68px] gap-2.5 border-border/60 bg-background/70 shadow-panel" className="pointer-events-auto h-[66px] gap-3 border-border/60 bg-background/70"
> >
{/* ── Logo (fixed src → no refresh flash) ── */} {/* ── 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 <Link
href={homeHref} href={homeHref}
aria-label="mohfarawati — Home" 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"
>
<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 <Image
src="/logos/light-primary.svg" src="/logos/light-primary.svg"
@@ -80,46 +101,43 @@ export function SiteDock({ defaultLocale, isSuperAdmin = false }: SiteDockProps)
height={104} height={104}
sizes="104px" sizes="104px"
priority priority
className="h-full w-full rounded-[inherit] object-contain" className="h-full w-full object-contain"
/> />
</motion.span>
</Link> </Link>
<DockTip label="mohfarawati" /> <DockTip label="mohfarawati" />
</DockIcon> </DockIcon>
<div className="mx-0.5 h-9 w-px self-center bg-border/70" aria-hidden /> <div className="mx-0.5 h-9 w-px self-center bg-border/70" aria-hidden />
{/* ── Pages ── */} {/* ── Pages (icon art carries its own squircle shape) ── */}
{navItems.map(({ key, path, disabled }) => { {navItems.map(({ key, path, icon: iconSrc, disabled }) => {
const itemPath = path || "/"; const itemPath = path || "/";
const isActive = isNavItemActive(currentPath, itemPath); const isActive = isNavItemActive(currentPath, itemPath);
const label = t(key); const label = t(key);
const src = `/dock/${key}${isActive ? "-active" : ""}.svg`;
const icon = ( const icon = (
<motion.span
className="flex h-full w-full items-center justify-center"
{...(disabled ? {} : bounceProps(key))}
>
<Image <Image
src={src} src={iconSrc}
alt={label} alt={label}
width={104} width={104}
height={104} height={104}
sizes="104px" sizes="104px"
className={cn("h-full w-full rounded-[inherit] object-contain", disabled && "opacity-55")} className={cn("h-full w-full object-contain", disabled && "opacity-55")}
/> />
</motion.span>
); );
return ( return (
<DockIcon <DockIcon key={key} className="group relative overflow-visible">
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)]",
)}
>
{disabled ? ( {disabled ? (
<span <span
aria-disabled="true" 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} {icon}
</span> </span>
@@ -128,11 +146,20 @@ export function SiteDock({ defaultLocale, isSuperAdmin = false }: SiteDockProps)
href={getLocalizedPath(locale, itemPath, defaultLocale)} href={getLocalizedPath(locale, itemPath, defaultLocale)}
aria-label={label} aria-label={label}
aria-current={isActive ? "page" : undefined} 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} {icon}
</Link> </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} /> <DockTip label={label} soon={disabled ? t("soon") : undefined} />
</DockIcon> </DockIcon>
); );
@@ -150,7 +177,7 @@ function DockTip({ label, soon }: { label: string; soon?: string }) {
return ( return (
<span <span
role="tooltip" 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} {label}
{soon ? <span className="ms-1.5 text-xs tracking-wide text-primary">{soon}</span> : null} {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], [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, { const scaleSize = useSpring(sizeTransform, {
mass: 0.1, mass: 0.1,
stiffness: 150, stiffness: 280,
damping: 12, damping: 22,
}); });
return ( 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"> <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.26"/><stop offset="0.5" stop-color="#fff" stop-opacity="0"/></linearGradient>
</defs> </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="22" fill="url(#bg)"/>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#gl)"/> <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="27.25" fill="none" stroke="#fff" stroke-opacity="0.14" stroke-width="1.5"/> <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"> <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> <circle cx="50" cy="41" r="11"/><path d="M30 72 C30 57, 70 57, 70 72"/></g>
</svg> </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"> <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.26"/><stop offset="0.5" stop-color="#fff" stop-opacity="0"/></linearGradient>
</defs> </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="22" fill="url(#bg)"/>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#gl)"/> <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="27.25" fill="none" stroke="#fff" stroke-opacity="0.14" stroke-width="1.5"/> <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"> <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> <rect x="27" y="35" width="46" height="30" rx="6"/><path d="M30 40 L50 54 L70 40"/></g>
</svg> </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"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs> <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"> <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> </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="22" fill="url(#bg)"/>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#gl)"/> <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="27.25" fill="none" stroke="#fff" stroke-opacity="0.14" stroke-width="1.5"/> <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"/>
<g fill="#fff"> <rect x="27" y="31" width="46" height="38" rx="7" fill="#ffffff"/>
<rect x="32" y="32" width="15" height="15" rx="4"/><rect x="53" y="32" width="15" height="15" rx="4"/> <g clip-path="url(#card)">
<rect x="32" y="53" width="15" height="15" rx="4"/><rect x="53" y="53" width="15" height="15" rx="4"/></g> <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> </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"> <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.26"/><stop offset="0.5" stop-color="#fff" stop-opacity="0"/></linearGradient>
</defs> </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="22" fill="url(#bg)"/>
<rect x="0" y="0" width="100" height="100" rx="28" fill="url(#gl)"/> <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="27.25" fill="none" stroke="#fff" stroke-opacity="0.14" stroke-width="1.5"/> <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"> <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> <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> </svg>

Before

Width:  |  Height:  |  Size: 894 B

After

Width:  |  Height:  |  Size: 894 B