Compare commits

...

60 Commits
v2 ... master

Author SHA1 Message Date
b41404e86b
use astro collections 2024-08-28 00:39:17 -04:00
f806365baa
many changes :3 2024-08-28 00:14:30 -04:00
4ce242e87e
remove automated carousel 2023-11-01 14:15:58 -04:00
60a5577a6b
feat: carousel rotation 2023-11-01 12:39:33 -04:00
01da52e855
a11y improvements 2023-11-01 12:38:39 -04:00
3eb42a820e
feat: use smaller album art image 2023-11-01 12:32:56 -04:00
489060ecd5
im so smart 2023-11-01 12:30:33 -04:00
85ce43cc4c
fix image optimization 2023-11-01 12:28:15 -04:00
44b1a581cb
fix: mobile responsiveness 2023-11-01 12:21:38 -04:00
0417af9e96
feat: add image optimization 2023-11-01 12:20:22 -04:00
32590c0d13
fix: update resume 2023-10-31 23:20:33 -04:00
356b92233c
wheeee i love vercel 2023-10-31 22:47:51 -04:00
383d1b7c87
delete vercel folder 2023-10-31 22:36:01 -04:00
a94da9a450
fix vercel stuff 2023-10-31 22:29:01 -04:00
58c20d64f1
fix favicon 2023-10-31 22:11:17 -04:00
c7e179e270
add logo and favicon 2023-10-31 19:45:52 -04:00
7a9cc80977
optimize drone images 2023-10-31 19:41:29 -04:00
ac2724951f
update gitignore and readme 2023-10-31 19:37:52 -04:00
0eb1df0bf9
Merge branch 'master' into yet-another-revamp 2023-10-31 19:35:09 -04:00
29a314d1a2
revamp 2023-10-31 19:22:25 -04:00
224ff2a6b4
fix: cors stuff for matrix 2023-07-19 21:29:39 -05:00
12cd9f4fe6
fix: remove redirect, use proxied data 2023-07-19 21:26:14 -05:00
d142db7fe4
feat: add matrix redirects 2023-07-19 21:09:21 -05:00
47bd1899ee
feat: add resume 2023-06-28 18:41:05 -05:00
8dbcb6494b
fix: image issues 2023-06-21 15:09:37 -07:00
261fa687ea
fix OG images again 2023-06-21 13:03:38 -07:00
f081435b17
i swear this was copilots fault not mine 2023-06-21 12:07:31 -07:00
bb780b414a
fix: padding on blog post 2023-06-21 12:05:43 -07:00
5f7ff6be0a
fix: opengraph images 2023-06-21 12:03:32 -07:00
f6fbd90a17
feat: add metadata to posts and projects 2023-06-21 12:00:12 -07:00
2f0ae31852
fix mobile responsiveness 2023-06-21 11:51:28 -07:00
75d9c82390
fix: add vercel analytics 2023-06-14 23:11:35 -05:00
9a8d072064
add favicon 2023-06-14 23:05:56 -05:00
7e2aa13b26
Merge branch 'master' into v2 2023-06-14 22:55:42 -05:00
cee941525f cleanup 2022-05-08 21:45:14 -05:00
Jack Merrill
102456d7eb
Update _document.tsx 2022-05-08 21:39:24 -05:00
Jack Merrill
ec9546c49c
Create _document.tsx 2022-05-08 21:38:01 -05:00
Jack Merrill
251e37f104
Merge pull request #1 from jackmerrill/devel/redesign-tailwind-ui
Redesign with TailwindUI
2021-10-04 16:41:46 -07:00
Jack Merrill
122753474a fix: typeerrors 2021-10-04 18:38:50 -05:00
Jack Merrill
3afea9b415 added new logo 2021-10-04 18:35:20 -05:00
Jack Merrill
58b5c15fe9 i dont know what i added 2021-10-04 22:13:53 +00:00
Jack Merrill
d374d19a8a feat: more projects 2021-09-10 17:12:20 +00:00
Jack Merrill
c6174fab8d feat: a lot of stuff 2021-09-07 17:14:50 +00:00
Jack Merrill
768a16c170 start revision 2021-09-03 17:13:30 +00:00
Jack Merrill
ea98b243c4 fix: update site with new logos and projects 2021-06-08 20:50:52 -05:00
Jack Merrill
c3c3fd4baf feat: add age to website 2021-04-15 13:09:36 -05:00
Jack Merrill
761d34f5ff feat: add more tools to cards 2021-04-15 09:27:16 -05:00
Jack Merrill
9e536660ce fix: performance improvements 2021-04-14 14:24:17 -05:00
Jack Merrill
67dae9bd8f fix: title again 2021-04-14 11:29:36 -05:00
Jack Merrill
b636376a45 fix: no title 2021-04-14 11:20:28 -05:00
Jack Merrill
62b30a0335 feat: favicon 2021-04-14 11:15:10 -05:00
Jack Merrill
17ccb97185 feat: SEO! 🎉 2021-04-14 11:06:15 -05:00
Jack Merrill
ad4e6e39c9 feat/fix: better mobile support 2021-04-14 10:49:43 -05:00
Jack Merrill
1222296901 fix: changed button link in header 2021-04-13 21:42:57 -05:00
Jack Merrill
bf603e1e76 🎉 initial release! 2021-04-13 21:39:58 -05:00
Jack Merrill
bae21571e9 fix(again): Fonts issue? 2021-03-31 01:16:28 -05:00
Jack Merrill
9e2e6579eb fix(layout): Fixed Tailwind purging causing no fonts to show 2021-03-31 00:55:01 -05:00
Jack Merrill
d035adcfe9 fix(TypeError): Fix TypeErrors in Navbar and Index 2021-03-31 00:32:53 -05:00
Jack Merrill
33d7608eef fix(README, LICENSE): Update README and License. 2021-03-31 00:30:42 -05:00
Jack Merrill
0eee4e6a07 🎉 Initial Commit! 2021-03-31 00:27:36 -05:00
136 changed files with 5387 additions and 29484 deletions

10
.editorconfig Normal file
View File

@ -0,0 +1,10 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
[*.{js,json,yml}]
charset = utf-8
indent_style = space
indent_size = 2

View File

@ -1,3 +0,0 @@
{
"extends": "next/core-web-vitals"
}

4
.gitattributes vendored Normal file
View File

@ -0,0 +1,4 @@
/.yarn/** linguist-vendored
/.yarn/releases/* binary
/.yarn/plugins/**/* binary
/.pnp.* binary linguist-generated

50
.gitignore vendored
View File

@ -1,44 +1,22 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# build output
dist/
# generated types
.astro/
# dependencies
/node_modules
/.pnp
.pnp.js
node_modules/
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
# logs
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
# sanity
/dist
.sanity/
# env
# environment variables
.env
.env.local
.env.development.local
.env.production
# macOS-specific files
.DS_Store
.vercel

