simo 1 kuukausi sitten
vanhempi
sitoutus
7b78f6f601

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 2846 - 934
pnpm-lock.yaml


BIN
src/assets/dualingo.png


+ 206 - 0
src/components/duolingo.astro

@@ -0,0 +1,206 @@
+---
+import duolingoImg from "../assets/dualingo.png";
+---
+
+<div id="duolingo" class="window duolingo-popup" style="display: none; position: fixed; top: 20px; right: 20px; width: 320px; z-index: 1000;">
+	<div class="title-bar" id="duolingoheader">
+		<div class="title-bar-text">Duolingo Stats</div>
+		<div class="title-bar-controls">
+			<button class="close" aria-label="Minimize"></button>
+			<button class="close" aria-label="Close"></button>
+		</div>
+	</div>
+	<div class="window-body">
+		<div id="loading-message" style="text-align: center; padding: 20px;">
+			<p>⏳ Loading stats...</p>
+		</div>
+		<div id="duolingo-content" style="display: none;">
+			<div class="card">
+				<div class="card-body">
+					<div class="streak-display">
+						<div class="streak-info">
+							<div class="streak-number" id="streak-number">0</div>
+							<div class="streak-label">Day Streak</div>
+						</div>
+						<div class="mascot">
+							<img src={duolingoImg.src} alt="Duo" />
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+		<div id="error-message" style="display: none; text-align: center; padding: 20px; color: #800000;">
+			<p>⚠ Failed to load stats</p>
+		</div>
+	</div>
+</div>
+
+<script>
+	const styleElement = document.createElement('style');
+	styleElement.textContent = `
+		#duolingo.window {
+			font-family: "Pixelated MS Sans Serif", Arial, sans-serif;
+			background-color: #c0c0c0;
+			border: 2px solid;
+			border-color: #ffffff #808080 #808080 #ffffff;
+			box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.5);
+		}
+
+		#duolingo .title-bar {
+			background: linear-gradient(90deg, #000080, #1084d0);
+			padding: 3px 5px;
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+			cursor: move;
+		}
+
+		#duolingo .title-bar-text {
+			color: white;
+			font-weight: bold;
+			font-size: 11px;
+		}
+
+		#duolingo .close {
+			width: 16px;
+			height: 14px;
+			background-color: #c0c0c0;
+			border: 1px solid;
+			border-color: #ffffff #000000 #000000 #ffffff;
+			cursor: pointer;
+			position: relative;
+			padding: 0;
+		}
+
+		#duolingo .close::before {
+			content: "×";
+			position: absolute;
+			top: -3px;
+			left: 3px;
+			font-size: 14px;
+			color: #000000;
+		}
+
+		#duolingo .close:active {
+			border-color: #000000 #ffffff #ffffff #000000;
+		}
+
+		#duolingo .window-body {
+			background-color: #c0c0c0;
+
+		}
+
+		#duolingo #loading-message p,
+		#duolingo #error-message p {
+			font-size: 11px;
+			margin: 0;
+		}
+
+		#duolingo .card {
+			background: linear-gradient(135deg, #2d2d2d 0%, #1a1a1a 100%);
+			border: 2px solid;
+			border-color: #808080 #ffffff #ffffff #808080;
+			border-radius: 0;
+		}
+
+		#duolingo .card-body {
+			padding: 15px;
+		}
+
+		#duolingo .streak-display {
+			display: flex;
+			align-items: center;
+			justify-content: space-around;
+			gap: 20px;
+		}
+
+		#duolingo .streak-info {
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			gap: 4px;
+			flex: 1;
+		}
+
+
+		#duolingo .streak-number {
+			font-size: 36px;
+			font-weight: bold;
+			color: #ff69b4;
+			line-height: 1;
+		}
+
+		#duolingo .streak-label {
+			font-size: 11px;
+			color: #ffffff;
+			opacity: 0.9;
+			text-transform: uppercase;
+			letter-spacing: 1px;
+		}
+
+		#duolingo .mascot {
+			position: relative;
+			width: 70px;
+			height: 70px;
+			flex-shrink: 0;
+			transform: scale(1.3);
+		}
+
+		#duolingo .mascot img {
+			width: 100%;
+			height: 100%;
+			object-fit: contain;
+			image-rendering: pixelated;
+			image-rendering: -moz-crisp-edges;
+			image-rendering: crisp-edges;
+		}
+	`;
+	document.head.appendChild(styleElement);
+
+	async function loadDuolingoStats() {
+		const username = 's1monlol';
+		const content = document.getElementById('duolingo-content');
+		const loadingMsg = document.getElementById('loading-message');
+		const errorMsg = document.getElementById('error-message');
+
+		if (loadingMsg) loadingMsg.style.display = 'block';
+		if (content) content.style.display = 'none';
+		if (errorMsg) errorMsg.style.display = 'none';
+
+		try {
+			const response = await fetch(`https://www.duolingo.com/2017-06-30/users?username=${username}`);
+			const data = await response.json();
+			const user = data.users[0];
+
+			if (!user) throw new Error('User not found');
+
+			// Update streak
+			const streakEl = document.getElementById('streak-number');
+			if (streakEl) streakEl.textContent = user.streak.toLocaleString();
+
+			if (content) content.style.display = 'block';
+			if (loadingMsg) loadingMsg.style.display = 'none';
+
+		} catch (error) {
+			console.error('Error loading Duolingo stats:', error);
+			if (errorMsg) errorMsg.style.display = 'block';
+			if (loadingMsg) loadingMsg.style.display = 'none';
+		}
+	}
+
+	const observer = new MutationObserver((mutations) => {
+		mutations.forEach((mutation) => {
+			// @ts-ignore
+			if (mutation.target.style.display === 'block') {
+				loadDuolingoStats();
+				observer.disconnect();
+				return;
+			}
+		});
+	});
+
+	const duolingoWindow = document.getElementById('duolingo');
+	if (duolingoWindow) {
+		observer.observe(duolingoWindow, { attributes: true, attributeFilter: ['style'] });
+	}
+</script>

