simo 2 týždňov pred
rodič
commit
f0f63774e7
3 zmenil súbory, kde vykonal 438 pridanie a 100 odobranie
  1. 18 52
      src/components/contact.astro
  2. 394 33
      src/components/projects.astro
  3. 26 15
      src/pages/index.astro

+ 18 - 52
src/components/contact.astro

@@ -1,55 +1,21 @@
-<div
-	id="contact"
-	class="window"
-	style="position: absolute; left: 15%; top: 22%; display: none;"
+---
+import Win98Window from './win98window.astro';
+---
+
+<Win98Window
+	title="contact"
+	layout={{
+		desktop: { left: '15%', top: '22%', width: '30%', height: 'auto' },
+		mobile: { left: '10%', top: '15%', width: '80%', height: 'auto' },
+	}}
 >
-	<div id="contactheader" class="title-bar">
-		<div class="title-bar-text">Contact</div>
-		<div class="title-bar-controls">
-			<button class="close" aria-label="Minimize" style="background-color: silver;"></button>
-			<button class="close" aria-label="Close" style="background-color: silver;"></button>
-		</div>
-	</div>
-	<div class="window-body">
-		<div class="field-row-stacked">
-			<!-- prettier-ignore -->
-			<textarea
-          readonly
-          id="text203"
-          style="font-size: 22px; resize: none; padding: 10px;"
-          >Email : simon@simo.ng
+	<div class="field-row-stacked">
+		<!-- prettier-ignore -->
+		<textarea
+			readonly
+			id="text203"
+			style="font-size: 22px; resize: none; padding: 10px;"
+			>Email : simon@simo.ng
 Discord : s1monlol</textarea>
-		</div>
 	</div>
-</div>
-
-<style>
-	/* Default style for mobile and smaller devices */
-	#contact {
-		width: 30%;
-
-		animation: createBox 0.2s;
-	}
-
- @keyframes createBox {
-    	from {
-    		transform: scale(0);
-    	}
-    	to {
-    		transform: scale(1);
-    	}
-    }
-
-
-	/* Style for tablets, desktops, and larger devices */
-	@media (max-width: 540px) {
-		#contact {
-			width: 80%;
-		}
-	}
-	@media (max-width: 1080px) and (min-width: 540px) {
-		#contact {
-			width: 60%;
-		}
-	}
-</style>
+</Win98Window>

+ 394 - 33
src/components/projects.astro

@@ -10,9 +10,8 @@ let projects = Astro.props.projects;
 <Win98Window
   title="Projects "
   layout={{
-    mobile:  { left: '5%', top: '20%', width: '90%', height: '60vh' },
-    // tablet:  { left: '30%', top: '20%', width: '50%', height: 'auto' },
-    desktop: { left: "25%", top: "20%", width: "50%", height: '60vh' }
+    mobile:  { left: '3%', top: '10%', width: '94%', height: '78vh' },
+    desktop: { left: '10%', top: '10%', width: '80%', height: '75vh' }
   }}
 >
 		<div id="inner">
@@ -20,31 +19,144 @@ let projects = Astro.props.projects;
 				<span class="explorer-path">C:\Users\simon\Projects</span>
 			</div>
 			<div class="explorer-files">
-				<div id="projects-container">
-				{
-					projects.map((item) => (
-						<div
-							class="project"
-							id={`${item.id}item`}
-							tabindex="0"
-							style="display: flex; flex-direction: row; align-items: center; justify-content: flex-start; margin: 0px;"
-						>
-							<img src={scriptIcon.src} class="w-auto h-8" alt="" />
-							<h3
-								class="text-lg"
-								style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: left;"
-							>
-								{item.title}
-							</h3>
+					<div id="projects-container">
+						<div class="projects-grid" >
+							{projects.map((item) => (
+								<button
+									class="project"
+									id={`${item.id}item`}
+									type="button"
+									data-project={item.id}
+									aria-label={`Open ${item.title}`}
+								>
+									<span class="project-icon" aria-hidden="true"><img src={scriptIcon.src} class="w-auto h-8" alt="" /></span>
+									<span class="project-title">{item.title}</span>
+									<span class="project-meta" aria-hidden="true">Application</span>
+								</button>
+							))}
 						</div>
-					))
-				}
-
-				</div>
+						<div class="projects-details" aria-live="polite">
+							<div class="details-header">
+								<div class="details-title" id="project-details-title">Select a project</div>
+								<div class="details-actions">
+									<button class="details-btn" id="project-open" type="button" disabled>Open</button>
+									<button class="details-btn" id="project-repo" type="button" disabled>Repo</button>
+								</div>
+							</div>
+							<div class="details-body">
+								<div class="details-blurb" id="project-details-blurb">Click once to preview. Double-click still opens.</div>
+								<div class="details-framewrap" id="project-details-framewrap" hidden>
+									<iframe id="project-details-frame" title="Project preview" loading="lazy" referrerpolicy="no-referrer" sandbox="allow-scripts allow-same-origin allow-forms allow-popups"></iframe>
+								</div>
+							</div>
+						</div>
+					</div>
 			</div>
 		</div>
 </Win98Window>
 