4
.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"tabWidth": 2,
"useTabs": false
}

View File

@ -1 +0,0 @@
/src/app/internal

4
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

11
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

BIN
.yarn/install-state.gz vendored Normal file

Binary file not shown.

1
.yarnrc.yml Normal file
View File

@ -0,0 +1 @@
nodeLinker: node-modules

View File

@ -1,34 +1,3 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
# jackmerrill.com
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
This is my website! Written using Astro (and some React), styled with TailwindCSS, designed with Figma, and deployed with Vercel.

28
astro.config.mjs Normal file
View File

@ -0,0 +1,28 @@
import {
defineConfig,
passthroughImageService,
squooshImageService,
} from "astro/config";
import tailwind from "@astrojs/tailwind";
import react from "@astrojs/react";
import vercel from "@astrojs/vercel/serverless";
// https://astro.build/config
export default defineConfig({
integrations: [tailwind(), react()],
output: "hybrid",
adapter: vercel({
imageService: true,
webAnalytics: {
enabled: true,
},
speedInsights: {
enabled: true,
},
functionPerRoute: true,
}),
// image: {
// service: passthroughImageService(),
// },
});

View File

@ -1,8 +0,0 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: ["cdn.sanity.io"],
},
};
module.exports = nextConfig;

19845
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,58 +1,36 @@
{
"name": "jackmerrill.com",
"version": "0.1.0",
"private": true,
"name": "jackmerrill-com",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@heroicons/react": "^2.0.18",
"@next/bundle-analyzer": "^13.4.5",
"@radix-ui/react-context-menu": "^2.1.3",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.4",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-navigation-menu": "^1.1.2",
"@radix-ui/react-tooltip": "^1.0.6",
"@sanity/image-url": "^1.0.2",
"@sanity/vision": "^3.11.2",
"@tailwindcss/typography": "^0.5.9",
"@types/node": "20.2.1",
"@types/react": "18.2.6",
"@types/react-dom": "18.2.4",
"autoprefixer": "10.4.14",
"class-variance-authority": "^0.6.0",
"clsx": "^1.2.1",
"easymde": "2",
"eslint": "8.41.0",
"eslint-config-next": "13.4.3",
"groqd": "^0.15.6",
"lucide-react": "^0.220.0",
"next": "13.4.3",
"next-sanity": "^4.3.3",
"next-sanity-image": "^6.0.0",
"postcss": "8.4.23",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-markdown": "^8.0.7",
"react-syntax-highlighter": "^15.5.0",
"rehype-raw": "^6.1.1",
"remark-gfm": "^3.0.1",
"sanity": "^3.11.2",
"sanity-plugin-markdown": "^4.1.0",
"styled-components": "^5.3.10",
"tailwind-merge": "^1.12.0",
"tailwindcss": "3.3.2",
"tailwindcss-animate": "^1.0.5",
"tailwindcss-hero-patterns": "^0.1.2",
"twemoji": "^14.0.2",
"typescript": "5.0.4"
"@astrojs/react": "3.6.2",
"@astrojs/tailwind": "5.1.0",
"@astrojs/vercel": "7.7.2",
"@icons-pack/react-simple-icons": "^9.1.0",
"@react-three/drei": "^9.101.0",
"@react-three/fiber": "^8.15.19",
"@react-three/xr": "^5.7.1",
"@tailwindcss/typography": "^0.5.10",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"astro": "4.14.2",
"astro-seo": "^0.8.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"sharp": "^0.33.5",
"squoosh": "^0.0.0",
"tailwindcss": "^3.0.24",
"three": "^0.162.0"
},
"devDependencies": {
"@types/react-syntax-highlighter": "^15.5.7",
"@types/twemoji": "^13.1.2"
"sass": "^1.69.5",
"typescript": "^5.5.4"
}
}

View File

@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

182
public/css/sentient.css Normal file
View File

