diff --git a/src/App.tsx b/src/App.tsx index 70c3eed..a8fd475 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,3 @@ -import { useState } from "react"; import { Kitty } from "./components/Kitty"; import { AppProvider } from "./providers/AppProvider"; import { Music } from "./components/Music"; @@ -6,21 +5,33 @@ import { Nvim } from "./components/Nvim"; import { Waybar } from "./components/Waybar"; import { useApp } from "./hooks/useApp"; import { clamp } from "./utils/math"; +import { Sddm } from "./components/Sddm"; +import { Boot } from "./components/Boot"; +import { Off } from "./components/Off"; const AppRoot = () => { - const [loggedIn, setLoggedIn] = useState(false); - const { brightness } = useApp(); + const { brightness, state } = useApp(); const opacity = clamp(0.5 - (0.5 * brightness) / 100, 0, 0.5); + if (state === "off") { + return ; + } + + if (state === "boot") { + return ; + } + return ( <>
-
- {loggedIn ? ( +
+ {state === "login" ? ( + + ) : (
@@ -34,15 +45,6 @@ const AppRoot = () => {
- ) : ( -
- -
)}
diff --git a/src/components/Boot.tsx b/src/components/Boot.tsx new file mode 100644 index 0000000..1e6fa7a --- /dev/null +++ b/src/components/Boot.tsx @@ -0,0 +1,37 @@ +import { useEffect, useState } from "react"; +import { useApp } from "~/hooks/useApp"; + +const LINES = [ + "Loading Linux...", + "Loading initial ramdisk...", + "Feeding the cat...", + "Cleaning my room...", + "Preparing tuna tomato couscous...", + "Ready", +]; + +export const Boot = () => { + const { setState } = useApp(); + const [line, setLine] = useState(0); + + useEffect(() => { + if (line >= LINES.length) { + setState("login"); + return; + } + + const timeout = setTimeout( + () => setLine(line + 1), + Math.random() * 750 + 200, + ); + return () => clearTimeout(timeout); + }, [setState, line]); + + return ( +
+ {LINES.filter((_, i) => i <= line).map((line, i) => ( +

{line}

+ ))} +
+ ); +}; diff --git a/src/components/Off.tsx b/src/components/Off.tsx new file mode 100644 index 0000000..7620671 --- /dev/null +++ b/src/components/Off.tsx @@ -0,0 +1,33 @@ +import { useState } from "react"; +import { useApp } from "~/hooks/useApp"; + +export const Off = () => { + const { setState } = useApp(); + const [clicked, setClicked] = useState(false); + + const handleClick = () => { + setClicked(true); + + setTimeout(() => { + setState("boot"); + }, 1000); + }; + + return ( +
+ +
+ ); +}; diff --git a/src/components/Sddm.tsx b/src/components/Sddm.tsx new file mode 100644 index 0000000..2743418 --- /dev/null +++ b/src/components/Sddm.tsx @@ -0,0 +1,188 @@ +import { type ReactNode, useEffect, useRef, useState } from "react"; +import { useApp } from "~/hooks/useApp"; + +const SddmActionButton = (props: { + icon: ReactNode; + text: string; + onClick?: () => void; +}) => ( + +); + +const PASSWORD_LENGTH = 12; + +export const Sddm = () => { + const { setState } = useApp(); + + const passwordInputRef = useRef(null); + + const [password, setPassword] = useState(0); + const [showPassword, setShowPassword] = useState(false); + const [now, setNow] = useState(new Date()); + + useEffect(() => { + const interval = setInterval(() => { + setNow(new Date()); + }, 1000); + + return () => clearInterval(interval); + }, []); + + useEffect(() => { + if (password >= PASSWORD_LENGTH) { + passwordInputRef.current?.blur(); + return; + } + + const timeout = setTimeout( + () => { + passwordInputRef.current?.focus(); + + const canType = + password < 4 || + password === PASSWORD_LENGTH - 1 || + Math.random() > 0.18; + setPassword(Math.max(0, password + (canType ? 1 : -1))); + }, + password === 0 ? 3000 : Math.random() * 250 + 250, + ); + return () => clearTimeout(timeout); + }, [password]); + + return ( + <> +
+ +
+
+
+
+

Welcome!

+

+ {now.toLocaleTimeString("en-us", { + hour: "2-digit", + minute: "2-digit", + })} +

+

{now.toLocaleDateString("en-us", { dateStyle: "long" })}

+
+ +
+
+
+  +
+ +
+ +
+
+  +
+ 0 + ? "no." + : "" + : "•".repeat(password) + } + placeholder="Password" + spellCheck={false} + maxLength={PASSWORD_LENGTH} + /> +
+ + +
+ +
+ +

Session: Hyprland

+
+
+ + + + } + text="Suspend" + /> + + + + + + + } + text="Reboot" + /> + + + + } + text="Shutdown" + /> +
+
+
+
+ + ); +}; diff --git a/src/context/AppContext.tsx b/src/context/AppContext.tsx index a30d68c..f8ac36a 100644 --- a/src/context/AppContext.tsx +++ b/src/context/AppContext.tsx @@ -1,11 +1,11 @@ import { createContext } from "react"; import { type Prettify, type State } from "~/utils/types"; -export const AppContext = createContext< - | Prettify< - State<"activeKitty", string> & - State<"brightness", number> & - State<"volume", number> & { screenWidth: number } - > - | undefined ->(undefined); +export const AppContext = createContext(undefined); + +export type AppContextProps = Prettify< + State<"state", "off" | "boot" | "login" | "desktop"> & + State<"activeKitty", string> & + State<"brightness", number> & + State<"volume", number> & { screenWidth: number } +>; diff --git a/src/index.scss b/src/index.scss index 4cda258..ca23741 100644 --- a/src/index.scss +++ b/src/index.scss @@ -51,3 +51,7 @@ body { font-style: normal; font-display: swap; } + +.drop-shadow-white:hover { + filter: drop-shadow(0px 0px 4px rgba(255, 255, 255, 1)); +} diff --git a/src/providers/AppProvider.tsx b/src/providers/AppProvider.tsx index 4371095..4cee683 100644 --- a/src/providers/AppProvider.tsx +++ b/src/providers/AppProvider.tsx @@ -1,10 +1,11 @@ import { type ReactNode, useState, useEffect } from "react"; -import { AppContext } from "~/context/AppContext"; +import { AppContext, type AppContextProps } from "~/context/AppContext"; export const AppProvider = (props: { children?: ReactNode }) => { const [activeKitty, setActiveKitty] = useState(":r0:"); const [brightness, setBrightness] = useState(100); const [volume, setVolume] = useState(100); + const [state, setState] = useState("off"); const [screenWidth, setScreenWidth] = useState(window.innerWidth); @@ -28,6 +29,8 @@ export const AppProvider = (props: { children?: ReactNode }) => { volume, setVolume, screenWidth, + state, + setState, }} > {props.children} diff --git a/tailwind.config.ts b/tailwind.config.ts index cd0a745..e8bf5d3 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -5,6 +5,29 @@ const config = { content: ["index.html", "./src/**/*.tsx", "./src/**/*.ts"], theme: { extend: { + keyframes: { + fadeOut: { + "0%": { opacity: "1" }, + "100%": { opacity: "0" }, + }, + breathing: { + "0%, 100%": { transform: "scale(1)", opacity: "0.9" }, + "50%": { + transform: "scale(1.1)", + opacity: "1", + }, + }, + disappear: { + "0%": { transform: "scale(1)", opacity: "0.95" }, + "20%": { transform: "scale(1.2)", opacity: "1" }, + "100%": { transform: "scale(0)", opacity: "0" }, + }, + }, + animation: { + fadeOut: "fadeOut 250ms ease-out forwards", + breathing: "breathing 4s ease-in-out infinite", + disappear: "disappear 750ms ease-out forwards", + }, fontSize: { sm: "0.8rem", base: "1rem",