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",