@ -0,0 +1,182 @@
/**
* @license
*
* Font Family: Sentient
* Designed by: Noopur Choksi
* URL: https://www.fontshare.com/fonts/sentient
* © 2023 Indian Type Foundry
*
* Font Styles:
* Sentient Variable(Variable font)
* Sentient Variable Italic(Variable font)
* Sentient Extralight
* Sentient Extralight Italic
* Sentient Light
* Sentient Light Italic
* Sentient Regular
* Sentient Italic
* Sentient Medium
* Sentient Medium Italic
* Sentient Bold
* Sentient Bold Italic
*
*/
/**
* This is a variable font
* You can controll variable axes as shown below:
* font-variation-settings: 'wght' 700.0 'wght' 400.0;
*
* available axes:
* 'wght' (range from 200.0 to 700.0)
* 'wght' (range from 200.0 to 700.0)
*/
@font-face {
font-family: 'Sentient-Variable';
src: url('../fonts/Sentient-Variable.woff2') format('woff2'),
url('../fonts/Sentient-Variable.woff') format('woff'),
url('../fonts/Sentient-Variable.ttf') format('truetype');
font-weight: 200 700;
font-display: swap;
font-style: normal;
}
/**
* This is a variable font
* You can controll variable axes as shown below:
* font-variation-settings: 'wght' 700.0 'wght' 400.0;
*
* available axes:
* 'wght' (range from 200.0 to 700.0)
* 'wght' (range from 200.0 to 700.0)
*/
@font-face {
font-family: 'Sentient-VariableItalic';
src: url('../fonts/Sentient-VariableItalic.woff2') format('woff2'),
url('../fonts/Sentient-VariableItalic.woff') format('woff'),
url('../fonts/Sentient-VariableItalic.ttf') format('truetype');
font-weight: 200 700;
font-display: swap;
font-style: italic;
}
@font-face {
font-family: 'Sentient-Extralight';
src: url('../fonts/Sentient-Extralight.woff2') format('woff2'),
url('../fonts/Sentient-Extralight.woff') format('woff'),
url('../fonts/Sentient-Extralight.ttf') format('truetype');
font-weight: 200;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Sentient-ExtralightItalic';
src: url('../fonts/Sentient-ExtralightItalic.woff2') format('woff2'),
url('../fonts/Sentient-ExtralightItalic.woff') format('woff'),
url('../fonts/Sentient-ExtralightItalic.ttf') format('truetype');
font-weight: 200;
font-display: swap;
font-style: italic;
}
@font-face {
font-family: 'Sentient-Light';
src: url('../fonts/Sentient-Light.woff2') format('woff2'),
url('../fonts/Sentient-Light.woff') format('woff'),
url('../fonts/Sentient-Light.ttf') format('truetype');
font-weight: 300;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Sentient-LightItalic';
src: url('../fonts/Sentient-LightItalic.woff2') format('woff2'),
url('../fonts/Sentient-LightItalic.woff') format('woff'),
url('../fonts/Sentient-LightItalic.ttf') format('truetype');
font-weight: 300;
font-display: swap;
font-style: italic;
}
@font-face {
font-family: 'Sentient-Regular';
src: url('../fonts/Sentient-Regular.woff2') format('woff2'),
url('../fonts/Sentient-Regular.woff') format('woff'),
url('../fonts/Sentient-Regular.ttf') format('truetype');
font-weight: 400;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Sentient-Italic';
src: url('../fonts/Sentient-Italic.woff2') format('woff2'),
url('../fonts/Sentient-Italic.woff') format('woff'),
url('../fonts/Sentient-Italic.ttf') format('truetype');
font-weight: 400;
font-display: swap;
font-style: italic;
}
@font-face {
font-family: 'Sentient-Medium';
src: url('../fonts/Sentient-Medium.woff2') format('woff2'),
url('../fonts/Sentient-Medium.woff') format('woff'),
url('../fonts/Sentient-Medium.ttf') format('truetype');
font-weight: 500;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Sentient-MediumItalic';
src: url('../fonts/Sentient-MediumItalic.woff2') format('woff2'),
url('../fonts/Sentient-MediumItalic.woff') format('woff'),
url('../fonts/Sentient-MediumItalic.ttf') format('truetype');
font-weight: 500;
font-display: swap;
font-style: italic;
}
@font-face {
font-family: 'Sentient-Bold';
src: url('../fonts/Sentient-Bold.woff2') format('woff2'),
url('../fonts/Sentient-Bold.woff') format('woff'),
url('../fonts/Sentient-Bold.ttf') format('truetype');
font-weight: 700;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Sentient-BoldItalic';
src: url('../fonts/Sentient-BoldItalic.woff2') format('woff2'),
url('../fonts/Sentient-BoldItalic.woff') format('woff'),
url('../fonts/Sentient-BoldItalic.ttf') format('truetype');
font-weight: 700;
font-display: swap;
font-style: italic;
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/resume.pdf Normal file

Binary file not shown.

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

Before

Width:  |  Height:  |  Size: 629 B

View File

@ -1,10 +0,0 @@
/**
* This configuration file lets you run `$ sanity [command]` in this folder
* Go to https://www.sanity.io/docs/cli to learn more.
**/
import { defineCliConfig } from 'sanity/cli'
const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID
const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET
export default defineCliConfig({ api: { projectId, dataset } })

View File

@ -1,27 +0,0 @@
/**
* This configuration is used to for the Sanity Studio thats mounted on the `/app/internal/studio/[[...index]]/page.tsx` route
*/
import { visionTool } from "@sanity/vision";
import { defineConfig } from "sanity";
import { deskTool } from "sanity/desk";
import { markdownSchema } from "sanity-plugin-markdown";
// Go to https://www.sanity.io/docs/api-versioning to learn how API versioning works
import { apiVersion, dataset, projectId } from "./sanity/env";
import { schema } from "./sanity/schema";
export default defineConfig({
basePath: "/internal/studio",
projectId,
dataset,
// Add and edit the content schema in the './sanity/schema' folder
schema,
plugins: [
deskTool(),
// Vision is a tool that lets you query your content with GROQ in the studio
// https://www.sanity.io/docs/the-vision-plugin
visionTool({ defaultApiVersion: apiVersion }),
markdownSchema(),
],
});

View File

@ -1,22 +0,0 @@
export const apiVersion =
process.env.NEXT_PUBLIC_SANITY_API_VERSION || "2023-05-25";
export const dataset = assertValue(
process.env.NEXT_PUBLIC_SANITY_DATASET,
"Missing environment variable: NEXT_PUBLIC_SANITY_DATASET"
);
export const projectId = assertValue(
process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
"Missing environment variable: NEXT_PUBLIC_SANITY_PROJECT_ID"
);
export const useCdn = false;
function assertValue<T>(v: T | undefined, errorMessage: string): T {
if (v === undefined) {
throw new Error(errorMessage);
}
return v;
}

View File

@ -1,10 +0,0 @@
import { createClient } from 'next-sanity'
import { apiVersion, dataset, projectId, useCdn } from '../env'
export const client = createClient({
apiVersion,
dataset,
projectId,
useCdn,
})

View File

@ -1,13 +0,0 @@
import createImageUrlBuilder from '@sanity/image-url'
import type { Image } from 'sanity'
import { dataset, projectId } from '../env'
const imageBuilder = createImageUrlBuilder({
projectId: projectId || '',
dataset: dataset || '',
})
export const urlForImage = (source: Image) => {
return imageBuilder?.image(source).auto('format').fit('max')
}

View File

@ -1,11 +0,0 @@
import { type SchemaTypeDefinition } from "sanity";
import blockContent from "./schemas/blockContent";
import category from "./schemas/category";
import post from "./schemas/post";
import author from "./schemas/author";
import project from "./schemas/project";
export const schema: { types: SchemaTypeDefinition[] } = {
types: [post, author, category, blockContent, project],
};

View File

@ -1,57 +0,0 @@
import { defineField, defineType } from "sanity";
export default defineType({
name: "author",
title: "Author",
type: "document",
fields: [
defineField({
name: "name",
title: "Name",
type: "string",
}),
defineField({
name: "slug",
title: "Slug",
type: "slug",
options: {
source: "name",
maxLength: 96,
},
}),
defineField({
name: "image",
title: "Image",
type: "image",
options: {
hotspot: true,
},
fields: [
{
name: "alt",
type: "string",
title: "Alternative Text",
},
],
}),
defineField({
name: "bio",
title: "Bio",
type: "array",
of: [
{
title: "Block",
type: "block",
styles: [{ title: "Normal", value: "normal" }],
lists: [],
},
],
}),
],
preview: {
select: {
title: "name",
media: "image",
},
},
});

View File

@ -1,75 +0,0 @@
import {defineType, defineArrayMember} from 'sanity'
/**
* This is the schema type for block content used in the post document type
* Importing this type into the studio configuration's `schema` property
* lets you reuse it in other document types with:
* {
* name: 'someName',
* title: 'Some title',
* type: 'blockContent'
* }
*/
export default defineType({
title: 'Block Content',
name: 'blockContent',
type: 'array',
of: [
defineArrayMember({
title: 'Block',
type: 'block',
// Styles let you define what blocks can be marked up as. The default
// set corresponds with HTML tags, but you can set any title or value
// you want, and decide how you want to deal with it where you want to
// use your content.
styles: [
{title: 'Normal', value: 'normal'},
{title: 'H1', value: 'h1'},
{title: 'H2', value: 'h2'},
{title: 'H3', value: 'h3'},
{title: 'H4', value: 'h4'},
{title: 'Quote', value: 'blockquote'},
],
lists: [{title: 'Bullet', value: 'bullet'}],
// Marks let you mark up inline text in the Portable Text Editor
marks: {
// Decorators usually describe a single property e.g. a typographic
// preference or highlighting
decorators: [
{title: 'Strong', value: 'strong'},
{title: 'Emphasis', value: 'em'},
],
// Annotations can be any object structure e.g. a link or a footnote.
annotations: [
{
title: 'URL',
name: 'link',
type: 'object',
fields: [
{
title: 'URL',
name: 'href',
type: 'url',
},
],
},
],
},
}),
// You can add additional types here. Note that you can't use
// primitive types such as 'string' and 'number' in the same array
// as a block type.
defineArrayMember({
type: 'image',
options: {hotspot: true},
fields: [
{
name: 'alt',
type: 'string',
title: 'Alternative Text',
}
]
}),
],
})

View File

@ -1,19 +0,0 @@
import {defineField, defineType} from 'sanity'
export default defineType({
name: 'category',
title: 'Category',
type: 'document',
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string',
}),
defineField({
name: 'description',
title: 'Description',
type: 'text',
}),
],
})

