initial commit, setup
This commit is contained in:
21
README.md
21
README.md
@@ -2,8 +2,6 @@ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-
|
|||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
First, run the development server:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
# or
|
# or
|
||||||
@@ -15,22 +13,3 @@ bun dev
|
|||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
|
||||||
|
|
||||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
To learn more about Next.js, take a look at the following resources:
|
|
||||||
|
|
||||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
||||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
||||||
|
|
||||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
|
||||||
|
|
||||||
## Deploy on Vercel
|
|
||||||
|
|
||||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
||||||
|
|
||||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
|
||||||
|
|||||||
BIN
app/favicon.ico
BIN
app/favicon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
119
app/globals.css
119
app/globals.css
@@ -1,26 +1,107 @@
|
|||||||
@import "tailwindcss";
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: #ffffff;
|
|
||||||
--foreground: #171717;
|
/* Background */
|
||||||
|
--bg: #101114;
|
||||||
|
--bg-raised: #141518;
|
||||||
|
--surface: #1a1b1f;
|
||||||
|
--surface-hover: #212229;
|
||||||
|
--surface-active: #282a33;
|
||||||
|
|
||||||
|
/* Border */
|
||||||
|
--border: #2c2d34;
|
||||||
|
--border-strong: #3d3f48;
|
||||||
|
--border-subtle: #232429;
|
||||||
|
|
||||||
|
/* Foreground / text */
|
||||||
|
--fg: #e4e5e9;
|
||||||
|
--fg-secondary: #a0a3ad;
|
||||||
|
--muted: #5f6270;
|
||||||
|
--faint: #43454f;
|
||||||
|
|
||||||
|
/* Accent */
|
||||||
|
--accent: #8a9dff;
|
||||||
|
--accent-hover: #9dacff;
|
||||||
|
--accent-strong: #7088ff;
|
||||||
|
--accent-bg: rgba(138, 157, 255, 0.08);
|
||||||
|
--accent-bg-hover: rgba(138, 157, 255, 0.14);
|
||||||
|
|
||||||
|
/* Semantic */
|
||||||
|
--success: #5ec98f;
|
||||||
|
--success-bg: rgba(94, 201, 143, 0.08);
|
||||||
|
--warning: #e0a855;
|
||||||
|
--warning-bg: rgba(224, 168, 85, 0.08);
|
||||||
|
--error: #e06565;
|
||||||
|
--error-bg: rgba(224, 101, 101, 0.08);
|
||||||
|
--info: #60a5d6;
|
||||||
|
--info-bg: rgba(96, 165, 214, 0.08);
|
||||||
|
|
||||||
|
/* Overlay / depth */
|
||||||
|
--overlay: rgba(0, 0, 0, 0.55);
|
||||||
|
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.25);
|
||||||
|
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.30);
|
||||||
|
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.35);
|
||||||
|
|
||||||
|
/* Radii */
|
||||||
|
--radius-sm: 4px;
|
||||||
|
--radius-md: 6px;
|
||||||
|
--radius-lg: 10px;
|
||||||
|
--radius-xl: 16px;
|
||||||
|
|
||||||
|
/* Spacing */
|
||||||
|
--space-1: 4px;
|
||||||
|
--space-2: 8px;
|
||||||
|
--space-3: 12px;
|
||||||
|
--space-4: 16px;
|
||||||
|
--space-5: 20px;
|
||||||
|
--space-6: 24px;
|
||||||
|
--space-8: 32px;
|
||||||
|
--space-10: 40px;
|
||||||
|
--space-12: 48px;
|
||||||
|
--space-16: 64px;
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--text-xs: 11px;
|
||||||
|
--text-sm: 13px;
|
||||||
|
--text-base: 15px;
|
||||||
|
--text-lg: 18px;
|
||||||
|
--text-xl: 22px;
|
||||||
|
--text-2xl: 28px;
|
||||||
|
--text-3xl: 36px;
|
||||||
|
--text-4xl: 42px;
|
||||||
|
--leading-tight: 1.2;
|
||||||
|
--leading-normal: 1.5;
|
||||||
|
--leading-relaxed: 1.7;
|
||||||
|
|
||||||
|
/* Transitions */
|
||||||
|
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
--duration-fast: 120ms;
|
||||||
|
--duration-normal: 200ms;
|
||||||
|
--duration-slow: 350ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
*,
|
||||||
--color-background: var(--background);
|
*::before,
|
||||||
--color-foreground: var(--foreground);
|
*::after {
|
||||||
--font-sans: var(--font-geist-sans);
|
box-sizing: border-box;
|
||||||
--font-mono: var(--font-geist-mono);
|
margin: 0;
|
||||||
}
|
padding: 0;
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--background: #0a0a0a;
|
|
||||||
--foreground: #ededed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: var(--background);
|
background: var(--bg);
|
||||||
color: var(--foreground);
|
color: var(--fg);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background: rgba(110, 231, 183, 0.25);
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 32px !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { JetBrains_Mono, DM_Sans } from "next/font/google";
|
||||||
|
import { SITE } from "@/data/content";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const mono = JetBrains_Mono({
|
||||||
variable: "--font-geist-sans",
|
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
|
variable: "--mono",
|
||||||
|
display: "swap",
|
||||||
});
|
});
|
||||||
|
|
||||||
const geistMono = Geist_Mono({
|
const sans = DM_Sans({
|
||||||
variable: "--font-geist-mono",
|
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
|
variable: "--sans",
|
||||||
|
display: "swap",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: SITE.title,
|
||||||
description: "Generated by create next app",
|
description: SITE.description,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html
|
<html lang="en" className={`${mono.variable} ${sans.variable}`}>
|
||||||
lang="en"
|
<body>{children}</body>
|
||||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
|
||||||
>
|
|
||||||
<body className="min-h-full flex flex-col">{children}</body>
|
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
104
app/page.tsx
104
app/page.tsx
@@ -1,65 +1,59 @@
|
|||||||
import Image from "next/image";
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { PROFILE } from "@/data/content";
|
||||||
|
import { useMountTransition } from "@/hooks/useAnimations";
|
||||||
|
import Nav from "@/components/Nav";
|
||||||
|
import Profile from "@/components/Profile";
|
||||||
|
import Projects from "@/components/Projects";
|
||||||
|
import Footer from "@/components/Footer";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
const [section, setSection] = useState("profile");
|
||||||
|
const mounted = useMountTransition();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
<div style={{ minHeight: "100vh", paddingBottom: 36 }}>
|
||||||
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
<div
|
||||||
<Image
|
style={{
|
||||||
className="dark:invert"
|
maxWidth: 880,
|
||||||
src="/next.svg"
|
margin: "0 auto",
|
||||||
alt="Next.js logo"
|
padding: "0 24px",
|
||||||
width={100}
|
opacity: mounted ? 1 : 0,
|
||||||
height={20}
|
transform: mounted ? "none" : "translateY(8px)",
|
||||||
priority
|
transition: "all 0.5s cubic-bezier(0.16, 1, 0.3, 1)",
|
||||||
/>
|
}}
|
||||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
|
||||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
|
||||||
To get started, edit the page.tsx file.
|
|
||||||
</h1>
|
|
||||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
|
||||||
Looking for a starting point or more instructions? Head over to{" "}
|
|
||||||
<a
|
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
|
||||||
>
|
>
|
||||||
Templates
|
<header
|
||||||
</a>{" "}
|
style={{
|
||||||
or the{" "}
|
padding: "24px 0 0",
|
||||||
<a
|
display: "flex",
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
justifyContent: "space-between",
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontFamily: "var(--mono)",
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: "var(--accent)",
|
||||||
|
letterSpacing: "-0.02em",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Learning
|
|
||||||
</a>{" "}
|
|
||||||
center.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
|
||||||
<a
|
|
||||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
className="dark:invert"
|
|
||||||
src="/vercel.svg"
|
|
||||||
alt="Vercel logomark"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Deploy Now
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Documentation
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<Nav section={section} setSection={setSection} />
|
||||||
|
|
||||||
|
<main>
|
||||||
|
{section === "profile" && <Profile />}
|
||||||
|
{section === "projects" && <Projects />}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
153
components/ContactMethods.tsx
Normal file
153
components/ContactMethods.tsx
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import type { ContactMethod } from "@/data/content";
|
||||||
|
import { useStaggerReveal } from "@/hooks/useAnimations";
|
||||||
|
|
||||||
|
function ContactIcon({ type }: { type: string }) {
|
||||||
|
const s = {
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
strokeWidth: 1.5,
|
||||||
|
stroke: "currentColor",
|
||||||
|
fill: "none" as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type === "tel")
|
||||||
|
return (
|
||||||
|
<svg {...s} viewBox="0 0 24 24">
|
||||||
|
<path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.67A2 2 0 014.11 2h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 16.92z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (type === "email")
|
||||||
|
return (
|
||||||
|
<svg {...s} viewBox="0 0 24 24">
|
||||||
|
<rect x="2" y="4" width="20" height="16" rx="2" />
|
||||||
|
<path d="M22 4L12 13 2 4" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg {...s} viewBox="0 0 24 24">
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<path d="M12 16v-4M12 8h.01" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function maskValue(label: string, icon: string): string {
|
||||||
|
if (icon === "tel") {
|
||||||
|
const digits = label.replace(/\D/g, "");
|
||||||
|
return `•••-•••-${digits.slice(-4)}`;
|
||||||
|
}
|
||||||
|
return "•".repeat(Math.min(label.length, 12));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHref(label: string, icon: string): string {
|
||||||
|
if (icon === "tel") return `tel:${label.replace(/\D/g, "")}`;
|
||||||
|
if (icon === "email") return `mailto:${label}`;
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
methods: ContactMethod[];
|
||||||
|
visibleCount: number;
|
||||||
|
visibleComponent: Set<Number>;
|
||||||
|
isVisible: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ContactMethods({ methods, visibleComponent, visibleCount, isVisible }: Props) {
|
||||||
|
const [revealed, setRevealed] = useState<Set<number>>(new Set());
|
||||||
|
const [hovered, setHovered] = useState<number | null>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginTop: 32 }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: 12,
|
||||||
|
fontFamily: "var(--mono)",
|
||||||
|
fontSize: "var(--text-xs)",
|
||||||
|
color: "var(--muted)",
|
||||||
|
letterSpacing: "0.08em",
|
||||||
|
textTransform: "uppercase",
|
||||||
|
opacity: isVisible ? 1.0 : 0.0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{"contact"}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||||
|
{methods.map((method, i) => {
|
||||||
|
const isRevealed = !method.masked || revealed.has(i);
|
||||||
|
const isHovered = hovered === i;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={`${method.icon}-${i}`}
|
||||||
|
onMouseEnter={() => {
|
||||||
|
setHovered(i);
|
||||||
|
if (method.masked) {
|
||||||
|
setRevealed((prev) => new Set(prev).add(i));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => setHovered(null)}
|
||||||
|
style={{
|
||||||
|
display: "inline-flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 10,
|
||||||
|
padding: "8px 14px",
|
||||||
|
fontFamily: "var(--mono)",
|
||||||
|
fontSize: "var(--text-sm)",
|
||||||
|
color: isHovered ? "var(--fg)" : "var(--fg-secondary)",
|
||||||
|
background: isHovered ? "var(--surface-hover)" : "var(--surface)",
|
||||||
|
border: `1px solid ${isHovered ? "var(--border-strong)" : "var(--border)"}`,
|
||||||
|
borderRadius: "var(--radius-md)",
|
||||||
|
transition: `all var(--duration-normal) var(--ease-in-out)`,
|
||||||
|
cursor: isRevealed ? "pointer" : "default",
|
||||||
|
alignSelf: "flex-start",
|
||||||
|
opacity: visibleComponent.has(i + visibleCount) ? 1.0 : 0.0
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (isRevealed) {
|
||||||
|
window.location.href = getHref(method.label, method.icon);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: isHovered ? "var(--accent)" : "var(--muted)",
|
||||||
|
transition: `color var(--duration-normal) var(--ease-in-out)`,
|
||||||
|
display: "flex",
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ContactIcon type={method.icon} />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
transition: `opacity var(--duration-normal) var(--ease-in-out)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isRevealed ? method.label : maskValue(method.label, method.icon)}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{method.masked && !revealed.has(i) && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: "var(--text-xs)",
|
||||||
|
color: "var(--faint)",
|
||||||
|
fontStyle: "italic",
|
||||||
|
marginLeft: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
hover to reveal
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
26
components/Cursor.tsx
Normal file
26
components/Cursor.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
export default function Cursor() {
|
||||||
|
const [on, setOn] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const i = setInterval(() => setOn((v) => !v), 530);
|
||||||
|
return () => clearInterval(i);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: "inline-block",
|
||||||
|
width: 10,
|
||||||
|
height: "1.1em",
|
||||||
|
background: on ? "var(--accent)" : "transparent",
|
||||||
|
marginLeft: 2,
|
||||||
|
verticalAlign: "text-bottom",
|
||||||
|
transition: "background 0.05s",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
50
components/Footer.tsx
Normal file
50
components/Footer.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
|
||||||
|
import { SITE } from "@/data/content";
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: 28,
|
||||||
|
background: "var(--surface)",
|
||||||
|
borderTop: "1px solid var(--border)",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-evenly",
|
||||||
|
padding: "0 16px",
|
||||||
|
fontFamily: "var(--mono)",
|
||||||
|
fontSize: 11,
|
||||||
|
color: "var(--muted)",
|
||||||
|
zIndex: 100,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", gap: 16 }}>
|
||||||
|
<span>
|
||||||
|
(2026) - Hunter W.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", gap: 16 }}>
|
||||||
|
<span>Use this design:
|
||||||
|
<a
|
||||||
|
href={SITE.source.link}
|
||||||
|
style={{
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 8,
|
||||||
|
padding: "10px 12px",
|
||||||
|
fontFamily: "var(--mono)",
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: "var(--fg-secondary)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{SITE.source.label}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
63
components/LinkIcon.tsx
Normal file
63
components/LinkIcon.tsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import type { SocialLink } from "@/data/content";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
type: SocialLink["icon"];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function LinkIcon({ type }: Props) {
|
||||||
|
const s = {
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
strokeWidth: 1.5,
|
||||||
|
stroke: "currentColor",
|
||||||
|
fill: "none" as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type === "gh")
|
||||||
|
return (
|
||||||
|
<svg {...s} viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.166 6.839 9.489.5.092.682-.217.682-.482 0-.237-.009-.866-.013-1.7-2.782.604-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.831.092-.646.35-1.086.636-1.336-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836a9.59 9.59 0 012.504.337c1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.163 22 16.418 22 12c0-5.523-4.477-10-10-10z"
|
||||||
|
fill="currentColor"
|
||||||
|
stroke="none"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (type === "li")
|
||||||
|
return (
|
||||||
|
<svg {...s} viewBox="0 0 24 24">
|
||||||
|
<path d="M16 8a6 6 0 016 6v7h-4v-7a2 2 0 00-4 0v7h-4v-7a6 6 0 016-6z" />
|
||||||
|
<rect x="2" y="9" width="4" height="12" />
|
||||||
|
<circle cx="4" cy="4" r="2" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (type === "x")
|
||||||
|
return (
|
||||||
|
<svg {...s} viewBox="0 0 24 24">
|
||||||
|
<path d="M4 4l6.5 8L4 20h2l5.5-6.8L16 20h4l-6.8-8.5L19.5 4h-2l-5 6.2L8 4H4z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (type === "dev")
|
||||||
|
return (
|
||||||
|
<svg {...s} viewBox="0 0 24 24">
|
||||||
|
<rect x="2" y="4" width="20" height="16" rx="2" />
|
||||||
|
<text
|
||||||
|
x="12"
|
||||||
|
y="15"
|
||||||
|
textAnchor="middle"
|
||||||
|
fontSize="7"
|
||||||
|
fontWeight="bold"
|
||||||
|
fill="currentColor"
|
||||||
|
stroke="none"
|
||||||
|
fontFamily="monospace"
|
||||||
|
>
|
||||||
|
DEV
|
||||||
|
</text>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
58
components/Nav.tsx
Normal file
58
components/Nav.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
section: string;
|
||||||
|
setSection: (s: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = ["profile", "projects"];
|
||||||
|
|
||||||
|
export default function Nav({ section, setSection }: Props) {
|
||||||
|
return (
|
||||||
|
<nav
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
gap: 0,
|
||||||
|
borderBottom: "1px solid var(--border)",
|
||||||
|
fontFamily: "var(--mono)",
|
||||||
|
fontSize: 13,
|
||||||
|
userSelect: "none",
|
||||||
|
}}>
|
||||||
|
|
||||||
|
{items.map((item) => (
|
||||||
|
|
||||||
|
<button
|
||||||
|
key={item}
|
||||||
|
onClick={() => setSection(item)}
|
||||||
|
style={{
|
||||||
|
background: section === item ? "var(--surface)" : "transparent",
|
||||||
|
color: section === item ? "var(--accent)" : "var(--muted)",
|
||||||
|
border: "none",
|
||||||
|
borderBottom: section === item ? "2px solid var(--accent)" : "2px solid transparent",
|
||||||
|
padding: "12px 24px",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontFamily: "inherit",
|
||||||
|
fontSize: "inherit",
|
||||||
|
fontWeight: 500,
|
||||||
|
letterSpacing: "0.03em",
|
||||||
|
textTransform: "lowercase",
|
||||||
|
transition: "all 0.2s",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
if (section !== item)
|
||||||
|
(e.target as HTMLElement).style.color = "var(--fg)";
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
if (section !== item)
|
||||||
|
(e.target as HTMLElement).style.color = "var(--muted)";
|
||||||
|
}}>
|
||||||
|
|
||||||
|
<span style={{ opacity: 0.4, marginRight: 6 }}></span>
|
||||||
|
{item}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
96
components/Profile.tsx
Normal file
96
components/Profile.tsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { PROFILE } from "@/data/content";
|
||||||
|
import LinkIcon from "./LinkIcon";
|
||||||
|
import ContactMethods from "./ContactMethods";
|
||||||
|
import { useStaggerReveal } from "@/hooks/useAnimations";
|
||||||
|
|
||||||
|
export default function Profile() {
|
||||||
|
const [hovered, setHovered] = useState<number | null>(null);
|
||||||
|
const visible = useStaggerReveal(PROFILE.links.length + PROFILE.contactMethods.length, 100);
|
||||||
|
return (
|
||||||
|
<div style={{ padding: "48px 0", maxWidth: 640 }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: 8,
|
||||||
|
fontFamily: "var(--mono)",
|
||||||
|
fontSize: 11,
|
||||||
|
color: "var(--muted)",
|
||||||
|
letterSpacing: "0.08em",
|
||||||
|
textTransform: "uppercase",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{"# about"}
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontFamily: "var(--sans)",
|
||||||
|
fontSize: 42,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: "var(--fg)",
|
||||||
|
margin: "0 0 4px 0",
|
||||||
|
lineHeight: 1.1,
|
||||||
|
letterSpacing: "-0.02em",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{PROFILE.name}
|
||||||
|
</h1>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontFamily: "var(--mono)",
|
||||||
|
fontSize: 15,
|
||||||
|
color: "var(--accent)",
|
||||||
|
marginBottom: 32,
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{PROFILE.title}
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontFamily: "var(--sans)",
|
||||||
|
fontSize: 16,
|
||||||
|
lineHeight: 1.7,
|
||||||
|
color: "var(--fg-secondary)",
|
||||||
|
margin: "0 0 40px 0",
|
||||||
|
maxWidth: 560,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{PROFILE.bio}
|
||||||
|
</p>
|
||||||
|
<div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
|
||||||
|
{PROFILE.links.map((link, i) => (
|
||||||
|
<a key={link.label}
|
||||||
|
href={link.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
onMouseEnter={() => setHovered(i)}
|
||||||
|
onMouseLeave={() => setHovered(null)}
|
||||||
|
style={{
|
||||||
|
display: "inline-flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 8,
|
||||||
|
padding: "10px 18px",
|
||||||
|
fontFamily: "var(--mono)",
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: hovered === i ? "var(--accent)" : "var(--fg-secondary)",
|
||||||
|
background: hovered === i ? "var(--accent-bg)" : "var(--surface)",
|
||||||
|
border: `1px solid ${hovered === i ? "var(--accent)" : "var(--border)"}`,
|
||||||
|
borderRadius: 6,
|
||||||
|
textDecoration: "none",
|
||||||
|
transition: "all var(--duration-slow)",
|
||||||
|
transform: hovered === i ? "translateY(-2px)" : "none",
|
||||||
|
opacity: visible.has(i) ? 1.0 : 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LinkIcon type={link.icon} />
|
||||||
|
{link.label}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<ContactMethods methods={PROFILE.contactMethods} visibleCount={PROFILE.links.length} visibleComponent={visible} isVisible={visible.has(PROFILE.links.length-1)} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
103
components/ProjectCard.tsx
Normal file
103
components/ProjectCard.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import type { Project } from "@/data/content";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
project: Project;
|
||||||
|
visible: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ProjectCard({ project, visible }: Props) {
|
||||||
|
const [hovered, setHovered] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={project.link}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
textDecoration: "none",
|
||||||
|
padding: 28,
|
||||||
|
background: hovered ? "var(--surface-hover)" : "var(--surface)",
|
||||||
|
border: `1px solid ${hovered ? "var(--accent)" : "var(--border)"}`,
|
||||||
|
borderRadius: 8,
|
||||||
|
transition: "all 0.35s cubic-bezier(0.16, 1, 0.3, 1)",
|
||||||
|
transform: visible
|
||||||
|
? hovered
|
||||||
|
? "translateY(-3px)"
|
||||||
|
: "translateY(0)"
|
||||||
|
: "translateY(12px)",
|
||||||
|
opacity: visible ? 1 : 0,
|
||||||
|
boxShadow: hovered ? "0 8px 32px rgba(0,0,0,0.12)" : "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "flex-start",
|
||||||
|
marginBottom: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontFamily: "var(--mono)",
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: hovered ? "var(--accent)" : "var(--fg)",
|
||||||
|
transition: "color 0.2s",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{project.title}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontFamily: "var(--mono)",
|
||||||
|
fontSize: 11,
|
||||||
|
color: "var(--muted)",
|
||||||
|
padding: "2px 8px",
|
||||||
|
background: "var(--bg)",
|
||||||
|
borderRadius: 4,
|
||||||
|
border: "1px solid var(--border)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{project.year}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontFamily: "var(--sans)",
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: 1.6,
|
||||||
|
color: "var(--fg-secondary)",
|
||||||
|
margin: "0 0 16px 0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{project.description}
|
||||||
|
</p>
|
||||||
|
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||||
|
{project.tags.map((tag) => (
|
||||||
|
<span
|
||||||
|
key={tag}
|
||||||
|
style={{
|
||||||
|
fontFamily: "var(--mono)",
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: "var(--accent)",
|
||||||
|
padding: "3px 10px",
|
||||||
|
background: "var(--accent-bg)",
|
||||||
|
borderRadius: 4,
|
||||||
|
letterSpacing: "0.02em",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
components/Projects.tsx
Normal file
53
components/Projects.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { PROJECTS } from "@/data/content";
|
||||||
|
import { useStaggerReveal } from "@/hooks/useAnimations";
|
||||||
|
import ProjectCard from "./ProjectCard";
|
||||||
|
|
||||||
|
export default function Projects() {
|
||||||
|
const visible = useStaggerReveal(PROJECTS.length, 100);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ padding: "48px 0" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: 8,
|
||||||
|
fontFamily: "var(--mono)",
|
||||||
|
fontSize: 11,
|
||||||
|
color: "var(--muted)",
|
||||||
|
letterSpacing: "0.08em",
|
||||||
|
textTransform: "uppercase",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{"# projects"}
|
||||||
|
</div>
|
||||||
|
<h2
|
||||||
|
style={{
|
||||||
|
fontFamily: "var(--sans)",
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: "var(--fg)",
|
||||||
|
margin: "0 0 32px 0",
|
||||||
|
letterSpacing: "-0.01em",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{"Things I've built"}
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(auto-fill, minmax(320px, 1fr))",
|
||||||
|
gap: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{PROJECTS.map((project, i) => (
|
||||||
|
<ProjectCard
|
||||||
|
key={project.id}
|
||||||
|
project={project}
|
||||||
|
visible={visible.has(i)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
62
data/content.ts
Normal file
62
data/content.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
export type SocialLink = {
|
||||||
|
label: string;
|
||||||
|
url: string;
|
||||||
|
icon: "gh" | "li" | "x" | "dev";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ContactMethod = {
|
||||||
|
masked: boolean,
|
||||||
|
label: string,
|
||||||
|
icon: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Project = {
|
||||||
|
id: number;
|
||||||
|
slug: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
tags: string[];
|
||||||
|
link: string;
|
||||||
|
year: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const PROFILE = {
|
||||||
|
name: "Hunter W",
|
||||||
|
title: "Software Engineer",
|
||||||
|
email: "contact@hwilliams.dev",
|
||||||
|
bio: "I build user experiences before I ever write code. Experience with full-stack development across a variety of frameworks and languages including JS [Vue, React, Next.JS, Nuxt, Node.JS, Express], Python [Flask, FastAPI, Gunicorn], and more.",
|
||||||
|
links: [
|
||||||
|
{ label: "GitHub", url: "https://github.com/FerrenF", icon: "gh" },
|
||||||
|
{ label: "LinkedIn", url: "https://www.linkedin.com/in/hwilliamsf/", icon: "li" }
|
||||||
|
] satisfies SocialLink[],
|
||||||
|
contactMethods: [{
|
||||||
|
masked: true,
|
||||||
|
label: "478-331-2258",
|
||||||
|
icon: "tel"
|
||||||
|
}, {
|
||||||
|
masked: false,
|
||||||
|
label: "contact@hwilliams.dev",
|
||||||
|
icon: "email"
|
||||||
|
}
|
||||||
|
] satisfies ContactMethod[]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SITE = {
|
||||||
|
title: "Hunter W. - Software Engineer",
|
||||||
|
description: "SWE Looking for work",
|
||||||
|
source: {
|
||||||
|
label: "Git",
|
||||||
|
link: ""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PROJECTS: Project[] = [{
|
||||||
|
id: 1,
|
||||||
|
slug: "clean-space",
|
||||||
|
title: "Clean Space",
|
||||||
|
description: "Clean Space is an architecture, proof of concept, and an in-development tool for Space Engineers and Space Engineers 2 server owners featuring client and server side .",
|
||||||
|
tags: ["C#", ".NET", "WPF", "Netcode", "Security"],
|
||||||
|
link: "https://github.com/FerrenF/CleanSpace",
|
||||||
|
year: "2025",
|
||||||
|
}];
|
||||||
33
hooks/useAnimations.ts
Normal file
33
hooks/useAnimations.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
export function useStaggerReveal(count: number, baseDelay = 80) {
|
||||||
|
const [visible, setVisible] = useState<Set<number>>(new Set());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timers: ReturnType<typeof setTimeout>[] = [];
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
timers.push(
|
||||||
|
setTimeout(
|
||||||
|
() => setVisible((prev) => new Set(prev).add(i)),
|
||||||
|
baseDelay * (i + 1) + 200
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return () => timers.forEach(clearTimeout);
|
||||||
|
}, [count, baseDelay]);
|
||||||
|
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMountTransition(delay = 50) {
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const t = setTimeout(() => setMounted(true), delay);
|
||||||
|
return () => clearTimeout(t);
|
||||||
|
}, [delay]);
|
||||||
|
|
||||||
|
return mounted;
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
/* config options here */
|
|
||||||
reactCompiler: true,
|
reactCompiler: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 391 B |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 128 B |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 385 B |
Reference in New Issue
Block a user