+ 74 - 0
src/components/githubgraph.astro

@@ -0,0 +1,74 @@
+---
+---
+
+<div id="github-graph" class="window duolingo-popup" style="display: none; position: fixed; top: 60px; left: 60px; z-index: 1000;">
+	<div class="title-bar" id="githubgraphheader">
+		<div class="title-bar-text">Top Languages</div>
+		<div class="title-bar-controls">
+			<button class="close" aria-label="Minimize"></button>
+			<button class="close" aria-label="Close"></button>
+		</div>
+	</div>
+	<div class="window-body" style="padding: 10px;">
+		<iframe
+			src="https://github-readme-stats.vercel.app/api/top-langs/?username=S1monlol&theme=github_dark"
+			style="width: 100%; height: 300px; border: none; image-rendering: pixelated; image-rendering: -moz-crisp-edges; image-rendering: crisp-edges;"
+			frameborder="0"
+		></iframe>
+	</div>
+</div>
+
+
+
+<style>
+	#github-graph.window {
+		font-family: "Pixelated MS Sans Serif", Arial, sans-serif;
+		background-color: #c0c0c0;
+		border: 2px solid;
+		border-color: #ffffff #808080 #808080 #ffffff;
+		box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.5);
+	}
+
+	#github-graph .title-bar {
+		background: linear-gradient(90deg, #000080, #1084d0);
+		padding: 3px 5px;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		cursor: move;
+	}
+
+	#github-graph .title-bar-text {
+		color: white;
+		font-weight: bold;
+		font-size: 11px;
+	}
+
+	#github-graph .close {
+		width: 16px;
+		height: 14px;
+		background-color: #c0c0c0;
+		border: 1px solid;
+		border-color: #ffffff #000000 #000000 #ffffff;
+		cursor: pointer;
+		position: relative;
+		padding: 0;
+	}
+
+	#github-graph .close::before {
+		content: "×";
+		position: absolute;
+		top: -3px;
+		left: 3px;
+		font-size: 14px;
+		color: #000000;
+	}
+
+	#github-graph .close:active {
+		border-color: #000000 #ffffff #ffffff #000000;
+	}
+
+	#github-graph .window-body {
+		background-color: #c0c0c0;
+	}
+</style>

+ 15 - 0
src/components/projects/imdb.astro

@@ -30,3 +30,18 @@
 		</div>
 	</div>
 </div>
+
+<style>
+	#imdb {
+		animation: createBox 0.2s;
+	}
+
+	@keyframes createBox {
+		from {
+			transform: scale(0);
+		}
+		to {
+			transform: scale(1);
+		}
+	}
+</style>