View File

@ -1,80 +0,0 @@
import { defineField, defineType } from "sanity";
export default defineType({
name: "post",
title: "Post",
type: "document",
fields: [
defineField({
name: "title",
title: "Title",
type: "string",
}),
defineField({
name: "subtitle",
title: "Subtitle",
type: "string",
}),
defineField({
name: "slug",
title: "Slug",
type: "slug",
options: {
source: "title",
maxLength: 96,
},
}),
defineField({
name: "author",
title: "Author",
type: "reference",
to: { type: "author" },
}),
defineField({
name: "mainImage",
title: "Main image",
type: "image",
options: {
hotspot: true,
},
fields: [
{
name: "alt",
type: "string",
title: "Alternative Text",
},
],
}),
defineField({
name: "categories",
title: "Categories",
type: "array",
of: [{ type: "reference", to: { type: "category" } }],
}),
defineField({
name: "publishedAt",
title: "Published at",
type: "datetime",
}),
defineField({
name: "content",
title: "Body",
type: "markdown",
options: {
imageUrl: (imageAsset) => `${imageAsset.url}`,
},
}),
],
preview: {
select: {
title: "title",
author: "author.name",
media: "mainImage",
},
prepare(selection) {
const { author } = selection;
return { ...selection, subtitle: author && `by ${author}` };
},
},
});

View File

@ -1,80 +0,0 @@
import { defineField, defineType } from "sanity";
export default defineType({
name: "project",
title: "Project",
type: "document",
fields: [
defineField({
name: "title",
title: "Title",
type: "string",
}),
defineField({
name: "subtitle",
title: "Subtitle",
type: "string",
}),
defineField({
name: "slug",
title: "Slug",
type: "slug",
options: {
source: "title",
maxLength: 96,
},
}),
defineField({
name: "author",
title: "Author",
type: "reference",
to: { type: "author" },
}),
defineField({
name: "mainImage",
title: "Main image",
type: "image",
options: {
hotspot: true,
},
fields: [
{
name: "alt",
type: "string",
title: "Alternative Text",
},
],
}),
defineField({
name: "categories",
title: "Categories",
type: "array",
of: [{ type: "reference", to: { type: "category" } }],
}),
defineField({
name: "publishedAt",
title: "Published at",
type: "datetime",
}),
defineField({
name: "content",
title: "Body",
type: "markdown",
options: {
imageUrl: (imageAsset) => `${imageAsset.url}`,
},
}),
],
preview: {
select: {
title: "title",
author: "author.name",
media: "mainImage",
},
prepare(selection) {
const { author } = selection;
return { ...selection, subtitle: author && `by ${author}` };
},
},
});

View File

@ -1,122 +0,0 @@
"use client";
import React from "react";
import * as Dialog from "@radix-ui/react-dialog";
import { Cross2Icon } from "@radix-ui/react-icons";
import { useRouter } from "next/navigation";
import { q } from "groqd";
import { client } from "../../../../../sanity/lib/client";
import { cn } from "@/lib/utils";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import rehypeRaw from "rehype-raw";
import CodeBlock from "@/components/Codeblock";
import Image from "next/image";
import { useNextSanityImage } from "next-sanity-image";
type BlogPost = {
title: string;
subtitle: string;
slug: string;
publishedAt: Date;
content: string;
};
export default function BlogPostModal({
params: { id: slug },
}: {
params: {
id: string;
};
}) {
const router = useRouter();
const [post, setPost] = React.useState<BlogPost | null>(null);
const handleOpenChange = (open: boolean) => {
if (!open) {
router.back();
}
};
React.useEffect(() => {
async function getPost() {
const { query, schema } = q("*")
.filterByType("post")
.filter(`slug.current == "${slug}"`)
.grab$({
title: q.string(),
subtitle: q.string(),
slug: q.slug("slug"),
publishedAt: q.date(),
content: q.string(),
})
.slice(0, 1);
const post = schema.parse(await client.fetch(query));
setPost(post[0]);
}
getPost();
}, [slug]);
return (
<Dialog.Root open onOpenChange={handleOpenChange}>
<Dialog.Portal>
<Dialog.Overlay className="bg-zinc-900 opacity-75 data-[state=open]:animate-overlayShow fixed inset-0" />
<Dialog.Content className="data-[state=open]:animate-contentShow overflow-y-scroll fixed top-[50%] left-[50%] w-[90vw] max-h-[85vh] max-w-[50vw] translate-x-[-50%] translate-y-[-50%] rounded-[6px] bg-white dark:bg-zinc-800 px-8 py-12 shadow-[hsl(206_22%_7%_/_35%)_0px_10px_38px_-10px,_hsl(206_22%_7%_/_20%)_0px_10px_20px_-15px] focus:outline-none">
<Dialog.Title
className={cn(
"dark:text-white text-indigo-600 m-0 text-6xl font-bold",
!post && "bg-gray-500 animate-pulse block w-52 h-5"
)}
>
{post?.title}
</Dialog.Title>
<Dialog.Description
className={cn(
"dark:text-white text-indigo-500 font-semibold mt-[10px] mb-5 text-2xl leading-normal",
!post && "bg-gray-500 animate-pulse block w-72 h-5"
)}
>
{post?.subtitle}
</Dialog.Description>
<article className="prose dark:prose-invert prose-zinc max-w-none lg:prose-xl">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw]}
components={
{
code: CodeBlock,
img: ({ ...props }) => {
const { width, height } = props.src.match(
/(?<width>\d+)x(?<height>\d+)/
).groups;
return (
<Image
src={props.src}
alt={props.alt}
width={width}
height={height}
/>
);
},
} as any
}
>
{post?.content || ""}
</ReactMarkdown>
</article>
<Dialog.Close asChild>
<button
className="text-violet11 hover:bg-violet4 focus:shadow-violet7 absolute top-12 right-8 inline-flex h-[25px] w-[25px] appearance-none items-center justify-center rounded-full focus:shadow-[0_0_0_2px] focus:outline-none"
aria-label="Close"
>
<Cross2Icon />
</button>
</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}

View File

