diff --git a/.gitignore b/.gitignore index 25c8fdb..2c338e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ +.DS_Store +package-lock.json node_modules -package-lock.json \ No newline at end of file +.env + +db/db.json \ No newline at end of file diff --git a/package.json b/package.json index d7109b2..59175c9 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { "name": "Parchment", "version": "1.0.0", - "scripts": { - "start": "node server/index.js" - }, + "type": "module", + "scripts": { + "start": "node server/index.js" + }, "dependencies": { "argon2": "^0.44.0", "chalk": "^4.1.2", diff --git a/server/auth.js b/server/auth.js new file mode 100644 index 0000000..e1b860a --- /dev/null +++ b/server/auth.js @@ -0,0 +1,89 @@ +import dotenv from 'dotenv'; +import jwt from 'jsonwebtoken'; +import argon2 from 'argon2'; + +dotenv.config(); + +export default class AuthHandler { + ips = new Map() + #secret + + constructor() { + this.#secret = process.env.JWT_SECRET; + } + + isLoggedInUser(req, res) { + const token = req.cookies.auth_token; + + if (!token) { + return false; + } + + try { + const decoded = jwt.verify(token, process.env.JWT_SECRET); + req.user = decoded; + return true; + } catch (err) { + return false; + } + } + + getProfile(req, res) { + const token = req.cookies.auth_token; + if (!token) return res.status(401).send({ error: "No auth token" }); + + try { + const payload = jwt.verify(token, process.env.JWT_SECRET); + const email = payload.email; + + const user = db.members.getByEmail(email); + res.send({ email: user.email, name: user.firstName + " " + user.lastName }); + } catch (err) { + res.status(401).send({ error: "Invalid token" }); + } + } + + async login(req, res) { + const { email, password } = req.body; + let foundUser = global.db.members.getByEmail(email) + if(!foundUser) { + res.status(400).json({ error: 'Incorrect email.' }); + return; + } + const storedHash = foundUser.password + const valid = await argon2.verify(storedHash, password); + if (!valid) { + res.status(400).json({ error: 'Incorrect password.' }); + } else { + const payload = { email: foundUser.email }; + console.log(payload) + const secret = process.env.JWT_SECRET; + const options = { expiresIn: "2h" }; + const token = jwt.sign(payload, secret, options); + + res.cookie("auth_token", token, { + httpOnly: true, // cannot be accessed by JS + secure: process.env.ENV === "production", // only over HTTPS + sameSite: "lax", // like SameSiteLaxMode + maxAge: 2 * 60 * 60 * 1000, // 2 hours in milliseconds + path: "/", // available on entire site + domain: process.env.ENV === "production" ? "." + process.env.BASE_URL : undefined + }); + + res.redirect("/") + } + } + + logout(req, res) { + res.cookie('auth_token', '', { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + maxAge: 0, // expire immediately + path: '/', + domain: process.env.ENV === "production" ? "." + process.env.BASE_URL : undefined + }); + + res.redirect("/") + } +} \ No newline at end of file diff --git a/server/db/db.js b/server/db/db.js new file mode 100644 index 0000000..0b00f6b --- /dev/null +++ b/server/db/db.js @@ -0,0 +1,107 @@ +import fs from 'fs/promises'; +import chalk from 'chalk'; +import path from 'path'; + +import Titles from "./model/Titles.js" +import Members from './model/Members.js' +import Tokens from './model/Tokens.js' +import Payments from "./model/Payments.js" +import Posts from "./model/Forum/Posts.js" +import Conversations from "./model/Messages/Conversations.js" +import Messages from "./model/Messages/Messages.js" + +export default class Database { + titles = new Titles() + members = new Members() + tokens = new Tokens() + payments = new Payments() + posts = new Posts() + conversations = new Conversations() + messages = new Messages() + + fromID = { + "HY": this.titles, + "MEMBER": this.members, + "TOKEN": this.tokens, + "PAYMENT": this.payments, + "POST": this.posts, + "CONVERSATION": this.conversations, + "DM": this.messages + } + + constructor() { + this.loadData() + } + + async loadData() { + const dbData = await fs.readFile(path.join(process.cwd(), 'db/db.json'), 'utf8'); + let dbJson; + try { + dbJson = JSON.parse(dbData); + } catch { + dbJson = [] + } + let nodes = dbJson["nodes"]; + let entries = Object.entries(nodes) + + for(let i=0; i { + console.log("saving db") + global.db.saveData() + }, 5000) + } + + async saveData() { + let data = { + "nodes": { + + }, + "edges": { + + } + } + let arrs = [ + this.titles.entries, + this.members.entries, + this.tokens.entries, + this.posts.entries, + this.conversations.entries, + this.messages.entries, + this.payments.entries, + ] + let ids = [ + Object.entries(this.titles.ids), + Object.entries(this.members.ids), + Object.entries(this.tokens.ids), + Object.entries(this.posts.ids), + Object.entries(this.conversations.ids), + Object.entries(this.messages.ids), + Object.entries(this.payments.ids), + ] + for(let i=0; i {return s.startsWith("$argon2")} + + save(member) { + let id = `MEMBER-${member.id}` + let result = this.schema.safeParse(member) + if(result.success) { + try { + super.add(id, member) + } catch(e) { + console.error(e) + throw e + } + } else { + console.error("Failed parsing member: ", result.error) + throw new global.ServerError(400, "Invalid Member Data!: "); + } + } + + async add(newMember, tokenID) { + newMember.tokenUsed = tokenID + const hash = await argon2.hash(newMember.password); + newMember.password = hash + newMember.joined = global.currentTime() + newMember.id = this.entries.length+1 + this.save(newMember) + } + + get(id) { + return this.entries[this.ids[id]] + } + + getByEmail(email) { + for(let i=0; i { + let split = id.split("-") + return ( + split.length === 2 + && split[0] === "HY" + && !isNaN(Number(split[1])) + ) + } + let idres = checkID() + if(!idres) { + return false + } + + let checkFields = () => { + let fields = [ + "fullName", + ] + for(let i = 0; i < fields.length; i++) { + if(!node[fields[i]]) { + throw new Error(`Title ${id} is missing trait ${fields[i]}`) + return false + } + } + return true + } + let fieldres = checkFields() + if(!fieldres) { + return false + } + + return true + } +} \ No newline at end of file diff --git a/server/db/model/Tokens.js b/server/db/model/Tokens.js new file mode 100644 index 0000000..21de174 --- /dev/null +++ b/server/db/model/Tokens.js @@ -0,0 +1,41 @@ +import OrderedObject from "./OrderedObject.js" +import { z } from 'zod'; + +export default class Tokens extends OrderedObject { + + schema = z.object({ + index: z.number(), + url: z.string(), + uuid: z.string().regex( + /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, + "Invalid UUID" + ), + used: z.boolean(), + }) + + markUsed(uuid) { + let token = this.get(uuid) + token.used = true + super.update(`TOKEN-${uuid}`, token) + } + + save(token) { + let id = `TOKEN-${token.uuid}` + let result = this.schema.safeParse(token) + if(result.success) { + try { + super.add(id, token) + } catch(e) { + console.error(e) + throw e + } + } else { + console.error(result.error) + throw new global.ServerError(400, "Invalid Member Data!"); + } + } + + get(uuid) { + return this.entries[this.ids[`TOKEN-${uuid}`]] + } +} \ No newline at end of file diff --git a/index.js b/server/index.js similarity index 68% rename from index.js rename to server/index.js index 77eb21e..18db27c 100644 --- a/index.js +++ b/server/index.js @@ -1,31 +1,41 @@ -import express from 'express' -import cors from 'cors' -import cookieParser from 'cookie-parser' -import http from 'http' -import fs from 'fs' -import chalk from 'chalk' -import moment from 'moment' -import path from 'path' -import * as useragent from "express-useragent"; +import express from 'express'; +import cors from 'cors'; +import cookieParser from 'cookie-parser'; +import http from 'http'; +import fs from 'fs'; +import chalk from 'chalk'; +import moment from 'moment'; +import path from 'path'; +import * as useragent from 'express-useragent'; +import { fileURLToPath } from "url" +const __dirname = path.dirname(fileURLToPath(import.meta.url)) import "./util.js" import Socket from './ws/ws.js' import Database from "./db/db.js" import AuthHandler from './auth.js'; +import PaymentsHandler from "./payments.js" -export default class Server { +class Server { db; auth; - UIPath = path.join(global.__dirname, '../ui') - DBPath = path.join(global.__dirname, './db') + UIPath = path.join(__dirname, '../ui') + DBPath = path.join(__dirname, '../db') registerRoutes(router) { + /* Stripe */ + router.post("/create-checkout-session", PaymentsHandler.danceTicket) + router.post("/webhook", express.raw({ type: "application/json" }), PaymentsHandler.webhook) + /* Auth */ router.post('/login', this.auth.login) router.get('/profile', this.auth.getProfile) router.get('/signout', this.auth.logout) /* Site */ + router.get('/signup', this.verifyToken, this.get) + router.post('/signup', this.verifyToken, this.newUserSubmission) + router.get('/db/images/*', this.getUserImage) router.get('/*', this.get) return router } @@ -44,6 +54,21 @@ export default class Server { next() } + newUserSubmission = async (req, res) => { + const { token } = req.query; + try { + let tokenData = db.tokens.get(token) + if(tokenData.used) throw new global.ServerError(400, "Token alredy used!") + await db.members.add(req.body, tokenData.uuid) + db.tokens.markUsed(token) + global.db.saveData() + return res.status(200).json({}); + } catch(e) { + console.log(e) + return res.status(e.status).json({ error: 'Error adding new member' }); + } + } + authMiddleware = (req, res, next) => { const authHeader = req.headers.authorization; if (!authHeader) { @@ -64,8 +89,23 @@ export default class Server { } } + getUserImage = async (req, res) => { + function getFileByNumber(dir, number) { + const files = fs.readdirSync(dir); + const match = files.find(file => { + const base = path.parse(file).name; // filename without extension + return base === String(number); + }); + return match ? path.join(dir, match) : null; + } + let filePath = getFileByNumber(path.join(this.DBPath, "images"), path.basename(req.url)) + + res.sendFile(filePath) + } + get = async (req, res) => { + console.log("get") let url = req.url let publicSite = () => { @@ -132,6 +172,7 @@ export default class Server { global.db = this.db this.auth = new AuthHandler() const app = express(); + app.post("/webhook", express.raw({ type: "application/json" }), PaymentsHandler.webhook) app.use(cors({ origin: '*' })); app.use(express.json()); app.use(express.urlencoded({ extended: true })); @@ -147,10 +188,10 @@ export default class Server { const server = http.createServer(app); global.Socket = new Socket(server); - const PORT = 3005; + const PORT = 10002; server.listen(PORT, () => { console.log("\n") - console.log(chalk.yellow("*************** Hyperia ***************")) + console.log(chalk.yellow("*************** parchment.page ********")) console.log(chalk.yellowBright(`Server is running on port ${PORT}: http://localhost`)); console.log(chalk.yellow("***************************************")) console.log("\n") @@ -164,4 +205,6 @@ export default class Server { Object.preventExtensions(this); } -} \ No newline at end of file +} + +const server = new Server() \ No newline at end of file diff --git a/server/payments.js b/server/payments.js new file mode 100644 index 0000000..ad3ef54 --- /dev/null +++ b/server/payments.js @@ -0,0 +1,68 @@ +import Stripe from 'stripe'; +import dotenv from 'dotenv'; +dotenv.config(); + +const stripe = new Stripe(process.env.STRIPE_SECRET); + +export default class PaymentsHandler { + + static async danceTicket(req, res) { + try { + const session = await stripe.checkout.sessions.create({ + mode: "payment", + payment_method_types: ["card"], + metadata: { + productId: "austin_winter_ball_2025_ticket" + }, + line_items: [ + { + price_data: { + currency: "usd", + product_data: { + name: "Hyperia Winter Ball" + }, + unit_amount: 3500 + }, + quantity: 1 + } + ], + success_url: `${process.env.PROTOCOL + process.env.BASE_URL}/success`, + cancel_url: `${process.env.PROTOCOL + process.env.BASE_URL}/events` + }); + + res.json({ url: session.url }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "Something went wrong." }); + } + } + + static webhook = (req, res) => { + const sig = req.headers["stripe-signature"]; + + try { + const event = stripe.webhooks.constructEvent( + req.body, + sig, + process.env.WEBHOOK_SECRET + ); + + if (event.type === "checkout.session.completed") { + const session = event.data.object; + let toStore = { + "product": session.metadata.productId, + "email": session.customer_details.email, + "name": session.customer_details.name, + "time": global.currentTime(), + "amount": session.amount_total, + } + global.db.payments.add(toStore) + } + + res.sendStatus(200); + } catch (err) { + console.error(err); + res.sendStatus(400); + } + } +} \ No newline at end of file diff --git a/server/util.js b/server/util.js new file mode 100644 index 0000000..7ae1f68 --- /dev/null +++ b/server/util.js @@ -0,0 +1,24 @@ +global.ServerError = class extends Error { + constructor(status, msg) { + super(msg); + this.status = status; + } +} + +global.currentTime = function () { + const now = new Date(); + + const month = String(now.getMonth() + 1).padStart(2, "0"); + const day = String(now.getDate()).padStart(2, "0"); + const year = now.getFullYear(); + + let hours = now.getHours(); + const ampm = hours >= 12 ? "pm" : "am"; + hours = hours % 12 || 12; // convert to 12-hour format + + const minutes = String(now.getMinutes()).padStart(2, "0"); + const seconds = String(now.getSeconds()).padStart(2, "0"); + const ms = String(now.getMilliseconds()).padStart(4, "0"); // 4-digit like "5838" + + return `${month}.${day}.${year}-${hours}:${minutes}:${seconds}${ms}${ampm}`; +} \ No newline at end of file diff --git a/server/ws/handlers/ForumHandler.js b/server/ws/handlers/ForumHandler.js new file mode 100644 index 0000000..6092392 --- /dev/null +++ b/server/ws/handlers/ForumHandler.js @@ -0,0 +1,43 @@ +import { z } from 'zod'; + +const sendSchema = z.object({ + forum: z.string(), + text: z.string(), +}) +.strict() + +const getSchema = z.object({ + forum: z.string(), + number: z.number() +}) +.strict() + + +export default class ForumHandler { + static handleSend(msg, ws) { + try { + global.db.posts.add(msg.text, msg.forum, ws.userEmail) + global.Socket.broadcast({event: "new-post", app: "FORUM", forum: msg.forum, msg: this.handleGet({forum: msg.forum, number: 100})}) + return {success: true} + } catch(e) { + console.error(e) + } + } + + static handleGet(msg) { + let data = global.db.posts.get(msg.forum, msg.number) + return data + } + + static handle(operation, msg, ws) { + switch(operation) { + case "SEND": + if(!sendSchema.safeParse(msg).success) throw new Error("Incorrectly formatted Forum ws message!") + return this.handleSend(msg, ws) + case "GET": + if(!getSchema.safeParse(msg).success) throw new Error("Incorrectly formatted Forum ws message!") + return this.handleGet(msg) + } + + } +} \ No newline at end of file diff --git a/server/ws/handlers/MessagesHandler.js b/server/ws/handlers/MessagesHandler.js new file mode 100644 index 0000000..1abdeaa --- /dev/null +++ b/server/ws/handlers/MessagesHandler.js @@ -0,0 +1,40 @@ +import { z } from 'zod'; + +const sendSchema = z.object({ + conversation: z.string(), + text: z.string(), +}) +.strict() + +export default class MessagesHandler { + + static handleSend(msg, ws) { + let user = global.db.members.getByEmail(ws.userEmail) + let convo = global.db.conversations.get(msg.conversation) + if(convo.between.includes(`MEMBER-${user.id}`)) { + global.db.messages.add(msg.conversation, msg.text, `MEMBER-${user.id}`) + global.Socket.broadcast({event: "new-message", app: "MESSAGES", msg: {conversationID: convo.id, messages: global.db.messages.getByConversation(`CONVERSATION-${msg.conversation}`)}}) + + } else { + throw new Error("Can't add to a conversation user is not a part of!") + } + return {success: true} + } + + static handleGet(ws) { + let user = global.db.members.getByEmail(ws.userEmail) + let data = global.db.conversations.getByMember(`MEMBER-${user.id}`) + return data + } + + static handle(operation, msg, ws) { + switch(operation) { + case "GET": + return this.handleGet(ws) + case "SEND": + if(!sendSchema.safeParse(msg).success) throw new Error("Incorrectly formatted Forum ws message!") + return this.handleSend(msg, ws) + } + + } +} \ No newline at end of file diff --git a/server/ws/ws.js b/server/ws/ws.js new file mode 100644 index 0000000..c7edd8c --- /dev/null +++ b/server/ws/ws.js @@ -0,0 +1,109 @@ +import { WebSocket, WebSocketServer } from 'ws'; +import { z } from 'zod'; +import jwt from 'jsonwebtoken'; + +import ForumHandler from "./handlers/ForumHandler.js" +import MessagesHandler from "./handlers/MessagesHandler.js" + +export default class Socket { + wss; + messageSchema = z.object({ + id: z.string(), + app: z.string(), + operation: z.string().optional(), + msg: z.union([ + z.object({}).passthrough(), // allows any object + z.array(z.any()) // allows any array + ]).optional() + }) + .superRefine((data, ctx) => { + if (data.operation !== "GET" && data.msg === undefined) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["msg"], + message: "msg is required when operation is not GET" + }) + } + }) + .strict() + + constructor(server) { + this.wss = new WebSocketServer({ server }); + + this.wss.on('connection', (ws, req) => { + console.log('✅ New WebSocket client connected'); + + function parseCookies(cookieHeader = "") { + return Object.fromEntries( + cookieHeader.split(";").map(c => { + const [key, ...v] = c.trim().split("="); + return [key, decodeURIComponent(v.join("="))]; + }) + ); + } + + const cookies = parseCookies(req.headers.cookie); + const token = cookies.auth_token; + if (!token) throw new Error("No auth token"); + const payload = jwt.verify(token, process.env.JWT_SECRET); + ws.userEmail = payload.email; + + ws.on('message', (msg) => { + this.handleMessage(msg, ws); + }); + + ws.on('close', () => { + console.log('Client disconnected'); + }); + }); + + console.log('WebSocket server initialized'); + } + + // Build a system where the ws obj is updated every time on navigate, so it already has context + // this way, we can only send broadcast messages to clients that actually have that app / subapp open + handleMessage = (msg, ws) => { + try { + const text = msg.toString(); + const req = JSON.parse(text); + if(!this.messageSchema.safeParse(req).success) throw new Error("Socket.handleMessage: Incoming ws message has incorrect format!") + + let responseData; + switch (req.app) { + case "FORUM": + responseData = ForumHandler.handle(req.operation, req.msg, ws) + break; + + case "MESSAGES": + responseData = MessagesHandler.handle(req.operation, req.msg, ws) + break; + + default: + console.error("unknown ws message") + } + + let response = { + ...req + } + response.msg = responseData + + if(!this.messageSchema.safeParse(response).success) throw new Error("Socket.handleMessage: Outgoing ws message has incorrect format!") + ws.send(JSON.stringify(response)) + + } catch (e) { + console.error("Invalid WS message:", e); + } + } + + broadcast(event) { + if (!this.wss) return; + + let message = JSON.stringify(event) + + this.wss.clients.forEach(ws => { + if (ws.readyState === WebSocket.OPEN) { + ws.send(message); + } + }); + } +} diff --git a/ui/_/code/components/EmailJoinForm.js b/ui/_/code/components/EmailJoinForm.js new file mode 100644 index 0000000..58227f7 --- /dev/null +++ b/ui/_/code/components/EmailJoinForm.js @@ -0,0 +1,63 @@ +css(` + email-join-form { + display: flex + } +`) + +export default class EmailJoinForm extends HTMLElement { + constructor() { + super(); + } + + connectedCallback() { + this.querySelector('#submit-button').addEventListener('click', () => this.submitEmail()); + } + + isValidEmail(email) { + const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,16}$/; + return emailRegex.test(email); + } + + showError(message) { + $(this).find('#form-message') + .css('color', 'red') + .text(message); + } + + showSuccess(message) { + $(this).find('#form-message') + .css('color', 'green') + .text(message); + } + + clearError() { + this.querySelector('#form-message').textContent = ''; + } + + async submitEmail() { + const email = this.querySelector('#email-input').value.trim(); + this.clearError(); + + if (!this.isValidEmail(email)) { + this.showError('Please enter a valid email address.'); + return; + } + + const res = await fetch('/api/join', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ email }), + }); + + if (res.ok) { + this.showSuccess('Email sent.'); + } else { + const error = await res.text(); + this.showError(error) + } + } +} + +customElements.define("email-join-form", EmailJoinForm); \ No newline at end of file diff --git a/ui/_/code/quill.js b/ui/_/code/quill.js new file mode 100644 index 0000000..39ac364 --- /dev/null +++ b/ui/_/code/quill.js @@ -0,0 +1,1173 @@ +/* + Sam Russell + Captured Sun + 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. + 12.16.25 - [comalyr] - State + 11.25.25.1 - Added minHeight and minWidth to be counted as numerical styles + 11.25.25 - Added onChange for check boxes, added setQuery / onQueryChanged for easy filtering + 11.24.25 - Fixing onClick because it was reversed, adding event to onHover params + 11.23.25 - Added onSubmit() event for form submission, added marginHorizontal() and marginVertical() + 11.20.25 - Added "pct" style unit, added alignVertical and alignHorizontal for flex boxes + 11.19.25 - Allowing for "auto" values in otherwise numeric styles, adding vmin and vmax units + 11.17.25.3 - Adding styles() and fixing dynamic function from earlier + 11.17.25.2 - Fixing onNavigate() and onAppear() + 11.17.25 - Added dynamic function to have units in style func parameters. + 11.14.25 - Added onTouch, onTap. Changed style setters to work with Safari. Added center() funcs. + 11.13.25 - changed onFocus() to be a boolean event, added onInput() + 11.9.25 - changed p(innerText) to p(innerHTML), adjusted onNavigate to work for multiple elements and with correct "this" scope + 11.7.25 - changed registerShadow() to register(), changed onClick() to be like onHover() + 11.6.25 - adding default value for "button()" "children" parameter + 10.29.25 - adding "gap()" and "label()" functions +*/ + +/* $ NAVIGATION */ +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; +}; + +window.addEventListener('popstate', () => { + window.dispatchEvent(new Event('navigate')); +}); + +window.setQuery = function(key, value) { + const url = new URL(window.location.href); + const params = url.searchParams; + + if (value === null || value === undefined) { + params.delete(key); + } else { + params.set(key, value); + } + + const newUrl = url.toString(); + history.replaceState(null, "", newUrl); + window.dispatchEvent(new Event('query-changed')); + + return newUrl; +}; + +window.navigateTo = function(url) { + window.dispatchEvent(new Event('navigate')); + window.history.pushState({}, '', url); +} + +/* $ SELECTOR */ + +HTMLElement.prototype.$ = function(selector) { + return window.$(selector, this) +} +DocumentFragment.prototype.$ = function(selector) { + return window.$(selector, this) +} +window.$ = function(selector, el = document) { + return el.querySelector(selector) +} + +window.$$ = function(selector, el = document) { + return Array.from(el.querySelectorAll(selector)) +} +HTMLElement.prototype.$$ = function(selector) { + return window.$$(selector, this) +} +DocumentFragment.prototype.$$ = function(selector) { + return window.$$(selector, this) +} + +/* CONSOLE */ + +console.red = function(message) { + this.log(`%c${message}`, "color: rgb(254, 79, 42);"); +}; + +console.green = function(message) { + this.log(`%c${message}`, "color: rgb(79, 254, 42);"); + +} + +/* 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); + const color = rootStyles.getPropertyValue(`--${name}`).trim(); + if(!color) { + throw new Error("Color not found") + } + return color +} + +/* MOBILE */ + +window.isMobile = function isMobile() { + return /Android|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i.test(navigator.userAgent); +} + +window.css = function css(cssString) { + let container = document.querySelector("style#pageStyle"); + if(!container) { + container = document.createElement('style'); + container.id = "pageStyle"; + document.head.appendChild(container); + } + + let primarySelector = cssString.substring(0, cssString.indexOf("{")).trim(); + primarySelector = primarySelector.replace(/\*/g, "all"); + primarySelector = primarySelector.replace(/#/g, "id-"); + primarySelector = primarySelector.replace(/,/g, ""); + let stylesheet = container.querySelector(`:scope > style[id='${primarySelector}']`) + if(!stylesheet) { + stylesheet = document.createElement('style'); + stylesheet.id = primarySelector; + stylesheet.appendChild(document.createTextNode(cssString)); + container.appendChild(stylesheet); + } else { + stylesheet.innerText = cssString + } +} + +window.html = function html(elementString) { + let parser = new DOMParser(); + let doc = parser.parseFromString(elementString, 'text/html'); + return doc.body.firstChild; +} + +window.util = {} +window.util.observeClassChange = (el, callback) => { + if (!el || !(el instanceof Element)) { + throw new Error("observeClassChange requires a valid DOM element."); + } + + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.type === "attributes" && mutation.attributeName === "class") { + callback(el.classList); + } + } + }); + + observer.observe(el, { + attributes: true, + attributeFilter: ["class"] + }); + + return observer; // Optional: return it so you can disconnect later +} + +/* PAGE SETUP */ + +Object.defineProperty(Array.prototype, 'last', { + get() { + return this[this.length - 1]; + }, + enumerable: false, +}); + +/* QUILL */ + +window.quill = { + rendering: [], + lastState: null, + + render: (el) => { + if(el instanceof Shadow) { + let parent = quill.rendering[quill.rendering.length-1] + if(!parent) { + parent = document.body + } + parent.appendChild(el) + } else { + if(!el.render) {el.render = () => {}} + let parent = quill.rendering[quill.rendering.length-1] + if(!parent) throw new Error("Quill: no parent for element") + parent.appendChild(el) + } + + quill.rendering.push(el) + el.render() + quill.rendering.pop(el) + }, + + rerender: (el) => { + Array.from(el.attributes).forEach(attr => el.removeAttribute(attr.name)); + el.innerHTML = "" + el.removeAllListeners() + + quill.rendering.push(el) + el.render() + quill.rendering.pop() + }, + + isStack: (el) => { + return el.classList.contains("HStack") || el.classList.contains("ZStack") || el.classList.contains("VStack") + }, +} + +window.Shadow = class extends HTMLElement { + constructor() { + super() + } +} + +window.register = (el, tagname) => { + if (typeof el.prototype.render !== 'function') { + throw new Error("Element must have a render: " + el.prototype.constructor.name) + } + if(!tagname) { + tagname = el.prototype.constructor.name.toLowerCase() + "-" + } + customElements.define(tagname, el) + if(el.css) { + css(el.css) + } + + window[el.prototype.constructor.name] = function (...params) { + let instance = new el(...params) + if(instance.state) { + const proxyCache = new WeakMap(); + + function reactive(value, path=[]) { + if (value && typeof value === "object") { + if (proxyCache.has(value)) return proxyCache.get(value); + + const p = new Proxy(value, createHandlers(path)); + proxyCache.set(value, p); + return p; + } + return value; + } + + function isNumericKey(prop) { + return typeof prop === "string" && prop !== "" && String(+prop) === prop; + } + + function createHandlers(path) { + return { + get(target, prop, receiver) { + if (typeof prop === "symbol") { + return Reflect.get(target, prop, receiver); + } + + let nextPath = (Array.isArray(target) && !isNumericKey(prop)) ? path : path.concat(prop) // To filter out arr.length, arr.map, arr.forEach, etc. + quill.lastState = nextPath.join("."); + + const v = Reflect.get(target, prop, receiver); + return reactive(v, nextPath); + }, + + set(target, prop, value, receiver) { + const oldLength = Array.isArray(target) ? target.length : undefined; + const oldValue = target[prop]; + if (oldValue === value) return true; + + const result = Reflect.set(target, prop, value, receiver); + + let changedPath = (Array.isArray(target) && (!isNumericKey(prop) || target.length !== oldLength)) ? path : path.concat(prop).join("."); // To filter out arr.length, arr.map, arr.forEach, and also a push/pop/unshift. + const watchers = instance.stateWatchers[changedPath]; + + if (watchers) { + watchers.forEach(cb => cb()); + } + + return result; + } + }; + } + + let proxy = reactive(instance.state) + + Object.defineProperty(instance, "state", { + value: proxy, + writable: false, + configurable: false, + enumerable: true + }); + + let stateWatchers = {} + Object.keys(instance.state).forEach((key) => stateWatchers[key] = []) + Object.defineProperty(instance, "stateWatchers", { + value: stateWatchers, + writable: false, + configurable: false, + enumerable: true + }); + } + quill.render(instance) + return instance + } +} + +HTMLElement.prototype.rerender = function() { + quill.rerender(this) +} + +/* Styling */ + +window.pct = "%" +window.vmin = "vmin" +window.vmax = "vmax" +window.vh = "vh" +window.vw = "vw" +window.px = "px" +window.em = "em" +window.rem = "rem" +window.inches = "in" + +HTMLElement.prototype.addStyle = function(func) { + return func(this) +} + +window.css = function css(cssString) { + let container = document.querySelector("style#pageStyle"); + if(!container) { + container = document.createElement('style'); + container.id = "pageStyle"; + document.head.appendChild(container); + } + + let primarySelector = cssString.substring(0, cssString.indexOf("{")).trim(); + primarySelector = primarySelector.replace(/\*/g, "all"); + primarySelector = primarySelector.replace(/#/g, "id-"); + primarySelector = primarySelector.replace(/,/g, ""); + let stylesheet = container.querySelector(`:scope > style[id='${primarySelector}']`) + if(!stylesheet) { + stylesheet = document.createElement('style'); + stylesheet.id = primarySelector; + stylesheet.appendChild(document.createTextNode(cssString)); + container.appendChild(stylesheet); + } else { + stylesheet.innerText = cssString + } +} + +function extendHTMLElementWithStyleSetters() { + + function cssValueType(prop) { + const div = document.createElement("div"); + const style = div.style; + if (!(prop in style)) return "invalid"; + + switch(prop) { + + case "gap": + case "borderRadius": + case "width": + case "height": + case "maxWidth": + case "maxHeight": + case "minWidth": + case "minHeight": + + case "left": + case "top": + case "bottom": + case "right": + + case "padding": + case "paddingLeft": + case "paddingTop": + case "paddingBottom": + case "paddingRight": + + case "margin": + case "marginLeft": + case "marginTop": + case "marginBottom": + case "marginRight": + + case "textUnderlineOffset": + case "letterSpacing": + + return "unit-number" + + default: + + return "string" + } + + } + + let allStyleProps = ["accentColor", "additiveSymbols", "alignContent", "alignItems", "alignSelf", "alignmentBaseline", "all", "anchorName", "anchorScope", "animation", "animationComposition", "animationDelay", "animationDirection", "animationDuration", "animationFillMode", "animationIterationCount", "animationName", "animationPlayState", "animationRange", "animationRangeEnd", "animationRangeStart", "animationTimeline", "animationTimingFunction", "appRegion", "appearance", "ascentOverride", "aspectRatio", "backdropFilter", "backfaceVisibility", "background", "backgroundAttachment", "backgroundBlendMode", "backgroundClip", "backgroundColor", "backgroundImage", "backgroundOrigin", "backgroundPosition", "backgroundPositionX", "backgroundPositionY", "backgroundRepeat", "backgroundSize", "basePalette", "baselineShift", "baselineSource", "blockSize", "border", "borderBlock", "borderBlockColor", "borderBlockEnd", "borderBlockEndColor", "borderBlockEndStyle", "borderBlockEndWidth", "borderBlockStart", "borderBlockStartColor", "borderBlockStartStyle", "borderBlockStartWidth", "borderBlockStyle", "borderBlockWidth", "borderBottom", "borderBottomColor", "borderBottomLeftRadius", "borderBottomRightRadius", "borderBottomStyle", "borderBottomWidth", "borderCollapse", "borderColor", "borderEndEndRadius", "borderEndStartRadius", "borderImage", "borderImageOutset", "borderImageRepeat", "borderImageSlice", "borderImageSource", "borderImageWidth", "borderInline", "borderInlineColor", "borderInlineEnd", "borderInlineEndColor", "borderInlineEndStyle", "borderInlineEndWidth", "borderInlineStart", "borderInlineStartColor", "borderInlineStartStyle", "borderInlineStartWidth", "borderInlineStyle", "borderInlineWidth", "borderLeft", "borderLeftColor", "borderLeftStyle", "borderLeftWidth", "borderRadius", "borderRight", "borderRightColor", "borderRightStyle", "borderRightWidth", "borderSpacing", "borderStartEndRadius", "borderStartStartRadius", "borderStyle", "borderTop", "borderTopColor", "borderTopLeftRadius", "borderTopRightRadius", "borderTopStyle", "borderTopWidth", "borderWidth", "bottom", "boxDecorationBreak", "boxShadow", "boxSizing", "breakAfter", "breakBefore", "breakInside", "bufferedRendering", "captionSide", "caretAnimation", "caretColor", "clear", "clip", "clipPath", "clipRule", "color", "colorInterpolation", "colorInterpolationFilters", "colorRendering", "colorScheme", "columnCount", "columnFill", "columnGap", "columnRule", "columnRuleColor", "columnRuleStyle", "columnRuleWidth", "columnSpan", "columnWidth", "columns", "contain", "containIntrinsicBlockSize", "containIntrinsicHeight", "containIntrinsicInlineSize", "containIntrinsicSize", "containIntrinsicWidth", "container", "containerName", "containerType", "content", "contentVisibility", "cornerBlockEndShape", "cornerBlockStartShape", "cornerBottomLeftShape", "cornerBottomRightShape", "cornerBottomShape", "cornerEndEndShape", "cornerEndStartShape", "cornerInlineEndShape", "cornerInlineStartShape", "cornerLeftShape", "cornerRightShape", "cornerShape", "cornerStartEndShape", "cornerStartStartShape", "cornerTopLeftShape", "cornerTopRightShape", "cornerTopShape", "counterIncrement", "counterReset", "counterSet", "cursor", "cx", "cy", "d", "descentOverride", "direction", "display", "dominantBaseline", "dynamicRangeLimit", "emptyCells", "fallback", "fieldSizing", "fill", "fillOpacity", "fillRule", "filter", "flex", "flexBasis", "flexDirection", "flexFlow", "flexGrow", "flexShrink", "flexWrap", "float", "floodColor", "floodOpacity", "font", "fontDisplay", "fontFamily", "fontFeatureSettings", "fontKerning", "fontOpticalSizing", "fontPalette", "fontSize", "fontSizeAdjust", "fontStretch", "fontStyle", "fontSynthesis", "fontSynthesisSmallCaps", "fontSynthesisStyle", "fontSynthesisWeight", "fontVariant", "fontVariantAlternates", "fontVariantCaps", "fontVariantEastAsian", "fontVariantEmoji", "fontVariantLigatures", "fontVariantNumeric", "fontVariantPosition", "fontVariationSettings", "fontWeight", "forcedColorAdjust", "gap", "grid", "gridArea", "gridAutoColumns", "gridAutoFlow", "gridAutoRows", "gridColumn", "gridColumnEnd", "gridColumnGap", "gridColumnStart", "gridGap", "gridRow", "gridRowEnd", "gridRowGap", "gridRowStart", "gridTemplate", "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows", "height", "hyphenateCharacter", "hyphenateLimitChars", "hyphens", "imageOrientation", "imageRendering", "inherits", "initialLetter", "initialValue", "inlineSize", "inset", "insetBlock", "insetBlockEnd", "insetBlockStart", "insetInline", "insetInlineEnd", "insetInlineStart", "interactivity", "interpolateSize", "isolation", "justifyContent", "justifyItems", "justifySelf", "left", "letterSpacing", "lightingColor", "lineBreak", "lineGapOverride", "lineHeight", "listStyle", "listStyleImage", "listStylePosition", "listStyleType", "margin", "marginBlock", "marginBlockEnd", "marginBlockStart", "marginBottom", "marginInline", "marginInlineEnd", "marginInlineStart", "marginLeft", "marginRight", "marginTop", "marker", "markerEnd", "markerMid", "markerStart", "mask", "maskClip", "maskComposite", "maskImage", "maskMode", "maskOrigin", "maskPosition", "maskRepeat", "maskSize", "maskType", "mathDepth", "mathShift", "mathStyle", "maxBlockSize", "maxHeight", "maxInlineSize", "maxWidth", "minBlockSize", "minHeight", "minInlineSize", "minWidth", "mixBlendMode", "navigation", "negative", "objectFit", "objectPosition", "objectViewBox", "offset", "offsetAnchor", "offsetDistance", "offsetPath", "offsetPosition", "offsetRotate", "opacity", "order", "orphans", "outline", "outlineColor", "outlineOffset", "outlineStyle", "outlineWidth", "overflow", "overflowAnchor", "overflowBlock", "overflowClipMargin", "overflowInline", "overflowWrap", "overflowX", "overflowY", "overlay", "overrideColors", "overscrollBehavior", "overscrollBehaviorBlock", "overscrollBehaviorInline", "overscrollBehaviorX", "overscrollBehaviorY", "pad", "padding", "paddingBlock", "paddingBlockEnd", "paddingBlockStart", "paddingBottom", "paddingInline", "paddingInlineEnd", "paddingInlineStart", "paddingLeft", "paddingRight", "paddingTop", "page", "pageBreakAfter", "pageBreakBefore", "pageBreakInside", "pageOrientation", "paintOrder", "perspective", "perspectiveOrigin", "placeContent", "placeItems", "placeSelf", "pointerEvents", "position", "positionAnchor", "positionArea", "positionTry", "positionTryFallbacks", "positionTryOrder", "positionVisibility", "prefix", "printColorAdjust", "quotes", "r", "range", "readingFlow", "readingOrder", "resize", "result", "right", "rotate", "rowGap", "rubyAlign", "rubyPosition", "rx", "ry", "scale", "scrollBehavior", "scrollInitialTarget", "scrollMargin", "scrollMarginBlock", "scrollMarginBlockEnd", "scrollMarginBlockStart", "scrollMarginBottom", "scrollMarginInline", "scrollMarginInlineEnd", "scrollMarginInlineStart", "scrollMarginLeft", "scrollMarginRight", "scrollMarginTop", "scrollMarkerGroup", "scrollPadding", "scrollPaddingBlock", "scrollPaddingBlockEnd", "scrollPaddingBlockStart", "scrollPaddingBottom", "scrollPaddingInline", "scrollPaddingInlineEnd", "scrollPaddingInlineStart", "scrollPaddingLeft", "scrollPaddingRight", "scrollPaddingTop", "scrollSnapAlign", "scrollSnapStop", "scrollSnapType", "scrollTargetGroup", "scrollTimeline", "scrollTimelineAxis", "scrollTimelineName", "scrollbarColor", "scrollbarGutter", "scrollbarWidth", "shapeImageThreshold", "shapeMargin", "shapeOutside", "shapeRendering", "size", "sizeAdjust", "speak", "speakAs", "src", "stopColor", "stopOpacity", "stroke", "strokeDasharray", "strokeDashoffset", "strokeLinecap", "strokeLinejoin", "strokeMiterlimit", "strokeOpacity", "strokeWidth", "suffix", "symbols", "syntax", "system", "tabSize", "tableLayout", "textAlign", "textAlignLast", "textAnchor", "textAutospace", "textBox", "textBoxEdge", "textBoxTrim", "textCombineUpright", "textDecoration", "textDecorationColor", "textDecorationLine", "textDecorationSkipInk", "textDecorationStyle", "textDecorationThickness", "textEmphasis", "textEmphasisColor", "textEmphasisPosition", "textEmphasisStyle", "textIndent", "textOrientation", "textOverflow", "textRendering", "textShadow", "textSizeAdjust", "textSpacingTrim", "textTransform", "textUnderlineOffset", "textUnderlinePosition", "textWrap", "textWrapMode", "textWrapStyle", "timelineScope", "top", "touchAction", "transform", "transformBox", "transformOrigin", "transformStyle", "transition", "transitionBehavior", "transitionDelay", "transitionDuration", "transitionProperty", "transitionTimingFunction", "translate", "types", "unicodeBidi", "unicodeRange", "userSelect", "vectorEffect", "verticalAlign", "viewTimeline", "viewTimelineAxis", "viewTimelineInset", "viewTimelineName", "viewTransitionClass", "viewTransitionGroup", "viewTransitionName", "visibility", "webkitAlignContent", "webkitAlignItems", "webkitAlignSelf", "webkitAnimation", "webkitAnimationDelay", "webkitAnimationDirection", "webkitAnimationDuration", "webkitAnimationFillMode", "webkitAnimationIterationCount", "webkitAnimationName", "webkitAnimationPlayState", "webkitAnimationTimingFunction", "webkitAppRegion", "webkitAppearance", "webkitBackfaceVisibility", "webkitBackgroundClip", "webkitBackgroundOrigin", "webkitBackgroundSize", "webkitBorderAfter", "webkitBorderAfterColor", "webkitBorderAfterStyle", "webkitBorderAfterWidth", "webkitBorderBefore", "webkitBorderBeforeColor", "webkitBorderBeforeStyle", "webkitBorderBeforeWidth", "webkitBorderBottomLeftRadius", "webkitBorderBottomRightRadius", "webkitBorderEnd", "webkitBorderEndColor", "webkitBorderEndStyle", "webkitBorderEndWidth", "webkitBorderHorizontalSpacing", "webkitBorderImage", "webkitBorderRadius", "webkitBorderStart", "webkitBorderStartColor", "webkitBorderStartStyle", "webkitBorderStartWidth", "webkitBorderTopLeftRadius", "webkitBorderTopRightRadius", "webkitBorderVerticalSpacing", "webkitBoxAlign", "webkitBoxDecorationBreak", "webkitBoxDirection", "webkitBoxFlex", "webkitBoxOrdinalGroup", "webkitBoxOrient", "webkitBoxPack", "webkitBoxReflect", "webkitBoxShadow", "webkitBoxSizing", "webkitClipPath", "webkitColumnBreakAfter", "webkitColumnBreakBefore", "webkitColumnBreakInside", "webkitColumnCount", "webkitColumnGap", "webkitColumnRule", "webkitColumnRuleColor", "webkitColumnRuleStyle", "webkitColumnRuleWidth", "webkitColumnSpan", "webkitColumnWidth", "webkitColumns", "webkitFilter", "webkitFlex", "webkitFlexBasis", "webkitFlexDirection", "webkitFlexFlow", "webkitFlexGrow", "webkitFlexShrink", "webkitFlexWrap", "webkitFontFeatureSettings", "webkitFontSmoothing", "webkitHyphenateCharacter", "webkitJustifyContent", "webkitLineBreak", "webkitLineClamp", "webkitLocale", "webkitLogicalHeight", "webkitLogicalWidth", "webkitMarginAfter", "webkitMarginBefore", "webkitMarginEnd", "webkitMarginStart", "webkitMask", "webkitMaskBoxImage", "webkitMaskBoxImageOutset", "webkitMaskBoxImageRepeat", "webkitMaskBoxImageSlice", "webkitMaskBoxImageSource", "webkitMaskBoxImageWidth", "webkitMaskClip", "webkitMaskComposite", "webkitMaskImage", "webkitMaskOrigin", "webkitMaskPosition", "webkitMaskPositionX", "webkitMaskPositionY", "webkitMaskRepeat", "webkitMaskSize", "webkitMaxLogicalHeight", "webkitMaxLogicalWidth", "webkitMinLogicalHeight", "webkitMinLogicalWidth", "webkitOpacity", "webkitOrder", "webkitPaddingAfter", "webkitPaddingBefore", "webkitPaddingEnd", "webkitPaddingStart", "webkitPerspective", "webkitPerspectiveOrigin", "webkitPerspectiveOriginX", "webkitPerspectiveOriginY", "webkitPrintColorAdjust", "webkitRtlOrdering", "webkitRubyPosition", "webkitShapeImageThreshold", "webkitShapeMargin", "webkitShapeOutside", "webkitTapHighlightColor", "webkitTextCombine", "webkitTextDecorationsInEffect", "webkitTextEmphasis", "webkitTextEmphasisColor", "webkitTextEmphasisPosition", "webkitTextEmphasisStyle", "webkitTextFillColor", "webkitTextOrientation", "webkitTextSecurity", "webkitTextSizeAdjust", "webkitTextStroke", "webkitTextStrokeColor", "webkitTextStrokeWidth", "webkitTransform", "webkitTransformOrigin", "webkitTransformOriginX", "webkitTransformOriginY", "webkitTransformOriginZ", "webkitTransformStyle", "webkitTransition", "webkitTransitionDelay", "webkitTransitionDuration", "webkitTransitionProperty", "webkitTransitionTimingFunction", "webkitUserDrag", "webkitUserModify", "webkitUserSelect", "webkitWritingMode", "whiteSpace", "whiteSpaceCollapse", "widows", "width", "willChange", "wordBreak", "wordSpacing", "wordWrap", "writingMode", "x", "y", "zIndex", "zoom"] + + allStyleProps.forEach(prop => { + if (prop === "translate") return; + + const type = cssValueType(prop); + + switch (type) { + case "unit-number": + HTMLElement.prototype[prop] = StyleFunction(function(value, unit = "px") { + if(value === "auto") { + this.style[prop] = value + return this + } + this.style[prop] = value + unit; + if (value !== "" && this.style[prop] === "") { + throw new Error(`Invalid CSS value for ${prop}: ` + value + unit); + } + return this; + }); + break; + + case "string": + HTMLElement.prototype[prop] = StyleFunction(function(value) { + this.style[prop] = value; + if (value !== "" && this.style[prop] === "") { + throw new Error(`Invalid CSS value for ${prop}: ` + value); + } + return this; + }); + break; + } + }); +} + +extendHTMLElementWithStyleSetters(); + +HTMLElement.prototype.addStateWatcher = function(field, cb) { + let parent = this + while(!(parent instanceof Shadow)) { + parent = parent.parentNode + } + parent.stateWatchers[field].push(cb) +} + +// Currently only works for one state variable in the function +// Could probably be fixed by just making lastState an array and clearing it out every function call? +HTMLElement.prototype.setUpState = function(styleFunc, cb) { + let format = (value) => {return Array.isArray(value) ? value : [value]} + + // 1. Run the callback to get the style argument and also update lastState + let styleArgs = format(cb()) + + // 2. Check if lastState has really been updated. If not, the user-provided cb did not access valid state + if(!quill.lastState) { + throw new Error("Quill: style state function does not access valid state") + } + + // 3. Construct function to run when state changes + let onStateChange = () => { + styleFunc.call(this, ...format(cb())) + } + + // 4. Now listen for the state to change + this.addStateWatcher(quill.lastState, onStateChange) + + // 5. Run the original function again, this time with the actual arguments + quill.lastState = null + styleFunc.call(this, ...styleArgs) +} + +function StyleFunction(func) { + let styleFunction = function(value, unit = "px") { + if(typeof value === 'function') { + this.setUpState(styleFunction, value) + return this + } else { + func.call(this, value, unit); // ".call" ensures that "this" is correct + return this + } + } + + return styleFunction +} + +HTMLElement.prototype.styles = function(cb) { + cb.call(this, this) + return this +} + +/* Type 1 */ + +HTMLElement.prototype.paddingVertical = StyleFunction(function(value, unit = "px") { + this.style.paddingTop = value + unit + this.style.paddingBottom = value + unit + return this +}) + +HTMLElement.prototype.paddingHorizontal = StyleFunction(function(value, unit = "px") { + this.style.paddingRight = value + unit + this.style.paddingLeft = value + unit + return this +}) + +HTMLElement.prototype.marginVertical = StyleFunction(function(value, unit = "px") { + this.style.marginTop = value + unit + this.style.marginBottom = value + unit + return this +}) + +HTMLElement.prototype.marginHorizontal = StyleFunction(function(value, unit = "px") { + this.style.marginRight = value + unit + this.style.marginLeft = value + unit + return this +}) + +HTMLElement.prototype.fontSize = StyleFunction(function(value, unit = "px") { + + switch(value) { + case "6xl": + value = "3.75"; unit = "rem" + break; + + case "5xl": + value = "3"; unit = "rem" + break; + + case "4xl": + value = "2.25"; unit = "rem" + break; + + case "3xl": + value = "1.875"; unit = "rem" + break; + + case "2xl": + value = "1.5"; unit = "rem" + break; + + case "xl": + value = "1.25"; unit = "rem" + break; + + case "l": + value = "1.125"; unit = "rem" + break; + + case "s": + value = "0.875"; unit = "rem" + break; + + case "xs": + value = "0.75"; unit = "rem" + break; + + default: + break; + } + this.style.fontSize = value + unit + return this +}) + + +HTMLElement.prototype.width = function(value, unit = "px") { + if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value)) + throw new Error(`Invalid value: ${value}. Expected a number.`); + this.style.width = value + unit + if(window.getComputedStyle(this).display === "inline") { + this.style.display = "block" + } + return this +} + +HTMLElement.prototype.height = function(value, unit = "px") { + if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value)) + throw new Error(`Invalid value: ${value}. Expected a number.`); + this.style.height = value + unit + if(window.getComputedStyle(this).display === "inline") { + this.style.display = "block" + } + return this +} + +function checkPositionType(el) { + let computed = window.getComputedStyle(el).position + if(!(computed === "absolute" || computed === "fixed")) { + el.style.position = "absolute" + } +} + +HTMLElement.prototype.x = function(value, unit = "px") { + if (typeof value !== 'number' || isNaN(value)) + throw new Error(`Invalid value: ${value}. Expected a number.`); + checkPositionType(this) + this.style.left = value + unit + return this +} + +HTMLElement.prototype.y = function(value, unit = "px") { + if (typeof value !== 'number' || isNaN(value)) + throw new Error(`Invalid value: ${value}. Expected a number.`); + checkPositionType(this) + this.style.top = value + unit + return this +} + +HTMLElement.prototype.xRight = function(value, unit = "px") { + if (typeof value !== 'number' || isNaN(value)) + throw new Error(`Invalid value: ${value}. Expected a number.`); + checkPositionType(this) + this.style.right = value + unit + return this +} + +HTMLElement.prototype.yBottom = function(value, unit = "px") { + if (typeof value !== 'number' || isNaN(value)) + throw new Error(`Invalid value: ${value}. Expected a number.`); + checkPositionType(this) + this.style.bottom = value + unit + return this +} + +HTMLElement.prototype.backgroundImage = function (...values) { + const formatted = values + .map(v => { + if(v.includes("/") && !v.includes("gradient")) { + v = "url(" + v + ")" + } + return String(v).trim(); + }) + .join(", "); + + this.style.backgroundImage = formatted; + return this; +}; + +HTMLElement.prototype.center = function () { + this.style.transform = "translate(-50%, -50%)" + return this; +}; + +HTMLElement.prototype.centerX = function () { + this.style.transform = "translateX(-50%)" + return this; +}; + +HTMLElement.prototype.centerY = function () { + this.style.transform = "translateY(-50%)" + return this; +}; + +HTMLElement.prototype.alignVertical = function (value) { + const direction = getComputedStyle(this).flexDirection; + if(!direction) { + throw new Error("alignVertical can be only be used on HStacks or VStacks!") + } + + if (direction === "column" || direction === "column-reverse") { + this.style.justifyContent = value; + } else { + this.style.alignItems = value; + } + return this +} + +HTMLElement.prototype.alignHorizontal = function (value) { + const direction = getComputedStyle(this).flexDirection; + if(!direction) { + throw new Error("alignHorizontal can be only be used on HStacks or VStacks!") + } + + if (direction === "column" || direction === "column-reverse") { + this.style.alignItems = value; + } else { + this.style.justifyContent = value; + } + return this +} + + +/* Elements */ + +quill.setChildren = function(el, innerContent) { + if(typeof innerContent === "string") { + el.innerText = innerContent + } else if(typeof innerContent === "function") { + el.render = innerContent + } else { + throw new Error("Children of unknown type") + } +} + +window.a = function a( href, inner=href ) { + if(!href) throw new Error("quill a: missing href argument. Function: a( href, inner=href )") + let link = document.createElement("a") + link.setAttribute('href', href); + quill.setChildren(link, inner) + quill.render(link) + return link +} + +window.img = function img(src, width="", height="") { + let image = document.createElement("img") + + if(!src || !(typeof src==="string")) { + throw new Error("img: missing first argument: src | String") + } else { + image.src = src + } + if(width && typeof width === "string") { + image.style.width = width + } else if(width) { + image.style.width = width + "px" + } + if(height && typeof height === "string") { + image.style.height = height + } else if(height) { + image.style.height = height + "px" + } + quill.render(image) + return image +} + +HTMLImageElement.prototype.backgroundColor = function(value) { + if (this.src.endsWith('.svg') || this.src.startsWith('data:image/svg+xml')) { + fetch(this.src).then(response => response.text()) + .then(svgText => { + const modifiedSvg = svgText.replace(/\bfill="[^"]*"/g, `fill="${value}"`); + const blob = new Blob([modifiedSvg], { type: 'image/svg+xml' }); + this.src = URL.createObjectURL(blob); + }).catch(error => { + console.error('Error updating SVG fill:', error); + }); + } else { + this.style.backgroundColor = value; + } + + return this; // Always returns the element itself + }; + +window.p = function p(innerHTML) { + let el = document.createElement("p") + if(typeof innerText === "function") { + el.render = innerHTML + } else { + el.innerHTML = innerHTML + } + el.style.margin = "0"; + quill.render(el) + return el +} + +window.h1 = function h1(innerText) { + let el = document.createElement("h1") + el.innerText = innerText + quill.render(el) + return el +} + +window.h2 = function h2(innerText) { + let el = document.createElement("h2") + el.innerText = innerText + quill.render(el) + return el +} + +window.h3 = function h3(innerText) { + let el = document.createElement("h3") + el.innerText = innerText + quill.render(el) + return el +} + +window.div = function (innerText) { + let el = document.createElement("div") + el.innerText = innerText ?? "" + quill.render(el) + return el +} + +window.span = function (innerText) { + let el = document.createElement("span") + el.innerText = innerText + quill.render(el) + return el +} + +window.button = function (children = "") { + let el = document.createElement("button") + quill.setChildren(el, children) + quill.render(el) + return el +} + +window.form = function(cb) { + let el = document.createElement("form") + el.render = cb + quill.render(el) + return el +} + +window.input = function(placeholder = "", width, height) { + let el = document.createElement("input") + el.placeholder = placeholder + el.style.width = width + el.style.height = height + quill.render(el) + return el +} + +window.label = function(inside) { + let el = document.createElement("label") + if(typeof inside === "string") { + el.innerText = inside + } else { + el.render = inside + } + quill.render(el) + return el +} + +window.textarea = function(placeholder = "") { + let el = document.createElement("textarea") + el.placeholder = placeholder + quill.render(el) + return el +} + + +/* STACKS */ + +handleStack = function(cb, name, styles="") { + let nowRendering = quill.rendering[quill.rendering.length-1] + if (nowRendering.innerHTML.trim() === "" && !quill.isStack(nowRendering)) { + nowRendering.style.cssText += styles + nowRendering.classList.add(name) + cb() + if(quill.lastState) { + nowRendering.addStateWatcher(quill.lastState, () => { + nowRendering.innerHTML = "" + cb() + }) + } + return nowRendering + } else { + let div = document.createElement("div") + div.classList.add(name) + div.style.cssText += styles + div.render = cb + quill.render(div) + return div + } +} + +window.VStack = function (cb = () => {}) { + let styles = ` + display: flex; + flex-direction: column; + ` + return handleStack(cb, "VStack", styles) +} + +window.HStack = function (cb = () => {}) { + let styles = ` + display: flex; + flex-direction: row; + `; + return handleStack(cb, "HStack", styles) +}; + +window.ZStack = function (cb = () => {}) { + return handleStack(cb, "ZStack") +}; + +/* SHAPES */ + +window.svgMethods = function(svg) { + svg.pulse = function (duration = 600) { + this.style.transition = `transform ${duration}ms ease-in-out` + this.style.transform = "scale(1.2)" + setTimeout(() => { + this.style.transform = "scale(1)" + }, duration / 2) + return this + } + + // Rotate (e.g. loading spinner) + svg.rotate = function (degrees = 360, duration = 1000) { + this.style.transition = `transform ${duration}ms linear` + this.style.transform = `rotate(${degrees}deg)` + return this + } + + // Change color + svg.fill = function (color) { + this.setAttribute("fill", color) + return this + } + + svg.height = function (height) { + this.setAttribute("height", height) + return this + } + + svg.width = function (width) { + this.setAttribute("width", width) + return this + } + + svg.stroke = function (width, color) { + this.setAttribute("stroke", color) + this.setAttribute("stroke-width", width) + return this + } + + // Toggle visibility + svg.toggle = function () { + this.style.display = this.style.display === "none" ? "" : "none" + return this + } +} + +window.Rectangle = function (width = "40px", height = "40px") { + const svgNS = "http://www.w3.org/2000/svg"; + const svgEl = document.createElementNS(svgNS, "svg"); + const rectEl = document.createElementNS(svgNS, "rect"); + + // SVG size + svgEl.setAttribute("width", width); + svgEl.setAttribute("height", height); + svgEl.setAttribute("viewBox", "0 0 100 100"); + svgEl.setAttribute("preserveAspectRatio", "xMidYMid meet"); + + // Rectangle: full size, slightly rounded corners + rectEl.setAttribute("x", "15"); // 15% margin from edges + rectEl.setAttribute("y", "15"); + rectEl.setAttribute("width", "70"); // 70% of viewBox + rectEl.setAttribute("height", "70"); + // rectEl.setAttribute("rx", "8"); // rounded corners (optional) + // rectEl.setAttribute("ry", "8"); + + svgEl.appendChild(rectEl); + svgMethods(svgEl); // assuming you have this + quill.render(svgEl); + return svgEl; +} + +window.Triangle = function (width = "40px", height = "40px") { + const svgNS = "http://www.w3.org/2000/svg" + const svgEl = document.createElementNS(svgNS, "svg") + const pathEl = document.createElementNS(svgNS, "path") + + // SVG size + svgEl.setAttribute("width", width) + svgEl.setAttribute("height", height) + svgEl.setAttribute("viewBox", "0 0 100 100") + svgEl.setAttribute("preserveAspectRatio", "xMidYMid meet") + // Right-pointing triangle (Play icon) + pathEl.setAttribute("d", "M 25 15 L 90 50 L 25 85 Z") // ◄ adjust points if needed + + svgEl.appendChild(pathEl) + svgMethods(svgEl) + quill.render(svgEl) + return svgEl +} + + +/* EVENTS */ + +HTMLElement.prototype.onAppear = function(func) { + func.call(this); + return this; +}; + +HTMLElement.prototype.onClick = function(func) { + const onMouseDown = (e) => func.call(this, false, e); + const onMouseUp = (e) => func.call(this, true, e); + this._storeListener("mousedown", onMouseDown); + this._storeListener("mouseup", onMouseUp); + return this; +}; + +HTMLElement.prototype.onMouseDown = function(func) { + this._storeListener("mousedown", func); + return this; +}; + +HTMLElement.prototype.onMouseUp = function(func) { + this._storeListener("mouseup", func); + return this; +}; + +HTMLElement.prototype.onRightClick = function(func) { + this._storeListener("contextmenu", func); + return this; +}; + +HTMLElement.prototype.onHover = function(cb) { + const onEnter = (e) => cb.call(this, true, e); + const onLeave = (e) => cb.call(this, false, e); + this._storeListener("mouseover", onEnter); + this._storeListener("mouseleave", onLeave); + return this; +}; + +HTMLElement.prototype.onFocus = function(cb) { + if (!this.matches('input, textarea, select, button')) { + throw new Error("Can't put focus event on non-form element!"); + } + const onFocus = () => cb.call(this, true); + const onBlur = () => cb.call(this, false); + this._storeListener("focus", onFocus); + this._storeListener("blur", onBlur); + return this; +}; + +HTMLElement.prototype.onKeyDown = function(cb) { + this._storeListener("keydown", cb); + return this; +}; + +HTMLElement.prototype.onInput = function(cb) { + if(!this.matches('input, textarea, [contenteditable=""], [contenteditable="true"]')) + throw new Error("Can't put input event on non-input element!") + this._storeListener("input", cb); + return this; +}; + +HTMLElement.prototype.onChange = function(cb) { + if(!this.matches('input, textarea, [contenteditable=""], [contenteditable="true"]')) + throw new Error("Can't put input event on non-input element!") + this._storeListener("change", cb); + return this; +}; + + +HTMLElement.prototype.onSubmit = function(cb) { + if(!this.matches('form')) + throw new Error("Can't put form event on non-form element!") + this._storeListener("submit", cb); + return this; +}; + +HTMLElement.prototype.onTouch = function(cb) { + const onStart = () => cb.call(this, true); + const onEnd = () => cb.call(this, false); + const onCancel = () => cb.call(this, null); + this._storeListener("touchstart", onStart); + this._storeListener("touchend", onEnd); + this._storeListener("touchcancel", onCancel); + return this; +}; + +HTMLElement.prototype.onTap = function(cb) { + this._storeListener("touchend", cb); + return this; +}; + +/* WHY THIS LISTENER IS THE WAY IT IS: +- 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. +*/ +navigateListeners = [] +window.addEventListener("navigate", () => { + for(entry of navigateListeners) { + entry.el.dispatchEvent(new CustomEvent("navigate")) + } +}) +HTMLElement.prototype.onNavigate = function(cb) { + this._storeListener("navigate", cb); + + let found = false + let elementIndex = Array.from(this.parentNode.children).indexOf(this) + for(entry of navigateListeners) { + if( + entry.cb.toString() === cb.toString() + && entry.index === elementIndex + && this.nodeName === entry.el.nodeName + ) { + found = true + break; + } + } + if(found === false) { + navigateListeners.push({el: this, cb: cb, index: elementIndex}) + } + + return this; +}; + +/* +Same principle applies +*/ +queryListeners = [] +HTMLElement.prototype.onQueryChanged = function(cb) { + this._storeListener("query-changed", cb); + + let found = false + for(entry of queryListeners) { + if(entry.cb.toString() === cb.toString() && + this.nodeName === entry.el.nodeName) { + found = true + break; + } + } + if(found === false) { + queryListeners.push({el: this, cb: cb}) + } + + return this; +}; +window.addEventListener("query-changed", () => { + for(entry of queryListeners) { + entry.el.dispatchEvent(new CustomEvent("query-changed")) + } +}) + +HTMLElement.prototype.onEvent = function(name, cb) { + window._storeListener(window, name, cb); + return this; +}; + +HTMLElement.prototype._storeListener = function(type, handler, options) { + window._storeListener(this, type, handler, options) +} + +window.__listeners = [] + +function _storeListener(target, type, handler, options) { + if (!target.__listeners) target.__listeners = []; + + const optionsString = JSON.stringify(options); + + const index = target.__listeners.findIndex(listener => + listener.type === type && + listener.handler.toString() === handler.toString() && + JSON.stringify(listener.options) === optionsString + ); + + if (index === -1) { // Listener is new + target.addEventListener(type, handler, options); + target.__listeners.push({ type, handler, options }); + } else { // Listener is a duplicate, can be replaced + const old = target.__listeners[index]; + target.removeEventListener(old.type, old.handler, old.options); + + // Replace with the new one + target.addEventListener(type, handler, options); + target.__listeners[index] = { type, handler, options }; + } +} + +HTMLElement.prototype.removeAllListeners = function() { + if (!this.__listeners) return; + for (const { type, handler, options } of this.__listeners) { + this.removeEventListener(type, handler, options); + } + this.__listeners = []; + return this; +}; + +/* ATTRIBUTES */ + +HTMLElement.prototype.attr = function(attributes) { + if ( + typeof attributes !== "object" || + attributes === null || + Array.isArray(attributes) + ) { + throw new TypeError("attr() expects an object with key-value pairs"); + } + + for (const [key, value] of Object.entries(attributes)) { + this.setAttribute(key, value); + } + return this; +}; diff --git a/ui/_/code/shared.css b/ui/_/code/shared.css new file mode 100644 index 0000000..fa0b3a9 --- /dev/null +++ b/ui/_/code/shared.css @@ -0,0 +1,111 @@ +:root { + --main: var(--brown); + --accent: var(--gold); + + --tan: #FFDFB4; + --gold: #F2B36F; + --divider: #bb7c36; + --green: #0857265c; + --red: #BC1C02; + --brown: #812A18; + --darkbrown: #3f0808; + + --accent2: var(--green); +} + +@media (prefers-color-scheme: dark) { +:root { + --main: var(--brown); + --accent: var(--gold); + --accent2: var(--gold); +} +} + +@font-face { + font-family: 'Canterbury'; + src: url('/_/fonts/Canterbury/Canterbury.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'Bona Nova'; + src: url('/_/fonts/BonaNova/BonaNova-Regular.woff') format('truetype'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'Bona Nova'; + src: url('/_/fonts/BonaNova/BonaNova-Bold.woff') format('truetype'); + font-weight: bold; + font-style: normal; +} + +body { + margin: 0px; + font-family: 'Bona Nova', sans-serif; + font-size: 16px; + background-color: var(--main); + color: var(--accent); +} + +@media (max-width: 480px) { + body, html{ + overflow-x: hidden; + } +} + +#title { + padding: 5px 10px; + font-size: 1.7rem; + position: fixed; top: 4.5vh; left: 6vw; + cursor: pointer; z-index: 1; +} + +a { + cursor: default; + text-decoration: none; + text-underline-offset: 5px; + transition: background .02s, color .2s; + user-select: none; + color: var(--accent); + display: inline-block; /* makes background and padding behave */ + padding: 0.2em 0.5em; /* adds breathing room */ +} + +a:hover { + text-decoration: none; + background: var(--green); + color: var(--tan); +} + +a:active { + background: var(--red); /* background color works now */ +} + +button { + background-color: transparent; + color: var(--accent); + padding: 0.5em; + box-shadow: none; + border: 1px solid var(--accent); + border-radius: 0.3em; +} + +input { + background-color: transparent; + border: 1px solid var(--accent2); + padding-left: 1em; + padding-top: 0.5em; + padding-bottom: 0.5em; + border-radius: 0.3em; +} + +input::placeholder { + color: var(--accent) +} + +input:focus { + outline: 1px solid var(--red); +} diff --git a/ui/_/code/ws/Connection.js b/ui/_/code/ws/Connection.js new file mode 100644 index 0000000..7d1d52c --- /dev/null +++ b/ui/_/code/ws/Connection.js @@ -0,0 +1,62 @@ +class Connection { + connectionTries = 0 + ws; + linkCreated; + wsStatus; + + constructor(receiveCB) { + this.init() + this.receiveCB = receiveCB + } + + init() { + if(window.location.hostname.includes("local")) { + this.ws = new WebSocket("ws://" + window.location.host) + } else { + this.ws = new WebSocket("wss://" + window.location.hostname + window.location.pathname) + } + this.ws.addEventListener('open', () => { + this.connectionTries = 0 + console.log("Websocket connection established."); + this.ws.addEventListener('message', this.receiveCB) + }); + this.ws.addEventListener("close", () => { + this.checkOpen(); + console.log('Websocket Closed') + }) + } + + async checkOpen() { + if (this.ws.readyState === WebSocket.OPEN) { + return true + } else { + await this.sleep(this.connectionTries < 20 ? 5000 : 60000) + this.connectionTries++ + console.log('Reestablishing connection') + this.init() + } + } + + sleep = (time) => { + return new Promise(resolve => { + setTimeout(resolve, time); + }); + } + + send = (msg) => { + console.log("sending") + if (this.ws.readyState === WebSocket.OPEN) { + this.ws.send(msg); + } + else if(this.connectionTries === 0) { + setTimeout(() => { + this.send(msg) + }, 100) + } + else { + console.error('No websocket connection: Cannot send message'); + } + } +} + +export default Connection \ No newline at end of file diff --git a/ui/_/code/ws/Socket.js b/ui/_/code/ws/Socket.js new file mode 100644 index 0000000..49357ef --- /dev/null +++ b/ui/_/code/ws/Socket.js @@ -0,0 +1,45 @@ +import Connection from "./Connection.js"; + +export default class Socket { + connection; + disabled = true; + requestID = 1; + pending = new Map(); + + constructor() { + this.connection = new Connection(this.receive); + } + + isOpen() { + if(this.connection.checkOpen()) { + return true; + } else { + return false; + } + } + + send(msg) { + return new Promise(resolve => { + const id = (++this.requestID).toString(); + this.pending.set(id, resolve); + this.connection.send(JSON.stringify({ id, ...msg })); + }); + } + + receive = (event) => { + const msg = JSON.parse(event.data); + if (msg.id && this.pending.has(msg.id)) { + this.pending.get(msg.id)(msg); + this.pending.delete(msg.id); + return; + } else { + this.onBroadcast(msg) + } + } + + onBroadcast(msg) { + window.dispatchEvent(new CustomEvent(msg.event, { + detail: msg.msg + })); + } +} \ No newline at end of file diff --git a/ui/_/code/zod.js b/ui/_/code/zod.js new file mode 100644 index 0000000..a4e9e54 --- /dev/null +++ b/ui/_/code/zod.js @@ -0,0 +1,8 @@ +/** + * Minified by jsDelivr using Terser v5.39.0. + * Original file: /npm/zod@3.24.4/lib/index.umd.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Zod={})}(this,(function(e){"use strict";var t;e.util=void 0,(t=e.util||(e.util={})).assertEqual=e=>e,t.assertIs=function(e){},t.assertNever=function(e){throw new Error},t.arrayToEnum=e=>{const t={};for(const a of e)t[a]=a;return t},t.getValidEnumValues=e=>{const a=t.objectKeys(e).filter((t=>"number"!=typeof e[e[t]])),s={};for(const t of a)s[t]=e[t];return t.objectValues(s)},t.objectValues=e=>t.objectKeys(e).map((function(t){return e[t]})),t.objectKeys="function"==typeof Object.keys?e=>Object.keys(e):e=>{const t=[];for(const a in e)Object.prototype.hasOwnProperty.call(e,a)&&t.push(a);return t},t.find=(e,t)=>{for(const a of e)if(t(a))return a},t.isInteger="function"==typeof Number.isInteger?e=>Number.isInteger(e):e=>"number"==typeof e&&isFinite(e)&&Math.floor(e)===e,t.joinValues=function(e,t=" | "){return e.map((e=>"string"==typeof e?`'${e}'`:e)).join(t)},t.jsonStringifyReplacer=(e,t)=>"bigint"==typeof t?t.toString():t,e.objectUtil=void 0,(e.objectUtil||(e.objectUtil={})).mergeShapes=(e,t)=>({...e,...t});const a=e.util.arrayToEnum(["string","nan","number","integer","float","boolean","date","bigint","symbol","function","undefined","null","array","object","unknown","promise","void","never","map","set"]),s=e=>{switch(typeof e){case"undefined":return a.undefined;case"string":return a.string;case"number":return isNaN(e)?a.nan:a.number;case"boolean":return a.boolean;case"function":return a.function;case"bigint":return a.bigint;case"symbol":return a.symbol;case"object":return Array.isArray(e)?a.array:null===e?a.null:e.then&&"function"==typeof e.then&&e.catch&&"function"==typeof e.catch?a.promise:"undefined"!=typeof Map&&e instanceof Map?a.map:"undefined"!=typeof Set&&e instanceof Set?a.set:"undefined"!=typeof Date&&e instanceof Date?a.date:a.object;default:return a.unknown}},r=e.util.arrayToEnum(["invalid_type","invalid_literal","custom","invalid_union","invalid_union_discriminator","invalid_enum_value","unrecognized_keys","invalid_arguments","invalid_return_type","invalid_date","invalid_string","too_small","too_big","invalid_intersection_types","not_multiple_of","not_finite"]),n=e=>JSON.stringify(e,null,2).replace(/"([^"]+)":/g,"$1:");class i extends Error{get errors(){return this.issues}constructor(e){super(),this.issues=[],this.addIssue=e=>{this.issues=[...this.issues,e]},this.addIssues=(e=[])=>{this.issues=[...this.issues,...e]};const t=new.target.prototype;Object.setPrototypeOf?Object.setPrototypeOf(this,t):this.__proto__=t,this.name="ZodError",this.issues=e}format(e){const t=e||function(e){return e.message},a={_errors:[]},s=e=>{for(const r of e.issues)if("invalid_union"===r.code)r.unionErrors.map(s);else if("invalid_return_type"===r.code)s(r.returnTypeError);else if("invalid_arguments"===r.code)s(r.argumentsError);else if(0===r.path.length)a._errors.push(t(r));else{let e=a,s=0;for(;se.message){const t={},a=[];for(const s of this.issues)s.path.length>0?(t[s.path[0]]=t[s.path[0]]||[],t[s.path[0]].push(e(s))):a.push(e(s));return{formErrors:a,fieldErrors:t}}get formErrors(){return this.flatten()}}i.create=e=>new i(e);const o=(t,s)=>{let n;switch(t.code){case r.invalid_type:n=t.received===a.undefined?"Required":`Expected ${t.expected}, received ${t.received}`;break;case r.invalid_literal:n=`Invalid literal value, expected ${JSON.stringify(t.expected,e.util.jsonStringifyReplacer)}`;break;case r.unrecognized_keys:n=`Unrecognized key(s) in object: ${e.util.joinValues(t.keys,", ")}`;break;case r.invalid_union:n="Invalid input";break;case r.invalid_union_discriminator:n=`Invalid discriminator value. Expected ${e.util.joinValues(t.options)}`;break;case r.invalid_enum_value:n=`Invalid enum value. Expected ${e.util.joinValues(t.options)}, received '${t.received}'`;break;case r.invalid_arguments:n="Invalid function arguments";break;case r.invalid_return_type:n="Invalid function return type";break;case r.invalid_date:n="Invalid date";break;case r.invalid_string:"object"==typeof t.validation?"includes"in t.validation?(n=`Invalid input: must include "${t.validation.includes}"`,"number"==typeof t.validation.position&&(n=`${n} at one or more positions greater than or equal to ${t.validation.position}`)):"startsWith"in t.validation?n=`Invalid input: must start with "${t.validation.startsWith}"`:"endsWith"in t.validation?n=`Invalid input: must end with "${t.validation.endsWith}"`:e.util.assertNever(t.validation):n="regex"!==t.validation?`Invalid ${t.validation}`:"Invalid";break;case r.too_small:n="array"===t.type?`Array must contain ${t.exact?"exactly":t.inclusive?"at least":"more than"} ${t.minimum} element(s)`:"string"===t.type?`String must contain ${t.exact?"exactly":t.inclusive?"at least":"over"} ${t.minimum} character(s)`:"number"===t.type?`Number must be ${t.exact?"exactly equal to ":t.inclusive?"greater than or equal to ":"greater than "}${t.minimum}`:"date"===t.type?`Date must be ${t.exact?"exactly equal to ":t.inclusive?"greater than or equal to ":"greater than "}${new Date(Number(t.minimum))}`:"Invalid input";break;case r.too_big:n="array"===t.type?`Array must contain ${t.exact?"exactly":t.inclusive?"at most":"less than"} ${t.maximum} element(s)`:"string"===t.type?`String must contain ${t.exact?"exactly":t.inclusive?"at most":"under"} ${t.maximum} character(s)`:"number"===t.type?`Number must be ${t.exact?"exactly":t.inclusive?"less than or equal to":"less than"} ${t.maximum}`:"bigint"===t.type?`BigInt must be ${t.exact?"exactly":t.inclusive?"less than or equal to":"less than"} ${t.maximum}`:"date"===t.type?`Date must be ${t.exact?"exactly":t.inclusive?"smaller than or equal to":"smaller than"} ${new Date(Number(t.maximum))}`:"Invalid input";break;case r.custom:n="Invalid input";break;case r.invalid_intersection_types:n="Intersection results could not be merged";break;case r.not_multiple_of:n=`Number must be a multiple of ${t.multipleOf}`;break;case r.not_finite:n="Number must be finite";break;default:n=s.defaultError,e.util.assertNever(t)}return{message:n}};let d=o;function u(e){d=e}function c(){return d}const l=e=>{const{data:t,path:a,errorMaps:s,issueData:r}=e,n=[...a,...r.path||[]],i={...r,path:n};if(void 0!==r.message)return{...r,path:n,message:r.message};let o="";const d=s.filter((e=>!!e)).slice().reverse();for(const e of d)o=e(i,{data:t,defaultError:o}).message;return{...r,path:n,message:o}},p=[];function h(e,t){const a=c(),s=l({issueData:t,data:e.data,path:e.path,errorMaps:[e.common.contextualErrorMap,e.schemaErrorMap,a,a===o?void 0:o].filter((e=>!!e))});e.common.issues.push(s)}class m{constructor(){this.value="valid"}dirty(){"valid"===this.value&&(this.value="dirty")}abort(){"aborted"!==this.value&&(this.value="aborted")}static mergeArray(e,t){const a=[];for(const s of t){if("aborted"===s.status)return f;"dirty"===s.status&&e.dirty(),a.push(s.value)}return{status:e.value,value:a}}static async mergeObjectAsync(e,t){const a=[];for(const e of t){const t=await e.key,s=await e.value;a.push({key:t,value:s})}return m.mergeObjectSync(e,a)}static mergeObjectSync(e,t){const a={};for(const s of t){const{key:t,value:r}=s;if("aborted"===t.status)return f;if("aborted"===r.status)return f;"dirty"===t.status&&e.dirty(),"dirty"===r.status&&e.dirty(),"__proto__"===t.value||void 0===r.value&&!s.alwaysSet||(a[t.value]=r.value)}return{status:e.value,value:a}}}const f=Object.freeze({status:"aborted"}),y=e=>({status:"dirty",value:e}),v=e=>({status:"valid",value:e}),_=e=>"aborted"===e.status,g=e=>"dirty"===e.status,b=e=>"valid"===e.status,k=e=>"undefined"!=typeof Promise&&e instanceof Promise;function x(e,t,a,s){if("a"===a&&!s)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!s:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===a?s:"a"===a?s.call(e):s?s.value:t.get(e)}function Z(e,t,a,s,r){if("m"===s)throw new TypeError("Private method is not writable");if("a"===s&&!r)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?e!==t||!r:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===s?r.call(e,a):r?r.value=a:t.set(e,a),a}var w,T,P;"function"==typeof SuppressedError&&SuppressedError,function(e){e.errToObj=e=>"string"==typeof e?{message:e}:e||{},e.toString=e=>"string"==typeof e?e:null==e?void 0:e.message}(w||(w={}));class O{constructor(e,t,a,s){this._cachedPath=[],this.parent=e,this.data=t,this._path=a,this._key=s}get path(){return this._cachedPath.length||(this._key instanceof Array?this._cachedPath.push(...this._path,...this._key):this._cachedPath.push(...this._path,this._key)),this._cachedPath}}const C=(e,t)=>{if(b(t))return{success:!0,data:t.value};if(!e.common.issues.length)throw new Error("Validation failed but no issues detected.");return{success:!1,get error(){if(this._error)return this._error;const t=new i(e.common.issues);return this._error=t,this._error}}};function N(e){if(!e)return{};const{errorMap:t,invalid_type_error:a,required_error:s,description:r}=e;if(t&&(a||s))throw new Error('Can\'t use "invalid_type_error" or "required_error" in conjunction with custom error map.');if(t)return{errorMap:t,description:r};return{errorMap:(t,r)=>{var n,i;const{message:o}=e;return"invalid_enum_value"===t.code?{message:null!=o?o:r.defaultError}:void 0===r.data?{message:null!==(n=null!=o?o:s)&&void 0!==n?n:r.defaultError}:"invalid_type"!==t.code?{message:r.defaultError}:{message:null!==(i=null!=o?o:a)&&void 0!==i?i:r.defaultError}},description:r}}class A{get description(){return this._def.description}_getType(e){return s(e.data)}_getOrReturnCtx(e,t){return t||{common:e.parent.common,data:e.data,parsedType:s(e.data),schemaErrorMap:this._def.errorMap,path:e.path,parent:e.parent}}_processInputParams(e){return{status:new m,ctx:{common:e.parent.common,data:e.data,parsedType:s(e.data),schemaErrorMap:this._def.errorMap,path:e.path,parent:e.parent}}}_parseSync(e){const t=this._parse(e);if(k(t))throw new Error("Synchronous parse encountered promise.");return t}_parseAsync(e){const t=this._parse(e);return Promise.resolve(t)}parse(e,t){const a=this.safeParse(e,t);if(a.success)return a.data;throw a.error}safeParse(e,t){var a;const r={common:{issues:[],async:null!==(a=null==t?void 0:t.async)&&void 0!==a&&a,contextualErrorMap:null==t?void 0:t.errorMap},path:(null==t?void 0:t.path)||[],schemaErrorMap:this._def.errorMap,parent:null,data:e,parsedType:s(e)},n=this._parseSync({data:e,path:r.path,parent:r});return C(r,n)}"~validate"(e){var t,a;const r={common:{issues:[],async:!!this["~standard"].async},path:[],schemaErrorMap:this._def.errorMap,parent:null,data:e,parsedType:s(e)};if(!this["~standard"].async)try{const t=this._parseSync({data:e,path:[],parent:r});return b(t)?{value:t.value}:{issues:r.common.issues}}catch(e){(null===(a=null===(t=null==e?void 0:e.message)||void 0===t?void 0:t.toLowerCase())||void 0===a?void 0:a.includes("encountered"))&&(this["~standard"].async=!0),r.common={issues:[],async:!0}}return this._parseAsync({data:e,path:[],parent:r}).then((e=>b(e)?{value:e.value}:{issues:r.common.issues}))}async parseAsync(e,t){const a=await this.safeParseAsync(e,t);if(a.success)return a.data;throw a.error}async safeParseAsync(e,t){const a={common:{issues:[],contextualErrorMap:null==t?void 0:t.errorMap,async:!0},path:(null==t?void 0:t.path)||[],schemaErrorMap:this._def.errorMap,parent:null,data:e,parsedType:s(e)},r=this._parse({data:e,path:a.path,parent:a}),n=await(k(r)?r:Promise.resolve(r));return C(a,n)}refine(e,t){const a=e=>"string"==typeof t||void 0===t?{message:t}:"function"==typeof t?t(e):t;return this._refinement(((t,s)=>{const n=e(t),i=()=>s.addIssue({code:r.custom,...a(t)});return"undefined"!=typeof Promise&&n instanceof Promise?n.then((e=>!!e||(i(),!1))):!!n||(i(),!1)}))}refinement(e,t){return this._refinement(((a,s)=>!!e(a)||(s.addIssue("function"==typeof t?t(a,s):t),!1)))}_refinement(t){return new Ae({schema:this,typeName:e.ZodFirstPartyTypeKind.ZodEffects,effect:{type:"refinement",refinement:t}})}superRefine(e){return this._refinement(e)}constructor(e){this.spa=this.safeParseAsync,this._def=e,this.parse=this.parse.bind(this),this.safeParse=this.safeParse.bind(this),this.parseAsync=this.parseAsync.bind(this),this.safeParseAsync=this.safeParseAsync.bind(this),this.spa=this.spa.bind(this),this.refine=this.refine.bind(this),this.refinement=this.refinement.bind(this),this.superRefine=this.superRefine.bind(this),this.optional=this.optional.bind(this),this.nullable=this.nullable.bind(this),this.nullish=this.nullish.bind(this),this.array=this.array.bind(this),this.promise=this.promise.bind(this),this.or=this.or.bind(this),this.and=this.and.bind(this),this.transform=this.transform.bind(this),this.brand=this.brand.bind(this),this.default=this.default.bind(this),this.catch=this.catch.bind(this),this.describe=this.describe.bind(this),this.pipe=this.pipe.bind(this),this.readonly=this.readonly.bind(this),this.isNullable=this.isNullable.bind(this),this.isOptional=this.isOptional.bind(this),this["~standard"]={version:1,vendor:"zod",validate:e=>this["~validate"](e)}}optional(){return Se.create(this,this._def)}nullable(){return je.create(this,this._def)}nullish(){return this.nullable().optional()}array(){return le.create(this)}promise(){return Ne.create(this,this._def)}or(e){return me.create([this,e],this._def)}and(e){return _e.create(this,e,this._def)}transform(t){return new Ae({...N(this._def),schema:this,typeName:e.ZodFirstPartyTypeKind.ZodEffects,effect:{type:"transform",transform:t}})}default(t){const a="function"==typeof t?t:()=>t;return new Ee({...N(this._def),innerType:this,defaultValue:a,typeName:e.ZodFirstPartyTypeKind.ZodDefault})}brand(){return new Ke({typeName:e.ZodFirstPartyTypeKind.ZodBranded,type:this,...N(this._def)})}catch(t){const a="function"==typeof t?t:()=>t;return new Fe({...N(this._def),innerType:this,catchValue:a,typeName:e.ZodFirstPartyTypeKind.ZodCatch})}describe(e){return new(0,this.constructor)({...this._def,description:e})}pipe(e){return $e.create(this,e)}readonly(){return Me.create(this)}isOptional(){return this.safeParse(void 0).success}isNullable(){return this.safeParse(null).success}}const S=/^c[^\s-]{8,}$/i,j=/^[0-9a-z]+$/,E=/^[0-9A-HJKMNP-TV-Z]{26}$/i,F=/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/i,I=/^[a-z0-9_-]{21}$/i,R=/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/,K=/^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/,$=/^(?!\.)(?!.*\.\.)([A-Z0-9_'+\-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i;let M;const D=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,L=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/(3[0-2]|[12]?[0-9])$/,z=/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/,V=/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,U=/^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/,B=/^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/,W="((\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\\d|3[01])|(0[469]|11)-(0[1-9]|[12]\\d|30)|(02)-(0[1-9]|1\\d|2[0-8])))",q=new RegExp(`^${W}$`);function J(e){let t="[0-5]\\d";e.precision?t=`${t}\\.\\d{${e.precision}}`:null==e.precision&&(t=`${t}(\\.\\d+)?`);return`([01]\\d|2[0-3]):[0-5]\\d(:${t})${e.precision?"+":"?"}`}function Y(e){let t=`${W}T${J(e)}`;const a=[];return a.push(e.local?"Z?":"Z"),e.offset&&a.push("([+-]\\d{2}:?\\d{2})"),t=`${t}(${a.join("|")})`,new RegExp(`^${t}$`)}function H(e,t){if(!R.test(e))return!1;try{const[a]=e.split("."),s=a.replace(/-/g,"+").replace(/_/g,"/").padEnd(a.length+(4-a.length%4)%4,"="),r=JSON.parse(atob(s));return"object"==typeof r&&null!==r&&(!(!r.typ||!r.alg)&&(!t||r.alg===t))}catch(e){return!1}}function G(e,t){return!("v4"!==t&&t||!L.test(e))||!("v6"!==t&&t||!V.test(e))}class X extends A{_parse(t){this._def.coerce&&(t.data=String(t.data));if(this._getType(t)!==a.string){const e=this._getOrReturnCtx(t);return h(e,{code:r.invalid_type,expected:a.string,received:e.parsedType}),f}const s=new m;let n;for(const a of this._def.checks)if("min"===a.kind)t.data.lengtha.value&&(n=this._getOrReturnCtx(t,n),h(n,{code:r.too_big,maximum:a.value,type:"string",inclusive:!0,exact:!1,message:a.message}),s.dirty());else if("length"===a.kind){const e=t.data.length>a.value,i=t.data.lengthe.test(t)),{validation:t,code:r.invalid_string,...w.errToObj(a)})}_addCheck(e){return new X({...this._def,checks:[...this._def.checks,e]})}email(e){return this._addCheck({kind:"email",...w.errToObj(e)})}url(e){return this._addCheck({kind:"url",...w.errToObj(e)})}emoji(e){return this._addCheck({kind:"emoji",...w.errToObj(e)})}uuid(e){return this._addCheck({kind:"uuid",...w.errToObj(e)})}nanoid(e){return this._addCheck({kind:"nanoid",...w.errToObj(e)})}cuid(e){return this._addCheck({kind:"cuid",...w.errToObj(e)})}cuid2(e){return this._addCheck({kind:"cuid2",...w.errToObj(e)})}ulid(e){return this._addCheck({kind:"ulid",...w.errToObj(e)})}base64(e){return this._addCheck({kind:"base64",...w.errToObj(e)})}base64url(e){return this._addCheck({kind:"base64url",...w.errToObj(e)})}jwt(e){return this._addCheck({kind:"jwt",...w.errToObj(e)})}ip(e){return this._addCheck({kind:"ip",...w.errToObj(e)})}cidr(e){return this._addCheck({kind:"cidr",...w.errToObj(e)})}datetime(e){var t,a;return"string"==typeof e?this._addCheck({kind:"datetime",precision:null,offset:!1,local:!1,message:e}):this._addCheck({kind:"datetime",precision:void 0===(null==e?void 0:e.precision)?null:null==e?void 0:e.precision,offset:null!==(t=null==e?void 0:e.offset)&&void 0!==t&&t,local:null!==(a=null==e?void 0:e.local)&&void 0!==a&&a,...w.errToObj(null==e?void 0:e.message)})}date(e){return this._addCheck({kind:"date",message:e})}time(e){return"string"==typeof e?this._addCheck({kind:"time",precision:null,message:e}):this._addCheck({kind:"time",precision:void 0===(null==e?void 0:e.precision)?null:null==e?void 0:e.precision,...w.errToObj(null==e?void 0:e.message)})}duration(e){return this._addCheck({kind:"duration",...w.errToObj(e)})}regex(e,t){return this._addCheck({kind:"regex",regex:e,...w.errToObj(t)})}includes(e,t){return this._addCheck({kind:"includes",value:e,position:null==t?void 0:t.position,...w.errToObj(null==t?void 0:t.message)})}startsWith(e,t){return this._addCheck({kind:"startsWith",value:e,...w.errToObj(t)})}endsWith(e,t){return this._addCheck({kind:"endsWith",value:e,...w.errToObj(t)})}min(e,t){return this._addCheck({kind:"min",value:e,...w.errToObj(t)})}max(e,t){return this._addCheck({kind:"max",value:e,...w.errToObj(t)})}length(e,t){return this._addCheck({kind:"length",value:e,...w.errToObj(t)})}nonempty(e){return this.min(1,w.errToObj(e))}trim(){return new X({...this._def,checks:[...this._def.checks,{kind:"trim"}]})}toLowerCase(){return new X({...this._def,checks:[...this._def.checks,{kind:"toLowerCase"}]})}toUpperCase(){return new X({...this._def,checks:[...this._def.checks,{kind:"toUpperCase"}]})}get isDatetime(){return!!this._def.checks.find((e=>"datetime"===e.kind))}get isDate(){return!!this._def.checks.find((e=>"date"===e.kind))}get isTime(){return!!this._def.checks.find((e=>"time"===e.kind))}get isDuration(){return!!this._def.checks.find((e=>"duration"===e.kind))}get isEmail(){return!!this._def.checks.find((e=>"email"===e.kind))}get isURL(){return!!this._def.checks.find((e=>"url"===e.kind))}get isEmoji(){return!!this._def.checks.find((e=>"emoji"===e.kind))}get isUUID(){return!!this._def.checks.find((e=>"uuid"===e.kind))}get isNANOID(){return!!this._def.checks.find((e=>"nanoid"===e.kind))}get isCUID(){return!!this._def.checks.find((e=>"cuid"===e.kind))}get isCUID2(){return!!this._def.checks.find((e=>"cuid2"===e.kind))}get isULID(){return!!this._def.checks.find((e=>"ulid"===e.kind))}get isIP(){return!!this._def.checks.find((e=>"ip"===e.kind))}get isCIDR(){return!!this._def.checks.find((e=>"cidr"===e.kind))}get isBase64(){return!!this._def.checks.find((e=>"base64"===e.kind))}get isBase64url(){return!!this._def.checks.find((e=>"base64url"===e.kind))}get minLength(){let e=null;for(const t of this._def.checks)"min"===t.kind&&(null===e||t.value>e)&&(e=t.value);return e}get maxLength(){let e=null;for(const t of this._def.checks)"max"===t.kind&&(null===e||t.values?a:s;return parseInt(e.toFixed(r).replace(".",""))%parseInt(t.toFixed(r).replace(".",""))/Math.pow(10,r)}X.create=t=>{var a;return new X({checks:[],typeName:e.ZodFirstPartyTypeKind.ZodString,coerce:null!==(a=null==t?void 0:t.coerce)&&void 0!==a&&a,...N(t)})};class ee extends A{constructor(){super(...arguments),this.min=this.gte,this.max=this.lte,this.step=this.multipleOf}_parse(t){this._def.coerce&&(t.data=Number(t.data));if(this._getType(t)!==a.number){const e=this._getOrReturnCtx(t);return h(e,{code:r.invalid_type,expected:a.number,received:e.parsedType}),f}let s;const n=new m;for(const a of this._def.checks)if("int"===a.kind)e.util.isInteger(t.data)||(s=this._getOrReturnCtx(t,s),h(s,{code:r.invalid_type,expected:"integer",received:"float",message:a.message}),n.dirty());else if("min"===a.kind){(a.inclusive?t.dataa.value:t.data>=a.value)&&(s=this._getOrReturnCtx(t,s),h(s,{code:r.too_big,maximum:a.value,type:"number",inclusive:a.inclusive,exact:!1,message:a.message}),n.dirty())}else"multipleOf"===a.kind?0!==Q(t.data,a.value)&&(s=this._getOrReturnCtx(t,s),h(s,{code:r.not_multiple_of,multipleOf:a.value,message:a.message}),n.dirty()):"finite"===a.kind?Number.isFinite(t.data)||(s=this._getOrReturnCtx(t,s),h(s,{code:r.not_finite,message:a.message}),n.dirty()):e.util.assertNever(a);return{status:n.value,value:t.data}}gte(e,t){return this.setLimit("min",e,!0,w.toString(t))}gt(e,t){return this.setLimit("min",e,!1,w.toString(t))}lte(e,t){return this.setLimit("max",e,!0,w.toString(t))}lt(e,t){return this.setLimit("max",e,!1,w.toString(t))}setLimit(e,t,a,s){return new ee({...this._def,checks:[...this._def.checks,{kind:e,value:t,inclusive:a,message:w.toString(s)}]})}_addCheck(e){return new ee({...this._def,checks:[...this._def.checks,e]})}int(e){return this._addCheck({kind:"int",message:w.toString(e)})}positive(e){return this._addCheck({kind:"min",value:0,inclusive:!1,message:w.toString(e)})}negative(e){return this._addCheck({kind:"max",value:0,inclusive:!1,message:w.toString(e)})}nonpositive(e){return this._addCheck({kind:"max",value:0,inclusive:!0,message:w.toString(e)})}nonnegative(e){return this._addCheck({kind:"min",value:0,inclusive:!0,message:w.toString(e)})}multipleOf(e,t){return this._addCheck({kind:"multipleOf",value:e,message:w.toString(t)})}finite(e){return this._addCheck({kind:"finite",message:w.toString(e)})}safe(e){return this._addCheck({kind:"min",inclusive:!0,value:Number.MIN_SAFE_INTEGER,message:w.toString(e)})._addCheck({kind:"max",inclusive:!0,value:Number.MAX_SAFE_INTEGER,message:w.toString(e)})}get minValue(){let e=null;for(const t of this._def.checks)"min"===t.kind&&(null===e||t.value>e)&&(e=t.value);return e}get maxValue(){let e=null;for(const t of this._def.checks)"max"===t.kind&&(null===e||t.value"int"===t.kind||"multipleOf"===t.kind&&e.util.isInteger(t.value)))}get isFinite(){let e=null,t=null;for(const a of this._def.checks){if("finite"===a.kind||"int"===a.kind||"multipleOf"===a.kind)return!0;"min"===a.kind?(null===t||a.value>t)&&(t=a.value):"max"===a.kind&&(null===e||a.valuenew ee({checks:[],typeName:e.ZodFirstPartyTypeKind.ZodNumber,coerce:(null==t?void 0:t.coerce)||!1,...N(t)});class te extends A{constructor(){super(...arguments),this.min=this.gte,this.max=this.lte}_parse(t){if(this._def.coerce)try{t.data=BigInt(t.data)}catch(e){return this._getInvalidInput(t)}if(this._getType(t)!==a.bigint)return this._getInvalidInput(t);let s;const n=new m;for(const a of this._def.checks)if("min"===a.kind){(a.inclusive?t.dataa.value:t.data>=a.value)&&(s=this._getOrReturnCtx(t,s),h(s,{code:r.too_big,type:"bigint",maximum:a.value,inclusive:a.inclusive,message:a.message}),n.dirty())}else"multipleOf"===a.kind?t.data%a.value!==BigInt(0)&&(s=this._getOrReturnCtx(t,s),h(s,{code:r.not_multiple_of,multipleOf:a.value,message:a.message}),n.dirty()):e.util.assertNever(a);return{status:n.value,value:t.data}}_getInvalidInput(e){const t=this._getOrReturnCtx(e);return h(t,{code:r.invalid_type,expected:a.bigint,received:t.parsedType}),f}gte(e,t){return this.setLimit("min",e,!0,w.toString(t))}gt(e,t){return this.setLimit("min",e,!1,w.toString(t))}lte(e,t){return this.setLimit("max",e,!0,w.toString(t))}lt(e,t){return this.setLimit("max",e,!1,w.toString(t))}setLimit(e,t,a,s){return new te({...this._def,checks:[...this._def.checks,{kind:e,value:t,inclusive:a,message:w.toString(s)}]})}_addCheck(e){return new te({...this._def,checks:[...this._def.checks,e]})}positive(e){return this._addCheck({kind:"min",value:BigInt(0),inclusive:!1,message:w.toString(e)})}negative(e){return this._addCheck({kind:"max",value:BigInt(0),inclusive:!1,message:w.toString(e)})}nonpositive(e){return this._addCheck({kind:"max",value:BigInt(0),inclusive:!0,message:w.toString(e)})}nonnegative(e){return this._addCheck({kind:"min",value:BigInt(0),inclusive:!0,message:w.toString(e)})}multipleOf(e,t){return this._addCheck({kind:"multipleOf",value:e,message:w.toString(t)})}get minValue(){let e=null;for(const t of this._def.checks)"min"===t.kind&&(null===e||t.value>e)&&(e=t.value);return e}get maxValue(){let e=null;for(const t of this._def.checks)"max"===t.kind&&(null===e||t.value{var a;return new te({checks:[],typeName:e.ZodFirstPartyTypeKind.ZodBigInt,coerce:null!==(a=null==t?void 0:t.coerce)&&void 0!==a&&a,...N(t)})};class ae extends A{_parse(e){this._def.coerce&&(e.data=Boolean(e.data));if(this._getType(e)!==a.boolean){const t=this._getOrReturnCtx(e);return h(t,{code:r.invalid_type,expected:a.boolean,received:t.parsedType}),f}return v(e.data)}}ae.create=t=>new ae({typeName:e.ZodFirstPartyTypeKind.ZodBoolean,coerce:(null==t?void 0:t.coerce)||!1,...N(t)});class se extends A{_parse(t){this._def.coerce&&(t.data=new Date(t.data));if(this._getType(t)!==a.date){const e=this._getOrReturnCtx(t);return h(e,{code:r.invalid_type,expected:a.date,received:e.parsedType}),f}if(isNaN(t.data.getTime())){return h(this._getOrReturnCtx(t),{code:r.invalid_date}),f}const s=new m;let n;for(const a of this._def.checks)"min"===a.kind?t.data.getTime()a.value&&(n=this._getOrReturnCtx(t,n),h(n,{code:r.too_big,message:a.message,inclusive:!0,exact:!1,maximum:a.value,type:"date"}),s.dirty()):e.util.assertNever(a);return{status:s.value,value:new Date(t.data.getTime())}}_addCheck(e){return new se({...this._def,checks:[...this._def.checks,e]})}min(e,t){return this._addCheck({kind:"min",value:e.getTime(),message:w.toString(t)})}max(e,t){return this._addCheck({kind:"max",value:e.getTime(),message:w.toString(t)})}get minDate(){let e=null;for(const t of this._def.checks)"min"===t.kind&&(null===e||t.value>e)&&(e=t.value);return null!=e?new Date(e):null}get maxDate(){let e=null;for(const t of this._def.checks)"max"===t.kind&&(null===e||t.valuenew se({checks:[],coerce:(null==t?void 0:t.coerce)||!1,typeName:e.ZodFirstPartyTypeKind.ZodDate,...N(t)});class re extends A{_parse(e){if(this._getType(e)!==a.symbol){const t=this._getOrReturnCtx(e);return h(t,{code:r.invalid_type,expected:a.symbol,received:t.parsedType}),f}return v(e.data)}}re.create=t=>new re({typeName:e.ZodFirstPartyTypeKind.ZodSymbol,...N(t)});class ne extends A{_parse(e){if(this._getType(e)!==a.undefined){const t=this._getOrReturnCtx(e);return h(t,{code:r.invalid_type,expected:a.undefined,received:t.parsedType}),f}return v(e.data)}}ne.create=t=>new ne({typeName:e.ZodFirstPartyTypeKind.ZodUndefined,...N(t)});class ie extends A{_parse(e){if(this._getType(e)!==a.null){const t=this._getOrReturnCtx(e);return h(t,{code:r.invalid_type,expected:a.null,received:t.parsedType}),f}return v(e.data)}}ie.create=t=>new ie({typeName:e.ZodFirstPartyTypeKind.ZodNull,...N(t)});class oe extends A{constructor(){super(...arguments),this._any=!0}_parse(e){return v(e.data)}}oe.create=t=>new oe({typeName:e.ZodFirstPartyTypeKind.ZodAny,...N(t)});class de extends A{constructor(){super(...arguments),this._unknown=!0}_parse(e){return v(e.data)}}de.create=t=>new de({typeName:e.ZodFirstPartyTypeKind.ZodUnknown,...N(t)});class ue extends A{_parse(e){const t=this._getOrReturnCtx(e);return h(t,{code:r.invalid_type,expected:a.never,received:t.parsedType}),f}}ue.create=t=>new ue({typeName:e.ZodFirstPartyTypeKind.ZodNever,...N(t)});class ce extends A{_parse(e){if(this._getType(e)!==a.undefined){const t=this._getOrReturnCtx(e);return h(t,{code:r.invalid_type,expected:a.void,received:t.parsedType}),f}return v(e.data)}}ce.create=t=>new ce({typeName:e.ZodFirstPartyTypeKind.ZodVoid,...N(t)});class le extends A{_parse(e){const{ctx:t,status:s}=this._processInputParams(e),n=this._def;if(t.parsedType!==a.array)return h(t,{code:r.invalid_type,expected:a.array,received:t.parsedType}),f;if(null!==n.exactLength){const e=t.data.length>n.exactLength.value,a=t.data.lengthn.maxLength.value&&(h(t,{code:r.too_big,maximum:n.maxLength.value,type:"array",inclusive:!0,exact:!1,message:n.maxLength.message}),s.dirty()),t.common.async)return Promise.all([...t.data].map(((e,a)=>n.type._parseAsync(new O(t,e,t.path,a))))).then((e=>m.mergeArray(s,e)));const i=[...t.data].map(((e,a)=>n.type._parseSync(new O(t,e,t.path,a))));return m.mergeArray(s,i)}get element(){return this._def.type}min(e,t){return new le({...this._def,minLength:{value:e,message:w.toString(t)}})}max(e,t){return new le({...this._def,maxLength:{value:e,message:w.toString(t)}})}length(e,t){return new le({...this._def,exactLength:{value:e,message:w.toString(t)}})}nonempty(e){return this.min(1,e)}}function pe(e){if(e instanceof he){const t={};for(const a in e.shape){const s=e.shape[a];t[a]=Se.create(pe(s))}return new he({...e._def,shape:()=>t})}return e instanceof le?new le({...e._def,type:pe(e.element)}):e instanceof Se?Se.create(pe(e.unwrap())):e instanceof je?je.create(pe(e.unwrap())):e instanceof ge?ge.create(e.items.map((e=>pe(e)))):e}le.create=(t,a)=>new le({type:t,minLength:null,maxLength:null,exactLength:null,typeName:e.ZodFirstPartyTypeKind.ZodArray,...N(a)});class he extends A{constructor(){super(...arguments),this._cached=null,this.nonstrict=this.passthrough,this.augment=this.extend}_getCached(){if(null!==this._cached)return this._cached;const t=this._def.shape(),a=e.util.objectKeys(t);return this._cached={shape:t,keys:a}}_parse(e){if(this._getType(e)!==a.object){const t=this._getOrReturnCtx(e);return h(t,{code:r.invalid_type,expected:a.object,received:t.parsedType}),f}const{status:t,ctx:s}=this._processInputParams(e),{shape:n,keys:i}=this._getCached(),o=[];if(!(this._def.catchall instanceof ue&&"strip"===this._def.unknownKeys))for(const e in s.data)i.includes(e)||o.push(e);const d=[];for(const e of i){const t=n[e],a=s.data[e];d.push({key:{status:"valid",value:e},value:t._parse(new O(s,a,s.path,e)),alwaysSet:e in s.data})}if(this._def.catchall instanceof ue){const e=this._def.unknownKeys;if("passthrough"===e)for(const e of o)d.push({key:{status:"valid",value:e},value:{status:"valid",value:s.data[e]}});else if("strict"===e)o.length>0&&(h(s,{code:r.unrecognized_keys,keys:o}),t.dirty());else if("strip"!==e)throw new Error("Internal ZodObject error: invalid unknownKeys value.")}else{const e=this._def.catchall;for(const t of o){const a=s.data[t];d.push({key:{status:"valid",value:t},value:e._parse(new O(s,a,s.path,t)),alwaysSet:t in s.data})}}return s.common.async?Promise.resolve().then((async()=>{const e=[];for(const t of d){const a=await t.key,s=await t.value;e.push({key:a,value:s,alwaysSet:t.alwaysSet})}return e})).then((e=>m.mergeObjectSync(t,e))):m.mergeObjectSync(t,d)}get shape(){return this._def.shape()}strict(e){return w.errToObj,new he({...this._def,unknownKeys:"strict",...void 0!==e?{errorMap:(t,a)=>{var s,r,n,i;const o=null!==(n=null===(r=(s=this._def).errorMap)||void 0===r?void 0:r.call(s,t,a).message)&&void 0!==n?n:a.defaultError;return"unrecognized_keys"===t.code?{message:null!==(i=w.errToObj(e).message)&&void 0!==i?i:o}:{message:o}}}:{}})}strip(){return new he({...this._def,unknownKeys:"strip"})}passthrough(){return new he({...this._def,unknownKeys:"passthrough"})}extend(e){return new he({...this._def,shape:()=>({...this._def.shape(),...e})})}merge(t){return new he({unknownKeys:t._def.unknownKeys,catchall:t._def.catchall,shape:()=>({...this._def.shape(),...t._def.shape()}),typeName:e.ZodFirstPartyTypeKind.ZodObject})}setKey(e,t){return this.augment({[e]:t})}catchall(e){return new he({...this._def,catchall:e})}pick(t){const a={};return e.util.objectKeys(t).forEach((e=>{t[e]&&this.shape[e]&&(a[e]=this.shape[e])})),new he({...this._def,shape:()=>a})}omit(t){const a={};return e.util.objectKeys(this.shape).forEach((e=>{t[e]||(a[e]=this.shape[e])})),new he({...this._def,shape:()=>a})}deepPartial(){return pe(this)}partial(t){const a={};return e.util.objectKeys(this.shape).forEach((e=>{const s=this.shape[e];t&&!t[e]?a[e]=s:a[e]=s.optional()})),new he({...this._def,shape:()=>a})}required(t){const a={};return e.util.objectKeys(this.shape).forEach((e=>{if(t&&!t[e])a[e]=this.shape[e];else{let t=this.shape[e];for(;t instanceof Se;)t=t._def.innerType;a[e]=t}})),new he({...this._def,shape:()=>a})}keyof(){return Pe(e.util.objectKeys(this.shape))}}he.create=(t,a)=>new he({shape:()=>t,unknownKeys:"strip",catchall:ue.create(),typeName:e.ZodFirstPartyTypeKind.ZodObject,...N(a)}),he.strictCreate=(t,a)=>new he({shape:()=>t,unknownKeys:"strict",catchall:ue.create(),typeName:e.ZodFirstPartyTypeKind.ZodObject,...N(a)}),he.lazycreate=(t,a)=>new he({shape:t,unknownKeys:"strip",catchall:ue.create(),typeName:e.ZodFirstPartyTypeKind.ZodObject,...N(a)});class me extends A{_parse(e){const{ctx:t}=this._processInputParams(e),a=this._def.options;if(t.common.async)return Promise.all(a.map((async e=>{const a={...t,common:{...t.common,issues:[]},parent:null};return{result:await e._parseAsync({data:t.data,path:t.path,parent:a}),ctx:a}}))).then((function(e){for(const t of e)if("valid"===t.result.status)return t.result;for(const a of e)if("dirty"===a.result.status)return t.common.issues.push(...a.ctx.common.issues),a.result;const a=e.map((e=>new i(e.ctx.common.issues)));return h(t,{code:r.invalid_union,unionErrors:a}),f}));{let e;const s=[];for(const r of a){const a={...t,common:{...t.common,issues:[]},parent:null},n=r._parseSync({data:t.data,path:t.path,parent:a});if("valid"===n.status)return n;"dirty"!==n.status||e||(e={result:n,ctx:a}),a.common.issues.length&&s.push(a.common.issues)}if(e)return t.common.issues.push(...e.ctx.common.issues),e.result;const n=s.map((e=>new i(e)));return h(t,{code:r.invalid_union,unionErrors:n}),f}}get options(){return this._def.options}}me.create=(t,a)=>new me({options:t,typeName:e.ZodFirstPartyTypeKind.ZodUnion,...N(a)});const fe=t=>t instanceof we?fe(t.schema):t instanceof Ae?fe(t.innerType()):t instanceof Te?[t.value]:t instanceof Oe?t.options:t instanceof Ce?e.util.objectValues(t.enum):t instanceof Ee?fe(t._def.innerType):t instanceof ne?[void 0]:t instanceof ie?[null]:t instanceof Se?[void 0,...fe(t.unwrap())]:t instanceof je?[null,...fe(t.unwrap())]:t instanceof Ke||t instanceof Me?fe(t.unwrap()):t instanceof Fe?fe(t._def.innerType):[];class ye extends A{_parse(e){const{ctx:t}=this._processInputParams(e);if(t.parsedType!==a.object)return h(t,{code:r.invalid_type,expected:a.object,received:t.parsedType}),f;const s=this.discriminator,n=t.data[s],i=this.optionsMap.get(n);return i?t.common.async?i._parseAsync({data:t.data,path:t.path,parent:t}):i._parseSync({data:t.data,path:t.path,parent:t}):(h(t,{code:r.invalid_union_discriminator,options:Array.from(this.optionsMap.keys()),path:[s]}),f)}get discriminator(){return this._def.discriminator}get options(){return this._def.options}get optionsMap(){return this._def.optionsMap}static create(t,a,s){const r=new Map;for(const e of a){const a=fe(e.shape[t]);if(!a.length)throw new Error(`A discriminator value for key \`${t}\` could not be extracted from all schema options`);for(const s of a){if(r.has(s))throw new Error(`Discriminator property ${String(t)} has duplicate value ${String(s)}`);r.set(s,e)}}return new ye({typeName:e.ZodFirstPartyTypeKind.ZodDiscriminatedUnion,discriminator:t,options:a,optionsMap:r,...N(s)})}}function ve(t,r){const n=s(t),i=s(r);if(t===r)return{valid:!0,data:t};if(n===a.object&&i===a.object){const a=e.util.objectKeys(r),s=e.util.objectKeys(t).filter((e=>-1!==a.indexOf(e))),n={...t,...r};for(const e of s){const a=ve(t[e],r[e]);if(!a.valid)return{valid:!1};n[e]=a.data}return{valid:!0,data:n}}if(n===a.array&&i===a.array){if(t.length!==r.length)return{valid:!1};const e=[];for(let a=0;a{if(_(e)||_(s))return f;const n=ve(e.value,s.value);return n.valid?((g(e)||g(s))&&t.dirty(),{status:t.value,value:n.data}):(h(a,{code:r.invalid_intersection_types}),f)};return a.common.async?Promise.all([this._def.left._parseAsync({data:a.data,path:a.path,parent:a}),this._def.right._parseAsync({data:a.data,path:a.path,parent:a})]).then((([e,t])=>s(e,t))):s(this._def.left._parseSync({data:a.data,path:a.path,parent:a}),this._def.right._parseSync({data:a.data,path:a.path,parent:a}))}}_e.create=(t,a,s)=>new _e({left:t,right:a,typeName:e.ZodFirstPartyTypeKind.ZodIntersection,...N(s)});class ge extends A{_parse(e){const{status:t,ctx:s}=this._processInputParams(e);if(s.parsedType!==a.array)return h(s,{code:r.invalid_type,expected:a.array,received:s.parsedType}),f;if(s.data.lengththis._def.items.length&&(h(s,{code:r.too_big,maximum:this._def.items.length,inclusive:!0,exact:!1,type:"array"}),t.dirty());const n=[...s.data].map(((e,t)=>{const a=this._def.items[t]||this._def.rest;return a?a._parse(new O(s,e,s.path,t)):null})).filter((e=>!!e));return s.common.async?Promise.all(n).then((e=>m.mergeArray(t,e))):m.mergeArray(t,n)}get items(){return this._def.items}rest(e){return new ge({...this._def,rest:e})}}ge.create=(t,a)=>{if(!Array.isArray(t))throw new Error("You must pass an array of schemas to z.tuple([ ... ])");return new ge({items:t,typeName:e.ZodFirstPartyTypeKind.ZodTuple,rest:null,...N(a)})};class be extends A{get keySchema(){return this._def.keyType}get valueSchema(){return this._def.valueType}_parse(e){const{status:t,ctx:s}=this._processInputParams(e);if(s.parsedType!==a.object)return h(s,{code:r.invalid_type,expected:a.object,received:s.parsedType}),f;const n=[],i=this._def.keyType,o=this._def.valueType;for(const e in s.data)n.push({key:i._parse(new O(s,e,s.path,e)),value:o._parse(new O(s,s.data[e],s.path,e)),alwaysSet:e in s.data});return s.common.async?m.mergeObjectAsync(t,n):m.mergeObjectSync(t,n)}get element(){return this._def.valueType}static create(t,a,s){return new be(a instanceof A?{keyType:t,valueType:a,typeName:e.ZodFirstPartyTypeKind.ZodRecord,...N(s)}:{keyType:X.create(),valueType:t,typeName:e.ZodFirstPartyTypeKind.ZodRecord,...N(a)})}}class ke extends A{get keySchema(){return this._def.keyType}get valueSchema(){return this._def.valueType}_parse(e){const{status:t,ctx:s}=this._processInputParams(e);if(s.parsedType!==a.map)return h(s,{code:r.invalid_type,expected:a.map,received:s.parsedType}),f;const n=this._def.keyType,i=this._def.valueType,o=[...s.data.entries()].map((([e,t],a)=>({key:n._parse(new O(s,e,s.path,[a,"key"])),value:i._parse(new O(s,t,s.path,[a,"value"]))})));if(s.common.async){const e=new Map;return Promise.resolve().then((async()=>{for(const a of o){const s=await a.key,r=await a.value;if("aborted"===s.status||"aborted"===r.status)return f;"dirty"!==s.status&&"dirty"!==r.status||t.dirty(),e.set(s.value,r.value)}return{status:t.value,value:e}}))}{const e=new Map;for(const a of o){const s=a.key,r=a.value;if("aborted"===s.status||"aborted"===r.status)return f;"dirty"!==s.status&&"dirty"!==r.status||t.dirty(),e.set(s.value,r.value)}return{status:t.value,value:e}}}}ke.create=(t,a,s)=>new ke({valueType:a,keyType:t,typeName:e.ZodFirstPartyTypeKind.ZodMap,...N(s)});class xe extends A{_parse(e){const{status:t,ctx:s}=this._processInputParams(e);if(s.parsedType!==a.set)return h(s,{code:r.invalid_type,expected:a.set,received:s.parsedType}),f;const n=this._def;null!==n.minSize&&s.data.sizen.maxSize.value&&(h(s,{code:r.too_big,maximum:n.maxSize.value,type:"set",inclusive:!0,exact:!1,message:n.maxSize.message}),t.dirty());const i=this._def.valueType;function o(e){const a=new Set;for(const s of e){if("aborted"===s.status)return f;"dirty"===s.status&&t.dirty(),a.add(s.value)}return{status:t.value,value:a}}const d=[...s.data.values()].map(((e,t)=>i._parse(new O(s,e,s.path,t))));return s.common.async?Promise.all(d).then((e=>o(e))):o(d)}min(e,t){return new xe({...this._def,minSize:{value:e,message:w.toString(t)}})}max(e,t){return new xe({...this._def,maxSize:{value:e,message:w.toString(t)}})}size(e,t){return this.min(e,t).max(e,t)}nonempty(e){return this.min(1,e)}}xe.create=(t,a)=>new xe({valueType:t,minSize:null,maxSize:null,typeName:e.ZodFirstPartyTypeKind.ZodSet,...N(a)});class Ze extends A{constructor(){super(...arguments),this.validate=this.implement}_parse(e){const{ctx:t}=this._processInputParams(e);if(t.parsedType!==a.function)return h(t,{code:r.invalid_type,expected:a.function,received:t.parsedType}),f;function s(e,a){return l({data:e,path:t.path,errorMaps:[t.common.contextualErrorMap,t.schemaErrorMap,c(),o].filter((e=>!!e)),issueData:{code:r.invalid_arguments,argumentsError:a}})}function n(e,a){return l({data:e,path:t.path,errorMaps:[t.common.contextualErrorMap,t.schemaErrorMap,c(),o].filter((e=>!!e)),issueData:{code:r.invalid_return_type,returnTypeError:a}})}const d={errorMap:t.common.contextualErrorMap},u=t.data;if(this._def.returns instanceof Ne){const e=this;return v((async function(...t){const a=new i([]),r=await e._def.args.parseAsync(t,d).catch((e=>{throw a.addIssue(s(t,e)),a})),o=await Reflect.apply(u,this,r);return await e._def.returns._def.type.parseAsync(o,d).catch((e=>{throw a.addIssue(n(o,e)),a}))}))}{const e=this;return v((function(...t){const a=e._def.args.safeParse(t,d);if(!a.success)throw new i([s(t,a.error)]);const r=Reflect.apply(u,this,a.data),o=e._def.returns.safeParse(r,d);if(!o.success)throw new i([n(r,o.error)]);return o.data}))}}parameters(){return this._def.args}returnType(){return this._def.returns}args(...e){return new Ze({...this._def,args:ge.create(e).rest(de.create())})}returns(e){return new Ze({...this._def,returns:e})}implement(e){return this.parse(e)}strictImplement(e){return this.parse(e)}static create(t,a,s){return new Ze({args:t||ge.create([]).rest(de.create()),returns:a||de.create(),typeName:e.ZodFirstPartyTypeKind.ZodFunction,...N(s)})}}class we extends A{get schema(){return this._def.getter()}_parse(e){const{ctx:t}=this._processInputParams(e);return this._def.getter()._parse({data:t.data,path:t.path,parent:t})}}we.create=(t,a)=>new we({getter:t,typeName:e.ZodFirstPartyTypeKind.ZodLazy,...N(a)});class Te extends A{_parse(e){if(e.data!==this._def.value){const t=this._getOrReturnCtx(e);return h(t,{received:t.data,code:r.invalid_literal,expected:this._def.value}),f}return{status:"valid",value:e.data}}get value(){return this._def.value}}function Pe(t,a){return new Oe({values:t,typeName:e.ZodFirstPartyTypeKind.ZodEnum,...N(a)})}Te.create=(t,a)=>new Te({value:t,typeName:e.ZodFirstPartyTypeKind.ZodLiteral,...N(a)});class Oe extends A{constructor(){super(...arguments),T.set(this,void 0)}_parse(t){if("string"!=typeof t.data){const a=this._getOrReturnCtx(t),s=this._def.values;return h(a,{expected:e.util.joinValues(s),received:a.parsedType,code:r.invalid_type}),f}if(x(this,T,"f")||Z(this,T,new Set(this._def.values),"f"),!x(this,T,"f").has(t.data)){const e=this._getOrReturnCtx(t),a=this._def.values;return h(e,{received:e.data,code:r.invalid_enum_value,options:a}),f}return v(t.data)}get options(){return this._def.values}get enum(){const e={};for(const t of this._def.values)e[t]=t;return e}get Values(){const e={};for(const t of this._def.values)e[t]=t;return e}get Enum(){const e={};for(const t of this._def.values)e[t]=t;return e}extract(e,t=this._def){return Oe.create(e,{...this._def,...t})}exclude(e,t=this._def){return Oe.create(this.options.filter((t=>!e.includes(t))),{...this._def,...t})}}T=new WeakMap,Oe.create=Pe;class Ce extends A{constructor(){super(...arguments),P.set(this,void 0)}_parse(t){const s=e.util.getValidEnumValues(this._def.values),n=this._getOrReturnCtx(t);if(n.parsedType!==a.string&&n.parsedType!==a.number){const t=e.util.objectValues(s);return h(n,{expected:e.util.joinValues(t),received:n.parsedType,code:r.invalid_type}),f}if(x(this,P,"f")||Z(this,P,new Set(e.util.getValidEnumValues(this._def.values)),"f"),!x(this,P,"f").has(t.data)){const t=e.util.objectValues(s);return h(n,{received:n.data,code:r.invalid_enum_value,options:t}),f}return v(t.data)}get enum(){return this._def.values}}P=new WeakMap,Ce.create=(t,a)=>new Ce({values:t,typeName:e.ZodFirstPartyTypeKind.ZodNativeEnum,...N(a)});class Ne extends A{unwrap(){return this._def.type}_parse(e){const{ctx:t}=this._processInputParams(e);if(t.parsedType!==a.promise&&!1===t.common.async)return h(t,{code:r.invalid_type,expected:a.promise,received:t.parsedType}),f;const s=t.parsedType===a.promise?t.data:Promise.resolve(t.data);return v(s.then((e=>this._def.type.parseAsync(e,{path:t.path,errorMap:t.common.contextualErrorMap}))))}}Ne.create=(t,a)=>new Ne({type:t,typeName:e.ZodFirstPartyTypeKind.ZodPromise,...N(a)});class Ae extends A{innerType(){return this._def.schema}sourceType(){return this._def.schema._def.typeName===e.ZodFirstPartyTypeKind.ZodEffects?this._def.schema.sourceType():this._def.schema}_parse(t){const{status:a,ctx:s}=this._processInputParams(t),r=this._def.effect||null,n={addIssue:e=>{h(s,e),e.fatal?a.abort():a.dirty()},get path(){return s.path}};if(n.addIssue=n.addIssue.bind(n),"preprocess"===r.type){const e=r.transform(s.data,n);if(s.common.async)return Promise.resolve(e).then((async e=>{if("aborted"===a.value)return f;const t=await this._def.schema._parseAsync({data:e,path:s.path,parent:s});return"aborted"===t.status?f:"dirty"===t.status||"dirty"===a.value?y(t.value):t}));{if("aborted"===a.value)return f;const t=this._def.schema._parseSync({data:e,path:s.path,parent:s});return"aborted"===t.status?f:"dirty"===t.status||"dirty"===a.value?y(t.value):t}}if("refinement"===r.type){const e=e=>{const t=r.refinement(e,n);if(s.common.async)return Promise.resolve(t);if(t instanceof Promise)throw new Error("Async refinement encountered during synchronous parse operation. Use .parseAsync instead.");return e};if(!1===s.common.async){const t=this._def.schema._parseSync({data:s.data,path:s.path,parent:s});return"aborted"===t.status?f:("dirty"===t.status&&a.dirty(),e(t.value),{status:a.value,value:t.value})}return this._def.schema._parseAsync({data:s.data,path:s.path,parent:s}).then((t=>"aborted"===t.status?f:("dirty"===t.status&&a.dirty(),e(t.value).then((()=>({status:a.value,value:t.value}))))))}if("transform"===r.type){if(!1===s.common.async){const e=this._def.schema._parseSync({data:s.data,path:s.path,parent:s});if(!b(e))return e;const t=r.transform(e.value,n);if(t instanceof Promise)throw new Error("Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.");return{status:a.value,value:t}}return this._def.schema._parseAsync({data:s.data,path:s.path,parent:s}).then((e=>b(e)?Promise.resolve(r.transform(e.value,n)).then((e=>({status:a.value,value:e}))):e))}e.util.assertNever(r)}}Ae.create=(t,a,s)=>new Ae({schema:t,typeName:e.ZodFirstPartyTypeKind.ZodEffects,effect:a,...N(s)}),Ae.createWithPreprocess=(t,a,s)=>new Ae({schema:a,effect:{type:"preprocess",transform:t},typeName:e.ZodFirstPartyTypeKind.ZodEffects,...N(s)});class Se extends A{_parse(e){return this._getType(e)===a.undefined?v(void 0):this._def.innerType._parse(e)}unwrap(){return this._def.innerType}}Se.create=(t,a)=>new Se({innerType:t,typeName:e.ZodFirstPartyTypeKind.ZodOptional,...N(a)});class je extends A{_parse(e){return this._getType(e)===a.null?v(null):this._def.innerType._parse(e)}unwrap(){return this._def.innerType}}je.create=(t,a)=>new je({innerType:t,typeName:e.ZodFirstPartyTypeKind.ZodNullable,...N(a)});class Ee extends A{_parse(e){const{ctx:t}=this._processInputParams(e);let s=t.data;return t.parsedType===a.undefined&&(s=this._def.defaultValue()),this._def.innerType._parse({data:s,path:t.path,parent:t})}removeDefault(){return this._def.innerType}}Ee.create=(t,a)=>new Ee({innerType:t,typeName:e.ZodFirstPartyTypeKind.ZodDefault,defaultValue:"function"==typeof a.default?a.default:()=>a.default,...N(a)});class Fe extends A{_parse(e){const{ctx:t}=this._processInputParams(e),a={...t,common:{...t.common,issues:[]}},s=this._def.innerType._parse({data:a.data,path:a.path,parent:{...a}});return k(s)?s.then((e=>({status:"valid",value:"valid"===e.status?e.value:this._def.catchValue({get error(){return new i(a.common.issues)},input:a.data})}))):{status:"valid",value:"valid"===s.status?s.value:this._def.catchValue({get error(){return new i(a.common.issues)},input:a.data})}}removeCatch(){return this._def.innerType}}Fe.create=(t,a)=>new Fe({innerType:t,typeName:e.ZodFirstPartyTypeKind.ZodCatch,catchValue:"function"==typeof a.catch?a.catch:()=>a.catch,...N(a)});class Ie extends A{_parse(e){if(this._getType(e)!==a.nan){const t=this._getOrReturnCtx(e);return h(t,{code:r.invalid_type,expected:a.nan,received:t.parsedType}),f}return{status:"valid",value:e.data}}}Ie.create=t=>new Ie({typeName:e.ZodFirstPartyTypeKind.ZodNaN,...N(t)});const Re=Symbol("zod_brand");class Ke extends A{_parse(e){const{ctx:t}=this._processInputParams(e),a=t.data;return this._def.type._parse({data:a,path:t.path,parent:t})}unwrap(){return this._def.type}}class $e extends A{_parse(e){const{status:t,ctx:a}=this._processInputParams(e);if(a.common.async){return(async()=>{const e=await this._def.in._parseAsync({data:a.data,path:a.path,parent:a});return"aborted"===e.status?f:"dirty"===e.status?(t.dirty(),y(e.value)):this._def.out._parseAsync({data:e.value,path:a.path,parent:a})})()}{const e=this._def.in._parseSync({data:a.data,path:a.path,parent:a});return"aborted"===e.status?f:"dirty"===e.status?(t.dirty(),{status:"dirty",value:e.value}):this._def.out._parseSync({data:e.value,path:a.path,parent:a})}}static create(t,a){return new $e({in:t,out:a,typeName:e.ZodFirstPartyTypeKind.ZodPipeline})}}class Me extends A{_parse(e){const t=this._def.innerType._parse(e),a=e=>(b(e)&&(e.value=Object.freeze(e.value)),e);return k(t)?t.then((e=>a(e))):a(t)}unwrap(){return this._def.innerType}}function De(e,t){const a="function"==typeof e?e(t):"string"==typeof e?{message:e}:e;return"string"==typeof a?{message:a}:a}function Le(e,t={},a){return e?oe.create().superRefine(((s,r)=>{var n,i;const o=e(s);if(o instanceof Promise)return o.then((e=>{var n,i;if(!e){const e=De(t,s),o=null===(i=null!==(n=e.fatal)&&void 0!==n?n:a)||void 0===i||i;r.addIssue({code:"custom",...e,fatal:o})}}));if(!o){const e=De(t,s),o=null===(i=null!==(n=e.fatal)&&void 0!==n?n:a)||void 0===i||i;r.addIssue({code:"custom",...e,fatal:o})}})):oe.create()}Me.create=(t,a)=>new Me({innerType:t,typeName:e.ZodFirstPartyTypeKind.ZodReadonly,...N(a)});const ze={object:he.lazycreate};var Ve;e.ZodFirstPartyTypeKind=void 0,(Ve=e.ZodFirstPartyTypeKind||(e.ZodFirstPartyTypeKind={})).ZodString="ZodString",Ve.ZodNumber="ZodNumber",Ve.ZodNaN="ZodNaN",Ve.ZodBigInt="ZodBigInt",Ve.ZodBoolean="ZodBoolean",Ve.ZodDate="ZodDate",Ve.ZodSymbol="ZodSymbol",Ve.ZodUndefined="ZodUndefined",Ve.ZodNull="ZodNull",Ve.ZodAny="ZodAny",Ve.ZodUnknown="ZodUnknown",Ve.ZodNever="ZodNever",Ve.ZodVoid="ZodVoid",Ve.ZodArray="ZodArray",Ve.ZodObject="ZodObject",Ve.ZodUnion="ZodUnion",Ve.ZodDiscriminatedUnion="ZodDiscriminatedUnion",Ve.ZodIntersection="ZodIntersection",Ve.ZodTuple="ZodTuple",Ve.ZodRecord="ZodRecord",Ve.ZodMap="ZodMap",Ve.ZodSet="ZodSet",Ve.ZodFunction="ZodFunction",Ve.ZodLazy="ZodLazy",Ve.ZodLiteral="ZodLiteral",Ve.ZodEnum="ZodEnum",Ve.ZodEffects="ZodEffects",Ve.ZodNativeEnum="ZodNativeEnum",Ve.ZodOptional="ZodOptional",Ve.ZodNullable="ZodNullable",Ve.ZodDefault="ZodDefault",Ve.ZodCatch="ZodCatch",Ve.ZodPromise="ZodPromise",Ve.ZodBranded="ZodBranded",Ve.ZodPipeline="ZodPipeline",Ve.ZodReadonly="ZodReadonly";const Ue=(e,t={message:`Input not instance of ${e.name}`})=>Le((t=>t instanceof e),t),Be=X.create,We=ee.create,qe=Ie.create,Je=te.create,Ye=ae.create,He=se.create,Ge=re.create,Xe=ne.create,Qe=ie.create,et=oe.create,tt=de.create,at=ue.create,st=ce.create,rt=le.create,nt=he.create,it=he.strictCreate,ot=me.create,dt=ye.create,ut=_e.create,ct=ge.create,lt=be.create,pt=ke.create,ht=xe.create,mt=Ze.create,ft=we.create,yt=Te.create,vt=Oe.create,_t=Ce.create,gt=Ne.create,bt=Ae.create,kt=Se.create,xt=je.create,Zt=Ae.createWithPreprocess,wt=$e.create,Tt=()=>Be().optional(),Pt=()=>We().optional(),Ot=()=>Ye().optional(),Ct={string:e=>X.create({...e,coerce:!0}),number:e=>ee.create({...e,coerce:!0}),boolean:e=>ae.create({...e,coerce:!0}),bigint:e=>te.create({...e,coerce:!0}),date:e=>se.create({...e,coerce:!0})},Nt=f;var At=Object.freeze({__proto__:null,defaultErrorMap:o,setErrorMap:u,getErrorMap:c,makeIssue:l,EMPTY_PATH:p,addIssueToContext:h,ParseStatus:m,INVALID:f,DIRTY:y,OK:v,isAborted:_,isDirty:g,isValid:b,isAsync:k,get util(){return e.util},get objectUtil(){return e.objectUtil},ZodParsedType:a,getParsedType:s,ZodType:A,datetimeRegex:Y,ZodString:X,ZodNumber:ee,ZodBigInt:te,ZodBoolean:ae,ZodDate:se,ZodSymbol:re,ZodUndefined:ne,ZodNull:ie,ZodAny:oe,ZodUnknown:de,ZodNever:ue,ZodVoid:ce,ZodArray:le,ZodObject:he,ZodUnion:me,ZodDiscriminatedUnion:ye,ZodIntersection:_e,ZodTuple:ge,ZodRecord:be,ZodMap:ke,ZodSet:xe,ZodFunction:Ze,ZodLazy:we,ZodLiteral:Te,ZodEnum:Oe,ZodNativeEnum:Ce,ZodPromise:Ne,ZodEffects:Ae,ZodTransformer:Ae,ZodOptional:Se,ZodNullable:je,ZodDefault:Ee,ZodCatch:Fe,ZodNaN:Ie,BRAND:Re,ZodBranded:Ke,ZodPipeline:$e,ZodReadonly:Me,custom:Le,Schema:A,ZodSchema:A,late:ze,get ZodFirstPartyTypeKind(){return e.ZodFirstPartyTypeKind},coerce:Ct,any:et,array:rt,bigint:Je,boolean:Ye,date:He,discriminatedUnion:dt,effect:bt,enum:vt,function:mt,instanceof:Ue,intersection:ut,lazy:ft,literal:yt,map:pt,nan:qe,nativeEnum:_t,never:at,null:Qe,nullable:xt,number:We,object:nt,oboolean:Ot,onumber:Pt,optional:kt,ostring:Tt,pipeline:wt,preprocess:Zt,promise:gt,record:lt,set:ht,strictObject:it,string:Be,symbol:Ge,transformer:bt,tuple:ct,undefined:Xe,union:ot,unknown:tt,void:st,NEVER:Nt,ZodIssueCode:r,quotelessJson:n,ZodError:i});e.BRAND=Re,e.DIRTY=y,e.EMPTY_PATH=p,e.INVALID=f,e.NEVER=Nt,e.OK=v,e.ParseStatus=m,e.Schema=A,e.ZodAny=oe,e.ZodArray=le,e.ZodBigInt=te,e.ZodBoolean=ae,e.ZodBranded=Ke,e.ZodCatch=Fe,e.ZodDate=se,e.ZodDefault=Ee,e.ZodDiscriminatedUnion=ye,e.ZodEffects=Ae,e.ZodEnum=Oe,e.ZodError=i,e.ZodFunction=Ze,e.ZodIntersection=_e,e.ZodIssueCode=r,e.ZodLazy=we,e.ZodLiteral=Te,e.ZodMap=ke,e.ZodNaN=Ie,e.ZodNativeEnum=Ce,e.ZodNever=ue,e.ZodNull=ie,e.ZodNullable=je,e.ZodNumber=ee,e.ZodObject=he,e.ZodOptional=Se,e.ZodParsedType=a,e.ZodPipeline=$e,e.ZodPromise=Ne,e.ZodReadonly=Me,e.ZodRecord=be,e.ZodSchema=A,e.ZodSet=xe,e.ZodString=X,e.ZodSymbol=re,e.ZodTransformer=Ae,e.ZodTuple=ge,e.ZodType=A,e.ZodUndefined=ne,e.ZodUnion=me,e.ZodUnknown=de,e.ZodVoid=ce,e.addIssueToContext=h,e.any=et,e.array=rt,e.bigint=Je,e.boolean=Ye,e.coerce=Ct,e.custom=Le,e.date=He,e.datetimeRegex=Y,e.default=At,e.defaultErrorMap=o,e.discriminatedUnion=dt,e.effect=bt,e.enum=vt,e.function=mt,e.getErrorMap=c,e.getParsedType=s,e.instanceof=Ue,e.intersection=ut,e.isAborted=_,e.isAsync=k,e.isDirty=g,e.isValid=b,e.late=ze,e.lazy=ft,e.literal=yt,e.makeIssue=l,e.map=pt,e.nan=qe,e.nativeEnum=_t,e.never=at,e.null=Qe,e.nullable=xt,e.number=We,e.object=nt,e.oboolean=Ot,e.onumber=Pt,e.optional=kt,e.ostring=Tt,e.pipeline=wt,e.preprocess=Zt,e.promise=gt,e.quotelessJson=n,e.record=lt,e.set=ht,e.setErrorMap=u,e.strictObject=it,e.string=Be,e.symbol=Ge,e.transformer=bt,e.tuple=ct,e[void 0]=Xe,e.union=ot,e.unknown=tt,e.void=st,e.z=At,Object.defineProperty(e,"__esModule",{value:!0})})); +//# sourceMappingURL=/sm/faeed0596d43c60cf0df488e38219b3725a534b9e2fba99cb50ebc3c0df8e313.map \ No newline at end of file diff --git a/ui/_/fonts/BonaNova/BonaNova-Bold.woff b/ui/_/fonts/BonaNova/BonaNova-Bold.woff new file mode 100644 index 0000000..1d01765 Binary files /dev/null and b/ui/_/fonts/BonaNova/BonaNova-Bold.woff differ diff --git a/ui/_/fonts/BonaNova/BonaNova-Italic.woff b/ui/_/fonts/BonaNova/BonaNova-Italic.woff new file mode 100644 index 0000000..940881a Binary files /dev/null and b/ui/_/fonts/BonaNova/BonaNova-Italic.woff differ diff --git a/ui/_/fonts/BonaNova/BonaNova-Regular.woff b/ui/_/fonts/BonaNova/BonaNova-Regular.woff new file mode 100644 index 0000000..b9f826c Binary files /dev/null and b/ui/_/fonts/BonaNova/BonaNova-Regular.woff differ diff --git a/ui/_/fonts/BonaNova/bona-nova-webfont.zip b/ui/_/fonts/BonaNova/bona-nova-webfont.zip new file mode 100644 index 0000000..d80861e Binary files /dev/null and b/ui/_/fonts/BonaNova/bona-nova-webfont.zip differ diff --git a/ui/_/fonts/BonaNova/bona-nova-webfont/example.html b/ui/_/fonts/BonaNova/bona-nova-webfont/example.html new file mode 100644 index 0000000..5220712 --- /dev/null +++ b/ui/_/fonts/BonaNova/bona-nova-webfont/example.html @@ -0,0 +1,18 @@ + + + + + + + + + +

Generated from: http://font.download


+

AaBbCcDdEeFfGgHhŞşIıİi Example

+

AaBbCcDdEeFfGgHhŞşIıİi Example

+

AaBbCcDdEeFfGgHhŞşIıİi Example

+ + + + \ No newline at end of file diff --git a/ui/_/fonts/BonaNova/bona-nova-webfont/style.css b/ui/_/fonts/BonaNova/bona-nova-webfont/style.css new file mode 100644 index 0000000..af76de4 --- /dev/null +++ b/ui/_/fonts/BonaNova/bona-nova-webfont/style.css @@ -0,0 +1,24 @@ +/* #### Generated By: http://font.download #### */ + + @font-face { + font-family: 'Bona Nova Regular'; + font-style: normal; + font-weight: normal; + src: local('Bona Nova Regular'), url('BonaNova-Regular.woff') format('woff'); + } + + + @font-face { + font-family: 'Bona Nova Italic'; + font-style: normal; + font-weight: normal; + src: local('Bona Nova Italic'), url('BonaNova-Italic.woff') format('woff'); + } + + + @font-face { + font-family: 'Bona Nova Bold'; + font-style: normal; + font-weight: normal; + src: local('Bona Nova Bold'), url('BonaNova-Bold.woff') format('woff'); + } \ No newline at end of file diff --git a/ui/_/fonts/BonaNova/old/BonaNova-Bold.ttf b/ui/_/fonts/BonaNova/old/BonaNova-Bold.ttf new file mode 100755 index 0000000..805cb80 Binary files /dev/null and b/ui/_/fonts/BonaNova/old/BonaNova-Bold.ttf differ diff --git a/ui/_/fonts/BonaNova/old/BonaNova-Italic.ttf b/ui/_/fonts/BonaNova/old/BonaNova-Italic.ttf new file mode 100755 index 0000000..114dbe5 Binary files /dev/null and b/ui/_/fonts/BonaNova/old/BonaNova-Italic.ttf differ diff --git a/ui/_/fonts/BonaNova/old/BonaNova-Regular.ttf b/ui/_/fonts/BonaNova/old/BonaNova-Regular.ttf new file mode 100755 index 0000000..3387076 Binary files /dev/null and b/ui/_/fonts/BonaNova/old/BonaNova-Regular.ttf differ diff --git a/ui/_/fonts/Canterbury/Canterbury.ttf b/ui/_/fonts/Canterbury/Canterbury.ttf new file mode 100755 index 0000000..f12fa3a Binary files /dev/null and b/ui/_/fonts/Canterbury/Canterbury.ttf differ diff --git a/ui/_/fonts/CrimsonText/CrimsonText-Bold.ttf b/ui/_/fonts/CrimsonText/CrimsonText-Bold.ttf new file mode 100755 index 0000000..5753863 Binary files /dev/null and b/ui/_/fonts/CrimsonText/CrimsonText-Bold.ttf differ diff --git a/ui/_/fonts/CrimsonText/CrimsonText-BoldItalic.ttf b/ui/_/fonts/CrimsonText/CrimsonText-BoldItalic.ttf new file mode 100755 index 0000000..233e387 Binary files /dev/null and b/ui/_/fonts/CrimsonText/CrimsonText-BoldItalic.ttf differ diff --git a/ui/_/fonts/CrimsonText/CrimsonText-Italic.ttf b/ui/_/fonts/CrimsonText/CrimsonText-Italic.ttf new file mode 100755 index 0000000..e8fe8b8 Binary files /dev/null and b/ui/_/fonts/CrimsonText/CrimsonText-Italic.ttf differ diff --git a/ui/_/fonts/CrimsonText/CrimsonText-Regular.ttf b/ui/_/fonts/CrimsonText/CrimsonText-Regular.ttf new file mode 100755 index 0000000..f24bf2a Binary files /dev/null and b/ui/_/fonts/CrimsonText/CrimsonText-Regular.ttf differ diff --git a/ui/_/fonts/CrimsonText/CrimsonText-SemiBold.ttf b/ui/_/fonts/CrimsonText/CrimsonText-SemiBold.ttf new file mode 100755 index 0000000..96f27e8 Binary files /dev/null and b/ui/_/fonts/CrimsonText/CrimsonText-SemiBold.ttf differ diff --git a/ui/_/fonts/CrimsonText/CrimsonText-SemiBoldItalic.ttf b/ui/_/fonts/CrimsonText/CrimsonText-SemiBoldItalic.ttf new file mode 100755 index 0000000..66b129b Binary files /dev/null and b/ui/_/fonts/CrimsonText/CrimsonText-SemiBoldItalic.ttf differ diff --git a/ui/_/fonts/CrimsonText/OFL.txt b/ui/_/fonts/CrimsonText/OFL.txt new file mode 100755 index 0000000..114c104 --- /dev/null +++ b/ui/_/fonts/CrimsonText/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2010 The Crimson Text Project Authors (https://github.com/googlefonts/Crimson) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/ui/_/icons/Column.svg b/ui/_/icons/Column.svg new file mode 100644 index 0000000..ab79e11 --- /dev/null +++ b/ui/_/icons/Column.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/_/icons/creditcards/amex.svg b/ui/_/icons/creditcards/amex.svg new file mode 100644 index 0000000..2959ce0 --- /dev/null +++ b/ui/_/icons/creditcards/amex.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ui/_/icons/creditcards/discover.svg b/ui/_/icons/creditcards/discover.svg new file mode 100644 index 0000000..2f2db02 --- /dev/null +++ b/ui/_/icons/creditcards/discover.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ui/_/icons/creditcards/mastercard.svg b/ui/_/icons/creditcards/mastercard.svg new file mode 100644 index 0000000..2fc1503 --- /dev/null +++ b/ui/_/icons/creditcards/mastercard.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/ui/_/icons/creditcards/visa.svg b/ui/_/icons/creditcards/visa.svg new file mode 100644 index 0000000..6ecbd0b --- /dev/null +++ b/ui/_/icons/creditcards/visa.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ui/_/icons/hamburger.svg b/ui/_/icons/hamburger.svg new file mode 100644 index 0000000..6014b63 --- /dev/null +++ b/ui/_/icons/hamburger.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/_/icons/jobs.svg b/ui/_/icons/jobs.svg new file mode 100644 index 0000000..f4a811f --- /dev/null +++ b/ui/_/icons/jobs.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/_/icons/letter.svg b/ui/_/icons/letter.svg new file mode 100644 index 0000000..9a1d4d8 --- /dev/null +++ b/ui/_/icons/letter.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/_/icons/locationPin.svg b/ui/_/icons/locationPin.svg new file mode 100644 index 0000000..02c7690 --- /dev/null +++ b/ui/_/icons/locationPin.svg @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/ui/_/icons/logo.svg b/ui/_/icons/logo.svg new file mode 100644 index 0000000..67b933e --- /dev/null +++ b/ui/_/icons/logo.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/_/icons/logo2.svg b/ui/_/icons/logo2.svg new file mode 100644 index 0000000..317a745 --- /dev/null +++ b/ui/_/icons/logo2.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/_/icons/place/austin.svg b/ui/_/icons/place/austin.svg new file mode 100644 index 0000000..cb03ee2 --- /dev/null +++ b/ui/_/icons/place/austin.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/ui/_/icons/profile.svg b/ui/_/icons/profile.svg new file mode 100644 index 0000000..28bbb9b --- /dev/null +++ b/ui/_/icons/profile.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/_/icons/x.svg b/ui/_/icons/x.svg new file mode 100644 index 0000000..c4cea2c --- /dev/null +++ b/ui/_/icons/x.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/_/images/castle-dark.svg b/ui/_/images/castle-dark.svg new file mode 100644 index 0000000..98518f7 --- /dev/null +++ b/ui/_/images/castle-dark.svg @@ -0,0 +1,576 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/_/images/castle-dark2.svg b/ui/_/images/castle-dark2.svg new file mode 100644 index 0000000..425085a --- /dev/null +++ b/ui/_/images/castle-dark2.svg @@ -0,0 +1,3094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/_/images/castle-dark3.svg b/ui/_/images/castle-dark3.svg new file mode 100644 index 0000000..8a610e2 --- /dev/null +++ b/ui/_/images/castle-dark3.svg @@ -0,0 +1,3094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/_/images/castle.svg b/ui/_/images/castle.svg new file mode 100644 index 0000000..b02ed5c --- /dev/null +++ b/ui/_/images/castle.svg @@ -0,0 +1,578 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/_/images/divider.svg b/ui/_/images/divider.svg new file mode 100644 index 0000000..b47a650 --- /dev/null +++ b/ui/_/images/divider.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/_/images/fabric.png b/ui/_/images/fabric.png new file mode 100644 index 0000000..c0dfbda Binary files /dev/null and b/ui/_/images/fabric.png differ diff --git a/ui/_/images/fabric.webp b/ui/_/images/fabric.webp new file mode 100644 index 0000000..25e4343 Binary files /dev/null and b/ui/_/images/fabric.webp differ diff --git a/ui/_/images/knight.png b/ui/_/images/knight.png new file mode 100644 index 0000000..27dcf5e Binary files /dev/null and b/ui/_/images/knight.png differ diff --git a/ui/_/images/knight.webp b/ui/_/images/knight.webp new file mode 100644 index 0000000..0472250 Binary files /dev/null and b/ui/_/images/knight.webp differ diff --git a/ui/_/images/the_return.webp b/ui/_/images/the_return.webp new file mode 100644 index 0000000..2485073 Binary files /dev/null and b/ui/_/images/the_return.webp differ diff --git a/ui/desktop/apps/Forum/Forum.js b/ui/desktop/apps/Forum/Forum.js new file mode 100644 index 0000000..0a26881 --- /dev/null +++ b/ui/desktop/apps/Forum/Forum.js @@ -0,0 +1,104 @@ +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(() => { + HStack(() => { + VStack(() => { + img("/_/icons/logo.svg", "2em") + .padding(0.8, em) + .borderRadius(12, px) + .marginHorizontal(1, em) + .onHover(function (hovering) { + if(hovering) { + this.style.background = "var(--darkbrown)" + } else { + this.style.background = "" + } + }) + .opacity(0) + + img("/_/icons/place/austin.svg", "2em") + .padding(0.8, em) + .borderRadius(12, px) + .marginHorizontal(1, em) + .onHover(function (hovering) { + if(hovering) { + this.style.background = "var(--darkbrown)" + } else { + this.style.background = "" + } + }) + .opacity(0) + + }) + .height(100, vh) + .paddingLeft(2, em) + .paddingRight(2, em) + .gap(1, em) + .marginTop(20, vh) + + 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) + .alignHorizontal("center") + .alignVertical("end") + }) + .width(100, "%") + .height(87, vh) + .x(0).y(0, vh) + }) + .width(100, pct) + .height(100, pct) + } + + +} + +register(Forum) \ No newline at end of file diff --git a/ui/desktop/apps/Forum/ForumPanel.js b/ui/desktop/apps/Forum/ForumPanel.js new file mode 100644 index 0000000..d2a6fa4 --- /dev/null +++ b/ui/desktop/apps/Forum/ForumPanel.js @@ -0,0 +1,90 @@ +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 () => { + console.log("appear") + 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) { + console.log("rerendering", res.msg) + 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/ui/desktop/apps/Jobs/Jobs.js b/ui/desktop/apps/Jobs/Jobs.js new file mode 100644 index 0000000..bb72598 --- /dev/null +++ b/ui/desktop/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/ui/desktop/apps/Jobs/JobsGrid.js b/ui/desktop/apps/Jobs/JobsGrid.js new file mode 100644 index 0000000..2af5d4f --- /dev/null +++ b/ui/desktop/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/ui/desktop/apps/Jobs/JobsSidebar.js b/ui/desktop/apps/Jobs/JobsSidebar.js new file mode 100644 index 0000000..1546cec --- /dev/null +++ b/ui/desktop/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/ui/desktop/apps/Market/Market.js b/ui/desktop/apps/Market/Market.js new file mode 100644 index 0000000..e3bceeb --- /dev/null +++ b/ui/desktop/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/ui/desktop/apps/Market/MarketGrid.js b/ui/desktop/apps/Market/MarketGrid.js new file mode 100644 index 0000000..8740f9e --- /dev/null +++ b/ui/desktop/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/ui/desktop/apps/Market/MarketSidebar.js b/ui/desktop/apps/Market/MarketSidebar.js new file mode 100644 index 0000000..9324618 --- /dev/null +++ b/ui/desktop/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/ui/desktop/apps/Messages/Messages.js b/ui/desktop/apps/Messages/Messages.js new file mode 100644 index 0000000..dd288a6 --- /dev/null +++ b/ui/desktop/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) + .alignHorizontal("center") + .alignVertical("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/ui/desktop/apps/Messages/MessagesPanel.js b/ui/desktop/apps/Messages/MessagesPanel.js new file mode 100644 index 0000000..b608212 --- /dev/null +++ b/ui/desktop/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/ui/desktop/apps/Messages/MessagesSidebar.js b/ui/desktop/apps/Messages/MessagesSidebar.js new file mode 100644 index 0000000..453a91d --- /dev/null +++ b/ui/desktop/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/ui/desktop/apps/Tasks/Tasks.js b/ui/desktop/apps/Tasks/Tasks.js new file mode 100644 index 0000000..4b0e733 --- /dev/null +++ b/ui/desktop/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/ui/desktop/components/AppMenu.js b/ui/desktop/components/AppMenu.js new file mode 100644 index 0000000..96a507e --- /dev/null +++ b/ui/desktop/components/AppMenu.js @@ -0,0 +1,133 @@ +css(` + app-menu { + color: var(--tan); + transform: translateX(-50%); + transition: transform .3s; + display: flex; gap: 2em; position: fixed; left: 50vw; bottom: 2em; + } + + app-menu.minimized { + color: var(--accent); + transform: translate(-50%, 65%); + border: 0.2px solid var(--accent); + padding-top: 0.5em; + padding-left: 2em; + padding-right: 2em; + padding-bottom: 4em; + bottom: 1em; + border-radius: 12px; + } + + app-menu p { + cursor: default; + transition: transform .3s, text-decoration .3s; + padding: 0.5em; + border-radius: 5px; + text-underline-offset: 5px; + } + app-menu p:hover { + text-decoration: underline; + transform: translateY(-5%) + } + app-menu p.touched { + text-decoration: underline; + transform: translateY(0%) + } + app-menu p.selected { + text-decoration: underline; + transform: translateY(-10%) + } + + #divider.minimized { + display: none; + } +`) + +register( + +class AppMenu extends Shadow { + selected; + + constructor(selected) { + super() + this.selected = selected + } + + render() { + VStack(() => { + HStack(() => { + p("Forum") + p("Messages") + p("Market") + p("Jobs") + }) + .justifyContent("center") + .gap(1.5, em) + .paddingRight(2, em) + + img("/_/images/divider.svg", "40vw") + .attr({ + "id": "divider", + }) + }) + .gap(0.5, em) + .onNavigate(() => { + if(window.location.pathname === "/") { + this.styleMaximized() + $("app-window").close() + } else { + this.styleMinimized() + $("app-window").open(this.selected) + } + }) + .onAppear(() => { + Array.from(this.querySelectorAll("p")).forEach((el) => { + el.addEventListener("mousedown", (e) => { + el.classList.add("touched") + }) + }) + window.addEventListener("mouseup", (e) => { + let target = e.target + if(!target.matches("app-menu p")) { + return + } + + target.classList.remove("touched") + + if(target.classList.contains("selected")) { + this.selected = "" + window.navigateTo("/") + } else { + this.selected = target.innerText + window.navigateTo("/app/" + target.innerText.toLowerCase()) + } + }) + }) + + if(this.selected) { + this.styleMinimized() + } + } + + styleMaximized() { + $$("app-menu p").forEach((el) => { + el.classList.remove("selected") + }) + this.classList.remove("minimized") + $("#divider").style.display = "" + } + + styleMinimized() { + $$("app-menu p").forEach((el) => { + if(el.innerText !== this.selected) { + el.classList.remove("selected") + } else { + el.classList.add("selected") + } + }) + this.classList.add("minimized") + $("#divider").style.display = "none" + } +} + +, "app-menu") \ No newline at end of file diff --git a/ui/desktop/components/AppWindow.js b/ui/desktop/components/AppWindow.js new file mode 100644 index 0000000..3f34b4a --- /dev/null +++ b/ui/desktop/components/AppWindow.js @@ -0,0 +1,54 @@ +import "../apps/Forum/Forum.js" +import "../apps/Tasks/Tasks.js" +import "../apps/Messages/Messages.js" +import "../apps/Market/Market.js" +import "../apps/Jobs/Jobs.js" + +class AppWindow extends Shadow { + app; + + constructor(app) { + super() + this.app = app + } + + render() { + ZStack(() => { + switch(this.app) { + case "Forum": + Forum() + break; + case "Messages": + Messages() + break; + case "Market": + Market() + break; + case "Jobs": + Jobs() + break; + } + }) + .position("fixed") + .display(this.app ? 'block' : 'none') + .width(100, "vw") + .height(100, "vh") + .background("#591d10") + .x(0) + .y(0) + // .backgroundImage("/_/images/fabric.png") + // .backgroundSize("33vw auto") + } + + open(app) { + this.app = app + this.rerender() + } + + close() { + this.style.display = "none" + } + +} + +register(AppWindow, "app-window") \ No newline at end of file diff --git a/ui/desktop/components/Home.js b/ui/desktop/components/Home.js new file mode 100644 index 0000000..54fe664 --- /dev/null +++ b/ui/desktop/components/Home.js @@ -0,0 +1,91 @@ +import "./AppWindow.js" +import "./AppMenu.js" +import "./ProfileButton.js" +import "./InputBox.js" +import "./Sidebar.js" + +class Home extends Shadow { + + render() { + ZStack(() => { + img("/_/icons/logo.svg", "2.5em") + .position("fixed") + .left(3, em) + .top(3, vh) + .zIndex(3) + .onClick(() => { + window.navigateTo("/") + }) + + div() + .width(100, vw) + .height(100, vh) + .margin(0) + .backgroundImage("/_/images/the_return.webp") + .backgroundSize("cover") + .backgroundPosition("48% 65%") + .backgroundRepeat("no-repeat") + + switch(window.location.pathname) { + case "/": + AppWindow() + AppMenu() + break + case "/app/jobs": + AppWindow("Jobs") + AppMenu("Jobs") + break; + case "/app/messages": + AppWindow("Messages") + AppMenu("Messages") + break; + case "/app/market": + AppWindow("Market") + AppMenu("Market") + break; + case "/app/forum": + AppWindow("Forum") + AppMenu("Forum") + break; + default: + throw new Error("Unknown route!") + } + + + HStack(() => { + ProfileButton() + .zIndex(1) + .cursor("default") + + a("/signout", "Sign Out") + .background("transparent") + .border(window.location.pathname === "/" ? "1px solid var(--tan)" : "0.5px solid #bb7c36") + .color(window.location.pathname === "/" ? "var(--tan)" : "var(--accent)") + .borderRadius(5, px) + .onHover(function (hovering) { + if(hovering) { + this.style.background = "var(--green)" + } else { + this.style.background = "" + } + }) + .onNavigate(function () { + if(window.location.pathname === "/") { + this.style.border = "1px solid var(--tan)" + this.style.color = "var(--tan)" + } else { + this.style.border = "0.5px solid #bb7c36" + this.style.color = "var(--accent)" + } + }) + }) + .gap(1, em) + .xRight(2, em).y(2.3, em) + .position("fixed") + .alignVertical("center") + + }) + } +} + +register(Home) \ No newline at end of file diff --git a/ui/desktop/components/InputBox.js b/ui/desktop/components/InputBox.js new file mode 100644 index 0000000..279a78c --- /dev/null +++ b/ui/desktop/components/InputBox.js @@ -0,0 +1,54 @@ +css(` + input-box { + display: block; + width: 60vw; + position: fixed; + left: 20vw; + bottom: 2vw; + } + + .input { + width: 100%; + background-color: var(--accent2); + opacity: 0.5; + border-radius: 12px; + border: none; + resize: none; + color: var(--orange); + padding: 1em; + height: 1em; + outline: none; + transition: opacity .1s, scale .1s + } + + .input:focus { + opacity: 80%; + scale: 1.02 + } +`) + +export default class InputBox extends HTMLElement { + hovered = false + + connectedCallback() { + this.render() + this.addListeners() + } + + render() { + this.innerHTML = /* html */` + + ` + } + + addListeners() { + this.$("textarea").addEventListener("keydown", (e) => { + if(e.key === "Enter") { + e.preventDefault() + e.target.blur() + } + }) + } +} + +customElements.define("input-box", InputBox) \ No newline at end of file diff --git a/ui/desktop/components/LoadingCircle.js b/ui/desktop/components/LoadingCircle.js new file mode 100644 index 0000000..f71a86c --- /dev/null +++ b/ui/desktop/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/ui/desktop/components/ProfileButton.js b/ui/desktop/components/ProfileButton.js new file mode 100644 index 0000000..7ffc029 --- /dev/null +++ b/ui/desktop/components/ProfileButton.js @@ -0,0 +1,43 @@ +import "./ProfileMenu.js" + +class ProfileButton extends Shadow { + + async render() { + ZStack(async () => { + img("/_/icons/profile.svg", "1.5em", "1.5em") + .backgroundColor("var(--accent)") + .padding(0.2, em) + .borderRadius(5, px) + + ProfileMenu() + + }) + .display("block") + .onAppear(() => { + window.addEventListener("mousedown", (e) => { // bad - adding every time it renders + if(!e.target.closest("profilebutton-")) { + this.$("profile-menu").style.display = "none" + } + }) + }) + .onHover((hovering, e) => { + console.log(hovering) + console.log(e.target) + if(hovering && !e.target.closest("profile-menu")) { + this.$("img").backgroundColor("var(--accent)") + this.$("img").style.outline = "1px solid black" + } else if(!e.target.closest("profile-menu")) { + this.$("img").backgroundColor("") + this.$("img").style.outline = "" + } + }) + .onClick((done) => { + console.log(done) + if(done) { + this.$("profile-menu").style.display = "" + } + }) + } +} + +register(ProfileButton) \ No newline at end of file diff --git a/ui/desktop/components/ProfileMenu.js b/ui/desktop/components/ProfileMenu.js new file mode 100644 index 0000000..f46a87e --- /dev/null +++ b/ui/desktop/components/ProfileMenu.js @@ -0,0 +1,68 @@ +class ProfileMenu extends Shadow { + + render() { + VStack(() => { + h2("Profile") + + HStack(() => { + p("Email: ") + .fontWeight("bold") + + p(window.profile?.email) + }) + .gap(1, em) + + HStack(() => { + p("Name: ") + .fontWeight("bold") + + p(window.profile?.name) + }) + .gap(1, em) + + p("X") + .onClick(() => { + this.style.display = "none" + }) + .xRight(2, em).y(1, em) + }) + .paddingLeft(1, em) + .color("var(--accent)") + .position("fixed") + .border("1px solid var(--accent)") + .x(50, vw).y(47, vh) + .width(70, vw) + .height(70, vh) + .backgroundColor("black") + .center() + .display("none") + .onAppear(async () => { + if(!window.profile) { + window.profile = await this.fetchProfile() + this.rerender() + } + }) + } + + async fetchProfile() { + try { + const res = await fetch("/profile", { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json" + } + }); + + if (!res.ok) throw new Error("Failed to fetch profile"); + + const profile = await res.json(); + console.log(profile); + return profile + } catch (err) { + console.error(err); + } + } +} + +register(ProfileMenu, "profile-menu") \ No newline at end of file diff --git a/ui/desktop/components/Sidebar.js b/ui/desktop/components/Sidebar.js new file mode 100644 index 0000000..58d1b82 --- /dev/null +++ b/ui/desktop/components/Sidebar.js @@ -0,0 +1,39 @@ +css(` + side-bar { + position: fixed; + top: 0; + left: 0; + height: 100vh; + width: 16vw; + border-right: 0.5px solid var(--accent2); + display: flex; + flex-direction: column; + padding-top: 13vh; + } + + side-bar button { + color: var(--darkbrown); + margin: 1.5em; + background-color: color-mix(in srgb, var(--accent2) 35%, var(--orange) 65%); + border: 1px solid var(--orange); + border-radius: 12px; + padding: 0.5em; + font-weight: bold; + } +`) + + +class Sidebar extends HTMLElement { + connectedCallback() { + this.render() + } + + render() { + this.innerHTML = /* html */ ` + hyperia + + ` + } +} + +customElements.define("side-bar", Sidebar) \ No newline at end of file diff --git a/ui/desktop/index.html b/ui/desktop/index.html new file mode 100644 index 0000000..24bd703 --- /dev/null +++ b/ui/desktop/index.html @@ -0,0 +1,14 @@ + + + + Hyperia + + + + + + + + + + \ No newline at end of file diff --git a/ui/desktop/index.js b/ui/desktop/index.js new file mode 100644 index 0000000..d0acae8 --- /dev/null +++ b/ui/desktop/index.js @@ -0,0 +1,8 @@ +import Socket from "./ws/Socket.js" +import "./components/Home.js" + +import util from "./util.js" +window.util = util + +window.Socket = new Socket() +Home() \ No newline at end of file diff --git a/ui/desktop/util.js b/ui/desktop/util.js new file mode 100644 index 0000000..43c3bd1 --- /dev/null +++ b/ui/desktop/util.js @@ -0,0 +1,9 @@ +export default class util { + static formatTime(str) { + const match = str.match(/-(\d+:\d+):\d+.*(am|pm)/i); + if (!match) return null; + + const [_, hourMin, ampm] = match; + return hourMin + ampm.toLowerCase(); + } +} \ No newline at end of file diff --git a/ui/desktop/ws/Connection.js b/ui/desktop/ws/Connection.js new file mode 100644 index 0000000..7d1d52c --- /dev/null +++ b/ui/desktop/ws/Connection.js @@ -0,0 +1,62 @@ +class Connection { + connectionTries = 0 + ws; + linkCreated; + wsStatus; + + constructor(receiveCB) { + this.init() + this.receiveCB = receiveCB + } + + init() { + if(window.location.hostname.includes("local")) { + this.ws = new WebSocket("ws://" + window.location.host) + } else { + this.ws = new WebSocket("wss://" + window.location.hostname + window.location.pathname) + } + this.ws.addEventListener('open', () => { + this.connectionTries = 0 + console.log("Websocket connection established."); + this.ws.addEventListener('message', this.receiveCB) + }); + this.ws.addEventListener("close", () => { + this.checkOpen(); + console.log('Websocket Closed') + }) + } + + async checkOpen() { + if (this.ws.readyState === WebSocket.OPEN) { + return true + } else { + await this.sleep(this.connectionTries < 20 ? 5000 : 60000) + this.connectionTries++ + console.log('Reestablishing connection') + this.init() + } + } + + sleep = (time) => { + return new Promise(resolve => { + setTimeout(resolve, time); + }); + } + + send = (msg) => { + console.log("sending") + if (this.ws.readyState === WebSocket.OPEN) { + this.ws.send(msg); + } + else if(this.connectionTries === 0) { + setTimeout(() => { + this.send(msg) + }, 100) + } + else { + console.error('No websocket connection: Cannot send message'); + } + } +} + +export default Connection \ No newline at end of file diff --git a/ui/desktop/ws/Socket.js b/ui/desktop/ws/Socket.js new file mode 100644 index 0000000..49357ef --- /dev/null +++ b/ui/desktop/ws/Socket.js @@ -0,0 +1,45 @@ +import Connection from "./Connection.js"; + +export default class Socket { + connection; + disabled = true; + requestID = 1; + pending = new Map(); + + constructor() { + this.connection = new Connection(this.receive); + } + + isOpen() { + if(this.connection.checkOpen()) { + return true; + } else { + return false; + } + } + + send(msg) { + return new Promise(resolve => { + const id = (++this.requestID).toString(); + this.pending.set(id, resolve); + this.connection.send(JSON.stringify({ id, ...msg })); + }); + } + + receive = (event) => { + const msg = JSON.parse(event.data); + if (msg.id && this.pending.has(msg.id)) { + this.pending.get(msg.id)(msg); + this.pending.delete(msg.id); + return; + } else { + this.onBroadcast(msg) + } + } + + onBroadcast(msg) { + window.dispatchEvent(new CustomEvent(msg.event, { + detail: msg.msg + })); + } +} \ No newline at end of file diff --git a/ui/mobile/apps/Forum/Forum.js b/ui/mobile/apps/Forum/Forum.js new file mode 100644 index 0000000..8ee2f0b --- /dev/null +++ b/ui/mobile/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) + .alignHorizontal("center") + .alignVertical("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/ui/mobile/apps/Forum/ForumPanel.js b/ui/mobile/apps/Forum/ForumPanel.js new file mode 100644 index 0000000..9dfdde6 --- /dev/null +++ b/ui/mobile/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/ui/mobile/apps/Jobs/Jobs.js b/ui/mobile/apps/Jobs/Jobs.js new file mode 100644 index 0000000..bb72598 --- /dev/null +++ b/ui/mobile/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/ui/mobile/apps/Jobs/JobsGrid.js b/ui/mobile/apps/Jobs/JobsGrid.js new file mode 100644 index 0000000..2af5d4f --- /dev/null +++ b/ui/mobile/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/ui/mobile/apps/Jobs/JobsSidebar.js b/ui/mobile/apps/Jobs/JobsSidebar.js new file mode 100644 index 0000000..1546cec --- /dev/null +++ b/ui/mobile/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/ui/mobile/apps/Market/Market.js b/ui/mobile/apps/Market/Market.js new file mode 100644 index 0000000..e3bceeb --- /dev/null +++ b/ui/mobile/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/ui/mobile/apps/Market/MarketGrid.js b/ui/mobile/apps/Market/MarketGrid.js new file mode 100644 index 0000000..8740f9e --- /dev/null +++ b/ui/mobile/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/ui/mobile/apps/Market/MarketSidebar.js b/ui/mobile/apps/Market/MarketSidebar.js new file mode 100644 index 0000000..9324618 --- /dev/null +++ b/ui/mobile/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/ui/mobile/apps/Messages/Messages.js b/ui/mobile/apps/Messages/Messages.js new file mode 100644 index 0000000..dd288a6 --- /dev/null +++ b/ui/mobile/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) + .alignHorizontal("center") + .alignVertical("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/ui/mobile/apps/Messages/MessagesPanel.js b/ui/mobile/apps/Messages/MessagesPanel.js new file mode 100644 index 0000000..b608212 --- /dev/null +++ b/ui/mobile/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/ui/mobile/apps/Messages/MessagesSidebar.js b/ui/mobile/apps/Messages/MessagesSidebar.js new file mode 100644 index 0000000..453a91d --- /dev/null +++ b/ui/mobile/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/ui/mobile/apps/Tasks/Tasks.js b/ui/mobile/apps/Tasks/Tasks.js new file mode 100644 index 0000000..4b0e733 --- /dev/null +++ b/ui/mobile/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/ui/mobile/components/AppMenu.js b/ui/mobile/components/AppMenu.js new file mode 100644 index 0000000..d533876 --- /dev/null +++ b/ui/mobile/components/AppMenu.js @@ -0,0 +1,77 @@ +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) + .onAppear(function () { + this.style.border = "1px solid black" + }) + .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) + .onAppear(function () { + this.style.border = "1px solid black" + }) + .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) + .onAppear(function () { + this.style.border = "1px solid black" + }) + .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/ui/mobile/components/Home.js b/ui/mobile/components/Home.js new file mode 100644 index 0000000..b0dca5e --- /dev/null +++ b/ui/mobile/components/Home.js @@ -0,0 +1,37 @@ +import "./AppMenu.js" +import "../apps/Forum/Forum.js" +import "../apps/Messages/Messages.js" +import "../apps/Jobs/Jobs.js" + +class Home extends Shadow { + + render() { + ZStack(() => { + + 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/ui/mobile/components/LoadingCircle.js b/ui/mobile/components/LoadingCircle.js new file mode 100644 index 0000000..f71a86c --- /dev/null +++ b/ui/mobile/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/ui/mobile/index.html b/ui/mobile/index.html new file mode 100644 index 0000000..24bd703 --- /dev/null +++ b/ui/mobile/index.html @@ -0,0 +1,14 @@ + + + + Hyperia + + + + + + + + + + \ No newline at end of file diff --git a/ui/mobile/index.js b/ui/mobile/index.js new file mode 100644 index 0000000..574b8e8 --- /dev/null +++ b/ui/mobile/index.js @@ -0,0 +1,8 @@ +import Socket from "/_/code/ws/Socket.js" +import "./components/Home.js" + +import util from "./util.js" +window.util = util + +window.Socket = new Socket() +Home() \ No newline at end of file diff --git a/ui/mobile/util.js b/ui/mobile/util.js new file mode 100644 index 0000000..43c3bd1 --- /dev/null +++ b/ui/mobile/util.js @@ -0,0 +1,9 @@ +export default class util { + static formatTime(str) { + const match = str.match(/-(\d+:\d+):\d+.*(am|pm)/i); + if (!match) return null; + + const [_, hourMin, ampm] = match; + return hourMin + ampm.toLowerCase(); + } +} \ No newline at end of file diff --git a/ui/public/components/Footer.js b/ui/public/components/Footer.js new file mode 100644 index 0000000..da9a91a --- /dev/null +++ b/ui/public/components/Footer.js @@ -0,0 +1,16 @@ +css(` + page-footer { + display: flex; + justify-content: flex-end; + } +`) + +export default class Footer extends HTMLElement { + connectedCallback() { + this.innerHTML += /* html */` + + ` + } +} + +customElements.define("page-footer", Footer) diff --git a/ui/public/components/NavBar.js b/ui/public/components/NavBar.js new file mode 100644 index 0000000..d955e16 --- /dev/null +++ b/ui/public/components/NavBar.js @@ -0,0 +1,55 @@ +class NavBar extends Shadow { + NavButton(text) { + + function normalizeText(text) { + return text.toLowerCase().replace(/[\s?]+/g, ""); + } + + function isSelected(el) { + return ("/" + normalizeText(el.innerText)) === window.location.pathname + } + + return p(text) + .cursor("default") + .textUnderlineOffset(0.5, em) + .onAppear(function() { + this.style.textDecoration = isSelected(this) ? "underline" : "" + }) + .onHover(function (hovering) { + if(hovering) { + this.style.textDecoration = "underline" + } else if(!isSelected(this)) { + this.style.textDecoration = "" + } + }) + .onClick(function (done) { + if(done) { + if(!isSelected(this)) { + window.navigateTo(normalizeText(this.innerText)) + } else { + window.navigateTo("/") + } + } + }) + + } + + render() { + HStack(() => { + this.NavButton("WHY?") + this.NavButton("EVENTS") + div().width(2.5, em).height(2.5, em) + this.NavButton("JOIN") + this.NavButton("SIGN IN") + }) + .x(50, vw).y(4, em) + .center() + .fontSize(0.85, em) + .justifyContent("center") + .gap(3, em) + .paddingRight(2, em) + .width(50, vw) + } +} + +register(NavBar) \ No newline at end of file diff --git a/ui/public/components/NavMenu.js b/ui/public/components/NavMenu.js new file mode 100644 index 0000000..c16c506 --- /dev/null +++ b/ui/public/components/NavMenu.js @@ -0,0 +1,27 @@ +css(` + nav-menu { + position: fixed; + bottom: 6vh; + right: 6vh; + width: 20vw; + height: 10vh; + background: var(--green); + color: var(--tan); + border: 20px solid var(--tan); + } +`) + +export default class NavMenu extends HTMLElement { + connectedCallback() { + this.innerHTML += /* html */ ` + + + +

Menu

+ ` + + document.addEventListener("") + } +} + +customElements.define("nav-menu", NavMenu) \ No newline at end of file diff --git a/ui/public/components/SideBar.js b/ui/public/components/SideBar.js new file mode 100644 index 0000000..a16112d --- /dev/null +++ b/ui/public/components/SideBar.js @@ -0,0 +1,46 @@ +css(` + side-bar { + position: fixed; + top: 0; + right: -100vw; + width: 80vw; + height: 100vh; + background: var(--tan); + border-left: 1px solid var(--green); + transition: right 0.3s ease; + z-index: 10; + padding: 5vw; + } + + side-bar a { + font-size: 8vw; + color: var(--red) + } + + side-bar h2 { + font-size: 6vw + } +`) + + +export default class SideBar extends HTMLElement { + connectedCallback() { + this.innerHTML += /* html */` +

Menu

+ + + ` + this.querySelector("img").addEventListener("click", () => { + const sidebar = document.querySelector("side-bar"); + sidebar.style.right = "-100vw" + }) + } +} + +customElements.define("side-bar", SideBar) \ No newline at end of file diff --git a/ui/public/components/SignupForm.js b/ui/public/components/SignupForm.js new file mode 100644 index 0000000..47bbcdf --- /dev/null +++ b/ui/public/components/SignupForm.js @@ -0,0 +1,138 @@ +class SignupForm extends Shadow { + + errorMessage = "Error signing up. Please try again later or email info@hyperia.so if the problem persists." + successMessage = "Success! You may now log in." + + inputStyles(el) { + return el + .border("1px solid var(--accent)") + .color("var(--accent)") + } + + render() { + ZStack(() => { + form(() => { + + VStack(() => { + + p() + .attr({id: "signupMessage"}) + .display("none") + .padding(1, em) + .color("var(--main)") + .background("var(--accent)") + + HStack(() => { + + VStack(() => { + input("First Name*") + .attr({name: "firstName", type: "name", required: "true"}) + .styles(this.inputStyles) + + input("Last Name*") + .attr({name: "lastName", type: "name", required: "true"}) + .styles(this.inputStyles) + + input("Email*") + .attr({name: "email", type: "email", required: "true"}) + .styles(this.inputStyles) + + input("Password*") + .attr({name: "password", type: "password", required: "true"}) + .styles(this.inputStyles) + + input("Confirm Password*") + .attr({name: "password", type: "password", required: "true"}) + .styles(this.inputStyles) + }) + .width(50, "%") + .gap(1, em) + + VStack(() => { + input("Street Address*") + .attr({ name: "address1", type: "text", autocomplete: "address-line1", required: "true" }) + .styles(this.inputStyles) + + input("Apt, Suite, Unit (optional)") + .attr({ name: "address2", type: "text", autocomplete: "address-line2" }) + .styles(this.inputStyles) + + input("City*") + .attr({ name: "city", type: "text", autocomplete: "address-level2", required: "true" }) + .styles(this.inputStyles) + + input("State*") + .attr({ name: "state", type: "text", autocomplete: "address-level1", required: "true" }) + .styles(this.inputStyles) + + input("ZIP Code*") + .attr({ name: "zip", type: "text", autocomplete: "postal-code", required: "true" }) + .styles(this.inputStyles) + + input("Country*") + .attr({ name: "country", type: "text", autocomplete: "country-name", required: "true" }) + .styles(this.inputStyles) + }) + .width(50, "%") + .gap(1, em) + + }) + .gap(2, em) + + button("Submit") + }) + .gap(2, em) + }) + .color("var(--accent)") + .onSubmit(async (e) => { + e.preventDefault() + console.log("submitting") + $("#signupMessage").style.display = "none" + + const formData = new FormData(this.$("form")); + const data = Object.fromEntries(formData.entries()); + let newMember = { + "email": data.email, + "firstName": data.firstName, + "lastName": data.lastName, + "password": data.password + } + let address = { + "address1": data.address1, + "address2": data.address2, + "zip": data.zip, + "state": data.state, + "city": data.city + } + newMember.address = address + + try { + const response = await fetch(window.location.pathname + window.location.search, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(newMember) + }); + + if (!response.ok) { + $("#signupMessage").style.display = "block" + $("#signupMessage").innerText = this.errorMessage + throw new Error(`HTTP error! status: ${response.status}`); + } else { + $("#signupMessage").style.display = "block" + $("#signupMessage").innerText = this.successMessage + } + + } catch (err) { + console.error("Fetch error:", err); + } + }) + .x(50, vw).y(53, vh) + .width(60, vw) + .center() + }) + } +} + +register(SignupForm) \ No newline at end of file diff --git a/ui/public/index.html b/ui/public/index.html new file mode 100644 index 0000000..c8749db --- /dev/null +++ b/ui/public/index.html @@ -0,0 +1,33 @@ + + + + Hyperia + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/public/index.js b/ui/public/index.js new file mode 100644 index 0000000..b6b7e32 --- /dev/null +++ b/ui/public/index.js @@ -0,0 +1,2 @@ +import "./pages/Home.js" +Home() \ No newline at end of file diff --git a/ui/public/pages/Events.js b/ui/public/pages/Events.js new file mode 100644 index 0000000..15612d5 --- /dev/null +++ b/ui/public/pages/Events.js @@ -0,0 +1,325 @@ +class Events extends Shadow { + + events = [ + { + date: `January 23, 2025`, + title: `Hyperia Winter Ball`, + description: `Join us in Austin, Texas for a dance. Live music and drinks will be included.
Admission for men is $50, women are free. Open to the public.`, + location: `Austin, TX` + } + ] + + render() { + ZStack(() => { + VStack(() => { + + h1("HYPERIA") + .marginBottom(0, em) + + p("Public Events") + .fontSize(1.2, em) + .marginBottom(2, em) + + const Stack = window.isMobile() ? VStack : HStack + Stack(() => { + + VStack(() => { + p(`January 23, 2025`) + + p(`Hyperia Winter Ball`) + .fontSize(1.2, em) + + p(`Austin, TX`) + + }) + + p(`Join us in Austin, Texas for a great dance, with free drinks and live music.

Admission: $35 for men, women are free.`) + .marginRight(4, em) + + HStack(() => { + img("/_/icons/creditcards/visa.svg") + img("/_/icons/creditcards/mastercard.svg") + img("/_/icons/creditcards/discover.svg") + img("/_/icons/creditcards/amex.svg") + }) + .alignSelf("flex-start") + .height(2, em) + .maxWidth(40, vw) + + button("Buy Ticket") + .color("var(--darkbrown") + .border("1px solid #ab2f007d") + .background('var(--green)') + .marginLeft("auto") + .onClick(async function() { + this.innerText = "Loading..." + const res = await fetch("/create-checkout-session", { method: "POST" }); + const data = await res.json(); + window.location = data.url; + }) + }) + .gap(3, em) + .color("var(--darkbrown)") + .background(`var(--accent)`) + .padding(1, em) + .borderRadius(12, px) + .border("2px solid #ab2f007d") + }) + .marginLeft(window.isMobile() ? 0 : 15, vmax) + .marginRight(window.isMobile() ? 0 : 15, vmax) + .marginTop(10, vmax) + + HStack(() => { + p("Privacy Policy") + .onHover(function (hovering) { + if(hovering) { + this.style.color = "var(--darkbrown)" + } else { + this.style.color = "" + } + }) + .onClick(() => { + this.$("#policyWindow").style.display = "flex" + }) + p("Refund and Return Policy") + .onHover(function (hovering) { + if(hovering) { + this.style.color = "var(--darkbrown)" + } else { + this.style.color = "" + } + }) + .onClick(() => { + this.$("#refundWindow").style.display = "flex" + }) + p("Contact Us") + .onHover(function (hovering) { + if(hovering) { + this.style.color = "var(--darkbrown)" + } else { + this.style.color = "" + } + }) + .onClick(() => { + this.$("#contactWindow").style.display = "flex" + }) + }) + .x(50, vw).yBottom(0, vh) + .center() + .gap(2, em) + .opacity(0.5) + .cursor("default") + }) + + VStack(() => { + + p("Privacy Policy") + .fontSize(2, em) + .fontWeight(600) + .marginBottom(1, em) + + p("We value your privacy. This Privacy Policy explains how we collect, use, store, and protect your information when you use our website or services.") + + p("1. Information We Collect") + .fontWeight(600) + .marginTop(1, em) + + p("• Personal information you provide, such as your name, email address, or other contact details.") + p("• Automatically collected data, including IP address, browser type, device information, and usage statistics.") + p("• Cookies or similar tracking technologies that help us improve the user experience.") + + p("2. How We Use Your Information") + .fontWeight(600) + .marginTop(1, em) + + p("• To operate and improve our website and services.") + p("• To communicate with you about updates, support requests, or account-related matters.") + p("• To maintain security, prevent fraud, and ensure proper functionality.") + + p("3. How We Share Information") + .fontWeight(600) + .marginTop(1, em) + + p("We do not sell your personal information. We may share data only with trusted service providers who help us operate the platform, or when required by law.") + + p("4. Data Storage & Security") + .fontWeight(600) + .marginTop(1, em) + + p("We use reasonable technical and administrative safeguards to protect your information. However, no system is completely secure, and we cannot guarantee absolute protection.") + + p("5. Cookies") + .fontWeight(600) + .marginTop(1, em) + + p("Our site may use cookies to remember preferences, analyze traffic, and enhance usability. You can disable cookies in your browser settings, but some features may stop working.") + + p("6. Your Rights") + .fontWeight(600) + .marginTop(1, em) + + p("Depending on your location, you may have rights to access, update, delete, or request a copy of your personal data. Contact us if you wish to exercise these rights.") + + p("7. Third-Party Links") + .fontWeight(600) + .marginTop(1, em) + + p("Our website may contain links to third-party sites. We are not responsible for their content or privacy practices.") + + p("8. Changes to This Policy") + .fontWeight(600) + .marginTop(1, em) + + p("We may update this Privacy Policy from time to time. Updated versions will be posted on this page with the effective date.") + + p("9. Contact Us") + .fontWeight(600) + .marginTop(1, em) + + p("If you have any questions about this Privacy Policy, feel free to contact us at info@hyperia.so.") + + p("x") + .onClick(function (done) { + if(done) { + this.parentElement.style.display = "none" + } + }) + .color("var(--red)") + .xRight(1, em).y(1, em) + .fontSize(2, em) + .cursor("pointer") + + }) + .x(50, vw).y(50, vh) + .width(70, vw).height(70, vh) + .center() + .backgroundColor("var(--accent)") + .display("none") + .overflow("scroll") + .padding(1, em) + .border("3px solid black") + .color("var(--darkbrown)") + .attr({ id: "policyWindow" }) + + VStack(() => { + + p("Refund & Return Policy") + .fontSize(2, em) + .fontWeight(600) + .marginBottom(1, em) + + p("1. Eligibility for Refunds") + .fontWeight(600) + .marginTop(1, em) + + p("• Refund requests may be considered when submitted within 14 days of purchase.") + p("• To qualify, you must provide proof of purchase and a valid reason for the request.") + p("• Certain digital products or services may be non-refundable once accessed or downloaded.") + + p("2. Non-Refundable Items") + .fontWeight(600) + .marginTop(1, em) + + p("• Products or services that have already been delivered, downloaded, or accessed in full.") + p("• Custom work, personalized items, or one-time service fees.") + p("• Any promotional or discounted items, unless required by law.") + + p("3. Returns (If Applicable)") + .fontWeight(600) + .marginTop(1, em) + + p("• Physical items must be returned in their original condition.") + p("• You are responsible for return shipping costs unless the item was defective or incorrect.") + p("• Items damaged through misuse or neglect cannot be returned.") + + p("4. Processing Refunds") + .fontWeight(600) + .marginTop(1, em) + + p("• Approved refunds are issued to the original payment method.") + p("• Processing times may vary depending on your bank or payment provider.") + p("• We will notify you once your refund has been initiated.") + + p("5. Cancellations") + .fontWeight(600) + .marginTop(1, em) + + p("• Orders or subscriptions may be cancelled before fulfillment or renewal.") + p("• If Hyperia declare a cancellation of any product or event, a refund will be issued to all parties.") + + p("6. Contact for Refund Requests") + .fontWeight(600) + .marginTop(1, em) + + p("If you need to request a refund, return an item, or cancel an order, please contact us at info@hyperia.so. Include your order number and relevant details so we can assist you promptly.") + + p("7. Policy Updates") + .fontWeight(600) + .marginTop(1, em) + + p("We may update this Refund & Return Policy from time to time. Any changes will be posted on this page with the effective date.") + + p("x") + .onClick(function (done) { + if(done) { + this.parentElement.style.display = "none" + } + }) + .color("var(--red)") + .xRight(1, em).y(1, em) + .fontSize(2, em) + .cursor("pointer") + + }) + .x(50, vw).y(50, vh) + .width(70, vw).height(70, vh) + .center() + .backgroundColor("var(--accent)") + .display("none") + .overflow("scroll") + .padding(1, em) + .border("3px solid black") + .color("var(--darkbrown)") + .attr({ id: "refundWindow" }) + + + VStack(() => { + + p("Contact Us") + .fontSize(2, em) + .fontWeight(600) + .marginBottom(1, em) + + p("Email: info@hyperia.so") + p("Phone: 813-373-9100") + p("Address: 2014 E 9th St, Unit A, Austin TX") + + p("x") + .onClick(function (done) { + if(done) { + this.parentElement.style.display = "none" + } + }) + .color("var(--red)") + .xRight(1, em).y(1, em) + .fontSize(2, em) + .cursor("pointer") + + }) + .gap(2, em) + .x(50, vw).y(50, vh) + .width(50, vw).height(50, vh) + .center() + .backgroundColor("var(--accent)") + .display("none") + .overflow("scroll") + .padding(1, em) + .border("3px solid black") + .color("var(--darkbrown)") + .attr({ id: "contactWindow" }) + + + } +} + +register(Events) \ No newline at end of file diff --git a/ui/public/pages/Home.js b/ui/public/pages/Home.js new file mode 100644 index 0000000..7aebfea --- /dev/null +++ b/ui/public/pages/Home.js @@ -0,0 +1,89 @@ +import "../components/NavBar.js" +import "../components/SignupForm.js" +import "./Why.js" +import "./Events.js" +import "./Join.js" +import "./SignIn.js" +import "./Success.js" + +class Home extends Shadow { + render() { + + ZStack(() => { + + NavBar() + + img("/_/icons/logo.svg", "2.5em") + .onClick((done) => { + if(!done) return + window.navigateTo("/") + }) + .position("absolute") + .left(50, vw).top(4, em) + .center() + .transform(`translate(${window.isMobile() ? "-50%" : "-2em"}, -50%)`) + + switch(window.location.pathname) { + case "/": + img("/_/images/knight.webp", "29vmax") + .position("absolute") + .left(50, vw).top(isMobile() ? 50 : 53, vh) + .center() + + if(!isMobile()) { + p("H   Y   P   E   R   I   A  ") + .x(50, vw).y(53, vh) + .textAlign("center") + .center() + .color("var(--gold)") + .fontSize(5, vw) + .maxWidth(isMobile() ? 1000 : 100, em) + } else { + p("H   Y   P   E   R   I   A  ") + .x(46, vw).y(isMobile() ? 50 : 53, vh) + .textAlign("center") + .color("var(--gold)") + .fontSize(5, vw) + } + + if(!isMobile()) { + let text = "A Classical Christian Network" + p(isMobile() ? text : text.toUpperCase()) + .x(50, vw).yBottom(isMobile() ? 1 : 3, vh) + .center() + .letterSpacing(0.1, em) + .width(isMobile() ? 80 : 100, vw) + .fontSize(isMobile() ? 0.8 : 1, em) + .textAlign("center") + } + break; + case "/why": + Why() + break; + case "/events": + Events() + break; + case "/join": + Join() + break; + case "/success": + Success() + break; + + default: + if(window.location.pathname.startsWith("/signup")) { + SignupForm() + } else if(window.location.pathname.startsWith("/signin")) { + SignIn() + } + } + + }) + .onNavigate(() => { + this.rerender() + }) + + } +} + +register(Home) \ No newline at end of file diff --git a/ui/public/pages/Join.js b/ui/public/pages/Join.js new file mode 100644 index 0000000..961262f --- /dev/null +++ b/ui/public/pages/Join.js @@ -0,0 +1,29 @@ +class Join extends Shadow { + render() { + + VStack(() => { + + + p("Membership is invitation-only. Wait to meet one of us, or come to one of our events!") + + + // p("Membership is invitation-only. But sign up for our newsletter to hear about more events!") + + // HStack(() => { + // input("Email", "40vmin") + // .attr({name: "email", type: "email"}) + + // button("Sign Up") + // .width(15, vmin) + // }) + // .gap(1, em) + // .marginTop(1, em) + }) + .alignItems("center") + .maxWidth(90, vw) + .x(50, vw).y(50, vh) + .center() + } +} + +register(Join) \ No newline at end of file diff --git a/ui/public/pages/SignIn.js b/ui/public/pages/SignIn.js new file mode 100644 index 0000000..c766caf --- /dev/null +++ b/ui/public/pages/SignIn.js @@ -0,0 +1,36 @@ +class SignIn extends Shadow { + + inputStyles(el) { + return el + .color("var(--accent)") + .border("1px solid var(--accent)") + } + + render() { + ZStack(() => { + if(window.location.search.includes("new")) { + p("Welcome to Hyperia! You may now log in.") + .x(50, vw).y(40, vh) + .center() + } + + form(() => { + input("Email") + .attr({name: "email", type: "email"}) + .margin(1, em) + .styles(this.inputStyles) + input("Password") + .attr({name: "password", type: "password"}) + .margin(1, em) + .styles(this.inputStyles) + button("Submit") + .margin(1, em) + }) + .attr({action: "/login", method: "POST"}) + .x(50, vw).y(50, vh) + .center() + }) + } +} + +register(SignIn) \ No newline at end of file diff --git a/ui/public/pages/Success.js b/ui/public/pages/Success.js new file mode 100644 index 0000000..c3753f6 --- /dev/null +++ b/ui/public/pages/Success.js @@ -0,0 +1,9 @@ +class Success extends Shadow { + render() { + p("Thanks for your purchase! You will receive a confirmation email shortly.

Keep that email; it will be checked at the door.") + .x(50, vw).y(50, vh) + .center() + } +} + +register(Success) \ No newline at end of file diff --git a/ui/public/pages/Why.js b/ui/public/pages/Why.js new file mode 100644 index 0000000..f65b12a --- /dev/null +++ b/ui/public/pages/Why.js @@ -0,0 +1,21 @@ +class Why extends Shadow { + render() { + p(`I grew up going to Classical Christian schools all my life. Little did I know, this was a very unique experience - we got to learn all about our history, and everyone had a shared moral understanding. + +

Only when I went out into the world did I realize that most Americans have no idea what this is like. They have never been a part of a shared culture, and the only value they know is multiculturalism. + +

As adults, that is the world the we are all expected to live in. + +

Classical Christian schools are great, but what if I want to live a Classical Christian life? + +

That is what Hyperia is for. It is a Classical Christian space for adults. + +

-- Sam Russell, Founder + `) + .marginTop(window.isMobile() ? 20 : 30, vh) + .marginHorizontal(window.isMobile() ? 10 : 20, vw) + .marginBottom(20, vh) + } +} + +register(Why) \ No newline at end of file diff --git a/ui/readme.md b/ui/readme.md new file mode 100644 index 0000000..6fde598 --- /dev/null +++ b/ui/readme.md @@ -0,0 +1,5 @@ +# Installs + +Stripe CLI + +stripe listen --forward-to localhost:3003/webhook \ No newline at end of file