diff --git a/main/main.go b/main/main.go index 8911b56..9d259d5 100644 --- a/main/main.go +++ b/main/main.go @@ -18,6 +18,7 @@ func main() { webserver.ServeLoginPage("/login") webserver.EnableLogoRoute() + webserver.EnableStaticRoute() webserver.ServeWebserver() // calDavData := get_caldav_data(serverConfig.Username, serverConfig.Password) diff --git a/webserver/static/card.css b/webserver/static/card.css new file mode 100644 index 0000000..6404144 --- /dev/null +++ b/webserver/static/card.css @@ -0,0 +1,20 @@ +.card { + background: rgba(255, 255, 255, 1); + border: 1px solid var(--border-subtle); + border-radius: 12px; + padding: 2.5rem; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.03); + transition: transform 0.2s ease; + + display: flex; + flex-direction: column; + gap: 15px; + overflow: hidden; +} + +.static_center { + position: fixed; + left: 50%; + top: 50%; + transform: translateX(-50%) translateY(-50%); +} diff --git a/webserver/static/error/error.css b/webserver/static/error/error.css new file mode 100644 index 0000000..755bc82 --- /dev/null +++ b/webserver/static/error/error.css @@ -0,0 +1,30 @@ +.error { + background-color: var(--error-red) !important; + border-radius: 10px; + padding: 20px; + border-style: solid; + border-color: var(--error-border-red); + border-width: 1px; + box-sizing: border-box; + font-size: 12px; + position: relative; + color: white; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); +} + +.close_error_button { + position: absolute !important; + background-color: rgba(0, 0, 0, 0) !important; + padding: 0px !important; + top: 20px; + right: 20px; + color: black !important; + font-weight: normal !important; + border: none; + width: fit-content !important; +} + +.close_error_button:hover { + cursor: default; + font-weight: bold !important; +} diff --git a/webserver/static/error/error.js b/webserver/static/error/error.js new file mode 100644 index 0000000..61ac8b4 --- /dev/null +++ b/webserver/static/error/error.js @@ -0,0 +1,7 @@ +var error_close_buttons = document.getElementsByClassName("close_error_button"); + +for (const close_button of error_close_buttons) { + close_button.addEventListener("click", function () { + this.parentElement.classList.add("hidden"); + }); +} diff --git a/webserver/static/javascript/show_password.js b/webserver/static/javascript/show_password.js new file mode 100644 index 0000000..59c650f --- /dev/null +++ b/webserver/static/javascript/show_password.js @@ -0,0 +1,22 @@ +const showPasswordButtons = document.getElementsByClassName( + "show_password_toggle", +); + +for (const button of showPasswordButtons) { + button.addEventListener("click", function () { + const input = this.parentElement.querySelector( + "input[type='password'], input[type='text']", + ); + if (!input) return; + const isHidden = input.type === "password"; + input.type = isHidden ? "text" : "password"; + + if (isHidden) { + this.classList.add("open"); + this.classList.remove("closed"); + } else { + this.classList.remove("open"); + this.classList.add("closed"); + } + }); +} diff --git a/webserver/static/login_page.css b/webserver/static/login_page.css new file mode 100644 index 0000000..45c7e67 --- /dev/null +++ b/webserver/static/login_page.css @@ -0,0 +1,35 @@ +#login_card { + position: fixed; + top: 50%; + left: 50%; + transform: translateX(-50%) translateY(-50%); + width: 350px; +} + +#login_card input { + width: 324px; +} + +#login_card div { + width: 100%; +} + +#action-buttons { + justify-content: center; + display: flex; + gap: 15px; +} + +#signup_button { + flex: 1; +} + +#login_button { + flex: 2; +} + +#welcome_text { + font-weight: 700; + font-size: 1.25rem; + margin-bottom: 8px; +} diff --git a/webserver/static/style.css b/webserver/static/style.css new file mode 100644 index 0000000..db19bf9 --- /dev/null +++ b/webserver/static/style.css @@ -0,0 +1,151 @@ +:root { + /* Backgrounds */ + --bg-main: #f7fff7; /* Soft Mint White */ + --bg-card: #ffffff; /* Pure White Card */ + --bg-input: #f0f4f8; /* Light Gray-Blue Inset */ + + /* Text & Lines */ + --text-main: #1a202c; /* Deep Charcoal (Better than pure black) */ + --text-muted: #718096; /* Cool Gray for placeholders */ + --border-subtle: #e2e8f0; /* Light Gray border */ + + /* Action Colors */ + --primary-accent: #1a535c; /* Deep Teal (Trustworthy/Secure) */ + --primary-hover: #14434a; /* Darker Teal for hover */ + + --error-red: #ef4444; /* Professional Red */ + --error-border-red: #e22d2d; + + --warning-yellow: #fef08a; + --warning-border-yellow: #fde047; + + --password-strength-weak: var(--error-red); + --password-strength-medium: var(--warning-border-yellow); + --password-strength-strong: #43ef6b; + + font-family: "Inter", sans-serif; + -webkit-font-smoothing: antialiased; +} + +body { + background-color: var(--bg-main); +} + +button { + padding: 12px; + background-color: var(--primary-accent); /* Our Teal/Blue */ + color: white; + border: none; + border-radius: 6px; + cursor: pointer; + font-weight: bold; + width: 100%; +} + +button:hover { + background-color: var(--primary-hover); +} + +button:focus { + outline: 2px solid var(--primary-accent); +} + +input { + padding: 12px; + background-color: var(--bg-input); + border: 1px solid var(--border-subtle); + color: var(--text-main); + border-radius: 6px; +} + +input:focus { + outline: 2px solid var(--primary-accent); +} + +input::placeholder { + color: var(--text-muted); +} + +.input_label { + color: var(--text-muted); + margin: 0; + padding: 0; + font-size: 15px; +} + +.hidden { + display: none !important; +} + +.blocked { + position: fixed; /* or absolute */ + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 9999; /* higher = on top */ + + backdrop-filter: blur(4px); + background-color: rgba(0, 0, 0, 0.3); + pointer-events: all; +} + +.dialouge_title { + font-size: 20px; + margin-bottom: 0px; + color: var(--text-main) !important; +} + +.password_box { + position: relative; + display: inline-block; +} + +.show_password_toggle { + width: 10px; + height: 10px; + position: absolute; + right: 5px; + bottom: 50%; + transform: translateY(50%); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + background-color: rgba(0, 0, 0, 0); +} + +.show_password_toggle.closed { + background-image: url("/static/images/closed_eye.png"); +} + +.show_password_toggle.open { + background-image: url("/static/images/filled_eye.png"); +} + +.show_password_toggle:hover { + background-color: rgba(0, 0, 0, 0); +} + +.show_password_toggle:focus { + outline: none !important; +} + +#logo_image { + position: fixed; + width: 300px; + left: 50%; + transform: translateX(-50%); +} + +.subtext { + color: var(--text-muted); +} + +.noselect { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} diff --git a/webserver/webserver.go b/webserver/webserver.go index 6b353b2..7f50458 100644 --- a/webserver/webserver.go +++ b/webserver/webserver.go @@ -21,6 +21,11 @@ func EnableLogoRoute() { }) } +func EnableStaticRoute() { + http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("webserver/static")))) + routes = append(routes, "/static/*") +} + func ServeWebpage(url string, handler func(http.ResponseWriter, *http.Request)) { http.HandleFunc(url, handler) routes = append(routes, url)