@ -1,3 +0,0 @@
export default function Default() {
return null;
}

View File

@ -1,122 +0,0 @@
"use client";
import React from "react";
import * as Dialog from "@radix-ui/react-dialog";
import { Cross2Icon } from "@radix-ui/react-icons";
import { useRouter } from "next/navigation";
import { q } from "groqd";
import { client } from "../../../../../sanity/lib/client";
import { cn } from "@/lib/utils";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import rehypeRaw from "rehype-raw";
import CodeBlock from "@/components/Codeblock";
import Image from "next/image";
import { useNextSanityImage } from "next-sanity-image";
type Project = {
title: string;
subtitle: string;
slug: string;
publishedAt: Date;
content: string;
};
export default function ProjectModal({
params: { id: slug },
}: {
params: {
id: string;
};
}) {
const router = useRouter();
const [project, setProject] = React.useState<Project | null>(null);
const handleOpenChange = (open: boolean) => {
if (!open) {
router.back();
}
};
React.useEffect(() => {
async function getProject() {
const { query: projectQuery, schema: projectSchema } = q("*")
.filterByType("project")
.filter(`slug.current == "${slug}"`)
.grab$({
title: q.string(),
subtitle: q.string(),
slug: q.slug("slug"),
publishedAt: q.date(),
content: q.string(),
})
.slice(0, 1);
const project = projectSchema.parse(await client.fetch(projectQuery));
setProject(project[0]);
}
getProject();
}, [slug]);
return (
<Dialog.Root open onOpenChange={handleOpenChange}>
<Dialog.Portal>
<Dialog.Overlay className="bg-zinc-900 opacity-75 data-[state=open]:animate-overlayShow fixed inset-0" />
<Dialog.Content className="data-[state=open]:animate-contentShow overflow-y-scroll fixed top-[50%] left-[50%] w-[90vw] max-h-[85vh] md:max-w-[50vw] max-w-[90vw] translate-x-[-50%] translate-y-[-50%] rounded-[6px] bg-white dark:bg-zinc-800 px-8 py-12 shadow-[hsl(206_22%_7%_/_35%)_0px_10px_38px_-10px,_hsl(206_22%_7%_/_20%)_0px_10px_20px_-15px] focus:outline-none">
<Dialog.Title
className={cn(
"dark:text-white text-indigo-600 m-0 text-6xl font-bold",
!project && "bg-gray-500 animate-pulse block w-52 h-5"
)}
>
{project?.title}
</Dialog.Title>
<Dialog.Description
className={cn(
"dark:text-white text-indigo-500 font-semibold mt-[10px] mb-5 text-2xl leading-normal",
!project && "bg-gray-500 animate-pulse block w-72 h-5"
)}
>
{project?.subtitle}
</Dialog.Description>
<article className="prose dark:prose-invert prose-zinc max-w-none lg:prose-xl">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw]}
components={
{
code: CodeBlock,
img: ({ ...props }) => {
const { width, height } = props.src.match(
/(?<width>\d+)x(?<height>\d+)/
).groups;
return (
<Image
src={props.src}
alt={props.alt}
width={width}
height={height}
/>
);
},
} as any
}
>
{project?.content || ""}
</ReactMarkdown>
</article>
<Dialog.Close asChild>
<button
className="text-violet11 hover:bg-violet4 focus:shadow-violet7 absolute top-12 right-8 inline-flex h-[25px] w-[25px] appearance-none items-center justify-center rounded-full focus:shadow-[0_0_0_2px] focus:outline-none"
aria-label="Close"
>
<Cross2Icon />
</button>
</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}

View File

@ -1,3 +0,0 @@
export default function Default() {
return null;
}

View File

@ -1,51 +0,0 @@
import Logo from "@/components/Logo";
import SidecardList from "@/components/SidecardList";
import { IdCardIcon } from "@radix-ui/react-icons";
import Image from "next/image";
import Link from "next/link";
export default async function Page() {
return (
<div className="grid grid-cols-5 gap-4 px-4 mx-auto text-black max-w-7xl dark:text-white">
<div className="col-span-4 space-y-3">
<h1 className="text-4xl font-black md:text-8xl">
Hey! I&apos;m Jack Merrill.
</h1>
<p className="text-xl md:text-2xl">
I&apos;m a software engineer, designer, and student from the United
States. I&apos;m working to bring accessible designs to the masses.
</p>
</div>
<div className="grid order-last w-full h-full grid-cols-2 px-8 py-4 rounded-md lg:order-none lg:space-y-12 gap-x-2 lg:row-span-3 lg:grid-cols-1 lg:col-span-1 col-span-full bg-violet-500">
<div className="flex justify-center lg:w-full">
<div className="h-auto p-8 border-4 border-white aspect-square w-fit max-h-48 lg:p-12 md:p-10 sm:p-6 rounded-xl">
<Logo />
</div>
</div>
<SidecardList />
</div>
<div className="col-span-4 space-y-3">
<h2 className="text-3xl font-black md:text-6xl">About Me</h2>
<p className="text-xl md:text-2xl">
I&apos;m a Division II (sophomore) student at Hampshire College,
studying interaction design. I&apos;m also a full-stack web developer
at{" "}
<Link
rel="noreferrer"
target="_blank"
href="https://merch.co"
className="font-bold text-violet-500"
>
Merch
</Link>
.
</p>
</div>
</div>
);
}

View File

@ -1,98 +0,0 @@
import { q } from "groqd";
import { client } from "../../../../sanity/lib/client";
import ReactMarkdown from "react-markdown";
import CodeBlock from "@/components/Codeblock";
import Image from "next/image";
import remarkGfm from "remark-gfm";
import rehypeRaw from "rehype-raw";
export default async function Page({
params: { id: slug },
}: {
params: { id: string };
}) {
const { query, schema } = q("*")
.filterByType("post")
.filter(`slug.current == "${slug}"`)
.grab$({
title: q.string(),
subtitle: q.string(),
slug: q.slug("slug"),
publishedAt: q.date(),
content: q.string(),
mainImage: q("mainImage").grabOne$("asset->url", q.string().optional()),
})
.slice(0, 1);
const post = schema.parse(await client.fetch(query))[0];
const r = post.mainImage?.match(/(?<width>\d+)x(?<height>\d+)/);
return (
<div className="flex flex-col items-center justify-center min-h-screen py-2">
<div className="flex items-center justify-center w-full py-24">
{/* the mainimage with the text on top of it */}
<div className="relative w-full h-96">
<div className="absolute inset-0 flex items-center justify-center w-full h-full bg-black bg-opacity-50">
<div className="flex flex-col items-center justify-center space-y-4">
<h1 className="text-4xl font-bold text-center text-white">
{post.title}
</h1>
<h2 className="text-2xl font-semibold text-center text-white">
{post.subtitle}
</h2>
</div>
</div>
{post.mainImage && (
<Image
className="object-cover w-full h-full"
src={post.mainImage}
alt={post.title}
width={parseInt(r?.groups?.width ?? "400")}
height={parseInt(r?.groups?.height ?? "400")}
/>
)}
</div>
</div>
{/* the content */}
<article className="mx-auto prose dark:prose-invert prose-zinc max-w-7xl lg:prose-xl">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw]}
components={
{
code: CodeBlock,
img: ({ ...props }) => {
const { width, height } = props.src.match(
/(?<width>\d+)x(?<height>\d+)/
).groups;
return (
<Image
src={props.src}
alt={props.alt}
width={width}
height={height}
/>
);
},
} as any
}
>
{post?.content || ""}
</ReactMarkdown>
</article>
</div>
);
}
export async function generateStaticParams() {
const { query, schema } = q("*")
.filterByType("post")
.grabOne$("slug.current", q.string());
const slugs = schema.parse(await client.fetch(query));
return slugs.map((slug) => ({ params: { id: slug } }));
}