+
+<script>
+	(function () {
+		const root = document.currentScript?.closest('.window') || document;
+		const container = root.querySelector('#projects-container');
+		if (!container) return;
+
+		const grid = container.querySelector('.projects-grid');
+		if (!grid) return;
+
+		const titleEl = container.querySelector('#project-details-title');
+		const blurbEl = container.querySelector('#project-details-blurb');
+		const frameWrap = container.querySelector('#project-details-framewrap');
+		const frame = container.querySelector('#project-details-frame');
+		const openBtn = container.querySelector('#project-open');
+		const repoBtn = container.querySelector('#project-repo');
+
+		const projects = {
+			mikro: {
+				title: 'Mikro',
+				previewUrl: 'https://mikro.simo.ng',
+				repoUrl: 'https://git.simo.ng/simo/Mikro',
+				blurb: 'Minimal, opinionated RSS reader.',
+			},
+			glance: {
+				title: 'Glance',
+				previewUrl: 'https://glance.simo.ng',
+				repoUrl: 'https://git.simo.ng/simo/Glance',
+				blurb: 'Idle dashboard optimized for E-Ink displays.',
+			},
+			mneme: {
+				title: 'Mneme',
+				previewUrl: null,
+				repoUrl: 'https://git.simo.ng/simo/mneme',
+				blurb: 'Tiny Spotify microservice.',
+			},
+		};
+
+		function openRedirect(url) {
+			if (!url) return;
+			window.open(url, '_blank', 'noopener,noreferrer');
+		}
+
+		function setSelected(projectId) {
+			const p = projects[projectId];
+			if (!p) return;
+
+			container.querySelectorAll('.project').forEach((el) => {
+				el.toggleAttribute('data-selected', el.getAttribute('data-project') === projectId);
+			});
+
+			if (titleEl) titleEl.textContent = p.title;
+			if (blurbEl) blurbEl.textContent = p.blurb || '';
+
+			const openUrl = p.previewUrl;
+			if (openBtn) {
+				openBtn.disabled = !openUrl;
+				openBtn.onclick = () => openRedirect(openUrl);
+			}
+			if (repoBtn) {
+				repoBtn.disabled = !p.repoUrl;
+				repoBtn.onclick = () => openRedirect(p.repoUrl);
+			}
+
+			if (frameWrap && frame) {
+				if (p.previewUrl) {
+					frameWrap.hidden = false;
+					frame.src = p.previewUrl;
+				} else {
+					frameWrap.hidden = true;
+					frame.removeAttribute('src');
+				}
+			}
+		}
+
+
+		// Prevent the homepage window-spawning click handler from running.
+		container.querySelectorAll('.project').forEach((pbtn) => {
+			pbtn.addEventListener('click', (e) => {
+				e.stopImmediatePropagation();
+				e.stopPropagation();
+				e.preventDefault();
+				const id = pbtn.getAttribute('data-project');
+				if (id) setSelected(id);
+			});
+			pbtn.addEventListener('dblclick', (e) => {
+				e.stopImmediatePropagation();
+				e.stopPropagation();
+				e.preventDefault();
+				const id = pbtn.getAttribute('data-project');
+				if (!id) return;
+				const p = projects[id];
+				if (!p) return;
+				openRedirect(p.previewUrl || p.repoUrl);
+			});
+		});
+
+		// Pick first project by default for quick scanning
+		const first = container.querySelector('.project[data-project]');
+		if (first) setSelected(first.getAttribute('data-project'));
+	})();
+</script>
 <style>
 	#inner {
 		height: 100%;
@@ -94,6 +206,8 @@ let projects = Astro.props.projects;
 	/* File area: add a little inner margin like Explorer */
 	.explorer-files {
 		padding: 6px;
+		height: calc(100% - 44px);
+		box-sizing: border-box;
 	}
 
 
@@ -136,17 +250,26 @@ let projects = Astro.props.projects;
 	}
 
 	#projects-container {
