6 Commits

Author SHA1 Message Date
f40ab6eb63 A e s t h e t i c s 2025-08-28 00:55:34 +02:00
7f8fee99ce HomeAssistant update 2025-08-27 23:43:40 +02:00
db934eb9e7 Footer 2025-08-27 23:32:08 +02:00
0205916b0a Dashboard with cards 2025-08-27 23:19:17 +02:00
64a33fcd31 Timetable styling 2025-08-27 21:53:12 +02:00
17cc07a684 Devcontainer support 2025-08-27 19:47:13 +02:00
18 changed files with 293 additions and 61 deletions

View File

@@ -0,0 +1,57 @@
{
"name": "bun",
"image": "oven/bun:debian",
"privileged": true,
"features": {
"ghcr.io/devcontainers/features/common-utils:1": {
"version": "latest",
"configureZshAsDefaultShell": true,
"username": "bun",
"userUid": "1000",
"userGid": "1000"
},
"ghcr.io/rocker-org/devcontainer-features/apt-packages:1": {
"packages": "stow,tmux,ripgrep,python3-venv,python3-virtualenv"
},
},
"remoteUser": "bun",
"workspaceFolder": "/home/bun/ws",
"workspaceMount": "source=${localWorkspaceFolder},target=/home/bun/ws,type=bind",
"containerEnv": {
},
"runArgs": [
"--net=host",
"-e",
"DISPLAY=${env:DISPLAY}",
"-e",
"TERM=${env:TERM}",
"-e",
"SHELL=${env:SHELL}",
"-v",
"${env:SSH_AUTH_SOCK}:/tmp/ssh-agent.socket",
"-e",
"SSH_AUTH_SOCK=/tmp/ssh-agent.socket"
],
"mounts": [
{
"source": "/tmp/.X11-unix",
"target": "/tmp/.X11-unix",
"type": "bind",
"consistency": "cached"
},
{
"source": "/dev/dri",
"target": "/dev/dri",
"type": "bind",
"consistency": "cached"
},
{
"source": "/dev/shm",
"target": "/dev/shm",
"type": "bind",
"consistency": "cached"
}
],
"postCreateCommand": {
}
}

View File

@@ -10,6 +10,7 @@
"lodash": "^4.17.21",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-fast-marquee": "^1.6.5",
"react-redux": "^9.2.0",
"redux-persist": "^6.0.0",
"zustand": "^5.0.6",
@@ -634,6 +635,8 @@
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
"react-fast-marquee": ["react-fast-marquee@1.6.5", "", { "peerDependencies": { "react": ">= 16.8.0 || ^18.0.0", "react-dom": ">= 16.8.0 || ^18.0.0" } }, "sha512-swDnPqrT2XISAih0o74zQVE2wQJFMvkx+9VZXYYNSLb/CUcAzU9pNj637Ar2+hyRw6b4tP6xh4GQZip2ZCpQpg=="],
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="],

View File

@@ -16,6 +16,7 @@
"lodash": "^4.17.21",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-fast-marquee": "^1.6.5",
"react-redux": "^9.2.0",
"redux-persist": "^6.0.0",
"zustand": "^5.0.6"

View File

@@ -1,42 +1,7 @@
#root {
height: 100vh;
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
padding: 0;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View File

@@ -1,14 +1,11 @@
import "@/App.css";
import Flatastic from "@/components/Flatastic/Flatastic";
import HomeAssistant from "@/components/HomeAssistant/HomeAssistant";
import Timetable from "@/components/Timetable/Timetable";
import Dashboard from "@/components/Dashboard/Dashboard";
function App() {
return (
<>
<Timetable />
<Flatastic />
<HomeAssistant />
<Dashboard />
</>
);
}

View File

@@ -0,0 +1,28 @@
import Flatastic from "@/components/Flatastic/Flatastic";
import HomeAssistant from "@/components/HomeAssistant/HomeAssistant";
import Timetable from "@/components/Timetable/Timetable";
import Footer from "@/components/Footer/Footer";
import style from "./style.module.css";
export default function Dashboard() {
return (
<div className={style.dashboard}>
<div className={style.cardWrapper}>
<div className={style.card}>
<Timetable />
</div>
<div className={style.card}>
<Flatastic />
</div>
<div className={style.card}>
<HomeAssistant />
</div>
</div>
<div className={style.footer}>
<Footer />
</div>
</div>
);
}