View File

@ -1,91 +0,0 @@
import { q } from "groqd";
import { client } from "../../../sanity/lib/client";
import Twemoji from "@/components/Twemoji";
import Image from "next/image";
import Link from "next/link";
export default async function Page() {
const { query, schema } = q("*")
.filterByType("post")
.order("publishedAt desc")
.grab$({
title: q.string(),
subtitle: q.string(),
slug: q.slug("slug"),
publishedAt: q.date(),
mainImage: q("mainImage").grabOne$("asset->url", q.string().optional()),
categories: q("categories")
.filter()
.deref()
.grabOne$("title", q.string())
.nullable(),
});
const posts = schema.parse(await client.fetch(query));
return (
<div>
<section className="flex items-center px-6 py-48 mx-auto h-2/3 max-w-7xl">
<div className="space-y-4">
<h1 className="flex items-center text-6xl font-bold gap-x-4">
<Twemoji emoji="📰" ext="svg" /> Blog
</h1>
<h2 className="text-2xl font-semibold">
My thoughts on web, tech, and life. (other things too)
</h2>
</div>
</section>
<div className="dark:bg-zinc-900">
<section className="grid grid-cols-2 gap-4 py-8 mx-auto max-w-7xl">
{posts.map((post) => {
const r = post.mainImage?.match(/(?<width>\d+)x(?<height>\d+)/);
return (
<Link
key={post.slug}
className="flex flex-col items-center justify-center pb-4 space-y-4 overflow-hidden transition-all duration-150 rounded-md dark:bg-zinc-800 hover:scale-105"
href={`/blog/${post.slug}`}
>
{post.mainImage && (
<Image
src={post.mainImage}
alt={post.title}
width={parseInt(r?.groups?.width ?? "400")}
height={parseInt(r?.groups?.height ?? "400")}
/>
)}
<h3 className="text-xl font-semibold">{post.title}</h3>
<p className="text-lg">{post.subtitle}</p>
{post.categories && post.categories.length > 0 && (
<p className="text-md text-zinc-400">
Categories:
{post.categories.map((category) => (
<span
key={category}
className="px-2 py-1 ml-2 text-sm font-semibold text-white bg-indigo-500 rounded-md"
>
{category}
</span>
))}
</p>
)}
<div className="flex items-center space-x-2">
<time
className="text-sm text-gray-500"
dateTime={post.publishedAt.toISOString()}
>
Published at{" "}
{new Date(post.publishedAt).toLocaleDateString()}
</time>
</div>
</Link>
);
})}
</section>
</div>
</div>
);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1,153 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%;
--card: 0 0% 100%;
--card-foreground: 222.2 47.4% 11.2%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 100% 50%;
--destructive-foreground: 210 40% 98%;
--ring: 215 20.2% 65.1%;
--radius: 0.5rem;
}
.dark {
--background: 224 71% 4%;
--foreground: 213 31% 91%;
--muted: 223 47% 11%;
--muted-foreground: 215.4 16.3% 56.9%;
--popover: 224 71% 4%;
--popover-foreground: 215 20.2% 65.1%;
--card: 224 71% 4%;
--card-foreground: 213 31% 91%;
--border: 216 34% 17%;
--input: 216 34% 17%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 1.2%;
--secondary: 222.2 47.4% 11.2%;
--secondary-foreground: 210 40% 98%;
--accent: 216 34% 17%;
--accent-foreground: 210 40% 98%;
--destructive: 0 63% 31%;
--destructive-foreground: 210 40% 98%;
--ring: 216 34% 17%;
--radius: 0.5rem;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
font-feature-settings: "rlig" 1, "calt" 1;
}
}
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
.rainbow-btn {
position: relative;
z-index: 1;
color: #fff;
}
.rainbow-btn::before {
width: 100%;
height: 175%;
content: "Contact";
padding: 0.5em 0.75em;
position: absolute;
left: 0;
top: -40%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-stroke: 12px transparent;
font-family: Nunito, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu,
Cantarell, "Noto Sans", sans-serif;
text-align: center;
font-size: 1em;
font-weight: 800;
filter: blur(17px);
z-index: -1;
animation: spin 10s linear infinite;
background: conic-gradient(
from 125deg at 50% 50%,
#ff4e4eff 1%,
#f3a43fff 14%,
#f3f23fff 27%,
#63f33fff 39%,
#3fb9f3ff 52%,
#a03ff3ff 66%,
#fc00b4ff 80%,
#ff4e4eff 96%
);
background-size: 100% 100%;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@ -1,23 +0,0 @@
"use client";
/**
* This route is responsible for the built-in authoring environment using Sanity Studio.
* All routes under your studio path is handled by this file using Next.js' catch-all routes:
* https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes
*
* You can learn more about the next-sanity package here:
* https://github.com/sanity-io/next-sanity
*/
import { NextStudio } from "next-sanity/studio";
import config from "../../../../../sanity.config";
const isDev =
process.env.NODE_ENV === "development" ||
!process.env.NODE_ENV ||
process.env.NEXT_PUBLIC_ENV === "development" ||
!process.env.NEXT_PUBLIC_ENV;
export default function StudioPage() {
return isDev ? <NextStudio config={config} /> : null;
}

View File

@ -1,121 +0,0 @@
import "./globals.css";
import { Inter } from "next/font/google";
import Background from "@/components/Background";
import Navbar from "@/components/Navbar";
import { client } from "../../sanity/lib/client";
import type ProjectType from "../../sanity/schemas/project";
import { q } from "groqd";
import Link from "next/link";
import {
GitHubLogoIcon,
LinkedInLogoIcon,
TwitterLogoIcon,
} from "@radix-ui/react-icons";
import Twemoji from "@/components/Twemoji";
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "Jack Merrill",
description:
"Web designer and developer working to bring accessible designs to the masses.",
};
export default async function RootLayout({
children,
project,
blogpost,
}: {
children: React.ReactNode;
project?: React.ReactNode;
blogpost?: React.ReactNode;
}) {
const { query: projectQuery, schema: projectSchema } = q("*")
.filterByType("project")
.order("publishedAt desc")
.grab$({
title: q.string(),
subtitle: q.string(),
slug: q.slug("slug"),
publishedAt: q.date(),
mainImage: q("mainImage").grabOne$("asset->url", q.string().optional()),
})
.slice(0, 2);
const { query: blogQuery, schema: blogSchema } = q("*")
.filterByType("post")
.slice(0, 3)
.order("publishedAt desc")
.grab$({
title: q.string(),
slug: q.slug("slug"),
publishedAt: q.date(),
categories: q("categories")
.filter()
.deref()
.grabOne$("title", q.string())
.nullable(),
});
const latestThreeProjects = projectSchema.parse(
await client.fetch(projectQuery)
);
const latestThreeBlogPosts = blogSchema.parse(await client.fetch(blogQuery));
return (
<html lang="en" className="dark">
<body
className={`${inter.className} min-h-[100vh] flex justify-between flex-col bg-white heropattern-wiggle-indigo-100 dark:bg-zinc-900 dark:heropattern-wiggle-zinc-800`}
>
<Navbar
projects={latestThreeProjects}
blogPosts={latestThreeBlogPosts}
/>
<main className="w-full min-h-full space-y-6">{children}</main>
<footer className="flex justify-center w-full py-8 dark:bg-zinc-900 bg-zinc-300">
<div className="flex flex-col items-center w-full px-4 space-y-2 md:flex-row md:justify-between max-w-7xl">
<p className="flex items-center space-x-1 text-lg text-center text-black dark:text-zinc-100">
Made with
<Twemoji
emoji="❤️"
className="w-5 h-5 mx-1 text-red-500 hover:animate-heartbeat"
ext="svg"
/>
by Jack Merrill
</p>
<p className="text-xs font-light text-zinc-700">
Build{" "}
{process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA
? process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA.slice(0, 7)
: "dev"}
</p>
<div className="flex space-x-4 text-black dark:text-zinc-100">
<Link
href="https://www.linkedin.com/in/jack-merrill-39aa7520b/"
target="_blank"
>
<LinkedInLogoIcon className="w-6 h-6 transition-colors duration-150 cursor-pointer hover:text-[#0a66c2]" />
</Link>
<Link href="https://github.com/jackmerrill" target="_blank">
<GitHubLogoIcon className="w-6 h-6 transition-colors duration-150 cursor-pointer hover:text-pink-500" />
</Link>
<Link href="https://twitter.com/jack__merrill" target="_blank">
<TwitterLogoIcon className="w-6 h-6 transition-colors duration-150 cursor-pointer hover:text-[#1d9bf0]" />
</Link>
</div>
</div>
</footer>
{project}
{blogpost}
</body>
</html>
);
}