-		display: flex;
-		flex-direction: column;
-		gap: 0;
-		padding: 0;
-		width: 100%;
-		max-width: 100%;
-		box-sizing: border-box;
+		display: grid;
+		grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr);
+		grid-template-rows: auto 1fr;
+		grid-template-areas:
+			"status details"
+			"grid details";
+		gap: 8px;
+		min-height: 0;
+		height: 100%;
+	}
 
-		height: calc(100% - 64px);
-		overflow-y: auto;
-		overflow-x: hidden;
+
+	.projects-grid {
+		grid-area: grid;
+		min-height: 0;
+	}
+
+	.projects-details {
+		grid-area: details;
+		min-height: 0;
 	}
 
 	/* Ensure the scroll area never spills past the inner frame */
@@ -159,4 +282,242 @@ let projects = Astro.props.projects;
 		background: #e6e6e6;
 		border-color: #808080;
 	}
+
+	.projects-status {
+		display: flex;
+		align-items: center;
+		gap: 6px;
+		padding: 6px 8px;
+		margin: 6px;
+		background: #efefef;
+		border: 1px solid #808080;
+		box-shadow: inset 0 1px #fff;
+		font-size: 12px;
+	}
+
+
+	.projects-grid {
+		height: 100%;
+		overflow: auto;
+		padding: 6px;
+		margin: 0 6px 6px;
+		background: #fff;
+		box-shadow: inset -1px -1px #fff, inset 1px 1px grey, inset -2px -2px #dfdfdf,
+			inset 2px 2px #0a0a0a;
+	}
+
+	.projects-grid {
+		display: flex;
+		flex-direction: column;
+		gap: 2px;
+	}
+
+
+	.project {
+		text-align: left;
+		background: transparent;
+	}
+
+	.projects-grid .project {
+		display: grid;
+		grid-template-columns: 34px 1fr auto;
+		align-items: center;
+		gap: 8px;
+		padding: 6px 8px;
+		border: 1px solid transparent;
+	}
+
+
+	.project-icon {
+		display: inline-flex;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.project-title {
+		font-weight: 700;
+		white-space: nowrap;
+		overflow: hidden;
+		text-overflow: ellipsis;
+	}
+
+	.project-meta {
+		font-size: 11px;
+		color: #222;
+		padding: 2px 6px;
+		border: 1px solid #808080;
+		background: #efefef;
+		box-shadow: inset 0 1px #fff;
+		white-space: nowrap;
+	}
+
+
+	.projects-grid .project:hover {
+		background: #e6e6e6;
+		border-color: #808080;
+	}
+
+	.projects-grid .project:focus {
+		outline: 1px dotted #000;
+		outline-offset: -3px;
+		border-color: #000;
+		background: #000080;
+		color: #fff;
+	}
+
+
+
+	#projects-container {
+		display: grid;
+		grid-template-columns: 1fr 1.25fr;
+		gap: 8px;
+		height: calc(100% - 0px);
+	}
+
+	.projects-details {
+		display: flex;
+		flex-direction: column;
+		margin: 6px 6px 6px 0;
+		background: #fff;
+		box-shadow: inset -1px -1px #fff, inset 1px 1px grey, inset -2px -2px #dfdfdf,
+			inset 2px 2px #0a0a0a;
+		min-width: 0;
+	}
+
+	.details-header {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		gap: 8px;
+		padding: 8px 10px;
+		background: #d4d0c8;
+		border-bottom: 1px solid #808080;
+		box-shadow: inset 0 1px #fff;
+	}
+
+	.details-title {
+		font-weight: 700;
+		white-space: nowrap;
+		overflow: hidden;
+		text-overflow: ellipsis;
+	}
+
+	.details-actions {
+		display: flex;
+		gap: 6px;
+		flex-shrink: 0;
+	}
+
+	.details-btn {
+		appearance: none;
+		font-family: inherit;
+		font-size: 12px;
+		padding: 2px 10px;
+		background: #d4d0c8;
+		border: 1px solid #000;
+		box-shadow: inset 1px 1px #fff, inset -1px -1px #808080;
+		cursor: pointer;
+	}
+	.details-btn:disabled {
+		opacity: 0.6;
+		cursor: not-allowed;
+	}
+
+	.details-body {
+		height: 100%;
+		padding: 10px;
+		overflow: hidden;
+		display: flex;
+		flex-direction: column;
+		gap: 10px;
+		min-height: 0;
+	}
+
+	.details-blurb {
+		font-size: 12px;
+		line-height: 1.3;
+		color: #222;
+		border: 1px solid #000;
+		background: #ffffe1;
+		box-shadow: 2px 2px 0 #000;
+		padding: 8px 10px;
+	}
+
+	.details-framewrap {
+		flex: 1;
+		min-height: 0;
+		border: 1px solid #000;
+		box-shadow: 2px 2px 0 #000;
+		background: #fff;
+		overflow: hidden;
+	}
+
+	#project-details-frame {
+		width: 100%;
+		height: 100%;
+		border: 0;
+		background: #fff;
+	}
+
+	.projects-grid {
+		margin: 6px 0 6px 6px;
+	}
+
+	.projects-grid .project[data-selected] {
+		outline: 1px dotted #000;
+		outline-offset: -3px;
+		border-color: #000;
+		background: #000080;
+		color: #fff;
+	}
+
+
+	@media (max-width: 510px) {
+		#projects-container {
+			grid-template-columns: 1fr;
+			grid-template-rows: auto 1fr;
+			grid-template-areas:
+				"grid"
+				"details";
+			height: 100%;
+		}
+
+		.projects-grid {
+			height: auto;
+			margin: 6px;
+			padding: 8px;
+			overflow-x: auto;
+			overflow-y: hidden;
+			display: flex;
+			flex-direction: row;
+			gap: 8px;
+			scroll-snap-type: x mandatory;
+			-webkit-overflow-scrolling: touch;
+		}
+
+		.projects-grid .project {
+			flex: 0 0 auto;
+			min-width: 170px;
+			max-width: 220px;
+			grid-template-columns: 34px 1fr;
+			grid-template-rows: auto auto;
+			align-items: center;
+		}
+
+		.projects-grid .project-meta {
+			display: none;
+		}
+
+		.projects-grid .project {
+			scroll-snap-align: start;
+			border: 1px solid #000;
+			box-shadow: 2px 2px 0 #000;
+			background: linear-gradient(180deg, #fff, #f3f3f3);
+		}
+
+		.projects-details {
+			margin: 0 6px 6px;
+		}
+	}
+
 </style>

