diff --git a/src/app/components/AndroidHome.tsx b/src/app/components/AndroidHome.tsx deleted file mode 100644 index 93f5dd6..0000000 --- a/src/app/components/AndroidHome.tsx +++ /dev/null @@ -1,90 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { APPS, APP_BY_ID } from "./apps"; -import Clock from "./Clock"; -import { useTheme } from "./useTheme"; -import { WALLPAPER } from "../data"; - -const DOCK_IDS = ["projects", "services", "socials", "contributions"]; - -export default function AndroidHome() { - const { theme, toggle } = useTheme(); - const [openId, setOpenId] = useState(null); - const openApp = openId ? APP_BY_ID[openId] : null; - - const renderIcon = (appId: string, inDock = false) => { - const app = APP_BY_ID[appId]; - if (!app) return null; - return ( - - ); - }; - - return ( -
- {openApp ? ( -
-
- - {openApp.title} -
-
{openApp.content}
-
- ) : ( -
-
- - - - -
- - - -
-
- -
-
dvorakmortonxd.dev
-
- -
{APPS.map((app) => renderIcon(app.id))}
- -
{DOCK_IDS.map((id) => renderIcon(id, true))}
- -
- -
-
- )} -
- ); -} diff --git a/src/app/components/MobileHome.tsx b/src/app/components/MobileHome.tsx new file mode 100644 index 0000000..757e009 --- /dev/null +++ b/src/app/components/MobileHome.tsx @@ -0,0 +1,132 @@ +"use client"; + +import { useState, useSyncExternalStore } from "react"; +import { APPS, APP_BY_ID } from "./apps"; +import Clock from "./Clock"; +import { WALLPAPER } from "../data"; + +const DOCK_IDS = ["projects", "services", "socials", "contributions"]; + +function subscribe(callback: () => void) { + const id = window.setInterval(callback, 1000); + return () => window.clearInterval(id); +} + +function HomeWidget() { + const date = useSyncExternalStore( + subscribe, + () => + new Date().toLocaleDateString([], { + weekday: "long", + month: "short", + day: "numeric", + }), + () => "" + ); + + return ( +
+
+ +
+
+ {date} +
+
dvorakmortonxd.dev
+
+ ); +} + +export default function MobileHome() { + const [openId, setOpenId] = useState(null); + const openApp = openId ? APP_BY_ID[openId] : null; + + const renderIcon = (appId: string, inDock = false) => { + const app = APP_BY_ID[appId]; + if (!app) return null; + return ( + + ); + }; + + return ( +
+ {openApp ? ( +
+
+ + {openApp.title} +
+
{openApp.content}
+
+ ) : ( +
+ + )} +
+ ); +} diff --git a/src/app/components/ThemeRoot.tsx b/src/app/components/ThemeRoot.tsx index e6d1eae..c1df1d5 100644 --- a/src/app/components/ThemeRoot.tsx +++ b/src/app/components/ThemeRoot.tsx @@ -3,7 +3,7 @@ import { useTheme } from "./useTheme"; import TerminalLayout from "./TerminalLayout"; import WindowsDesktop from "./WindowsDesktop"; -import AndroidHome from "./AndroidHome"; +import MobileHome from "./MobileHome"; export default function ThemeRoot() { const { theme } = useTheme(); @@ -18,7 +18,7 @@ export default function ThemeRoot() {
- +
); diff --git a/src/app/skeuo.css b/src/app/skeuo.css index 94291c9..f2a99a4 100644 --- a/src/app/skeuo.css +++ b/src/app/skeuo.css @@ -2,9 +2,9 @@ SKEUOMORPHIC THEME ------------------------------------------------------------ Desktop -> KDE Plasma (Breeze) - Mobile -> modern Android (Material You / Material 3) + Mobile -> iOS-style home screen Active when . The terminal theme is - the alternate, reachable from the tray / launcher / Android chip. + the alternate, reachable from the tray / launcher / app. ============================================================ */ html[data-theme="skeuo"] { @@ -35,7 +35,7 @@ html[data-theme="skeuo"] body { .skeuo-mobile-root { display: block; } } -/* Real phones always get the Android UI, even in landscape. */ +/* Real phones always get the mobile UI, even in landscape. */ html[data-device="phone"] .skeuo-desktop-root { display: none; } html[data-device="phone"] .skeuo-mobile-root { display: block; } @@ -285,7 +285,7 @@ a.win-icon { } /* ============================================================ - APP CONTENT SURFACE (shared by desktop windows + Android apps) + APP CONTENT SURFACE (shared by desktop windows + mobile apps) ============================================================ */ .app-surface { --white: #0e3355; @@ -1035,13 +1035,13 @@ a.win-icon { } /* ============================================================ - MOBILE -> modern Android (Material You) + MOBILE -> iOS-style home screen ============================================================ */ -.android-root { +.mobile-root { display: block; } -.android-home { +.mobile-home { position: fixed; inset: 0; overflow: hidden; @@ -1050,9 +1050,10 @@ a.win-icon { background-position: center; background-repeat: no-repeat; color: #ffffff; - font-family: Roboto, "Helvetica Neue", Arial, sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", Roboto, + "Helvetica Neue", Arial, sans-serif; } -.android-home::before { +.mobile-home::before { content: ""; position: absolute; inset: 0; @@ -1066,158 +1067,239 @@ a.win-icon { ); } -.android-statusbar { +.mobile-notch { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 132px; + height: 27px; + z-index: 60; + background: #000; + border-radius: 0 0 18px 18px; + pointer-events: none; +} +.mobile-notch::after { + content: ""; + position: absolute; + right: 22px; + top: 9px; + width: 7px; + height: 7px; + border-radius: 50%; + background: radial-gradient(circle at 35% 30%, #2b3a55, #05070b 70%); +} + +.mobile-statusbar { position: absolute; top: 0; left: 0; right: 0; - height: 28px; - z-index: 20; + height: 30px; + z-index: 55; display: flex; align-items: center; justify-content: space-between; - padding: 0 18px; - font-size: 0.75rem; - font-weight: 500; - letter-spacing: 0.02em; + padding: 0 16px; + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.01em; } -.android-status-icons { - letter-spacing: 2px; +.mobile-status-time { + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); } - -.android-theme-chip { - position: absolute; - top: 36px; - right: 14px; - z-index: 20; +.mobile-status-icons { display: inline-flex; align-items: center; gap: 6px; - height: 34px; - padding: 0 14px; - border-radius: 17px; - border: 1px solid rgba(255, 255, 255, 0.25); - background: rgba(255, 255, 255, 0.16); - color: #ffffff; - font-size: 0.75rem; - font-weight: 500; - -webkit-backdrop-filter: blur(10px); - backdrop-filter: blur(10px); - cursor: pointer; } -.android-theme-chip:active { - background: rgba(255, 255, 255, 0.28); +.mobile-status-icons svg { + display: block; } -.android-glance { - position: absolute; - top: 58px; - left: 20px; - z-index: 10; +.mobile-widget { + grid-column: span 2; + grid-row: span 2; + border-radius: 26px; + padding: 14px 16px; + display: flex; + flex-direction: column; + justify-content: center; + gap: 2px; + overflow: hidden; + color: #ffffff; + text-shadow: 0 1px 4px rgba(0, 0, 0, 0.45); + border: 1px solid rgba(255, 255, 255, 0.28); + background-color: rgba(24, 26, 32, 0.62); + background-image: linear-gradient( + 155deg, + rgba(255, 255, 255, 0.24), + rgba(255, 255, 255, 0.05) 46%, + rgba(0, 0, 0, 0.3) + ); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.45), + 0 10px 26px rgba(0, 0, 0, 0.4); + -webkit-backdrop-filter: blur(18px) saturate(150%); + backdrop-filter: blur(18px) saturate(150%); } -.android-glance-time { - font-size: 3.4rem; +.mobile-widget-time { + font-size: 2.5rem; font-weight: 300; line-height: 1; letter-spacing: -0.02em; - text-shadow: 0 2px 10px rgba(0, 0, 0, 0.6); } -.android-glance-sub { - margin-top: 3px; +.mobile-widget-date { font-size: 0.78rem; - opacity: 0.92; - text-shadow: 0 1px 4px rgba(0, 0, 0, 0.7); + font-weight: 500; + opacity: 0.95; +} +.mobile-widget-foot { + margin-top: 4px; + font-size: 0.62rem; + font-weight: 400; + letter-spacing: 0.02em; + opacity: 0.72; } -.android-grid { +.mobile-grid { position: absolute; - top: 182px; + top: 46px; left: 0; right: 0; bottom: 112px; z-index: 10; overflow-y: auto; - padding: 8px 16px; + padding: 6px 14px; display: grid; grid-template-columns: repeat(4, 1fr); - gap: 18px 8px; + grid-auto-rows: 86px; + gap: 14px 6px; align-content: start; } -.android-app { +.mobile-app { display: flex; flex-direction: column; align-items: center; - gap: 6px; + gap: 5px; padding: 0; border: none; background: transparent; cursor: pointer; + color: inherit; } -.android-app-icon { +.mobile-app-icon { position: relative; width: 58px; height: 58px; - border-radius: 18px; + border-radius: 21px; display: grid; place-items: center; font-size: 27px; overflow: hidden; - border: 1px solid rgba(255, 255, 255, 0.25); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35), 0 3px 10px rgba(0, 0, 0, 0.45); + border: 1px solid rgba(255, 255, 255, 0.24); + background-color: rgba(22, 24, 30, 0.42); + background-image: linear-gradient( + 160deg, + rgba(255, 255, 255, 0.38), + rgba(255, 255, 255, 0.08) 44%, + rgba(0, 0, 0, 0.08) 72%, + rgba(0, 0, 0, 0.3) + ); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.5), + inset 0 -9px 16px rgba(0, 0, 0, 0.2), + 0 4px 12px rgba(0, 0, 0, 0.42); + -webkit-backdrop-filter: blur(16px) saturate(140%); + backdrop-filter: blur(16px) saturate(140%); } -.android-app-icon::after { +.mobile-app-glyph { + position: relative; + z-index: 1; + filter: saturate(0.35) contrast(1.02); + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); +} +.mobile-app-icon::after { content: ""; position: absolute; inset: 0; - background: linear-gradient(to bottom, rgba(255, 255, 255, 0.26), rgba(255, 255, 255, 0) 55%); + background: linear-gradient( + to bottom, + rgba(255, 255, 255, 0.3), + rgba(255, 255, 255, 0) 48% + ); pointer-events: none; } -.android-app-label { +.mobile-app-icon::after { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient( + to bottom, + rgba(255, 255, 255, 0.3), + rgba(255, 255, 255, 0) 48% + ); + pointer-events: none; +} +.mobile-app-label { max-width: 100%; - font-size: 0.68rem; + font-size: 0.66rem; + font-weight: 400; line-height: 1.15; text-align: center; overflow-wrap: anywhere; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9); } -.android-dock { +.mobile-dock { position: absolute; - left: 0; - right: 0; - bottom: 42px; - z-index: 10; + left: 12px; + right: 12px; + bottom: 30px; + z-index: 15; display: flex; - justify-content: center; - gap: 20px; - padding: 0 16px; + align-items: center; + justify-content: space-evenly; + gap: 8px; + padding: 11px 12px; + border-radius: 30px; + background: rgba(255, 255, 255, 0.16); + border: 1px solid rgba(255, 255, 255, 0.22); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.28), + 0 10px 30px rgba(0, 0, 0, 0.35); + -webkit-backdrop-filter: blur(22px) saturate(150%); + backdrop-filter: blur(22px) saturate(150%); } -.android-app.in-dock .android-app-icon { - width: 52px; - height: 52px; - border-radius: 16px; - font-size: 24px; +.mobile-app.in-dock .mobile-app-icon { + width: 54px; + height: 54px; + border-radius: 19px; + font-size: 25px; } -.android-navbar { +.mobile-pagedots { position: absolute; left: 0; right: 0; - bottom: 0; - height: 22px; - z-index: 30; + bottom: 14px; + z-index: 15; display: flex; align-items: center; justify-content: center; + gap: 7px; } -.android-navbar-pill { - width: 130px; - height: 5px; - border-radius: 3px; - background: rgba(255, 255, 255, 0.85); +.mobile-pagedots span { + width: 6px; + height: 6px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.35); +} +.mobile-pagedots span.active { + background: rgba(255, 255, 255, 0.9); } -.android-app-view { +.mobile-app-view { position: fixed; inset: 0; z-index: 40; @@ -1227,7 +1309,7 @@ a.win-icon { color: #1d1b20; font-family: Roboto, "Helvetica Neue", Arial, sans-serif; } -.android-appbar { +.mobile-appbar { flex: 0 0 auto; display: flex; align-items: center; @@ -1236,7 +1318,7 @@ a.win-icon { background: #fef7ff; border-bottom: 1px solid #e7e0ec; } -.android-back { +.mobile-back { width: 40px; height: 40px; border: none; @@ -1246,19 +1328,19 @@ a.win-icon { font-size: 1.2rem; cursor: pointer; } -.android-back:hover { +.mobile-back:hover { background: #eadcff; } -.android-appbar-title { +.mobile-appbar-title { font-size: 1.1rem; font-weight: 500; } -.android-app-body { +.mobile-app-body { flex: 1; overflow-y: auto; background: #fef7ff; } -.android-app-body.app-surface { +.mobile-app-body.app-surface { --accent: #6750a4; --accent2: #7d5260; --accent3: #6750a4; @@ -1268,10 +1350,10 @@ a.win-icon { --fg: #1d1b20; color: #1d1b20; } -.android-app-body .project-card, -.android-app-body .service-button, -.android-app-body .social-button, -.android-app-body .gym-item { +.mobile-app-body .project-card, +.mobile-app-body .service-button, +.mobile-app-body .social-button, +.mobile-app-body .gym-item { border-radius: 14px; } @@ -1298,28 +1380,31 @@ a.win-icon { } @media (max-width: 380px) { - .android-grid { + .mobile-grid { grid-template-columns: repeat(3, 1fr); } } /* Phones in landscape: compact the home so the app grid (and its labels) fit. */ @media (orientation: landscape) and (max-height: 620px) { - .android-glance { top: 32px; left: 16px; } - .android-glance-time { font-size: 1.9rem; } - .android-glance-sub { margin-top: 1px; font-size: 0.66rem; } - .android-theme-chip { top: 28px; right: 12px; height: 30px; padding: 0 12px; font-size: 0.7rem; } - .android-grid { - top: 92px; - bottom: 80px; - grid-template-columns: repeat(6, 1fr); - gap: 10px 8px; - padding: 4px 24px; + .mobile-notch { width: 108px; height: 22px; } + .mobile-statusbar { height: 24px; font-size: 0.66rem; padding: 0 14px; } + .mobile-grid { + top: 30px; + bottom: 76px; + grid-template-columns: repeat(7, 1fr); + grid-auto-rows: 62px; + gap: 8px 6px; + padding: 4px 22px; } - .android-app { gap: 4px; } - .android-app-icon { width: 44px; height: 44px; border-radius: 14px; font-size: 21px; } - .android-app-label { font-size: 0.6rem; } - .android-dock { bottom: 26px; gap: 18px; } - .android-app.in-dock .android-app-icon { width: 42px; height: 42px; border-radius: 13px; font-size: 20px; } - .android-navbar { height: 16px; } + .mobile-app { gap: 3px; } + .mobile-app-icon { width: 42px; height: 42px; border-radius: 15px; font-size: 20px; } + .mobile-app-label { font-size: 0.58rem; } + .mobile-widget { border-radius: 20px; padding: 10px 12px; } + .mobile-widget-time { font-size: 1.6rem; } + .mobile-widget-date { font-size: 0.66rem; } + .mobile-widget-foot { display: none; } + .mobile-dock { bottom: 14px; padding: 7px 12px; border-radius: 24px; } + .mobile-app.in-dock .mobile-app-icon { width: 40px; height: 40px; border-radius: 14px; font-size: 19px; } + .mobile-pagedots { display: none; } }