From 094d5ef9afaf4e39485f5dae19bea48b2f1cd56e Mon Sep 17 00:00:00 2001 From: th3keyboard Date: Wed, 9 Oct 2024 18:18:15 -0500 Subject: [PATCH 01/29] small changes to index, added register.css --- static/index.html | 12 ++++++------ static/register.css | 5 +++++ static/register.html | 3 ++- static/style.css | 0 4 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 static/register.css delete mode 100644 static/style.css diff --git a/static/index.html b/static/index.html index df27294..66c10d2 100644 --- a/static/index.html +++ b/static/index.html @@ -14,22 +14,22 @@ diff --git a/static/register.css b/static/register.css new file mode 100644 index 0000000..47ac10c --- /dev/null +++ b/static/register.css @@ -0,0 +1,5 @@ +#rform { + border-radius: 10px; + padding: 10px; + background-color: orange; +} \ No newline at end of file diff --git a/static/register.html b/static/register.html index d1b56b4..3b8cb09 100644 --- a/static/register.html +++ b/static/register.html @@ -7,13 +7,14 @@ UTSA Placeholders +

Registration

-
+
diff --git a/static/style.css b/static/style.css deleted file mode 100644 index e69de29..0000000 From 71250b1496a0bf20a8d6f5c1f7c4cda59e7e4832 Mon Sep 17 00:00:00 2001 From: adanrsantos Date: Wed, 9 Oct 2024 18:19:10 -0500 Subject: [PATCH 02/29] added and linked canvas js and css file --- static/canvas.css | 3 +++ static/canvas.html | 7 +++++-- static/canvas.js | 0 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 static/canvas.css create mode 100644 static/canvas.js diff --git a/static/canvas.css b/static/canvas.css new file mode 100644 index 0000000..f8b8bdb --- /dev/null +++ b/static/canvas.css @@ -0,0 +1,3 @@ +canvas { + background-color: lightblue; +} \ No newline at end of file diff --git a/static/canvas.html b/static/canvas.html index e8e4dc2..f0cd5fa 100644 --- a/static/canvas.html +++ b/static/canvas.html @@ -7,8 +7,11 @@ UTSA Placeholders + - - + + +

Hello

