mirror of
https://github.com/Xevion/xevion.dev.git
synced 2025-12-05 23:16:57 -06:00
Prettier format global
This commit is contained in:
@@ -15,4 +15,4 @@ as a portfolio of what I have learned and what I can do.
|
|||||||
[vercel]: https://vercel.com
|
[vercel]: https://vercel.com
|
||||||
[next]: https://nextjs.org
|
[next]: https://nextjs.org
|
||||||
[trpc]: https://trpc.io/
|
[trpc]: https://trpc.io/
|
||||||
[tailwind]: https://tailwindcss.com/
|
[tailwind]: https://tailwindcss.com/
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const v2_redirects = [
|
|||||||
return {
|
return {
|
||||||
source: url,
|
source: url,
|
||||||
destination: `https://undefined.behavio.rs/posts${url.slice(
|
destination: `https://undefined.behavio.rs/posts${url.slice(
|
||||||
nthIndex(url, "/", 4)
|
nthIndex(url, "/", 4),
|
||||||
)}`,
|
)}`,
|
||||||
permanent: false,
|
permanent: false,
|
||||||
};
|
};
|
||||||
|
|||||||
15
package.json
15
package.json
@@ -23,10 +23,11 @@
|
|||||||
"@vercel/analytics": "^0.1.6",
|
"@vercel/analytics": "^0.1.6",
|
||||||
"next": "^15.1.1",
|
"next": "^15.1.1",
|
||||||
"plaiceholder": "^2.5.0",
|
"plaiceholder": "^2.5.0",
|
||||||
|
"prettier": "^3.4.2",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-icons": "^4.10.1",
|
"react-icons": "^4.10.1",
|
||||||
"react-markdown": "^8.0.4",
|
"react-markdown": "^9.0.1",
|
||||||
"react-ogp": "^0.0.3",
|
"react-ogp": "^0.0.3",
|
||||||
"react-wrap-balancer": "^0.4.0",
|
"react-wrap-balancer": "^0.4.0",
|
||||||
"sass": "^1.56.2",
|
"sass": "^1.56.2",
|
||||||
@@ -36,17 +37,17 @@
|
|||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
"@eslint/eslintrc": "^3",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"postcss": "^8",
|
"autoprefixer": "^10.4.7",
|
||||||
"tailwindcss": "^3.4.1",
|
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.1.1",
|
"eslint-config-next": "15.1.1",
|
||||||
"@eslint/eslintrc": "^3",
|
"postcss": "^8",
|
||||||
"autoprefixer": "^10.4.7",
|
"prettier-plugin-tailwindcss": "^0.6.9",
|
||||||
"prettier-plugin-tailwindcss": "^0.1.13"
|
"tailwindcss": "^3.4.1",
|
||||||
|
"typescript": "^5"
|
||||||
},
|
},
|
||||||
"ct3aMetadata": {
|
"ct3aMetadata": {
|
||||||
"initVersion": "6.11.3"
|
"initVersion": "6.11.3"
|
||||||
|
|||||||
@@ -1,87 +1,105 @@
|
|||||||
import type {FunctionComponent, ReactNode} from "react";
|
import type { FunctionComponent, ReactNode } from "react";
|
||||||
import {Disclosure} from '@headlessui/react'
|
import { Disclosure } from "@headlessui/react";
|
||||||
import {HiBars3, HiXMark} from "react-icons/hi2";
|
import { HiBars3, HiXMark } from "react-icons/hi2";
|
||||||
import {classNames} from "../utils/helpers";
|
import { classNames } from "../utils/helpers";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
const navigation: { id: string, name: string, href: string }[] = [
|
const navigation: { id: string; name: string; href: string }[] = [
|
||||||
{id: 'home', name: 'Home', href: '/',},
|
{ id: "home", name: "Home", href: "/" },
|
||||||
{id: 'projects', name: 'Projects', href: '/projects'},
|
{ id: "projects", name: "Projects", href: "/projects" },
|
||||||
{id: 'contact', name: 'Contact', href: '/contact'},
|
{ id: "contact", name: "Contact", href: "/contact" },
|
||||||
]
|
];
|
||||||
|
|
||||||
type WrapperProps = {
|
type WrapperProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
hideNavigation?: boolean;
|
hideNavigation?: boolean;
|
||||||
current?: string;
|
current?: string;
|
||||||
children?: ReactNode | ReactNode[] | null;
|
children?: ReactNode | ReactNode[] | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AppWrapper: FunctionComponent<WrapperProps> = ({current, children, hideNavigation, className}: WrapperProps) => {
|
const AppWrapper: FunctionComponent<WrapperProps> = ({
|
||||||
return <main className={classNames("body-gradient min-h-screen text-zinc-50", className)}>
|
current,
|
||||||
{!hideNavigation ? <Disclosure as="nav">
|
children,
|
||||||
{({open}) => (
|
hideNavigation,
|
||||||
<>
|
className,
|
||||||
<div className="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
|
}: WrapperProps) => {
|
||||||
<div className="relative flex h-16 items-center justify-between">
|
return (
|
||||||
<div className="absolute inset-y-0 left-0 flex items-center sm:hidden">
|
<main
|
||||||
{/* Mobile menu button*/}
|
className={classNames(
|
||||||
<Disclosure.Button
|
"body-gradient min-h-screen text-zinc-50",
|
||||||
className="inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-zinc-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
|
className,
|
||||||
<span className="sr-only">Open main menu</span>
|
)}
|
||||||
{open ? (
|
>
|
||||||
<HiXMark className="block h-6 w-6" aria-hidden="true"/>
|
{!hideNavigation ? (
|
||||||
) : (
|
<Disclosure as="nav">
|
||||||
<HiBars3 className="block h-6 w-6" aria-hidden="true"/>
|
{({ open }) => (
|
||||||
)}
|
<>
|
||||||
</Disclosure.Button>
|
<div className="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
|
||||||
</div>
|
<div className="relative flex h-16 items-center justify-between">
|
||||||
<div className="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
|
<div className="absolute inset-y-0 left-0 flex items-center sm:hidden">
|
||||||
<div className="hidden sm:ml-6 sm:block">
|
{/* Mobile menu button*/}
|
||||||
<div className="flex space-x-4 text-lg font-roboto ">
|
<Disclosure.Button className="inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-zinc-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
|
||||||
{navigation.map((item) => (
|
<span className="sr-only">Open main menu</span>
|
||||||
<Link
|
{open ? (
|
||||||
key={item.name}
|
<HiXMark className="block h-6 w-6" aria-hidden="true" />
|
||||||
href={item.href}
|
) : (
|
||||||
className={classNames(
|
<HiBars3 className="block h-6 w-6" aria-hidden="true" />
|
||||||
item.id == current ? 'bg-zinc-900 text-white' : 'text-gray-300 hover:bg-zinc-800/60 hover:text-white',
|
)}
|
||||||
'px-2.5 py-1 rounded-md'
|
</Disclosure.Button>
|
||||||
)}
|
</div>
|
||||||
aria-current={item.id == current ? 'page' : undefined}
|
<div className="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
|
||||||
>
|
<div className="hidden sm:ml-6 sm:block">
|
||||||
{item.name}
|
<div className="flex space-x-4 font-roboto text-lg">
|
||||||
</Link>
|
{navigation.map((item) => (
|
||||||
))}
|
<Link
|
||||||
</div>
|
key={item.name}
|
||||||
</div>
|
href={item.href}
|
||||||
</div>
|
className={classNames(
|
||||||
</div>
|
item.id == current
|
||||||
|
? "bg-zinc-900 text-white"
|
||||||
|
: "text-gray-300 hover:bg-zinc-800/60 hover:text-white",
|
||||||
|
"rounded-md px-2.5 py-1",
|
||||||
|
)}
|
||||||
|
aria-current={
|
||||||
|
item.id == current ? "page" : undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Disclosure.Panel className="sm:hidden">
|
<Disclosure.Panel className="sm:hidden">
|
||||||
<div className="space-y-1 px-2 pt-2 pb-3">
|
<div className="space-y-1 px-2 pb-3 pt-2">
|
||||||
{navigation.map((item) => (
|
{navigation.map((item) => (
|
||||||
<Link key={item.name} href={item.href}>
|
<Link key={item.name} href={item.href}>
|
||||||
<Disclosure.Button
|
<Disclosure.Button
|
||||||
|
as="a"
|
||||||
as="a"
|
className={classNames(
|
||||||
className={classNames(
|
item.id == current
|
||||||
item.id == current ? 'bg-zinc-900 text-white' : 'text-gray-300 hover:bg-zinc-700 hover:text-white',
|
? "bg-zinc-900 text-white"
|
||||||
'block px-3 py-2 rounded-md text-base font-medium'
|
: "text-gray-300 hover:bg-zinc-700 hover:text-white",
|
||||||
)}
|
"block rounded-md px-3 py-2 text-base font-medium",
|
||||||
aria-current={item.id == current ? 'page' : undefined}
|
)}
|
||||||
>
|
aria-current={item.id == current ? "page" : undefined}
|
||||||
{item.name}
|
>
|
||||||
</Disclosure.Button>
|
{item.name}
|
||||||
</Link>
|
</Disclosure.Button>
|
||||||
))}
|
</Link>
|
||||||
</div>
|
))}
|
||||||
</Disclosure.Panel>
|
</div>
|
||||||
</>
|
</Disclosure.Panel>
|
||||||
)}
|
</>
|
||||||
</Disclosure> : null}
|
)}
|
||||||
{children}
|
</Disclosure>
|
||||||
|
) : null}
|
||||||
|
{children}
|
||||||
</main>
|
</main>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default AppWrapper;
|
export default AppWrapper;
|
||||||
|
|||||||
@@ -1,26 +1,30 @@
|
|||||||
import Image, {ImageProps} from "next/image";
|
import Image, { ImageProps } from "next/image";
|
||||||
import {useMemo, useState} from "react";
|
import { useMemo, useState } from "react";
|
||||||
|
|
||||||
type DependentProps = {
|
type DependentProps = {
|
||||||
className?: string | ((loaded: boolean) => string);
|
className?: string | ((loaded: boolean) => string);
|
||||||
}
|
};
|
||||||
|
|
||||||
type DependentImageProps = Omit<ImageProps, "className"> & DependentProps;
|
type DependentImageProps = Omit<ImageProps, "className"> & DependentProps;
|
||||||
|
|
||||||
const DependentImage = (props: DependentImageProps) => {
|
const DependentImage = (props: DependentImageProps) => {
|
||||||
const [loaded, setLoaded] = useState(false)
|
const [loaded, setLoaded] = useState(false);
|
||||||
const {className} = props;
|
const { className } = props;
|
||||||
const renderedClassName = useMemo(() => {
|
const renderedClassName = useMemo(() => {
|
||||||
if (className === undefined) return "";
|
if (className === undefined) return "";
|
||||||
if (typeof className === "function") return className(loaded);
|
if (typeof className === "function") return className(loaded);
|
||||||
return className;
|
return className;
|
||||||
}, [loaded, className])
|
}, [loaded, className]);
|
||||||
|
|
||||||
return <Image {...props}
|
return (
|
||||||
className={renderedClassName}
|
<Image
|
||||||
onLoadingComplete={() => {
|
{...props}
|
||||||
setLoaded(true)
|
className={renderedClassName}
|
||||||
}}/>
|
onLoadingComplete={() => {
|
||||||
}
|
setLoaded(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default DependentImage;
|
export default DependentImage;
|
||||||
|
|||||||
@@ -1,92 +1,126 @@
|
|||||||
import React, {useRef} from "react";
|
import React, { useRef } from "react";
|
||||||
import {useOnClickOutside, useToggle} from "usehooks-ts";
|
import { useOnClickOutside, useToggle } from "usehooks-ts";
|
||||||
import {classNames, isHoverable} from "../utils/helpers";
|
import { classNames, isHoverable } from "../utils/helpers";
|
||||||
import DependentImage from "./DependentImage";
|
import DependentImage from "./DependentImage";
|
||||||
import ReactMarkdown from 'react-markdown'
|
import ReactMarkdown from "react-markdown";
|
||||||
import Balancer from "react-wrap-balancer";
|
import Balancer from "react-wrap-balancer";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import {useRouter} from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import {type LinkIcon, LinkIcons} from "../utils/types";
|
import { type LinkIcon, LinkIcons } from "../utils/types";
|
||||||
|
|
||||||
type ItemCardProps = {
|
type ItemCardProps = {
|
||||||
banner: string;
|
banner: string;
|
||||||
bannerSettings?: { quality: number};
|
bannerSettings?: { quality: number };
|
||||||
bannerBlur: string;
|
bannerBlur: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
links?: LinkIcon[];
|
links?: LinkIcon[];
|
||||||
location: string;
|
location: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const ItemCard = ({banner, bannerBlur, title, description, links, location, bannerSettings}: ItemCardProps) => {
|
const ItemCard = ({
|
||||||
const itemRef = useRef<HTMLDivElement>(null);
|
banner,
|
||||||
const mobileButtonRef = useRef<HTMLAnchorElement>(null);
|
bannerBlur,
|
||||||
const [active, toggleActive, setActive] = useToggle()
|
title,
|
||||||
const router = useRouter();
|
description,
|
||||||
|
links,
|
||||||
|
location,
|
||||||
|
bannerSettings,
|
||||||
|
}: ItemCardProps) => {
|
||||||
|
const itemRef = useRef<HTMLDivElement>(null);
|
||||||
|
const mobileButtonRef = useRef<HTMLAnchorElement>(null);
|
||||||
|
const [active, toggleActive, setActive] = useToggle();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
useOnClickOutside(itemRef, (event) => {
|
useOnClickOutside(itemRef, (event) => {
|
||||||
if (mobileButtonRef.current != null && mobileButtonRef.current?.contains(event.target as Node))
|
if (
|
||||||
return;
|
mobileButtonRef.current != null &&
|
||||||
else
|
mobileButtonRef.current?.contains(event.target as Node)
|
||||||
setActive(false);
|
)
|
||||||
})
|
return;
|
||||||
|
else setActive(false);
|
||||||
|
});
|
||||||
|
|
||||||
const navigate = () => {
|
const navigate = () => {
|
||||||
if (!isHoverable()) toggleActive();
|
if (!isHoverable()) toggleActive();
|
||||||
else {
|
else {
|
||||||
router.push(location);
|
router.push(location);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return <>
|
return (
|
||||||
<div ref={itemRef}
|
<>
|
||||||
className={classNames("item [&:not(:first-child)]:mt-3", active ? "active" : null)}
|
<div
|
||||||
onClick={navigate}>
|
ref={itemRef}
|
||||||
<DependentImage fill src={banner}
|
className={classNames(
|
||||||
quality={bannerSettings?.quality ?? 75}
|
"item [&:not(:first-child)]:mt-3",
|
||||||
blurDataURL={bannerBlur}
|
active ? "active" : null,
|
||||||
className={(loaded) => classNames("object-cover", loaded ? null : "blur-xl")}
|
)}
|
||||||
placeholder="blur"
|
onClick={navigate}
|
||||||
alt={`Banner for ${title}`}
|
>
|
||||||
/>
|
<DependentImage
|
||||||
<div className="elements grid grid-cols-12 h-full m-2 px-1 sm:px-4">
|
fill
|
||||||
<div
|
src={banner}
|
||||||
className="col-span-12 sm:col-span-9 lg:col-span-8 max-h-full overflow-hidden drop-shadow-2xl pb-2 md:p-1 pl-2">
|
quality={bannerSettings?.quality ?? 75}
|
||||||
<Link href={{pathname: location}}
|
blurDataURL={bannerBlur}
|
||||||
className="text-lg sm:text-2xl md:text-3xl font-semibold font-roboto">{title}</Link>
|
className={(loaded) =>
|
||||||
<div className="description mt-0 md:mt-1.5 text-base sm:text-xl md:text-xl font-light overflow-hidden"
|
classNames("object-cover", loaded ? null : "blur-xl")
|
||||||
onClick={(e) => {
|
}
|
||||||
e.stopPropagation();
|
placeholder="blur"
|
||||||
navigate();
|
alt={`Banner for ${title}`}
|
||||||
}}>
|
/>
|
||||||
<Balancer>
|
<div className="elements m-2 grid h-full grid-cols-12 px-1 sm:px-4">
|
||||||
<ReactMarkdown>{description}</ReactMarkdown>
|
<div className="col-span-12 max-h-full overflow-hidden pb-2 pl-2 drop-shadow-2xl sm:col-span-9 md:p-1 lg:col-span-8">
|
||||||
</Balancer>
|
<Link
|
||||||
</div>
|
href={{ pathname: location }}
|
||||||
</div>
|
className="font-roboto text-lg font-semibold sm:text-2xl md:text-3xl"
|
||||||
{(links?.length ?? 0) > 0 ?
|
>
|
||||||
<div
|
{title}
|
||||||
className="hidden sm:flex col-span-3 lg:col-span-4 w-full justify-end max-h-full md:py-5">
|
</Link>
|
||||||
<div className="grid grid-cols-2 grid-rows-2 p-2 md:gap-3 aspect-square icon-grid">
|
<div
|
||||||
{links!.map(({icon, location, newTab}) =>
|
className="description mt-0 overflow-hidden text-base font-light sm:text-xl md:mt-1.5 md:text-xl"
|
||||||
<Link key={location} href={location} target={(newTab ?? true) ? "_blank" : "_self"}
|
onClick={(e) => {
|
||||||
onClick={e => e.stopPropagation()}>
|
e.stopPropagation();
|
||||||
{LinkIcons[icon]?.({})}
|
navigate();
|
||||||
</Link>)}
|
}}
|
||||||
</div>
|
>
|
||||||
</div> : null}
|
<Balancer>
|
||||||
|
<ReactMarkdown>{description}</ReactMarkdown>
|
||||||
|
</Balancer>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{(links?.length ?? 0) > 0 ? (
|
||||||
|
<div className="col-span-3 hidden max-h-full w-full justify-end sm:flex md:py-5 lg:col-span-4">
|
||||||
|
<div className="icon-grid grid aspect-square grid-cols-2 grid-rows-2 p-2 md:gap-3">
|
||||||
|
{links!.map(({ icon, location, newTab }) => (
|
||||||
|
<Link
|
||||||
|
key={location}
|
||||||
|
href={location}
|
||||||
|
target={(newTab ?? true) ? "_blank" : "_self"}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{LinkIcons[icon]?.({})}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<Link aria-disabled={!active} ref={mobileButtonRef} href={active ? {pathname: location} : {}}
|
</div>
|
||||||
className={classNames(
|
<Link
|
||||||
"transition-all bg-zinc-800 rounded border border-zinc-900 shadow w-full flex items-center justify-center",
|
aria-disabled={!active}
|
||||||
active ? "opacity-100 h-9 p-2" : "opacity-0 h-0 p-0"
|
ref={mobileButtonRef}
|
||||||
)}>
|
href={active ? { pathname: location } : {}}
|
||||||
Learn More
|
className={classNames(
|
||||||
</Link>
|
"flex w-full items-center justify-center rounded border border-zinc-900 bg-zinc-800 shadow transition-all",
|
||||||
|
active ? "h-9 p-2 opacity-100" : "h-0 p-0 opacity-0",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Learn More
|
||||||
|
</Link>
|
||||||
</>
|
</>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default ItemCard;
|
export default ItemCard;
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import {FunctionComponent} from "react";
|
import { FunctionComponent } from "react";
|
||||||
|
|
||||||
type SteppedSpanProps = {
|
type SteppedSpanProps = {
|
||||||
children: string;
|
children: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const SteppedSpan: FunctionComponent<SteppedSpanProps> = ({children}: SteppedSpanProps) => {
|
const SteppedSpan: FunctionComponent<SteppedSpanProps> = ({
|
||||||
return <div className="stepped">
|
children,
|
||||||
|
}: SteppedSpanProps) => {
|
||||||
{children.split('').map((char: string, index) => {
|
return (
|
||||||
return <span key={index}>
|
<div className="stepped">
|
||||||
{char}
|
{children.split("").map((char: string, index) => {
|
||||||
</span>
|
return <span key={index}>{char}</span>;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default SteppedSpan;
|
export default SteppedSpan;
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
import type {FunctionComponent} from "react";
|
import type { FunctionComponent } from "react";
|
||||||
|
|
||||||
const WorkInProgress: FunctionComponent = () => {
|
const WorkInProgress: FunctionComponent = () => {
|
||||||
return <div className="w-full my-10 flex flex-col items-center">
|
return (
|
||||||
<div
|
<div className="my-10 flex w-full flex-col items-center">
|
||||||
className="bg-zinc-800/30 border border-zinc-700 rounded-md max-w-[23rem] sm:max-w-[25rem] lg:max-w-[30rem] mx-3 w-full p-5 flex flex-col items-center justify-center">
|
<div className="mx-3 flex w-full max-w-[23rem] flex-col items-center justify-center rounded-md border border-zinc-700 bg-zinc-800/30 p-5 sm:max-w-[25rem] lg:max-w-[30rem]">
|
||||||
<span className="bg-gradient-to-r from-orange-500 via-fuchsia-600 to-cyan-500 text-transparent font-semibold bg-clip-text text-3xl sm:text-4xl pb-2 text-center">
|
<span className="bg-gradient-to-r from-orange-500 via-fuchsia-600 to-cyan-500 bg-clip-text pb-2 text-center text-3xl font-semibold text-transparent sm:text-4xl">
|
||||||
Work In Progress
|
Work In Progress
|
||||||
</span>
|
</span>
|
||||||
<p className="text-lg text-center">
|
<p className="text-center text-lg">
|
||||||
This website is a work in-progress.
|
This website is a work in-progress.
|
||||||
<br/>
|
<br />
|
||||||
Unfortunately, this page hasn't been finished yet. Check back later.
|
Unfortunately, this page hasn't been finished yet. Check back
|
||||||
</p>
|
later.
|
||||||
</div>
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default WorkInProgress;
|
export default WorkInProgress;
|
||||||
|
|||||||
@@ -1,19 +1,33 @@
|
|||||||
import {Html, Head, Main, NextScript} from 'next/document'
|
import { Html, Head, Main, NextScript } from "next/document";
|
||||||
|
|
||||||
export default function Document() {
|
export default function Document() {
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head>
|
<Head>
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
<link
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
rel="apple-touch-icon"
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
sizes="180x180"
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
href="/apple-touch-icon.png"
|
||||||
<meta name="theme-color" content="#000000" />
|
/>
|
||||||
</Head>
|
<link
|
||||||
<body className="bg-black">
|
rel="icon"
|
||||||
<Main/>
|
type="image/png"
|
||||||
<NextScript/>
|
sizes="32x32"
|
||||||
</body>
|
href="/favicon-32x32.png"
|
||||||
</Html>
|
/>
|
||||||
)
|
<link
|
||||||
}
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
|
href="/favicon-16x16.png"
|
||||||
|
/>
|
||||||
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
</Head>
|
||||||
|
<body className="bg-black">
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,46 +1,56 @@
|
|||||||
import {type NextPage} from "next";
|
import { type NextPage } from "next";
|
||||||
import AppWrapper from "../components/AppWrapper";
|
import AppWrapper from "../components/AppWrapper";
|
||||||
import {BsDiscord, BsGithub} from "react-icons/bs";
|
import { BsDiscord, BsGithub } from "react-icons/bs";
|
||||||
import {AiFillMail} from "react-icons/ai";
|
import { AiFillMail } from "react-icons/ai";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import type {IconType} from "react-icons";
|
import type { IconType } from "react-icons";
|
||||||
import Tippy from "@tippyjs/react";
|
import Tippy from "@tippyjs/react";
|
||||||
import 'tippy.js/dist/tippy.css';
|
import "tippy.js/dist/tippy.css";
|
||||||
|
|
||||||
const socials: { icon: IconType, href?: string, hint?: string, hideHint?: boolean }[] = [
|
const socials: {
|
||||||
{
|
icon: IconType;
|
||||||
icon: BsGithub,
|
href?: string;
|
||||||
href: "https://github.com/Xevion/"
|
hint?: string;
|
||||||
},
|
hideHint?: boolean;
|
||||||
{
|
}[] = [
|
||||||
icon: AiFillMail,
|
{
|
||||||
href: "mailto:xevion@xevion.dev",
|
icon: BsGithub,
|
||||||
hint: "xevion@xevion.dev"
|
href: "https://github.com/Xevion/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: BsDiscord,
|
icon: AiFillMail,
|
||||||
hint: "Xevion#8506"
|
href: "mailto:xevion@xevion.dev",
|
||||||
}
|
hint: "xevion@xevion.dev",
|
||||||
]
|
},
|
||||||
|
{
|
||||||
|
icon: BsDiscord,
|
||||||
|
hint: "Xevion#8506",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const ContactPage: NextPage = () => {
|
const ContactPage: NextPage = () => {
|
||||||
return <AppWrapper current='contact'>
|
return (
|
||||||
<div className="w-full my-10 flex flex-col items-center">
|
<AppWrapper current="contact">
|
||||||
<div
|
<div className="my-10 flex w-full flex-col items-center">
|
||||||
className="bg-zinc-800/50 border border-zinc-800 rounded-md max-w-[23rem] sm:max-w-[25rem] lg:max-w-[30rem] mx-3 w-full p-5 flex flex-col">
|
<div className="mx-3 flex w-full max-w-[23rem] flex-col rounded-md border border-zinc-800 bg-zinc-800/50 p-5 sm:max-w-[25rem] lg:max-w-[30rem]">
|
||||||
<div className="flex justify-center gap-x-5 text-center">
|
<div className="flex justify-center gap-x-5 text-center">
|
||||||
{socials.map(({icon: Icon, href, hint, hideHint}, index) => {
|
{socials.map(({ icon: Icon, href, hint, hideHint }, index) => {
|
||||||
const inner = <Icon className="w-8 h-8"/>;
|
const inner = <Icon className="h-8 w-8" />;
|
||||||
return <Tippy key={index} disabled={hideHint} content={hint ?? href}>
|
return (
|
||||||
{
|
<Tippy key={index} disabled={hideHint} content={hint ?? href}>
|
||||||
href != undefined ? <Link href={href}>{inner}</Link> : <span>{inner}</span>
|
{href != undefined ? (
|
||||||
}
|
<Link href={href}>{inner}</Link>
|
||||||
</Tippy>
|
) : (
|
||||||
})}
|
<span>{inner}</span>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</Tippy>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</AppWrapper>
|
</AppWrapper>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default ContactPage;
|
export default ContactPage;
|
||||||
|
|||||||
@@ -1,77 +1,102 @@
|
|||||||
import type {NextPage} from "next";
|
import type { NextPage } from "next";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import {BsGithub} from "react-icons/bs";
|
import { BsGithub } from "react-icons/bs";
|
||||||
import {RxOpenInNewWindow} from "react-icons/rx";
|
import { RxOpenInNewWindow } from "react-icons/rx";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import AppWrapper from "../components/AppWrapper";
|
import AppWrapper from "../components/AppWrapper";
|
||||||
import type {ReactNode} from "react";
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
type Screenshot = [string, null | string | ReactNode];
|
type Screenshot = [string, null | string | ReactNode];
|
||||||
type ScreenshotWithQuality = [string, null | string | ReactNode, number];
|
type ScreenshotWithQuality = [string, null | string | ReactNode, number];
|
||||||
const images: (Screenshot | ScreenshotWithQuality)[] = [
|
const images: (Screenshot | ScreenshotWithQuality)[] = [
|
||||||
["/grain/index.jpg", null, 100],
|
["/grain/index.jpg", null, 100],
|
||||||
["/grain/hidden.jpg", null, 100]
|
["/grain/hidden.jpg", null, 100],
|
||||||
]
|
];
|
||||||
|
|
||||||
const GrainPage: NextPage = () => {
|
const GrainPage: NextPage = () => {
|
||||||
return <>
|
return (
|
||||||
<Head>
|
<>
|
||||||
<title>Grain | Xevion.dev</title>
|
<Head>
|
||||||
</Head>
|
<title>Grain | Xevion.dev</title>
|
||||||
<AppWrapper>
|
</Head>
|
||||||
<div className="w-full overflow-auto h-full min-h-screen flex justify-center">
|
<AppWrapper>
|
||||||
<div className="relative my-10 p-3 px-6 w-full max-w-screen-md">
|
<div className="flex h-full min-h-screen w-full justify-center overflow-auto">
|
||||||
<div className="pb-2 flex justify-between">
|
<div className="relative my-10 w-full max-w-screen-md p-3 px-6">
|
||||||
<div className="text-3xl font-semibold">
|
<div className="flex justify-between pb-2">
|
||||||
Grain
|
<div className="text-3xl font-semibold">Grain</div>
|
||||||
</div>
|
<div className="flex items-center justify-end space-x-1.5">
|
||||||
<div className="flex items-center justify-end space-x-1.5">
|
<Link href="https://grain.xevion.dev" target="_blank">
|
||||||
<Link href="https://grain.xevion.dev" target="_blank">
|
<RxOpenInNewWindow className="h-6 w-6 hover:text-zinc-200" />
|
||||||
<RxOpenInNewWindow className="w-6 h-6 hover:text-zinc-200"/>
|
</Link>
|
||||||
</Link>
|
<Link href="https://github.com/Xevion/grain" target="_blank">
|
||||||
<Link href="https://github.com/Xevion/grain" target="_blank">
|
<BsGithub className="h-6 w-6 hover:text-zinc-200" />
|
||||||
<BsGithub className="w-6 h-6 hover:text-zinc-200"/>
|
</Link>
|
||||||
</Link>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="relative">
|
|
||||||
<Link href="https://grain.xevion.dev/">
|
|
||||||
<Image fill quality={100} sizes="100vw" src="/grain/banner.jpeg" alt=""
|
|
||||||
className="!relative pointer-events-none min-h-[10rem] rounded-md object-cover"/>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className="mt-3 w-full prose prose-invert prose-lg">
|
|
||||||
<p>
|
|
||||||
After seeing an online post with beautiful noise patterns & gradients, I decided to
|
|
||||||
try and recreate it. The result was Grain, a simple web app that generates beautiful noise.
|
|
||||||
Under the hood, this app uses multiple layers of SVGs that automatically rescale with the browsers viewport.
|
|
||||||
That way, the noise is always crisp and clear, no matter the screen size.
|
|
||||||
</p>
|
|
||||||
<ul className="md:columns-2">
|
|
||||||
<li>Performant - SVG generation and layering is optimized</li>
|
|
||||||
<li>Small - Builds in less than 16 seconds</li>
|
|
||||||
<li>Open Source - Want to use my gradients? Check it out on <Link href="https://github.com/Xevion/grain" target="_blank">GitHub</Link>.</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Screenshots</h3>
|
|
||||||
<div className="relative space-y-8">
|
|
||||||
{images.map(([src, description, quality]) => {
|
|
||||||
return <div key={src} className="flex flex-col justify-center w-full">
|
|
||||||
<Image fill sizes="100vw" src={src} alt="" quality={quality ?? 75}
|
|
||||||
className="shadow-lg !my-1 !relative pointer-events-none min-h-[10rem] rounded-md object-cover"/>
|
|
||||||
{description != null ?
|
|
||||||
<span
|
|
||||||
className="text-center text-base">{description}</span> : null}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</AppWrapper>
|
<div className="relative">
|
||||||
|
<Link href="https://grain.xevion.dev/">
|
||||||
|
<Image
|
||||||
|
fill
|
||||||
|
quality={100}
|
||||||
|
sizes="100vw"
|
||||||
|
src="/grain/banner.jpeg"
|
||||||
|
alt=""
|
||||||
|
className="pointer-events-none !relative min-h-[10rem] rounded-md object-cover"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="prose prose-lg prose-invert mt-3 w-full">
|
||||||
|
<p>
|
||||||
|
After seeing an online post with beautiful noise patterns &
|
||||||
|
gradients, I decided to try and recreate it. The result was
|
||||||
|
Grain, a simple web app that generates beautiful noise. Under
|
||||||
|
the hood, this app uses multiple layers of SVGs that
|
||||||
|
automatically rescale with the browsers viewport. That way, the
|
||||||
|
noise is always crisp and clear, no matter the screen size.
|
||||||
|
</p>
|
||||||
|
<ul className="md:columns-2">
|
||||||
|
<li>Performant - SVG generation and layering is optimized</li>
|
||||||
|
<li>Small - Builds in less than 16 seconds</li>
|
||||||
|
<li>
|
||||||
|
Open Source - Want to use my gradients? Check it out on{" "}
|
||||||
|
<Link href="https://github.com/Xevion/grain" target="_blank">
|
||||||
|
GitHub
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Screenshots</h3>
|
||||||
|
<div className="relative space-y-8">
|
||||||
|
{images.map(([src, description, quality]) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={src}
|
||||||
|
className="flex w-full flex-col justify-center"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
fill
|
||||||
|
sizes="100vw"
|
||||||
|
src={src}
|
||||||
|
alt=""
|
||||||
|
quality={quality ?? 75}
|
||||||
|
className="pointer-events-none !relative !my-1 min-h-[10rem] rounded-md object-cover shadow-lg"
|
||||||
|
/>
|
||||||
|
{description != null ? (
|
||||||
|
<span className="text-center text-base">
|
||||||
|
{description}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AppWrapper>
|
||||||
</>
|
</>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default GrainPage;
|
export default GrainPage;
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export async function getStaticProps() {
|
|||||||
...project,
|
...project,
|
||||||
bannerBlur: base64,
|
bannerBlur: base64,
|
||||||
};
|
};
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -145,7 +145,7 @@ const Home: NextPage<HomeStaticProps> = ({
|
|||||||
{buttons.map(({ text, href }) => (
|
{buttons.map(({ text, href }) => (
|
||||||
<Link
|
<Link
|
||||||
key={href}
|
key={href}
|
||||||
className="text-sm text-zinc-500 duration-500 hover:text-zinc-300"
|
className="text-sm text-zinc-500 duration-500 hover:text-zinc-300"
|
||||||
href={href}
|
href={href}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import {type NextPage} from "next";
|
import { type NextPage } from "next";
|
||||||
import AppWrapper from "../components/AppWrapper";
|
import AppWrapper from "../components/AppWrapper";
|
||||||
import WorkInProgress from "../components/WorkInProgress";
|
import WorkInProgress from "../components/WorkInProgress";
|
||||||
|
|
||||||
const PathsPage: NextPage = () => {
|
const PathsPage: NextPage = () => {
|
||||||
return <AppWrapper>
|
return (
|
||||||
<WorkInProgress />
|
<AppWrapper>
|
||||||
|
<WorkInProgress />
|
||||||
</AppWrapper>
|
</AppWrapper>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default PathsPage;
|
export default PathsPage;
|
||||||
|
|||||||
@@ -1,59 +1,65 @@
|
|||||||
import {NextPage} from "next";
|
import { NextPage } from "next";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import {BsGithub} from "react-icons/bs";
|
import { BsGithub } from "react-icons/bs";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import AppWrapper from "../components/AppWrapper";
|
import AppWrapper from "../components/AppWrapper";
|
||||||
|
|
||||||
const PhototagPage: NextPage = () => {
|
const PhototagPage: NextPage = () => {
|
||||||
return <>
|
return (
|
||||||
<Head>
|
<>
|
||||||
<title>Phototag | Xevion.dev</title>
|
<Head>
|
||||||
</Head>
|
<title>Phototag | Xevion.dev</title>
|
||||||
<AppWrapper>
|
</Head>
|
||||||
<div className="w-full overflow-auto h-full min-h-screen flex justify-center">
|
<AppWrapper>
|
||||||
<div className="relative my-10 p-3 px-6 w-full max-w-screen-md">
|
<div className="flex h-full min-h-screen w-full justify-center overflow-auto">
|
||||||
<div className="pb-2 flex justify-between">
|
<div className="relative my-10 w-full max-w-screen-md p-3 px-6">
|
||||||
<div className="text-2xl font-semibold">
|
<div className="flex justify-between pb-2">
|
||||||
Phototag
|
<div className="text-2xl font-semibold">Phototag</div>
|
||||||
</div>
|
<div className="flex items-center justify-end space-x-1.5">
|
||||||
<div className="flex items-center justify-end space-x-1.5">
|
<Link href="https://github.com/Xevion/phototag" target="_blank">
|
||||||
<Link href="https://github.com/Xevion/phototag" target="_blank">
|
<BsGithub className="h-5 w-5 hover:text-zinc-200" />
|
||||||
<BsGithub className="w-5 h-5 hover:text-zinc-200"/>
|
</Link>
|
||||||
</Link>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="relative">
|
|
||||||
<Image fill sizes="100vw" src="/phototag.png" alt=""
|
|
||||||
className="!relative pointer-events-none min-h-[10rem] rounded-md object-cover"/>
|
|
||||||
</div>
|
|
||||||
<div className="mt-3 w-full prose prose-invert prose-lg">
|
|
||||||
<p>
|
|
||||||
Phototag is a powerful tool that helps you quickly and easily add rich, descriptive tags to
|
|
||||||
your photos. Using Google's Vision API, Phototag automatically generates tags based on
|
|
||||||
the visual content of your photos, making it easier than ever to organize and find your
|
|
||||||
photos.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
With support for IPTC metadata and Adobe XMP Sidecar files, you can easily integrate
|
|
||||||
Phototag into your existing workflow on Windows. Whether you're a professional
|
|
||||||
photographer or a casual snapshot taker, Phototag is the perfect tool for adding clarity and
|
|
||||||
context to your photos.
|
|
||||||
</p>
|
|
||||||
<ul className="md:columns-2">
|
|
||||||
<li>Simple, but configurable</li>
|
|
||||||
<li>Fully automatic</li>
|
|
||||||
<li>Leverages compression to reduce network load</li>
|
|
||||||
<li>Supports JPEG, PNG, GIF etc.</li>
|
|
||||||
<li>Supports IPTC metadata</li>
|
|
||||||
<li>Supports Adobe XMP sidecar files</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</AppWrapper>
|
<div className="relative">
|
||||||
|
<Image
|
||||||
|
fill
|
||||||
|
sizes="100vw"
|
||||||
|
src="/phototag.png"
|
||||||
|
alt=""
|
||||||
|
className="pointer-events-none !relative min-h-[10rem] rounded-md object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="prose prose-lg prose-invert mt-3 w-full">
|
||||||
|
<p>
|
||||||
|
Phototag is a powerful tool that helps you quickly and easily
|
||||||
|
add rich, descriptive tags to your photos. Using Google's
|
||||||
|
Vision API, Phototag automatically generates tags based on the
|
||||||
|
visual content of your photos, making it easier than ever to
|
||||||
|
organize and find your photos.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
With support for IPTC metadata and Adobe XMP Sidecar files, you
|
||||||
|
can easily integrate Phototag into your existing workflow on
|
||||||
|
Windows. Whether you're a professional photographer or a
|
||||||
|
casual snapshot taker, Phototag is the perfect tool for adding
|
||||||
|
clarity and context to your photos.
|
||||||
|
</p>
|
||||||
|
<ul className="md:columns-2">
|
||||||
|
<li>Simple, but configurable</li>
|
||||||
|
<li>Fully automatic</li>
|
||||||
|
<li>Leverages compression to reduce network load</li>
|
||||||
|
<li>Supports JPEG, PNG, GIF etc.</li>
|
||||||
|
<li>Supports IPTC metadata</li>
|
||||||
|
<li>Supports Adobe XMP sidecar files</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AppWrapper>
|
||||||
</>
|
</>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default PhototagPage;
|
export default PhototagPage;
|
||||||
|
|||||||
@@ -1,81 +1,114 @@
|
|||||||
import type {NextPage} from "next";
|
import type { NextPage } from "next";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import {BsGithub} from "react-icons/bs";
|
import { BsGithub } from "react-icons/bs";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import AppWrapper from "../components/AppWrapper";
|
import AppWrapper from "../components/AppWrapper";
|
||||||
import type {ReactNode} from "react";
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
const images: [string, string | ReactNode][] = [
|
const images: [string, string | ReactNode][] = [
|
||||||
["/portal/home.jpeg", "The home page."],
|
["/portal/home.jpeg", "The home page."],
|
||||||
["/portal/events.png", <> A page listing all current events. <br/> Initial data is cached for performance, but
|
[
|
||||||
becomes
|
"/portal/events.png",
|
||||||
dynamic when filtered.</>],
|
<>
|
||||||
["/portal/admin.png", "A secure admin panel for our officers to view, filter & edit members & events."],
|
{" "}
|
||||||
["/portal/event.png", "The view of a specific event."],
|
A page listing all current events. <br /> Initial data is cached for
|
||||||
["/portal/checkin.png", "The check-in view."],
|
performance, but becomes dynamic when filtered.
|
||||||
["/portal/filters.png", "Organization filtering options. Dynamic semester filtering & event sorting is also available."],
|
</>,
|
||||||
["/portal/login.png", "The login. Fast form validation, seamless login."],
|
],
|
||||||
["/portal/profile.png", <>The member profile view; fully editable on both desktop & mobile. <br/> Seamless editing
|
[
|
||||||
of profiles for users. Full validation available.</>],
|
"/portal/admin.png",
|
||||||
["/portal/status.png", "Members can check their progress towards becoming full members & view what events they attended."],
|
"A secure admin panel for our officers to view, filter & edit members & events.",
|
||||||
]
|
],
|
||||||
|
["/portal/event.png", "The view of a specific event."],
|
||||||
|
["/portal/checkin.png", "The check-in view."],
|
||||||
|
[
|
||||||
|
"/portal/filters.png",
|
||||||
|
"Organization filtering options. Dynamic semester filtering & event sorting is also available.",
|
||||||
|
],
|
||||||
|
["/portal/login.png", "The login. Fast form validation, seamless login."],
|
||||||
|
[
|
||||||
|
"/portal/profile.png",
|
||||||
|
<>
|
||||||
|
The member profile view; fully editable on both desktop & mobile. <br />{" "}
|
||||||
|
Seamless editing of profiles for users. Full validation available.
|
||||||
|
</>,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"/portal/status.png",
|
||||||
|
"Members can check their progress towards becoming full members & view what events they attended.",
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
const PortalPage: NextPage = () => {
|
const PortalPage: NextPage = () => {
|
||||||
return <>
|
return (
|
||||||
<Head>
|
<>
|
||||||
<title>Portal | Xevion.dev</title>
|
<Head>
|
||||||
</Head>
|
<title>Portal | Xevion.dev</title>
|
||||||
<AppWrapper>
|
</Head>
|
||||||
<div className="w-full overflow-auto h-full min-h-screen flex justify-center">
|
<AppWrapper>
|
||||||
<div className="relative my-10 p-3 px-6 w-full max-w-screen-md">
|
<div className="flex h-full min-h-screen w-full justify-center overflow-auto">
|
||||||
<div className="pb-2 flex justify-between">
|
<div className="relative my-10 w-full max-w-screen-md p-3 px-6">
|
||||||
<div className="text-3xl font-semibold">
|
<div className="flex justify-between pb-2">
|
||||||
Portal
|
<div className="text-3xl font-semibold">Portal</div>
|
||||||
</div>
|
<div className="flex items-center justify-end space-x-1.5">
|
||||||
<div className="flex items-center justify-end space-x-1.5">
|
<Link href="https://github.com/acmutsa/Portal" target="_blank">
|
||||||
<Link href="https://github.com/acmutsa/Portal" target="_blank">
|
<BsGithub className="h-6 w-6 hover:text-zinc-200" />
|
||||||
<BsGithub className="w-6 h-6 hover:text-zinc-200"/>
|
</Link>
|
||||||
</Link>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="relative">
|
|
||||||
<Link href="https://portal.acmutsa.org/">
|
|
||||||
<Image fill sizes="100vw" src="/portal/banner.jpeg" alt=""
|
|
||||||
className="!relative pointer-events-none min-h-[10rem] rounded-md object-cover"/>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className="mt-3 w-full prose prose-invert prose-lg">
|
|
||||||
<p>
|
|
||||||
Created in service of our membership, Portal was designed as a approachable membership
|
|
||||||
portal
|
|
||||||
for our users so we could <b>track membership</b>, <b>advertise events</b> and replace our
|
|
||||||
existing <b>database solution</b>.
|
|
||||||
</p>
|
|
||||||
<ul className="md:columns-2">
|
|
||||||
<li>Fast - built to serve thousands</li>
|
|
||||||
<li>Cheap - minimize costs</li>
|
|
||||||
<li>Open Source - help us improve</li>
|
|
||||||
<li>Cutting Edge - the latest technology</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Screenshots</h3>
|
|
||||||
<div className="relative space-y-8">
|
|
||||||
{images.map(([src, description]) => {
|
|
||||||
return <div key={src} className="flex flex-col justify-center w-full">
|
|
||||||
<Image fill sizes="100vw" src={src} alt=""
|
|
||||||
className="shadow-lg !my-1 !relative pointer-events-none min-h-[10rem] rounded-md object-cover"/>
|
|
||||||
<span
|
|
||||||
className="text-center text-base">{description}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</AppWrapper>
|
<div className="relative">
|
||||||
|
<Link href="https://portal.acmutsa.org/">
|
||||||
|
<Image
|
||||||
|
fill
|
||||||
|
sizes="100vw"
|
||||||
|
src="/portal/banner.jpeg"
|
||||||
|
alt=""
|
||||||
|
className="pointer-events-none !relative min-h-[10rem] rounded-md object-cover"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="prose prose-lg prose-invert mt-3 w-full">
|
||||||
|
<p>
|
||||||
|
Created in service of our membership, Portal was designed as a
|
||||||
|
approachable membership portal for our users so we could{" "}
|
||||||
|
<b>track membership</b>, <b>advertise events</b> and replace our
|
||||||
|
existing <b>database solution</b>.
|
||||||
|
</p>
|
||||||
|
<ul className="md:columns-2">
|
||||||
|
<li>Fast - built to serve thousands</li>
|
||||||
|
<li>Cheap - minimize costs</li>
|
||||||
|
<li>Open Source - help us improve</li>
|
||||||
|
<li>Cutting Edge - the latest technology</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Screenshots</h3>
|
||||||
|
<div className="relative space-y-8">
|
||||||
|
{images.map(([src, description]) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={src}
|
||||||
|
className="flex w-full flex-col justify-center"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
fill
|
||||||
|
sizes="100vw"
|
||||||
|
src={src}
|
||||||
|
alt=""
|
||||||
|
className="pointer-events-none !relative !my-1 min-h-[10rem] rounded-md object-cover shadow-lg"
|
||||||
|
/>
|
||||||
|
<span className="text-center text-base">
|
||||||
|
{description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AppWrapper>
|
||||||
</>
|
</>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default PortalPage;
|
export default PortalPage;
|
||||||
|
|||||||
@@ -1,142 +1,158 @@
|
|||||||
import {type NextPage} from "next";
|
import { type NextPage } from "next";
|
||||||
import AppWrapper from "../components/AppWrapper";
|
import AppWrapper from "../components/AppWrapper";
|
||||||
import {RiMagicLine} from "react-icons/ri";
|
import { RiMagicLine } from "react-icons/ri";
|
||||||
import {BiBus, BiHash, BiNetworkChart} from "react-icons/bi";
|
import { BiBus, BiHash, BiNetworkChart } from "react-icons/bi";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import {IconType} from "react-icons";
|
import { IconType } from "react-icons";
|
||||||
import {PiPlantBold} from "react-icons/pi";
|
import { PiPlantBold } from "react-icons/pi";
|
||||||
import {HiOutlineRss} from "react-icons/hi";
|
import { HiOutlineRss } from "react-icons/hi";
|
||||||
import {GiHummingbird, GiPathDistance} from "react-icons/gi";
|
import { GiHummingbird, GiPathDistance } from "react-icons/gi";
|
||||||
import {MdOutlineGrain, MdOutlineDns, MdOutlineLeaderboard} from "react-icons/md";
|
import {
|
||||||
import {FiLayers} from "react-icons/fi";
|
MdOutlineGrain,
|
||||||
import {FaReact} from "react-icons/fa";
|
MdOutlineDns,
|
||||||
import {SiPowershell} from "react-icons/si";
|
MdOutlineLeaderboard,
|
||||||
import {RxTimer} from "react-icons/rx";
|
} from "react-icons/md";
|
||||||
|
import { FiLayers } from "react-icons/fi";
|
||||||
|
import { FaReact } from "react-icons/fa";
|
||||||
|
import { SiPowershell } from "react-icons/si";
|
||||||
|
import { RxTimer } from "react-icons/rx";
|
||||||
|
|
||||||
const ProjectsPage: NextPage = () => {
|
const ProjectsPage: NextPage = () => {
|
||||||
const projects: { name: string, description: string, url?: string, icon: IconType }[] = [
|
const projects: {
|
||||||
{
|
name: string;
|
||||||
name: "Portal",
|
description: string;
|
||||||
description: "ACM Membership & Event System",
|
url?: string;
|
||||||
url: "https://portal.acmutsa.org",
|
icon: IconType;
|
||||||
icon: RiMagicLine
|
}[] = [
|
||||||
},
|
{
|
||||||
{
|
name: "Portal",
|
||||||
name: "Runnerspace",
|
description: "ACM Membership & Event System",
|
||||||
description: "Hackathon MySpace Clone",
|
url: "https://portal.acmutsa.org",
|
||||||
url: "https://runnerspace.xevion.dev",
|
icon: RiMagicLine,
|
||||||
icon: RiMagicLine
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "Runnerspace",
|
||||||
name: "v6 Place",
|
description: "Hackathon MySpace Clone",
|
||||||
description: "Paint Images with IPv6 Addresses",
|
url: "https://runnerspace.xevion.dev",
|
||||||
url: "https://github.com/Xevion/v6-place",
|
icon: RiMagicLine,
|
||||||
icon: BiNetworkChart
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "v6 Place",
|
||||||
name: "Phototag",
|
description: "Paint Images with IPv6 Addresses",
|
||||||
description: "Effortlessly Tag Photos",
|
url: "https://github.com/Xevion/v6-place",
|
||||||
url: "/phototag",
|
icon: BiNetworkChart,
|
||||||
icon: BiHash
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "Phototag",
|
||||||
name: "Paths",
|
description: "Effortlessly Tag Photos",
|
||||||
description: "Graph Traversal Algorithms",
|
url: "/phototag",
|
||||||
url: "https://github.com/Xevion/Paths",
|
icon: BiHash,
|
||||||
icon: GiPathDistance
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "Paths",
|
||||||
name: "undefined behaviors",
|
description: "Graph Traversal Algorithms",
|
||||||
description: "Astro-based Blog",
|
url: "https://github.com/Xevion/Paths",
|
||||||
url: "https://undefined.behavio.rs",
|
icon: GiPathDistance,
|
||||||
icon: HiOutlineRss
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "undefined behaviors",
|
||||||
name: "v2.xevion.dev",
|
description: "Astro-based Blog",
|
||||||
description: "Jekyll-based Blog",
|
url: "https://undefined.behavio.rs",
|
||||||
url: "https://v2.xevion.dev",
|
icon: HiOutlineRss,
|
||||||
icon: HiOutlineRss
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "v2.xevion.dev",
|
||||||
name: "Grain",
|
description: "Jekyll-based Blog",
|
||||||
description: "Pretty SVG-based Noise",
|
url: "https://v2.xevion.dev",
|
||||||
url: "https://grain.xevion.dev",
|
icon: HiOutlineRss,
|
||||||
icon: MdOutlineGrain
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "Grain",
|
||||||
name: "Hydroponics Expanded",
|
description: "Pretty SVG-based Noise",
|
||||||
description: "A RimWorld Mod for Hydroponics",
|
url: "https://grain.xevion.dev",
|
||||||
url: "https://github.com/Xevion/RimWorld-Hydroponics-Expanded",
|
icon: MdOutlineGrain,
|
||||||
icon: PiPlantBold
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "Hydroponics Expanded",
|
||||||
name: "Time Banner",
|
description: "A RimWorld Mod for Hydroponics",
|
||||||
description: "Instant PNG Timestamps in Rust",
|
url: "https://github.com/Xevion/RimWorld-Hydroponics-Expanded",
|
||||||
url: "https://github.com/Xevion/time-banner",
|
icon: PiPlantBold,
|
||||||
icon: RxTimer
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "Time Banner",
|
||||||
name: "The Office",
|
description: "Instant PNG Timestamps in Rust",
|
||||||
description: "View Quotes from The Office",
|
url: "https://github.com/Xevion/time-banner",
|
||||||
url: "https://the-office.xevion.dev",
|
icon: RxTimer,
|
||||||
icon: FiLayers
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "The Office",
|
||||||
name: "Boids",
|
description: "View Quotes from The Office",
|
||||||
description: "Flocking Simulation",
|
url: "https://the-office.xevion.dev",
|
||||||
url: "https://github.com/Xevion/Boids",
|
icon: FiLayers,
|
||||||
icon: GiHummingbird
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "Boids",
|
||||||
name: "bus-reminder",
|
description: "Flocking Simulation",
|
||||||
description: "Last Bus Departure Reminder",
|
url: "https://github.com/Xevion/Boids",
|
||||||
url: "http://github.com/Xevion/bus-reminder",
|
icon: GiHummingbird,
|
||||||
icon: BiBus
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "bus-reminder",
|
||||||
name: "rdap",
|
description: "Last Bus Departure Reminder",
|
||||||
description: "Javascript RDAP Client",
|
url: "http://github.com/Xevion/bus-reminder",
|
||||||
url: "https://rdap.xevion.dev",
|
icon: BiBus,
|
||||||
icon: MdOutlineDns
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "rdap",
|
||||||
name: "icons",
|
description: "Javascript RDAP Client",
|
||||||
description: "Dynamic React-Icons Loading",
|
url: "https://rdap.xevion.dev",
|
||||||
url: "https://icons.xevion.dev",
|
icon: MdOutlineDns,
|
||||||
icon: FaReact
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "icons",
|
||||||
name: "trivia",
|
description: "Dynamic React-Icons Loading",
|
||||||
description: "CLI + Flask Leaderboard",
|
url: "https://icons.xevion.dev",
|
||||||
url: "http://github.com/Xevion/trivia",
|
icon: FaReact,
|
||||||
icon: MdOutlineLeaderboard
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "trivia",
|
||||||
name: "Powershell",
|
description: "CLI + Flask Leaderboard",
|
||||||
description: "Scripts & Guides",
|
url: "http://github.com/Xevion/trivia",
|
||||||
url: "https://powershell.xevion.dev",
|
icon: MdOutlineLeaderboard,
|
||||||
icon: SiPowershell
|
},
|
||||||
}
|
{
|
||||||
]
|
name: "Powershell",
|
||||||
return <AppWrapper current='projects'>
|
description: "Scripts & Guides",
|
||||||
<div className="mt-20 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-20 gap-y-14 h-full py-2 max-w-500 w-max mx-auto flex ">
|
url: "https://powershell.xevion.dev",
|
||||||
{projects.map(({name, description, url, icon: Icon}) => <Link
|
icon: SiPowershell,
|
||||||
key={name}
|
},
|
||||||
className="relative flex flex-shrink items-center opacity-75 hover:opacity-100 transition-opacity"
|
];
|
||||||
href={url ?? ""}
|
return (
|
||||||
target="_blank"
|
<AppWrapper current="projects">
|
||||||
rel="noreferrer"
|
<div className="max-w-500 mx-auto mt-20 flex grid h-full w-max grid-cols-1 gap-x-20 gap-y-14 py-2 md:grid-cols-2 lg:grid-cols-3">
|
||||||
title={name}>
|
{projects.map(({ name, description, url, icon: Icon }) => (
|
||||||
<div className="pt-2 pr-5">
|
<Link
|
||||||
<Icon className="text-3xl saturate-0 opacity-80"/>
|
key={name}
|
||||||
</div>
|
className="relative flex flex-shrink items-center opacity-75 transition-opacity hover:opacity-100"
|
||||||
<div className="flex-auto">
|
href={url ?? ""}
|
||||||
<div className="text-normal">{name}</div>
|
target="_blank"
|
||||||
<div className="text-sm opacity-70 font-normal">{description}</div>
|
rel="noreferrer"
|
||||||
</div>
|
title={name}
|
||||||
</Link>)}
|
>
|
||||||
</div>
|
<div className="pr-5 pt-2">
|
||||||
|
<Icon className="text-3xl opacity-80 saturate-0" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-auto">
|
||||||
|
<div className="text-normal">{name}</div>
|
||||||
|
<div className="text-sm font-normal opacity-70">
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</AppWrapper>
|
</AppWrapper>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProjectsPage;
|
export default ProjectsPage;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import url('https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@900&display=swap');
|
@import url("https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@900&display=swap");
|
||||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap");
|
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap");
|
||||||
@import url("https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap");
|
@import url("https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap");
|
||||||
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@200;300;400;500;700;900&display=swap");
|
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@200;300;400;500;700;900&display=swap");
|
||||||
@@ -8,7 +8,12 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
.body-gradient {
|
.body-gradient {
|
||||||
background-image: linear-gradient(to top left, black, rgb(161 161 170 / 0.1), black);
|
background-image: linear-gradient(
|
||||||
|
to top left,
|
||||||
|
black,
|
||||||
|
rgb(161 161 170 / 0.1),
|
||||||
|
black
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin active {
|
@mixin active {
|
||||||
@@ -21,12 +26,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html,
|
||||||
|
body {
|
||||||
@apply font-inter;
|
@apply font-inter;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
@apply w-full aspect-[7/2] lg:aspect-[5/3] sm:h-[14rem] md:h-[16rem] relative pointer-events-auto cursor-pointer overflow-hidden transition-all rounded;
|
@apply pointer-events-auto relative aspect-[7/2] w-full cursor-pointer overflow-hidden rounded transition-all sm:h-[14rem] md:h-[16rem] lg:aspect-[5/3];
|
||||||
> img {
|
> img {
|
||||||
@apply rounded transition-all;
|
@apply rounded transition-all;
|
||||||
}
|
}
|
||||||
@@ -52,16 +58,17 @@ html, body {
|
|||||||
|
|
||||||
.icon-grid {
|
.icon-grid {
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
@apply min-w-0 max-w-full min-h-0 max-h-full;
|
@apply max-h-full min-h-0 min-w-0 max-w-full;
|
||||||
|
|
||||||
> a > svg {
|
> a > svg {
|
||||||
@apply w-full h-full;
|
@apply h-full w-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
> svg, a {
|
> svg,
|
||||||
|
a {
|
||||||
width: 75%;
|
width: 75%;
|
||||||
height: 75%;
|
height: 75%;
|
||||||
@apply transition-transform drop-shadow-md hover:scale-[120%] opacity-80 hover:opacity-100 text-white m-auto aspect-square;
|
@apply m-auto aspect-square text-white opacity-80 drop-shadow-md transition-transform hover:scale-[120%] hover:opacity-100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,5 +80,5 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply h-full
|
@apply h-full;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
import type {IconType} from "react-icons";
|
import type { IconType } from "react-icons";
|
||||||
import {AiFillGithub, AiOutlineLink} from "react-icons/ai";
|
import { AiFillGithub, AiOutlineLink } from "react-icons/ai";
|
||||||
import {RxOpenInNewWindow} from "react-icons/rx";
|
import { RxOpenInNewWindow } from "react-icons/rx";
|
||||||
|
|
||||||
export type Project = {
|
export type Project = {
|
||||||
title: string;
|
title: string;
|
||||||
banner: string;
|
banner: string;
|
||||||
bannerSettings?: {quality: number;}
|
bannerSettings?: { quality: number };
|
||||||
longDescription: string;
|
longDescription: string;
|
||||||
shortDescription: string;
|
shortDescription: string;
|
||||||
links?: LinkIcon[];
|
links?: LinkIcon[];
|
||||||
location: string;
|
location: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export const LinkIcons: Record<string, IconType> = {
|
export const LinkIcons: Record<string, IconType> = {
|
||||||
github: AiFillGithub,
|
github: AiFillGithub,
|
||||||
external: RxOpenInNewWindow,
|
external: RxOpenInNewWindow,
|
||||||
link: AiOutlineLink
|
link: AiOutlineLink,
|
||||||
}
|
};
|
||||||
export type LinkIcon = {
|
export type LinkIcon = {
|
||||||
icon: keyof typeof LinkIcons;
|
icon: keyof typeof LinkIcons;
|
||||||
location: string;
|
location: string;
|
||||||
newTab?: boolean;
|
newTab?: boolean;
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
zinc: {
|
zinc: {
|
||||||
850: "#1D1D20"
|
850: "#1D1D20",
|
||||||
}
|
|
||||||
},
|
|
||||||
fontSize: {
|
|
||||||
'10xl': '10rem',
|
|
||||||
},
|
|
||||||
fontFamily: {
|
|
||||||
inter: ["\"Inter\"", "sans-serif"],
|
|
||||||
roboto: ["\"Roboto\"", "sans-serif"],
|
|
||||||
mono: ["\"Roboto Mono\"", "monospace"],
|
|
||||||
hanken: ["\"Hanken Grotesk\"", "sans-serif"],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
fontSize: {
|
||||||
|
"10xl": "10rem",
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
inter: ['"Inter"', "sans-serif"],
|
||||||
|
roboto: ['"Roboto"', "sans-serif"],
|
||||||
|
mono: ['"Roboto Mono"', "monospace"],
|
||||||
|
hanken: ['"Hanken Grotesk"', "sans-serif"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [require('@tailwindcss/typography')],
|
},
|
||||||
|
plugins: [require("@tailwindcss/typography")],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user