+ 15 - 0
src/components/projects/notion.astro

@@ -36,3 +36,18 @@
 		document.getElementById("vid").style.display = "none";
 	}
 </script>
+
+<style>
+	#notion {
+		animation: createBox 0.2s;
+	}
+
+	@keyframes createBox {
+		from {
+			transform: scale(0);
+		}
+		to {
+			transform: scale(1);
+		}
+	}
+</style>

+ 17 - 2
src/components/projects/summize.astro

@@ -19,12 +19,12 @@ import summizeExample from "../../assets/summizeExample.png";
 			<!-- prettier-ignore -->
 			<div
         id="text207"
-        style="font-size: 22px; resize: none; padding: 10px; height:90%; overflow-y: auto; padding-top: 0px;" 
+        style="font-size: 22px; resize: none; padding: 10px; height:90%; overflow-y: auto; padding-top: 0px;"
         >
 		<a href="https://github.com/S1monlol/summize"><h1 style="width: fit-content;"><i><u><b>Summize - A Youtube Video Summarizer</b></u></i></h1></a>
 		<img src={summizeExample.src}>
 		<h2>
-			Summize is a browser extension that summarizes YouTube videos using OpenAI's GPT-3.5 language model. 
+			Summize is a browser extension that summarizes YouTube videos using OpenAI's GPT-3.5 language model.
 
 
 		</h2>
@@ -32,3 +32,18 @@ import summizeExample from "../../assets/summizeExample.png";
 		</div>
 	</div>
 </div>
+
+<style>
+	#summize {
+		animation: createBox 0.2s;
+	}
+
+	@keyframes createBox {
+		from {
+			transform: scale(0);
+		}
+		to {
+			transform: scale(1);
+		}
+	}
+</style>

+ 2 - 0
src/components/taskbar.astro

@@ -9,6 +9,8 @@
     <!-- <img class="h-full w-full" src={start.src} alt="start" /> -->
     <h1 id="start-button"></h1>
   </button>
+  <div id="taskbar-windows" style="display: flex; gap: 0.25rem; margin-left: 0.5rem; flex: 1; overflow-x: auto;">
+  </div>
 </div>
 
 <style>

+ 1 - 1
src/layouts/Layout.astro

@@ -15,7 +15,7 @@ const { title } = Astro.props;
 		<meta content="#ff0504" name="theme-color">
 		<link type="application/json+oembed" href="https://simo.ng/oembed.json" />
 		<link rel="icon" type="image/svg+xml" href="/favicon.gif" />
-		<link rel="stylesheet" href="https://unpkg.com/98.css" />
+		<link rel="stylesheet" href="https://unpkg.com/98.css@0.1.16" />
 		<meta name="generator" content={Astro.generator} />
 		<title>{title}</title>
 	</head>

+ 211 - 5
src/pages/index.astro

@@ -8,6 +8,8 @@ import Redirect from "../components/redirect.astro";
 import Contact from "../components/contact.astro";
 import Pgp from "../components/pgp.astro";
 import Projects from "../components/projects.astro";
+import Duolingo from "../components/duolingo.astro";
+import GitHubGraph from "../components/githubgraph.astro";
 
 // Projects
 import Summize from "../components/projects/summize.astro";
@@ -258,6 +260,8 @@ apps = apps.map((item) => {
 		<Summize />
 		<Notion />
 		<Imdb />
+		<Duolingo />
+		<GitHubGraph />
 	</div>
 	<Taskbar />
 </Layout>
@@ -317,7 +321,6 @@ apps = apps.map((item) => {
 			setTimeout(() => {
 				document.getElementById(id).style.display = "block";
 				raiseUpSpec(id);
-				// console.log("test");
 			}, 1);
 		};
 	});
@@ -347,10 +350,19 @@ apps = apps.map((item) => {
 			}
 		);
 
-		Array.from(window.getElementsByClassName("close")).forEach((button) => {
-			button.addEventListener("click", () => {
-				window.style.display = "none";
-			});
+		Array.from(window.getElementsByClassName("close")).forEach((button, index) => {
+			if (index === 0) {
+				// First button is minimize
+				button.addEventListener("click", () => {
+					minimizeWindow(window.id);
+				});
+			} else {
+				// Second button is close
+				button.addEventListener("click", () => {
+					window.style.display = "none";
+					removeFromTaskbar(window.id);
+				});
+			}
 		});
 	});
 
@@ -368,4 +380,198 @@ apps = apps.map((item) => {
 			appFunc();
 		});
 	});
