diff --git a/.all-contributorsrc b/.all-contributorsrc
index 1f38159..3e9a555 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -1,348 +1,8 @@
{
- "files": [
- "README.md"
- ],
- "imageSize": 100,
- "commit": false,
- "contributors": [{
- "login": "ashutosh1919",
- "name": "Ashutosh Hathidara",
- "avatar_url": "https://avatars3.githubusercontent.com/u/20843596?s=460&u=7f184b911f73ae1dc5765ab686fff2b2e984830f&v=4",
- "profile": "http://ashutosh1919.github.io",
- "contributions": [
- "code",
- "doc",
- "design",
- "maintenance",
- "ideas"
- ]
- },
+ "contributors": [
{
- "login": "danielmarostica",
- "name": "Daniel Marostica",
- "avatar_url": "https://avatars3.githubusercontent.com/u/3595998?v=4",
- "profile": "https://danielmarostica.github.io/",
- "contributions": [
- "doc",
- "design"
- ]
- },
- {
- "login": "dineshnadimpalli",
- "name": "Dinesh Nadimpalli",
- "avatar_url": "https://avatars2.githubusercontent.com/u/13104926?v=4",
- "profile": "https://dineshnadimpalli.com",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "jivthesh",
- "name": "Jivthesh M R",
- "avatar_url": "https://avatars3.githubusercontent.com/u/20579980?v=4",
- "profile": "http://jivthesh.github.io",
- "contributions": [
- "doc"
- ]
- },
- {
- "login": "mrjatinchauhan",
- "name": "Jatin Chauhan",
- "avatar_url": "https://avatars2.githubusercontent.com/u/40722235?v=4",
- "profile": "http://jatinchauhan.tech",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "th3c0d3br34ker",
- "name": "Jainam Desai",
- "avatar_url": "https://avatars1.githubusercontent.com/u/60807938?v=4",
- "profile": "https://th3c0d3br34ker.github.io/",
- "contributions": [
- "code",
- "question"
- ]
- },
- {
- "login": "Tasin5541",
- "name": "Miftaul Mannan",
- "avatar_url": "https://avatars0.githubusercontent.com/u/42533823?v=4",
- "profile": "https://miftaulmannan.wordpress.com/",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "a-mishra",
- "name": "Ashutosh Mishra",
- "avatar_url": "https://avatars0.githubusercontent.com/u/10567548?v=4",
- "profile": "http://a-mishra.github.io",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "tamojit-123",
- "name": "Tamojit",
- "avatar_url": "https://avatars0.githubusercontent.com/u/40804626?v=4",
- "profile": "http://tamojit.wixsite.com/mrtamojit",
- "contributions": [
- "doc",
- "code",
- "design"
- ]
- },
- {
- "login": "githubprabin143",
- "name": "Prabin Karki",
- "avatar_url": "https://avatars.githubusercontent.com/u/43983896?v=4",
- "profile": "https://prabin-karki.com.np",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "praveenscience",
- "name": "Praveen Kumar Purushothaman",
- "avatar_url": "https://avatars.githubusercontent.com/u/1830380?v=4",
- "profile": "https://praveen.science/",
- "contributions": [
- "doc"
- ]
- },
- {
- "login": "baulml",
- "name": "paul",
- "avatar_url": "https://avatars.githubusercontent.com/u/33395806?v=4",
- "profile": "http://baul.ml",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "SandipDhang",
- "name": "Sandip Dhang",
- "avatar_url": "https://avatars.githubusercontent.com/u/50694884?v=4",
- "profile": "https://github.com/SandipDhang",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "ioribrn",
- "name": "Jawad Moustadif",
- "avatar_url": "https://avatars.githubusercontent.com/u/43734587?v=4",
- "profile": "https://github.com/ioribrn",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "priyanshk20",
- "name": "Priyansh Khandelwal",
- "avatar_url": "https://avatars.githubusercontent.com/u/52736997?v=4",
- "profile": "https://github.com/priyanshk20",
- "contributions": [
- "code",
- "design"
- ]
- },
- {
- "login": "abdslam01",
- "name": "Abdessalam Bahafid",
- "avatar_url": "https://avatars.githubusercontent.com/u/59281502?v=4",
- "profile": "https://github.com/abdslam01",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "dhruvkrishnavaid",
- "name": "Dhruv Krishna Vaid",
- "avatar_url": "https://avatars.githubusercontent.com/u/69188774?v=4",
- "profile": "https://dhruvkrishnavaid.github.io",
- "contributions": [
- "code",
- "doc",
- "ideas",
- "question"
- ]
- },
- {
- "login": "KasRoudra",
- "name": "KasRoudra",
- "avatar_url": "https://avatars.githubusercontent.com/u/78908440?v=4",
- "profile": "https://kasroudra.github.io",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "AlbertEinsteinTG",
- "name": "Albert Einstein",
- "avatar_url": "https://avatars.githubusercontent.com/u/73480087?v=4",
- "profile": "https://telegram.dog/AlbertEinstein_TG",
- "contributions": [
- "doc"
- ]
- },
- {
- "login": "SurajPratap10",
- "name": "Suraj Pratap",
- "avatar_url": "https://avatars.githubusercontent.com/u/92919173?v=4",
- "profile": "https://github.com/SurajPratap10",
- "contributions": [
- "doc"
- ]
- },
- {
- "login": "saiteja13427",
- "name": "Sai Teja",
- "avatar_url": "https://avatars.githubusercontent.com/u/40917760?v=4",
- "profile": "https://lightmap.dev",
- "contributions": [
- "code",
- "doc",
- "maintenance",
- "ideas"
- ]
- },
- {
- "login": "AnirudhPanda",
- "name": "Anirudh Panda",
- "avatar_url": "https://avatars.githubusercontent.com/u/66218496?v=4",
- "profile": "https://anirudhpanda.in/",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "hidayat7z",
- "name": "Md Hidayat Rasool",
- "avatar_url": "https://avatars.githubusercontent.com/u/63554016?v=4",
- "profile": "https://hidayat7z.github.io",
- "contributions": [
- "doc"
- ]
- },
- {
- "login": "SiddhantSadangi",
- "name": "Siddhant Sadangi",
- "avatar_url": "https://avatars.githubusercontent.com/u/41324509?v=4",
- "profile": "https://www.linkedin.com/in/siddhantsadangi/",
- "contributions": [
- "doc"
- ]
- },
- {
- "login": "vanoop729",
- "name": "Anoop V",
- "avatar_url": "https://avatars.githubusercontent.com/u/59723767?v=4",
- "profile": "https://anoopvarghese.in/",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "aash1999",
- "name": "Aakash Singh",
- "avatar_url": "https://avatars.githubusercontent.com/u/39939476?v=4",
- "profile": "https://github.com/aash1999",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "aherrera3",
- "name": "AngΓ©lica Herrera Alba",
- "avatar_url": "https://avatars.githubusercontent.com/u/52289646?v=4",
- "profile": "https://aherrera3.github.io/",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "dminkovski",
- "name": "David Minkovski",
- "avatar_url": "https://avatars.githubusercontent.com/u/1609264?v=4",
- "profile": "https://davidminkovski.com",
- "contributions": [
- "ideas",
- "code"
- ]
- },
- {
- "login": "cdigruttola",
- "name": "Carmine Di Gruttola",
- "avatar_url": "https://avatars.githubusercontent.com/u/9153905?v=4",
- "profile": "http://cdigruttola.it",
- "contributions": [
- "ideas",
- "promotion"
- ]
- },
- {
- "login": "Vyomrana02",
- "name": "Vyom Rana",
- "avatar_url": "https://avatars.githubusercontent.com/u/87069619?v=4",
- "profile": "https://github.com/Vyomrana02",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "parthrc",
- "name": "Parth Chawande",
- "avatar_url": "https://avatars.githubusercontent.com/u/101104958?v=4",
- "profile": "https://github.com/parthrc",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "0Armaan025",
- "name": "Armaan",
- "avatar_url": "https://avatars.githubusercontent.com/u/104704093?v=4",
- "profile": "https://github.com/0Armaan025",
- "contributions": [
- "doc"
- ]
- },
- {
- "login": "varundhand",
- "name": "Varun Dhand",
- "avatar_url": "https://avatars.githubusercontent.com/u/110025628?v=4",
- "profile": "https://varundhand.netlify.app/",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "vjspranav",
- "name": "VJS Pranavasri",
- "avatar_url": "https://avatars.githubusercontent.com/u/17949836?v=4",
- "profile": "http://vjspranav.dev",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "Rahulkush1",
- "name": "Rahul Kushwaha",
- "avatar_url": "https://avatars.githubusercontent.com/u/90745824?v=4",
- "profile": "https://rahulkush1.github.io/",
- "contributions": [
- "code"
- ]
+ "id": "Leguna",
+ "github": "https://github.com/Leguna"
}
- ],
- "contributorsPerLine": 7,
- "projectName": "masterPortfolio",
- "projectOwner": "ashutosh1919",
- "repoType": "github",
- "repoHost": "https://github.com",
- "skipCi": true,
- "commitConvention": "angular",
- "commitType": "docs"
-}
+ ]
+}
\ No newline at end of file
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/CONTRIBUTING.md b/CONTRIBUTING.md
index c8535fd..aa5562b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,81 +1,85 @@
-# π Contributing to the MasterPortfolio Project
+f
+i
-Thank you for considering contributing to the MasterPortfolio project! Your help is greatly appreciated. π
+If you'd like to contribute to our source code, please follow these steps:
-## π€ How to Contribute
+1. **Fork the repository**: Clone the project and create a new branch for your changes.
+2. **Create an issue**: Report any issues or bugs you encounter in the GitHub issues section.
+3. **Submit a pull request**: Once you've made some changes, submit them as a pull request.
-Here are some fun and precise steps to get you started:
+## Guidelines
-1. **Fork the Repository** π΄
+When contributing to our source code, please follow these guidelines:
- - Click the "Fork" button on the top right of the [MasterPortfolio repository](https://github.com/ashutosh1919/masterPortfolio) page on GitHub. This will create a copy of the project in your own GitHub account.
+- **Code style**: Adhere to the existing code style and formatting.
+- **Testing**: Write unit tests for any new features or bug fixes.
+- **Documentation**: Update documentation as needed to reflect changes.
-2. **Clone Your Fork** π§ββοΈ
+## Code of Conduct
- - In your terminal, run the following command to clone your fork to your local machine:
- ```
- git clone https://github.com/YourUsername/masterPortfolio.git
- ```
+By contributing to this project, you agree to abide by our code of conduct. Please review it [here](CODE_OF_CONDUCT.md).
-3. **Switch to the Project Directory** π
+## Contributing Guidelines for Specific Sections
- - Move into the project directory using:
- ```
- cd masterPortfolio
- ```
+### Documentation
-4. **Move to Gatsby** π
+- Ensure the changes are clearly described in the commit message.
+- Write a clear and concise description of the change in the pull request.
- - If you're working on this task, please help us migrate the project to Gatsby. Make the necessary changes and test everything to ensure it works smoothly.
+### Code Changes
-5. **Add Podcast and Video Sections** ποΈπΉ
+- Ensure all new features or bug fixes include unit tests.
+- Refactor existing code to improve readability and maintainability.
- - We'd love to have sections for podcasts and videos on the portfolio. Create these sections and make sure they look fantastic.
+## How We Use Your Contributions
-6. **Add Tests for Automation** π§ͺ
+We welcome any contributions, from small bug fixes to large feature additions. When reviewing your contributions, we'll consider factors such as:
- - To ensure code quality, add tests where appropriate. Use test frameworks like Jest or Cypress, and make sure the tests pass.
+- **Code quality**: Will the changes improve the overall quality of the project?
+- **Usefulness**: Are the changes useful and relevant to our users?
-7. **Set Up Travis Workflow** π€
+## Commit Messages
- - Implement a Travis CI workflow to automate builds and testing. This will help ensure that changes don't break the project.
+Commit messages should follow standard guidelines:
-8. **Create a Branch** πΏ
+- Keep it concise.
+- Use imperative mood (e.g., "Add feature X").
+- Include a brief description.
- - Create a new branch for your changes with a descriptive name. For example:
- ```
- git checkout -b gatsby-migration
- ```
+By following these guidelines, we can ensure that your contributions are properly incorporated into the project.
-9. **Make Your Changes** π‘
+### Contributing Guidelines for Contributors with Vested Interest
- - Implement your awesome changes and features.
+If you're contributing to our source code as part of a professional engagement or in pursuit of personal gain, please note:
-10. **Commit Your Changes** π
+- **Conflicts of interest**: If you have any conflicts of interest, disclose them in the pull request.
+- **Intellectual property**: Ensure that your contributions don't infringe on existing intellectual property.
- - Commit your changes with a descriptive message. For example:
- ```
- git commit -m "Added Gatsby migration and podcast section"
- ```
+## Contributing Guidelines for New Features
-11. **Push Your Changes** π’
+When introducing new features, consider:
- - Push your changes to your fork on GitHub:
- ```
- git push origin gatsby-migration
- ```
+- **User feedback**: Consult with users and stakeholders to ensure the feature meets their needs.
+- **Documentation**: Provide clear documentation for any new features.
-12. **Create a Pull Request** π
+By following these guidelines, we can ensure that your contributions are of high quality and align with our project goals.
- - Go to your fork on GitHub, and click the "New Pull Request" button. Follow the prompts, and submit your changes for review.
+## Contributing Guidelines for Bug Fixes
-13. **Celebrate** π
- - You've contributed to the MasterPortfolio project! Thanks for your hard work and dedication.
+When fixing bugs:
-## π Issues
+- **Test thoroughly**: Verify that the fix doesn't introduce any regressions.
+- **Document changes**: Document the bug, including any relevant information about the issue.
-If you encounter any issues or have ideas for improvements, please open an [issue](https://github.com/ashutosh1919/masterPortfolio/issues) and let us know.
+## Frequently Asked Questions (FAQ)
-## π Thank You
+- Q: What if I encounter an issue while contributing?
+ A: Report it in the GitHub issues section and we'll help you troubleshoot or provide a fix.
+- Q: Can I contribute to specific sections of the codebase?
+ A: Yes, please review our [contributing guidelines](#code-of-conduct) for specifics.
-We appreciate your contribution and look forward to working together to make the MasterPortfolio project even better! ππ
+## Maintainer's Contact Information
+
+If you have any questions or need assistance with contributing:
+
+Email: ahmadtuflihunxd@gmail.com
diff --git a/src/components/competitiveSites/CompetitiveSites.js b/src/components/competitiveSites/CompetitiveSites.js
index fb22748..65ee726 100644
--- a/src/components/competitiveSites/CompetitiveSites.js
+++ b/src/components/competitiveSites/CompetitiveSites.js
@@ -31,7 +31,12 @@ class CompetitiveSites extends React.Component {
style={logo.style}
data-inline="false"
>
-
+
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 {
+ } />
}
/>
;
+ return ;
}
}
diff --git a/src/containers/skills/IotImg.js b/src/containers/skills/IotImg.js
index d5813db..c8c8607 100644
--- a/src/containers/skills/IotImg.js
+++ b/src/containers/skills/IotImg.js
@@ -2,7 +2,6 @@ import React, { Component } from "react";
export default class IotImg extends Component {
render() {
- const theme = this.props.theme;
- return ;
+ return ;
}
}
diff --git a/src/pages/desktop/AppWindow.js b/src/pages/desktop/AppWindow.js
new file mode 100644
index 0000000..57fe1a2
--- /dev/null
+++ b/src/pages/desktop/AppWindow.js
@@ -0,0 +1,214 @@
+import React, { useState, useRef, useEffect } from "react";
+
+export default function AppWindow({
+ title,
+ icon,
+ children,
+ zIndex,
+ isActive,
+ defaultPos = { x: 100, y: 60 },
+ defaultSize = { width: 800, height: 520 },
+ onClose,
+ onMinimize,
+ onFocus,
+}) {
+ const [pos, setPos] = useState(defaultPos);
+ const [isMaximized, setIsMaximized] = useState(false);
+ const [isMobile, setIsMobile] = useState(false);
+ const savedPos = useRef(defaultPos);
+ const dragging = useRef(false);
+ const dragOffset = useRef({ x: 0, y: 0 });
+
+ useEffect(() => {
+ 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;
+ };
+ const updateViewport = () => {
+ setIsMobile(window.innerWidth <= 768);
+ };
+
+ updateViewport();
+ window.addEventListener("mousemove", onMove);
+ window.addEventListener("mouseup", onUp);
+ window.addEventListener("resize", updateViewport);
+
+ return () => {
+ window.removeEventListener("mousemove", onMove);
+ window.removeEventListener("mouseup", onUp);
+ window.removeEventListener("resize", updateViewport);
+ };
+ }, []);
+
+ const onBarMouseDown = (e) => {
+ if (e.button !== 0 || isMaximized || isMobile) 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 viewportWidth = typeof window !== "undefined" ? window.innerWidth : 1280;
+ const viewportHeight = typeof window !== "undefined" ? window.innerHeight : 900;
+ const responsiveWidth = isMobile
+ ? "100vw"
+ : Math.min(defaultSize.width, Math.max(280, viewportWidth - 16));
+ const responsiveHeight = isMobile
+ ? "calc(100dvh - 48px)"
+ : Math.min(defaultSize.height, Math.max(180, viewportHeight - 64));
+ const style = isMaximized || isMobile
+ ? {
+ position: "fixed",
+ left: 0,
+ top: 0,
+ width: "100vw",
+ height: "calc(100dvh - 48px)",
+ borderRadius: 0,
+ zIndex,
+ maxWidth: "100vw",
+ maxHeight: "calc(100dvh - 48px)",
+ }
+ : {
+ position: "fixed",
+ left: Math.min(pos.x, Math.max(0, viewportWidth - responsiveWidth - 8)),
+ top: Math.min(pos.y, Math.max(0, viewportHeight - responsiveHeight - 56)),
+ width: responsiveWidth,
+ height: responsiveHeight,
+ zIndex,
+ maxWidth: `calc(100vw - 16px)`,
+ maxHeight: `calc(100dvh - 64px)`,
+ };
+
+ return (
+
+
+
{icon}
+
{title}
+
+ e.stopPropagation()}
+ onClick={onMinimize}
+ >
+
+
+ e.stopPropagation()}
+ onClick={() => {
+ onFocus();
+ toggleMax();
+ }}
+ >
+ {isMaximized ? : }
+
+ e.stopPropagation()}
+ onClick={onClose}
+ >
+
+
+
+
+
+
{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..8589b83
--- /dev/null
+++ b/src/pages/desktop/Desktop.css
@@ -0,0 +1,1503 @@
+/* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ Windows 11 Desktop Clone β Desktop.css
+ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
+
+/* ββ Base ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
+.win11-desktop {
+ position: fixed;
+ inset: 0;
+ width: 100vw;
+ height: 100dvh;
+ min-height: 100dvh;
+ 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;
+}
+
+/* ββ Weather Widget βββββββββββββββββββββββββββββββββββββββββββ */
+.desktop-weather-widget {
+ position: absolute;
+ top: 16px;
+ right: 16px;
+ z-index: 20;
+ width: min(280px, calc(100vw - 32px));
+ padding: 14px 14px 12px;
+ border-radius: 16px;
+ background: rgba(15, 23, 42, 0.78);
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ box-shadow: 0 12px 32px rgba(2, 6, 23, 0.35);
+ color: #f8fafc;
+ backdrop-filter: blur(18px);
+ -webkit-backdrop-filter: blur(18px);
+}
+
+.desktop-weather-widget__header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 10px;
+}
+
+.desktop-weather-widget__icon {
+ font-size: 28px;
+ line-height: 1;
+}
+
+.desktop-weather-widget__location {
+ font-size: 13px;
+ font-weight: 600;
+ line-height: 1.3;
+}
+
+.desktop-weather-widget__status {
+ font-size: 11px;
+ color: rgba(248, 250, 252, 0.7);
+}
+
+.desktop-weather-widget__body {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+}
+
+.desktop-weather-widget__temp {
+ font-size: 26px;
+ font-weight: 700;
+}
+
+.desktop-weather-widget__details {
+ font-size: 12px;
+ color: rgba(248, 250, 252, 0.85);
+ text-align: right;
+ line-height: 1.4;
+}
+
+.desktop-weather-widget__footnote {
+ margin-top: 8px;
+ font-size: 11px;
+ color: rgba(248, 250, 252, 0.78);
+}
+
+.desktop-weather-widget__button {
+ margin-top: 10px;
+ border: none;
+ border-radius: 999px;
+ padding: 7px 10px;
+ background: rgba(255, 255, 255, 0.16);
+ color: #f8fafc;
+ font-size: 11px;
+ font-weight: 600;
+ cursor: pointer;
+}
+
+.desktop-weather-widget__button:hover {
+ background: rgba(255, 255, 255, 0.24);
+}
+
+/* ββ 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;
+}
+
+/* ββ News App ββββββββββββββββββββββββββββββββββββββββββββββββββββ */
+.news-app {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ background: linear-gradient(180deg, #0f172a 0%, #111827 100%);
+ color: #f8fafc;
+}
+
+.news-app__header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16px;
+ padding: 18px 20px 14px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
+ flex-shrink: 0;
+}
+
+.news-app__title {
+ margin: 0 0 4px;
+ font-size: 20px;
+}
+
+.news-app__subtitle {
+ margin: 0;
+ font-size: 12px;
+ line-height: 1.5;
+ color: rgba(248, 250, 252, 0.7);
+}
+
+.news-app__actions {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: flex-end;
+ gap: 8px;
+}
+
+.news-app__btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: none;
+ border-radius: 999px;
+ padding: 8px 12px;
+ font-size: 12px;
+ font-weight: 600;
+ cursor: pointer;
+ background: rgba(255, 255, 255, 0.14);
+ color: #f8fafc;
+ text-decoration: none;
+}
+
+.news-app__btn:hover {
+ background: rgba(255, 255, 255, 0.2);
+}
+
+.news-app__btn--link {
+ background: linear-gradient(135deg, #38bdf8, #2563eb);
+}
+
+.news-app__statusbar {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ padding: 10px 20px;
+ font-size: 12px;
+ color: rgba(248, 250, 252, 0.82);
+ background: rgba(255, 255, 255, 0.04);
+ border-bottom: 1px solid rgba(255, 255, 255, 0.06);
+ flex-shrink: 0;
+}
+
+.news-app__list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 12px;
+}
+
+.news-card {
+ display: block;
+ padding: 14px 16px;
+ border-radius: 12px;
+ text-decoration: none;
+ color: #f8fafc;
+ background: rgba(255, 255, 255, 0.06);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ margin-bottom: 8px;
+ transition: background 0.12s, border-color 0.12s, transform 0.12s;
+}
+
+.news-card:hover {
+ background: rgba(255, 255, 255, 0.1);
+ border-color: rgba(255, 255, 255, 0.14);
+ transform: translateY(-1px);
+}
+
+.news-card__top {
+ margin-bottom: 10px;
+}
+
+.news-card__title {
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 1.45;
+ margin-bottom: 4px;
+}
+
+.news-card__meta {
+ font-size: 11.5px;
+ color: rgba(248, 250, 252, 0.68);
+}
+
+.news-card__bottom {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ font-size: 11.5px;
+ color: rgba(248, 250, 252, 0.78);
+}
+
+.news-app__empty {
+ min-height: 160px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ gap: 6px;
+ padding: 24px;
+ color: rgba(248, 250, 252, 0.78);
+}
+
+.news-app__empty-title {
+ font-size: 15px;
+ font-weight: 600;
+ color: #f8fafc;
+}
+
+.news-app__empty-copy {
+ font-size: 12px;
+ line-height: 1.5;
+ max-width: 280px;
+}
+
+/* ββ Sudoku App βββββββββββββββββββββββββββββββββββββββββββββββββ */
+.sudoku-app {
+ height: 100%;
+ box-sizing: border-box;
+ padding: 18px 20px 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+ background: linear-gradient(135deg, #111827 0%, #1f2937 100%);
+ color: #f9fafb;
+}
+
+.sudoku-app__header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.sudoku-app__title {
+ margin: 0 0 4px;
+ font-size: 20px;
+}
+
+.sudoku-app__subtitle {
+ margin: 0;
+ font-size: 12px;
+ color: rgba(255, 255, 255, 0.72);
+ line-height: 1.5;
+}
+
+.sudoku-app__actions {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: flex-end;
+ gap: 8px;
+}
+
+.sudoku-app__btn {
+ border: none;
+ border-radius: 999px;
+ padding: 8px 12px;
+ font-size: 11px;
+ font-weight: 600;
+ cursor: pointer;
+ background: rgba(255, 255, 255, 0.14);
+ color: #f9fafb;
+}
+
+.sudoku-app__btn:hover {
+ background: rgba(255, 255, 255, 0.2);
+}
+
+.sudoku-app__btn--primary {
+ background: linear-gradient(135deg, #38bdf8, #2563eb);
+}
+
+.sudoku-app__statusbar {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ padding: 8px 10px;
+ border-radius: 8px;
+ background: rgba(255, 255, 255, 0.08);
+ font-size: 12px;
+ color: rgba(255, 255, 255, 0.92);
+}
+
+.sudoku-app__message {
+ color: #fde68a;
+}
+
+.sudoku-app__board {
+ display: grid;
+ grid-template-columns: repeat(9, minmax(0, 1fr));
+ gap: 0;
+ width: min(100%, 460px);
+ aspect-ratio: 1 / 1;
+ margin: 0 auto;
+ background: #f8fafc;
+ border: 2px solid #111827;
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
+}
+
+.sudoku-app__cell {
+ border: 1px solid #d1d5db;
+ background: #fff;
+ color: #111827;
+ font-size: 16px;
+ font-weight: 600;
+ cursor: pointer;
+ padding: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.sudoku-app__cell:hover {
+ background: #fef3c7;
+}
+
+.sudoku-app__cell--selected {
+ background: #dbeafe;
+}
+
+.sudoku-app__cell--fixed {
+ background: #f3f4f6;
+ color: #111827;
+ font-weight: 700;
+}
+
+.sudoku-app__cell--wrong {
+ color: #dc2626;
+}
+
+.sudoku-app__cell--row-divider {
+ border-bottom-width: 2px;
+ border-bottom-color: #111827;
+}
+
+.sudoku-app__cell--col-divider {
+ border-right-width: 2px;
+ border-right-color: #111827;
+}
+
+.sudoku-app__keypad {
+ display: grid;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ gap: 8px;
+ width: min(100%, 460px);
+ margin: 0 auto;
+}
+
+.sudoku-app__number {
+ border: none;
+ border-radius: 8px;
+ padding: 10px 0;
+ font-size: 14px;
+ font-weight: 700;
+ cursor: pointer;
+ background: rgba(255, 255, 255, 0.16);
+ color: #f9fafb;
+}
+
+.sudoku-app__number:hover {
+ background: rgba(255, 255, 255, 0.24);
+}
+
+.sudoku-app__number--clear {
+ background: rgba(249, 115, 22, 0.9);
+}
+
+/* ββ Snake App ββββββββββββββββββββββββββββββββββββββββββββββββββ */
+.snake-app {
+ height: 100%;
+ box-sizing: border-box;
+ padding: 18px 20px 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+ background: linear-gradient(135deg, #0f172a 0%, #111827 100%);
+ color: #f3f4f6;
+}
+
+.snake-app__header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.snake-app__title {
+ margin: 0 0 4px;
+ font-size: 20px;
+}
+
+.snake-app__subtitle {
+ margin: 0;
+ font-size: 12px;
+ color: rgba(255, 255, 255, 0.72);
+ line-height: 1.5;
+}
+
+.snake-app__stats {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ min-width: 108px;
+}
+
+.snake-app__stat {
+ padding: 7px 10px;
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.14);
+ font-size: 12px;
+ font-weight: 600;
+ text-align: center;
+}
+
+.snake-app__board-wrap {
+ position: relative;
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 0;
+}
+
+.snake-app__board {
+ display: grid;
+ grid-template-columns: repeat(20, minmax(0, 1fr));
+ grid-template-rows: repeat(20, minmax(0, 1fr));
+ width: min(100%, 460px);
+ aspect-ratio: 1 / 1;
+ background: rgba(2, 6, 23, 0.9);
+ border: 2px solid rgba(255, 255, 255, 0.12);
+ border-radius: 10px;
+ overflow: hidden;
+ box-shadow: inset 0 0 18px rgba(0, 0, 0, 0.4);
+}
+
+.snake-app__cell {
+ background: rgba(255, 255, 255, 0.03);
+ border: 1px solid rgba(255, 255, 255, 0.03);
+}
+
+.snake-app__cell--head {
+ background: linear-gradient(135deg, #22c55e, #16a34a);
+ border-radius: 3px;
+}
+
+.snake-app__cell--body {
+ background: linear-gradient(135deg, #4ade80, #15803d);
+ border-radius: 3px;
+}
+
+.snake-app__cell--food {
+ background: linear-gradient(135deg, #fb923c, #ef4444);
+ border-radius: 50%;
+ transform: scale(0.78);
+}
+
+.snake-app__overlay {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ background: rgba(2, 6, 23, 0.72);
+ border-radius: 10px;
+ color: #f8fafc;
+ font-weight: 600;
+ text-align: center;
+ padding: 16px;
+ backdrop-filter: blur(4px);
+}
+
+.snake-app__overlay-title {
+ font-size: 18px;
+}
+
+.snake-app__overlay-copy {
+ font-size: 12px;
+ color: rgba(248, 250, 252, 0.82);
+}
+
+.snake-app__controls {
+ display: flex;
+ justify-content: center;
+ gap: 10px;
+}
+
+.snake-app__btn {
+ border: none;
+ border-radius: 999px;
+ padding: 8px 14px;
+ font-size: 12px;
+ font-weight: 600;
+ cursor: pointer;
+ background: rgba(255, 255, 255, 0.14);
+ color: #f8fafc;
+ transition: transform 0.12s ease, background 0.12s ease;
+}
+
+.snake-app__btn:hover {
+ background: rgba(255, 255, 255, 0.2);
+ transform: translateY(-1px);
+}
+
+.snake-app__btn--primary {
+ background: linear-gradient(135deg, #3b82f6, #2563eb);
+}
+
+.snake-app__pad {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+}
+
+.snake-app__pad-row {
+ display: flex;
+ gap: 8px;
+}
+
+.snake-app__pad-btn {
+ width: 44px;
+ height: 44px;
+ border: none;
+ border-radius: 10px;
+ background: rgba(255, 255, 255, 0.18);
+ color: #f8fafc;
+ font-size: 18px;
+ cursor: pointer;
+}
+
+.snake-app__pad-btn:hover {
+ background: rgba(255, 255, 255, 0.28);
+}
+
+.snake-app__hint {
+ margin: 0;
+ text-align: center;
+ font-size: 12px;
+ color: rgba(248, 250, 252, 0.72);
+}
+
+/* ββ Mobile fallback βββββββββββββββββββββββββββββββββββββββββββββ */
+@media (max-width: 700px) {
+ .win11-desktop {
+ padding-bottom: env(safe-area-inset-bottom);
+ }
+
+ .desktop-weather-widget {
+ top: 10px;
+ right: 10px;
+ left: 10px;
+ width: auto;
+ max-width: none;
+ }
+
+ .desktop-icons {
+ top: 12px;
+ left: 12px;
+ right: 12px;
+ bottom: 72px;
+ flex-direction: row;
+ flex-wrap: wrap;
+ align-content: flex-start;
+ gap: 8px;
+ max-height: none;
+ overflow-y: auto;
+ }
+
+ .desktop-icon {
+ width: calc(25% - 6px);
+ min-width: 0;
+ padding: 8px 4px;
+ }
+
+ .di-img {
+ font-size: 28px;
+ }
+
+ .di-label {
+ font-size: 10px;
+ }
+
+ .taskbar {
+ height: 56px;
+ padding-bottom: env(safe-area-inset-bottom);
+ }
+
+ .taskbar__center {
+ max-width: calc(100vw - 140px);
+ overflow-x: auto;
+ padding: 0 6px;
+ }
+
+ .taskbar__right {
+ right: 6px;
+ gap: 4px;
+ }
+
+ .taskbar__clock {
+ font-size: 10px;
+ padding: 4px 6px;
+ }
+
+ .taskbar__btn {
+ width: 40px;
+ height: 40px;
+ }
+
+ .start-menu {
+ left: 8px;
+ right: 8px;
+ bottom: 64px;
+ width: auto;
+ max-width: none;
+ max-height: min(78dvh, 620px);
+ transform: none;
+ padding: 16px 14px 0;
+ }
+
+ .start-menu__grid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+
+ .start-menu__search-placeholder {
+ font-size: 12px;
+ }
+
+ .start-menu__footer {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+ }
+
+ .about-app,
+ .contact-app {
+ padding: 16px 16px 24px;
+ }
+
+ .about-app__header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 12px;
+ }
+
+ .about-app__name {
+ font-size: 20px;
+ }
+
+ .projects-app__header,
+ .news-app__header,
+ .sudoku-app__header,
+ .snake-app__header {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .news-app__actions,
+ .sudoku-app__actions {
+ justify-content: flex-start;
+ }
+
+ .news-app__header,
+ .sudoku-app__header,
+ .snake-app__header {
+ gap: 12px;
+ }
+
+ .news-app__statusbar,
+ .sudoku-app__statusbar {
+ align-items: flex-start;
+ }
+
+ .sudoku-app {
+ padding: 14px 12px 16px;
+ }
+
+ .sudoku-app__board {
+ width: min(100%, 92vw);
+ }
+
+ .sudoku-app__keypad {
+ width: min(100%, 92vw);
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ }
+
+ .snake-app {
+ padding: 14px 12px 16px;
+ }
+
+ .snake-app__stats {
+ flex-direction: row;
+ flex-wrap: wrap;
+ min-width: unset;
+ }
+
+ .snake-app__board {
+ width: min(100%, 92vw);
+ }
+
+ .snake-app__pad-row {
+ gap: 6px;
+ }
+
+ .snake-app__pad-btn {
+ width: 40px;
+ height: 40px;
+ }
+}
diff --git a/src/pages/desktop/Desktop.js b/src/pages/desktop/Desktop.js
new file mode 100644
index 0000000..1d38097
--- /dev/null
+++ b/src/pages/desktop/Desktop.js
@@ -0,0 +1,917 @@
+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 (
+
+ );
+}
+
+function AboutApp() {
+ return (
+
+
+
AT
+
+
Ahmad Tuflihun
+
tuf-tuf / leguna
+
+
+
+ Developer passionate about game dev and mobile app dev. Loves building
+ AI Bots, but hates Data Cleaning. Specialises in Flutter, Unity, and
+ full-stack web development.
+
+
+
+ π
+ Pontianak, West Borneo, Indonesia
+
+
+ π§
+ ahmadtuflihunxd@gmail.com
+
+
+ π±
+ +62 852 4665 4277
+
+
+
+
Skills
+
+ {[
+ "Flutter",
+ "Kotlin",
+ "React",
+ "Node.js",
+ "Unity",
+ "Unreal Engine",
+ "C#",
+ "Python",
+ "Laravel",
+ "Arduino",
+ "Raspberry Pi",
+ "A-Frame / WebXR",
+ ].map((s) => (
+
+ {s}
+
+ ))}
+
+
+ );
+}
+
+function ProjectsApp() {
+ const projects = [
+ {
+ name: "AR Training App",
+ desc:
+ "Augmented Reality application for Air Conditioning factory employee training using Unity AR Foundation.",
+ tech: ["Unity", "AR Foundation", "C#"],
+ url: "https://numetagen.id/",
+ },
+ {
+ name: "Sejutacita / Dealls Mobile App",
+ desc:
+ "Flutter mobile application for job seekers. Implemented BLoC design pattern and performance improvements.",
+ tech: ["Flutter", "Dart", "BLoC"],
+ url: "https://usedeall.com/",
+ },
+ {
+ name: "Inventory Management App",
+ desc:
+ "Android app with QR code scanning and label printer integration for stock management.",
+ tech: ["Flutter", "Kotlin", "QR Code"],
+ url: "https://idekite.id/",
+ },
+ {
+ name: "WebXR Virtual Event",
+ desc:
+ "Immersive virtual event platform using A-Frame WebXR for an online conference experience.",
+ tech: ["A-Frame", "JavaScript", "WebXR"],
+ url: "https://github.com/Leguna",
+ },
+ {
+ name: "IoT PLC WebApp",
+ desc:
+ "Web application for monitoring and controlling Programmable Logic Controllers via Raspberry Pi.",
+ tech: ["React", "Python", "Raspberry Pi", "MQTT"],
+ url: "https://github.com/Leguna",
+ },
+ {
+ name: "Discord AI Bot",
+ desc:
+ "AI-powered bot for community management and teaching-assistant tasks using Python and Discord API.",
+ tech: ["Python", "Discord API", "ML"],
+ url: "https://github.com/Leguna",
+ },
+ ];
+
+ return (
+
+ );
+}
+
+function ContactApp() {
+ const socials = [
+ { label: "GitHub", url: "https://github.com/Leguna", icon: "π" },
+ {
+ label: "LinkedIn",
+ url: "https://www.linkedin.com/in/tuflihun/",
+ icon: "πΌ",
+ },
+ {
+ label: "Instagram",
+ url: "https://www.instagram.com/gorilla_vegetarian/",
+ icon: "π·",
+ },
+ {
+ label: "YouTube",
+ url: "https://www.youtube.com/channel/UCDWxRwupxJ1h0xN8AevlKVA",
+ icon: "βΆοΈ",
+ },
+ {
+ label: "Stack Overflow",
+ url: "https://stackoverflow.com/users/8574540/leguna",
+ icon: "π",
+ },
+ ];
+
+ return (
+
+
π« Contact
+
+
+ π§
+ ahmadtuflihunxd@gmail.com
+
+
+ π±
+ +62 852 4665 4277
+
+
+ π
+ Pontianak, West Borneo, Indonesia 78114
+
+
+
Social Media
+
+
+ );
+}
+
+function WeatherWidget() {
+ const [weather, setWeather] = useState(null);
+ const [locationLabel, setLocationLabel] = useState("Bandung, Indonesia");
+ const [status, setStatus] = useState("loading");
+
+ const loadWeather = useCallback(() => {
+ if (!navigator.geolocation) {
+ setStatus("fallback");
+ setWeather({
+ temperature: 27,
+ weatherCode: 1,
+ description: "Clear skies",
+ });
+ return;
+ }
+
+ navigator.geolocation.getCurrentPosition(
+ (position) => {
+ const { latitude, longitude } = position.coords;
+ const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude.toFixed(3)}&longitude=${longitude.toFixed(3)}¤t=temperature_2m,weather_code&timezone=auto`;
+ const geocodeUrl = `https://geocoding-api.open-meteo.com/v1/reverse?latitude=${latitude.toFixed(3)}&longitude=${longitude.toFixed(3)}&language=en`;
+
+ Promise.all([
+ fetch(weatherUrl).then((response) => response.json()),
+ fetch(geocodeUrl).then((response) => response.json()),
+ ])
+ .then(([weatherResponse, geocodeResponse]) => {
+ const current = weatherResponse?.current;
+ const place = geocodeResponse?.results?.[0];
+ const nextWeather = {
+ temperature: Math.round(current?.temperature_2m ?? 27),
+ weatherCode: current?.weather_code ?? 1,
+ description: getWeatherDescription(current?.weather_code ?? 1),
+ };
+ const nextLabel = place ? `${place.name}${place.country ? `, ${place.country}` : ""}` : "Your location";
+ setWeather(nextWeather);
+ setLocationLabel(nextLabel);
+ setStatus("ready");
+ })
+ .catch(() => {
+ setWeather({
+ temperature: 27,
+ weatherCode: 1,
+ description: "Clear skies",
+ });
+ setStatus("fallback");
+ });
+ },
+ () => {
+ setWeather({
+ temperature: 27,
+ weatherCode: 1,
+ description: "Clear skies",
+ });
+ setStatus("fallback");
+ }
+ );
+ }, []);
+
+ useEffect(() => {
+ loadWeather();
+ }, [loadWeather]);
+
+ const icon = getWeatherIcon(weather?.weatherCode ?? 1);
+
+ return (
+
+
+
{icon}
+
+
Weather
+
{locationLabel}
+
+
+
+ {status === "loading" ? (
+
Checking your local forecastβ¦
+ ) : (
+ <>
+
{weather?.temperature ?? 27}Β°C
+
{weather?.description ?? "Clear skies"}
+ >
+ )}
+
+
+ );
+}
+
+function getWeatherDescription(code) {
+ switch (code) {
+ case 0:
+ return "Clear skies";
+ case 1:
+ case 2:
+ return "Mostly clear";
+ case 3:
+ return "Cloudy";
+ case 45:
+ case 48:
+ return "Foggy";
+ case 61:
+ case 63:
+ case 65:
+ return "Rain";
+ case 71:
+ case 73:
+ case 75:
+ return "Snow";
+ default:
+ return "Pleasant";
+ }
+}
+
+function getWeatherIcon(code) {
+ switch (code) {
+ case 0:
+ return "βοΈ";
+ case 1:
+ case 2:
+ return "π€οΈ";
+ case 3:
+ return "βοΈ";
+ case 45:
+ case 48:
+ return "π«οΈ";
+ case 61:
+ case 63:
+ case 65:
+ return "π§οΈ";
+ case 71:
+ case 73:
+ case 75:
+ return "βοΈ";
+ default:
+ return "π¦οΈ";
+ }
+}
+
+function SudokuApp() {
+ const initialBoard = [
+ [5, 3, null, null, 7, null, null, null, null],
+ [6, null, null, 1, 9, 5, null, null, null],
+ [null, 9, 8, null, null, null, null, 6, null],
+ [8, null, null, null, 6, null, null, null, 3],
+ [4, null, null, 8, null, 3, null, null, 1],
+ [7, null, null, null, 2, null, null, null, 6],
+ [null, 6, null, null, null, null, 2, 8, null],
+ [null, null, null, 4, 1, 9, null, null, 5],
+ [null, null, null, null, 8, null, null, 7, 9],
+ ];
+
+ const [board, setBoard] = useState(initialBoard);
+ const [selectedCell, setSelectedCell] = useState(null);
+ const [message, setMessage] = useState("Tap a cell and choose a number.");
+
+ const handleCellClick = (row, column) => {
+ if (board[row][column] !== null) {
+ setMessage("That space is locked. Pick an empty square.");
+ return;
+ }
+
+ setSelectedCell({ row, column });
+ setMessage(`Editing row ${row + 1}, column ${column + 1}.`);
+ };
+
+ const handleNumberSelect = (value) => {
+ if (!selectedCell) {
+ setMessage("First select an empty square.");
+ return;
+ }
+
+ const nextBoard = board.map((row) => [...row]);
+ nextBoard[selectedCell.row][selectedCell.column] = value;
+ setBoard(nextBoard);
+ setMessage(`Placed ${value} in the selected square.`);
+ };
+
+ const resetPuzzle = () => {
+ setBoard(initialBoard.map((row) => [...row]));
+ setSelectedCell(null);
+ setMessage("Puzzle reset. Choose a square to begin.");
+ };
+
+ return (
+
+
+
+
Mini Sudoku
+
Try a quick puzzle with a few taps.
+
+
+ Reset
+
+
+
+ {board.map((row, rowIndex) =>
+ row.map((value, columnIndex) => {
+ const isSelected = selectedCell?.row === rowIndex && selectedCell?.column === columnIndex;
+ const cellClasses = [
+ "sudoku-app__cell",
+ value !== null ? "sudoku-app__cell--fixed" : "",
+ isSelected ? "sudoku-app__cell--selected" : "",
+ rowIndex === 2 || rowIndex === 5 ? "sudoku-app__cell--row-divider" : "",
+ columnIndex === 2 || columnIndex === 5 ? "sudoku-app__cell--col-divider" : "",
+ ]
+ .filter(Boolean)
+ .join(" ");
+
+ return (
+ handleCellClick(rowIndex, columnIndex)}
+ >
+ {value ?? ""}
+
+ );
+ })
+ )}
+
+
+
+ {[1, 2, 3, 4, 5, 6, 7, 8, 9].map((value) => (
+ handleNumberSelect(value)} className="pill-button">
+ {value}
+
+ ))}
+
+
{message}
+
+
+ );
+}
+
+function SnakeApp() {
+ const BOARD_SIZE = 20;
+ const initialSnake = [{ x: 10, y: 10 }, { x: 9, y: 10 }];
+
+ const getRandomFood = useCallback((snake) => {
+ const available = [];
+ for (let y = 0; y < BOARD_SIZE; y += 1) {
+ for (let x = 0; x < BOARD_SIZE; x += 1) {
+ if (!snake.some((segment) => segment.x === x && segment.y === y)) {
+ available.push({ x, y });
+ }
+ }
+ }
+ return available[Math.floor(Math.random() * available.length)] || { x: 0, y: 0 };
+ }, []);
+
+ const [snake, setSnake] = useState(initialSnake);
+ const [food, setFood] = useState(() => getRandomFood(initialSnake));
+ const [direction, setDirection] = useState({ x: 1, y: 0 });
+ const [isRunning, setIsRunning] = useState(false);
+ const [gameOver, setGameOver] = useState(false);
+ const [score, setScore] = useState(0);
+
+ const restartGame = useCallback(() => {
+ const freshSnake = [{ x: 10, y: 10 }, { x: 9, y: 10 }];
+ setSnake(freshSnake);
+ setFood(getRandomFood(freshSnake));
+ setDirection({ x: 1, y: 0 });
+ setIsRunning(true);
+ setGameOver(false);
+ setScore(0);
+ }, [getRandomFood]);
+
+ useEffect(() => {
+ if (!isRunning || gameOver) {
+ return undefined;
+ }
+
+ const interval = window.setInterval(() => {
+ setSnake((prevSnake) => {
+ const nextHead = {
+ x: prevSnake[0].x + direction.x,
+ y: prevSnake[0].y + direction.y,
+ };
+
+ const hitWall =
+ nextHead.x < 0 || nextHead.y < 0 || nextHead.x >= BOARD_SIZE || nextHead.y >= BOARD_SIZE;
+ const hitSelf = prevSnake.some((segment) => segment.x === nextHead.x && segment.y === nextHead.y);
+
+ if (hitWall || hitSelf) {
+ setGameOver(true);
+ setIsRunning(false);
+ return prevSnake;
+ }
+
+ const ateFood = nextHead.x === food.x && nextHead.y === food.y;
+ const nextSnake = ateFood ? [nextHead, ...prevSnake] : [nextHead, ...prevSnake.slice(0, -1)];
+ const nextFood = ateFood ? getRandomFood(nextSnake) : food;
+
+ if (ateFood) {
+ setScore((prevScore) => prevScore + 1);
+ }
+ setFood(nextFood);
+ return nextSnake;
+ });
+ }, 120);
+
+ return () => window.clearInterval(interval);
+ }, [BOARD_SIZE, direction, food, gameOver, getRandomFood, isRunning]);
+
+ useEffect(() => {
+ const handleKeyDown = (event) => {
+ const map = {
+ ArrowUp: { x: 0, y: -1 },
+ ArrowDown: { x: 0, y: 1 },
+ ArrowLeft: { x: -1, y: 0 },
+ ArrowRight: { x: 1, y: 0 },
+ };
+
+ const nextDirection = map[event.key];
+ if (!nextDirection) {
+ return;
+ }
+
+ event.preventDefault();
+ setDirection((prevDirection) => {
+ if (prevDirection.x + nextDirection.x === 0 && prevDirection.y + nextDirection.y === 0) {
+ return prevDirection;
+ }
+ return nextDirection;
+ });
+ };
+
+ window.addEventListener("keydown", handleKeyDown);
+ return () => window.removeEventListener("keydown", handleKeyDown);
+ }, []);
+
+ const handleDirectionTap = (nextDirection) => {
+ setDirection((prevDirection) => {
+ if (prevDirection.x + nextDirection.x === 0 && prevDirection.y + nextDirection.y === 0) {
+ return prevDirection;
+ }
+ return nextDirection;
+ });
+ };
+
+ return (
+
+
+
+
Snake
+
Use the arrows or tap the controls to snack your way around the board.
+
+
+ {isRunning ? "Restart" : "Start"}
+
+
+
+ Score: {score}
+ Status: {gameOver ? "Game over" : isRunning ? "Running" : "Paused"}
+
+
+ {Array.from({ length: BOARD_SIZE * BOARD_SIZE }, (_, index) => {
+ const x = index % BOARD_SIZE;
+ const y = Math.floor(index / BOARD_SIZE);
+ const isHead = snake[0]?.x === x && snake[0]?.y === y;
+ const isBody = snake.some((segment) => segment.x === x && segment.y === y);
+ const isFood = food.x === x && food.y === y;
+ const cellClasses = [
+ "snake-app__cell",
+ isHead ? "snake-app__cell--head" : "",
+ isBody && !isHead ? "snake-app__cell--body" : "",
+ isFood ? "snake-app__cell--food" : "",
+ ]
+ .filter(Boolean)
+ .join(" ");
+
+ return
;
+ })}
+ {!isRunning && !gameOver && (
+
+
Tap start and steer with the arrow keys.
+
+ )}
+ {gameOver && (
+
+
Game over. Start a new round.
+
+ )}
+
+
+ handleDirectionTap({ x: 0, y: -1 })} className="pill-button">
+ β
+
+ handleDirectionTap({ x: 0, y: 1 })} className="pill-button">
+ β
+
+ handleDirectionTap({ x: -1, y: 0 })} className="pill-button">
+ β
+
+ handleDirectionTap({ x: 1, y: 0 })} className="pill-button">
+ β
+
+
+
+ );
+}
+
+/* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ App Definitions
+ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
+const APP_DEFS = [
+ {
+ id: "resume",
+ title: "My CV",
+ icon: "π",
+ defaultPos: { x: 80, y: 40 },
+ defaultSize: { width: 860, height: 580 },
+ component: ,
+ },
+ {
+ id: "about",
+ title: "About Me",
+ icon: "π€",
+ defaultPos: { x: 140, y: 60 },
+ defaultSize: { width: 560, height: 500 },
+ component: ,
+ },
+ {
+ id: "projects",
+ title: "Projects",
+ icon: "π₯οΈ",
+ defaultPos: { x: 180, y: 70 },
+ defaultSize: { width: 620, height: 510 },
+ component: ,
+ },
+ {
+ id: "contact",
+ title: "Contact",
+ icon: "π«",
+ defaultPos: { x: 220, y: 80 },
+ defaultSize: { width: 460, height: 480 },
+ component: ,
+ },
+ {
+ id: "sudoku",
+ title: "Sudoku",
+ icon: "π§©",
+ defaultPos: { x: 260, y: 100 },
+ defaultSize: { width: 480, height: 520 },
+ component: ,
+ },
+ {
+ id: "snake",
+ title: "Snake",
+ icon: "π",
+ defaultPos: { x: 300, y: 120 },
+ defaultSize: { width: 520, height: 560 },
+ component: ,
+ },
+];
+
+const DESKTOP_ICONS = [
+ { id: "resume", label: "My CV", icon: "π", action: "app" },
+ { id: "about", label: "About Me", icon: "π€", action: "app" },
+ { id: "projects", label: "Projects", icon: "π₯οΈ", action: "app" },
+ { id: "contact", label: "Contact", icon: "π«", action: "app" },
+ { id: "sudoku", label: "Sudoku", icon: "π§©", action: "app" },
+ { id: "snake", label: "Snake", icon: "π", action: "app" },
+ {
+ id: "github",
+ label: "GitHub",
+ icon: "π",
+ action: "url",
+ url: "https://github.com/Leguna",
+ },
+ {
+ id: "linkedin",
+ label: "LinkedIn",
+ icon: "πΌ",
+ action: "url",
+ url: "https://www.linkedin.com/in/tuflihun/",
+ },
+];
+
+const WINDOW_BASE_Z = 100;
+
+/* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ Desktop Component
+ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
+export default function Desktop() {
+ // Map of appId β { id, minimized, z }
+ const [windows, setWindows] = useState({});
+ const [activeId, setActiveId] = useState(null);
+ const [showStart, setShowStart] = useState(false);
+ const [selectedIcon, setSelectedIcon] = useState(null);
+
+ // Prevent body scroll while desktop is mounted
+ useEffect(() => {
+ const prev = document.body.style.overflow;
+ document.body.style.overflow = "hidden";
+ return () => {
+ document.body.style.overflow = prev;
+ };
+ }, []);
+
+ const nextZ = useCallback((prev) => {
+ return Math.max(0, ...Object.values(prev).map((w) => w.z || 0)) + 1;
+ }, []);
+
+ const openApp = useCallback(
+ (id) => {
+ setWindows((prev) => {
+ const z = Math.max(nextZ(prev), WINDOW_BASE_Z);
+ if (prev[id]) {
+ return { ...prev, [id]: { ...prev[id], minimized: false, z } };
+ }
+ return { ...prev, [id]: { id, minimized: false, z } };
+ });
+ setActiveId(id);
+ setShowStart(false);
+ },
+ [nextZ]
+ );
+
+ const closeApp = useCallback((id) => {
+ setWindows((prev) => {
+ const next = { ...prev };
+ delete next[id];
+ return next;
+ });
+ setActiveId((prev) => (prev === id ? null : prev));
+ }, []);
+
+ const minimizeApp = useCallback((id) => {
+ setWindows((prev) => ({
+ ...prev,
+ [id]: { ...prev[id], minimized: true },
+ }));
+ setActiveId((prev) => (prev === id ? null : prev));
+ }, []);
+
+ const bringToFront = useCallback(
+ (id) => {
+ setWindows((prev) => {
+ const z = Math.max(nextZ(prev), WINDOW_BASE_Z);
+ return { ...prev, [id]: { ...prev[id], z } };
+ });
+ setActiveId(id);
+ },
+ [nextZ]
+ );
+
+ const handleTaskbarApp = useCallback(
+ (id) => {
+ const win = windows[id];
+ if (!win) {
+ openApp(id);
+ } else if (win.minimized) {
+ setWindows((prev) => {
+ const z = Math.max(nextZ(prev), WINDOW_BASE_Z);
+ return { ...prev, [id]: { ...prev[id], minimized: false, z } };
+ });
+ setActiveId(id);
+ } else if (activeId === id) {
+ minimizeApp(id);
+ } else {
+ bringToFront(id);
+ }
+ },
+ [windows, activeId, openApp, minimizeApp, bringToFront, nextZ]
+ );
+
+ const handleIconClick = (icon) => {
+ setSelectedIcon(icon.id);
+ };
+
+ const handleIconDoubleClick = (icon) => {
+ setSelectedIcon(null);
+ if (icon.action === "url") {
+ window.open(icon.url, "_blank", "noopener,noreferrer");
+ } else {
+ openApp(icon.id);
+ }
+ };
+
+ const taskbarApps = APP_DEFS.map((d) => ({
+ id: d.id,
+ title: d.title,
+ icon: d.icon,
+ isOpen: !!(windows[d.id] && !windows[d.id].minimized),
+ isActive: activeId === d.id,
+ }));
+
+ return (
+ {
+ setSelectedIcon(null);
+ }}
+ >
+ {/* Desktop Icons */}
+
+ {DESKTOP_ICONS.map((icon) => (
+
{
+ e.stopPropagation();
+ handleIconClick(icon);
+ }}
+ onDoubleClick={(e) => {
+ e.stopPropagation();
+ handleIconDoubleClick(icon);
+ }}
+ title={`Double-click to open ${icon.label}`}
+ >
+ {icon.icon}
+ {icon.label}
+
+ ))}
+
+
+ {/* App Windows */}
+ {APP_DEFS.map((def) => {
+ const win = windows[def.id];
+ if (!win || win.minimized) return null;
+ return (
+
closeApp(def.id)}
+ onMinimize={() => minimizeApp(def.id)}
+ onFocus={() => bringToFront(def.id)}
+ >
+ {def.component}
+
+ );
+ })}
+
+ {/* Start Menu */}
+ {showStart && (
+ <>
+
setShowStart(false)}
+ />
+ setShowStart(false)}
+ />
+ >
+ )}
+
+ {/* Taskbar */}
+ setShowStart((s) => !s)}
+ showStartMenu={showStart}
+ />
+
+ );
+}
diff --git a/src/pages/desktop/StartMenu.js b/src/pages/desktop/StartMenu.js
new file mode 100644
index 0000000..d99d87f
--- /dev/null
+++ b/src/pages/desktop/StartMenu.js
@@ -0,0 +1,77 @@
+import React from "react";
+
+export default function StartMenu({ apps, onAppOpen, onClose }) {
+ return (
+
e.stopPropagation()}>
+ {/* Search bar */}
+
+
+
+
+
+
+ Search for apps, files, settings
+
+
+
+ {/* Pinned section */}
+
Pinned
+
+ {apps.map((app) => (
+
{
+ onAppOpen(app.id);
+ onClose();
+ }}
+ >
+ {app.icon}
+ {app.title}
+
+ ))}
+
+
+ {/* Footer */}
+
+
+
{
+ if (window.confirm("Reload the page?")) window.location.reload();
+ }}
+ >
+
+
+
+
+ );
+}
+
+function PowerIcon() {
+ return (
+
+
+
+
+ );
+}
diff --git a/src/pages/desktop/Taskbar.js b/src/pages/desktop/Taskbar.js
new file mode 100644
index 0000000..29de9fb
--- /dev/null
+++ b/src/pages/desktop/Taskbar.js
@@ -0,0 +1,100 @@
+import React, { useState, useEffect } from "react";
+
+export default function Taskbar({
+ taskbarApps,
+ onAppClick,
+ onStartClick,
+ showStartMenu,
+}) {
+ const [now, setNow] = useState(new Date());
+
+ useEffect(() => {
+ const t = setInterval(() => setNow(new Date()), 1000);
+ return () => clearInterval(t);
+ }, []);
+
+ const pad = (n) => String(n).padStart(2, "0");
+ const h = now.getHours() % 12 || 12;
+ const m = pad(now.getMinutes());
+ const ampm = now.getHours() >= 12 ? "PM" : "AM";
+ const dateStr = now.toLocaleDateString("en-US", {
+ month: "numeric",
+ day: "numeric",
+ year: "numeric",
+ });
+
+ return (
+
+
+ {/* Start button */}
+
+
+
+
+ {/* Pinned / open apps */}
+ {taskbarApps.map((app) => (
+ onAppClick(app.id)}
+ title={app.title}
+ >
+ {app.icon}
+
+ ))}
+
+
+ {/* System tray β clock */}
+
+
+
+ {h}:{m} {ampm}
+
+
{dateStr}
+
+
+
+
+
+
+ );
+}
+
+function WinLogo() {
+ return (
+
+
+
+
+
+
+ );
+}
+
+function NotifIcon() {
+ return (
+
+
+
+
+ );
+}
diff --git a/src/pages/opensource/Opensource.js b/src/pages/opensource/Opensource.js
index 4d70e42..52fe006 100644
--- a/src/pages/opensource/Opensource.js
+++ b/src/pages/opensource/Opensource.js
@@ -1,8 +1,6 @@
import React, { Component } from "react";
import Header from "../../components/header/Header";
import Footer from "../../components/footer/Footer";
-import OpensourceCharts from "../../containers/opensourceCharts/OpensourceCharts";
-import Organizations from "../../containers/organizations/Organizations";
import PullRequests from "../../containers/pullRequests/PullRequests";
import Issues from "../../containers/issues/Issues";
import TopButton from "../../components/topButton/TopButton";
diff --git a/src/pages/projects/Projects.js b/src/pages/projects/Projects.js
index d48fa1f..103eb9f 100644
--- a/src/pages/projects/Projects.js
+++ b/src/pages/projects/Projects.js
@@ -7,7 +7,6 @@ import Button from "../../components/button/Button";
import TopButton from "../../components/topButton/TopButton";
import { Fade } from "react-reveal";
import {
- greeting,
projectsHeader,
publicationsHeader,
publications,
diff --git a/src/portfolio.js b/src/portfolio.js
index 03cc6f3..fd9dc31 100644
--- a/src/portfolio.js
+++ b/src/portfolio.js
@@ -23,8 +23,7 @@ const greeting = {
nickname: "tuf-tuf / leguna",
subTitle:
"Developer that passionate at game dev and mobile app dev. Love Building AI Bot, but hate Data Cleaning.",
- resumeLink:
- "https://drive.google.com/file/d/1ilwOgmy7_1Cub0qOBgfI6k-NHC9A3XeI/view?usp=sharing",
+ resumeLink: "https://drive.google.com/file/d/1EAoyWagT9bmfIevIuvVlHq8azNR4hHYE/view?usp=sharing",
portfolio_repository: "https://github.com/Leguna/leguna.github.io/",
};