diff --git a/public/images/portfolio1.jpg b/public/images/portfolio1.jpg
new file mode 100644
index 0000000..545e94a
Binary files /dev/null and b/public/images/portfolio1.jpg differ
diff --git a/public/images/portfolio2.jpg b/public/images/portfolio2.jpg
new file mode 100644
index 0000000..545e94a
Binary files /dev/null and b/public/images/portfolio2.jpg differ
diff --git a/src/app/components/FadeInSection.js b/src/app/components/FadeInSection.js
new file mode 100644
index 0000000..c80d999
--- /dev/null
+++ b/src/app/components/FadeInSection.js
@@ -0,0 +1,54 @@
+"use client";
+
+import { useRef, useState, useEffect } from 'react';
+
+const FadeInSection = ({ children }) => {
+ const domRef = useRef();
+ const [hasBeenVisible, setHasBeenVisible] = useState(false);
+ const [opacity, setOpacity] = useState(0);
+
+ useEffect(() => {
+ const thresholds = Array.from({ length: 101 }, (_, i) => i / 100);
+ const observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ // If 30% or more of the element is visible and it hasn't been set visible before...
+ if (entry.intersectionRatio >= 0.3 && !hasBeenVisible) {
+ setHasBeenVisible(true);
+ setOpacity(1);
+ } else if (!hasBeenVisible) {
+ // Gradually update opacity until the threshold is met
+ setOpacity(entry.intersectionRatio);
+ }
+ });
+ },
+ {
+ threshold: thresholds,
+ // A slight negative bottom margin delays the fade until more of the section is in view
+ rootMargin: "0px 0px -20% 0px",
+ }
+ );
+ if (domRef.current) {
+ observer.observe(domRef.current);
+ }
+ return () => {
+ observer.disconnect();
+ };
+ }, [hasBeenVisible]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default FadeInSection;
\ No newline at end of file
diff --git a/src/app/globals.css b/src/app/globals.css
index 045a4b0..e98ae2d 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,3 +1,13 @@
+/* Global Styles */
+body {
+ background: linear-gradient(135deg, #000000, #1a001a);
+ font-size: 1.125rem;
+ font-family: 'Helvetica', sans-serif;
+ color: #ffffff;
+ margin: 0;
+ padding: 0;
+}
+
.container {
max-width: 1200px;
margin: 0 auto;
@@ -12,58 +22,86 @@
.title {
font-size: 2.5rem;
margin-bottom: 0.5rem;
+ color: #ffffff;
+}
+
+/* Title animation classes */
+.title.normal {
+ opacity: 1;
+}
+.title.fadeOut {
+ animation: fadeOutAnimation 0.5s forwards;
+}
+.title.fadeIn {
+ animation: fadeInAnimation 0.5s forwards;
+}
+
+@keyframes fadeOutAnimation {
+ from { opacity: 1; }
+ to { opacity: 0; }
+}
+@keyframes fadeInAnimation {
+ from { opacity: 0; }
+ to { opacity: 1; }
}
.intro {
- font-size: 1.25rem;
- color: #666;
+ font-size: 1.5rem;
+ color: #cccccc;
+}
+
+/* Default fade-in section: occupies at least one full viewport */
+.fade-in-section {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ transform: translateY(20px);
+ transition: opacity 0.3s ease-out, transform 0.3s ease-out;
+ will-change: opacity, transform;
+}
+
+/* Custom override for the first content section */
+.firstSection {
+ min-height: auto; /* Remove full viewport height requirement */
+ padding: 2rem 0; /* Add vertical padding if needed */
}
.section {
display: flex;
align-items: center;
justify-content: space-between;
- margin-bottom: 2rem;
-}
-
-/* Section where text is on the left */
-.textLeft {
- flex: 1;
- padding: 1rem;
-}
-
-/* Section where image is on the right */
-.imageRight {
- flex: 1;
- padding: 1rem;
-}
-
-/* Section where image is on the left */
-.imageLeft {
- flex: 1;
- padding: 1rem;
-}
-
-/* Section where button is on the right */
-.buttonRight {
- flex: 1;
- padding: 1rem;
- display: flex;
- justify-content: flex-end;
-}
-
-/* Ensure images are responsive */
-.imageLeft img,
-.imageRight img {
width: 100%;
+}
+
+.textLeft,
+.imageRight,
+.imageLeft,
+.buttonLeft {
+ padding: 1rem;
+ flex: 1;
+}
+
+/* Button container aligned to the left */
+.buttonLeft {
+ display: flex;
+ justify-content: flex-start;
+}
+
+/* Slightly smaller images */
+.imageRight img,
+.imageLeft img {
+ width: 90%;
height: auto;
border-radius: 8px;
}
+/* Enlarged Learn More button with dark purple tones */
.learnMoreButton {
- padding: 0.75rem 1.5rem;
- font-size: 1rem;
- background-color: #0070f3;
+ padding: 1rem 2rem;
+ font-size: 1.25rem;
+ background-color: #3d003d;
border: none;
border-radius: 5px;
color: #fff;
@@ -72,5 +110,38 @@
}
.learnMoreButton:hover {
- background-color: #005bb5;
-}
\ No newline at end of file
+ background-color: #2e002e;
+}
+
+/* Scroll Prompt styling with no background */
+.scrollPrompt {
+ position: fixed;
+ bottom: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ padding: 0.5rem 1rem;
+ border-radius: 5px;
+ color: #fff;
+ font-size: 1rem;
+ z-index: 1000;
+ background: none;
+}
+
+/* When section is fully visible */
+.fade-in-section.is-visible {
+ opacity: 1;
+ transform: none;
+}
+
+/* Hide scrollbar for Chrome, Safari, Opera */
+::-webkit-scrollbar {
+ display: none;
+}
+
+/* Hide scrollbar for IE, Edge and Firefox */
+body {
+ -ms-overflow-style: none; /* IE and Edge */
+ scrollbar-width: none; /* Firefox */
+}
+
+
diff --git a/src/app/page.js b/src/app/page.js
index 890dde9..f7af429 100644
--- a/src/app/page.js
+++ b/src/app/page.js
@@ -1,51 +1,101 @@
+"use client";
+
+import { useState, useEffect } from "react";
import Image from "next/image";
+import Link from "next/link";
+import FadeInSection from "./components/FadeInSection";
export default function Home() {
+ const [showScrollPrompt, setShowScrollPrompt] = useState(false);
+ const [title, setTitle] = useState("Fretux");
+ const [phase, setPhase] = useState("normal");
+
+ // Title flicker effect: every 3 seconds, fade out, toggle title, then fade in.
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setPhase("fadeOut");
+ setTimeout(() => {
+ setTitle((prev) => (prev === "Fretux" ? "Frederik Spirgi" : "Fretux"));
+ setPhase("fadeIn");
+ setTimeout(() => {
+ setPhase("normal");
+ }, 500);
+ }, 500);
+ }, 3000);
+ return () => clearInterval(interval);
+ }, []);
+
+ // Show scroll prompt after a delay (once the title has loaded in).
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ setShowScrollPrompt(true);
+ }, 1500); // Adjust the delay as needed
+ return () => clearTimeout(timer);
+ }, []);
+
+ // Remove the scroll prompt permanently upon first scroll.
+ useEffect(() => {
+ const handleScroll = () => {
+ setShowScrollPrompt(false);
+ window.removeEventListener("scroll", handleScroll);
+ };
+
+ window.addEventListener("scroll", handleScroll);
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, []);
+
return (
-
- {/* Header with title and introduction */}
-
+
+ {showScrollPrompt && (
+
+ )}
- {/* Section 1: Text on left, image on right */}
-
-
-
- I specialize in building responsive, dynamic web applications that deliver exceptional user experiences.
+
+
-
-
-
-
+
+
- {/* Section 2: Image on left, button on right */}
-
-
-
-
-
-
-
-
+
+
+
+
+ I specialize in creating intuitive user experiences and dynamic web solutions using Next.js, React, and modern design principles.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
-}
+}
\ No newline at end of file