initial commit and first version release
This commit is contained in:
parent
b3603c51cf
commit
10b179f6e3
2
.env.example
Normal file
2
.env.example
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
APP_NAME=
|
||||||
|
APP_LANG=
|
@ -1,3 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "next/core-web-vitals"
|
"extends": ["next/core-web-vitals", "prettier"],
|
||||||
|
"rules": {
|
||||||
|
"react/no-unescaped-entities": "off",
|
||||||
|
"@next/next/no-page-custom-font": "off"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
53
.prettierignore
Normal file
53
.prettierignore
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Using this project's .gitignore file as base.
|
||||||
|
#
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next
|
||||||
|
/out
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
# configs
|
||||||
|
package.json
|
||||||
|
package-lock.json
|
||||||
|
next.config.js
|
||||||
|
pnpm-lock.yaml
|
||||||
|
postcss.config.js
|
||||||
|
tsconfig.json
|
||||||
|
yarn.lock
|
||||||
|
.eslintrc.json
|
||||||
|
.prettierrc.json
|
||||||
|
|
||||||
|
# public
|
||||||
|
/public
|
||||||
|
README.md
|
14
.prettierrc.json
Normal file
14
.prettierrc.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"quoteProps": "as-needed",
|
||||||
|
"jsxSingleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"bracketSameLine": true,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"singleAttributePerLine": true
|
||||||
|
}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"discord.enabled": false
|
||||||
|
}
|
@ -1,27 +0,0 @@
|
|||||||
import "@mantine/core/styles.css";
|
|
||||||
import React from "react";
|
|
||||||
import { MantineProvider, ColorSchemeScript } from "@mantine/core";
|
|
||||||
import { theme } from "../theme";
|
|
||||||
|
|
||||||
export const metadata = {
|
|
||||||
title: "Mantine Next.js template",
|
|
||||||
description: "I am using Mantine with Next.js!",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({ children }: { children: any }) {
|
|
||||||
return (
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<ColorSchemeScript />
|
|
||||||
<link rel="shortcut icon" href="/favicon.svg" />
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="minimum-scale=1, initial-scale=1, width=device-width, user-scalable=no"
|
|
||||||
/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<MantineProvider theme={theme}>{children}</MantineProvider>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
export default function HomePage() {
|
|
||||||
return <div>Home page</div>;
|
|
||||||
}
|
|
19
package.json
19
package.json
@ -1,19 +1,28 @@
|
|||||||
{
|
{
|
||||||
"name": "mantine-minimal-next-template",
|
"name": "portfolio",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"author": {
|
||||||
|
"name": "Refansa",
|
||||||
|
"email": "m.refansa.am@gmail.com",
|
||||||
|
"url": "https://github.com/Refansa"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint",
|
||||||
|
"prettier": "prettier . \"./**/*.{js,jsx,ts,tsx}\" --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/core": "7.1.3",
|
"@mantine/core": "7.1.3",
|
||||||
"@mantine/hooks": "7.1.3",
|
"@mantine/hooks": "7.1.3",
|
||||||
|
"@tabler/icons-react": "^2.39.0",
|
||||||
|
"framer-motion": "^10.16.4",
|
||||||
"next": "13.4.4",
|
"next": "13.4.4",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0"
|
"react-dom": "18.2.0",
|
||||||
|
"react-intersection-observer": "^9.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "20.2.5",
|
"@types/node": "20.2.5",
|
||||||
@ -21,9 +30,11 @@
|
|||||||
"@types/react-dom": "18.2.4",
|
"@types/react-dom": "18.2.4",
|
||||||
"eslint": "8.41.0",
|
"eslint": "8.41.0",
|
||||||
"eslint-config-next": "13.4.4",
|
"eslint-config-next": "13.4.4",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"postcss": "^8.4.24",
|
"postcss": "^8.4.24",
|
||||||
"postcss-preset-mantine": "1.8.0",
|
"postcss-preset-mantine": "1.8.0",
|
||||||
"postcss-simple-vars": "^7.0.1",
|
"postcss-simple-vars": "^7.0.1",
|
||||||
|
"prettier": "3.0.3",
|
||||||
"typescript": "5.0.4"
|
"typescript": "5.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
2656
pnpm-lock.yaml
Normal file
2656
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
1
public/assets/hand-coding.svg
Normal file
1
public/assets/hand-coding.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 56 KiB |
4
public/grid.svg
Normal file
4
public/grid.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg id="download" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
|
||||||
|
<path id="Path_3" data-name="Path 3" d="M96,95h4v1H96v4H95V96H86v4H85V96H76v4H75V96H66v4H65V96H56v4H55V96H46v4H45V96H36v4H35V96H26v4H25V96H16v4H15V96H0V95H15V86H0V85H15V76H0V75H15V66H0V65H15V56H0V55H15V46H0V45H15V36H0V35H15V26H0V25H15V16H0V15H15V0h1V15h9V0h1V15h9V0h1V15h9V0h1V15h9V0h1V15h9V0h1V15h9V0h1V15h9V0h1V15h9V0h1V15h4v1H96v9h4v1H96v9h4v1H96v9h4v1H96v9h4v1H96v9h4v1H96v9h4v1H96v9h4v1H96Zm-1,0V86H86v9ZM85,95V86H76v9ZM75,95V86H66v9ZM65,95V86H56v9ZM55,95V86H46v9ZM45,95V86H36v9ZM35,95V86H26v9ZM25,95V86H16v9ZM16,85h9V76H16Zm10,0h9V76H26Zm10,0h9V76H36Zm10,0h9V76H46Zm10,0h9V76H56Zm10,0h9V76H66Zm10,0h9V76H76Zm10,0h9V76H86Zm9-10V66H86v9ZM85,75V66H76v9ZM75,75V66H66v9ZM65,75V66H56v9ZM55,75V66H46v9ZM45,75V66H36v9ZM35,75V66H26v9ZM25,75V66H16v9ZM16,65h9V56H16Zm10,0h9V56H26Zm10,0h9V56H36Zm10,0h9V56H46Zm10,0h9V56H56Zm10,0h9V56H66Zm10,0h9V56H76Zm10,0h9V56H86Zm9-10V46H86v9ZM85,55V46H76v9ZM75,55V46H66v9ZM65,55V46H56v9ZM55,55V46H46v9ZM45,55V46H36v9ZM35,55V46H26v9ZM25,55V46H16v9ZM16,45h9V36H16Zm10,0h9V36H26Zm10,0h9V36H36Zm10,0h9V36H46Zm10,0h9V36H56Zm10,0h9V36H66Zm10,0h9V36H76Zm10,0h9V36H86Zm9-10V26H86v9ZM85,35V26H76v9ZM75,35V26H66v9ZM65,35V26H56v9ZM55,35V26H46v9ZM45,35V26H36v9ZM35,35V26H26v9ZM25,35V26H16v9ZM16,25h9V16H16Zm10,0h9V16H26Zm10,0h9V16H36Zm10,0h9V16H46Zm10,0h9V16H56Zm10,0h9V16H66Zm10,0h9V16H76Zm10,0h9V16H86Z" fill="rgba(255,255,255,0.1)" fill-rule="evenodd" opacity="0.5"/>
|
||||||
|
<path id="Path_4" data-name="Path 4" d="M6,5V0H5V5H0V6H5v94H6V6h94V5Z" fill="rgba(255,255,255,0.1)" fill-rule="evenodd"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
12
src/app/global.css
Normal file
12
src/app/global.css
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Offset the anchor hashtag link
|
||||||
|
*/
|
||||||
|
:target::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
height: 144px;
|
||||||
|
margin-top: -144px;
|
||||||
|
}
|
67
src/app/layout.tsx
Normal file
67
src/app/layout.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { MantineProvider, ColorSchemeScript } from '@mantine/core'
|
||||||
|
import { Metadata } from 'next'
|
||||||
|
|
||||||
|
import './global.css'
|
||||||
|
import '@mantine/core/styles.css'
|
||||||
|
import { resolver, theme } from '../styles/theme'
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
authors: [
|
||||||
|
{
|
||||||
|
name: 'Refansa',
|
||||||
|
url: 'https://github.com/Refansa',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title: 'Refansa - Software Developer',
|
||||||
|
description: "Refansa's portfolio website",
|
||||||
|
viewport: 'width=device-width, initial-scale=1',
|
||||||
|
colorScheme: 'dark',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<html lang={process.env.APP_LANG ?? 'en'}>
|
||||||
|
<head>
|
||||||
|
<link
|
||||||
|
rel='preconnect'
|
||||||
|
href='https://fonts.googleapis.com'
|
||||||
|
crossOrigin='anonymous'
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel='preconnect'
|
||||||
|
href='https://fonts.gstatic.com'
|
||||||
|
crossOrigin='anonymous'
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href='https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&display=swap'
|
||||||
|
rel='preload'
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href='https://fonts.googleapis.com/css2?family=Gabarito:wght@400;500;600;700;800;900&display=swap'
|
||||||
|
rel='preload'
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href='https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'
|
||||||
|
rel='preload'
|
||||||
|
/>
|
||||||
|
<ColorSchemeScript
|
||||||
|
defaultColorScheme={'dark'}
|
||||||
|
forceColorScheme={'dark'}
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<MantineProvider
|
||||||
|
defaultColorScheme={'dark'}
|
||||||
|
forceColorScheme={'dark'}
|
||||||
|
cssVariablesResolver={resolver}
|
||||||
|
theme={theme}>
|
||||||
|
{children}
|
||||||
|
</MantineProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
33
src/app/page.tsx
Normal file
33
src/app/page.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { AppShell, Container, rem } from '@mantine/core'
|
||||||
|
import Introduction from '../partials/Introduction'
|
||||||
|
import About from '../partials/About'
|
||||||
|
import Navbar from '../components/Navbar'
|
||||||
|
import Footer from '../components/Footer'
|
||||||
|
import SlideUpWhenVisible from '../hooks/slideUpWhenVisible'
|
||||||
|
|
||||||
|
export default function HomePage() {
|
||||||
|
return (
|
||||||
|
<AppShell
|
||||||
|
withBorder={false}
|
||||||
|
padding={'xl'}
|
||||||
|
header={{ height: 72 }}
|
||||||
|
className={'shell-root'}>
|
||||||
|
<AppShell.Header>
|
||||||
|
<Navbar />
|
||||||
|
</AppShell.Header>
|
||||||
|
<AppShell.Main>
|
||||||
|
<SlideUpWhenVisible>
|
||||||
|
<Container
|
||||||
|
size={rem(1920)}
|
||||||
|
px={{ base: '0rem', lg: '4rem', xl: '8rem' }}>
|
||||||
|
<Introduction />
|
||||||
|
<About />
|
||||||
|
<Footer />
|
||||||
|
</Container>
|
||||||
|
</SlideUpWhenVisible>
|
||||||
|
</AppShell.Main>
|
||||||
|
</AppShell>
|
||||||
|
)
|
||||||
|
}
|
18
src/components/Footer.tsx
Normal file
18
src/components/Footer.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Anchor, Container, Text } from '@mantine/core'
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Text
|
||||||
|
fw='bold'
|
||||||
|
ta='center'>
|
||||||
|
Created with ❤️ by{' '}
|
||||||
|
<Anchor
|
||||||
|
fw='bold'
|
||||||
|
href='https://github.com/Refansa'>
|
||||||
|
Refansa
|
||||||
|
</Anchor>
|
||||||
|
</Text>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
3
src/components/NavIcon.module.css
Normal file
3
src/components/NavIcon.module.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.bracket {
|
||||||
|
color: var(--mantine-color-dark-4);
|
||||||
|
}
|
21
src/components/NavIcon.tsx
Normal file
21
src/components/NavIcon.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Anchor, Title } from '@mantine/core'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
import classes from './NavIcon.module.css'
|
||||||
|
|
||||||
|
function NavIcon() {
|
||||||
|
return (
|
||||||
|
<Anchor
|
||||||
|
component={Link}
|
||||||
|
href='/'
|
||||||
|
underline='never'>
|
||||||
|
<Title ff='Fira Code'>
|
||||||
|
<span className={classes.bracket}>{'{'}</span>
|
||||||
|
<span>R</span>
|
||||||
|
<span className={classes.bracket}>{'}'}</span>
|
||||||
|
</Title>
|
||||||
|
</Anchor>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NavIcon
|
20
src/components/NavLink.tsx
Normal file
20
src/components/NavLink.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Button } from '@mantine/core'
|
||||||
|
|
||||||
|
interface NavLinkProps {
|
||||||
|
content: string
|
||||||
|
href: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function NavLink({ content, href }: NavLinkProps) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
component={'a'}
|
||||||
|
variant='subtle'
|
||||||
|
size='md'
|
||||||
|
href={href}>
|
||||||
|
{content}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NavLink
|
8
src/components/Navbar.module.css
Normal file
8
src/components/Navbar.module.css
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.navigation {
|
||||||
|
display: flex;
|
||||||
|
padding-inline: 3vw;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 100%;
|
||||||
|
border-bottom: 1px solid var(--mantine-color-dark-7);
|
||||||
|
}
|
27
src/components/Navbar.tsx
Normal file
27
src/components/Navbar.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Box, Group } from '@mantine/core'
|
||||||
|
|
||||||
|
import classes from './Navbar.module.css'
|
||||||
|
import NavIcon from './NavIcon'
|
||||||
|
import NavLink from './NavLink'
|
||||||
|
|
||||||
|
function Navbar() {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
component='nav'
|
||||||
|
className={classes.navigation}>
|
||||||
|
<NavIcon />
|
||||||
|
<Group visibleFrom='sm'>
|
||||||
|
<NavLink
|
||||||
|
content='About'
|
||||||
|
href='/#about'
|
||||||
|
/>
|
||||||
|
<NavLink
|
||||||
|
content='Projects'
|
||||||
|
href='https://github.com/Refansa?tab=repositories'
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Navbar
|
33
src/hooks/slideUpWhenVisible.tsx
Normal file
33
src/hooks/slideUpWhenVisible.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { motion, useAnimation } from 'framer-motion'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useInView } from 'react-intersection-observer'
|
||||||
|
|
||||||
|
export default function SlideUpWhenVisible({
|
||||||
|
children,
|
||||||
|
threshold,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
threshold?: number | number[]
|
||||||
|
}) {
|
||||||
|
const controls = useAnimation()
|
||||||
|
const [ref, inView] = useInView({ threshold: threshold ? threshold : 0.35 })
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (inView) {
|
||||||
|
controls.start('visible')
|
||||||
|
}
|
||||||
|
}, [controls, inView])
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
ref={ref}
|
||||||
|
animate={controls}
|
||||||
|
initial='hidden'
|
||||||
|
transition={{ duration: 0.4 }}
|
||||||
|
variants={{
|
||||||
|
visible: { opacity: 1, y: 0 },
|
||||||
|
hidden: { opacity: 0, y: 20 },
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
)
|
||||||
|
}
|
3
src/partials/About.module.css
Normal file
3
src/partials/About.module.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.grayscale {
|
||||||
|
filter: grayscale(1);
|
||||||
|
}
|
76
src/partials/About.tsx
Normal file
76
src/partials/About.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import {
|
||||||
|
Anchor,
|
||||||
|
Box,
|
||||||
|
Container,
|
||||||
|
Image,
|
||||||
|
SimpleGrid,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
} from '@mantine/core'
|
||||||
|
import classes from './About.module.css'
|
||||||
|
|
||||||
|
function About() {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
id='about'
|
||||||
|
mt='xl'
|
||||||
|
mah='100vh'
|
||||||
|
h='100vh'>
|
||||||
|
<SimpleGrid
|
||||||
|
cols={{ base: 1, sm: 2 }}
|
||||||
|
spacing={{ base: 'sm', md: 'md', lg: 'lg' }}>
|
||||||
|
<Stack>
|
||||||
|
<Title>A bit About Me</Title>
|
||||||
|
<Stack gap='md'>
|
||||||
|
<Text>
|
||||||
|
Hello 👋 Refansa here. Muhammad Refansa Ali Muzky is my full name.
|
||||||
|
I'm an 18 years old software developer, just came fresh out of the
|
||||||
|
oven. Constantly seeking new open source projects to contribute to
|
||||||
|
and enjoy working with others to make a meaningful contribution.
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
I thrive on challenging coding projects and love nothing more than
|
||||||
|
diving into complex software development tasks. As a lover of open
|
||||||
|
source, I believe that sharing knowledge and collaborating on
|
||||||
|
projects is essential for the advancement of the tech industry.
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
mt='lg'
|
||||||
|
c='dark.1'
|
||||||
|
fs='italic'>
|
||||||
|
I use Archlinux btw.
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
mt='xl'
|
||||||
|
fw='bold'>
|
||||||
|
Peace from Indonesia 🇮🇩
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
<Container visibleFrom='sm'>
|
||||||
|
<Stack
|
||||||
|
justify='center'
|
||||||
|
w={360}>
|
||||||
|
<Image
|
||||||
|
className={classes.grayscale}
|
||||||
|
src='/assets/hand-coding.svg'
|
||||||
|
alt='Coding Illustration'
|
||||||
|
width={360}
|
||||||
|
height='auto'
|
||||||
|
/>
|
||||||
|
<Anchor
|
||||||
|
ta='center'
|
||||||
|
fs='italic'
|
||||||
|
fz='xs'
|
||||||
|
href='https://storyset.com/work'>
|
||||||
|
Work illustrations by Storyset
|
||||||
|
</Anchor>
|
||||||
|
</Stack>
|
||||||
|
</Container>
|
||||||
|
</SimpleGrid>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default About
|
20
src/partials/Introduction.module.css
Normal file
20
src/partials/Introduction.module.css
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
.background {
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
background-image: url('/grid.svg');
|
||||||
|
background-repeat: repeat;
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
opacity: 0.4;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 4rem;
|
||||||
|
height: 0.125rem;
|
||||||
|
background-color: var(--mantine-color-coffee-8);
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
66
src/partials/Introduction.tsx
Normal file
66
src/partials/Introduction.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { Box, Button, Flex, Group, Text, Title } from '@mantine/core'
|
||||||
|
import { IconBrandGithub, IconMail } from '@tabler/icons-react'
|
||||||
|
import classes from './Introduction.module.css'
|
||||||
|
|
||||||
|
function Introduction() {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
mt='xl'
|
||||||
|
mah='100vh'
|
||||||
|
h='80vh'
|
||||||
|
className={classes.background}>
|
||||||
|
<Flex
|
||||||
|
align='center'
|
||||||
|
gap='md'>
|
||||||
|
<Box className={classes.line} />
|
||||||
|
<Text
|
||||||
|
fz='display-lg'
|
||||||
|
fw='bold'
|
||||||
|
c='coffee.6'>
|
||||||
|
Hi There!
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
<Title
|
||||||
|
variant='shadow'
|
||||||
|
fz='display-xl'
|
||||||
|
fw='bold'
|
||||||
|
lh={1.2}>
|
||||||
|
I'm Refansa.
|
||||||
|
</Title>
|
||||||
|
<Text
|
||||||
|
fz='display-lg'
|
||||||
|
fw='bold'
|
||||||
|
lh='xs'>
|
||||||
|
A Passionate, self-taught Software Developer{' '}
|
||||||
|
<Text
|
||||||
|
component='span'
|
||||||
|
display='block'
|
||||||
|
inherit
|
||||||
|
c='dark.1'>
|
||||||
|
A supporter of Open Source Software.
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Group mt={'xl'}>
|
||||||
|
<Button
|
||||||
|
component='a'
|
||||||
|
href='https://github.com/Refansa'
|
||||||
|
target='_blank'
|
||||||
|
leftSection={<IconBrandGithub />}
|
||||||
|
size='lg'
|
||||||
|
variant='light'>
|
||||||
|
Github
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
component='a'
|
||||||
|
href='mailto:m.refansa.am@gmail.com'
|
||||||
|
leftSection={<IconMail />}
|
||||||
|
size='lg'
|
||||||
|
variant='light'>
|
||||||
|
Email
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Introduction
|
9
src/styles/extends/Button.module.css
Normal file
9
src/styles/extends/Button.module.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.button {
|
||||||
|
transition: all linear 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
&:active {
|
||||||
|
transform: scale(calc(0.95 * var(--mantine-scale)));
|
||||||
|
}
|
||||||
|
}
|
8
src/styles/extends/Button.ts
Normal file
8
src/styles/extends/Button.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Button as Component } from '@mantine/core'
|
||||||
|
import classes from './Button.module.css'
|
||||||
|
|
||||||
|
export const Button = Component.extend({
|
||||||
|
defaultProps: {
|
||||||
|
className: classes.button,
|
||||||
|
},
|
||||||
|
})
|
13
src/styles/extends/Text.module.css
Normal file
13
src/styles/extends/Text.module.css
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.text {
|
||||||
|
@mixin dark {
|
||||||
|
&[data-variant='curvy'] {
|
||||||
|
text-decoration-style: wavy;
|
||||||
|
text-decoration-color: var(--mantine-color-coffee-4);
|
||||||
|
text-decoration-line: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-variant='shadow'] {
|
||||||
|
text-shadow: 0.25rem 0.25rem var(--mantine-color-dark-4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
src/styles/extends/Text.ts
Normal file
9
src/styles/extends/Text.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Text as Component } from '@mantine/core'
|
||||||
|
|
||||||
|
import classes from './Text.module.css'
|
||||||
|
|
||||||
|
export const Text = Component.extend({
|
||||||
|
defaultProps: {
|
||||||
|
className: classes.text,
|
||||||
|
},
|
||||||
|
})
|
5
src/styles/extends/TextInput.module.css
Normal file
5
src/styles/extends/TextInput.module.css
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.input {
|
||||||
|
@mixin dark {
|
||||||
|
background-color: var(--mantine-color-dark-6);
|
||||||
|
}
|
||||||
|
}
|
11
src/styles/extends/TextInput.ts
Normal file
11
src/styles/extends/TextInput.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { TextInput as Component } from '@mantine/core'
|
||||||
|
|
||||||
|
import classes from './TextInput.module.css'
|
||||||
|
|
||||||
|
export const TextInput = Component.extend({
|
||||||
|
defaultProps: {
|
||||||
|
classNames: {
|
||||||
|
input: classes.input,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
5
src/styles/extends/Textarea.module.css
Normal file
5
src/styles/extends/Textarea.module.css
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.input {
|
||||||
|
@mixin dark {
|
||||||
|
background-color: var(--mantine-color-dark-6);
|
||||||
|
}
|
||||||
|
}
|
11
src/styles/extends/Textarea.ts
Normal file
11
src/styles/extends/Textarea.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Textarea as Component } from '@mantine/core'
|
||||||
|
|
||||||
|
import classes from './Textarea.module.css'
|
||||||
|
|
||||||
|
export const Textarea = Component.extend({
|
||||||
|
defaultProps: {
|
||||||
|
classNames: {
|
||||||
|
input: classes.input,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
13
src/styles/extends/Title.module.css
Normal file
13
src/styles/extends/Title.module.css
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.title {
|
||||||
|
@mixin dark {
|
||||||
|
&[data-variant='curvy'] {
|
||||||
|
text-decoration-style: wavy;
|
||||||
|
text-decoration-color: var(--mantine-color-coffee-4);
|
||||||
|
text-decoration-line: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-variant='shadow'] {
|
||||||
|
text-shadow: 0.25rem 0.25rem var(--mantine-color-dark-4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
src/styles/extends/Title.ts
Normal file
9
src/styles/extends/Title.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Title as Component } from '@mantine/core'
|
||||||
|
|
||||||
|
import classes from './Title.module.css'
|
||||||
|
|
||||||
|
export const Title = Component.extend({
|
||||||
|
defaultProps: {
|
||||||
|
className: classes.title,
|
||||||
|
},
|
||||||
|
})
|
5
src/styles/extends/index.ts
Normal file
5
src/styles/extends/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export { Button } from './Button'
|
||||||
|
export { Text } from './Text'
|
||||||
|
export { Textarea } from './Textarea'
|
||||||
|
export { TextInput } from './TextInput'
|
||||||
|
export { Title } from './Title'
|
168
src/styles/theme.ts
Normal file
168
src/styles/theme.ts
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import {
|
||||||
|
CSSVariablesResolver,
|
||||||
|
MantineColorsTuple,
|
||||||
|
createTheme,
|
||||||
|
} from '@mantine/core'
|
||||||
|
import { fluidDisplay, fluidTypography } from '../utils/typography'
|
||||||
|
import { Button, Text, TextInput, Textarea, Title } from './extends'
|
||||||
|
|
||||||
|
const coffee: MantineColorsTuple = [
|
||||||
|
'#fff4e5',
|
||||||
|
'#f4e7d8',
|
||||||
|
'#e5cfb5',
|
||||||
|
'#d4b590',
|
||||||
|
'#c59e6f',
|
||||||
|
'#bd905a',
|
||||||
|
'#ba894f',
|
||||||
|
'#a3763e',
|
||||||
|
'#926835',
|
||||||
|
'#805928',
|
||||||
|
]
|
||||||
|
|
||||||
|
export const theme = createTheme({
|
||||||
|
// Controls focus ring styles. Supports the following options:
|
||||||
|
// - `auto` – focus ring is displayed only when the user navigates with keyboard (default value)
|
||||||
|
// - `always` – focus ring is displayed when the user navigates with keyboard and mouse
|
||||||
|
// - `never` – focus ring is always hidden (not recommended)
|
||||||
|
//
|
||||||
|
focusRing: 'auto',
|
||||||
|
|
||||||
|
// rem units scale, change if you customize font-size of `<html />` element
|
||||||
|
// default value is `1` (for `100%`/`16px` font-size on `<html />`)
|
||||||
|
//
|
||||||
|
scale: 1,
|
||||||
|
|
||||||
|
// Determines whether `font-smoothing` property should be set on the body, `true` by default
|
||||||
|
fontSmoothing: true,
|
||||||
|
|
||||||
|
// White color
|
||||||
|
white: '#ffffff',
|
||||||
|
|
||||||
|
// Black color
|
||||||
|
black: '#000000',
|
||||||
|
|
||||||
|
// Object of colors, key is color name, value is an array of at least 10 strings (colors)
|
||||||
|
colors: {
|
||||||
|
coffee,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Index of theme.colors[color].
|
||||||
|
// Primary shade is used in all components to determine which color from theme.colors[color] should be used.
|
||||||
|
// Can be either a number (0–9) or an object to specify different color shades for light and dark color schemes.
|
||||||
|
// Default value `{ light: 6, dark: 8 }`
|
||||||
|
//
|
||||||
|
// For example,
|
||||||
|
// { primaryShade: 6 } // shade 6 is used both for dark and light color schemes
|
||||||
|
// { primaryShade: { light: 6, dark: 7 } } // different shades for dark and light color schemes
|
||||||
|
//
|
||||||
|
primaryShade: { light: 4, dark: 8 },
|
||||||
|
|
||||||
|
// Key of `theme.colors`, hex/rgb/hsl values are not supported.
|
||||||
|
// Determines which color will be used in all components by default.
|
||||||
|
// Default value – `blue`.
|
||||||
|
//
|
||||||
|
primaryColor: 'coffee',
|
||||||
|
|
||||||
|
// Function to resolve colors based on variant.
|
||||||
|
// Can be used to deeply customize how colors are applied to `Button`, `ActionIcon`, `ThemeIcon`
|
||||||
|
// and other components that use colors from theme.
|
||||||
|
//
|
||||||
|
// variantColorResolver: VariantColorsResolver;
|
||||||
|
|
||||||
|
// font-family used in all components, system fonts by default
|
||||||
|
fontFamily:
|
||||||
|
'Poppins, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji',
|
||||||
|
|
||||||
|
// Monospace font-family, used in code and other similar components, system fonts by default
|
||||||
|
fontFamilyMonospace:
|
||||||
|
'Fira Code, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace',
|
||||||
|
|
||||||
|
// Controls various styles of h1-h6 elements, used in TypographyStylesProvider and Title components
|
||||||
|
headings: {
|
||||||
|
fontFamily:
|
||||||
|
'Gabarito, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Object of values that are used to set `border-radius` in all components that support it
|
||||||
|
// radius: MantineRadiusValues;
|
||||||
|
|
||||||
|
// Key of `theme.radius` or any valid CSS value. Default `border-radius` used by most components
|
||||||
|
defaultRadius: 0,
|
||||||
|
|
||||||
|
// Object of values that are used to set various CSS properties that control spacing between elements
|
||||||
|
// spacing: MantineSpacingValues;
|
||||||
|
|
||||||
|
// Object of values that are used to control `font-size` property in all components
|
||||||
|
fontSizes: {
|
||||||
|
'fluid-xs': fluidTypography(8, 12, 480, 1440),
|
||||||
|
'fluid-sm': fluidTypography(10, 14, 480, 1440),
|
||||||
|
'fluid-md': fluidTypography(12, 16, 480, 1440),
|
||||||
|
'fluid-lg': fluidTypography(14, 18, 480, 1440),
|
||||||
|
'fluid-xl': fluidTypography(16, 20, 480, 1440),
|
||||||
|
'display-xl': fluidDisplay(80, 144, 768, 1920),
|
||||||
|
'display-lg': fluidDisplay(24, 36, 768, 1920),
|
||||||
|
'display-md': fluidDisplay(16, 24, 768, 1920),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Object of values that are used to control `line-height` property in `Text` component
|
||||||
|
// lineHeights: MantineLineHeightValues;
|
||||||
|
|
||||||
|
// Object of values that are used to control breakpoints in all components,
|
||||||
|
// values are expected to be defined in em
|
||||||
|
//
|
||||||
|
// breakpoints: MantineBreakpointsValues;
|
||||||
|
|
||||||
|
// Object of values that are used to add `box-shadow` styles to components that support `shadow` prop
|
||||||
|
// shadows: MantineShadowsValues;
|
||||||
|
|
||||||
|
// Determines whether user OS settings to reduce motion should be respected, `false` by default
|
||||||
|
// respectReducedMotion: boolean;
|
||||||
|
|
||||||
|
// Determines which cursor type will be used for interactive elements
|
||||||
|
// - `default` – cursor that is used by native HTML elements, for example, `input[type="checkbox"]` has `cursor: default` styles
|
||||||
|
// - `pointer` – sets `cursor: pointer` on interactive elements that do not have these styles by default
|
||||||
|
//
|
||||||
|
cursorType: 'pointer',
|
||||||
|
|
||||||
|
// Default gradient configuration for components that support `variant="gradient"`
|
||||||
|
// defaultGradient: MantineGradient;
|
||||||
|
|
||||||
|
// Class added to the elements that have active styles, for example, `Button` and `ActionIcon`
|
||||||
|
// activeClassName: string;
|
||||||
|
|
||||||
|
// Class added to the elements that have focus styles, for example, `Button` or `ActionIcon`.
|
||||||
|
// Overrides `theme.focusRing` property.
|
||||||
|
//
|
||||||
|
// focusClassName: string;
|
||||||
|
|
||||||
|
// Allows adding `classNames`, `styles` and `defaultProps` to any component
|
||||||
|
components: {
|
||||||
|
Button,
|
||||||
|
Text,
|
||||||
|
Textarea,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Any other properties that you want to access with the theme objects
|
||||||
|
other: {
|
||||||
|
bodyLight: '#ffffff',
|
||||||
|
textLight: '#000000',
|
||||||
|
bodyDark: '#000000',
|
||||||
|
textDark: '#ffffff',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const resolver: CSSVariablesResolver = (t) => ({
|
||||||
|
variables: {},
|
||||||
|
light: {
|
||||||
|
'--mantine-color-body': t.other.bodyLight,
|
||||||
|
'--mantine-color-text': t.other.textLight,
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
'--mantine-color-body': t.other.bodyDark,
|
||||||
|
'--mantine-color-text': t.other.textDark,
|
||||||
|
},
|
||||||
|
})
|
58
src/utils/typography.ts
Normal file
58
src/utils/typography.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
export const PIXEL_PER_REM = 16
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a number from pixel to rem.
|
||||||
|
*
|
||||||
|
* @param value The value in pixel.
|
||||||
|
*/
|
||||||
|
export const rem = (value: number): number => {
|
||||||
|
return value / PIXEL_PER_REM
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string of calculated value used for font size in display typography.
|
||||||
|
*
|
||||||
|
* Formula based from https://github.com/abdulrcs/abdulrahman.id/blob/main/styles/theme.js
|
||||||
|
*
|
||||||
|
* @param minFont Minimal font size in pixel.
|
||||||
|
* @param maxFont Maximum font size in pixel.
|
||||||
|
* @param minVW Minimum font size in pixel.
|
||||||
|
* @param maxVW Maximum font size in pixel.
|
||||||
|
*/
|
||||||
|
export const fluidDisplay = (
|
||||||
|
minFont: number,
|
||||||
|
maxFont: number,
|
||||||
|
minVW: number,
|
||||||
|
maxVW: number,
|
||||||
|
) => {
|
||||||
|
let XX = minVW / 100
|
||||||
|
let YY = (100 * (maxFont - minFont)) / (maxVW - minVW)
|
||||||
|
let ZZ = rem(minFont)
|
||||||
|
return `calc(${ZZ}rem + ((1vw - ${XX}px) * ${YY}))`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string of clamp value used for font size in normal typography.
|
||||||
|
*
|
||||||
|
* Formula based from https://www.aleksandrhovhannisyan.com/blog/fluid-type-scale-with-css-clamp/
|
||||||
|
*
|
||||||
|
* @param minFont Minimal font size in pixel.
|
||||||
|
* @param maxFont Maximum font size in pixel.
|
||||||
|
* @param minVW Minimum font size in pixel.
|
||||||
|
* @param maxVW Maximum font size in pixel.
|
||||||
|
*/
|
||||||
|
export const fluidTypography = (
|
||||||
|
minFont: number,
|
||||||
|
maxFont: number,
|
||||||
|
minVW: number,
|
||||||
|
maxVW: number,
|
||||||
|
): string => {
|
||||||
|
const slope = (maxFont - minFont) / (maxVW - minVW)
|
||||||
|
const intercept = rem(minFont - slope * minVW)
|
||||||
|
const slopeVW = slope * 100
|
||||||
|
|
||||||
|
const minSize = rem(minFont)
|
||||||
|
const maxSize = rem(maxFont)
|
||||||
|
|
||||||
|
return `clamp(${minSize}rem, ${slopeVW}vw + ${intercept}rem, ${maxSize}rem)`
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user