View File

@ -1,112 +0,0 @@
import Background from "@/components/Background";
import Button from "@/components/Button";
import Navbar from "@/components/Navbar";
import Twemoji from "@/components/Twemoji";
import {
CodeIcon,
CubeIcon,
FrameIcon,
HobbyKnifeIcon,
MagicWandIcon,
ScissorsIcon,
} from "@radix-ui/react-icons";
import Image from "next/image";
export default function Home() {
return (
<>
<section className="flex items-center px-6 py-48 mx-auto text-black dark:text-white h-2/3 max-w-7xl">
<div className="space-y-4">
<h1 className="flex items-center text-6xl font-bold gap-x-4">
<Twemoji
emoji="👋"
ext="svg"
className="motion-safe:animate-hand-wave"
/>{" "}
Hey hey!
</h1>
<h2 className="text-2xl font-semibold">
I&apos;m Jack Merrill, a web designer and developer working to bring
accessible designs to the masses.
</h2>
<div className="flex">
<Button link href="mailto:contact@jackmerrill.com">
Contact me
</Button>
</div>
</div>
</section>
<section className="px-6 py-28 dark:bg-zinc-900 bg-slate-100">
<div className="flex flex-col items-center mx-auto space-y-4 max-w-7xl">
<h2 className="text-4xl font-bold">What I do</h2>
<div className="grid gap-4 text-black md:grid-cols-2 lg:grid-cols-4 dark:text-zinc-100">
<div className="grid items-center grid-rows-2 p-4 group gap-y-2 dark:bg-zinc-800 bg-slate-300">
<div></div>
<div className="space-y-2">
<FrameIcon className="w-12 h-12 transition-colors duration-150 group-hover:text-pink-500" />
<h3 className="text-2xl font-semibold">UI Design</h3>
<p className="text-lg">
Designing interfaces and websites that are accessible, usable,
and intuitive.
</p>
</div>
</div>
<div className="grid items-center grid-rows-2 p-4 group gap-y-2 dark:bg-zinc-800 bg-slate-300">
<div></div>
<div className="space-y-2">
<CodeIcon className="w-12 h-12 transition-colors duration-150 group-hover:text-teal-500" />{" "}
<h3 className="text-2xl font-semibold">Development</h3>
<p className="text-lg">
Building websites and software that are fast, responsive, and
accessible.
</p>
</div>
</div>
<div className="grid items-center grid-rows-2 p-4 group gap-y-2 dark:bg-zinc-800 bg-slate-300">
<div></div>
<div className="space-y-2">
<MagicWandIcon className="w-12 h-12 transition-colors duration-150 group-hover:text-indigo-500" />{" "}
<h3 className="text-2xl font-semibold">Creative</h3>
<p className="text-lg">
Creating stunning designs and illustrations that are unique
and memorable.
</p>
</div>
</div>
<div className="grid items-center grid-rows-2 p-4 group gap-y-2 dark:bg-zinc-800 bg-slate-300">
<div></div>
<div className="space-y-2">
<ScissorsIcon className="w-12 h-12 transition-colors duration-150 group-hover:text-green-500" />{" "}
<h3 className="text-2xl font-semibold">Other</h3>
<p className="text-lg">
I&apos;m always looking to learn new things and expand my
skillset. Check out my projects for more.
</p>
</div>
</div>
</div>
</div>
</section>
<section className="px-6 py-28">
<div className="flex flex-col items-center mx-auto space-y-4 text-center max-w-7xl">
<h2 className="text-4xl font-bold text-black dark:text-zinc-100">
Tell me about your next project
</h2>
<div className="flex">
<Button rainbow link href="mailto:contact@jackmerrill.com">
Reach out
</Button>
</div>
</div>
</section>
</>
);
}

View File

