diff --git a/README.md b/readme.md
similarity index 81%
rename from README.md
rename to readme.md
index 2f6215b..e1c6661 100644
--- a/README.md
+++ b/readme.md
@@ -16,7 +16,6 @@ npm start
In src/manifest.json, "#31d53d" refers to the green color which is visible in the background in the web version. This is not visible in the built version.
### Running iOS
-
https://capacitorjs.com/docs/ios#adding-the-ios-platform
npm install @capacitor/ios
@@ -24,4 +23,7 @@ npx cap add ios
npx cap open ios
To Rerun:
-npm run build && npx cap copy ios
\ No newline at end of file
+npm run build && npx cap copy ios
+
+### Note
+You need to be in mobile mode in order for the app to work, in the top right corner of dev tools.
diff --git a/src/Home.js b/src/Home.js
new file mode 100644
index 0000000..d1cade4
--- /dev/null
+++ b/src/Home.js
@@ -0,0 +1,39 @@
+import "./components/Sidebar.js"
+import "./components/AppMenu.js"
+import "./apps/Forum/Forum.js"
+import "./apps/Messages/Messages.js"
+import "./apps/Jobs/Jobs.js"
+
+class Home extends Shadow {
+
+ render() {
+ ZStack(() => {
+ Sidebar()
+
+ ZStack(() => {
+ switch(window.location.pathname) {
+ case "/":
+ Forum()
+ break;
+
+ case "/messages":
+ Messages()
+ break;
+
+ case "/jobs":
+ Jobs()
+ break;
+ }
+ })
+ .onNavigate(function () {
+ console.log("navigate")
+ this.rerender()
+ })
+
+ AppMenu()
+ })
+ .overflowX("hidden")
+ }
+}
+
+register(Home)
\ No newline at end of file
diff --git a/src/_/code/quill.js b/src/_/code/quill.js
index 19242eb..e144543 100644
--- a/src/_/code/quill.js
+++ b/src/_/code/quill.js
@@ -1,6 +1,7 @@
/*
Sam Russell
Captured Sun
+ 1.16.26 - Moving nav event dispatch out of pushState, adding null feature to attr()
1.5.26 - Switching verticalAlign and horizontalAlign names, adding borderVertical and Horizontal
12.26.25 - State for arrays, nested objects. State for stacks (Shadow-only)
12.17.25 - [Hyperia] - adding width, height functions. adding "e" to onClick. adding the non-window $$ funcs.
@@ -27,7 +28,6 @@ let oldPushState = history.pushState;
history.pushState = function pushState() {
let ret = oldPushState.apply(this, arguments);
window.dispatchEvent(new Event('pushstate'));
- window.dispatchEvent(new Event('navigate'));
return ret;
};
@@ -51,10 +51,15 @@ window.setQuery = function(key, value) {
return newUrl;
};
-
+
window.navigateTo = function(url) {
- window.dispatchEvent(new Event('navigate'));
window.history.pushState({}, '', url);
+ window.dispatchEvent(new Event('navigate'));
+}
+
+window.setLocation = function(url) {
+ window.dispatchEvent(new Event('navigate'));
+ window.history.replaceState({}, '', url);
}
/* $ SELECTOR */
@@ -91,8 +96,6 @@ console.green = function(message) {
}
/* GET CSS VARIABLES FOR DARK OR LIGHT MODE */
-window.darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
-document.documentElement.classList.add(darkMode ? 'dark' : 'light');
window.getColor = function(name) {
const rootStyles = getComputedStyle(document.documentElement);
@@ -815,6 +818,20 @@ window.input = function(placeholder = "", width, height) {
return el
}
+window.select = function(cb) {
+ let el = document.createElement("select")
+ el.render = cb
+ quill.render(el)
+ return el
+}
+
+window.option = function(placeholder = "") {
+ let el = document.createElement("option")
+ el.innerText = placeholder
+ quill.render(el)
+ return el
+}
+
window.label = function(inside) {
let el = document.createElement("label")
if(typeof inside === "string") {
@@ -1034,7 +1051,7 @@ HTMLElement.prototype.onInput = function(cb) {
};
HTMLElement.prototype.onChange = function(cb) {
- if(!this.matches('input, textarea, [contenteditable=""], [contenteditable="true"]'))
+ if(!this.matches('input, textarea, select, [contenteditable=""], [contenteditable="true"]'))
throw new Error("Can't put input event on non-input element!")
this._storeListener("change", cb);
return this;
@@ -1067,7 +1084,7 @@ HTMLElement.prototype.onTap = function(cb) {
- We can't just put a listener on the element, because a window "navigate" event won't trigger it
- We can't just put a listener on the window, because the "this" variable will only refer to the window
- And, if we try to re-add that scope using bind(), it makes the return value of .toString() unreadable, which means we cannot detect duplicate listeners.
-- Therefore, we attach a global navigate event to the window, and each navigate event in this array, and manually trigger each event when the global one fires.
+- Therefore, we attach a global navigate event to the window, and store each navigate event in this navigateListeners array, and manually trigger each event on the elements when the global one fires.
*/
navigateListeners = []
window.addEventListener("navigate", () => {
@@ -1180,7 +1197,11 @@ HTMLElement.prototype.attr = function(attributes) {
}
for (const [key, value] of Object.entries(attributes)) {
- this.setAttribute(key, value);
+ if(value === null) {
+ this.removeAttribute(key)
+ } else {
+ this.setAttribute(key, value);
+ }
}
return this;
};
diff --git a/src/_/code/styles.css b/src/_/code/styles.css
index 2f0b2fe..7ca742d 100644
--- a/src/_/code/styles.css
+++ b/src/_/code/styles.css
@@ -1,5 +1,5 @@
:root {
- --main: #AEBDFF;
+ --main: #FFE9C8;
--accent: #60320c;
--text: #340000;
--yellow: #f1f3c3;
@@ -9,4 +9,17 @@
:root {
}
+}
+
+html,
+body {
+ padding: 0;
+ margin: 0;
+}
+
+body {
+ padding-top: env(safe-area-inset-top);
+ padding-bottom: env(safe-area-inset-bottom);
+ padding-left: env(safe-area-inset-left);
+ padding-right: env(safe-area-inset-right);
}
\ No newline at end of file
diff --git a/src/_/icons/column.svg b/src/_/icons/column.svg
new file mode 100644
index 0000000..841bf5f
--- /dev/null
+++ b/src/_/icons/column.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/_/icons/column2.svg b/src/_/icons/column2.svg
new file mode 100644
index 0000000..ab79e11
--- /dev/null
+++ b/src/_/icons/column2.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/_/icons/columnwhite.svg b/src/_/icons/columnwhite.svg
new file mode 100644
index 0000000..5be3f63
--- /dev/null
+++ b/src/_/icons/columnwhite.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/_/icons/forum.png b/src/_/icons/forum.png
new file mode 100644
index 0000000..4b0fa1b
Binary files /dev/null and b/src/_/icons/forum.png differ
diff --git a/src/_/icons/jobs.svg b/src/_/icons/jobs.svg
new file mode 100644
index 0000000..f4a811f
--- /dev/null
+++ b/src/_/icons/jobs.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/src/_/icons/letter.svg b/src/_/icons/letter.svg
new file mode 100644
index 0000000..9a1d4d8
--- /dev/null
+++ b/src/_/icons/letter.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/src/apps/Forum/Forum.js b/src/apps/Forum/Forum.js
new file mode 100644
index 0000000..a507efc
--- /dev/null
+++ b/src/apps/Forum/Forum.js
@@ -0,0 +1,66 @@
+import './ForumPanel.js'
+
+css(`
+ forum- {
+ font-family: 'Bona';
+ }
+
+ forum- input::placeholder {
+ font-family: 'Bona Nova';
+ font-size: 0.9em;
+ color: var(--accent);
+ }
+
+ input[type="checkbox"] {
+ appearance: none; /* remove default style */
+ -webkit-appearance: none;
+ width: 1em;
+ height: 1em;
+ border: 1px solid var(--accent);
+ }
+
+ input[type="checkbox"]:checked {
+ background-color: var(--red);
+ }
+`)
+
+class Forum extends Shadow {
+
+ selectedForum = "HY"
+
+ render() {
+ ZStack(() => {
+ VStack(() => {
+
+ ForumPanel()
+
+ input("Message Hyperia", "98%")
+ .paddingVertical(1, em)
+ .paddingLeft(2, pct)
+ .color("var(--accent)")
+ .background("var(--darkbrown)")
+ .marginBottom(6, em)
+ .border("none")
+ .fontSize(1, em)
+ .onKeyDown(function (e) {
+ if (e.key === "Enter") {
+ window.Socket.send({app: "FORUM", operation: "SEND", msg: {forum: "HY", text: this.value }})
+ this.value = ""
+ }
+ })
+ })
+ .gap(0.5, em)
+ .width(100, pct)
+ .height(100, vh)
+ .horizontalAlign("center")
+ .verticalAlign("end")
+ })
+ .onAppear(() => document.body.style.backgroundColor = "var(--darkbrown)")
+ .width(100, pct)
+ .height(100, pct)
+ }
+
+
+}
+
+register(Forum)
\ No newline at end of file
diff --git a/src/apps/Forum/ForumPanel.js b/src/apps/Forum/ForumPanel.js
new file mode 100644
index 0000000..9dfdde6
--- /dev/null
+++ b/src/apps/Forum/ForumPanel.js
@@ -0,0 +1,88 @@
+import "../../components/LoadingCircle.js"
+
+class ForumPanel extends Shadow {
+ forums = [
+ "HY"
+ ]
+ messages = []
+
+ render() {
+ VStack(() => {
+ if(this.messages.length > 0) {
+
+ let previousDate = null
+
+ for(let i=0; i {
+ HStack(() => {
+ p(message.sentBy)
+ .fontWeight("bold")
+ .marginBottom(0.3, em)
+
+ p(util.formatTime(message.time))
+ .opacity(0.2)
+ .marginLeft(1, em)
+ })
+ p(message.text)
+ })
+ }
+ } else {
+ LoadingCircle()
+ }
+ })
+ .gap(1, em)
+ .position("relative")
+ .overflow("scroll")
+ .height(100, pct)
+ .width(96, pct)
+ .paddingTop(5, em)
+ .paddingBottom(2, em)
+ .paddingLeft(4, pct)
+ .backgroundColor("var(--darkbrown)")
+ .onAppear(async () => {
+ requestAnimationFrame(() => {
+ this.scrollTop = this.scrollHeight
+ });
+ let res = await Socket.send({app: "FORUM", operation: "GET", msg: {forum: "HY", number: 100}})
+ if(!res) console.error("failed to get messages")
+ if(res.msg.length > 0 && this.messages.length === 0) {
+ this.messages = res.msg
+ this.rerender()
+ }
+ window.addEventListener("new-post", (e) => {
+ this.messages = e.detail
+ if(e.detail.length !== this.messages || e.detail.last.time !== this.messages.last.time || e.detail.first.time !== this.messages.first.time) {
+ this.rerender()
+ }
+ })
+ })
+ }
+
+ parseDate(str) {
+ // Format: MM.DD.YYYY-HH:MM:SSxxxxxx(am|pm)
+ const match = str.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})-(\d{1,2}):(\d{2}).*(am|pm)$/i);
+ if (!match) return null;
+
+ const [, mm, dd, yyyy, hh, min, ampm] = match;
+ const date = `${mm}/${dd}/${yyyy}`;
+ const time = `${hh}:${min}${ampm.toLowerCase()}`;
+
+ return { date, time };
+ }
+}
+
+register(ForumPanel)
\ No newline at end of file
diff --git a/src/apps/Jobs/Jobs.js b/src/apps/Jobs/Jobs.js
new file mode 100644
index 0000000..bb72598
--- /dev/null
+++ b/src/apps/Jobs/Jobs.js
@@ -0,0 +1,101 @@
+import "./JobsSidebar.js"
+import "./JobsGrid.js"
+
+css(`
+ jobs- {
+ font-family: 'Bona';
+ }
+
+ jobs- input::placeholder {
+ font-family: 'Bona Nova';
+ font-size: 0.9em;
+ color: var(--accent);
+ }
+
+ input[type="checkbox"] {
+ appearance: none; /* remove default style */
+ -webkit-appearance: none;
+ width: 1em;
+ height: 1em;
+ border: 1px solid var(--accent);
+ }
+
+ input[type="checkbox"]:checked {
+ background-color: var(--red);
+ }
+`)
+
+class Jobs extends Shadow {
+ jobs = [
+ {
+ title: "Austin Chapter Lead",
+ salary: "1% of Local Revenue",
+ company: "Hyperia",
+ city: "Austin",
+ state: "TX"
+ }
+ ]
+
+ render() {
+ ZStack(() => {
+ HStack(() => {
+ JobsSidebar()
+
+ JobsGrid(this.jobs)
+ })
+ .width(100, "%")
+ .x(0).y(13, vh)
+
+ HStack(() => {
+ input("Search jobs... (Coming Soon!)", "45vw")
+ .attr({
+ "type": "text",
+ "disabled": "true"
+ })
+ .fontSize(1.1, em)
+ .paddingLeft(1.3, em)
+ .background("transparent")
+ .border("0.5px solid var(--divider)")
+ .outline("none")
+ .color("var(--accent)")
+ .opacity(0.5)
+ .borderRadius(10, px)
+ .background("grey")
+ .cursor("not-allowed")
+
+ button("+ Add Job")
+ .width(7, em)
+ .marginLeft(1, em)
+ .borderRadius(10, px)
+ .background("transparent")
+ .border("0.3px solid var(--accent2)")
+ .color("var(--accent)")
+ .fontFamily("Bona Nova")
+ .onHover(function (hovering) {
+ if(hovering) {
+ this.style.background = "var(--green)"
+
+ } else {
+ this.style.background = "transparent"
+
+ }
+ })
+ .onClick((clicking) => {
+ console.log(this, "clicked")
+ })
+
+ })
+ .x(55, vw).y(4, vh)
+ .position("absolute")
+ .transform("translateX(-50%)")
+ })
+ .width(100, "%")
+ .height(100, "%")
+ }
+
+ connectedCallback() {
+ // Optional additional logic
+ }
+}
+
+register(Jobs)
\ No newline at end of file
diff --git a/src/apps/Jobs/JobsGrid.js b/src/apps/Jobs/JobsGrid.js
new file mode 100644
index 0000000..2af5d4f
--- /dev/null
+++ b/src/apps/Jobs/JobsGrid.js
@@ -0,0 +1,60 @@
+class JobsGrid extends Shadow {
+ jobs;
+
+ constructor(jobs) {
+ super()
+ this.jobs = jobs
+ }
+
+ boldUntilFirstSpace(text) {
+ const index = text.indexOf(' ');
+ if (index === -1) {
+ // No spaces — bold the whole thing
+ return `${text}`;
+ }
+ return `${text.slice(0, index)}${text.slice(index)}`;
+ }
+
+ render() {
+ VStack(() => {
+ h3("Results")
+ .marginTop(0.1, em)
+ .marginBottom(1, em)
+ .marginLeft(0.4, em)
+ .color("var(--accent2)")
+
+ if (this.jobs.length > 0) {
+ ZStack(() => {
+ for (let i = 0; i < this.jobs.length; i++) {
+ VStack(() => {
+ p(this.jobs[i].title)
+ .fontSize(1.2, em)
+ .fontWeight("bold")
+ .marginBottom(0.5, em)
+ p(this.jobs[i].company)
+ p(this.jobs[i].city + ", " + this.jobs[i].state)
+ .marginBottom(0.5, em)
+ p(this.boldUntilFirstSpace(this.jobs[i].salary))
+ })
+ .padding(1, em)
+ .borderRadius(5, "px")
+ .background("var(--darkbrown)")
+ }
+ })
+ .display("grid")
+ .gridTemplateColumns("repeat(auto-fill, minmax(250px, 1fr))")
+ .gap(1, em)
+ } else {
+ p("No Jobs!")
+ }
+ })
+ .height(100, vh)
+ .paddingLeft(2, em)
+ .paddingRight(2, em)
+ .paddingTop(2, em)
+ .gap(0, em)
+ .width(100, "%")
+ }
+}
+
+register(JobsGrid)
diff --git a/src/apps/Jobs/JobsSidebar.js b/src/apps/Jobs/JobsSidebar.js
new file mode 100644
index 0000000..1546cec
--- /dev/null
+++ b/src/apps/Jobs/JobsSidebar.js
@@ -0,0 +1,26 @@
+class JobsSidebar extends Shadow {
+ render() {
+ VStack(() => {
+ h3("Location")
+ .color("var(--accent2)")
+ .marginBottom(0, em)
+
+ HStack(() => {
+ input("Location", "100%")
+ .paddingLeft(3, em)
+ .paddingVertical(0.75, em)
+ .backgroundImage("/_/icons/locationPin.svg")
+ .backgroundRepeat("no-repeat")
+ .backgroundSize("18px 18px")
+ .backgroundPosition("10px center")
+ })
+ })
+ .paddingTop(1, em)
+ .paddingLeft(3, em)
+ .paddingRight(3, em)
+ .gap(1, em)
+ .minWidth(10, vw)
+ }
+}
+
+register(JobsSidebar)
\ No newline at end of file
diff --git a/src/apps/Market/Market.js b/src/apps/Market/Market.js
new file mode 100644
index 0000000..e3bceeb
--- /dev/null
+++ b/src/apps/Market/Market.js
@@ -0,0 +1,105 @@
+import "./MarketSidebar.js"
+import "./MarketGrid.js"
+
+css(`
+ market- {
+ font-family: 'Bona';
+ }
+
+ market- input::placeholder {
+ font-family: 'Bona Nova';
+ font-size: 0.9em;
+ color: var(--accent);
+ }
+
+ input[type="checkbox"] {
+ appearance: none; /* remove default style */
+ -webkit-appearance: none;
+ width: 1em;
+ height: 1em;
+ border: 1px solid var(--accent);
+ }
+
+ input[type="checkbox"]:checked {
+ background-color: var(--red);
+ }
+`)
+
+class Market extends Shadow {
+
+ listings = [
+ {
+ title: "Shield Lapel Pin",
+ stars: "5",
+ reviews: 1,
+ price: "$12",
+ company: "Hyperia",
+ type: "new",
+ image: "/db/images/1",
+ madeIn: "America"
+ }
+ ]
+
+ render() {
+ ZStack(() => {
+ HStack(() => {
+ MarketSidebar()
+
+ MarketGrid(this.listings)
+ })
+ .width(100, "%")
+ .x(0).y(13, vh)
+
+ HStack(() => {
+ input("Search for products... (Coming Soon!)", "45vw")
+ .attr({
+ "type": "text",
+ "disabled": "true"
+ })
+ .fontSize(1.1, em)
+ .paddingLeft(1.3, em)
+ .background("transparent")
+ .border("0.5px solid var(--divider)")
+ .outline("none")
+ .color("var(--accent)")
+ .opacity(0.5)
+ .borderRadius(10, px)
+ .background("grey")
+ .cursor("not-allowed")
+
+ button("+ Add Item")
+ .width(7, em)
+ .marginLeft(1, em)
+ .borderRadius(10, px)
+ .background("transparent")
+ .border("0.5px solid var(--accent2)")
+ .color("var(--accent)")
+ .fontFamily("Bona Nova")
+ .onHover(function (hovering) {
+ if(hovering) {
+ this.style.background = "var(--green)"
+
+ } else {
+ this.style.background = "transparent"
+
+ }
+ })
+ .onClick((clicking) => {
+ console.log(this, "clicked")
+ })
+
+ })
+ .x(55, vw).y(4, vh)
+ .position("absolute")
+ .transform("translateX(-50%)")
+ })
+ .width(100, "%")
+ .height(100, "%")
+ }
+
+ connectedCallback() {
+ // Optional additional logic
+ }
+}
+
+register(Market)
diff --git a/src/apps/Market/MarketGrid.js b/src/apps/Market/MarketGrid.js
new file mode 100644
index 0000000..8740f9e
--- /dev/null
+++ b/src/apps/Market/MarketGrid.js
@@ -0,0 +1,140 @@
+class MarketGrid extends Shadow {
+ listings;
+
+ constructor(listings) {
+ super()
+ this.listings = listings
+ }
+
+ boldUntilFirstSpace(text) {
+ if(!text) return
+ const index = text.indexOf(' ');
+ if (index === -1) {
+ // No spaces — bold the whole thing
+ return `${text}`;
+ }
+ return `${text.slice(0, index)}${text.slice(index)}`;
+ }
+
+ render() {
+ VStack(() => {
+ h3("Results")
+ .marginTop(0.1, em)
+ .marginBottom(1, em)
+ .marginLeft(0.4, em)
+ .color("var(--accent)")
+ .opacity(0.7)
+
+ if (this.listings.length > 0) {
+ ZStack(() => {
+ // BuyModal()
+
+ let params = new URLSearchParams(window.location.search);
+
+ const hyperiaMade = params.get("hyperia-made") === "true";
+ const americaMade = params.get("america-made") === "true";
+ const newItem = params.get("new") === "true";
+ const usedItem = params.get("used") === "true";
+
+
+ let filtered = this.listings;
+ if (hyperiaMade) {
+ filtered = filtered.filter(item => item.madeIn === "Hyperia");
+ }
+ if (americaMade) {
+ filtered = filtered.filter(item => item.madeIn === "America");
+ }
+ if (newItem) {
+ filtered = filtered.filter(item => item.type === "new");
+ }
+ if (usedItem) {
+ filtered = filtered.filter(item => item.type === "used");
+ }
+
+ for (let i = 0; i < filtered.length; i++) {
+ const rating = filtered[i].stars
+ const percent = (rating / 5)
+
+ VStack(() => {
+ img(filtered[i].image)
+ .marginBottom(0.5, em)
+
+ p(filtered[i].company)
+ .marginBottom(0.5, em)
+
+ p(filtered[i].title)
+ .fontSize(1.2, em)
+ .fontWeight("bold")
+ .marginBottom(0.5, em)
+
+ HStack(() => {
+ p(filtered[i].stars)
+ .marginRight(0.2, em)
+
+ ZStack(() => {
+ div("★★★★★") // Empty stars (background)
+ .color("#ccc")
+
+ div("★★★★★") // Filled stars (foreground, clipped by width)
+ .color("#ffa500")
+ .position("absolute")
+ .top(0)
+ .left(0)
+ .whiteSpace("nowrap")
+ .overflow("hidden")
+ .width(percent * 5, em)
+ })
+ .display("inline-block")
+ .position("relative")
+ .fontSize(1.2, em)
+ .lineHeight(1)
+
+ p(filtered[i].reviews)
+ .marginLeft(0.2, em)
+ })
+ .marginBottom(0.5, em)
+
+ p(filtered[i].price)
+ .fontSize(1.75, em)
+ .marginBottom(0.5, em)
+
+ button("Coming Soon!")
+ .onClick((finished) => {
+ if(finished) {
+
+ }
+ })
+ .onHover(function (hovering) {
+ if(hovering) {
+ this.style.backgroundColor = "var(--green)"
+ } else {
+ this.style.backgroundColor = ""
+ }
+ })
+
+ })
+ .padding(1, em)
+ .border("1px solid var(--accent2)")
+ .borderRadius(5, "px")
+ }
+ })
+ .display("grid")
+ .gridTemplateColumns("repeat(auto-fill, minmax(250px, 1fr))")
+ .gap(1, em)
+ } else {
+ p("No Listings!")
+ }
+ })
+ .onQueryChanged(() => {
+ console.log("query did change yup")
+ this.rerender()
+ })
+ .height(100, vh)
+ .paddingLeft(2, em)
+ .paddingRight(2, em)
+ .gap(0, em)
+ .width(100, "%")
+ }
+}
+
+register(MarketGrid)
\ No newline at end of file
diff --git a/src/apps/Market/MarketSidebar.js b/src/apps/Market/MarketSidebar.js
new file mode 100644
index 0000000..9324618
--- /dev/null
+++ b/src/apps/Market/MarketSidebar.js
@@ -0,0 +1,85 @@
+class MarketSidebar extends Shadow {
+
+ handleChecked(e) {
+ let checked = e.target.checked
+ let label = $(`label[for="${e.target.id}"]`).innerText
+ if(checked) {
+ window.setQuery(label.toLowerCase(), true)
+ } else {
+ window.setQuery(label.toLowerCase(), null)
+ }
+ }
+
+ render() {
+ VStack(() => {
+
+ p("Make")
+
+ HStack(() => {
+ input()
+ .attr({
+ "type": "checkbox",
+ "id": "hyperia-check"
+ })
+ .onChange(this.handleChecked)
+ label("Hyperia-Made")
+ .attr({
+ "for": "hyperia-check"
+ })
+ .marginLeft(0.5, em)
+ })
+
+ HStack(() => {
+ input()
+ .attr({
+ "type": "checkbox",
+ "id": "america-check"
+ })
+ .onChange(this.handleChecked)
+ label("America-Made")
+ .attr({
+ "for": "america-check"
+ })
+ .marginLeft(0.5, em)
+ })
+
+ p("Condition")
+
+ HStack(() => {
+ input()
+ .attr({
+ "type": "checkbox",
+ "id": "new-check"
+ })
+ .onChange(this.handleChecked)
+ label("New")
+ .attr({
+ "for": "new-check"
+ })
+ .marginLeft(0.5, em)
+ })
+
+ HStack(() => {
+ input()
+ .attr({
+ "type": "checkbox",
+ "id": "used-check"
+ })
+ .onChange(this.handleChecked)
+ label("Used")
+ .attr({
+ "for": "used-check"
+ })
+ .marginLeft(0.5, em)
+ })
+ })
+ .paddingTop(12, vh)
+ .paddingLeft(3, em)
+ .paddingRight(3, em)
+ .gap(1, em)
+ .minWidth(10, vw)
+ .userSelect('none')
+ }
+}
+
+register(MarketSidebar)
\ No newline at end of file
diff --git a/src/apps/Messages/Messages.js b/src/apps/Messages/Messages.js
new file mode 100644
index 0000000..40b9722
--- /dev/null
+++ b/src/apps/Messages/Messages.js
@@ -0,0 +1,188 @@
+import "./MessagesSidebar.js"
+import "./MessagesPanel.js"
+
+css(`
+ messages- {
+ font-family: 'Bona';
+ }
+
+ messages- input::placeholder {
+ font-family: 'Bona Nova';
+ font-size: 0.9em;
+ color: var(--accent);
+ }
+
+ input[type="checkbox"] {
+ appearance: none; /* remove default style */
+ -webkit-appearance: none;
+ width: 1em;
+ height: 1em;
+ border: 1px solid var(--accent);
+ }
+
+ input[type="checkbox"]:checked {
+ background-color: var(--red);
+ }
+`)
+
+class Messages extends Shadow {
+ conversations = []
+ selectedConvoID = null
+ onConversationSelect(i) {
+ console.log("convo selected: ", i)
+ this.selectedConvoID = i
+ this.$("messagessidebar-").rerender()
+ this.$("messagespanel-").rerender()
+ }
+
+ getConvoFromID(id) {
+ for(let i=0; i {
+ HStack(() => {
+ MessagesSidebar(this.conversations, this.selectedConvoID, this.onConversationSelect)
+
+ VStack(() => {
+ if(this.getConvoFromID(this.selectedConvoID)) {
+ MessagesPanel(this.getConvoFromID(this.selectedConvoID).messages)
+ } else {
+ MessagesPanel()
+ }
+
+ input("Send Message", "93%")
+ .paddingVertical(1, em)
+ .paddingHorizontal(2, em)
+ .color("var(--accent)")
+ .background("var(--darkbrown)")
+ .marginBottom(6, em)
+ .border("none")
+ .fontSize(1, em)
+ .onKeyDown((e) => {
+ if (e.key === "Enter") {
+ window.Socket.send({app: "MESSAGES", operation: "SEND", msg: { conversation: `CONVERSATION-${this.selectedConvoID}`, text: e.target.value }})
+ e.target.value = ""
+ }
+ })
+ })
+ .gap(1, em)
+ .width(100, pct)
+ .horizontalAlign("center")
+ .verticalAlign("end")
+ })
+ .onAppear(async () => {
+ let res = await Socket.send({app: "MESSAGES", operation: "GET"})
+ if(!res) console.error("failed to get messages")
+
+ if(res.msg.length > 0 && this.conversations.length === 0) {
+ this.conversations = res.msg
+ this.selectedConvoID = this.conversations[0].id
+ this.rerender()
+ }
+
+ window.addEventListener("new-message", (e) => {
+ let convoID = e.detail.conversationID
+ let messages = e.detail.messages
+ let convo = this.getConvoFromID(convoID)
+ convo.messages = messages
+ this.rerender()
+ })
+ })
+ .width(100, "%")
+ .height(87, vh)
+ .x(0).y(13, vh)
+
+ VStack(() => {
+ p("Add Message")
+
+ input("enter email...")
+ .color("var(--accent)")
+ .onKeyDown(function (e) {
+ if (e.key === "Enter") {
+ window.Socket.send({app: "MESSAGES", operation: "ADDCONVERSATION", msg: {email: this.value }})
+ this.value = ""
+ }
+ })
+
+ p("x")
+ .onClick(function (done) {
+ if(done) {
+ this.parentElement.style.display = "none"
+ }
+ })
+ .xRight(2, em).y(2, em)
+ .fontSize(1.4, em)
+ .cursor("pointer")
+
+ })
+ .gap(1, em)
+ .alignVertical("center")
+ .alignHorizontal("center")
+ .backgroundColor("black")
+ .border("1px solid var(--accent)")
+ .position("fixed")
+ .x(50, vw).y(50, vh)
+ .center()
+ .width(60, vw)
+ .height(60, vh)
+ .display("none")
+ .attr({id: "addPanel"})
+
+ HStack(() => {
+ input("Search messages... (Coming Soon!)", "45vw")
+ .attr({
+ "type": "text",
+ "disabled": "true"
+ })
+ .fontSize(1.1, em)
+ .paddingLeft(1.3, em)
+ .background("transparent")
+ .border("0.5px solid var(--divider)")
+ .outline("none")
+ .color("var(--accent)")
+ .opacity(0.5)
+ .borderRadius(10, px)
+ .background("grey")
+ .cursor("not-allowed")
+
+ button("+ New Message")
+ .width(13, em)
+ .marginLeft(1, em)
+ .borderRadius(10, px)
+ .background("transparent")
+ .border("0.5px solid var(--divider)")
+ .color("var(--accent)")
+ .fontFamily("Bona Nova")
+ .onHover(function (hovering) {
+ if(hovering) {
+ this.style.background = "var(--green)"
+
+ } else {
+ this.style.background = "transparent"
+
+ }
+ })
+ .onClick((done) => {
+ console.log("click")
+ if(done) {
+ this.$("#addPanel").style.display = "flex"
+ }
+ console.log(this, "clicked")
+ })
+
+ })
+ .x(55, vw).y(4, vh)
+ .position("absolute")
+ .transform("translateX(-50%)")
+ })
+ .width(100, "%")
+ .height(100, "%")
+ }
+}
+
+register(Messages)
\ No newline at end of file
diff --git a/src/apps/Messages/MessagesPanel.js b/src/apps/Messages/MessagesPanel.js
new file mode 100644
index 0000000..b608212
--- /dev/null
+++ b/src/apps/Messages/MessagesPanel.js
@@ -0,0 +1,56 @@
+import "../../components/LoadingCircle.js"
+
+class MessagesPanel extends Shadow {
+ messages
+
+ constructor(messages) {
+ super()
+ this.messages = messages
+ }
+
+ render() {
+ VStack(() => {
+ if(this.messages) {
+ for(let i=0; i {
+ HStack(() => {
+ p(message.from.firstName + " " + message.from.lastName)
+ .fontWeight("bold")
+ .marginBottom(0.3, em)
+
+ p(util.formatTime(message.time))
+ .opacity(0.2)
+ .marginLeft(1, em)
+ })
+ p(message.text)
+ })
+ .paddingVertical(0.5, em)
+ .marginLeft(fromMe ? 70 : 0, pct)
+ .paddingRight(fromMe ? 10 : 0, pct)
+ .marginRight(fromMe ? 0 : 70, pct)
+ .paddingLeft(fromMe ? 5 : 10, pct)
+ .background(fromMe ? "var(--brown)" : "var(--green)")
+ }
+ } else {
+ LoadingCircle()
+ }
+ })
+ .onAppear(async () => {
+ requestAnimationFrame(() => {
+ this.scrollTop = this.scrollHeight
+ });
+ })
+ .gap(1, em)
+ .position("relative")
+ .overflow("scroll")
+ .height(95, pct)
+ .width(100, pct)
+ .paddingTop(2, em)
+ .paddingBottom(2, em)
+ .backgroundColor("var(--darkbrown)")
+ }
+}
+
+register(MessagesPanel)
\ No newline at end of file
diff --git a/src/apps/Messages/MessagesSidebar.js b/src/apps/Messages/MessagesSidebar.js
new file mode 100644
index 0000000..453a91d
--- /dev/null
+++ b/src/apps/Messages/MessagesSidebar.js
@@ -0,0 +1,73 @@
+class MessagesSidebar extends Shadow {
+ conversations = []
+ selectedConvoID
+ onSelect
+
+ constructor(conversations, selectedConvoID, onSelect) {
+ super()
+ this.conversations = conversations
+ this.selectedConvoID = selectedConvoID
+ this.onSelect = onSelect
+ }
+
+ render() {
+ VStack(() => {
+ this.conversations.forEach((convo, i) => {
+
+ VStack(() => {
+ HStack(() => {
+
+ p(this.makeConvoTitle(convo.between))
+ .textAlign("left")
+ .marginLeft(0.5, inches)
+ .paddingTop(0.2, inches)
+ .width(100, pct)
+ .marginTop(0)
+ .fontSize(1, em)
+ .fontWeight("bold")
+
+ p(util.formatTime(convo.messages.last.time))
+ .paddingTop(0.2, inches)
+ .fontSize(0.8, em)
+ .marginRight(0.1, inches)
+ .color("var(--divider")
+ })
+ .justifyContent("space-between")
+ .marginBottom(0)
+
+ p(convo.messages.last.text)
+ .fontSize(0.8, em)
+ .textAlign("left")
+ .marginLeft(0.5, inches)
+ .marginBottom(2, em)
+ .color("var(--divider)")
+ })
+ .background(convo.id === this.selectedConvoID ? "var(--darkbrown)" : "")
+ .onClick(() => {
+ this.onSelect(i)
+ })
+ })
+ })
+ .minWidth(15, vw)
+ .height(100, vh)
+ .gap(0, em)
+ }
+
+ makeConvoTitle(members) {
+ let membersString = ""
+ for(let i=0; i 2) {
+ membersString += member.firstName
+ } else {
+ membersString += member.firstName + " " + member.lastName
+ }
+ }
+ return membersString
+ }
+}
+
+register(MessagesSidebar)
\ No newline at end of file
diff --git a/src/apps/Tasks/Tasks.js b/src/apps/Tasks/Tasks.js
new file mode 100644
index 0000000..4b0e733
--- /dev/null
+++ b/src/apps/Tasks/Tasks.js
@@ -0,0 +1,153 @@
+css(`
+ tasks- {
+ font-family: 'Bona';
+ }
+
+ tasks- input::placeholder {
+ font-family: 'Bona Nova';
+ font-size: 0.9em;
+ color: var(--accent);
+ }
+
+ input[type="checkbox"] {
+ appearance: none; /* remove default style */
+ -webkit-appearance: none;
+ width: 1em;
+ height: 1em;
+ border: 1px solid var(--accent);
+ }
+
+ input[type="checkbox"]:checked {
+ background-color: var(--red);
+ }
+`)
+
+class Tasks extends Shadow {
+ projects = [
+ {
+ "title": "Blockcatcher",
+ "tasks": {}
+ }
+ ]
+ columns = [
+ {
+ "title": "backlog",
+ "tasks": {}
+ }
+ ]
+
+ render() {
+ ZStack(() => {
+ HStack(() => {
+ VStack(() => {
+ h3("Projects")
+ .marginTop(0)
+ .marginBottom(1, em)
+ .marginLeft(0.4, em)
+
+ if (this.projects.length >= 1) {
+ for(let i = 0; i < this.projects.length; i++) {
+ p(this.projects[i].title)
+ }
+ } else {
+ p("No Projects!")
+ }
+ })
+ .height(100, vh)
+ .paddingLeft(2, em)
+ .paddingRight(2, em)
+ .paddingTop(2, em)
+ .gap(0, em)
+ .borderRight("0.5px solid var(--accent2)")
+
+ HStack(() => {
+ if (this.columns.length >= 1) {
+ for(let i = 0; i < this.columns.length; i++) {
+ p(this.columns[i].name)
+ }
+ } else {
+ p("No Conversations!")
+ }
+ })
+ .height(100, vh)
+ .paddingLeft(2, em)
+ .paddingRight(2, em)
+ .paddingTop(2, em)
+ .gap(0, em)
+ .borderRight("0.5px solid var(--accent2)")
+ })
+ .width(100, "%")
+ .x(0).y(13, vh)
+ .borderTop("0.5px solid var(--accent2)")
+
+ p("0 Items")
+ .position("absolute")
+ .x(50, vw).y(50, vh)
+ .transform("translate(-50%, -50%)")
+
+ HStack(() => {
+ input("Search tasks...", "45vw")
+ .attr({
+ "type": "text"
+ })
+ .fontSize(1.1, em)
+ .paddingLeft(1.3, em)
+ .background("transparent")
+ .border("0.5px solid var(--accent2)")
+ .outline("none")
+ .color("var(--accent)")
+ .borderRadius(10, px)
+
+ button("Search")
+ .marginLeft(2, em)
+ .borderRadius(10, px)
+ .background("transparent")
+ .border("0.5px solid var(--accent2)")
+ .color("var(--accent)")
+ .fontFamily("Bona Nova")
+ .onHover(function (hovering) {
+ if(hovering) {
+ this.style.background = "var(--green)"
+
+ } else {
+ this.style.background = "transparent"
+
+ }
+ })
+
+ button("+ New Task")
+ .width(9, em)
+ .marginLeft(1, em)
+ .borderRadius(10, px)
+ .background("transparent")
+ .border("0.5px solid var(--accent2)")
+ .color("var(--accent)")
+ .fontFamily("Bona Nova")
+ .onHover(function (hovering) {
+ if(hovering) {
+ this.style.background = "var(--green)"
+
+ } else {
+ this.style.background = "transparent"
+
+ }
+ })
+ .onClick((clicking) => {
+ console.log(this, "clicked")
+ })
+
+ })
+ .x(55, vw).y(4, vh)
+ .position("absolute")
+ .transform("translateX(-50%)")
+ })
+ .width(100, "%")
+ .height(100, "%")
+ }
+
+ connectedCallback() {
+ // Optional additional logic
+ }
+}
+
+register(Tasks)
\ No newline at end of file
diff --git a/src/components/AppMenu.js b/src/components/AppMenu.js
new file mode 100644
index 0000000..73aa805
--- /dev/null
+++ b/src/components/AppMenu.js
@@ -0,0 +1,68 @@
+class AppMenu extends Shadow {
+ selected = ""
+
+ onNewSelection() {
+ this.$$("img").forEach((image) => {
+ image.style.background = ""
+ })
+ }
+
+ render() {
+ console.log("rendering")
+ HStack(() => {
+ img("/_/icons/Column.svg", "1.5em", "1.5em")
+ .attr({app: "forum"})
+ .padding(0.5, em)
+ .borderRadius(10, px)
+ .onClick((finished, e) => {
+ if(finished) {
+ this.onNewSelection()
+ }
+ e.target.style.background = "var(--accent)"
+ console.log(e.target, e.target.style.background)
+ if(finished) {
+ window.navigateTo("/")
+ }
+ })
+ img("/_/icons/letter.svg", "1.5em", "1.5em")
+ .attr({app: "messages"})
+ .padding(0.5, em)
+ .borderRadius(10, px)
+ .onClick((finished, e) => {
+ if(finished) {
+ this.onNewSelection()
+ }
+ e.target.style.background = "rgb(112 150 114)"
+ if(finished) {
+ window.navigateTo("/messages")
+ }
+ })
+ img("/_/icons/jobs.svg", "1.5em", "1.5em")
+ .attr({app: "jobs"})
+ .padding(0.5, em)
+ .borderRadius(10, px)
+ .onClick((finished, e) => {
+ if(finished) {
+ this.onNewSelection()
+ }
+ e.target.style.background = "#9392bb"
+ if(finished) {
+ window.navigateTo("/jobs")
+ }
+ })
+ })
+ .borderTop("1px solid black")
+ .height("auto")
+ .position('fixed')
+ .background("var(--main)")
+ .zIndex(1)
+ .x(0).yBottom(0)
+ .justifyContent("space-between")
+ .paddingHorizontal(4, em)
+ .paddingVertical(1, em)
+ .width(100, vw)
+ .boxSizing("border-box")
+ }
+}
+
+register(AppMenu)
\ No newline at end of file
diff --git a/src/components/LoadingCircle.js b/src/components/LoadingCircle.js
new file mode 100644
index 0000000..f71a86c
--- /dev/null
+++ b/src/components/LoadingCircle.js
@@ -0,0 +1,25 @@
+class LoadingCircle extends Shadow {
+ render() {
+ div()
+ .borderRadius(100, pct)
+ .width(2, em).height(2, em)
+ .x(45, pct).y(50, pct)
+ .center()
+ .backgroundColor("var(--accent")
+ .transition("transform 1.75s ease-in-out")
+ .onAppear(function () {
+ let growing = true;
+
+ setInterval(() => {
+ if (growing) {
+ this.style.transform = "scale(1.5)";
+ } else {
+ this.style.transform = "scale(0.7)";
+ }
+ growing = !growing;
+ }, 750);
+ });
+ }
+}
+
+register(LoadingCircle)
\ No newline at end of file
diff --git a/src/components/Sidebar.js b/src/components/Sidebar.js
new file mode 100644
index 0000000..75f5180
--- /dev/null
+++ b/src/components/Sidebar.js
@@ -0,0 +1,46 @@
+class Sidebar extends Shadow {
+
+ SidebarItem(text) {
+ return p(text)
+ .fontSize(1.5, em)
+ .fontWeight("bold")
+ .fontFamily("Sedan SC")
+ .marginLeft(2, em)
+ .fontStyle("italic")
+ .onClick(function () {
+ if(this.innerText === "Home") {
+ window.navigateTo("/")
+ return
+ }
+ window.navigateTo(this.innerText.toLowerCase().replace(/\s+/g, ""))
+ })
+ }
+
+ render() {
+ VStack(() => {
+ this.SidebarItem("Home")
+ this.SidebarItem("Map")
+ this.SidebarItem("Logout")
+ })
+ .gap(2, em)
+ .paddingTop(30, vh)
+ .height(100, vh)
+ .width(70, vw)
+ .borderLeft("1px solid black")
+ .position("fixed")
+ .background("var(--main)")
+ .xRight(-70, vw)
+ .transition("right .3s")
+ .zIndex(1)
+ }
+
+ toggle() {
+ if(this.style.right === "-70vw") {
+ this.style.right = "0vw"
+ } else {
+ this.style.right = "-70vw"
+ }
+ }
+}
+
+register(Sidebar)
\ No newline at end of file
diff --git a/src/css/style.css b/src/css/style.css
deleted file mode 100644
index 2b35d05..0000000
--- a/src/css/style.css
+++ /dev/null
@@ -1,12 +0,0 @@
-html,
-body {
- padding: 0;
- margin: 0;
-}
-
-body {
- padding-top: env(safe-area-inset-top);
- padding-bottom: env(safe-area-inset-bottom);
- padding-left: env(safe-area-inset-left);
- padding-right: env(safe-area-inset-right);
-}
\ No newline at end of file
diff --git a/src/index.html b/src/index.html
index bbb5f72..39e98bc 100644
--- a/src/index.html
+++ b/src/index.html
@@ -2,7 +2,7 @@
- Blockcatcher
+ Forum
-
+
diff --git a/src/index.js b/src/index.js
index c1feeba..5892631 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,3 +1,3 @@
-import "./js/Home.js"
+import "./Home.js"
Home()
document.body.style.backgroundColor = "var(--main)"
\ No newline at end of file
diff --git a/src/js/Home.js b/src/js/Home.js
deleted file mode 100644
index 2a6c5df..0000000
--- a/src/js/Home.js
+++ /dev/null
@@ -1,197 +0,0 @@
-class Home extends Shadow {
-
- tracking = false
- logs = []
- timer = null
-
- async startTracking() {
- if (!navigator.geolocation) {
- alert("Geolocation is not supported by your browser.");
- return;
- }
-
- this.tracking = true
-
- navigator.geolocation.requestPermission?.() || Promise.resolve('granted');
-
- const permission = await new Promise((resolve) => {
- navigator.geolocation.getCurrentPosition(
- () => resolve('granted'),
- () => resolve('denied')
- );
- });
-
- if (permission === 'denied') {
- alert("Location permission required");
- this.tracking = false
- return;
- }
-
- const timer = setInterval(async () => {
- navigator.geolocation.getCurrentPosition(
- async (pos) => {
- const { latitude, longitude } = pos.coords;
- const now = new Date();
- const log = `${now.toISOString()} — (${latitude}, ${longitude})`;
- const timestamp = this.formatCentralTime(now);
-
- this.logs = [log, ...this.logs]
-
- this.updateLogs()
- await this.sendLocation(latitude, longitude, timestamp);
- },
- (err) => console.error("Location error:", err),
- { enableHighAccuracy: true }
- );
- }, 1000);
-
- this.timer = timer
- }
-
- stopTracking() {
- if (this.timer) {
- clearInterval(this.timer);
- this.timer = null;
- this.tracking = false;
- }
- }
-
- render() {
- ZStack(() => {
- // Title bar
- HStack(() => {
- img("./_/icons/runner.svg", "2em", "2em")
- p("San Marcos, TX")
- .fontSize(1.2, em)
- img("./_/icons/hamburger.svg", "2em", "2em")
- })
- .gap(15, vw)
- .position("fixed")
- .top(0)
- .left(0)
- .width(100, vw)
- .paddingTop(2, em)
- .paddingBottom(2, em)
- .borderBottom("0.1rem solid var(--text)")
- .backgroundColor("var(--yellow)")
- .fontWeight("bold")
- .display("flex")
- .alignItems("center")
- .justifyContent("center")
- .zIndex(10)
-
- VStack(() => {
- button(() => {
- if(this.tracking) {
- Rectangle("48%", "48%")
- .fill("#264B61")
- .stroke("0.2em", "black")
- } else {
- Triangle("58%", "58%")
- .fill("#9F292B")
- .stroke("0.2em", "black")
- }
- })
- .attr({"id": "playButton"})
- .width(120, px)
- .height(120, px)
- .x(50, vw).y(50, vh)
- .center()
- .borderRadius(50, "%")
- .backgroundColor(this.tracking ? "#CD593E" : "#A6EABD")
- .border("0.2em solid black")
- .cursor("pointer")
- .display("flex")
- .alignItems("center")
- .justifyContent("center")
- .onTap(() => {
- console.log("tapped")
- if (this.tracking) {
- this.stopTracking();
- this.rerender()
- } else {
- this.startTracking();
- this.rerender()
- }
- })
-
- VStack(() => {
- this.logs.map(log =>
- div(log)
- .paddingHorizontal("8px")
- .paddingVertical("12px")
- .backgroundColor("rgba(255,255,255,0.9)")
- .borderRadius(6, px)
- .marginBottom(6, px)
- .fontSize(0.9, rem)
- .color("#222")
- .fontFamily("monospace")
- )
- })
- .attr({"id": "logList"})
- .flex(1)
- .overflowY("auto")
- .paddingHorizontal(16, px)
- })
- .marginTop(7, em)
- })
- .overflowX("hidden")
- .width(100, vw)
- .height(100, vh)
- .display("block")
- .margin(0)
- .color("var(--text)")
- .fontFamily("Arial")
- }
-
- showTracking() {
- this.$("#playButton").rerender()
- this.$("#playButton").style.backgroundColor = this.tracking ? "#CD593E" : "#A6EABD"
- }
-
- updateLogs() {
- let list = this.$("#logList")
- list.rerender()
- list.attr({"id": "logList"})
- }
-
- formatCentralTime(date) {
- return new Intl.DateTimeFormat('en-US', {
- timeZone: 'America/Chicago',
- month: '2-digit',
- day: '2-digit',
- year: '2-digit',
- hour: 'numeric',
- minute: '2-digit',
- second: '2-digit',
- hour12: true
- })
- .format(date)
- .replace(/,/, '') // "04/05/25, 2:30:00 PM" → "04/05/25 2:30:00 PM"
- .replace(/\//g, '.') // → "04.05.25 2:30:00 PM"
- .toLowerCase()
- .replace(' pm', 'pm')
- .replace(' am', 'am');
- }
-
- async sendLocation(lat, lon, timestamp) {
- try {
- const resp = await fetch('http://localhost:3008/api/location', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- name: "Freddy Krueger",
- latitude: lat,
- longitude: lon,
- timestamp
- })
- });
- if (!resp.ok) throw new Error(resp.status);
- console.log('Location sent');
- } catch (e) {
- console.error('Failed to send location:', e);
- }
- }
-}
-
-register(Home)
\ No newline at end of file