From 3eba6d9bdeb32dea82f4a76814e4df5fa0c1bdcb Mon Sep 17 00:00:00 2001 From: Ahmad Tuflihun Date: Sun, 2 Aug 2026 01:11:31 +0700 Subject: [PATCH 1/8] feat: add Windows 11-style desktop clone as landing page --- .eslintrc.js | 3 +- src/containers/Main.js | 5 +- src/pages/desktop/AppWindow.js | 193 ++++++++ src/pages/desktop/Desktop.css | 780 +++++++++++++++++++++++++++++++++ src/pages/desktop/Desktop.js | 494 +++++++++++++++++++++ src/pages/desktop/StartMenu.js | 77 ++++ src/pages/desktop/Taskbar.js | 100 +++++ 7 files changed, 1648 insertions(+), 4 deletions(-) create mode 100644 src/pages/desktop/AppWindow.js create mode 100644 src/pages/desktop/Desktop.css create mode 100644 src/pages/desktop/Desktop.js create mode 100644 src/pages/desktop/StartMenu.js create mode 100644 src/pages/desktop/Taskbar.js diff --git a/.eslintrc.js b/.eslintrc.js index 4652ed3..601f4a4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,7 +4,7 @@ module.exports = { es6: true, node: true, }, - extends: ["plugin:react/recommended"], + extends: ["react-app"], globals: { Atomics: "readonly", SharedArrayBuffer: "readonly", @@ -15,6 +15,5 @@ module.exports = { }, ecmaVersion: 2018, }, - plugins: ["react"], rules: {}, }; diff --git a/src/containers/Main.js b/src/containers/Main.js index 7f48141..c615956 100644 --- a/src/containers/Main.js +++ b/src/containers/Main.js @@ -2,6 +2,7 @@ import React, { Component } from "react"; import { Route, Switch, HashRouter } from "react-router-dom"; import Home from "../pages/home/HomeComponent"; import Splash from "../pages/splash/Splash"; +import Desktop from "../pages/desktop/Desktop"; import Education from "../pages/education/EducationComponent"; import Experience from "../pages/experience/Experience"; import Opensource from "../pages/opensource/Opensource"; @@ -80,9 +81,9 @@ export default class Main extends Component {
+ } /> } /> { + const onMove = (e) => { + if (!dragging.current) return; + setPos({ + x: Math.max(0, e.clientX - dragOffset.current.x), + y: Math.max(0, e.clientY - dragOffset.current.y), + }); + }; + const onUp = () => { + dragging.current = false; + }; + window.addEventListener("mousemove", onMove); + window.addEventListener("mouseup", onUp); + return () => { + window.removeEventListener("mousemove", onMove); + window.removeEventListener("mouseup", onUp); + }; + }, []); + + const onBarMouseDown = (e) => { + if (e.button !== 0 || isMaximized) return; + dragging.current = true; + dragOffset.current = { x: e.clientX - pos.x, y: e.clientY - pos.y }; + onFocus(); + e.preventDefault(); + }; + + const toggleMax = () => { + if (isMaximized) { + setPos(savedPos.current); + setIsMaximized(false); + } else { + savedPos.current = pos; + setIsMaximized(true); + } + }; + + const style = isMaximized + ? { + position: "fixed", + left: 0, + top: 0, + width: "100vw", + height: "calc(100vh - 48px)", + borderRadius: 0, + zIndex, + } + : { + position: "fixed", + left: pos.x, + top: pos.y, + width: defaultSize.width, + height: defaultSize.height, + zIndex, + }; + + return ( +
+
+ {icon} + {title} +
+ + + +
+
+ +
{children}
+
+ ); +} + +function MinimizeIcon() { + return ( + + + + ); +} + +function MaximizeIcon() { + return ( + + + + ); +} + +function RestoreIcon() { + return ( + + + + + ); +} + +function CloseIcon() { + return ( + + + + + ); +} diff --git a/src/pages/desktop/Desktop.css b/src/pages/desktop/Desktop.css new file mode 100644 index 0000000..71e9626 --- /dev/null +++ b/src/pages/desktop/Desktop.css @@ -0,0 +1,780 @@ +/* ═══════════════════════════════════════════════════════════════ + Windows 11 Desktop Clone – Desktop.css + ═══════════════════════════════════════════════════════════════ */ + +/* ── Base ──────────────────────────────────────────────────────── */ +.win11-desktop { + position: fixed; + inset: 0; + overflow: hidden; + font-family: "Segoe UI", system-ui, -apple-system, BlinkMacSystemFont, + sans-serif; + user-select: none; + /* Win11 "Bloom" wallpaper approximation */ + background: radial-gradient( + ellipse 120% 80% at 10% 65%, + rgba(0, 90, 210, 0.75) 0%, + transparent 55% + ), + radial-gradient( + ellipse 70% 90% at 90% 15%, + rgba(120, 20, 200, 0.65) 0%, + transparent 50% + ), + radial-gradient( + ellipse 60% 60% at 85% 85%, + rgba(20, 60, 190, 0.55) 0%, + transparent 50% + ), + radial-gradient( + ellipse 50% 50% at 50% 50%, + rgba(255, 110, 180, 0.08) 0%, + transparent 50% + ), + radial-gradient( + ellipse 40% 40% at 40% 30%, + rgba(30, 200, 220, 0.12) 0%, + transparent 50% + ), + #080d22; +} + +/* ── Desktop Icons ─────────────────────────────────────────────── */ +.desktop-icons { + position: absolute; + top: 16px; + left: 16px; + display: flex; + flex-direction: column; + gap: 6px; + z-index: 10; + flex-wrap: wrap; + max-height: calc(100vh - 80px); +} + +.desktop-icon { + display: flex; + flex-direction: column; + align-items: center; + width: 76px; + padding: 8px 6px; + border-radius: 6px; + cursor: pointer; + transition: background 0.12s; +} + +.desktop-icon:hover { + background: rgba(255, 255, 255, 0.14); +} + +.desktop-icon:active { + background: rgba(255, 255, 255, 0.24); +} + +.desktop-icon.desktop-icon--selected { + background: rgba(0, 120, 212, 0.45); + outline: 1px solid rgba(0, 120, 212, 0.8); +} + +.di-img { + font-size: 36px; + line-height: 1; + margin-bottom: 5px; + filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.6)); +} + +.di-label { + color: #fff; + font-size: 11.5px; + text-align: center; + line-height: 1.3; + text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9), 0 0 8px rgba(0, 0, 0, 0.6); + word-break: break-word; +} + +/* ── Taskbar ───────────────────────────────────────────────────── */ +.taskbar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + height: 48px; + background: rgba(22, 22, 22, 0.82); + backdrop-filter: blur(40px) saturate(180%); + -webkit-backdrop-filter: blur(40px) saturate(180%); + border-top: 1px solid rgba(255, 255, 255, 0.07); + display: flex; + align-items: center; + justify-content: center; + z-index: 9000; +} + +.taskbar__center { + display: flex; + align-items: center; + gap: 2px; +} + +.taskbar__right { + position: absolute; + right: 8px; + display: flex; + align-items: center; + gap: 2px; +} + +.taskbar__btn { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + border: none; + background: transparent; + border-radius: 6px; + cursor: pointer; + transition: background 0.12s; + outline: none; +} + +.taskbar__btn:hover { + background: rgba(255, 255, 255, 0.09); +} + +.taskbar__btn:active { + background: rgba(255, 255, 255, 0.05); +} + +.taskbar__btn::after { + content: ""; + position: absolute; + bottom: 4px; + width: 0; + height: 3px; + border-radius: 2px; + transition: width 0.15s, background 0.15s; +} + +/* open (minimized or visible) */ +.taskbar__btn--open::after { + width: 6px; + background: rgba(255, 255, 255, 0.55); +} + +/* active (focused window) */ +.taskbar__btn--active::after { + width: 16px; + background: #0078d4; +} + +/* start button lit (menu open) */ +.taskbar__start.taskbar__btn--lit { + background: rgba(255, 255, 255, 0.12); +} + +.taskbar__app-icon { + font-size: 20px; + line-height: 1; +} + +/* Clock */ +.taskbar__clock { + color: rgba(255, 255, 255, 0.9); + font-size: 11.5px; + text-align: right; + line-height: 1.5; + padding: 4px 8px; + border-radius: 4px; + cursor: default; +} + +.taskbar__clock:hover { + background: rgba(255, 255, 255, 0.09); +} + +.taskbar__notif { + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + cursor: pointer; + transition: background 0.12s; +} + +.taskbar__notif:hover { + background: rgba(255, 255, 255, 0.09); +} + +/* ── App Window ────────────────────────────────────────────────── */ +.app-win { + display: flex; + flex-direction: column; + background: rgba(243, 243, 243, 0.96); + backdrop-filter: blur(40px) saturate(160%); + -webkit-backdrop-filter: blur(40px) saturate(160%); + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.2); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45), 0 2px 6px rgba(0, 0, 0, 0.25); + overflow: hidden; + min-width: 280px; + min-height: 180px; + transition: box-shadow 0.15s; + animation: win-open 0.18s cubic-bezier(0.25, 0.46, 0.45, 0.94); +} + +.app-win--active { + box-shadow: 0 16px 56px rgba(0, 0, 0, 0.55), 0 4px 10px rgba(0, 0, 0, 0.3); +} + +@keyframes win-open { + from { + opacity: 0; + transform: scale(0.94) translateY(6px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +/* Title bar */ +.app-win__bar { + display: flex; + align-items: center; + height: 32px; + padding: 0 0 0 10px; + background: #f3f3f3; + cursor: grab; + flex-shrink: 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.06); +} + +.app-win__bar:active { + cursor: grabbing; +} + +.app-win--active .app-win__bar { + background: #f8f8f8; +} + +.app-win__bar-icon { + font-size: 14px; + margin-right: 7px; + flex-shrink: 0; +} + +.app-win__bar-title { + font-size: 12px; + color: #1a1a1a; + font-weight: 400; + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Window control buttons */ +.app-win__controls { + display: flex; + flex-shrink: 0; + margin-left: 4px; +} + +.app-win__ctrl { + width: 46px; + height: 32px; + border: none; + background: transparent; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: #1a1a1a; + font-size: 12px; + transition: background 0.1s, color 0.1s; + outline: none; +} + +.app-win__ctrl:hover { + background: rgba(0, 0, 0, 0.07); +} + +.app-win__ctrl--close:hover { + background: #c42b1c; + color: #fff; +} + +.app-win__ctrl svg { + pointer-events: none; +} + +/* Window body */ +.app-win__body { + flex: 1; + overflow: auto; + background: #fff; +} + +/* ── Start Menu ────────────────────────────────────────────────── */ +.start-backdrop { + position: fixed; + inset: 0; + z-index: 9100; +} + +.start-menu { + position: fixed; + bottom: 56px; + left: 50%; + transform: translateX(-50%); + width: 620px; + max-height: 620px; + background: rgba(30, 30, 30, 0.94); + backdrop-filter: blur(50px) saturate(180%); + -webkit-backdrop-filter: blur(50px) saturate(180%); + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 20px 70px rgba(0, 0, 0, 0.65); + z-index: 9200; + display: flex; + flex-direction: column; + padding: 20px 22px 0; + animation: start-open 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94); + overflow: hidden; +} + +@keyframes start-open { + from { + opacity: 0; + transform: translateX(-50%) translateY(12px) scale(0.97); + } + to { + opacity: 1; + transform: translateX(-50%) translateY(0) scale(1); + } +} + +.start-menu__search { + display: flex; + align-items: center; + gap: 10px; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 6px; + padding: 8px 14px; + margin-bottom: 18px; + cursor: text; +} + +.start-menu__search-placeholder { + color: rgba(255, 255, 255, 0.45); + font-size: 13px; +} + +.start-menu__section-label { + color: rgba(255, 255, 255, 0.85); + font-size: 12px; + font-weight: 600; + margin-bottom: 10px; + letter-spacing: 0.02em; +} + +.start-menu__grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 4px; + flex: 1; + overflow-y: auto; + margin-bottom: 8px; +} + +.start-menu__app { + display: flex; + flex-direction: column; + align-items: center; + padding: 12px 8px; + border: none; + background: transparent; + border-radius: 8px; + cursor: pointer; + transition: background 0.12s; + gap: 6px; +} + +.start-menu__app:hover { + background: rgba(255, 255, 255, 0.1); +} + +.start-menu__app:active { + background: rgba(255, 255, 255, 0.06); +} + +.start-menu__app-icon { + font-size: 28px; + line-height: 1; +} + +.start-menu__app-name { + color: rgba(255, 255, 255, 0.88); + font-size: 11.5px; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 90px; +} + +.start-menu__footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 0 14px; + border-top: 1px solid rgba(255, 255, 255, 0.08); + margin-top: 4px; +} + +.start-menu__user { + display: flex; + align-items: center; + gap: 10px; + color: rgba(255, 255, 255, 0.88); + font-size: 13px; + padding: 6px 8px; + border-radius: 6px; + cursor: pointer; + transition: background 0.12s; +} + +.start-menu__user:hover { + background: rgba(255, 255, 255, 0.08); +} + +.start-menu__avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background: linear-gradient(135deg, #0078d4, #7c3aed); + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 12px; + font-weight: 600; + flex-shrink: 0; +} + +.start-menu__power { + width: 36px; + height: 36px; + border: none; + background: transparent; + color: rgba(255, 255, 255, 0.75); + border-radius: 6px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.12s, color 0.12s; +} + +.start-menu__power:hover { + background: rgba(255, 255, 255, 0.1); + color: white; +} + +/* ═══════════════════════════════════════════════════════════════ + App Content Styles + ═══════════════════════════════════════════════════════════════ */ + +/* ── Resume App ────────────────────────────────────────────────── */ +.resume-app { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; +} + +.resume-app__toolbar { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background: #f5f5f5; + border-bottom: 1px solid #e0e0e0; + flex-shrink: 0; +} + +.resume-app__toolbar-title { + font-size: 13px; + color: #555; + flex: 1; +} + +.resume-app__btn { + display: flex; + align-items: center; + gap: 5px; + padding: 5px 10px; + background: #0078d4; + color: white; + border: none; + border-radius: 4px; + font-size: 12px; + cursor: pointer; + text-decoration: none; + transition: background 0.12s; +} + +.resume-app__btn:hover { + background: #106ebe; +} + +.resume-app__frame { + flex: 1; + border: none; + display: block; +} + +/* ── About App ─────────────────────────────────────────────────── */ +.about-app { + padding: 28px 32px; + overflow-y: auto; + height: 100%; + box-sizing: border-box; +} + +.about-app__header { + display: flex; + align-items: center; + gap: 20px; + margin-bottom: 22px; +} + +.about-app__avatar { + width: 72px; + height: 72px; + border-radius: 50%; + background: linear-gradient(135deg, #0078d4 0%, #7c3aed 100%); + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 24px; + font-weight: 700; + flex-shrink: 0; +} + +.about-app__name { + font-size: 22px; + font-weight: 600; + color: #1a1a1a; + margin: 0 0 4px; +} + +.about-app__nick { + font-size: 13px; + color: #666; + margin: 0; +} + +.about-app__bio { + font-size: 14px; + color: #444; + line-height: 1.6; + margin-bottom: 20px; +} + +.about-app__info { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 22px; +} + +.about-app__info-row { + display: flex; + align-items: center; + gap: 10px; + font-size: 13.5px; + color: #555; +} + +.about-app__info-row span:first-child { + font-size: 16px; +} + +.about-app__section-title { + font-size: 13px; + font-weight: 600; + color: #1a1a1a; + margin: 0 0 10px; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.about-app__skills { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.skill-chip { + background: #e8f0fe; + color: #1a56db; + padding: 4px 10px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; +} + +/* ── Projects App ──────────────────────────────────────────────── */ +.projects-app { + height: 100%; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.projects-app__header { + padding: 16px 20px 12px; + border-bottom: 1px solid #ebebeb; + font-size: 16px; + font-weight: 600; + color: #1a1a1a; + flex-shrink: 0; +} + +.projects-app__list { + flex: 1; + overflow-y: auto; + padding: 8px 12px; +} + +.project-card { + display: block; + padding: 14px 16px; + border-radius: 8px; + text-decoration: none; + transition: background 0.12s; + margin-bottom: 4px; +} + +.project-card:hover { + background: #f5f5f5; +} + +.project-card__name { + font-size: 14px; + font-weight: 600; + color: #0078d4; + margin-bottom: 3px; +} + +.project-card__desc { + font-size: 13px; + color: #444; + margin-bottom: 5px; + line-height: 1.4; +} + +.project-card__tech { + display: inline-flex; + gap: 4px; + flex-wrap: wrap; +} + +.tech-tag { + background: #f0f0f0; + color: #666; + padding: 2px 7px; + border-radius: 3px; + font-size: 11px; +} + +/* ── Contact App ───────────────────────────────────────────────── */ +.contact-app { + padding: 28px 32px; + overflow-y: auto; + height: 100%; + box-sizing: border-box; +} + +.contact-app__title { + font-size: 18px; + font-weight: 600; + color: #1a1a1a; + margin: 0 0 20px; +} + +.contact-app__rows { + display: flex; + flex-direction: column; + gap: 12px; + margin-bottom: 28px; +} + +.contact-app__row { + display: flex; + align-items: center; + gap: 12px; + font-size: 14px; + color: #333; +} + +.contact-app__row span:first-child { + font-size: 18px; + width: 28px; + text-align: center; +} + +.contact-app__links-title { + font-size: 13px; + font-weight: 600; + color: #1a1a1a; + margin: 0 0 12px; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.contact-app__social { + display: flex; + flex-direction: column; + gap: 6px; +} + +.social-link { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + border-radius: 8px; + text-decoration: none; + color: #1a1a1a; + font-size: 13.5px; + transition: background 0.12s; + background: #f7f7f7; + border: 1px solid #ebebeb; +} + +.social-link:hover { + background: #eaf3fb; + border-color: #b3d4f0; + color: #0078d4; +} + +.social-link span:first-child { + font-size: 18px; +} + +/* ── Mobile fallback ───────────────────────────────────────────── */ +@media (max-width: 700px) { + .start-menu { + width: 95vw; + } + .start-menu__grid { + grid-template-columns: repeat(3, 1fr); + } +} diff --git a/src/pages/desktop/Desktop.js b/src/pages/desktop/Desktop.js new file mode 100644 index 0000000..33022ea --- /dev/null +++ b/src/pages/desktop/Desktop.js @@ -0,0 +1,494 @@ +import React, { useState, useCallback, useEffect } from "react"; +import "./Desktop.css"; +import AppWindow from "./AppWindow"; +import Taskbar from "./Taskbar"; +import StartMenu from "./StartMenu"; +import { greeting } from "../../portfolio"; + +/* ═══════════════════════════════════════════════════════════════ + App Content Components + ═══════════════════════════════════════════════════════════════ */ + +function ResumeApp() { + const embedUrl = greeting.resumeLink.replace("/view?usp=sharing", "/preview"); + return ( +
+
+ + 📄 Ahmad Tuflihun – Curriculum Vitae + + + + + + + + Open in Drive + +
+