+ \ No newline at end of file diff --git a/static/canvas.js b/static/canvas.js new file mode 100644 index 0000000..e69de29 From c3e5165765d26006b8e738254e5d3535f39f199d Mon Sep 17 00:00:00 2001 From: reddishquill371 Date: Wed, 9 Oct 2024 18:30:42 -0500 Subject: [PATCH 03/29] Change background color in canvas.css --- static/canvas.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/canvas.css b/static/canvas.css index f8b8bdb..def5ac0 100644 --- a/static/canvas.css +++ b/static/canvas.css @@ -1,3 +1,3 @@ canvas { - background-color: lightblue; + background-color: black; } \ No newline at end of file From 1fb1b8ffdeb275746fd6a81c55666a04c89dfaee Mon Sep 17 00:00:00 2001 From: th3keyboard Date: Wed, 9 Oct 2024 18:49:48 -0500 Subject: [PATCH 04/29] changes to register.css --- static/register.css | 46 ++++++++++++++++++++++++++++++++++++++++++-- static/register.html | 6 ++---- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/static/register.css b/static/register.css index 47ac10c..cf481db 100644 --- a/static/register.css +++ b/static/register.css @@ -1,5 +1,47 @@ +:root { + --utsa-orange: #f15a22; + --utsa-blue: #0c2340; +} + +html { + background-color: var(--utsa-orange); +} + #rform { border-radius: 10px; - padding: 10px; - background-color: orange; + border: solid var(--utsa-orange) 2px; + margin: 100px; + padding: 30px; + background-color: white; +} + +#formbg { + background-color: var(--utsa-orange); +} + +#h3bg { + background-color: var(--utsa-blue); +} + +h3 { + border-radius: 10px; + padding: 20px; + margin: 10px; + background-color: var(--utsa-blue); + color: var(--utsa-orange); + font-size: 40px; + font-family: Verdana, Geneva, Tahoma, sans-serif; +} + +.form-label { + font-family: Verdana, Geneva, Tahoma, sans-serif; +} + +.form-control { + font-family: Verdana, Geneva, Tahoma, sans-serif; +} + +.form-text { + padding: 3px; + font-family: Verdana, Geneva, Tahoma, sans-serif; } \ No newline at end of file diff --git a/static/register.html b/static/register.html index 3b8cb09..2a566b3 100644 --- a/static/register.html +++ b/static/register.html @@ -10,10 +10,8 @@ - -
-

Registration

-
+

Registration

+
From dcb9e37dc878f4e8fce38e9ada31e0200d03457b Mon Sep 17 00:00:00 2001 From: adanrsantos Date: Wed, 9 Oct 2024 19:02:59 -0500 Subject: [PATCH 05/29] added style to canvas --- static/canvas.css | 35 ++++++++++++++++++++++++++++++++++- static/canvas.html | 12 ++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/static/canvas.css b/static/canvas.css index def5ac0..ad81b73 100644 --- a/static/canvas.css +++ b/static/canvas.css @@ -1,3 +1,36 @@ +* { + padding: 0; + margin: 0; +} + +html, body { + height: 100%; +} + +.wrapper { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.navbarCont { + background-color: grey; + width: 100%; + text-align: center; + margin-bottom: 10px; +} + +.canvasCont { + display: flex; + justify-content: center; + align-items: center; + padding: 20px; + background-color: yellow; + border: solid black; + border-radius: 20px; +} + canvas { - background-color: black; + background-color: lightblue; } \ No newline at end of file diff --git a/static/canvas.html b/static/canvas.html index f0cd5fa..6152038 100644 --- a/static/canvas.html +++ b/static/canvas.html @@ -11,7 +11,15 @@ - -

Hello

+
+ +
+ + +
+
\ No newline at end of file From 260bda45a951be70bba4b728abf1e863a09a2526 Mon Sep 17 00:00:00 2001 From: adanrsantos Date: Wed, 9 Oct 2024 23:18:42 -0500 Subject: [PATCH 06/29] added drawing to the canvas --- static/canvas.css | 35 ++++++++++++++++++++++++++++- static/canvas.html | 17 ++++++++++++-- static/canvas.js | 56 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 3 deletions(-) diff --git a/static/canvas.css b/static/canvas.css index ad81b73..60a531a 100644 --- a/static/canvas.css +++ b/static/canvas.css @@ -23,14 +23,47 @@ html, body { .canvasCont { display: flex; + flex-direction: column; justify-content: center; align-items: center; - padding: 20px; background-color: yellow; border: solid black; border-radius: 20px; } +.toolbar { + display: flex; + justify-content: space-around; + align-items: center; + border-top: solid black 1px; + width: 100%; + height: 60px; + z-index: 10; +} + +.toolbarItems { + display: flex; + flex-direction: column; + justify-content: center; + font-weight: 900; + color: blue; + text-align: center; + height: 100%; + width: 100px; +} + +.strokePicker { + padding: 0; + margin: 0; + width: 100%; +} + +.strokePicker:hover { + cursor: pointer; +} + + canvas { background-color: lightblue; + margin: 20px; } \ No newline at end of file diff --git a/static/canvas.html b/static/canvas.html index 6152038..f4e0c3a 100644 --- a/static/canvas.html +++ b/static/canvas.html @@ -18,8 +18,21 @@
- +
+
+ + +
+
+ + +
+
+ +
+
-
+
+ \ No newline at end of file diff --git a/static/canvas.js b/static/canvas.js index e69de29..f88c632 100644 --- a/static/canvas.js +++ b/static/canvas.js @@ -0,0 +1,56 @@ +const canvas = document.getElementById("canvas"); +const toolbar = document.getElementById("toolbar"); +const strokePicker = document.getElementById("stroke"); +const ctx = canvas.getContext("2d"); + +const canvasOffsetX = canvas.offsetLeft; +const canvasOffsetY = canvas.offsetTop; + +canvas.width = 850; +canvas.height = 500; + +let isPainting = false; +let lineWidth = 5; +let strokeColor = "#000000"; +let startX; +let startY; + +toolbar.addEventListener("click", e => { + if (e.target.id === "clear"){ + ctx.clearRect(0, 0, canvas.width, canvas.height); + } + + if (e.target.id === "lineWidth"){ + lineWidth = e.target.value; + } +}); + +strokePicker.addEventListener("input", (e) => { + strokeColor = e.target.value; +}); + +const draw = (e) => { + if (!isPainting){ + return; + } + ctx.lineWidth = lineWidth; + ctx.strokeStyle = strokeColor; + ctx.lineCap = "round"; + + ctx.lineTo(e.clientX - canvasOffsetX, e.clientY - canvasOffsetY); + ctx.stroke(); +} + +canvas.addEventListener("mousedown", (e) => { + isPainting = true; + startX = e.clientX; + startY = e.clientY; +}); + +canvas.addEventListener("mouseup", (e) => { + isPainting = false; + ctx.stroke(); + ctx.beginPath(); +}); + +canvas.addEventListener("mousemove", draw); \ No newline at end of file From ef375752dc4c6e1ed8397cb283c56de358ef6d9f Mon Sep 17 00:00:00 2001 From: Logan Date: Fri, 11 Oct 2024 05:36:08 -0500 Subject: [PATCH 07/29] Made IP and port configurable --- README.md | 4 +++- server.go | 13 ++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7514898..9d1996e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ within a restricted amount of time to make art. 1. Install [Go](https://go.dev/dl/) 2. Run the command `go run .` inside this folder 3. View the website at [127.0.0.1:8080](http://127.0.0.1:8080/) +You can choose a different IP address or port using the flags: +`go run . -ip=192.168.0.1 -port=3000` ## Go project structure * [go.mod](go.mod) Go version and library dependencies @@ -24,4 +26,4 @@ CSS framework. * [Registration page](static/register.html) yaya -testing \ No newline at end of file +testing diff --git a/server.go b/server.go index 6b9f195..8d6a403 100644 --- a/server.go +++ b/server.go @@ -1,6 +1,7 @@ package main import ( + "flag" "fmt" "log" "net/http" @@ -8,9 +9,6 @@ import ( "github.com/gorilla/sessions" ) -const ADDRESS = "127.0.0.1" -const PORT = "8080" - type Server struct { // Registered user information Users map[string]UserData @@ -19,6 +17,11 @@ type Server struct { } func main() { + ipFlag := flag.String("ip", "127.0.0.1", "IP address to receive traffic from") + portFlag := flag.String("port", "8080", "Port to receive traffic from") + flag.Parse() + address := *ipFlag + port := *portFlag // Create server object secret := []byte("super-secret-key") server := Server{ @@ -40,8 +43,8 @@ func main() { }) http.HandleFunc("/secret", server.secret) // Start web server at 127.0.0.1:8080 - fmt.Printf("Listening to %s on port %s...\n", ADDRESS, PORT) - err := http.ListenAndServe(ADDRESS+":"+PORT, nil) + fmt.Printf("Listening to %s on port %s...\n", address, port) + err := http.ListenAndServe(address+":"+port, nil) // Print any errors if err != nil { fmt.Println("Error starting server:") From 47a3550430186a9e7beb0dcb1e3b6de83ed9fca0 Mon Sep 17 00:00:00 2001 From: Logan Gatlin <50534996+Xterminate1818@users.noreply.github.com> Date: Fri, 11 Oct 2024 05:39:44 -0500 Subject: [PATCH 08/29] Fixed README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9d1996e..6ce1eb8 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ within a restricted amount of time to make art. 1. Install [Go](https://go.dev/dl/) 2. Run the command `go run .` inside this folder 3. View the website at [127.0.0.1:8080](http://127.0.0.1:8080/) + You can choose a different IP address or port using the flags: `go run . -ip=192.168.0.1 -port=3000` From 0041dfe87a32086cb0f1206036473d5481b11d05 Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 16 Oct 2024 16:43:13 -0500 Subject: [PATCH 09/29] Started canvas backend --- server.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/server.go b/server.go index 8d6a403..95c38c6 100644 --- a/server.go +++ b/server.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "flag" "fmt" "log" @@ -9,14 +10,29 @@ import ( "github.com/gorilla/sessions" ) +const CANVAS_WIDTH = 1000 +const CANVAS_HEIGHT = 1000 + +type Canvas struct { + Data [CANVAS_WIDTH * CANVAS_HEIGHT]byte `json:"data"` + Width int `json:"width"` + Height int `json:"height"` +} + type Server struct { // Registered user information Users map[string]UserData // Login sessions Sessions *sessions.CookieStore + Canvas Canvas +} + +func (s *Server) canvas(w http.ResponseWriter, r *http.Request) { + json.NewEncoder(w).Encode(s.canvas) } func main() { + // Grab command line arguments ipFlag := flag.String("ip", "127.0.0.1", "IP address to receive traffic from") portFlag := flag.String("port", "8080", "Port to receive traffic from") flag.Parse() @@ -27,7 +43,15 @@ func main() { server := Server{ Users: make(map[string]UserData), Sessions: sessions.NewCookieStore(secret), + Canvas: Canvas{}, } + + for x := 0; x < CANVAS_WIDTH; x++ { + for y := 0; y < CANVAS_HEIGHT; y++ { + server.Canvas.Data[y*CANVAS_HEIGHT+x] = byte(CANVAS_WIDTH / x) + } + } + // Host static files static_files := http.FileServer(http.Dir("static/")) http.Handle("/", static_files) @@ -42,6 +66,7 @@ func main() { http.Redirect(w, r, "/", http.StatusFound) }) http.HandleFunc("/secret", server.secret) + http.HandleFunc("/canvas", canvas) // Start web server at 127.0.0.1:8080 fmt.Printf("Listening to %s on port %s...\n", address, port) err := http.ListenAndServe(address+":"+port, nil) From b8585d990a1233902a4b112a9e381bb299d6c244 Mon Sep 17 00:00:00 2001 From: adanrsantos Date: Wed, 16 Oct 2024 18:41:26 -0500 Subject: [PATCH 10/29] still testing out the canvas --- static/canvas.html | 3 +-- static/canvas.js | 62 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/static/canvas.html b/static/canvas.html index f4e0c3a..5a36a46 100644 --- a/static/canvas.html +++ b/static/canvas.html @@ -14,10 +14,9 @@
- +
diff --git a/static/canvas.js b/static/canvas.js index f88c632..b3de37e 100644 --- a/static/canvas.js +++ b/static/canvas.js @@ -1,23 +1,70 @@ const canvas = document.getElementById("canvas"); -const toolbar = document.getElementById("toolbar"); -const strokePicker = document.getElementById("stroke"); const ctx = canvas.getContext("2d"); -const canvasOffsetX = canvas.offsetLeft; -const canvasOffsetY = canvas.offsetTop; - -canvas.width = 850; +canvas.width = 500; canvas.height = 500; +const dataSize = 1000; +let x; +let y; +let pixelSize = canvas.width / dataSize; +const centerPixel = pixelSize / 2; + +function draw() { + ctx.fillRect(x, y, pixelSize, pixelSize); +} + +canvas.addEventListener("mousedown", (e) => { + x = e.clientX - canvas.getBoundingClientRect().left - centerPixel; + y = e.clientY - canvas.getBoundingClientRect().top - centerPixel; + draw(); +}) + +//const toolbar = document.getElementById("toolbar"); +//const strokePicker = document.getElementById("stroke"); +//const canvasOffsetX = canvas.offsetLeft; +//const canvasOffsetY = canvas.offsetTop; +//canvas.width = 150; +//canvas.height = 150; + +/* let isPainting = false; let lineWidth = 5; let strokeColor = "#000000"; let startX; let startY; +function drawGrid(lineInterval, lineColor) { + // Set the color of the grid lines + ctx.strokeStyle = lineColor; + ctx.lineWidth = 1; // Set the gridline thickness + + // Draw vertical grid lines + for (let x = 0; x <= canvas.width; x += lineInterval) { + ctx.beginPath(); + ctx.moveTo(x, 0); // Start from top of the canvas + ctx.lineTo(x, canvas.height); // Draw line down to the bottom + ctx.stroke(); + } + + // Draw horizontal grid lines + for (let y = 0; y <= canvas.height; y += lineInterval) { + ctx.beginPath(); + ctx.moveTo(0, y); // Start from the left of the canvas + ctx.lineTo(canvas.width, y); // Draw line across to the right + ctx.stroke(); + } + ctx.beginPath(); +} + +// Call the drawGrid function with 50px intervals and a light gray color +drawGrid(10, '#A9A9A9'); + toolbar.addEventListener("click", e => { if (e.target.id === "clear"){ ctx.clearRect(0, 0, canvas.width, canvas.height); + + drawGrid(10, '#A9A9A9'); } if (e.target.id === "lineWidth"){ @@ -53,4 +100,5 @@ canvas.addEventListener("mouseup", (e) => { ctx.beginPath(); }); -canvas.addEventListener("mousemove", draw); \ No newline at end of file +canvas.addEventListener("mousemove", draw); +*/ \ No newline at end of file From e86cd2d8feea8ef0151900929f24f64bad0629d0 Mon Sep 17 00:00:00 2001 From: th3keyboard Date: Wed, 16 Oct 2024 18:51:34 -0500 Subject: [PATCH 11/29] added confirmation and forgot password pages --- static/confirmation.css | 5 ++++ static/confirmation.html | 23 +++++++++++++++++ static/forgotpassword.css | 0 static/forgotpassword.html | 15 +++++++++++ static/login.css | 51 ++++++++++++++++++++++++++++++++++++++ static/login.html | 17 +++++++------ static/register.html | 4 +-- 7 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 static/confirmation.css create mode 100644 static/confirmation.html create mode 100644 static/forgotpassword.css create mode 100644 static/forgotpassword.html create mode 100644 static/login.css diff --git a/static/confirmation.css b/static/confirmation.css new file mode 100644 index 0000000..72da6ed --- /dev/null +++ b/static/confirmation.css @@ -0,0 +1,5 @@ +input[type=number]::-webkit-inner-spin-button, +input[type=number]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} \ No newline at end of file diff --git a/static/confirmation.html b/static/confirmation.html new file mode 100644 index 0000000..0c87b26 --- /dev/null +++ b/static/confirmation.html @@ -0,0 +1,23 @@ + + + + + + + UTSA Placeholders + + + + + + +
+ Check your email for a 6 digit code and enter it below. +
+ +
+ +
+ + + \ No newline at end of file diff --git a/static/forgotpassword.css b/static/forgotpassword.css new file mode 100644 index 0000000..e69de29 diff --git a/static/forgotpassword.html b/static/forgotpassword.html new file mode 100644 index 0000000..cf8c163 --- /dev/null +++ b/static/forgotpassword.html @@ -0,0 +1,15 @@ + + + + + + + UTSA Placeholders + + + + + + + + \ No newline at end of file diff --git a/static/login.css b/static/login.css new file mode 100644 index 0000000..8409690 --- /dev/null +++ b/static/login.css @@ -0,0 +1,51 @@ +:root { + --utsa-orange: #f15a22; + --utsa-blue: #0c2340; +} + +html { + background-color: var(--utsa-orange); +} + +#lform { + border-radius: 10px; + border: solid var(--utsa-orange) 2px; + margin: 100px; + padding: 30px; + background-color: white; +} + +#formbg { + background-color: var(--utsa-orange); +} + +#h3bg { + background-color: var(--utsa-blue); +} + +h3 { + border-radius: 10px; + padding: 20px; + margin: 10px; + background-color: var(--utsa-blue); + color: var(--utsa-orange); + font-size: 40px; + font-family: Verdana, Geneva, Tahoma, sans-serif; +} + +.form-label { + font-family: Verdana, Geneva, Tahoma, sans-serif; +} + +.form-control { + font-family: Verdana, Geneva, Tahoma, sans-serif; +} + +.form-text { + padding: 3px; + font-family: Verdana, Geneva, Tahoma, sans-serif; +} + +.loginlinks { + font-size: 12px; +} \ No newline at end of file diff --git a/static/login.html b/static/login.html index 199609a..69679e1 100644 --- a/static/login.html +++ b/static/login.html @@ -7,12 +7,12 @@ UTSA Placeholders + -
-

Login

-
-
+

Login

+
+
@@ -22,14 +22,17 @@
+ +
-
- Don't have an account? Register here. -
diff --git a/static/register.html b/static/register.html index 2a566b3..2dd9030 100644 --- a/static/register.html +++ b/static/register.html @@ -12,7 +12,7 @@

Registration

-
+
@@ -35,7 +35,7 @@

- +
From 8e6ff08eb4edde279455daffd53a6d83c10d35b2 Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 16 Oct 2024 22:30:14 -0500 Subject: [PATCH 12/29] Added point class --- static/canvas.js | 64 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/static/canvas.js b/static/canvas.js index b3de37e..06df99a 100644 --- a/static/canvas.js +++ b/static/canvas.js @@ -1,23 +1,67 @@ const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); -canvas.width = 500; -canvas.height = 500; +const canvasSize = 500; const dataSize = 1000; +canvas.width = canvasSize; +canvas.height = canvasSize; -let x; -let y; -let pixelSize = canvas.width / dataSize; +class Point { + // new Point(x, y) + constructor(x, y) { + this.x = x; + this.y = y; + } + // Convert canvas position to data position + canvasToData() { + return new Point( + Math.round(this.x / canvasSize * dataSize), + Math.round(this.y / canvasSize * dataSize) + ); + } + // Convert data position to canvas position + dataToCanvas() { + return new Point( + this.x / dataSize * canvasSize, + this.y / dataSize * canvasSize + ); + } + // Convert to array index + index() { + return this.y * dataSize + this.x; + } + // Basic math functions + plus(otherPoint) { + return new Point( + this.x + otherPoint.x, + this.y + otherPoint.y + ); + } + minus(otherPoint) { + return new Point( + this.x - otherPoint.x, + this.y - otherPoint.y + ); + } + scaleBy(number) { + return new Point( + this.x * number, + this.y * number + ); + } +} + +let pixelSize = canvasSize / dataSize; const centerPixel = pixelSize / 2; -function draw() { +function draw(x, y) { ctx.fillRect(x, y, pixelSize, pixelSize); } canvas.addEventListener("mousedown", (e) => { - x = e.clientX - canvas.getBoundingClientRect().left - centerPixel; - y = e.clientY - canvas.getBoundingClientRect().top - centerPixel; - draw(); + let x = e.clientX - canvas.getBoundingClientRect().left - centerPixel; + let y = e.clientY - canvas.getBoundingClientRect().top - centerPixel; + draw(x, y); }) //const toolbar = document.getElementById("toolbar"); @@ -101,4 +145,4 @@ canvas.addEventListener("mouseup", (e) => { }); canvas.addEventListener("mousemove", draw); -*/ \ No newline at end of file +*/ From 3462fc966cbddd60a2d5520d374149df360e0367 Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 23 Oct 2024 16:44:49 -0500 Subject: [PATCH 13/29] Prototype for backend email confirmaion --- server.go | 21 +-------------------- static/register.html | 4 ++-- users.go | 16 +++++++++++++++- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/server.go b/server.go index 95c38c6..d9f2c3b 100644 --- a/server.go +++ b/server.go @@ -1,7 +1,6 @@ package main import ( - "encoding/json" "flag" "fmt" "log" @@ -13,22 +12,11 @@ import ( const CANVAS_WIDTH = 1000 const CANVAS_HEIGHT = 1000 -type Canvas struct { - Data [CANVAS_WIDTH * CANVAS_HEIGHT]byte `json:"data"` - Width int `json:"width"` - Height int `json:"height"` -} - type Server struct { // Registered user information Users map[string]UserData // Login sessions Sessions *sessions.CookieStore - Canvas Canvas -} - -func (s *Server) canvas(w http.ResponseWriter, r *http.Request) { - json.NewEncoder(w).Encode(s.canvas) } func main() { @@ -43,13 +31,6 @@ func main() { server := Server{ Users: make(map[string]UserData), Sessions: sessions.NewCookieStore(secret), - Canvas: Canvas{}, - } - - for x := 0; x < CANVAS_WIDTH; x++ { - for y := 0; y < CANVAS_HEIGHT; y++ { - server.Canvas.Data[y*CANVAS_HEIGHT+x] = byte(CANVAS_WIDTH / x) - } } // Host static files @@ -66,7 +47,7 @@ func main() { http.Redirect(w, r, "/", http.StatusFound) }) http.HandleFunc("/secret", server.secret) - http.HandleFunc("/canvas", canvas) + http.HandleFunc("/confirm-email", server.handle_confirmation) // Start web server at 127.0.0.1:8080 fmt.Printf("Listening to %s on port %s...\n", address, port) err := http.ListenAndServe(address+":"+port, nil) diff --git a/static/register.html b/static/register.html index 2dd9030..2a566b3 100644 --- a/static/register.html +++ b/static/register.html @@ -12,7 +12,7 @@

Registration

-
+
@@ -35,7 +35,7 @@

- +
diff --git a/users.go b/users.go index 450be15..d9de3e6 100644 --- a/users.go +++ b/users.go @@ -13,6 +13,7 @@ import ( const SESSION_COOKIE_NAME = "utsa-place-session" const SESSION_AUTH = "auth" const SESSION_STARTED = "age" +const SESSION_CONFIRMED = "confirmed" const ENCRYPTION_STRENGTH = 14 @@ -126,16 +127,29 @@ func (s *Server) handle_register(w http.ResponseWriter, r *http.Request) { // Make session valid session.Values[SESSION_AUTH] = true session.Values[SESSION_STARTED] = now.String() + session.Values[SESSION_CONFIRMED] = false // Send session token to browser session.Save(r, w) // Redirect to index.html fmt.Println("Registered user: ", email) - http.Redirect(w, r, "/", http.StatusFound) + http.Redirect(w, r, "/confirm-email", http.StatusFound) default: http.Error(w, "Forbidden", http.StatusForbidden) } } +func (s *Server) handle_confirmation(w http.ResponseWriter, r *http.Request) { + session, err := s.Sessions.Get(r, SESSION_COOKIE_NAME) + if err != nil { + s.handle_logout(w, r) + http.Redirect(w, r, "/register", http.StatusFound) + return + } + confirmed := session.Values[SESSION_CONFIRMED] + fmt.Println("Session confirmed: ", confirmed) + http.ServeFile(w, r, "./static/confirmation.html") +} + func (s *Server) secret(w http.ResponseWriter, r *http.Request) { session, _ := s.Sessions.Get(r, SESSION_COOKIE_NAME) From d25e5c7d1a471225bcac3aecd6e58d2326b2d9eb Mon Sep 17 00:00:00 2001 From: adanrsantos Date: Wed, 23 Oct 2024 18:01:27 -0500 Subject: [PATCH 14/29] working on canvas --- static/canvas.js | 125 +++++++++++++---------------------------------- 1 file changed, 35 insertions(+), 90 deletions(-) diff --git a/static/canvas.js b/static/canvas.js index 06df99a..191e672 100644 --- a/static/canvas.js +++ b/static/canvas.js @@ -3,9 +3,39 @@ const ctx = canvas.getContext("2d"); const canvasSize = 500; const dataSize = 1000; +const gridCount = 10 ; canvas.width = canvasSize; canvas.height = canvasSize; +//let pixelSize = canvasSize / dataSize; +let pixelSize = canvasSize / gridCount; +const centerPixel = pixelSize / 2; + +function drawGrid() { + ctx.strokeStyle = '#000'; // Set grid line color + ctx.lineWidth = 1; // Set grid line width + + // Draw horizontal lines + for (let i = 0; i <= gridCount; i++) { + let y = i * pixelSize; + ctx.beginPath(); + ctx.moveTo(0, y); // Start the line at the left edge (x = 0) + ctx.lineTo(canvasSize, y); // Draw to the right edge (x = canvasSize) + ctx.stroke(); + } + + // Draw vertical lines + for (let i = 0; i <= gridCount; i++) { + let x = i * pixelSize; + ctx.beginPath(); + ctx.moveTo(x, 0); // Start the line at the top edge (y = 0) + ctx.lineTo(x, canvasSize); // Draw to the bottom edge (y = canvasSize) + ctx.stroke(); + } +} + +drawGrid(); + class Point { // new Point(x, y) constructor(x, y) { @@ -51,98 +81,13 @@ class Point { } } -let pixelSize = canvasSize / dataSize; -const centerPixel = pixelSize / 2; - -function draw(x, y) { - ctx.fillRect(x, y, pixelSize, pixelSize); +function draw(point) { + ctx.fillRect(point.x, point.y, pixelSize, pixelSize); } canvas.addEventListener("mousedown", (e) => { let x = e.clientX - canvas.getBoundingClientRect().left - centerPixel; let y = e.clientY - canvas.getBoundingClientRect().top - centerPixel; - draw(x, y); -}) - -//const toolbar = document.getElementById("toolbar"); -//const strokePicker = document.getElementById("stroke"); -//const canvasOffsetX = canvas.offsetLeft; -//const canvasOffsetY = canvas.offsetTop; -//canvas.width = 150; -//canvas.height = 150; - -/* -let isPainting = false; -let lineWidth = 5; -let strokeColor = "#000000"; -let startX; -let startY; - -function drawGrid(lineInterval, lineColor) { - // Set the color of the grid lines - ctx.strokeStyle = lineColor; - ctx.lineWidth = 1; // Set the gridline thickness - - // Draw vertical grid lines - for (let x = 0; x <= canvas.width; x += lineInterval) { - ctx.beginPath(); - ctx.moveTo(x, 0); // Start from top of the canvas - ctx.lineTo(x, canvas.height); // Draw line down to the bottom - ctx.stroke(); - } - - // Draw horizontal grid lines - for (let y = 0; y <= canvas.height; y += lineInterval) { - ctx.beginPath(); - ctx.moveTo(0, y); // Start from the left of the canvas - ctx.lineTo(canvas.width, y); // Draw line across to the right - ctx.stroke(); - } - ctx.beginPath(); -} - -// Call the drawGrid function with 50px intervals and a light gray color -drawGrid(10, '#A9A9A9'); - -toolbar.addEventListener("click", e => { - if (e.target.id === "clear"){ - ctx.clearRect(0, 0, canvas.width, canvas.height); - - drawGrid(10, '#A9A9A9'); - } - - if (e.target.id === "lineWidth"){ - lineWidth = e.target.value; - } -}); - -strokePicker.addEventListener("input", (e) => { - strokeColor = e.target.value; -}); - -const draw = (e) => { - if (!isPainting){ - return; - } - ctx.lineWidth = lineWidth; - ctx.strokeStyle = strokeColor; - ctx.lineCap = "round"; - - ctx.lineTo(e.clientX - canvasOffsetX, e.clientY - canvasOffsetY); - ctx.stroke(); -} - -canvas.addEventListener("mousedown", (e) => { - isPainting = true; - startX = e.clientX; - startY = e.clientY; -}); - -canvas.addEventListener("mouseup", (e) => { - isPainting = false; - ctx.stroke(); - ctx.beginPath(); -}); - -canvas.addEventListener("mousemove", draw); -*/ + let point = new Point(x, y) + draw(point); +}); \ No newline at end of file From f3de96ac54a483cde3486703e78d8d83bc66e059 Mon Sep 17 00:00:00 2001 From: Logan Gatlin Date: Wed, 23 Oct 2024 18:09:48 -0500 Subject: [PATCH 15/29] fixed to grid --- email.go | 42 ++++++++++++++++++++++++++++++++++++++++++ go.mod | 6 +++++- go.sum | 4 ++++ server.go | 1 + static/canvas.js | 11 ++++++----- users.go | 22 ++++++++++++++-------- 6 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 email.go diff --git a/email.go b/email.go new file mode 100644 index 0000000..06b0eb8 --- /dev/null +++ b/email.go @@ -0,0 +1,42 @@ +package main + +import ( + "crypto/tls" + "fmt" + "gopkg.in/gomail.v2" + "net/smtp" +) + +func Send(body string) { + from := "xterminate18181@gmail.com" + pass := "nvzp fodm ihzw gter" + to := "joson.anthoney@frontdomain.org" + + msg := "From: " + from + "\n" + + "To: " + to + "\n" + + "Subject: Hello there\n\n" + + body + + err := smtp.SendMail("smtp.gmail.com:587", + smtp.PlainAuth("", from, pass, "smtp.gmail.com"), + from, []string{to}, []byte(msg)) + + if err != nil { + fmt.Printf("smtp error: %s", err) + return + } + fmt.Println("Successfully sended to " + to) +} + +func MailTest() { + d := gomail.NewDialer("smtp.gmail.com", 587, "xterminate18181@gmail.com", "nvzp fodm ihzw gter") + d.TLSConfig = &tls.Config{InsecureSkipVerify: true} + m := gomail.NewMessage() + m.SetHeader("From", "xterminate18181@gmail.com") + m.SetHeader("To", "logan@gatlintc.com") + m.SetHeader("Subject", "Confirm Email") + m.SetBody("text/html", "Test body") + if err := d.DialAndSend(m); err != nil { + panic(err) + } +} diff --git a/go.mod b/go.mod index 55c3846..69ffa76 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,8 @@ require ( golang.org/x/crypto v0.27.0 ) -require github.com/gorilla/securecookie v1.1.2 // indirect +require ( + github.com/gorilla/securecookie v1.1.2 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect +) diff --git a/go.sum b/go.sum index b04990b..1b240e4 100644 --- a/go.sum +++ b/go.sum @@ -6,3 +6,7 @@ github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzq github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= diff --git a/server.go b/server.go index d9f2c3b..a3752fe 100644 --- a/server.go +++ b/server.go @@ -20,6 +20,7 @@ type Server struct { } func main() { + Send("test body") // Grab command line arguments ipFlag := flag.String("ip", "127.0.0.1", "IP address to receive traffic from") portFlag := flag.String("port", "8080", "Port to receive traffic from") diff --git a/static/canvas.js b/static/canvas.js index 191e672..5203ebe 100644 --- a/static/canvas.js +++ b/static/canvas.js @@ -45,15 +45,15 @@ class Point { // Convert canvas position to data position canvasToData() { return new Point( - Math.round(this.x / canvasSize * dataSize), - Math.round(this.y / canvasSize * dataSize) + Math.round(this.x / canvasSize * gridCount), + Math.round(this.y / canvasSize * gridCount) ); } // Convert data position to canvas position dataToCanvas() { return new Point( - this.x / dataSize * canvasSize, - this.y / dataSize * canvasSize + this.x / gridCount * canvasSize, + this.y / gridCount * canvasSize ); } // Convert to array index @@ -82,6 +82,7 @@ class Point { } function draw(point) { + point = point.canvasToData().dataToCanvas() ctx.fillRect(point.x, point.y, pixelSize, pixelSize); } @@ -90,4 +91,4 @@ canvas.addEventListener("mousedown", (e) => { let y = e.clientY - canvas.getBoundingClientRect().top - centerPixel; let point = new Point(x, y) draw(point); -}); \ No newline at end of file +}); diff --git a/users.go b/users.go index d9de3e6..329cb7d 100644 --- a/users.go +++ b/users.go @@ -14,6 +14,7 @@ const SESSION_COOKIE_NAME = "utsa-place-session" const SESSION_AUTH = "auth" const SESSION_STARTED = "age" const SESSION_CONFIRMED = "confirmed" +const SESSION_CONFIRM_KEY = "confirm-key" const ENCRYPTION_STRENGTH = 14 @@ -139,15 +140,20 @@ func (s *Server) handle_register(w http.ResponseWriter, r *http.Request) { } func (s *Server) handle_confirmation(w http.ResponseWriter, r *http.Request) { - session, err := s.Sessions.Get(r, SESSION_COOKIE_NAME) - if err != nil { - s.handle_logout(w, r) - http.Redirect(w, r, "/register", http.StatusFound) - return + switch r.Method { + case http.MethodGet: + session, err := s.Sessions.Get(r, SESSION_COOKIE_NAME) + if err != nil { + s.handle_logout(w, r) + http.Redirect(w, r, "/register", http.StatusFound) + return + } + confirmed := session.Values[SESSION_CONFIRMED] + fmt.Println("Session confirmed: ", confirmed) + http.ServeFile(w, r, "./static/confirmation.html") + case http.MethodPost: + default: } - confirmed := session.Values[SESSION_CONFIRMED] - fmt.Println("Session confirmed: ", confirmed) - http.ServeFile(w, r, "./static/confirmation.html") } func (s *Server) secret(w http.ResponseWriter, r *http.Request) { From ac664eddf3b3d36276b18736ef953ee138f91cac Mon Sep 17 00:00:00 2001 From: reddishquill371 Date: Wed, 23 Oct 2024 18:12:27 -0500 Subject: [PATCH 16/29] worked on forgot password page --- static/forgotpassword.css | 47 ++++++++++++++++++++++++++++++++++++++ static/forgotpassword.html | 22 +++++++++++++++++- static/register.html | 2 +- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/static/forgotpassword.css b/static/forgotpassword.css index e69de29..cf481db 100644 --- a/static/forgotpassword.css +++ b/static/forgotpassword.css @@ -0,0 +1,47 @@ +:root { + --utsa-orange: #f15a22; + --utsa-blue: #0c2340; +} + +html { + background-color: var(--utsa-orange); +} + +#rform { + border-radius: 10px; + border: solid var(--utsa-orange) 2px; + margin: 100px; + padding: 30px; + background-color: white; +} + +#formbg { + background-color: var(--utsa-orange); +} + +#h3bg { + background-color: var(--utsa-blue); +} + +h3 { + border-radius: 10px; + padding: 20px; + margin: 10px; + background-color: var(--utsa-blue); + color: var(--utsa-orange); + font-size: 40px; + font-family: Verdana, Geneva, Tahoma, sans-serif; +} + +.form-label { + font-family: Verdana, Geneva, Tahoma, sans-serif; +} + +.form-control { + font-family: Verdana, Geneva, Tahoma, sans-serif; +} + +.form-text { + padding: 3px; + font-family: Verdana, Geneva, Tahoma, sans-serif; +} \ No newline at end of file diff --git a/static/forgotpassword.html b/static/forgotpassword.html index cf8c163..6051400 100644 --- a/static/forgotpassword.html +++ b/static/forgotpassword.html @@ -10,6 +10,26 @@ - +

UTSA Place

+
+
+
+ Forgot Password +
+

+
+ + +
Enter your email and we will send you a 6-digit recovery code.
+
+ +
+
+ + Click here to go back to login. + +
+
+
\ No newline at end of file diff --git a/static/register.html b/static/register.html index 2a566b3..5de67c3 100644 --- a/static/register.html +++ b/static/register.html @@ -35,7 +35,7 @@

- +
From 09602d48f1c3e1114045446f4cc3c4b5f4926777 Mon Sep 17 00:00:00 2001 From: reddishquill371 Date: Wed, 23 Oct 2024 18:26:12 -0500 Subject: [PATCH 17/29] reverted unnecessary changes to register.html --- static/register.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/register.html b/static/register.html index 5de67c3..2a566b3 100644 --- a/static/register.html +++ b/static/register.html @@ -35,7 +35,7 @@

- +
From 502a87ef2458b4a44a2a0ee0529b7ab6bcb552a9 Mon Sep 17 00:00:00 2001 From: reddishquill371 Date: Wed, 23 Oct 2024 18:32:02 -0500 Subject: [PATCH 18/29] added links for forgotpassword page and confirmation page --- static/index.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/static/index.html b/static/index.html index 66c10d2..21eff23 100644 --- a/static/index.html +++ b/static/index.html @@ -20,6 +20,12 @@
  • Login
  • +
  • + Forgot Password +
  • +
  • + Confirmation +
  • Log Out
  • From 2bc52182f70e9cc80a685bcfb22c054120b3bd6e Mon Sep 17 00:00:00 2001 From: th3keyboard Date: Wed, 23 Oct 2024 18:39:57 -0500 Subject: [PATCH 19/29] email confirm bootstrap, placeholder for sending email again to user added --- static/confirmation.css | 12 +++++++++++- static/confirmation.html | 23 ++++++++++++++++------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/static/confirmation.css b/static/confirmation.css index 72da6ed..a3ddfeb 100644 --- a/static/confirmation.css +++ b/static/confirmation.css @@ -2,4 +2,14 @@ input[type=number]::-webkit-inner-spin-button, input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; -} \ No newline at end of file +} + + +html { + background-color: black; +} +/* + + lol + + */ \ No newline at end of file diff --git a/static/confirmation.html b/static/confirmation.html index 0c87b26..e4506a0 100644 --- a/static/confirmation.html +++ b/static/confirmation.html @@ -1,5 +1,5 @@ - + @@ -7,16 +7,25 @@ UTSA Placeholders - + -
    -
    - Check your email for a 6 digit code and enter it below. + +
    + Check your email for a 6-digit code and enter it below.
    -
    - +
    + +
    + +
    + +
    + + From 984dfaab5b3175997043982b52dc3bb38c108bd0 Mon Sep 17 00:00:00 2001 From: adanrsantos Date: Thu, 24 Oct 2024 23:51:19 -0500 Subject: [PATCH 20/29] added to canvas restriction --- static/canvas.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/static/canvas.js b/static/canvas.js index 5203ebe..706bb94 100644 --- a/static/canvas.js +++ b/static/canvas.js @@ -3,21 +3,27 @@ const ctx = canvas.getContext("2d"); const canvasSize = 500; const dataSize = 1000; -const gridCount = 10 ; +const gridCount = 10; +let zoom = 1.0; +const centerPixel = getPixelSize() / 2; + +function getPixelSize() { + return canvasSize / gridCount * zoom; +} + canvas.width = canvasSize; canvas.height = canvasSize; -//let pixelSize = canvasSize / dataSize; -let pixelSize = canvasSize / gridCount; -const centerPixel = pixelSize / 2; - function drawGrid() { + if (getPixelSize() < 5) { + return; + } ctx.strokeStyle = '#000'; // Set grid line color ctx.lineWidth = 1; // Set grid line width // Draw horizontal lines for (let i = 0; i <= gridCount; i++) { - let y = i * pixelSize; + let y = i * getPixelSize(); ctx.beginPath(); ctx.moveTo(0, y); // Start the line at the left edge (x = 0) ctx.lineTo(canvasSize, y); // Draw to the right edge (x = canvasSize) @@ -26,7 +32,7 @@ function drawGrid() { // Draw vertical lines for (let i = 0; i <= gridCount; i++) { - let x = i * pixelSize; + let x = i * getPixelSize(); ctx.beginPath(); ctx.moveTo(x, 0); // Start the line at the top edge (y = 0) ctx.lineTo(x, canvasSize); // Draw to the bottom edge (y = canvasSize) @@ -83,7 +89,7 @@ class Point { function draw(point) { point = point.canvasToData().dataToCanvas() - ctx.fillRect(point.x, point.y, pixelSize, pixelSize); + ctx.fillRect(point.x, point.y, getPixelSize(), getPixelSize()); } canvas.addEventListener("mousedown", (e) => { From 8db276823b1dfbd5b38ebff7f135f978a156ee4b Mon Sep 17 00:00:00 2001 From: Logan Gatlin Date: Sun, 27 Oct 2024 03:47:12 -0500 Subject: [PATCH 21/29] Fixed bug --- users.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/users.go b/users.go index 329cb7d..55cfca8 100644 --- a/users.go +++ b/users.go @@ -129,6 +129,7 @@ func (s *Server) handle_register(w http.ResponseWriter, r *http.Request) { session.Values[SESSION_AUTH] = true session.Values[SESSION_STARTED] = now.String() session.Values[SESSION_CONFIRMED] = false + session.Values[SESSION_CONFIRM_KEY] = "asdf" // Send session token to browser session.Save(r, w) // Redirect to index.html @@ -140,18 +141,23 @@ func (s *Server) handle_register(w http.ResponseWriter, r *http.Request) { } func (s *Server) handle_confirmation(w http.ResponseWriter, r *http.Request) { + session, err := s.Sessions.Get(r, SESSION_COOKIE_NAME) + if err != nil { + s.handle_logout(w, r) + http.Redirect(w, r, "/register", http.StatusFound) + return + } switch r.Method { case http.MethodGet: - session, err := s.Sessions.Get(r, SESSION_COOKIE_NAME) - if err != nil { - s.handle_logout(w, r) - http.Redirect(w, r, "/register", http.StatusFound) - return + confirmed := session.Values[SESSION_CONFIRMED].(bool) + fmt.Println("User email confirmed: ", confirmed) + if confirmed { + http.Redirect(w, r, "/", http.StatusFound) + } else { + http.ServeFile(w, r, "./static/confirmation.html") } - confirmed := session.Values[SESSION_CONFIRMED] - fmt.Println("Session confirmed: ", confirmed) - http.ServeFile(w, r, "./static/confirmation.html") case http.MethodPost: + code := r.FormValue("code") default: } } From e12821f92469aeaf04a962a1339307fd505262f0 Mon Sep 17 00:00:00 2001 From: adanrsantos Date: Tue, 29 Oct 2024 21:32:06 -0500 Subject: [PATCH 22/29] working on the zooming in --- static/canvas.html | 9 ++++---- static/canvas.js | 55 +++++++++++++++++++++++++++++++++------------- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/static/canvas.html b/static/canvas.html index 5a36a46..3296512 100644 --- a/static/canvas.html +++ b/static/canvas.html @@ -18,16 +18,15 @@
    +
    + +
    - - -
    -
    - +
    diff --git a/static/canvas.js b/static/canvas.js index 706bb94..19a5807 100644 --- a/static/canvas.js +++ b/static/canvas.js @@ -3,12 +3,18 @@ const ctx = canvas.getContext("2d"); const canvasSize = 500; const dataSize = 1000; -const gridCount = 10; let zoom = 1.0; -const centerPixel = getPixelSize() / 2; + +function getGridCount() { + return canvasSize / zoom; +} + +function getCenterPixel() { + return getPixelSize() / 2; +} function getPixelSize() { - return canvasSize / gridCount * zoom; + return canvasSize / getGridCount() * zoom; } canvas.width = canvasSize; @@ -20,18 +26,16 @@ function drawGrid() { } ctx.strokeStyle = '#000'; // Set grid line color ctx.lineWidth = 1; // Set grid line width - // Draw horizontal lines - for (let i = 0; i <= gridCount; i++) { + for (let i = 0; i <= getGridCount(); i++) { let y = i * getPixelSize(); ctx.beginPath(); ctx.moveTo(0, y); // Start the line at the left edge (x = 0) ctx.lineTo(canvasSize, y); // Draw to the right edge (x = canvasSize) ctx.stroke(); } - // Draw vertical lines - for (let i = 0; i <= gridCount; i++) { + for (let i = 0; i <= getGridCount(); i++) { let x = i * getPixelSize(); ctx.beginPath(); ctx.moveTo(x, 0); // Start the line at the top edge (y = 0) @@ -51,15 +55,15 @@ class Point { // Convert canvas position to data position canvasToData() { return new Point( - Math.round(this.x / canvasSize * gridCount), - Math.round(this.y / canvasSize * gridCount) + Math.round(this.x / canvasSize * getGridCount()), + Math.round(this.y / canvasSize * getGridCount()) ); } // Convert data position to canvas position dataToCanvas() { return new Point( - this.x / gridCount * canvasSize, - this.y / gridCount * canvasSize + this.x / getGridCount() * canvasSize, + this.y / getGridCount() * canvasSize ); } // Convert to array index @@ -87,14 +91,35 @@ class Point { } } -function draw(point) { +function draw(point, color) { point = point.canvasToData().dataToCanvas() + console.log(point.x + " " + point.y); + ctx.fillStyle = color; ctx.fillRect(point.x, point.y, getPixelSize(), getPixelSize()); } canvas.addEventListener("mousedown", (e) => { - let x = e.clientX - canvas.getBoundingClientRect().left - centerPixel; - let y = e.clientY - canvas.getBoundingClientRect().top - centerPixel; + let x = e.clientX - canvas.getBoundingClientRect().left - getCenterPixel(); + let y = e.clientY - canvas.getBoundingClientRect().top - getCenterPixel(); + let color = document.getElementById("stroke").value; let point = new Point(x, y) - draw(point); + draw(point, color); +}); + +document.getElementById("zoomIn").addEventListener("click", () => { + if (zoom < 100.0){ + zoom += 1.0; + } + console.log(zoom); + ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas + drawGrid(); // Redraw the grid with updated zoom +}); + +document.getElementById("zoomOut").addEventListener("click", () => { + if (zoom > 1.0){ + zoom -= 1.0; + } + console.log(zoom); + ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas + drawGrid(); // Redraw the grid with updated zoom }); From 83d729a2a34cd3cbaf5225476086ef79dbf2c6f3 Mon Sep 17 00:00:00 2001 From: adanrsantos Date: Tue, 29 Oct 2024 22:36:15 -0500 Subject: [PATCH 23/29] figuring out the pixels but need to work on zoom --- static/canvas.js | 88 +++++++++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/static/canvas.js b/static/canvas.js index 19a5807..3817bbe 100644 --- a/static/canvas.js +++ b/static/canvas.js @@ -2,41 +2,36 @@ const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); const canvasSize = 500; -const dataSize = 1000; let zoom = 1.0; - -function getGridCount() { - return canvasSize / zoom; -} - -function getCenterPixel() { - return getPixelSize() / 2; -} - -function getPixelSize() { - return canvasSize / getGridCount() * zoom; -} +let baseGridCount = 500; // Base grid count at zoom = 1 +let gridCount = baseGridCount; +let pixelSize = canvasSize / gridCount; canvas.width = canvasSize; canvas.height = canvasSize; +function updateGridCount() { + return Math.floor(baseGridCount / zoom); // Adjust grid count based on zoom +} + +function updatePixelSize() { + return canvasSize / gridCount; // Update pixel size based on new grid count +} + function drawGrid() { - if (getPixelSize() < 5) { - return; - } ctx.strokeStyle = '#000'; // Set grid line color ctx.lineWidth = 1; // Set grid line width // Draw horizontal lines - for (let i = 0; i <= getGridCount(); i++) { - let y = i * getPixelSize(); + for (let i = 0; i <= gridCount; i++) { + let y = i * pixelSize; ctx.beginPath(); ctx.moveTo(0, y); // Start the line at the left edge (x = 0) ctx.lineTo(canvasSize, y); // Draw to the right edge (x = canvasSize) ctx.stroke(); } // Draw vertical lines - for (let i = 0; i <= getGridCount(); i++) { - let x = i * getPixelSize(); + for (let i = 0; i <= gridCount; i++) { + let x = i * pixelSize; ctx.beginPath(); ctx.moveTo(x, 0); // Start the line at the top edge (y = 0) ctx.lineTo(x, canvasSize); // Draw to the bottom edge (y = canvasSize) @@ -44,7 +39,7 @@ function drawGrid() { } } -drawGrid(); +//drawGrid(); class Point { // new Point(x, y) @@ -55,15 +50,15 @@ class Point { // Convert canvas position to data position canvasToData() { return new Point( - Math.round(this.x / canvasSize * getGridCount()), - Math.round(this.y / canvasSize * getGridCount()) + Math.round(this.x / canvasSize * gridCount), + Math.round(this.y / canvasSize * gridCount) ); } // Convert data position to canvas position dataToCanvas() { return new Point( - this.x / getGridCount() * canvasSize, - this.y / getGridCount() * canvasSize + this.x / gridCount * canvasSize, + this.y / gridCount * canvasSize ); } // Convert to array index @@ -92,34 +87,49 @@ class Point { } function draw(point, color) { - point = point.canvasToData().dataToCanvas() - console.log(point.x + " " + point.y); + point = point.canvasToData().dataToCanvas(); ctx.fillStyle = color; - ctx.fillRect(point.x, point.y, getPixelSize(), getPixelSize()); + ctx.fillRect(point.x, point.y, pixelSize, pixelSize); } canvas.addEventListener("mousedown", (e) => { - let x = e.clientX - canvas.getBoundingClientRect().left - getCenterPixel(); - let y = e.clientY - canvas.getBoundingClientRect().top - getCenterPixel(); + let x = e.clientX - canvas.getBoundingClientRect().left - (pixelSize / 2); + let y = e.clientY - canvas.getBoundingClientRect().top - (pixelSize / 2); let color = document.getElementById("stroke").value; - let point = new Point(x, y) + let point = new Point(x, y); draw(point, color); }); document.getElementById("zoomIn").addEventListener("click", () => { - if (zoom < 100.0){ - zoom += 1.0; + if (zoom < 100.0) { + if (zoom == 1.0){ + zoom = 4.0; + } + else{ + zoom += 4.0; + } + gridCount = updateGridCount(); + pixelSize = updatePixelSize(); + console.log(zoom); + console.log(gridCount); } - console.log(zoom); - ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas + ctx.clearRect(0, 0, canvas.width, canvas.height); drawGrid(); // Redraw the grid with updated zoom }); document.getElementById("zoomOut").addEventListener("click", () => { - if (zoom > 1.0){ - zoom -= 1.0; + if (zoom > 1.0) { + if (zoom <= 4.0){ + zoom = 1.0; + } + else{ + zoom -= 4.0; + } + gridCount = updateGridCount(); + pixelSize = updatePixelSize(); + console.log(zoom); + console.log(gridCount); } - console.log(zoom); - ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas + ctx.clearRect(0, 0, canvas.width, canvas.height); drawGrid(); // Redraw the grid with updated zoom }); From 8df10018e72a5c46397f3fd466cfd725c8889c78 Mon Sep 17 00:00:00 2001 From: adanrsantos Date: Wed, 30 Oct 2024 16:30:24 -0500 Subject: [PATCH 24/29] trying to take a quadrant approach --- static/canvas.js | 77 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/static/canvas.js b/static/canvas.js index 3817bbe..689f7d5 100644 --- a/static/canvas.js +++ b/static/canvas.js @@ -4,14 +4,14 @@ const ctx = canvas.getContext("2d"); const canvasSize = 500; let zoom = 1.0; let baseGridCount = 500; // Base grid count at zoom = 1 -let gridCount = baseGridCount; +let gridCount = 500; let pixelSize = canvasSize / gridCount; canvas.width = canvasSize; canvas.height = canvasSize; function updateGridCount() { - return Math.floor(baseGridCount / zoom); // Adjust grid count based on zoom + return Math.floor(gridCount / 2); // Adjust grid count based on zoom } function updatePixelSize() { @@ -92,6 +92,7 @@ function draw(point, color) { ctx.fillRect(point.x, point.y, pixelSize, pixelSize); } +/* canvas.addEventListener("mousedown", (e) => { let x = e.clientX - canvas.getBoundingClientRect().left - (pixelSize / 2); let y = e.clientY - canvas.getBoundingClientRect().top - (pixelSize / 2); @@ -99,7 +100,77 @@ canvas.addEventListener("mousedown", (e) => { let point = new Point(x, y); draw(point, color); }); +*/ +canvas.addEventListener("mousedown", (e) => { + let x = e.clientX - canvas.getBoundingClientRect().left - (pixelSize / 2); + let y = e.clientY - canvas.getBoundingClientRect().top - (pixelSize / 2); + if (x > 249){ + if (y < 250){ + console.log("Quadrant 1"); + console.log("Previous Grid Size:", gridCount); + console.log("Previous PixelSize:", pixelSize); + gridCount = updateGridCount(); + pixelSize = updatePixelSize(); + console.log("Updated Grid Size:", gridCount); + console.log("Updated PixelSize:", pixelSize); + ctx.clearRect(0, 0, canvas.width, canvas.height); + drawStoredPixels(1); + } + else{ + console.log("Quadrant 4"); + } + } + else{ + if (y < 250){ + console.log("Quadrant 2"); + } + else{ + console.log("Quadrant 3"); + } + } + console.log("This is where the user clicked to zoom:", x); + console.log("This is where the user clicked to zoom:", y); +}); + +const pixelData = {}; + +function storePixel(x, y, color){ + pixelData[`${x},${y}`] = color; +} + +for (let i = 0; i < 500; i++){ + storePixel(i, 250, '#0000FF'); + storePixel(i, i, '#FF0000'); + storePixel(i, 499 - i, '#FFFF00'); + storePixel(250, i, '#FF6600') +} + +function drawStoredPixels(quadrant) { + if (quadrant == 1){ + for (const [key, color] of Object.entries(pixelData)) { + const [x, y] = key.split(',').map(Number); + if (x >= 250 && x <= 499 && y >= 0 && y <= 249) { + const pixelCanvasPos = new Point(x, y); + ctx.fillStyle = color; + console.log(pixelCanvasPos); + draw(pixelCanvasPos, color); + } + } + } + else{ + for (const [key, color] of Object.entries(pixelData)) { + const [x, y] = key.split(',').map(Number); + const pixelCanvasPos = new Point(x, y); + ctx.fillStyle = color; + draw(pixelCanvasPos, color); + } + } +} + +drawStoredPixels(); + +/* document.getElementById("zoomIn").addEventListener("click", () => { if (zoom < 100.0) { if (zoom == 1.0){ @@ -117,6 +188,7 @@ document.getElementById("zoomIn").addEventListener("click", () => { drawGrid(); // Redraw the grid with updated zoom }); +/* document.getElementById("zoomOut").addEventListener("click", () => { if (zoom > 1.0) { if (zoom <= 4.0){ @@ -133,3 +205,4 @@ document.getElementById("zoomOut").addEventListener("click", () => { ctx.clearRect(0, 0, canvas.width, canvas.height); drawGrid(); // Redraw the grid with updated zoom }); +*/ \ No newline at end of file From 2c524fe98084a5aa0ebc386279f6ab7957a5588b Mon Sep 17 00:00:00 2001 From: Logan Gatlin Date: Wed, 30 Oct 2024 17:14:01 -0500 Subject: [PATCH 25/29] fixed bug --- users.go | 1 - 1 file changed, 1 deletion(-) diff --git a/users.go b/users.go index 55cfca8..8df9f2b 100644 --- a/users.go +++ b/users.go @@ -157,7 +157,6 @@ func (s *Server) handle_confirmation(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./static/confirmation.html") } case http.MethodPost: - code := r.FormValue("code") default: } } From d3afa597d61ee99c259ed80d412942435d42e3f6 Mon Sep 17 00:00:00 2001 From: Logan Gatlin Date: Wed, 30 Oct 2024 17:14:54 -0500 Subject: [PATCH 26/29] Removed test email --- server.go | 1 - 1 file changed, 1 deletion(-) diff --git a/server.go b/server.go index a3752fe..d9f2c3b 100644 --- a/server.go +++ b/server.go @@ -20,7 +20,6 @@ type Server struct { } func main() { - Send("test body") // Grab command line arguments ipFlag := flag.String("ip", "127.0.0.1", "IP address to receive traffic from") portFlag := flag.String("port", "8080", "Port to receive traffic from") From c9783a0669e3f7bee16dcf1ee8b37225c8a125f8 Mon Sep 17 00:00:00 2001 From: th3keyboard Date: Wed, 30 Oct 2024 18:43:08 -0500 Subject: [PATCH 27/29] comments in code lol --- static/canvas.html | 4 +++- static/confirmation.html | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/static/canvas.html b/static/canvas.html index 3296512..031a8be 100644 --- a/static/canvas.html +++ b/static/canvas.html @@ -15,6 +15,7 @@ +
    @@ -29,7 +30,8 @@
    -
    +
    +
    diff --git a/static/confirmation.html b/static/confirmation.html index e4506a0..7e5699a 100644 --- a/static/confirmation.html +++ b/static/confirmation.html @@ -20,7 +20,8 @@
    - + +
    From eaa87cb214bd94fe19123807293bdf9b31d71ceb Mon Sep 17 00:00:00 2001 From: Logan Date: Tue, 5 Nov 2024 11:51:17 -0600 Subject: [PATCH 28/29] Started redoing canvas --- cat.jpg | Bin 0 -> 36434 bytes static/canvas.css | 3 +- static/canvas.js | 242 +++++++++------------------------------------- 3 files changed, 48 insertions(+), 197 deletions(-) create mode 100644 cat.jpg diff --git a/cat.jpg b/cat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..37e310735c015b4cf756ff7bc19ce9155a5188bb GIT binary patch literal 36434 zcmb4qWmFtZu=WxlxVu9Zx8UyX?oM!b*Fb>a65M@ZvEZ-}+zIZo!QEW~_v9NJ)a8S^3@o};7-}~7A4FdDulW=f| z@bHM(sK}_;|7ZEv1HeLrxrMofh4}>dfCU4K1@mtJKn4K3&kW|j`TjqFgNOYHfcb!c zg!rzO#{~Rm_ya8bfASIGzjyio3kQ#dO~w9E9Osid0xpNS8&KkFay=fWhDQ-K7x$M2 z8c9vhUPOE?_pK`)3(J(^nai;Ep@`pY{4YcAEA-z`AK!IAtas&y{{;5`PXy)z7VNtb z9C0{~Pn_zw|5gF0@4EM!SO8JLtvk0&3D#+hr+9KJT%KYcS;yy`z582I>4t1mC<_mz zu&xSmPFSUMaN`aWLp#EZqHB~Ah-gR`cN4)}!h*3yI)(n=s3M}U} zVsI52@20z1?w1=!`$=d=_=mY&fQc!r;@34#BOVH@(r!Jv8GIc&blVjV1>$<0tMebp$e>| zSNt|%UEB4i#B7Yn7JcjEQWt7EX!tJWv+#;3PU3o(;vNj~2s%keQyN+9F{&&PL2%OwQxej#(kY za)k>Mq+zXc3;5bQB1Ix^uwvzALuB0BD{sMy!(BtJ>#iNf#}&CF;;e0ZZ~>Dvt&_g32pURS5~NE#jPji8>#E3=Z!@x7!4g(EKZ=k08lJ{H z&^9?66{oY4c+*{}i@8&kJ|Y7Ja>XToFQJC<=v$pKrnS?cm0-wp07xrHVH4Bjh&5A< z8|6IYPqlCc3^v$F;)-m+D0|G`6ATIA1o{U16-hdkQP^|i8u z55j%S`*iox9LgydcqK43$+Z_%-}GMp1Qa*TradKkt!#3xzZdHmiwm3Qan~DR2}Sqn%>W! z9#huLQ)RfhD*dC>j>Z#0E)#WZD{@F&iBs>i`COEt=zQh=-DHdE6Jt-Y9{&J1Rc{{M z+ObvDre_B0_j&!#)u!Sc*=}8f$?0r9#`tK9As-GOOn5Y%)XCC)hTZGe-xv+_UTj`* zy_7<$=<}!u?HKu-2J6OiBFwzldT4B=CQ`lZ*dii-kfLRH-tIMgAkGxPicjXc6*1Zp zsG*Hq9biesPWtFa-zrO@cYtcqpmDfsF2EUDp5SMy^ornioTqW_%{|ROfXaBMxXiJb zWPAc>%)gInJx^_Tu#Z2*p4%Gy z6N6sexUiLC*Z`nD8;gwT@(nlCnKdBUSP=MXN;=OLu1ORR3RAF{YpQPR9x>S2fcMaj zZIa>dj6c=IcaU^!~e;>_r}=Klg`4%u0^Me0NV-+5*(72u>1^{gAn&!y}u4J|bs-MXOo+t>O3m zh>*-7^$xtM-WewEepE$T!^o6TSq8Y$g4sd7FX5i8yFp+XZqki3wP`IuW$j+JpWTnR zUHMCE)K5XA)j|l=7ANy^yrfOKF4sR)^7_PP1?Ejqxn8C@q^EUtYLx zIsOk5HLYanB{Fi}U=37myrRq}*Ai%?3)X4a5<@AO-;a+zP0?nL&zrD={}bCGeYAio zg4xdwA-Y&HxDG=~NX~G?#KY!l{gPO{Dm)94TlOO26QVDsH{6y(@gb7dnSIUI<2=sj z)q=}65&TqEGtQotc8^cY$to$MkYMPr1D#+&%(Q3M1PPVngb=OoY-wPjxOBD>w{9}~ zD9bvTXO6&*OmdXA@t?s@T-!X;IOoeIat&@U7m`d)LA##NtcvXVQ$ zezPOb(^iz%R|OX%!X{qsFgAK0A`4X9O>2Fp_Wm0_R@$)R8r`R$XS`WpFWDh7A()ww*!~B@-`omkmX#HbtyCfHQe@x_lMG=G$B%S}(T7ICB&0Kn+bvkkq{~#GB1eZY_=b z&*=)1rL1cj_w&fDQ1S9iE%^FLL4CeaKoNcfrm-^b8Mx~lh5$AVSsgU}2VngNm@!Ro zq2Jk?yim=0h~jR|zDjOZe0r`nGWiD>Iv2fBebxCKP=zKkaPBFmUaQ@EvoT)8-|joqcY%w04Wa+g+)1f-KbEwRyA-#vgY4gkj~r;a9(&d%hfIUQ|} zFEDL+gj>R1ID=|T2!?$uQvf@l-yiDN!NsLv2ojQB9tlUNc??t)!5|*C;Xo<((qC+f zsBHc8OB8eqGGLWT#^O{#H4@YdsE@>)_h(0Tp1c}SPYVWk^frVw8?r=jx=9hw`Y}dy zw~D5gsE?{vz?`L(WIqB6`iio#c}%&E++j&zJOvg>glX~dlBysqH7oiH-Vk!!++*l5 zz!^tN_Z**pCoU6s)ttySjNG9<7wNkBY?moZ{f-Gb|h`4y^Z!>Ya|j+t0An=9JaSW3u(o`aD(p zO0z2`s&A-gEm~yuZIfwXrH^4XUpG$?JkidJ0|W#4w>^p|f{OIP0^oV0p?;My`He;r zU_(Q|_i7N^`ma@MiiioSwwLJ>6JvGPv=sBU0KdNU{I-dG2R^~d&-dMK`SSEx)X9P( zX3*@=`bCVUSaRU#S&JxF<$_>Q^n$ke-T*h~8-dzha~vQ~dxSNt^JZpM{>0i;V``Frf#J57@^l-5%^gj^svy+ky`Pf4CwtgKZ!4G7I_2<>%qU z$d%AUnu>?JlnKo2kl#S~wtpuv)=p?2>OtrZvnA4@F|6@yFvMk+N9T-9vZpBZEI%&! z(>2N<@<38Gpx8;s(khpRTSs&S74(62>@-25X+}~OR5eSg|tEBiy*&753c95 z&XygbL^Xp2poMKUHOvOBW2CgKZ(XjNk{Ea;^nb$)FnryEl$>v^XpH4)sN=U-kEVAxl#oj7^h3GCAZvW*IivODlrHOnMhVIJ_>0cXg+ounr5DPg`zbsZk zq?@bXeoLx8si&oSu|%Z&BtY;cOKxEx-6v4+Jle%|h}Omf3V0)KRtOZ(xhI)8Y$%XX zvw9%;K|u`-UYyc>Wsv}bFGL%G;J;^eke+?4t@PZzetz*}qa0>O&Umrbz*_2%e>I!lkQws*@5*WFdewe)yR5Q!&2vua*CE|9dFPat ztcmML{y(`jhW;k$8>V>T@zuWGb!?NGT8#A##p&w|jQ!#LKJc{do~J*w1;j*XyeiU~ z2`U3KF-R;L$oGJh&W&x8Jjle-BIH>PUPP+^iVz6?87_Xew+KPZ5}yr_A1dVahW6Pa z+ijTuPVl6t3M=5ZdHv5_o513}D*I24Y=l5Zv(_!x_%wHL^JMA?O%GW^o~|mHRD7Tl zi-k--15Lmd=#NFHIvzKRq|t=R#@udEg=nb`lHIr zPp$L3Hs<(Rz)ec{F9jltHbJ;UklS0V%I8(ScHu6Yjc5YMOUO3GbG7ltrzQV7e%QZ~ z{{Y&t8|ijAM>`B&GG1JXKz<62xUz>BSpv-u2y>8jj$xb;Rww~peqKoe^0i+Oq&Q{H zGi`9ljx7LA(w4Q6cULGZYyC+%AoT&VbD9^D|e*K198Z$E(z|Z zadObxZGwA&sx%s^3U0s0VM;cUWw6_*fvTLWsXsVigxXFdg5|Q@vDnAnRrqH<$7IjT z7<3NV991lgGZk;_BtOOs=(6fQE$i8R|2mkef&5Hp{q_OCxS%hICTV;xiR2r}DByEs z4hT%rtwGP;zM@VF=t5sJ7cQt$F8RwK5ZkiH-l&YGWu}N;jJ~39!KIvi{mQf0^f8f# z|EVw~{nUOJ79LLxMV^wJqcAY8Rl~tj#0+6GwnZ!8l|P`fG$PL_nzRXfom%8rk(7VL zSk^Wh6SdKv8NrF-nMs>VeQx)_y)+Z)lOVWgztuVIwUG;Zgn}f~+e1xThZw9MOHp~X z<2V}kzTSuL4&NJD*&`vl^3=%;4nqrfJhm2#gYZ*UL(feV8qx5raemr&5e6bKe>CAN z57RUQ#5=Ma3y((Jv%7fv$c3j~VYq`@7~UR&93WV^rsaO*N|x79-r;#yEj7%38hzcW za}I-bMm*+%KXWejcbs8WPnL3vA!*GXjhk0nTJZP_s2}wmY5K}|p?ZrV#}B4p&-DpJ zjva{g!5|22@yDdlS9H>^UiKhF)g`@$y*^X0z$pIFWw7PkW#2doIwVRzmuW0$P`J%iu~2Z`*X}R#+7Y@; zuoO18{-%m+5&w}Jm_oFl`Rg>*qCct99N*4GHJfAcB40$#w$@$aM`Uy^zxjF6pdmAf z*=D&;F^0lse^}hNldec}~XdwM}Js($r6ns#4?3Z`ujJVh`O(Yd35%Q2mw5 zMaQb8H@B|J&k{U?EEw*TRtGxTLdj_ps>_+Buj-J`pX0X_3P zPo?KYSM)0s@KZYwCt=<*Bx9xia)l6Rf%bB7@}2srQZldmS27zF9!6f_LaQu6M!zsW zFnY$wV$%*G(`-qF!2nquBY!RDgPo{F{FXz65s zp!d>-XTJlQRfhSY;o7T9hQ+!44y~gK>wX|XO0f1Scq_T-{&9`ElP_OfQfFFcI*&jC zaY)W?%+1*Afv7al*(CvE2!lZ?L(V?7KvM%QVb>;=*vk|$hdGniu%Q3iuFj&5$GS{G z4=)7S)QnfbtY61}-7~=-CJv|JG`Pvlt*9hZ6pUN_LrSVjZDFZ=aK>`QL)(-g=!5gq zIH#G#=u^5G5i3`&w|4N)^Z+Za`KCFDPJ1VFg9%!8Ea*rw#8yG-;?XlVOikPKG?A9) zI5d!d?=z@{q$Vu8T<&&MnUa2aFQvHf_)!iC(e6BH~S5pPV znhINl#8Leb@Q!H-S+fAr2VTOVQ78C5IU_QWbGnHQqz;#f>~h+HkLzb+Q=YK04&aWq zVSSwKb<|5_)`VeohZH?1Qa+n26unmmoKS)}$0ZF*`jMN|0=e0(UHE2{S|D@j8Q1AM z@$urU5RajkgHQ9s1YmwX zYQ((U8su|B-TLwP8^i|j&ytyAbfd;k?W}d`fau;EZw9DG#davHV$Et}yBK_sp@0u4 zOIsYFQ&&*tjL3F8R4@n&9ptqiLv$Rzohe937G|ofb8Ha(iBf<7(*S+bN5%ZuKp^{j zWffbG93b*Rtcy6$U18M6_k*ycBg{X57Anv8Di`+A`#3jer1<3#)$_#=FED!#Z|bo* z**u&2g2Blbj{YBs5U=3xc4W#7!TP1gZbQW}uQ_<6Xb;iZmar_nlJmOCu+-dGktZU? z4@lg`+xTyH&js4tV{BZ&-{Ga z(R(bGiTu-(3JE{wMPn<=sks`m+TRF&hG^j_g9Q#Azv0Ja5Wt33sjVBTS5Aup>GBeH zOF`X{oQ7bSZ+y)Yf)u0NL`D|c*o5}RB;zOs-aVS{pInf17D^eHSiIgee|KBn@Zn6P zZR1*EMqN?k`dMvTf2fXLG<%Br$SObTz95R^V`k_tuxK>hGRAF&{(>#SHnyik%VRY;OM2k2BzSQ(NSq4z?L!!3U>?he$ zZwHOUpRE$*&!Botp@HbX{m@t1?lc4oK;;1w%4kZe<>x&@8`a0PP?%wYoz!yj7c3#&-G%XGY8!XYq!9v3A#G$#+<4VY(TJcnSZtMn6 z#$VCWXG3WYeUQ-Vqc6j~N-}dmk)Lu;^{i>1)+Sac=2B&+6EXYnD>+C~0sFkZEHGOM z!v~FoK~@%vL@a9};G;acibDu+z%ML=&8G}R?annF{ z9P=9Hl%9a3P4D4!M&&uSLFu@Zqb;nw3xi{mreV3~%oqiQ^#T38h~hVFKf^c-dSw|e z+4hOQk}P%}Mt;40RYuSLKPTsKc9{>p{L+0p8&(JAPcJ^lJ&5bhNlO!%QO&a1Ah&#C z$m8eQE^E@BNGayU_|;A!5ed;WIs%vDvi}|trxX;5Fi&_wi_E)IiLUjX+;y$+3i9*H z_ZvlZ5-b=Jr({Grjg)gBJq8hA`z*BU=pQpUtBs8fah-+kqvENQR`cR3>dIHnNQg`) zhhfY(uJ4saG-Eh)5?s1oYOaVG)pkT+1)H0AD*O&c~)o#QiVGtN| zrKCr^>VmBwB~rlURXjtWI^xV}TtJ_?s~JhbXiriL8N1M(o9zc4$(JS_a(r|TuF@j# z&==>56n@7EcW4Xw1(miZo1VOFhQhePwt?AsRYzkaHHHf3X%01)E&0V|E6gO0#UsvD zS`l-(Kc8}a1q;u080;AlZI>e!YOA2(mZ@7c3mu56kLgu6s3dx>hkEDa9`=)UdN&#SxgMDji&omu`O9x-l>8! zasDPUV}4Q@Um##6;I}(z6xt61YiUinBP3`JxW{%O z>0e+!gmNb-d>1~d$&4u!>S^_OP^>wTGuXt0u$RAxeo`-yW%pp6^KK4BTP z7gqSgoW7zY(NzTOze>9xUKBpH!W|Rw)4)p*ewgGz-htuZvj1q;!*a5pPxBL0fyv+7 znzzr5dFqb>hKjPXq4GXQo>`b66HwYP>aT=s2YD@x4@*z4(mw!F{PpXpbItT|{nC7| z!kEuImwkLVWofHOG3MyD#!pe5wR>&ioa!6lp<xUaR;mRlu3sTfT4 zwii2J}1E@*`YPF zPpx>wD>7pO;$#*Bj+?OSHH-SSte}9+PXa!TSe-skb@GLW0U_-Iim7qwG8bh#8m27y z<;~ZUa{Jk|Xjw17y~Bo4G85Ce2gX>kjlbd#F`uB=UmsM#=7C2s*&ta%^NNsg!JDn{ zpU7Jt_W>&O(IsxNdgHxo8Y#VEC&Kj6?HLGY2!XVmHX07f>9z$p_8$|Tl$?zn@Uq=h zjyVOt&sCAo>DorXL^R}%^SW%wF)^}Cq|IMr(Rf^KvBkYAbW4H*PMtb>$u>ad-!-+& zL=nEhsS(RGnA6W<nbZg%yL*Pp9FSpMo>oz)dm5fKJf?Qp%bWPP02uEUhfcVXwS;NPK}o94Rk6 zjk_zWUhVRuUCrA~SzXwY&PXNx^{YyC+Pq77Hh)2J9U&UBzqPEoHg3eJgW(8+&8c4b ziGhXcz1!5b)jC?H>T%2l%^j{p%6q=Vac}p0vX=qemPo8wWjO9j=Vevfs-?4DTwLg3 z%4vKs90IoRKO#mnA>lMwP5CV-1`98pBRPD`P<67I%3j05x@Pb}C8ah7hWYn5HVPi( z&S55&2ILUWlv0P?^U{t#6*Zn+cT9zeV@7tCW_-h6DwiZjx7*|$D=Na$vcE;ac^gg1 zyLIVG!eO^yifnh?UKo0wy{#FmlhuA+RnKGo@G zIpL>(9-oz;u&-G7N5WlAY9n+mtN1Qk&sC(qo%U0*?wl`IWhS)wPnYoJraa42WDenu zGz}~}6ixKTgOsIE3B5r{M7Qc+XchU7?B#8He`qt^aO#ycN;Kb0Q)S|I;I^}WS z;hzWVY&x>&H=L6d54Yp&R%CCrLEd=I(9{MA!u7MhI_;6l!;{m-B-kcbQ1%UtV05Zk z+LFf7!rGt1UGH|FnH&EI|33g!?c^M1oId#?Df~e(W{O!2GU!k9G{tD4xl6a8by)6U zVMn!g0kI>~%$=FquUZI{C5~5>gzhd_)xK$V&+4q6sgPJO#kM|j`&=8pr8MmfKMnJW zf}bk={{e_<{{b@J9gWq=i*?7_yH^4a-rmgnF5J%YhMi4{#fMWw8^$y!$nJehB0GsN zA>df@GeTsxLD*MEZFdOxy5-xM|L3=Ve9AunnH8s@##XKmlt@^U+T4MxY7X8nzGeGx zmvo15K~jGg-J`xh1Ki9WRM|MTM8JpBzQIuXMZs3l;>M9JmFIj2ZGAm4J0In|sBH=~ zfyugslclm`H~$bKRSYir9be--@G|$Dox5tPwx9dcnZ57EzM}(_bv^RRoYQ=dnQfsRrN5Y> zcman5&L>!IsfQ&2pIk6Jo?h6#yQuqWv=4$*+~Tg&m;RW0ei85`rZ^%T13>)b9Ejo@ z+w>Qg>(bu}IrDHQStu=cFY`4O)gE|1&!ZzTO)?yTp{l^6UWXo1bnU4!hYff{Cu^n5 zt#vR6@0$3By2A3XSz%4$5C|1h%`3ukr+YrpS zj^dyww*Ff12KEgeov_%bPW%tEVkMW^Q|9C=t>^u1#T3 z=iAd(8r-4OUsoe+JmV_Im8cis8c%mYN;imoj%Zm5Lo@rFCHb)Frj>Y)k`VuI(| zwZGx+DrFc_LtEXTk*dl22e5w}CXeYRYX~!Vj~~+Mkb8gno#H`4d}Fd^#b&QRp21R= zhBQ;?lH@)UnI-YHP7yh~K^<;C)D$N0m4KX$5rb=neAE|aTfR4O)UTTGgtl(IxF>*? zI1`EvNG=-A%ZG7)3h0k|ayUMqjceJLRSDl7dO^_?txGRD1}SoQUW<>gKf zDQ&E#PEU;cvhK5qTYC2WzFiA_C}~0lYtpb-_1-06FnKS;(SmV2Ep|ZZdW`5)ltA6ZzHgoAsoZb7CUXwu$l?!ZRD;5q7giFq%aDQSdbjR$?F&i|d)D#fG zd43hu9I~j%njK4R#<7_gmjmW?E(6>l+Eq1^hl+%B-s#s|;5_MBaqn^Q(d&*Y^)_L0 zs8wa>z?Q`2nMLjnvU5dVs<4oGS;fa47{Vl8IJ0w6<_P1Kd1X1`Lq-Zj*L54js$3Sx z+^CTA?B=??TMrG7Gx2S+7g<-jV2$Ozz{)yNNea)wGt2znJu7Wx{Tfi9_%ecSW@7Qp zTTX>j(86s7Qn;aF8*%H)hn7d&T|uZ_Lfv<{rQL!oL}yY%N>D|NI<@}>tYmX|y#7DH zitkcWjQvUc;N|q3>&pifcaf@D!JI$Um4{PrrxZ=k zSQEMevW3pK?o`g568r%LwzOf;U3<=#<83zx2$nzO_5M3oT59X_&b@19#VD4j>$95EIN z5y|aVnT9}@L!Q-2Q|ftu9wIBHW0_L5f0@&^s;iR~*t&115kA|?;!2(cDsO7)`E_9? z>&xz=>SffFHGknCx5j^oJ1BShd|Z+Z+}^GxCXT!8{`hq2hWr-2(`&eC z-tG{6jC!jgm{nEZ(VforH*K&UY0=1&!fMeZcCbrouIJk6UpRp{kbQeJYX?z3*D;*- z9omgca?#cgs_6j`5XT%0El}U_uO<=ogMzzpeA|k&K`OC4BJtO;bN?O3h?Gv8vYws8 zG6J#t10daQ5jgSw8&w@~*f(a+4EA|r2BylXZ%^S-qWdNezAcIh!#z9ut_KVHHOcep zeXF+lM6gIyZ5tl>YP`?OBy10vxpJF(2)VMhfz8s@*118az>@Uzuwp)YQj_cVSY$xS zi#o>k_6HOVLs{&2NL6Erh}a#8o@LRnpT+Z(0Gk?vTx@sR3qEatI~0jp#Ntq!uBw8} z6qU7{^#koapSY%4=UR!K-De`2k616aAiFBYe}k*Q&hZKZc0R}P-48QND-I?mYf5h?DcS5C2H{=|Py z({A)y7(y0DRlz3fT}CZi%4r_4F4oO7@-^F_Z#D!uWTNb|43CthC=>lSg~ow@_?OFj z)?br7!7HkpGO)09e!S`9_$!dsoVoFk&Gi=BijL>*|J%D_O`gQsx-&xR z#MI_@3GI#(I;uls(boVLG$E_36k$F-K<+6BSLc;Zvj4?I@_{1RIMa=z`6;E};JBkk zNyTG*noY8ULh8JWZjdwZBhKC_-rLP-Mt}Rbp|x%F&H0Nn#%A`K0Ai+ig>z--kVk~* zjg#UyC}weB2WXyxy?y?;rF{_uZ%lU9%4AN-pXH0PcK(Z9)4A3=-2weJj+A9?c^6Cc z)QYD6QO7Ts{kl?E6s}%fW;N8{)VbQ3_^2O;(D8F%{8drLcn?~i`DbOhF@gykQc`G- zNG#LixW!oQBYFZAh+n93ryO+MQkfmwM%Uo?<);?3FffvnoEPp}?JH3e&bG7R5OmSw z>U{O3P6#r>(khUoLMT3Wtv#84!k|2>z&P{+B`?CKpyU_={_;qC#z8i+!r%NCt3^#= z^!!eFK8RcfzW_fmO|5P9EHNM!kibu}pM_;jgAl#Vx8o5WzmU22muT(e z@Hqf%tZOp;xDSB$%Rhpf2{@~U!C5QaX}COD_&s(nslLyi7t{S$6w@!ja=KUe;jD_T&PVqUe+l5A%BO<0wUOOUIxAPoT z3(zr%mQqEkIz;B!NsNy=e^6Ro6}FP9vVCX;82W{$%@U&@f?c~KHnKt^X_JC~QC7zS z2!{U{PjqzOLh?%!&Pmh?_)+n#VpL!?98~N$5fFV!EJAIC>RG>L%Ds*MdzO3dvE#^Y z=NY%_WUlFs$Mt!H2xy;o@BcB6gyzAKn%mqj`sIy)&D4bfV}<{Yz@&o;w88@ry5{g{k1gl*6TaCj;Xtu9&D)N{aXUcS(*{pZCIRKFaAYDU!&7$z$Z&w@g zzAeSomCuAVm}%+j@u(d!8^(mB(VVt&>eRk!>~IG?7P@os<+O6pXm;8t?KYf`N2sr?dhUr9w*AF+A~=C7a%VozC0G&tT7j13KTY-{d8J7>sQC<(fUNUy*b7?P z5|2Pv3rm<4LY=>*)&jpil%>cm_j;hTJu}8$H1iy9VT3m(_!=B7${WsGH`MClkNDUL z!OskS%hz1}C0Qn54ZfmxWP|W2Mn!Yw>%74S467{Y2^inrrruTOd&5*W{)Tz3BPv)G z(iDdWkL(YUN^u`NzG^R(*qvdkn5t7d7Fa})D_rah=?LX!$u@FqTeGlGP0$JYe+thM zG(crZl;103E{rIrEqE|etQ()pL&H@xx6SDKe7z2xdGXpy9}(*iR7iF7Y#I8R$fA6-xuaZuPJxs zQ*lGTQz>d8DgxmspB(EiKFh_IERQwDUYve$R&URHqfu8-{HkRzM~Mh3S3wS4_Md7F zX~6Xpzd*-lzGDM~F?PgpLZ2xnC?sZu#Q|=*gR956vblx+AbOv6*LyAP;KqEbm4a5M zTSRtNEB{I8&Z9Fb_fSR+%JA&pAnEvBVTGh~{Uv0~FlOXn#Fq)QfaTQtVs4^?| z7=8D|`d+fJ#T9&MWN?u}`yjSju3Q+%YH$Q5V>^C>J5yh6eJ`z7mtv^todE2ZrAV4V zG}H-MIRvr4mN@;aYtpj*Rh!zzcCyA$`ZJ)hQ?S=z`E~gpK>V-{KTYcrTsu9dIXU!w zD~|nQvIkwXQ09+)1X)eJgck?xlZbr$tj@3cxz%hTjE5<*FvSH-9)Wk3L?AY2Uy)!n zk?d-tDK_)^(+}%Cfd+Bv>-o73`M~51E}K;FkFq=_X{BK-@qN18Z%6)Pe-oQzlofwC zM?gptc6aei>YeW>Bg>-X{1UPGV4li#{HR^sk}q5lh!_Wbj&4--1y2l%IlOqUC}#^jR?(rUnDeb-33i>j@y*QXyzYPS?EZXrG8FF}Bd9Q0 z@_ZeLJ5$+t#dT|H{$rJ~@kR~_8SA)J?|*Hp%J)&t@@b9yb57j;ka7Q1ZDiU#%t~>C z@m~K6&d((dqRU!izT5~iJ9EyshU+DQ~lI|tt<(b2< znNV7#^UB4h^)g|r1?HC9lB2`Ct@+>XOHErF*;h{&t$rv%0de0B!W3R%caY5xGkaZJviygPplM{`UYrDSXe(7OE>h+w7Q}SZbO73Iwb2Q`gBHjY0HSvZ7N) z^{yg2n>Hz(g5Xv3hMViY=h}=QcZVE0VnUZ;!?B z^PB+72=wo1LMPm!DUpqd=dVX^WW%=hjbo-7$bOoZ*L@J@QbdH4>MXyk8lTQ03+ z`Fwsh+pzBWHj*X6Lze;4vis?ELIfGn^wgU8pim+$EPue%x9a+Sn9>O|A(VDMYi_1 zU9;(PmuB=Q&lC%FvM^~$2RtvDH0j^pTHSN!Wrdad80{&Kv)x1dx58v+j7U$ksU-xK zfe!BgHDB9GdW^n!O&m8iXOe&Tn1cP8P7_OW+8i`re* z!oy~yVp^uwaXD2@{SHy+Zt#OSo1d4+U}lvY4OT9QJZqde_Cq_`GToO|=UPxA8mxy_ zLB8f2egkzfg@M^b+h_uQd}I6BORzavBtLSm6A++8RD@L3tQ)%>SGPCqpOZh)DFQ9O zL&W3Ec#-wZXSo%LzTZ>V0q=9pyu0S_4W6~695#r&i`_uEA=+EK9ZO9ey9Rsj^k#)0 zUwM2DWr$K0=h{6dxRWlZYA7)svz2L_xMx+qv&06~wHqbT{xx7)7r(WF)p`ro_~7#z z?4?QfT-V~-0)C5*rWR^Cf^bQ@RJY`;NnnXvqEThHEl1HyTpe7_>JO(&f+Q%5v;1eq zBG-R_W@ufx18s}}5_p4%IFLimdqw;?uaSt z{fln1q@kiQ3SD8|kGYNztSCNYf-@A>UzcvT+BpjpUFuyDMkqmK5%v%^G-{WshKf5T z#xF%7khUiB(Sl6xm z6GY+$t0QaVffH|4Q5j?bo|hSGgJUg9TFo0VBH#seF7tSEzQ-0_CbF*!ay% zn)z4ua+a(*p%em^?d#L5Nl0+ead12yu7AZH5=r~8mwLzi`j_Sy#_#I}zcE!EKQG`x zDQejk`-lp}(++~C{n;mqfAoE8o}j?bA4hitvd(PdHrIvL*U`b}oR?;Ij?(k$%b5`>&w+xV{UR;Zo)6L2>z`3%|j*-R`;aDx>uu zfL{-I&F$g2=KPF$D5U$by+d$DkwQh1+42ZxxFdBBqNrNTkkTPD_8Ix{=0kCVvQt_! zZW?-IG3PFe_t=znaNGKpg>$oq%Hyyzvf{QNzp}nwNIwBH;OvXxCe$F@F2tvY5Dul_N$Nj%2g_19U(0mU4B(2LsxnZ9xJPAZd?7~cejXHJz(QL*r?;BX;WH#idC17ZrR7WjewZ1r z`rDP8;aru0ZKO8HLjBtU<;!KHO6`i`XEYE*TY-`3(>t5^`xosLyb!KZ+uU5_>^v`= z60h%_R@LqN-4X+d6UgDyR1THq&NO&#<%oeYxwaxf=>c9}Kx6LxEFk-krSH0hd&hFXnljz**pQ5lGJ5k_JZ|q`j;SL$<8$c@%zJre*jSb!&}gWQ%#9yJ z-lpEYgA&%Wm5+QeJU6v_fBD&NG1}j!lCCDu9fk{Tn1S5n^{uMU-=@SrH$cp5==A6x zz}f|9aR;KzcI~R7 zDs!WnlFr0EL++bkt+zFoG`y+!7D(g-tS1i(LTGH>U-@Q{@mXx)NjqAN>3@J8#%_t_ zvj=2*ep6J9--qi@Mw{qts-h{kiVG=QXLLpKHKo}pvSx=Yln0EBmiI2fM+)?GR!qmX zdcz2jH}TT&685!O#||}CV7@G}(Zp*JvR0G#9nt2kqD}uis=QW&`nT`(@p@?IZ_aW@ zyc=z4`+TNUkQz;Ai1dlc$e9O+jnSShr|N$6a1w?k`R-$<+4j~o>>Lu>V(K_f=GNC6 zr4`fAO_g1Y2^dY$M3Tf}49epioGiTy3|4eHJ_*l-C}mexgy6BM`C(=Kr?PucW*G)) z*w5WzuU2w{M5Uja?8W$)FlJE0lK8LTgz%co8G3p{1j%(k#yw?0#!u+xO1?NZyj+H@ z0PISro78c(9WblgTGkxWA?S*9n)HWs#VAnb#P+l#<0^Q*vu@DXXBKDXPkH8JlGFj2 zz&-ji^{p~y7~$*Mc=kELVTjXWCL>F|NRHCy=g%nr1DQZ(zo@htXlJO3p|XGmT0aHl zen$?I$CmO1ajJ7^OPM2`fw?6EMCys+E_6>V#&u)2swSGgDuX0)M~sdTj)tlWi13N< z9L?MEUT#=A>xrsbTn0umi>Ac5(BM+(X}mgdQBn(9*Aw+!^zGi7?LUVbT_1*f_gIT< z6=0)aYp}@=4m&G2u9{kS#MKRvz-Kw&Ds}EAX=Akp2IC8uFHz9yf4sHiGvLb4A#jqn z5tmEk1A=?04P=HE>B$Zm%B@?vg}0Biw?fJfHt50(hZ%1HYxEFbsAis?Hd7NDvJ-oK zM3&oYV3g}U%4bbIn)e$YZJDk(&IwsjPZV(ZfGdlYha$IERwN`6Gw6kD#)H1;m22tp zbg*X$HB^9S!(9IWZfll;J^ExiwnJWO8T%HqFCVynf0F7~7dySs<-{TS4^`x)^uM+*J=5g+a5x5>o ze^;iY6Cm7hmzE5b8jU`K43UTLh0u_+5u68ej_aq#zRO-7J|xl{$1`IEjRXh$Tb30_Pm?)UupN$0jx zxvdb#f;R-Xo&Nw5oYm6DO(WR@wE%fR7h&51si>Ki1q}slCSOG>qbHKowps|`k@8kk zPf+)6=M6<#BdNnkAr(t;(YBV5cHN<8lcNqzMLN%Rp^oIn!BtUA=AGhr*#2Q(<%Y*{ zG0QzY8zQq)BoXJl>IY|;M!=}TW_jya;JZxrM@ zG7d0MJw5E^>2SP0YlE2`D|d%DpaB+G~FN4&Z(am!^SPiIQ3k? zS?0iK$IS%c#AFZ6MJ`zjgH8!umjSuXP?d5QJIHVcsxKm;f25W2WyHIEzKT0-xyK$& zlbWZs(}u_i$?UAw`UpMOP-g!CMH*RMrJ;0g4#5B`eU86K3^~HXZEK@xW{?l6nx23_ z?Ctm|+O|V?NaFE_c}U?Rta%f^e=nkB_qdQ-z)LF{e$n2kZLF)!eXG3G(J_&b0PTRh zG2yhbR7B?Gj^r&kg*>0BTqEaxl9rMHM%Je6)?CxAI#Z{Y5U4aYbxJN1r+c;HvCK^>irN;r;bJ%i5l&3`LH-u z_>DpRU4Et3%6MaQq`=>O)iVD8({&ys6|q-IOX3eRV>!>@a-EK*r@D_0T0+Agm&MXH zOyZ-br-CYmlRmAnFT&rdd?tbP4^kUNL!mOdXoGiinlt(<%T2DXoz-hCAXI&_f}v~<+MKMN0Q zJ};UIrsid91ZND8d4TSrtuCvinx34owQLy#ov@KpSF)CgqP9kfBX%4C+$YCk*unBS zz8H?}X|bs@+w_HxMIppOd+7WeW0pYR^(S zQO0LfIjtvQm2Rc$s(Y*eLj7;<6%wk78=w))=QMoK(OcPLn3_lB{mJ2SXN|cya<{Vi zaJl%6`!6ZRRE?$%Zv3L3NJ(}SZliNs!mmTH3BJ#7Rm2i*r+qW?oN$K}t`9CPDwm6B>|+zP z1>YnDGa6dKu?V_RsGj7G6kHaLB$USe_*20B$ZubaBYXH@tMo(?UP;gvNTX%wq|4Bh9*%fxmg9 zDP4VWY=jS7uDoue4oY@!jSukDt&`q21b%9dU2!2-YVOi_pJq!>)k3ztR;A5jT1gup zRpTDg<*cuJsc6X_JG5={QoiR@;&U+|{MS^~ZKgWUn4U_l$wQxJ-2-mu+k8%#Hd_Ax zidR2=RoXDXYCm&B$i9}_FOX>#qoRU4-7&X$kE z2Xw|7`D2v1#XBmxI!%z1NPcN7Hz`BG2b9UPx_JER^UBjL7SueN#{E_D%O8nK2goX9 z@M_5l;nFYu$)OY2*|Feh^jp#Y0QM0KZWu?QU3Nm`8%D@kVDU8%kw!>%!WLSF!ZbYL z6j8c@iL8cV@txP_PP46bHPON|hGgu&O?6#Fa#4`d7v>*n(Xdp>D~>Sb=YCI`>f-hk zU0G9Za~ZR{VIi%%%}+x1RzeJ5d{{ux-Q%aJ)YIwuD@-%e+@T@F7sxS%=H5MI>yI%# zPiBlu`&OXPJ3c}_@}7sN=&C6l?$sVE9xDorw>T91=-E`%-js25i-Ca?0f}+`Tqp5b8+SW=l-@oXq ze$WL)HRe*uBy%d-9fp)OQL#qTLy);uD-IW}I%RL!8N4$4gaE z&9kJq;Cz#7bgtjpPS`lBqWssoz}FP9?@iWNDUWI2gDASCfxwSFTdnCa;V-1F=>9uC>o=wN|!% z4Lpe@kE-qzQnorukraP>B{S*^6?uT(L=fzIayS-lYEk%vLUI zOIuR#cfnY`pBdsbeQS^R!}*0s-WM!mn zy7dAuFqeDFW8Aw4XIzV2&dH$B&^L4Hv|w)7-yte>*d81Z&b!Ja*b)Kwm_5>!VH@e1j*l^si_AJkswTZDGv-v*57UyjDNOX!|jX19GQT}!>*Of-@m>a8-u*E3{-;Yb}Q zBZ11!%7RLX3*E6J=!bMtxDp8{@lUE^8ZwThSAjnT6qBFGOJ)TpJ>F-Uy-P{Kt`lT#q*#hrGRV7OxAnL$VWr$p# zD;vS(Mrv9ID!nb$xcJ5p@3~}oU67+x+*qWUg~NT4OX)6lK{|a~%3UScf?6Im4%tYq zy{Dt^ruTfeE(g?h`XrQ5nu$T>#QsRTrH*zqK8Mcp z_dr}3PcFIWdA?OmR3^4n)WqiU2^c@oHK)JS%_B#4iNuWP)&(N|R`l2zN8;y%1wBaz1eu>s+q%poiKDm&yts&f-5o-w7H zJNl?3mgh@KOqFz#(lOZt#BzT&rqsP#X1H79r+q+%qzq^baL@EjQCoxA)t<7x3R+)( zWaYyz1-b|HR{sEKJv!TePt#CQ0@Bui={ud#Hi|b>N-CkLWU;q>AaD977s{GWsI|F< zq9|%2@`jUwgUKlRNz7q%@wxt8oOc0bf|Nmbd{W3n4%mRLH%Hd={hYQYd*(*rq-P4D zL0eB*C?-z^tHc5Eka9Eq6*Kg2RKKY$t&gak1r*H?hnE8L=1OhXsO^=~!%)}AWPSm0 z*m~h!ddt~tmyu6$qpUHuCIc>m!mQNvWdSTKnptJY1>5c0D;!wz*?l3n^#|;xIMPJ& z$^I7;%3kl=6^#`z(l|qmm$(wG_1zIkTS)~!yg8A;0oGNH*;Nlu+gn1-62b;NNjJ)! zS1wWGkzVQ@T-fE4P8`C%!JS7!kkRPftHRcj&qCm*vGx zUB4OB!X&7Wp3Wg+?xV?5GhMN4gb=ZPEZ&-VeQb9sH6Y)Wg`?4J6{yw^iEQ$;J|$|d zA|v>b(Qtx@Z@QbsBjGqu)(t!bnbPDRj{gASD{Pp#$1WujCZbO$K}#asvl7#igfArS zN>SO9%(dbzG-afoR8q(UcpEunCv3QK=iYGi5PPw^iq=kZozOR@tPOVMJEE^ zVm)*@(ncNI2ug@NMi-w{RXA`BYl+495jTMtl>1cOQSoaGDzp2 zGl}}5k-rzWs?({ND^5b-k1$2T1F_s9N&f(6G)PDY$bQN&-;y4-xIrVD(~qKpp1Z_& zB$5_+Es3!G*-$g%T$+Kt4#%>{)dFoT8ra;4Pgk%DlvzLfhwY4ZH0 zRB-8YRx&mnK2h^T0icaLeHPdxcD;jExZBY}#}YD5{;F0DMY-?Fzap6cx>Y&`WF9wF z>aL=Edlz9@?i9W*Lx6C{aJ=)YVix#A+SfReeicl;DGgx1hmo#NH`OQVDuGuNgI^(e zZOhs_Efo%Ak;pK4B@XXvRF#b*Q?fY=8|}1HI{G(LRmjGi1E_dF6jLFiS>t!~iX zK$?&S5y|yc+HlFGV+jCvT-?oYWObhA%)vz0Nse-dr|Rpi9af)q4r|UajNv)`lp&D5 zmSY{2ijnwtY4 zjg{2V7qAC9atFeDf7Nv~b#-*crms|zNX+YY*FHHG8 zB%$lp>lBV|2}HaNvoCdU@R45s06Gt5D~34^3Sy6)%<6syb?Fi4N{-aOWZ`pGwKW ztCPcicGPp9qR$o)Tk4y&Z5?#2fFBPVVONcP;^h!zLFBJNsHCF05F^hXXJeFQvXYX3 zv_%vz1D6rH+Z8fWZ1#JcpnlL}w3CCMM8cz_KyZC4c0Url)ZXp1(YwV2oNUlGRf)Q& z7d%qOrKIhEqc2L9N7EX9EsBEqW)#*s*Mr0^P6fr)10)box0!rPMQdNXOb*!n7d{`` zi2N&D>{r!y`)7Wu6qA5zW-b{fLz1z5Ha?*G)YY@X(@6DOCuLa9qdcr@cg|KF%H+FG zb!x66THqGQ`Gg^x9oGh=`lxFL7hv)h2}2J1rS7OXSSktbi5Tf}xDE;r6XvtT`Jf~` zHM^kY$#^+kx=oY?ZT1L%orHsDRh&%Ha*91??pXJ@(`2n0+elC zxm6c9j33P|G6UI7a&Z!$ss=X_Q3BichXZ6~GcX7}f(|W?N2+GVX^6Z0r4pjuszYmC zt2oX%Mo~sx0g^GR=#$*_R9u|FpNU!MLv6P3UNT5opU+TUtquu@43 zavyxgk-0x4UCPbVJx4_9cwX03mYiVe!N<)lpb^!8XW`o?Q1sopvk#OSa~}?j;JI3} zo}Z7ss{0GNQQl>ehEI8k&MpC`fT%Ha9n#$sjI_Xv`fk5$b>~bvdN)aK@ijH4zDZsq zSxY1iYbTJBGpU4kRqGpZ-M>6tXzD|s5Q;`;wvv-YY_5Pf$m|orAcgv6%%qLFs%W8j z{wxfkr|1iJQbc?`Fz0=jCoVSEPCaAB>;iuSOPtc?@=4B9I$8>Pr^@N3Y)@d$3rW@V zSLb^lqO>v73gYa2-AOH?z}j2fqN+V`b}kE>J}Dhsc}w7q2;-=Qt&OCDNI&IGx?`RG-9R3vLnz>*GjR<%~vGapzF`J!q}>nX0|fjXq|y9Jl1U ztz{jx?=*2#TqOem@&l*v7*_{Ji>r-BuP$)XHgQ#p)R$-0J@lrU2=j)s0b$n5;g6+W zyQ=Q5r+h7@ikaK#?3(n|HAOE^&2YTcG0y{No63T*l=UqQy2R&Bcyo3cOzExgn`x$* zvU$FwEpeLmrbKXE<+z7Q7;l8$&Ks@6RSfb08*uFIm>W}QDP(Lo$0a9ViWdVDTpUjO zD{56%IH^No4g@>sfxJ1Q-B!R3T?>YP1?qmd&;#|G2AGZMdbP}R5Y1JELgf|&nR|{4pf7MfrIi`!<0KmFI*x#AmQCc_G#e` z(Hh0IrD=_)XyxiL$x=Uzt|A(px^=4iN;@q-_km;1gNCStrO)39)}yY3w2;k_R668% z`6wa5z&t0`8c61>YYuds&gs^nZuWg;_qxWRp^|zsYk^AZ%OkmI}4U_k{-!D5~e{nCam` z97k5lpsHhsqs#unKsg};r@?Na&&?GR{>9Eb;Yg{9K>M4dJc6gIs3ql{vxT^@>bXeg zDhUZQxVsq#WlWaDDWxV%eNpm32TAlnJ=ViCjpJtHNiEsyHEY8&(Bbi^EGY+`aZ~wI$MNkg_GK! zByGWr{nDc~@1Tr|bp+KV#qRDK+c9P9!f)Z>FNr!GI{3E8AIxgpLZJ3F0| z+jXtN03~!d^wYY_ENfhHd3{wyJeM5Y8sh^=aU^izxJz#|QMR$QRBr{6ebMvTdnMDL z?U&1RusRtj-z$TRq5EWdY>`p>8KI-gr>>|tIc}%q5;qFB3U-e->Z{{lZiYCZ)hik? z_-v)O^$Wjd(-8$BwdhL40FFXX9k zu@@*KsH@|5PeD-1Z8a`-I{(}s0KGcbV*Ury7Pm!3X>E;lQE zMMK?BO%acBTw~mopNUii`#lX!P>{dGg`5I{zsV$IM`?B#2LVq;*(i8lzgBPEf#vy>T-2W(d6lVFF=N+z1qN*+OJR=$+;Tp5*4qBv@? z^z~7Ox<8ple{axshqctKF`VdUo8-HaP||lPJ%Xj)?WpiZ=$fkLwOxa#LAoDcxqyBJF1b`DXjtI-)Aih@e8G>s)h z*@Izl&iG1duBK_UMtIpoYTW=PL$Z+_9z|@0+!DF3^2@25WpNE#M8x`(ML}$uaotlT zv1zq`WZm`(cq>^Zz?}UOda3*;XbYNJoi6296D6BoSQK>=j5i04P-3g>lrmK0{tIuV zKFe+uF*2yV&LP0$Y>esa`|3PKO5^2rTl7e74%btURV|b3s-8VmC32eSs!&)-9rjo* z4tt1bN<2?7Q$;2+eHF1&BbrG|-UEpRH;wIUIo%?qb{ZazWmpZ}U7T*ZXFq1}seMgO zPNR*Lrlzr^j3js3*q;lSaj5W#QC$N-aB)iN>0H*5agVB&zG)zt%_ozx1@%tOmy?3I z^7hjZ?TgG0Wj>N|jn*01b{&@nUg1+E{>*LfF}yk>yHPdOb5~R}&U}sI2Pq(^bZ5$W zOz3B0vmlMoKn@x<&#ROohCM94_wkHtTFGn%z|-(n{Pp zfN}FtS}E8WmpdFL9dPsMd$`}JNwK|2He9u}0=^LIFi?a8HgW(@V(HYkg z5<8Uv)K4e#Q#|ErIP&gQsi)({>ar01+MvdC`IQb@2lq~}< z4I}0d@0ZQS6wI#sFw)#(zECiZk%NY}@<80nHw&@S6@3>n6D^-JfU#*m&ef)@gK zL%pOwbChIF)^WjHR6W!Y4 z!aJP)3IZ^%h=hW9C-Yka3UI-UfNplqy14+Qju|-e_W_Q_8?2UF>;!f(z?}89D_8zL4EsL9_Nb&hm(?}dgoBE|! z8sQ{`&HS>4hUcX5jr~;@T>MN$vybM7mSc^aN8*8}tMMb{Nh7?uQ?yhP?&418)pbWE zi8I0VTs-6wMi7mw(wrSByO#W znRC#)Do>wtH4s*RbDAP!NY`q8ryhx*d20YCLa{HsEd*m5rI1Sk)q?OK-B;aR+!kjho zmhc>!hQ|u$w0Ruw%}qyinl}>S?tM_Nk3jNnq8=c2uHK5dNFOrfYBqb1;#AF@t#HGz zkcl0FKp_28FRgfX`P=5GF4Xl++s&V;M4G$~%8eb_@$f&YDhs@`%GPH{_0CXRpL2Ua z0mmOzJ8p(ZfS=CLcy{W%%36us10F~{@TGHKOzji9kCKf+W@Te1W=Y%84OLH1J6+;M z3-Uhm0ZGM9gR{2uUAExY1DAhP>jHkF_Iql-xt{RI;CXJ|8v}FsFE{FsV(zA*s}N18 zkA#+t_R_tgo_M8uT_G80z$i=nQ99XEBXUb2lfOPvnuFt|_MP+>y83+Q!l-*m{8l15 zq2%>7*qm=!C+e|C`j?DJBH!H~MYnSGq41BQ(le;YIpD7176_OE%Vt0$K;NtM(oGTQS zWsNyJ1ykxiyQQ@-i2!lI$H@n#bk)w9%R{6|8yH5x&H+}g{?u_)=pcJY*NlfR6S;dy zeM^ps92)I_9g^EsZB2c99S)}g?3FU;e#GeOnXI)5oZ#mX>a0RdL4*;wUQN(lM#@WT zV5lLkYj;+ddghzn1ScDeYV=EXEcGrr(2tuzJB87mu7pV&#DT+*a+lRhhAM!r)xXX%V5Q+XCc|cX(g*KOG-Y%Sz+@=3wIu&dGAc! z*)&C@jrUfFZtV~`t#&XJ_E(|ZK#o}63!9w>Y_A&X7CIY=Xvo~`tByvMtDKO{7+X8Br#jO>Bk zEpd>2pvTn{?@Y%n{L;q$?Pzim&4$gWZj-_Ss$9Pza36rUnc9M{QL!lX2&yT7m}S5N z-ziJBQbi`=P+h5djyfV4mN1hgd;cr=4psrLUvUAGJk;2g+a-p7GKMDeJNZf@H&Urx^r+}e# zOZ`=#Vq7zqZd8nzY8#~vsw3jh;Jc2aQ?|;|*cjB7bN>KTsza9&6w}IH$73oG!ebfd zx(zcrfgJZuVfSZ7MMFt&8)3vH@PN`wl$zPOc5-;x1QNWp&IgR_hI7WO9!fGuOPE}7 z>Jpp8%%h=vvPj0cgFBQ8dXK1S?G1At;i$LOcsEquUvRCF!T1`EHtd`Azfk-2W6W|F z2)-;T^_t01)OR*U#(6gL6M%zVKWLuyaBC@JA%OXuMnd&#b)K5T6CB0?DIQ`@RQ7u; zT{}fM=)Cr#b2P_M@7Z0tnWRYK z;BZ2wz5pCujg&2nT6$Mg;~KZl0=v_|Ne#xzrP37S#tzsk&}iv$LE9fxSVg{^FpQ2q zX`Ipkzz+%r2D2b`P}NJ28rB-E`Cd+jgO%WY zcYS=vbFM|d~t0U>sLEaw9Xqe;{-OHOI3wds3t zvvQo+%rr1a*xYnIM$xLabca!vPa`Bdw_&onw&p$7N9x0{yl$4T(bgBo#;l*Bb3ar% zNc6^CgYr$p>YUGdk<|}tq|OSA*1m3B4*rU}6-SI*?44cmkGIXJ;OAuAHBO?Gx!^x^ z!LsQ1HbYyfz2V~o52;4gF_hzTpQh@WEsR8EbGazGH#87k>X!KIAoKYwZDU!H=KPgn z$8}XKdoi}jY_$hhl#C!>R$T3gThv?u9gq~_4l<{vAU^>BB@RAZqG|FSp2!>Pz{h2u zRSa*)&gyV8!VJpCaJul^x8XRqY$o6mL9kp8F5yiiL^mfJD07ghWg9Le{1k<9MSxZ|KO&N+;wzm6 z%-L&*IY z92Eyub&YjZSA_(bh1n;}m2{s>*7Y61kEe~!X_In71=fz2xOEL{BBqSueaZPRUa@qD zu-lr*m<-4FhtQ3c&c)NT_8Jzl0N)VubnXvz#bCsky$sV zLnG4G>Fx$HtM&GmMjQr83ax0%NJ~B4&O3Kj2ix2pLF~DG0CqcIn&27|CAA)$ShTb{~?USgnS?sM(R0 zhjEOB0>#n2CnM!J2b}e_{8_Ay-v!{W&lefxsjBhbN%1p<=-$&WlRl(HAj<|o z{L1rpu)V~b@4#B(1}}VV4hMw#*>sXPL3@r0P}ujuN7Kj0=&O)u%KrcwO^QFo@`SDk z@a={Z7@4qC3@Bvd=AF{F!yx*kbnL+eG=@w7GJ)h(Z;iiZa8|`*-hS6~s5qAl5~qlr zTjc`}5o{c&qU?$TWDt*u5g5x#(?}kRpzvG(LEKTJ;E!Z9U%UyM2pHK0+RQOeA7kjD zF3D3x%8Z1jcDG`>WP$BCLlKu1&>EZN+X}diP5ZKgZwQ$kh7f^}5HJ>>LS>2=p2FQO zCm~GSw35Jo6$3jck1J9!@*GI~0lJOf5czjNOne2m{_1b!$^L?%VOb&j8NZV!`U(%2 z6<^;ff12frasi&BKk=NgRDFA8$~Uuy=jyqVwURRg0IO}($&Z`LJE=s+e=^mb8&xRB zl;tg!L#u_eHe3NaD5M~`;Z~~|ujI2|$#Ssm%7mm2%FRj#NCE7%;)GinD2+>KVJOci zh8%<}NX5}R6X>+iK;3Fr0Kzgddm^7;}!tTs>e-qgZifA z1H+jUWX|K_3EGZ2C!p&3va|@>FS1ulo6KUw85Xlz)ARCgM^u?x+ zrtP8%Cb5_UC)A=RsP~7R4cz)CY7kYHvBOB;zs#jIF?f$IB#)AFa)d`F3EZVJ@_wo} zT1f)AppmC-C-Xzp#MdjFp-{}y=J`P>x@s#m)Pb*Y9=?f8u*eQtG6`0?ic=$e z%_HWP$8Iv!vNC+6rs=2#&1shDRUI@9bDcA=NE|QNKF<18woa0Qo$WgD@ezW&_t@V+ zQb$uAT}GEVj>!kI_lG&U1aP+v9AP(7q}{CsH#yBNKZ32!*G5PodUbp)dw$SkZt9zQ zq5=A&83VXbJlm^Ts*%GdWe2k>6DsRXOpkCaIno=lC#qh+#|O9p#_9h6vf2~i>uU}i zHa@5>AZgMA2bD)q;5W{nl5g%#Ao&hQJ0$lyX9952rYmVpRbyBP8>;mYlzhPd|}JCEqR75HZdqxEyvOZjOPu zz`}|qY>TI01Z76(C5_SoCN}Z9=L0}p#^LG{bQH$<#y3XA2*#~PKSVV)3wJ(cIdhsj ztspWc4LQl#N+0q=GEf*i!|0@BjUzbV#CVC6~T1`6i`lF)wLJf>Ba1Gq&V2^APQ?weP;e*Cd zvu@t)EK90pI!>R9D7u++#zr?hIV`QIj+#AkIBE^ z?l?H~O1jsqsvPqK2SZ6dVcAx#6q8;mTF4%9NYw0SD4w*^$7rd))&f|3GVS(W(P!y; zI~^oXgfvNw@#=)>n*>%{bZ#wfw~f!LZue!N6ZAu?xhmvn?}fuF9>bidT=zR-asuJx z;SQ{*k<~BOP_jqKQuu|N1G3FPj1*iPg8(R)-C0t|a^gD$!1C~--2tYH%0dK2BAKpt z@eF-aX8=aw3nLk*<*3WEr3L*_$(zvksfx?mcX6I8Qv!UdKlHoi3-Pzw{4@vbrT~}<5cq9eH zd74Tl?^?)drE8j4TGN7{d5=}Nn795V=>Ey!&88=n;J5C)j!VPnrFkt9;FH;V@3ZNm zHb{-7;mr&g4LRKhpfs@RKCZ$ZqJk;6I6ytO3^c~l0o^gBhIht1a&;++*|NE;F-?fmb%)?+V3K?e%oG62Bw>bHCAH zW(a5?{Kp8}O*B=N<~og{!~Xz8*6(SCkhIfaw`DO(tjQUSobQiCk+8@K0~qy8x=8!L~N5z+=8yY(K@y|7@ZY6wPkqmr=O~B5W>=PBhO?3*zDC^RaoXZ z#6(?!=Vf~LO!RFfiLD{JjyEa4N%V9bIcZ>(gT)7PgzBQ1ts0s(BPau!To#S=H?ri- z>y;f*zzPE)!UDsCmfqcv?2!--LI@>{hUX&+0R7RDjK+uX_6P?oCC81{yfgAdh77J` zZk0@g9vS%|CT<*af{q)Y=lK5s6*z$E>AY58oFr9tcKC)yot^Oltqx{dmeoh(SE6p2 z>RR5Hl)8!M!^X|eb!M;jvr}7bmM6mVJDY&)3@S-x%WT2e4a$L5yF%-P^UBuZS)H9j zviI*`9YYPa)GhV$Jj?(AFVKx zM)KlD=Nm363t}yPriQyX&ukneHr}ANP(=(dR>7SEk~v;i?N8fFY?aecQUNK1j5)tC zpZckT*PVKbmQ6*$`5T6nS?YeSrLo)UY8)ezSlO~*?!PhB`)j<^Rb~KN8A4rp(z~cB z{2KXOGrucupa--o*y?+<^)7@$(s0oj0ahTQF~&E@`KG2a3RB<+Bw zY$SRqn-~R4;q0k>2&0cah=#*}4$FoNA{+1p5O)+fJ(TT>SEA-W`YEGY2;C8B#xlAv z65N1^1Zo*mWx6;}7q*q|^i#HiL4%f~VlCTXiVkm}KpR?E(UHPsZjp@uyYfRy_J-^b zteCb!T6QQL)yUU;9nhm-xQe7I}X@-o)j`-PbeUK z%5!h3qKN+hwf_Kj*%MZ673{^oX9Q=Guw!$3WO{|zJ0j|$EOBgO$^sDW$|1Hle63dR zY^)!}PayvQ@hi42D?h{Pf0HNrN+t6ysif7<{KBzyQT^_t`L0;1v+C>W1B2QM?m2SS zY&2L0Q_ss~w_500--P8%W~&W)^$d_v@KVWC)1)1fN89O( zK-A{9J+Q3yTXm|Ml1XNsKw8|8;xYOyB3^k|9Y#Z>$kLW|T~#F)OFp8SR=RhCmgO$? zo9!Bw8D)aj)=1_YYCJC?j;g-*Az_wd8{P1<>=e|(%BUymD~v74^AZa7j*ID9Zk?ft z&0z5e4ky&0I$NWlvWV(tX_jXSI=N8E5aeZJkcg<1xFy&t zPlrbtRKAA7(D>FgtREVTK@oRTMewL%?xu@{pe{ZaYBmr8=)W8&8}7Ja^OYk!#bApL zC$cM(wtOpW*yVAkkA!TtAB1Q2DY9S%hM)(bOUN1ShkVX1&d8H?)eI;qBod8~45@J- zU?QR#x%ENNZi3wQOlfL#G+_-SAhdF_7wLAK3?OXE;#-9j^EyGWP={#a3VL@n?mF?f zKsd?eO-yvSuBDBQdD2c49wvjb38j;qC{~Muxm@eV7(v>Kv;01%`7(c`sT%C^qpbe` z52^l4pXn$*X4HRssQzn~EU5nXQT*2|Rax~oyAG2d@s!l_27!}=IsN3H&RcHSR9xDg zsjL|;$D(6TaR84v!mf!h7JI=QA~d{H*1mGl7oR^g-by0cCrKxift|{rTduFBm5(RN z-cvh=SGuAi(CsV(^GJ@7muskk1LqCGrCxgR@zKi!bLu2*f_F=L&#HVV3u1QXE6wO4 zuf5f}IPycCv0B5d7DTuD=S0@Ij*#OV4PgdTfTW4>$inEIoG4ihML0RY+#uwS z%cmUdjeMJL-9#CwR^%2sg9kX-O&ur$RyIbscx~8aE~=}Qu5e>;+$%~XbDNE@P;=wD z5L7=WoaJ=sNgJu7XI5~*l8a?$x zE=bm&ACxNr8bH{twE}rtbbGAu@!3Gm#c<}5c3rGeBP}D9r6imc80A~N8`6Ci7UW~P zlyFv>-9=W2L=G>`ot8@xJOpzi*>L#OXe==}9m3=^>fs#t(C%gmi4kifI&-UKG)@3= zpgb%Iqh&7 zPH?%7H);nPDB0Z@&wwD)+%)o|GM4;2ta{}H-ac8m-9-6uxhqOJz7!IPGu?HlcTh&8 zJfNA6XymRdByYG>4DMl*AN{3pqpJSrH}Yiv07*gfQm#hFSx_Eud{=C-L3RHC5~==S zSgO39A6F`9reKu0g_(XqLpLGCr0}kHyR`*nr$sgd`N>rGjZzn5PJ1dW#oJ>qaMEzN z&KI7>UXZjp>D zA*$TuW!|A{h>oUbLn(6{T62+(!4LaJM!!BIvhH`~aZvqu&vLnfnAA)d(fFyLx#g0( zNNb(*-5W&_qhVp?-PTjl8bhV{F2bHcm`Oa26bNP1{hCh8wlTqgWaqk)m;wPjslxy{ z^;EKfpODlftq;f{FqxiHx(Uq2{;Jc!T>1gIXbX>WI!;p)kO=uGcXLiQQ*wYt+C$hw znlOZx(qqnoK=~^@0|Uk$GK_t)Ikp2C^$IryC|k6%a#H%Z16{GmSGv0(jyG-&-ionZ zMTrP#;YdR0^tfPS3P?9y^ zXD9}eoB_Z>xu9+6i5C=L0K5 zF7)M?Kr{JSqgfCgt%!O zAl)8hAx_6{scbh>@`#~KLp%l8U>$-W#hwb+yPr^utC*Sz++|JSGeJ5=R-=Hok`%6P z7($sXN+z@p0`0m-Aa1!E1qI5Qoyl8ca#uzo z3X&#i=XC(8I#x_TdqDgX(`ki}YH4n9*(8opwva+E-%p36>f_Zwivf>wM&V4;lh_-Qct;5Z~p230O>0U%fi)9_bQL&Km8>@`Kfgu?K-dL zJM~z%b^ibptNwGpRf?-VvcY1K;Zfni<#^>uaI!wxAKM&}-D-;Q`#rMC--}Y}?|nBY zG`RE1qU0Vnryr6Qx~IdYejGFPQC#ccqm1RlAS#=#&bT~B0rSZ0W8{HlWK|ctgWzVT zdGOo}?7b7GI%byBOB|^ij@e7yaXDBnF3Qspm9!d;rh|j> zKuZUOjgJ2SB-NJ}9f}^Je-!>XQWuEx$SYFE<>46a6mA(IKwnWGlfMaZkDZg*{mc<{KstjSl7z@PZ>A>yxsYUg%dmHOM=E3xKsN^)xP{{{W&4EXZ9pF`%xG zE4DW%1d=~MxW+J!@cB_bJ&Fif!j*~vADI%yX$Xqxys3i*J17S#UdYO}Mi!sJShP4= zVw7u%3hGaU{ZumW6{w5vqGR0y_t7zYZ9q|vE0Qp#DmU^LISn-7c3yC!Sc6V^%EmnB zb+y!O)KtJ+5@2q+eMlMEa2bH1m%7$^7+tL7E~0R2##BuBRz28gK-f;w&d3UrsoO4N zk$201Rmey8g36!nRUgWK`bz2l02o}<{{V8R{!{+|(pGM~GOkdQjd?1&zT!$`Gy6n*gw>`Kk#_ly&D{RhXTyjQQ?K}Fe{{ZhrL4Y;S zIL7!nR9$20+xRnLC+zM}T_IZ8FFq`;14D*A6;4^?u9s2NK<6V1De7NKBg{qZBSomK~xNmByEjH2?x0Cr!M67 zSlo@uuUWdLj=ljS=WmKRZa7V(z0qiE8!#_Du(g2AZtaA|*-;Z*4E%(?F|C2kAc8$t zQAM2RAgQ9uT~$Wt0EarU>Z_BI2_uZDJTS+NZYw7N-v;~IDZv~@u3Ee0vCu?S0y1?ZQ$pcw}x8jU)sVyDK;O5|=UeHGC zw>X{`B;anefO?3-J`^0gZjGpLc=Gl^&9M9wDQyb(lbqoO^OqD;hI52Fm~x8CQI{Oy z5fRFOwB<`1z!@l=)W?-9ZopAzJB8aE0NoALg~um_$pOISYgqMJtYHn*ZeR}CW>-#W zaoKA@^h9p7Ia2mfhMcJp3|Q|dI9SoEg^~gR%F`5~JB$^?j2kI*9A$F$l5iEM4?xz5 ziGUDy*+l5v*JSXdjz)uo6PXXmW<$Ou8=eO$GcYG&vZ7fCYkaG5`LUG)qyzz#G*1}{ z2VtO+6wF|5;Y0=rJ&Kv66h(L95kWXnI!48W?Tx9@!yJQP4Ij%6CUK z9B}FzIiWR%f=LN6!smRzTQOBYp$Rv64|(o&9)_EhIwrKajU zh@;(-4(BV_i}gEpjrrL@RLF~)4%sNsGB*GND1T>T&3zsGSe%Z*@rD9du95^ z4caX;IQBG})&HHvG0zN9pLmU?jU91+*hA`WSCmr&w?e?K&ls7N% z&N(UttzuDY>icc!`XQ@@x;-0=Nz`=yq{931f!MAiZV~BT|e%vV1V~SN-`AE9oHDjg>E5)eEif; zrgM(ToNWY*fzP5LjBdY$zmh4}M1JT%-9alnG6J-+CKepAr|?Ruqi(;2zmgyb;4P`7 zXdD6gDA+QIabQRtKP2f#?)=153()a#4>1Ct!te!-}QyrOQrFAx{^?6ipd$MMefj z!4Am}!n~6(7t$s>RISKD$*N z4|7|v)TGh_$?gyR7A!&NJrKsp8$;UHWcLa8RNCo99$OPZBYXvm5P4;NJ0hu&FklC4 zlT^6sa9FVd*`p2zD@=*6#&WS@*AbO=`g)f(3F2$xBP0;1eSOi^s%UjXP8xPQ6^j(n z<5wmMTB68C8W!oJY@;t3Sg}B1ZMaQAT@a=%XnEgd@0}+z=b(x59Ju2v79e)x_JwgL z3l<=bFN}Rr6}+`##8)DT_$p?8>?~M}{@lg!6$JkP8pVjy6lap6e7}ic=iB z;VM4s7AR{NwI>B)#jXsqMzLbWH4V3w)c*k7sy~w_`bx!$xq4e4Z<@C+@d}UTAJt;T Ii<#2@+1SBaod5s; literal 0 HcmV?d00001 diff --git a/static/canvas.css b/static/canvas.css index 60a531a..82b4c9c 100644 --- a/static/canvas.css +++ b/static/canvas.css @@ -66,4 +66,5 @@ html, body { canvas { background-color: lightblue; margin: 20px; -} \ No newline at end of file + image-rendering: pixelated; +} diff --git a/static/canvas.js b/static/canvas.js index 689f7d5..99de9e9 100644 --- a/static/canvas.js +++ b/static/canvas.js @@ -1,208 +1,58 @@ const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); -const canvasSize = 500; -let zoom = 1.0; -let baseGridCount = 500; // Base grid count at zoom = 1 -let gridCount = 500; -let pixelSize = canvasSize / gridCount; +const CANVAS_SIZE = 500; +const DATA_SIZE = 200; +let scale = 1; +// Array of [red, blue, green, transparency] * width * height +const image = new ImageData(DATA_SIZE, DATA_SIZE); -canvas.width = canvasSize; -canvas.height = canvasSize; - -function updateGridCount() { - return Math.floor(gridCount / 2); // Adjust grid count based on zoom -} - -function updatePixelSize() { - return canvasSize / gridCount; // Update pixel size based on new grid count -} - -function drawGrid() { - ctx.strokeStyle = '#000'; // Set grid line color - ctx.lineWidth = 1; // Set grid line width - // Draw horizontal lines - for (let i = 0; i <= gridCount; i++) { - let y = i * pixelSize; - ctx.beginPath(); - ctx.moveTo(0, y); // Start the line at the left edge (x = 0) - ctx.lineTo(canvasSize, y); // Draw to the right edge (x = canvasSize) - ctx.stroke(); - } - // Draw vertical lines - for (let i = 0; i <= gridCount; i++) { - let x = i * pixelSize; - ctx.beginPath(); - ctx.moveTo(x, 0); // Start the line at the top edge (y = 0) - ctx.lineTo(x, canvasSize); // Draw to the bottom edge (y = canvasSize) - ctx.stroke(); +// Color is an array of four numbers 0-255 (RGB + transparency) +function setPixel(data, x, y, color) { + let start = (y * DATA_SIZE + x) * 4; + for (let i = 0; i < 4; i++) { + data[start + i] = color[i]; } } -//drawGrid(); - -class Point { - // new Point(x, y) - constructor(x, y) { - this.x = x; - this.y = y; - } - // Convert canvas position to data position - canvasToData() { - return new Point( - Math.round(this.x / canvasSize * gridCount), - Math.round(this.y / canvasSize * gridCount) - ); - } - // Convert data position to canvas position - dataToCanvas() { - return new Point( - this.x / gridCount * canvasSize, - this.y / gridCount * canvasSize - ); - } - // Convert to array index - index() { - return this.y * dataSize + this.x; - } - // Basic math functions - plus(otherPoint) { - return new Point( - this.x + otherPoint.x, - this.y + otherPoint.y - ); - } - minus(otherPoint) { - return new Point( - this.x - otherPoint.x, - this.y - otherPoint.y - ); - } - scaleBy(number) { - return new Point( - this.x * number, - this.y * number - ); - } -} - -function draw(point, color) { - point = point.canvasToData().dataToCanvas(); - ctx.fillStyle = color; - ctx.fillRect(point.x, point.y, pixelSize, pixelSize); -} - -/* -canvas.addEventListener("mousedown", (e) => { - let x = e.clientX - canvas.getBoundingClientRect().left - (pixelSize / 2); - let y = e.clientY - canvas.getBoundingClientRect().top - (pixelSize / 2); - let color = document.getElementById("stroke").value; - let point = new Point(x, y); - draw(point, color); -}); -*/ - -canvas.addEventListener("mousedown", (e) => { - let x = e.clientX - canvas.getBoundingClientRect().left - (pixelSize / 2); - let y = e.clientY - canvas.getBoundingClientRect().top - (pixelSize / 2); - if (x > 249){ - if (y < 250){ - console.log("Quadrant 1"); - console.log("Previous Grid Size:", gridCount); - console.log("Previous PixelSize:", pixelSize); - gridCount = updateGridCount(); - pixelSize = updatePixelSize(); - console.log("Updated Grid Size:", gridCount); - console.log("Updated PixelSize:", pixelSize); - ctx.clearRect(0, 0, canvas.width, canvas.height); - drawStoredPixels(1); - } - else{ - console.log("Quadrant 4"); - } - } - else{ - if (y < 250){ - console.log("Quadrant 2"); - } - else{ - console.log("Quadrant 3"); - } - } - console.log("This is where the user clicked to zoom:", x); - console.log("This is where the user clicked to zoom:", y); -}); - -const pixelData = {}; - -function storePixel(x, y, color){ - pixelData[`${x},${y}`] = color; -} - -for (let i = 0; i < 500; i++){ - storePixel(i, 250, '#0000FF'); - storePixel(i, i, '#FF0000'); - storePixel(i, 499 - i, '#FFFF00'); - storePixel(250, i, '#FF6600') -} - -function drawStoredPixels(quadrant) { - if (quadrant == 1){ - for (const [key, color] of Object.entries(pixelData)) { - const [x, y] = key.split(',').map(Number); - if (x >= 250 && x <= 499 && y >= 0 && y <= 249) { - const pixelCanvasPos = new Point(x, y); - ctx.fillStyle = color; - console.log(pixelCanvasPos); - draw(pixelCanvasPos, color); - } - } - } - else{ - for (const [key, color] of Object.entries(pixelData)) { - const [x, y] = key.split(',').map(Number); - const pixelCanvasPos = new Point(x, y); - ctx.fillStyle = color; - draw(pixelCanvasPos, color); - } - } -} - -drawStoredPixels(); - -/* -document.getElementById("zoomIn").addEventListener("click", () => { - if (zoom < 100.0) { - if (zoom == 1.0){ - zoom = 4.0; - } - else{ - zoom += 4.0; - } - gridCount = updateGridCount(); - pixelSize = updatePixelSize(); - console.log(zoom); - console.log(gridCount); - } +async function draw(image) { + ctx.fillStyle = "rgb(0 0 0 255)"; ctx.clearRect(0, 0, canvas.width, canvas.height); - drawGrid(); // Redraw the grid with updated zoom + let drawScale = CANVAS_SIZE / DATA_SIZE * scale; + let bitmap = await createImageBitmap(image); + ctx.imageSmoothingEnabled = false; + ctx.scale(drawScale, drawScale); + ctx.drawImage(bitmap, 0, 0); + ctx.setTransform(1, 0, 0, 1, 0, 0); +} + +canvas.addEventListener("wheel", async (e) => { + if (e.deltaY < 0) { + scale += 0.1; + } else if (e.deltaY > 0) { + scale -= 0.1; + } + await draw(image); }); -/* -document.getElementById("zoomOut").addEventListener("click", () => { - if (zoom > 1.0) { - if (zoom <= 4.0){ - zoom = 1.0; - } - else{ - zoom -= 4.0; - } - gridCount = updateGridCount(); - pixelSize = updatePixelSize(); - console.log(zoom); - console.log(gridCount); +canvas.addEventListener("contextmenu", (e) => { + e.preventDefault(); +}) + +canvas.addEventListener("mousedown", async (e) => { + // Left click + if (e.button == 0) { + let x = e.clientX - canvas.getBoundingClientRect().left; + let y = e.clientY - canvas.getBoundingClientRect().top; + + let cx = Math.round(x / (CANVAS_SIZE * scale) * DATA_SIZE); + let cy = Math.round(y / (CANVAS_SIZE * scale) * DATA_SIZE); + + setPixel(image.data, cx, cy, [255, 0, 0, 255]); + await draw(image); + } + // Right click + else if (e.button == 2) { + } - ctx.clearRect(0, 0, canvas.width, canvas.height); - drawGrid(); // Redraw the grid with updated zoom }); -*/ \ No newline at end of file From cb296a5fe41077dc353ee0d49ceb89ba01164634 Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 6 Nov 2024 02:02:36 -0600 Subject: [PATCH 29/29] Implemented zooming and panning --- static/canvas.js | 75 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 13 deletions(-) diff --git a/static/canvas.js b/static/canvas.js index 99de9e9..236e9cb 100644 --- a/static/canvas.js +++ b/static/canvas.js @@ -4,8 +4,29 @@ const ctx = canvas.getContext("2d"); const CANVAS_SIZE = 500; const DATA_SIZE = 200; let scale = 1; +let offx = 0; +let offy = 0; + +let clickx = 0; +let clicky = 0; + +let mouseClicked = false; // Array of [red, blue, green, transparency] * width * height -const image = new ImageData(DATA_SIZE, DATA_SIZE); +let image = new ImageData(DATA_SIZE, DATA_SIZE); +// Drawable image +let bitmap = null; + +window.addEventListener("load", async (e) => { + // Create drawable image, update it every half second + await redraw(); + setInterval(redraw, 500); + setInterval(draw, 500); +}); + + +async function redraw() { + bitmap = await createImageBitmap(image); +} // Color is an array of four numbers 0-255 (RGB + transparency) function setPixel(data, x, y, color) { @@ -15,24 +36,28 @@ function setPixel(data, x, y, color) { } } -async function draw(image) { +function draw() { ctx.fillStyle = "rgb(0 0 0 255)"; ctx.clearRect(0, 0, canvas.width, canvas.height); let drawScale = CANVAS_SIZE / DATA_SIZE * scale; - let bitmap = await createImageBitmap(image); ctx.imageSmoothingEnabled = false; ctx.scale(drawScale, drawScale); - ctx.drawImage(bitmap, 0, 0); + ctx.drawImage(bitmap, offx, offy); ctx.setTransform(1, 0, 0, 1, 0, 0); } canvas.addEventListener("wheel", async (e) => { + let oldScale = scale; if (e.deltaY < 0) { scale += 0.1; } else if (e.deltaY > 0) { scale -= 0.1; } - await draw(image); + /* + offX += (DATA_SIZE * scale - DATA_SIZE * oldScale) / 2; + offY += (DATA_SIZE * scale - DATA_SIZE * oldScale) / 2; + */ + draw(image); }); canvas.addEventListener("contextmenu", (e) => { @@ -42,17 +67,41 @@ canvas.addEventListener("contextmenu", (e) => { canvas.addEventListener("mousedown", async (e) => { // Left click if (e.button == 0) { + mouseClicked = true; + setTimeout(() => { + mouseClicked = false; + }, 200); + } +}); + +canvas.addEventListener("mouseup", async (e) => { + if (e.button == 0 && mouseClicked) { + mouseClicked = false; + + clickx = e.clientX; + clicky = e.clientY; + let x = e.clientX - canvas.getBoundingClientRect().left; let y = e.clientY - canvas.getBoundingClientRect().top; - let cx = Math.round(x / (CANVAS_SIZE * scale) * DATA_SIZE); - let cy = Math.round(y / (CANVAS_SIZE * scale) * DATA_SIZE); - - setPixel(image.data, cx, cy, [255, 0, 0, 255]); - await draw(image); - } - // Right click - else if (e.button == 2) { + let cx = Math.round(x / (CANVAS_SIZE * scale) * DATA_SIZE - offx); + let cy = Math.round(y / (CANVAS_SIZE * scale) * DATA_SIZE - offy); + console.log(cx, cy); + if (cx < 0 || cx > CANVAS_SIZE || cy < 0 || cy > CANVAS_SIZE) { + return; + } + setPixel(image.data, cx, cy, [255, 0, 0, 255]); + await redraw(); + draw(); } }); + +canvas.addEventListener("mousemove", async (e) => { + // `buttons` is a bitflag for some dumb reason + if (e.buttons & 1) { + offx += e.movementX * (1.0 / scale) * 0.45; + offy += e.movementY * (1.0 / scale) * 0.45; + draw(image); + } +});