@ -1,101 +0,0 @@
import { q } from "groqd";
import { client } from "../../../../sanity/lib/client";
import ReactMarkdown from "react-markdown";
import CodeBlock from "@/components/Codeblock";
import Image from "next/image";
import remarkGfm from "remark-gfm";
import rehypeRaw from "rehype-raw";
export default async function Page({
params: { id },
}: {
params: { id: string };
}) {
const { query: projectQuery, schema: projectSchema } = q("*")
.filterByType("project")
.filter(`slug.current == "${id}"`)
.grab$({
title: q.string(),
subtitle: q.string(),
slug: q.slug("slug"),
publishedAt: q.date(),
content: q.string(),
mainImage: q("mainImage").grabOne$("asset->url", q.string().optional()),
});
const project = projectSchema.parse(await client.fetch(projectQuery))[0];
const r = project.mainImage?.match(/(?<width>\d+)x(?<height>\d+)/);
return (
<div className="flex flex-col items-center justify-center min-h-screen py-2">
<div className="flex items-center justify-center w-full py-24">
{/* the mainimage with the text on top of it */}
<div className="relative w-full h-96">
<div className="absolute inset-0 flex items-center justify-center w-full h-full bg-black bg-opacity-50">
<div className="flex flex-col items-center justify-center space-y-4">
<h1 className="text-4xl font-bold text-center text-white">
{project.title}
</h1>
<h2 className="text-2xl font-semibold text-center text-white">
{project.subtitle}
</h2>
</div>
</div>
{project.mainImage && (
<Image
className="object-cover w-full h-full"
src={project.mainImage}
alt={project.title}
width={parseInt(r?.groups?.width ?? "400")}
height={parseInt(r?.groups?.height ?? "400")}
/>
)}
</div>
</div>
{/* the content */}
<article className="px-4 pb-12 mx-auto prose dark:prose-invert prose-zinc max-w-7xl lg:prose-xl">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw]}
components={
{
code: CodeBlock,
img: ({ ...props }) => {
const { width, height } = props.src.match(
/(?<width>\d+)x(?<height>\d+)/
).groups;
return (
<Image
src={props.src}
alt={props.alt}
width={width}
height={height}
/>
);
},
} as any
}
>
{project?.content || ""}
</ReactMarkdown>
</article>
</div>
);
}
export async function generateStaticParams() {
const { query: projectQuery, schema: projectSchema } = q("*")
.filterByType("project")
.grabOne$("slug.current", q.string());
const projects = projectSchema.parse(await client.fetch(projectQuery));
return projects.map((project) => ({
params: {
slug: project,
},
}));
}

View File

@ -1,95 +0,0 @@
import { q } from "groqd";
import { client } from "../../../sanity/lib/client";
import Twemoji from "@/components/Twemoji";
import Image from "next/image";
import Link from "next/link";
export default async function Page() {
const { query: projectQuery, schema: projectSchema } = q("*")
.filterByType("project")
.order("publishedAt desc")
.grab$({
title: q.string(),
subtitle: q.string(),
slug: q.slug("slug"),
publishedAt: q.date(),
mainImage: q("mainImage").grabOne$("asset->url", q.string().optional()),
categories: q("categories")
.filter()
.deref()
.grabOne$("title", q.string())
.nullable(),
});
const projects = projectSchema.parse(await client.fetch(projectQuery));
return (
<div>
<section className="flex items-center px-6 py-48 mx-auto text-black dark:text-white h-2/3 max-w-7xl">
<div className="space-y-4">
<h1 className="flex items-center text-6xl font-bold gap-x-4">
<Twemoji emoji="🛠" ext="svg" /> Projects
</h1>
<h2 className="text-2xl font-semibold">
The weird and wonderful things I work on.
</h2>
</div>
</section>
<div className="dark:bg-zinc-900 bg-slate-200">
<section className="grid gap-4 px-4 py-8 mx-auto md:grid-cols-4 max-w-7xl">
{projects.map((project) => {
const r = project.mainImage?.match(/(?<width>\d+)x(?<height>\d+)/);
return (
<Link
key={project.slug}
className="flex flex-col items-center justify-center h-full p-6 space-y-4 text-black transition-all duration-150 rounded-md dark:text-white dark:bg-zinc-800 bg-slate-300 hover:scale-105"
href={`/projects/${project.slug}`}
>
<div>
{project.mainImage && (
<Image
src={project.mainImage}
alt={project.title}
width={parseInt(r?.groups?.width ?? "400")}
height={parseInt(r?.groups?.height ?? "400")}
/>
)}
<h3 className="text-xl font-semibold">{project.title}</h3>
<p className="text-lg">{project.subtitle}</p>
</div>
<div className="flex-grow">
{project.categories && project.categories.length > 0 && (
<div className="flex flex-wrap flex-grow gap-1 text-md dark:text-zinc-400 text-slate-600">
<span className="w-full">Categories:</span>
{project.categories.map((category) => (
<span
key={category}
className="px-2 py-1 text-sm font-semibold text-white bg-indigo-500 rounded-md"
>
{category}
</span>
))}
</div>
)}
</div>
<div className="flex items-center mt-auto space-x-2">
<time
className="text-sm text-gray-500"
dateTime={project.publishedAt.toISOString()}
>
Published at{" "}
{new Date(project.publishedAt).toLocaleDateString()}
</time>
</div>
</Link>
);
})}
</section>
</div>
</div>
);
}

BIN
src/assets/img/DJI_0565.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
src/assets/img/DJI_0605.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
src/assets/img/IMG_0196.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 KiB

BIN
src/assets/img/IMG_3942.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 KiB

BIN
src/assets/memoji.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

66
src/components/AR.tsx Normal file
View File

@ -0,0 +1,66 @@
import { Interactive, XR, ARButton, Controllers } from "@react-three/xr";
import { Text } from "@react-three/drei";
import { Canvas } from "@react-three/fiber";
import { useState, Suspense } from "react";
function Box({ color, size, scale, children, ...rest }: any) {
return (
<mesh scale={scale} {...rest}>
<boxBufferGeometry args={size} />
<meshPhongMaterial color={color} />
{children}
</mesh>
);
}
function Button(props: any) {
const [hover, setHover] = useState(false);
const [color, setColor] = useState<any>("blue");
const onSelect = () => {
setColor((Math.random() * 0xffffff) | 0);
};
return (
<Interactive
onHover={() => setHover(true)}
onBlur={() => setHover(false)}
onSelect={onSelect}
>
<Box
color={color}
scale={hover ? [0.6, 0.6, 0.6] : [0.5, 0.5, 0.5]}
size={[0.4, 0.1, 0.1]}
{...props}
>
<Suspense fallback={null}>
<Text
position={[0, 0, 0.06]}
fontSize={0.05}
color="#000"
anchorX="center"
anchorY="middle"
>
Hello react-xr!
</Text>
</Suspense>
</Box>
</Interactive>
);
}
export default function AR() {
return (
<>
<ARButton />
<Canvas>
<XR referenceSpace="local">
<ambientLight />
<pointLight position={[10, 10, 10]} />
<Button position={[0, 0.1, -0.2]} />
<Controllers />
</XR>
</Canvas>
</>
);
}

Some files were not shown because too many files have changed in this diff Show More