View File

@@ -0,0 +1,25 @@
.dashboard {
display: flex;
flex-direction: column;
height: 100%;
}
.cardWrapper {
margin: 30px;
height: 100%;
gap: 30px;
flex-direction: column;
display: flex;
justify-content: flex-start;
}
.card {
border-radius: 10px;
padding: 1px 100px 30px 100px;
border: 1px solid rgba(220, 220, 220, 0.4);
box-shadow: 5px 5px 7px rgba(220, 220, 220, 0.5);
}
.footer {
background-color: rgba(220, 220, 220, 0.5);
}

View File

@@ -58,11 +58,13 @@ export default function DepartureList(props: {
return (
<div className={style.container}>
<h2>{name} Departures</h2>
<div className={style.heading}>
<h2>{name}</h2>
</div>
<div className={style.departureLists}>
{departureTables(left)}
{departureTables(right)}
</div>
{departureTables(left)}
{departureTables(right)}
</div>
</div>
);
}

View File

@@ -1,7 +1,12 @@
.container {
display: flex;
flex-direction: column;
align-items: center;
align-items: stretch;
border: 1px solid rgba(220, 220, 220, 0.4);
box-shadow: 5px 5px 7px rgba(220, 220, 220, 0.5);
border-radius: 10px;
padding: 0 10px 20px 10px;
margin-bottom: 20px;
}
.departureLists {
@@ -9,4 +14,9 @@
flex-direction: row;
justify-content: space-between;
width: 100%;
gap: 120px;
}
.heading {
text-align: left;
}

View File

@@ -3,6 +3,8 @@ import { useEffect } from "react";
import type { FlatasticChore } from "@/types/flatasticChore";
import style from "./style.module.css";
const idToNameMap: Record<number, string> = {
1836104: "Gruber",
1836101: "Darius",
@@ -25,11 +27,13 @@ export default function Flatastic() {
return (
<div>
<h1>Flatastic Chores</h1>
<ul>
<ul className={style.choreList}>
{chores.map((chore: FlatasticChore) => (
<li key={chore.id} style={{ textAlign: "left" }}>
{idToNameMap[chore.currentUser]}: {chore.title} -
Points: {chore.points}
<li key={chore.id} className={style.chore}>
<span className={style.userName}>
{idToNameMap[chore.currentUser]}
</span>
: {chore.title} - {"🪙".repeat(chore.points)}
</li>
))}
</ul>

View File

@@ -0,0 +1,20 @@
.choreList {
list-style-type: none;
display: flex;
flex-direction: row;
flex-wrap: wrap;
border-radius: 10px;
gap: 10px;
padding: 10px 0;
}
.chore {
padding: 5px 10px;
border: 1px solid rgba(220, 220, 220, 0.4);
box-shadow: 5px 5px 7px rgba(220, 220, 220, 0.5);
text-align: left;
}
.userName {
font-weight: bold;
}

View File

@@ -0,0 +1,17 @@
import Marquee from "react-fast-marquee";
import style from "./style.module.css";
export default function Footer() {
let pasta =
'Ok, so I was playing the hit game Among Us the other day, and when the game started, a red bean-shaped character that appeared to be wearing a spacesuit told me "shh," while having his index finger in front of where his mouth should be. I believe this act made this red bean character extremely suspicious. To understand why this red bean character is suspicious, we first must understand how the game “Among Us” works. The game consists of 10 bean-shaped characters, called crewmates, that are given tasks for them to complete. As these characters do their tasks, they may witness abnormal things that are not supposed to happen, such as the lights turning off on their own, sudden reactor meltdown and other crewmates dying. These acts show that there is an imposter among the crewmates that is sabotaging and is trying to kill everyone. Now why is this important to determine why the red bean is suspicious? Well now we know how the game works, now we must analyze the red beans actions. At the beginning of the game, the red bean tells us “shh” while having his index finger in front of where his mouth should be. This action suggests that the red bean wants us to be quiet, or keep our mouths shut. Now why would the red bean want us to do this? This could be because the red bean wants to limit our communication in order to prevent us from spreading information. What information does the red bean want to prevent from spreading? We can assume that the reason why the red bean wants to prevent us from spreading information, is because he is actually the imposter, and he is planning on committing the crimes mentioned earlier. He does not want others to find out about actions he will cause, therefore he does not want us to communicate with each other. This concludes the reason for why I believe the red bean from the hit game Among Us is suspicious. So if you happen to see a red bean-shaped character wearing a spacesuit, please be careful.';
return (
<div className={style.container}>
<div className={style.info}>BREAKING</div>
<div className={style.marquee}>
<Marquee>{pasta}</Marquee>
</div>
</div>
);
}

View File

@@ -0,0 +1,17 @@
.container {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.info {
background-color: red;
color: white;
font-weight: bold;
padding: 5px;
}
.marquee {
}

View File

@@ -1,9 +1,15 @@
import { useEffect } from "react";
import { useHomeAssistantStore } from "@/store/homeAssistant";
import style from "./style.module.css";
export default function Timetable() {
const fetchHomeAssistantData = useHomeAssistantStore((state) => state.fetch);
const tentTemperature = useHomeAssistantStore((state) => state.tentTemperature);
const fetchHomeAssistantData = useHomeAssistantStore(
(state) => state.fetch,
);
const tentTemperature = useHomeAssistantStore(
(state) => state.tentTemperature,
);
const tentHumidity = useHomeAssistantStore((state) => state.tentHumidity);
useEffect(() => {
@@ -17,8 +23,17 @@ export default function Timetable() {
return (
<div>
<h1>Tent</h1>
<p>Temperature: {tentTemperature}°C</p>
<p>Humidity: {tentHumidity}%</p>
<div className={style.cardContainer}>
<div className={style.card}>
<h4>Temperature</h4>
<p>{tentTemperature}°C</p>
</div>
<div className={style.card}>
<h4>Humidity</h4>
<p>{tentHumidity}%</p>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,18 @@
.cardContainer {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 20px;
}
.card {
height: 150px;
width: 150px;
display: flex;
flex-direction: column;
align-items: stretch;
border: 1px solid rgba(220, 220, 220, 0.4);
box-shadow: 5px 5px 7px rgba(220, 220, 220, 0.5);
border-radius: 10px;
}

View File

@@ -2,6 +2,8 @@ import { useEffect } from "react";
import DepartureList from "@/components/DepartureList/DepartureList";
import { useKVVStore } from "@/store/kvv";
import style from "./style.module.css";
export default function Timetable() {
const fetchTimetable = useKVVStore((state) => state.fetch);
const pStreet = useKVVStore((state) => state.pStreet);
@@ -16,8 +18,8 @@ export default function Timetable() {
}, [fetchTimetable]);
return (
<div>
<h1>Timetable</h1>
<div className={style.wrapper}>
<h1>Timetable 🚉</h1>
<DepartureList departures={pStreet.departureList} name="P-Street" />
<DepartureList departures={hStreet.departureList} name="H-Street" />
</div>

View File

@@ -10,12 +10,35 @@ export default function TimetableRow({
const hour = String(departure.dateTime.hour).padStart(2, "0");
const minute = String(departure.dateTime.minute).padStart(2, "0");
const dateTimeString = `${hour}:${minute}`;
const lineNumber = departure.servingLine.number;
return (
<tr className={styles.timetableRow}>
<td>{dateTimeString}</td>
<td>{departure.servingLine.number}</td>
<td>({departure.servingLine.direction})</td>
<td className={styles.departureTime}>{dateTimeString}</td>
<td
className={`${lineNumberToStyle(lineNumber)} ${styles.lineNumber}`}
>
{lineNumber}
</td>
<td>{departure.servingLine.direction}</td>
</tr>
);
}
const lineNumberToStyle = (number) => {
switch (number) {
case "S2":
return styles.S2;
break;
case "S5":
case "S51":
return styles.S5;
break;
case "S1":
case "S11":
return styles.S1;
break;
default:
return styles.lineNumberDefault;
}
};

View File

@@ -1,3 +1,31 @@
td {
padding: 4px 7px;
}
.timetableRow {
text-align: left;
}
.departureTime {
font-weight: 600;
}
.lineNumber {
text-align: center;
}
.S2 {
background-color: rgba(200, 100, 200, 0.2);
}
.S5 {
background-color: rgba(230, 20, 20, 0.2);
}
.S1 {
background-color: rgba(20, 180, 90, 0.2);
}
.lineNumberDefault {
background-color: rgba(100, 100, 100, 0.2);
}