diff --git a/skedul/.gitignore b/skedul/.gitignore
new file mode 100644
index 0000000..114ea57
--- /dev/null
+++ b/skedul/.gitignore
@@ -0,0 +1 @@
+data.json
\ No newline at end of file
diff --git a/skedul/README b/skedul/README
new file mode 100644
index 0000000..44e414f
--- /dev/null
+++ b/skedul/README
@@ -0,0 +1,36 @@
+very simple customized class schedules applet. to use it, create a data.json file in the project root and put all the files in the static directory of your webserver!
+
+features
+* shows first, next, and last classes for the day
+* shows all your classes for the week on a grid
+* configure links for class buildings (google maps or whatever)
+
+data.json is structured like so:
+{
+ "year": 2025,
+ "semester": "Semester", // spring, fall, whatever
+ "credits": 30, // your class credits this semester [purely cosmetic]
+ "maps": {
+ "building name": "link to the building in google maps or whatever"
+ },
+ "week": [
+ {
+ "days": [1, 3, 5], // 1 is monday, 2 is tuesday, etc. weekend courses currently aren't supported.
+ "start_time": 1100, // 24-hour timestamps.
+ "end_time": 1215,
+ "name": "Physics",
+ "type": "Lecture", // options are Lecture, Lab, Studio, Class, and Test.
+ "building": "The Bestest Building", // must be exactly the same as the building name in `maps` for the links to work
+ "room": "101" // your room in the building
+ },
+ {
+ "days": [2, 4],
+ "start_time": 1230,
+ "end_time": 1515,
+ "name": "Chemistry",
+ "type": "Lab",
+ "building": "Laboratorium!",
+ "room": "66"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/skedul/index.html b/skedul/index.html
new file mode 100644
index 0000000..3c7b3ea
--- /dev/null
+++ b/skedul/index.html
@@ -0,0 +1,43 @@
+
+
+
+ Tyler's Course Schedule
+
+
+
+
+
+
+
+
+ Monday
+ Tuesday
+ Wednesday
+ Thursday
+ Friday
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/skedul/main.css b/skedul/main.css
new file mode 100644
index 0000000..989e3d3
--- /dev/null
+++ b/skedul/main.css
@@ -0,0 +1,121 @@
+#topline {
+ padding: 10px;
+ background-color: rgb(0, 51, 68);
+ color: white;
+ font-family: monospace;
+ display: flex;
+}
+
+#topline > * {
+ margin-left: 20px;
+ margin-right: 20px;
+}
+
+#topline a {
+ color: white;
+}
+
+#firstclass {
+ margin-left: auto;
+}
+
+#schedule>tbody td:first-child {
+ font-size: 0.7em;
+ color: transparent;
+ position: relative;
+ padding-right: 20px;
+}
+
+td:first-child>span {
+ position: absolute;
+ top: -0.7em;
+ left: 0px;
+ background-color: white;
+ width: auto;
+ color: black;
+}
+
+thead td {
+ font-weight: bold;
+ text-align: center;
+}
+
+td:not(:first-child) {
+ width: 20%;
+}
+
+table {
+ border-collapse: collapse;
+ width: auto;
+}
+
+tr {
+ border-top: 1px solid rgb(167, 167, 167);
+ position: relative;
+ height: 1.1em;
+}
+
+.coursename {
+ font-weight: bold;
+ display: block;
+ text-align: center;
+ font-size: 1.1em;
+}
+
+.coursetype {
+ font-weight: bold;
+}
+
+td:not(:first-child) {
+ vertical-align: top;
+ font-family: monospace;
+ padding: 4px;
+ position: relative;
+}
+
+td.Lecture {
+ background-color: rgb(255, 207, 255);
+}
+
+td.Lab {
+ background-color: lightblue;
+}
+
+td.Class {
+ background-color: yellow;
+}
+
+td.Studio {
+ background-color: lightgreen;
+}
+
+td.Test {
+ background-color: red;
+ box-shadow: 0px 0px 20px 10px rgb(88, 0, 0);
+}
+
+.youarehere {
+ color: white;
+ background-color: black;
+ text-align: center;
+}
+
+.youarehere::after{
+ font-weight: bold;
+ content: 'you are HERE!';
+}
+
+@media (max-width:1000px) {
+ td {
+ font-size: 0.5em !important;
+ }
+ #topline {
+ flex-direction: column;
+ }
+ #topline > * {
+ margin: 20px !important;
+ }
+ tr {
+ height: 0.7em;
+ }
+}
\ No newline at end of file
diff --git a/skedul/main.js b/skedul/main.js
new file mode 100644
index 0000000..ea40fcd
--- /dev/null
+++ b/skedul/main.js
@@ -0,0 +1,104 @@
+function tohours(mns) { // convert a value in minutes to a 24-hour timestamp, e.g. 330 -> 0530
+ let hour = Math.floor(mns / 60);
+ let minute = mns % 60;
+ return hour * 100 + minute;
+}
+
+function tomins(hrs) { // convert a value in 24-hour to minutes, the inverse of tohours
+ let hour = Math.floor(hrs / 100);
+ let minute = hrs % 100;
+ return hour * 60 + minute;
+}
+
+const daystart = tomins(1000);
+
+function zpad(thing, zeroes) { // left-pad a string with zeroes
+ thing = "" + thing;
+ while (thing.length < zeroes) {
+ thing = "0" + thing;
+ }
+ return thing;
+}
+
+function tonicehrs(hrs) { // convert a value in 24-hour to a string (800 -> "08:00", 1645 -> "16:45")
+ let hour = Math.floor(hrs / 100);
+ if (hour > 12) {
+ hour -= 12;
+ }
+ let minute = hrs % 100;
+ return zpad(hour, 2) + ":" + zpad(minute, 2);
+}
+
+function tablesetup() {
+ for (var i = 0; i < 11 * 4; i ++) {
+ let row = document.createElement("tr");
+ let time = tonicehrs(tohours(daystart + i * 15))
+ row.innerHTML = `${time}${time} `;
+ document.querySelector("#schedule > tbody").appendChild(row);
+ }
+}
+
+
+function setClassTo(el, c, map) {
+ el.querySelector(".cname").innerText = c.name;
+ el.querySelector(".ctype").innerText = c.type;
+ el.querySelector(".bloc").href = map[c.building];
+ el.querySelector(".building").innerText = c.building;
+ el.querySelector(".room").innerText = c.room;
+ el.querySelector(".start").innerText = tonicehrs(c.start_time);
+ el.querySelector(".end").innerText = tonicehrs(c.end_time);
+}
+
+
+window.addEventListener("load", async () => {
+ let data = await (await fetch("data.json")).json();
+ document.getElementById("year").innerHTML = data.year;
+ document.getElementById("semester").innerHTML = data.semester;
+ document.getElementById("credits").innerHTML = data.credits;
+ tablesetup();
+ let tbody = document.querySelector("#schedule > tbody")
+ let curday = new Date().getDay();
+ let firstClass = undefined;
+ let nextClass = undefined;
+ let lastClass = undefined;
+ let now = new Date();
+ let curTime = now.getMinutes() + now.getHours() * 100;
+ let curTimeSnap = Math.round((tomins(curTime) - daystart) / 15);
+ tbody.children[curTimeSnap].children[curday].classList.add("youarehere");
+ for (let cls of data.week) {
+ for (let day of cls.days) {
+ if (day == curday) {
+ if (firstClass == undefined || cls.start_time < firstClass.start_time) {
+ firstClass = cls;
+ }
+ if (lastClass == undefined || cls.end_time > lastClass.end_time) {
+ lastClass = cls;
+ }
+ if (cls.start_time > curTime) {
+ if (nextClass == undefined || cls.start_time < nextClass.start_time) {
+ nextClass = cls;
+ }
+ }
+ }
+ let start = Math.floor((tomins(cls.start_time) - daystart) / 15);
+ let el = tbody.children[start].children[day];
+ el.classList.add("class");
+ let rows = Math.floor((tomins(cls.end_time) - tomins(cls.start_time)) / 15);
+ el.rowSpan = rows;
+ for (var i = start + 1; i < start + rows; i ++) { // cull the tds that this td is overlapping
+ tbody.children[i].removeChild(tbody.children[i].children[day]);
+ }
+ el.classList.add(cls.type);
+ el.innerHTML = `${cls.name} ${cls.type} at ${cls.building} ${cls.room}`
+ }
+ }
+ if (firstClass) {
+ setClassTo(document.getElementById("firstclass"), firstClass, data.maps);
+ }
+ if (nextClass) {
+ setClassTo(document.getElementById("nextclass"), nextClass, data.maps);
+ }
+ if (lastClass) {
+ setClassTo(document.getElementById("lastclass"), lastClass, data.maps);
+ }
+});
\ No newline at end of file