+
+	// Minimize and restore window functions
+	function minimizeWindow(windowId) {
+		const window = document.getElementById(windowId);
+
+		// First add to taskbar to get the button position
+		addToTaskbar(windowId);
+
+		// Get window and target button positions
+		const windowRect = window.getBoundingClientRect();
+		const taskbarButton = document.getElementById(`taskbar-${windowId}`);
+		const buttonRect = taskbarButton.getBoundingClientRect();
+
+		// Calculate the translation needed
+		const translateX = buttonRect.left - windowRect.left;
+		const translateY = buttonRect.top - windowRect.top;
+
+		// Create custom animation
+		window.style.transition = "all 0.3s ease-out";
+		window.style.transform = `translate(${translateX}px, ${translateY}px) scale(0.1)`;
+		window.style.opacity = "0";
+
+		setTimeout(() => {
+			window.style.display = "none";
+			window.style.transition = "";
+			window.style.transform = "";
+			window.style.opacity = "";
+		}, 300);
+	}
+
+	function restoreWindow(windowId) {
+		const window = document.getElementById(windowId);
+		const taskbarButton = document.getElementById(`taskbar-${windowId}`);
+		const buttonRect = taskbarButton.getBoundingClientRect();
+		const windowRect = window.getBoundingClientRect();
+
+		// Calculate starting position
+		const translateX = buttonRect.left - windowRect.left;
+		const translateY = buttonRect.top - windowRect.top;
+
+		// Set initial state
+		window.style.transform = `translate(${translateX}px, ${translateY}px) scale(0.1)`;
+		window.style.opacity = "0";
+		window.style.display = "block";
+
+		// Remove from taskbar
+		removeFromTaskbar(windowId);
+		raiseUpSpec(windowId);
+
+		// Trigger animation after a frame
+		requestAnimationFrame(() => {
+			window.style.transition = "all 0.3s ease-out";
+			window.style.transform = "translate(0, 0) scale(1)";
+			window.style.opacity = "1";
+
+			setTimeout(() => {
+				window.style.transition = "";
+				window.style.transform = "";
+				window.style.opacity = "";
+			}, 300);
+		});
+	}
+
+	function addToTaskbar(windowId) {
+		const taskbarWindows = document.getElementById("taskbar-windows");
+
+		// Check if already in taskbar
+		if (document.getElementById(`taskbar-${windowId}`)) {
+			return;
+		}
+
+		const window = document.getElementById(windowId);
+		const titleBar = window.querySelector(".title-bar-text");
+		const title = titleBar ? titleBar.textContent : windowId;
+
+		// Get icon for the window
+		const iconMap = {
+			'aboutme': '👤',
+			'contact': '📧',
+			'pgp': '🔐',
+			'projects': '📁',
+			'duolingo': '🦉',
+			'redirect': '🔗',
+			'summize': '📝',
+			'notion': '📋',
+			'imdb': '🎬'
+		};
+		const icon = iconMap[windowId] || '📄';
+
+		const button = document.createElement("button");
+		button.id = `taskbar-${windowId}`;
+		button.style.cssText = `
+			min-width: 140px;
+			max-width: 180px;
+			height: 2.25rem;
+			padding: 0 0.5rem;
+			overflow: hidden;
+			cursor: pointer;
+			background-color: #c0c0c0;
+			box-shadow: -2px -2px #e0dede, -2px 0 #e0dede, 0 -2px #e0dede, -4px -4px white, -4px 0 white, 0 -4px white, 2px 2px #818181, 0 2px #818181, 2px 0 #818181, 2px -2px #e0dede, -2px 2px #818181, -4px 2px white, -4px 4px black, 4px 4px black, 4px 0 black, 0 4px black, 2px -4px white, 4px -4px black;
+			display: flex;
+			align-items: center;
+			gap: 0.35rem;
+			border: none;
+		`;
+
+		// Create icon span
+		const iconSpan = document.createElement("span");
+		iconSpan.textContent = icon;
+		iconSpan.style.cssText = `
+			font-size: 18px;
+			flex-shrink: 0;
+			line-height: 1;
+		`;
+
+		// Create text span
+		const textSpan = document.createElement("span");
+		textSpan.textContent = title;
+		textSpan.style.cssText = `
+			overflow: hidden;
+			text-overflow: ellipsis;
+			white-space: nowrap;
+			font-weight: bold;
+			font-size: 11px;
+			letter-spacing: 0.3px;
+		`;
+
+		button.appendChild(iconSpan);
+		button.appendChild(textSpan);
+
+		// Add active state styling
+		button.addEventListener("mousedown", () => {
+			button.style.boxShadow = "-2px -2px #818181, -2px 0 #818181, 0 -2px #818181, -4px -4px black, -4px 0 black, 0 -4px black, 2px 2px #e0dede, 0 2px #e0dede, 2px 0 #e0dede, 2px -2px #818181, -2px 2px #e0dede, -4px 2px black, -4px 4px white, 4px 4px white, 4px 0 white, 0 4px white, 2px -4px black, 4px -4px white";
+		});
+
+		button.addEventListener("mouseup", () => {
+			button.style.boxShadow = "-2px -2px #e0dede, -2px 0 #e0dede, 0 -2px #e0dede, -4px -4px white, -4px 0 white, 0 -4px white, 2px 2px #818181, 0 2px #818181, 2px 0 #818181, 2px -2px #e0dede, -2px 2px #818181, -4px 2px white, -4px 4px black, 4px 4px black, 4px 0 black, 0 4px black, 2px -4px white, 4px -4px black";
+		});
+
+		button.addEventListener("click", () => {
+			restoreWindow(windowId);
+		});
+
+		taskbarWindows.appendChild(button);
+	}
+
+	function removeFromTaskbar(windowId) {
+		const button = document.getElementById(`taskbar-${windowId}`);
+		if (button) {
+			button.remove();
+		}
+	}
+
+	// Function to show Duolingo with animation
+	function showDuolingo() {
+		const duolingoWindow = document.getElementById("duolingo");
+		duolingoWindow.style.display = "block";
+		duolingoWindow.classList.add("show");
+		raiseUpSpec("duolingo");
+
+		// Remove animation class after it completes
+		setTimeout(() => {
+			duolingoWindow.classList.remove("show");
+		}, 400);
+	}
+
+	// Function to show GitHub graph with animation
+	function showGitHubGraph() {
+		const githubWindow = document.getElementById("github-graph");
+		githubWindow.style.display = "block";
+		githubWindow.classList.add("show");
+		raiseUpSpec("github-graph");
+
+		// Remove animation class after it completes
+		setTimeout(() => {
+			githubWindow.classList.remove("show");
+		}, 400);
+	}
+
+	// Track start button clicks
+	let startButtonClicks = 0;
+	const startButton = document.getElementById("start-button");
+	if (startButton) {
+		startButton.addEventListener("click", () => {
+			startButtonClicks++;
+			if (startButtonClicks === 1) {
+				showDuolingo();
+			} else if (startButtonClicks === 2) {
+				showGitHubGraph();
+			}
+		});
+	}
+
+	// Don't auto-show Duolingo anymore - wait for start button click
 </script>

+ 40 - 3
src/styles/global.css

@@ -8,13 +8,50 @@
 	}
 }
 
-
-
 .fullscreen {
 	z-index: 9999 !important;
 	width: 100% !important;
 	height: calc(100vh - 3.5rem) !important;
 	top: 0px !important;
 	left: 0px !important;
-  
+}
+
+@keyframes minimizeWindow {
+	0% {
+		opacity: 1;
+		transform: scale(1) translateY(0);
+	}
+	100% {
+		opacity: 0;
+		transform: scale(0.1) translateY(calc(100vh - 100%));
+	}
+}
+
+@keyframes restoreWindow {
+	0% {
+		opacity: 0;
+		transform: scale(0.1) translateY(calc(100vh - 100%));
+	}
+	100% {
+		opacity: 1;
+		transform: scale(1) translateY(0);
+	}
+}
+
+@keyframes duolingoPopup {
+	0% {
+		opacity: 0;
+		transform: scale(0) translateY(20px);
+	}
+	50% {
+		transform: scale(1.1) translateY(-5px);
+	}
+	100% {
+		opacity: 1;
+		transform: scale(1) translateY(0);
+	}
+}
+
+.duolingo-popup.show {
+	animation: duolingoPopup 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
 }

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä