├── .eslintrc ├── .gitignore ├── README.md ├── components ├── Button.js ├── FeaturedProjectCard.js ├── Icon.js ├── MockupToolbar.js ├── NewIcon.js ├── ProjectCard.js ├── SourceButton.js ├── blog │ ├── BlogItem.js │ └── BlogList.js ├── icons │ ├── AdobeXd.js │ ├── AfterEffects.js │ ├── Bootstrap.js │ ├── Css.js │ ├── Express.js │ ├── ExternalLink.js │ ├── Figma.js │ ├── Firebase.js │ ├── GitHub.js │ ├── GitHubProfile.js │ ├── Html.js │ ├── Illustrator.js │ ├── Javascript.js │ ├── LinkedInProfile.js │ ├── MongoDb.js │ ├── NextJs.js │ ├── NodeJs.js │ ├── Photoshop.js │ ├── ReactJs.js │ ├── Sass.js │ ├── Supabase.js │ ├── Tailwind.js │ └── TwitterProfile.js └── structure │ └── Header.js ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── api │ └── hello.js └── index.js ├── postcss.config.js ├── public ├── bg.svg ├── diamond.svg ├── favicon.ico ├── favicon.png ├── headshot-2023.jpg ├── headshot-with-frame-2.jpg ├── headshot-with-frame.jpg ├── headshot.jpg ├── html.svg ├── icon.svg ├── logo-02.svg ├── logo-faded.svg ├── logo.svg ├── projects │ ├── butlr.png │ ├── colorhub.png │ ├── gps-embroidery.png │ ├── profileme.png │ ├── quotr.png │ ├── ratemyfilm.png │ ├── reportr.png │ ├── smylo.png │ ├── spotlight.png │ └── yodlr.png └── videos │ └── landing-page-video.mp4 ├── styles └── globals.css ├── tailwind.config.js └── utils └── constants.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next", "next/core-web-vitals"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Portfolio - danielcranney.com 3 | ## Project description 4 | My portfolio website, built with NextJS, React and TailwindCSS. 5 | 6 | 7 | ## Installation 8 | 9 | **Install dependencies** 10 | ```bash 11 | npm install 12 | ``` 13 | 14 | **Run development server** 15 | ```bash 16 | npm run dev 17 | ``` 18 | 19 | ## Packages 20 | 21 | - [Next.js](https://nextjs.org/docs) 22 | - [Tailwindcss](https://tailwindcss.com/docs) 23 | 24 | ## License 25 | [GNU General Public License v3.0](https://choosealicense.com/licenses/gpl-3.0/) 26 | -------------------------------------------------------------------------------- /components/Button.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Button = ({ link, text, icon, square }) => { 4 | return ( 5 | 6 | 20 | 21 | ); 22 | }; 23 | 24 | export default Button; 25 | -------------------------------------------------------------------------------- /components/FeaturedProjectCard.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import MockupToolbar from "./MockupToolbar"; 3 | import Button from "./Button"; 4 | import SourceButton from "./SourceButton"; 5 | import Image from "next/image"; 6 | 7 | import GitHub from "./icons/GitHub"; 8 | import ExternalLink from "./icons/ExternalLink"; 9 | 10 | const FeaturedProjectCard = ({ 11 | title, 12 | status, 13 | float, 14 | flexDirection, 15 | description, 16 | imgWidth, 17 | imgHeight, 18 | imgSrc, 19 | stack, 20 | liveLink, 21 | repoLink, 22 | }) => { 23 | return ( 24 |
27 | {/* Project image */} 28 |
29 | 30 | {`${title} 36 |
37 | 38 | {/* Project info */} 39 |
42 |

43 | {status} 44 |

45 |

{title}

46 |
 
47 |
{stack}
48 |

49 | {description} 50 |

51 |
52 | {liveLink !== null ? ( 53 |
70 |
71 |
72 | ); 73 | }; 74 | 75 | export default FeaturedProjectCard; 76 | -------------------------------------------------------------------------------- /components/Icon.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Icon = ({ 4 | IconType, 5 | padding, 6 | width, 7 | height, 8 | flexDirection, 9 | title, 10 | titleMargins, 11 | titleSize, 12 | marginBottom, 13 | marginRight, 14 | textTransform, 15 | fixedHeight, 16 | }) => { 17 | return ( 18 |
21 |
24 | 25 |
26 | {title ? ( 27 | <> 28 | {/*

31 | {title} 32 |

33 | ) : null} */} 34 | 35 | ) : null} 36 |
37 | ); 38 | }; 39 | 40 | export default Icon; 41 | -------------------------------------------------------------------------------- /components/MockupToolbar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const MockupToolbar = () => { 4 | return ( 5 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | ); 30 | }; 31 | export default MockupToolbar; 32 | -------------------------------------------------------------------------------- /components/NewIcon.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const NewIcon = ({ 4 | IconType, 5 | padding, 6 | width, 7 | height, 8 | flexDirection, 9 | title, 10 | titleMargins, 11 | titleSize, 12 | marginBottom, 13 | marginRight, 14 | textTransform, 15 | fixedHeight, 16 | }) => { 17 | return ( 18 | 23 | 35 | 36 | 45 | 46 | 47 | 48 | ); 49 | }; 50 | 51 | export default NewIcon; 52 | -------------------------------------------------------------------------------- /components/ProjectCard.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ExternalLink from "./icons/ExternalLink"; 3 | import GitHub from "./icons/GitHub"; 4 | import Button from "./Button"; 5 | 6 | const ProjectCard = ({ project }) => { 7 | const { title, overview, stack, link, repo, isSiteLive } = project; 8 | 9 | return ( 10 |
11 | 18 | 22 | 26 | 35 | 36 |

37 | {title} 38 |

39 |

{overview}

40 | {/* Stack icons inner container */} 41 |
42 | 54 |
55 | {/* Live and GitHub container */} 56 |
57 | {link ? ( 58 |
77 |
78 | ); 79 | }; 80 | 81 | export default ProjectCard; 82 | -------------------------------------------------------------------------------- /components/SourceButton.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import GitHub from "./icons/GitHub"; 3 | 4 | const SourceButton = ({ sourceLink }) => { 5 | return ( 6 | 12 | 18 | 19 | ); 20 | }; 21 | 22 | export default SourceButton; 23 | -------------------------------------------------------------------------------- /components/blog/BlogItem.js: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | export default function BlogItem({ post }) { 3 | const redirectToHashnode = () => { 4 | window.open("https://blog.danielcranney.com/" + post.slug, "_blank"); 5 | }; 6 | 7 | const getDateAdded = () => { 8 | let d = new Date(post.dateAdded); 9 | let ye = new Intl.DateTimeFormat("en", { year: "numeric" }).format(d); 10 | let mo = new Intl.DateTimeFormat("en", { month: "short" }).format(d); 11 | let da = new Intl.DateTimeFormat("en", { day: "2-digit" }).format(d); 12 | return `${mo} ${da}, ${ye}`; 13 | }; 14 | 15 | const formattedDate = getDateAdded(); 16 | 17 | return ( 18 |
22 |
23 | 29 |
30 |
31 |

{post.title}

32 |
33 | {formattedDate} 34 |
35 |

{post.brief.substr(0, 150)}...

36 |

Read more

37 |
38 |
39 |
40 |
41 |

Open

42 | 49 | 55 | 56 |
57 |
58 |
59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /components/blog/BlogList.js: -------------------------------------------------------------------------------- 1 | import BlogItem from "./BlogItem"; 2 | 3 | export default function BlogList({ publications }) { 4 | let posts = publications.data.user.publication.posts; 5 | 6 | return ( 7 |
8 | {posts.map((post, index) => { 9 | return ( 10 |
11 | 12 |
13 | ); 14 | })} 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /components/icons/AdobeXd.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const AdobeXd = () => { 4 | return ( 5 | 6 | 10 | 11 | ); 12 | }; 13 | 14 | export default AdobeXd; 15 | -------------------------------------------------------------------------------- /components/icons/AfterEffects.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const AfterEffects = () => { 4 | return ( 5 | 6 | 7 | 11 | 15 | 16 | ); 17 | }; 18 | export default AfterEffects; 19 | -------------------------------------------------------------------------------- /components/icons/Bootstrap.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Bootstrap = () => { 4 | return ( 5 | 6 | 10 | 11 | ); 12 | }; 13 | 14 | export default Bootstrap; 15 | -------------------------------------------------------------------------------- /components/icons/Css.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Css = () => { 4 | return ( 5 | 6 | 10 | 11 | ); 12 | }; 13 | 14 | export default Css; 15 | -------------------------------------------------------------------------------- /components/icons/Express.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Express = () => { 4 | return ( 5 | 6 | 10 | 11 | ); 12 | }; 13 | 14 | export default Express; 15 | -------------------------------------------------------------------------------- /components/icons/ExternalLink.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ExternalLink = ({ square }) => { 4 | return ( 5 | 14 | 20 | 21 | ); 22 | }; 23 | 24 | export default ExternalLink; 25 | -------------------------------------------------------------------------------- /components/icons/Figma.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Figma = () => { 4 | return ( 5 | 6 | 10 | 11 | ); 12 | }; 13 | 14 | export default Figma; 15 | -------------------------------------------------------------------------------- /components/icons/Firebase.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function Firebase() { 4 | return ( 5 | 6 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /components/icons/GitHub.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const GitHub = ({ square }) => { 4 | return ( 5 | 11 | 16 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | export default GitHub; 27 | -------------------------------------------------------------------------------- /components/icons/GitHubProfile.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const GitHubProfile = ({ marginBottom }) => { 4 | return ( 5 |
6 | 11 | 18 | 19 | 20 | 21 |
22 | ); 23 | }; 24 | 25 | export default GitHubProfile; 26 | -------------------------------------------------------------------------------- /components/icons/Html.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Html = () => { 4 | return ( 5 | 6 | 10 | 11 | ); 12 | }; 13 | 14 | export default Html; 15 | -------------------------------------------------------------------------------- /components/icons/Illustrator.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function Illustrator() { 4 | return ( 5 | 6 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /components/icons/Javascript.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Javascript = () => { 4 | return ( 5 | 6 | 10 | 11 | ); 12 | }; 13 | 14 | export default Javascript; 15 | -------------------------------------------------------------------------------- /components/icons/LinkedInProfile.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const LinkedInProfile = ({ marginBottom }) => { 4 | return ( 5 |
6 | 11 | 18 | 19 | 20 | 21 |
22 | ); 23 | }; 24 | 25 | export default LinkedInProfile; 26 | -------------------------------------------------------------------------------- /components/icons/MongoDb.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const MongoDb = () => { 4 | return ( 5 | 6 | 10 | 11 | ); 12 | }; 13 | 14 | export default MongoDb; 15 | -------------------------------------------------------------------------------- /components/icons/NextJs.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const NextJs = () => { 4 | return ( 5 | 6 | 10 | 11 | ); 12 | }; 13 | 14 | export default NextJs; 15 | -------------------------------------------------------------------------------- /components/icons/NodeJs.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const NodeJs = () => { 4 | return ( 5 | 6 | 10 | 11 | ); 12 | }; 13 | 14 | export default NodeJs; 15 | -------------------------------------------------------------------------------- /components/icons/Photoshop.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Photoshop = () => { 4 | return ( 5 | 6 | 12 | 13 | ); 14 | }; 15 | 16 | export default Photoshop; 17 | -------------------------------------------------------------------------------- /components/icons/ReactJs.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ReactJs = () => { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | export default ReactJs; 15 | -------------------------------------------------------------------------------- /components/icons/Sass.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Sass = () => { 4 | return ( 5 | 6 | 12 | 13 | ); 14 | }; 15 | 16 | export default Sass; 17 | -------------------------------------------------------------------------------- /components/icons/Supabase.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Supabase = () => { 4 | return ( 5 | 6 | 10 | 11 | ); 12 | }; 13 | 14 | export default Supabase; 15 | -------------------------------------------------------------------------------- /components/icons/Tailwind.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Tailwind = () => { 4 | return ( 5 | 6 | 11 | 12 | ); 13 | }; 14 | 15 | export default Tailwind; 16 | -------------------------------------------------------------------------------- /components/icons/TwitterProfile.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const TwitterProfile = ({ marginBottom }) => { 4 | return ( 5 |
6 | 11 | 18 | 19 | 20 | 21 |
22 | ); 23 | }; 24 | 25 | export default TwitterProfile; 26 | -------------------------------------------------------------------------------- /components/structure/Header.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/components/structure/Header.js -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | images: { 3 | remotePatterns: [ 4 | { 5 | protocol: "https", 6 | hostname: "cdn.hashnode.com", 7 | port: "", 8 | }, 9 | ], 10 | }, 11 | reactStrictMode: true, 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "portfolio-2021", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "next": "13.2.4", 13 | "next-themes": "^0.2.0", 14 | "react": "18.2.0", 15 | "react-dom": "18.2.0", 16 | "react-typing-effect": "^2.0.5" 17 | }, 18 | "devDependencies": { 19 | "@svgr/webpack": "^5.5.0", 20 | "@tailwindcss/aspect-ratio": "^0.4.0", 21 | "@tailwindcss/forms": "^0.5.1", 22 | "@tailwindcss/line-clamp": "^0.4.0", 23 | "@tailwindcss/typography": "^0.5.2", 24 | "autoprefixer": "^10.4.7", 25 | "eslint": "7.32.0", 26 | "eslint-config-next": "11.0.1", 27 | "postcss": "^8.4.13", 28 | "tailwindcss": "^3.0.24" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import { ThemeProvider } from "next-themes"; 3 | import { useTheme } from "next-themes"; 4 | 5 | function MyApp({ Component, pageProps }) { 6 | return ( 7 | 8 | 9 | 10 | ) 11 | } 12 | 13 | export default MyApp 14 | -------------------------------------------------------------------------------- /pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default function handler(req, res) { 4 | res.status(200).json({ name: 'John Doe' }) 5 | } 6 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from "react"; 2 | import Head from "next/head"; 3 | import Image from "next/image"; 4 | import Link from "next/link"; 5 | import ReactTypingEffect from "react-typing-effect"; 6 | 7 | import Icon from "../components/Icon"; 8 | // Icons 9 | import Html from "../components/icons/Html"; 10 | import Css from "../components/icons/Css"; 11 | import Javascript from "../components/icons/Javascript"; 12 | import Tailwind from "../components/icons/Tailwind"; 13 | import Bootstrap from "../components/icons/Bootstrap"; 14 | import Sass from "../components/icons/Sass"; 15 | import ReactJs from "../components/icons/ReactJs"; 16 | import NextJs from "../components/icons/NextJs"; 17 | import NodeJs from "../components/icons/NodeJs"; 18 | import Firebase from "../components/icons/Firebase"; 19 | import Figma from "../components/icons/Figma"; 20 | import Photoshop from "../components/icons/Photoshop"; 21 | import Illustrator from "../components/icons/Illustrator"; 22 | import AfterEffects from "../components/icons/AfterEffects"; 23 | import AdobeXd from "../components/icons/AdobeXd"; 24 | import Supabase from "../components/icons/Supabase"; 25 | import MongoDb from "../components/icons/MongoDb"; 26 | import Express from "../components/icons/Express"; 27 | // Project Card 28 | import ProjectCard from "../components/ProjectCard"; 29 | import GitHubProfile from "../components/icons/GitHubProfile"; 30 | import TwitterProfile from "../components/icons/TwitterProfile"; 31 | import LinkedInProfile from "../components/icons/LinkedInProfile"; 32 | import FeaturedProjectCard from "../components/FeaturedProjectCard"; 33 | 34 | // Blog Components 35 | import BlogList from "../components/blog/BlogList"; 36 | import BlogItem from "../components/blog/BlogItem"; 37 | 38 | // Dark Mode 39 | import { useTheme } from "next-themes"; 40 | 41 | import { projects } from "../utils/constants"; 42 | import NewIcon from "../components/NewIcon"; 43 | 44 | const getDimensions = (ele) => { 45 | const { height } = ele.getBoundingClientRect(); 46 | const offsetTop = ele.offsetTop; 47 | const offsetBottom = offsetTop + height; 48 | 49 | return { 50 | height, 51 | offsetTop, 52 | offsetBottom, 53 | }; 54 | }; 55 | 56 | const scrollTo = (ele) => { 57 | ele.scrollIntoView({ 58 | behavior: "smooth", 59 | block: "start", 60 | }); 61 | }; 62 | 63 | export default function Home({ publications }) { 64 | const [visibleSection, setVisibleSection] = useState(); 65 | const [scrolling, setScrolling] = useState(false); 66 | const [scrollPosition, setScrollPosition] = useState(0); 67 | const [navbarOpen, setNavbarOpen] = useState(false); 68 | const [showHeader, setShowHeader] = useState(true); 69 | const [isMobile, setIsMobile] = useState(false); 70 | const [mounted, setMounted] = useState(false); 71 | const { systemTheme, theme, setTheme } = useTheme(); 72 | 73 | const handleResize = () => { 74 | if (window.innerWidth < 1024) { 75 | } else { 76 | setNavbarOpen(false); 77 | } 78 | }; 79 | 80 | const headerRef = useRef(null); 81 | const homeRef = useRef(null); 82 | const aboutRef = useRef(null); 83 | const skillsRef = useRef(null); 84 | const myWorkRef = useRef(null); 85 | const blogRef = useRef(null); 86 | const contactRef = useRef(null); 87 | 88 | useEffect(() => { 89 | const sectionRefs = [ 90 | { section: "home", ref: homeRef, id: 1 }, 91 | { section: "about", ref: aboutRef, id: 2 }, 92 | { section: "skills", ref: skillsRef, id: 3 }, 93 | { section: "my-work", ref: myWorkRef, id: 4 }, 94 | { section: "blog", ref: blogRef, id: 5 }, 95 | { section: "contact", ref: contactRef, id: 6 }, 96 | ]; 97 | 98 | const handleScroll = () => { 99 | const { height: headerHeight } = getDimensions(headerRef.current); 100 | const scrollPosition = window.scrollY + headerHeight; 101 | 102 | const selected = sectionRefs.find(({ section, ref }) => { 103 | const ele = ref.current; 104 | if (ele) { 105 | const { offsetBottom, offsetTop } = getDimensions(ele); 106 | return scrollPosition >= offsetTop && scrollPosition <= offsetBottom; 107 | } 108 | }); 109 | 110 | if (selected && selected.section !== visibleSection) { 111 | setVisibleSection(selected.section); 112 | // console.log(visibleSection); 113 | } else if (!selected && visibleSection) { 114 | setVisibleSection(undefined); 115 | } 116 | }; 117 | 118 | handleScroll(); 119 | window.addEventListener("scroll", handleScroll); 120 | return () => { 121 | window.removeEventListener("scroll", handleScroll); 122 | }; 123 | }, [visibleSection]); 124 | 125 | // Handle Header Scroll Away 126 | useEffect(() => { 127 | let prevScrollPos = window.pageYOffset; 128 | let timeoutId; 129 | 130 | const handleScrollBack = () => { 131 | const currentScrollPos = window.pageYOffset; 132 | 133 | // Delay before the header scrolls back into view 134 | if (currentScrollPos < scrollPosition - 50 && scrolling) { 135 | timeoutId = setTimeout(() => { 136 | setShowHeader(true); 137 | }, 250); 138 | } 139 | 140 | // Add an easing effect when the header scrolls into and out of view 141 | if (currentScrollPos > prevScrollPos + 10 && !scrolling && showHeader) { 142 | setScrolling(true); 143 | } else if (currentScrollPos < prevScrollPos - 10 && scrolling) { 144 | clearTimeout(timeoutId); 145 | setScrolling(false); 146 | setTimeout(() => { 147 | setShowHeader(false); 148 | }, 250); 149 | } 150 | 151 | prevScrollPos = currentScrollPos; 152 | }; 153 | 154 | window.addEventListener("scroll", handleScrollBack); 155 | return () => window.removeEventListener("scroll", handleScrollBack); 156 | }, [scrollPosition, scrolling, showHeader]); 157 | 158 | useEffect(() => { 159 | if (typeof window !== "undefined") { 160 | window.addEventListener("scroll", () => 161 | setScrolling(window.pageYOffset > 110) 162 | ); 163 | } 164 | }, []); 165 | 166 | useEffect(() => { 167 | window.addEventListener("resize", handleResize); 168 | }); 169 | 170 | useEffect(() => { 171 | setMounted(true); 172 | }, []); 173 | 174 | const currentTheme = theme === "system" ? systemTheme : theme; 175 | 176 | useEffect(() => { 177 | console.log(currentTheme); 178 | }, [currentTheme]); 179 | 180 | const renderThemeChanger = () => { 181 | if (!mounted) return null; 182 | 183 | if (currentTheme === "dark") { 184 | return ( 185 | 191 | 197 | 198 | ); 199 | } else { 200 | return ( 201 | 207 | 213 | 214 | ); 215 | } 216 | }; 217 | 218 | return ( 219 |
220 |
225 | 226 | Daniel Cranney | Frontend Developer & Designer 227 | 231 | 232 | 233 | 234 | {/* Full-screen Menu */} 235 |
242 |
243 | 353 |
354 |
355 | 356 | {/* Header and Nav */} 357 |
363 | {/* Logo and Nav container */} 364 |
365 | {/* Logo */} 366 |
367 | 368 |
369 | {/* Text */} 370 |
371 |

372 | Daniel Cranney 373 |

374 |
375 | {/* Nav */} 376 | 499 |
500 | {/* Dark mode */} 501 | 509 |
510 |
511 |
512 | 513 | {/* Content Container */} 514 |
515 | {/* Hero Content */} 516 |
517 | {/* Main */} 518 |
519 |
520 | {/* 521 | Hello! 👋 My name is 522 | */} 523 | 524 |

525 | Daniel Cranney 526 |

527 |

528 | 540 |

541 |

542 | I design and build websites that look good, and work well. 543 |

544 | 552 |
553 |
554 |
555 | 556 | {/* About */} 557 |
562 |
563 |

About

564 |
565 | 566 |
567 |
568 |

569 | Hi! I'm Dan and I'm a frontend developer, designer 570 | and teacher from Bristol, England. 571 |

572 |

573 | After building my first website aged thirteen, I knew I 574 | wanted to work with computers and technology, and I've 575 | never looked back. 576 |

577 |

578 | After graduating University with a Media degree, I began 579 | freelancing as a designer, creating graphics, video content 580 | and websites for small businesses, using content management 581 | systems like Wordpress, Joomla and Squarespace. 582 |

583 |

584 | In recent years, I've been focused on programming, 585 | building a solid frontend stack and creating exciting 586 | projects that solve real-world problems. 587 |

588 |

589 | Alongside my design and development work, I run a BA Media 590 | Production degree course and a corporate video production 591 | company called{" "} 592 | 598 | Spotlight Media 599 | 600 | , so I like to keep busy! 601 |

602 |

603 | Take a look at my work below to see what I'm working 604 | on, and get in touch if you'd like to work together! 605 |

606 |
607 |
608 | {"Daniel 615 |
616 |
617 |
618 |
619 | 620 | {/* Skills */} 621 |
626 |

Skills

627 |
628 | 629 | {/* Skills icons */} 630 |
631 | {/* HTML */} 632 | 646 | 647 | {/* CSS */} 648 | 662 | 663 | {/* Tailwind */} 664 | 678 | 679 | {/* Javascript */} 680 | 694 | 695 | {/* React */} 696 | 710 | 711 | {/* Next */} 712 | 726 | 727 | {/* Node */} 728 | 742 | 743 | {/* Express */} 744 | 758 | 759 | {/* Supabase */} 760 | 774 | 775 | {/* MongoDb */} 776 | 790 | 791 | {/* Sass */} 792 | 806 | 807 | {/* Bootstrap */} 808 | {/* */} 822 | 823 | {/* Firebase */} 824 | 838 | 839 | {/* Photoshop */} 840 | 854 | 855 | {/* Illustrator */} 856 | 870 | 871 | {/* After Effects */} 872 | 886 | 887 | {/* Adobe XD */} 888 | 902 |
903 |
904 | 905 | {/* My Work */} 906 |
911 | {/* My Work header */} 912 |

My Work

913 |
914 | 915 | {/* Featured Projects Container */} 916 |
917 | {/* Project One */} 918 | 931 | 946 | 947 | 962 | 963 | 978 | 979 | 994 | 995 | 1010 | 1011 | } 1012 | /> 1013 | {/* Project Two */} 1014 | 1027 | 1042 | 1043 | 1058 | 1059 | 1074 | 1075 | 1090 | 1091 | } 1092 | /> 1093 | {/* Project Three */} 1094 | 1107 | 1122 | 1123 | 1138 | 1139 | 1154 | 1155 | 1170 | 1171 | } 1172 | /> 1173 |
1174 | 1175 | {/* Other Projects header */} 1176 |

Other Projects

1177 |
1178 |

1179 | Check out some of the projects I've been a part of... 1180 |

1181 | 1182 | {/* Other Projects Container */} 1183 |
1184 | {projects.map(function (project, i) { 1185 | return ; 1186 | })} 1187 |
1188 |
1189 | 1190 | {/* Blog */} 1191 | {/*
1196 |

Blog

1197 |
1198 | 1199 | 1200 |
*/} 1201 | 1202 | {/* Contact */} 1203 |
1208 |

Contact

1209 |
1210 | 1211 |
1212 |
1213 |

1214 | I'm currently available to get involved in new projects, 1215 | so get in touch if you'd like to work together. 1216 |

1217 |

1218 | Email me at{" "} 1219 | 1223 | danielcranney@gmail.com 1224 | {" "} 1225 | and let's talk about your project! 1226 |

1227 |
1228 |
1229 |
1230 | 1231 | {/* Footer */} 1232 |
1233 |
1234 |
1235 | 1241 | 1245 | 1249 | 1258 | 1259 |
1260 | 1261 |
1262 |

1263 | © {new Date().getFullYear()} - Designed and built by Daniel 1264 | Cranney 1265 |

1266 | 1267 |
1268 | 1269 | 1270 | 1271 | 1272 | 1273 | 1274 | 1275 | 1276 | 1277 |
1278 |
1279 |
1280 |
1281 | 1282 | {/* Fixed Container */} 1283 |
1284 |
1285 | {/* Profile Icons */} 1286 |
1287 | 1288 | 1289 | 1290 |
1291 |
1292 | 1293 | {/* Pagination */} 1294 |
1295 | {/* Hero - Diamond 1 */} 1296 | 1334 | {/* About - Diamond 2 */} 1335 | 1373 | {/* Skills - Diamond 3 */} 1374 | 1412 | {/* My Work - Diamond 4 */} 1413 | 1451 | {/* Blog - Diamond 5 */} 1452 | 1490 | {/* Contact - Diamond 6 */} 1491 | 1529 | 1530 | {/* Line */} 1531 |
1532 |
1533 |
1534 |
1535 |
1536 |
1537 | ); 1538 | } 1539 | 1540 | // export async function getServerSideProps(context) { 1541 | // const res = await fetch("https://api.hashnode.com/", { 1542 | // method: "POST", 1543 | // headers: { 1544 | // "Content-Type": "application/json", 1545 | // Authorization: "32ab9fe7-0331-4efc-bdb8-5a3e0bfdd9b9", 1546 | // }, 1547 | // body: JSON.stringify({ 1548 | // query: 1549 | // 'query {user(username: "danielcranney") {publication {posts(page: 0) {title brief slug coverImage dateAdded}}}}', 1550 | // }), 1551 | // }); 1552 | // const publications = await res.json(); 1553 | 1554 | // if (!publications) { 1555 | // return { 1556 | // notFound: true, 1557 | // }; 1558 | // } 1559 | 1560 | // return { 1561 | // props: { 1562 | // publications, 1563 | // }, 1564 | // }; 1565 | // } 1566 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/bg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/diamond.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/favicon.png -------------------------------------------------------------------------------- /public/headshot-2023.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/headshot-2023.jpg -------------------------------------------------------------------------------- /public/headshot-with-frame-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/headshot-with-frame-2.jpg -------------------------------------------------------------------------------- /public/headshot-with-frame.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/headshot-with-frame.jpg -------------------------------------------------------------------------------- /public/headshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/headshot.jpg -------------------------------------------------------------------------------- /public/html.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /public/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/logo-02.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/logo-faded.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/projects/butlr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/projects/butlr.png -------------------------------------------------------------------------------- /public/projects/colorhub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/projects/colorhub.png -------------------------------------------------------------------------------- /public/projects/gps-embroidery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/projects/gps-embroidery.png -------------------------------------------------------------------------------- /public/projects/profileme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/projects/profileme.png -------------------------------------------------------------------------------- /public/projects/quotr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/projects/quotr.png -------------------------------------------------------------------------------- /public/projects/ratemyfilm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/projects/ratemyfilm.png -------------------------------------------------------------------------------- /public/projects/reportr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/projects/reportr.png -------------------------------------------------------------------------------- /public/projects/smylo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/projects/smylo.png -------------------------------------------------------------------------------- /public/projects/spotlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/projects/spotlight.png -------------------------------------------------------------------------------- /public/projects/yodlr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/projects/yodlr.png -------------------------------------------------------------------------------- /public/videos/landing-page-video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielcranney/portfolio-v1/60fe61584a2974b3cfca75185c3caaa803152eb4/public/videos/landing-page-video.mp4 -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @import url("https://use.typekit.net/ewi2vgp.css"); 2 | 3 | /* ./styles/globals.css */ 4 | @tailwind base; 5 | @tailwind components; 6 | @tailwind utilities; 7 | 8 | @layer base { 9 | * { 10 | @apply antialiased; 11 | } 12 | body { 13 | @apply bg-light/10 dark:bg-dark text-white; 14 | } 15 | } 16 | 17 | /* Typography Styles */ 18 | @layer base { 19 | h1, 20 | h2, 21 | h3, 22 | h4, 23 | h5, 24 | h6 { 25 | @apply font-bold mb-2 tracking-tight text-darker dark:text-white; 26 | } 27 | h1 { 28 | @apply font-display text-4xl md:text-5xl; 29 | } 30 | h2 { 31 | @apply font-display text-3xl md:text-4xl; 32 | } 33 | h3 { 34 | @apply font-display text-2xl md:text-3xl; 35 | } 36 | h4 { 37 | @apply font-display text-xl md:text-2xl; 38 | } 39 | h5 { 40 | @apply font-display text-lg md:text-xl; 41 | } 42 | h6 { 43 | @apply font-display text-sm md:text-base; 44 | } 45 | .small-text { 46 | @apply text-sm font-semibold uppercase dark:text-light text-dark; 47 | } 48 | p { 49 | @apply font-body text-base mb-4 leading-relaxed 50 | dark:text-light text-dark font-normal; 51 | } 52 | a { 53 | @apply font-semibold text-brand opacity-100 transition-all duration-200 hover:text-white; 54 | } 55 | } 56 | 57 | @layer components { 58 | .nav-item { 59 | @apply relative after:absolute after:bg-brand after:bottom-0 after:left-0 after:h-[2px] after:w-full after:origin-bottom-right after:transition-transform after:ease-in-out after:duration-300 font-bold leading-loose cursor-pointer dark:text-light dark:hover:text-white text-mid hover:text-darker font-body; 60 | } 61 | .nav-item.active { 62 | @apply after:scale-x-0 hover:after:origin-bottom-left hover:after:scale-x-100; 63 | } 64 | .nav-item.current { 65 | @apply after:scale-x-100 after:origin-bottom-left text-darker dark:text-white; 66 | } 67 | } 68 | 69 | @layer base { 70 | .underline-link { 71 | @apply hover:border-brand border-b-2 border-transparent; 72 | } 73 | } 74 | 75 | @layer components { 76 | button { 77 | @apply font-body; 78 | } 79 | } 80 | /* Large Button */ 81 | @layer components { 82 | .btn-lg { 83 | @apply inline-flex h-[3.25rem] items-center px-5 justify-center 84 | text-center 85 | font-semibold rounded-lg 86 | shadow-none 87 | self-start transition-all duration-150 ease-in-out; 88 | } 89 | } 90 | 91 | /* Medium Button */ 92 | @layer components { 93 | .btn-md { 94 | @apply inline-flex h-12 py-1 items-center justify-center px-6 flex-grow-0 95 | text-center 96 | font-semibold rounded-lg 97 | shadow-none 98 | text-base 99 | transition-all duration-300 ease-in-out; 100 | } 101 | } 102 | 103 | /* XS Button */ 104 | @layer components { 105 | .btn-xs { 106 | @apply inline-flex h-10 items-center justify-center 107 | px-3.5 flex-grow-0 108 | text-center 109 | font-semibold rounded-sm 110 | shadow-none 111 | text-sm 112 | transition-all duration-150 ease-in-out; 113 | } 114 | } 115 | 116 | /* Small Button */ 117 | @layer components { 118 | .btn-sm { 119 | @apply inline-flex h-12 items-center justify-center 120 | px-5 flex-grow-0 121 | text-center 122 | font-semibold rounded-sm 123 | shadow-none 124 | text-sm 125 | transition-all duration-150 ease-in-out; 126 | } 127 | } 128 | @layer components { 129 | .btn-sm-no-text { 130 | @apply inline-flex h-12 items-center justify-center 131 | flex-grow-0 132 | text-center 133 | font-semibold rounded-sm 134 | shadow-none 135 | text-sm 136 | transition-all duration-150 ease-in-out; 137 | } 138 | } 139 | /* Standard Buttons */ 140 | @layer components { 141 | .btn-brand { 142 | @apply border-2 143 | bg-brand 144 | border-brand 145 | hover:bg-brandAlt 146 | hover:border-brandAlt 147 | dark:text-darker text-darker hover:text-darker 148 | focus:outline-none 149 | focus:ring-0 150 | transition-all duration-150 ease-in-out; 151 | } 152 | } 153 | 154 | /* Outline buttons */ 155 | @layer components { 156 | .btn-outline { 157 | @apply border-2 158 | bg-transparent 159 | border-white 160 | hover:border-brand 161 | hover:bg-brand 162 | text-white 163 | focus:outline-none 164 | focus:ring-0 165 | transition-all duration-150 ease-in-out; 166 | } 167 | } 168 | 169 | @layer base { 170 | .selected { 171 | @apply border-brand dark:text-white text-dark group-hover:text-darker border-b-2 opacity-100 dark:hover:border-white hover:border-darker; 172 | } 173 | } 174 | 175 | @layer components { 176 | input, 177 | textarea { 178 | @apply w-full bg-darker rounded-sm appearance-none border-0 p-4 text-white focus:outline-none 179 | focus:ring-2 focus:ring-brand; 180 | } 181 | } 182 | 183 | html { 184 | scroll-behavior: smooth; 185 | } 186 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | // tailwind.config.js 2 | const colors = require("tailwindcss/colors"); 3 | 4 | module.exports = { 5 | content: [ 6 | "./pages/**/*.{js,ts,jsx,tsx}", 7 | "./components/**/*.{js,ts,jsx,tsx}", 8 | ], 9 | darkMode: "class", 10 | theme: { 11 | container: { 12 | padding: { 13 | DEFAULT: "1rem", 14 | sm: "2rem", 15 | lg: "4rem", 16 | xl: "2rem", 17 | "2xl": "2rem", 18 | }, 19 | }, 20 | extend: { 21 | fontFamily: { 22 | display: ["termina", "sans-serif"], 23 | body: ['"neue-haas-grotesk-text"', "sans-serif"], 24 | }, 25 | width: { 26 | "30pc": "30%", 27 | "31pc": "31%", 28 | "32pc": "32%", 29 | "49pc": "49%", 30 | "48pc": "48%", 31 | "1/8": "12.5%", 32 | "2/8": "25%", 33 | "3/8": "37.5%", 34 | "4/8": "50%", 35 | "5/8": "65.8%", 36 | "6/8": "75%", 37 | "7/8": "87.5%", 38 | }, 39 | transitionProperty: { 40 | width: "width", 41 | }, 42 | colors: { 43 | soft: "#f0f0f0", 44 | brandAlt: "#e4bc3b", 45 | brand: "#DFB537", 46 | darker: "#0C0C0D", 47 | dark: "#2F2E33", 48 | mid: "#827F8B", 49 | light: "#D4CFDE", 50 | lightest: "#FFFFFF", 51 | }, 52 | backgroundImage: (theme) => ({ 53 | "header-img": "url('/bg.svg')", 54 | }), 55 | }, 56 | }, 57 | plugins: [], 58 | }; 59 | -------------------------------------------------------------------------------- /utils/constants.js: -------------------------------------------------------------------------------- 1 | export const projects = [ 2 | { 3 | title: "Reportr", 4 | overview: "Write reports for your students in 60 seconds or less", 5 | stack: ["Html", "React", "Next", "Supabase"], 6 | link: "http://www.reportr.io", 7 | repo: null, 8 | isSiteLive: true, 9 | }, 10 | { 11 | title: "Yodlr (Archived)", 12 | overview: 13 | "Shoutout a Twitter user, and generate a custom profile card in under a minute. Update: Since Twitter announced new API pricing in 2023, this project no longer functions correctly, and has been archived.", 14 | stack: ["Html", "Tailwind", "React", "Next"], 15 | link: "http://yodlr.vercel.app", 16 | repo: "https://github.com/danielcranney/yodlr", 17 | isSiteLive: true, 18 | }, 19 | { 20 | title: "Rate My Film", 21 | overview: 22 | "A single-page application that helps filmmakers learn more about who their film might be suitable for.", 23 | stack: ["Html", "React", "Sass"], 24 | link: "http://www.ratemyfilm.co.uk", 25 | repo: "https://github.com/danielcranney/rate-my-film", 26 | isSiteLive: true, 27 | }, 28 | { 29 | title: "Spotlight Media", 30 | overview: 31 | "The website for my corporate videography company. This features a contact form powered by NodeJs and SendGrid.", 32 | stack: ["Html", "ReactJs", "Next", "Node"], 33 | link: "http://www.wearespotlight.co.uk", 34 | repo: "https://github.com/danielcranney/spotlight-media", 35 | isSiteLive: true, 36 | }, 37 | { 38 | title: "Quotr", 39 | overview: 40 | "A simple application built to help tradespeople automate the quotation process.", 41 | stack: ["Html", "React", "Next"], 42 | link: "https://quotr.vercel.app", 43 | repo: null, 44 | isSiteLive: true, 45 | }, 46 | { 47 | title: "GPS Embroidery", 48 | overview: 49 | "A fully-responsive and quick-rendering image-based website for an ongoing academic project.", 50 | stack: ["Html", "React", "Next"], 51 | link: "http://www.gps-embroidery.com", 52 | repo: "https://github.com/danielcranney/GPS-Embroidery", 53 | isSiteLive: true, 54 | }, 55 | ]; 56 | --------------------------------------------------------------------------------