+ 26 - 15
src/pages/index.astro

@@ -216,17 +216,17 @@ let apps: app[] = [
 ];
 
 let projectsArr: project[] = [
- 	{
-		title: "Glance",
-		id: "glance",
+	{
+		title: "Mikro",
+		id: "mikro",
 	},
 	{
-		title: "Summize",
-		id: "summize",
+		title: "Glance",
+		id: "glance",
 	},
 	{
-		title: "Notion2Canvas",
-		id: "notion",
+		title: "Mneme",
+		id: "mneme",
 	},
 ];
 
@@ -332,11 +332,15 @@ apps = apps.map((item) => {
 	}
 
 	Array.from(document.getElementsByClassName("project")).forEach((project) => {
-		// console.log(projectsArr[0].id + "item");
-
-		let id = projectsArr.find((i) => i.id + "item" == project.id).id;
+		// Projects window uses buttons with data-project for the preview pane.
+		// Skip wiring those into the legacy "open a new window" behavior.
+		if (project instanceof HTMLElement && project.dataset && project.dataset.project) {
+			return;
+		}
 
-		// console.log(id);
+		let found = projectsArr.find((i) => i.id + "item" == project.id);
+		if (!found) return;
+		let id = found.id;
 
 		if (id == "pgpcord") {
 			project.onclick = () => {
@@ -354,9 +358,11 @@ apps = apps.map((item) => {
 		}
 		project.onclick = () => {
 			setTimeout(() => {
-			  console.log(id)
-				document.getElementsByClassName(id)[0].style.display = "block";
-				raiseUpSpec(document.getElementsByClassName(id)[0].id);
+				console.log(id);
+				const el = document.getElementsByClassName(id)[0];
+				if (!el) return;
+				el.style.display = "block";
+				raiseUpSpec(el.id);
 			}, 1);
 		};
 	});
@@ -393,7 +399,7 @@ apps = apps.map((item) => {
 
 		const titleButtons = Array.from(
 			window.querySelectorAll('.title-bar-controls button')
-		) 
+		);
 
 		const minimizeBtn = titleButtons.find(
 			(btn) => (btn.getAttribute('aria-label') || '').toLowerCase() === 'minimize'
@@ -468,6 +474,11 @@ apps = apps.map((item) => {
 		}, 300);
 	}
 
+	// Expose a tiny API for legacy components that wire their own controls.
+	window.minimizeWindow = minimizeWindow;
+	window.removeFromTaskbar = removeFromTaskbar;
+	window.raiseUpSpec = raiseUpSpec;
+
 	function restoreWindow(windowId) {
 		const window = document.getElementById(windowId);
 		const taskbarButton = document.getElementById(`taskbar-${windowId}`);