diff options
author | Lizzy Hunt <elizabeth.hunt@simponic.xyz> | 2024-03-10 16:45:26 -0600 |
---|---|---|
committer | Lizzy Hunt <elizabeth.hunt@simponic.xyz> | 2024-03-10 16:45:26 -0600 |
commit | def61909c2423b19b063a5807cc399a41de39974 (patch) | |
tree | 0b035fbb1c29ac858b042092d183824f8bc1a5b5 | |
parent | 5a85dd89bc82d8fc133c0e9a58859ae9b98dbe0f (diff) | |
download | tilde.club-def61909c2423b19b063a5807cc399a41de39974.tar.gz tilde.club-def61909c2423b19b063a5807cc399a41de39974.zip |
fruitvote
43 files changed, 533 insertions, 20 deletions
@@ -1,3 +1,5 @@ module tilde.club/~simponic go 1.21.5 + +require github.com/mattn/go-sqlite3 v1.14.22 // indirect @@ -0,0 +1,2 @@ +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/html/fruitvote/fruits.json b/html/fruitvote/fruits.json new file mode 100644 index 0000000..a97f846 --- /dev/null +++ b/html/fruitvote/fruits.json @@ -0,0 +1,126 @@ +[ + { + "name": "apple", + "img": "/~simponic/img/fruitvote/dock-april.png" + }, + { + "name": "apricot", + "img": "/~simponic/img/fruitvote/apricot.jpg" + }, + { + "name": "avocado", + "img": "/~simponic/img/fruitvote/avocado.jpg" + }, + { + "name": "banana", + "img": "/~simponic/img/fruitvote/banana.png" + }, + { + "name": "blackberry", + "img": "/~simponic/img/fruitvote/blackberry.jpg" + }, + { + "name": "blueberry", + "img": "/~simponic/img/fruitvote/blueberry.jpg" + }, + { + "name": "cherry", + "img": "/~simponic/img/fruitvote/cherry.jpg" + }, + { + "name": "coconut", + "img": "/~simponic/img/fruitvote/coconut.jpg" + }, + { + "name": "cranberry", + "img": "/~simponic/img/fruitvote/cranberry.jpg" + }, + { + "name": "fig", + "img": "/~simponic/img/fruitvote/fig.jpg" + }, + { + "name": "grape", + "img": "/~simponic/img/fruitvote/grape.jpg" + }, + { + "name": "guava", + "img": "/~simponic/img/fruitvote/guava.jpg" + }, + { + "name": "honeydew", + "img": "/~simponic/img/fruitvote/honeydew.jpeg" + }, + { + "name": "kiwi", + "img": "/~simponic/img/fruitvote/kiwi.jpg" + }, + { + "name": "lemon", + "img": "/~simponic/img/fruitvote/lemon.jpg" + }, + { + "name": "lime", + "img": "/~simponic/img/fruitvote/lime.jpg" + }, + { + "name": "mango", + "img": "/~simponic/img/fruitvote/mango.jpg" + }, + { + "name": "melon", + "img": "/~simponic/img/fruitvote/melon.jpg" + }, + { + "name": "nectarine", + "img": "/~simponic/img/fruitvote/nectarine.jpg" + }, + { + "name": "orange", + "img": "/~simponic/img/fruitvote/orange.jpg" + }, + { + "name": "peach", + "img": "/~simponic/img/fruitvote/peach.jpg" + }, + { + "name": "pear", + "img": "/~simponic/img/fruitvote/pear.jpg" + }, + { + "name": "pineapple", + "img": "/~simponic/img/fruitvote/pineapple.jpg" + }, + { + "name": "persimmon", + "img": "/~simponic/img/fruitvote/persimmon.jpg" + }, + { + "name": "plum", + "img": "/~simponic/img/fruitvote/plum.jpg" + }, + { + "name": "pomegranate", + "img": "/~simponic/img/fruitvote/pomegranate.jpg" + }, + { + "name": "pumpkin", + "img": "/~simponic/img/fruitvote/pumpkin.jpg" + }, + { + "name": "raspberry", + "img": "/~simponic/img/fruitvote/raspberry.jpg" + }, + { + "name": "strawberry", + "img": "/~simponic/img/fruitvote/strawberry.jpg" + }, + { + "name": "tomato", + "img": "/~simponic/img/fruitvote/tomato.jpg" + }, + { + "name": "watermelon", + "img": "/~simponic/img/fruitvote/watermelon.jpg" + } +] diff --git a/html/fruitvote/fruitvote b/html/fruitvote/fruitvote Binary files differnew file mode 100755 index 0000000..51485cf --- /dev/null +++ b/html/fruitvote/fruitvote diff --git a/html/fruitvote/main.go b/html/fruitvote/main.go index b41c88f..cfd1bb8 100644 --- a/html/fruitvote/main.go +++ b/html/fruitvote/main.go @@ -1,9 +1,13 @@ package main import ( + "database/sql" + "encoding/json" "flag" "fmt" + _ "github.com/mattn/go-sqlite3" "log" + "math" "net" "net/http" "os" @@ -11,51 +15,241 @@ import ( "os/signal" "strings" "syscall" + "text/template" ) -func indexHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte("Hello, this is a Unix socket HTTP server in Go!")) +type Fruit struct { + Name string `json:"name"` + Img string `json:"img"` + Elo int `json:"elo"` } -func healthCheckHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte("healthy")) +type Context struct { + db *sql.DB + users []string + templatePath string + socketPath string + fruitsPath string +} + +type CurriedContextHandler func(*Context, http.ResponseWriter, *http.Request) + +func curryContext(context *Context) func(CurriedContextHandler) http.HandlerFunc { + return func(handler CurriedContextHandler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.URL.Path, r.RemoteAddr) + + handler(context, w, r) + } + } +} + +func indexHandler(context *Context, resp http.ResponseWriter, req *http.Request) { + // get from POST + winner := req.FormValue("winner") + contestants := req.Form["contestant[]"] + + if winner != "" && len(contestants) == 2 { + losingFruitName := string(contestants[0]) + if losingFruitName == winner { + losingFruitName = string(contestants[1]) + } + winningFruit := fruitByName(context.db, winner) + losingFruit := fruitByName(context.db, losingFruitName) + + winningFruit.Elo, losingFruit.Elo = updateElo(winningFruit.Elo, losingFruit.Elo) + + updateFruit(context.db, winningFruit) + updateFruit(context.db, losingFruit) + + log.Println(winningFruit.Name, "won against", losingFruit.Name, "new elo:", winningFruit.Elo, losingFruit.Elo) + } + + fruitOne := randomFruit(context.db) + fruitTwo := randomFruit(context.db) + for fruitOne.Name == fruitTwo.Name { + fruitTwo = randomFruit(context.db) + } + + templateFile := context.templatePath + "/vote.html" + vote, err := template.ParseFiles(templateFile) + if err != nil { + panic(err) + } + + fruits := []Fruit{fruitOne, fruitTwo} + err = vote.Execute(resp, fruits) + if err != nil { + panic(err) + } + resp.Header().Set("Content-Type", "text/html") +} + +func getStatsHandler(context *Context, resp http.ResponseWriter, _req *http.Request) { + rows, err := context.db.Query("SELECT name, img, elo FROM fruits ORDER BY elo DESC") + if err != nil { + panic(err) + } + defer rows.Close() + + fruits := []Fruit{} + for rows.Next() { + fruit := Fruit{} + err = rows.Scan(&fruit.Name, &fruit.Img, &fruit.Elo) + if err != nil { + panic(err) + } + fruits = append(fruits, fruit) + } + + templateFile := context.templatePath + "/stats.html" + stats, err := template.ParseFiles(templateFile) + if err != nil { + panic(err) + } + + err = stats.Execute(resp, fruits) + if err != nil { + panic(err) + } + resp.Header().Set("Content-Type", "text/html") +} + +func healthCheckHandler(context *Context, resp http.ResponseWriter, req *http.Request) { + resp.WriteHeader(http.StatusOK) + resp.Write([]byte("healthy")) } func main() { - socketPath, users := getArgs() - os.Remove(socketPath) + log.Println("starting server...") + log.SetFlags(log.LstdFlags | log.Lshortfile) + + context := getArgs() + log.Println("removing socket file", context.socketPath) + os.Remove(context.socketPath) - listener, err := net.Listen("unix", socketPath) + log.Println("migrating database...") + migrate(context.db) + seedFruits(context.db, context.fruitsPath) + + listener, err := net.Listen("unix", context.socketPath) if err != nil { panic(err) } - os.Chmod(socketPath, 0700) + log.Println("listening on", context.socketPath) + os.Chmod(context.socketPath, 0700) sigc := make(chan os.Signal, 1) signal.Notify(sigc, os.Interrupt, os.Kill, syscall.SIGTERM) go func(c chan os.Signal) { - // Wait for a SIGINT or SIGKILL: sig := <-c - log.Printf("Caught signal %s: shutting down.", sig) + log.Printf("caught signal %s: shutting down.", sig) listener.Close() os.Exit(0) }(sigc) defer listener.Close() - for _, user := range strings.Split(users, ",") { - setACL(socketPath, user) + log.Println("setting ACLs for users", context.users) + for _, user := range context.users { + setACL(context.socketPath, user) } + curriedContext := curryContext(context) mux := http.NewServeMux() - mux.HandleFunc("/", indexHandler) - mux.HandleFunc("/health", healthCheckHandler) + mux.HandleFunc("/", curriedContext(indexHandler)) + mux.HandleFunc("/health", curriedContext(healthCheckHandler)) + mux.HandleFunc("/stats", curriedContext(getStatsHandler)) + log.Println("serving http...") http.Serve(listener, mux) } +func calculateRatingDelta(winnerRating int, loserRating int, K int) (int, int) { + winnerRatingFloat := float64(winnerRating) + loserRatingFloat := float64(loserRating) + + expectedScoreWinner := 1 / (1 + math.Pow(10, (loserRatingFloat-winnerRatingFloat)/400)) + expectedScoreLoser := 1 - expectedScoreWinner + + changeWinner := int(math.Round(float64(K) * (1 - expectedScoreWinner))) + changeLoser := -int(math.Round(float64(K) * (expectedScoreLoser))) + + return changeWinner, changeLoser +} + +func updateElo(winnerElo int, loserElo int) (int, int) { + const K = 32 + + changeWinner, changeLoser := calculateRatingDelta(winnerElo, loserElo, K) + + newWinnerElo := winnerElo + changeWinner + newLoserElo := loserElo + changeLoser + + return newWinnerElo, newLoserElo +} + +func seedFruits(db *sql.DB, fruitsPath string) { + log.Println("seeding fruits (haha)...") + + fruitsContents, err := os.ReadFile(fruitsPath) + if err != nil { + panic(err) + } + jsonFruits := []Fruit{} + err = json.Unmarshal(fruitsContents, &jsonFruits) + if err != nil { + panic(err) + } + + for _, fruit := range jsonFruits { + // insert if not exists + _, err := db.Exec("INSERT OR IGNORE INTO fruits (name, img) VALUES (?, ?)", fruit.Name, fruit.Img) + if err != nil { + panic(err) + } + } +} + +func migrate(db *sql.DB) { + log.Println("creating fruits table...") + _, err := db.Exec(` + CREATE TABLE IF NOT EXISTS fruits ( + name TEXT PRIMARY KEY, + img TEXT, + elo INTEGER DEFAULT 1400 + ); + `) + if err != nil { + panic(err) + } +} + +func randomFruit(db *sql.DB) Fruit { + fruit := Fruit{} + err := db.QueryRow("SELECT name, img, elo FROM fruits ORDER BY RANDOM() LIMIT 1").Scan(&fruit.Name, &fruit.Img, &fruit.Elo) + if err != nil { + panic(err) + } + return fruit +} + +func fruitByName(db *sql.DB, name string) Fruit { + fruit := Fruit{} + err := db.QueryRow("SELECT name, img, elo FROM fruits WHERE name = ?", name).Scan(&fruit.Name, &fruit.Img, &fruit.Elo) + if err != nil { + panic(err) + } + return fruit +} + +func updateFruit(db *sql.DB, fruit Fruit) { + _, err := db.Exec("UPDATE fruits SET img = ?, elo = ? WHERE name = ?", fruit.Img, fruit.Elo, fruit.Name) + if err != nil { + panic(err) + } +} + func setACL(socketPath, user string) { cmd := exec.Command("setfacl", "-m", "u:"+user+":rwx", socketPath) if err := cmd.Run(); err != nil { @@ -63,15 +257,34 @@ func setACL(socketPath, user string) { } } -func getArgs() (string, string) { +func getArgs() *Context { socketPath := flag.String("socket-path", "/tmp/go-server.sock", "Path to the Unix socket") users := flag.String("users", "", "Comma-separated list of users for ACL") + database := flag.String("database-path", "/tmp/go-server.db", "Path to the SQLite database") + fruitsPath := flag.String("fruits", "/dev/null", "Path to the fruits file") + templatePath := flag.String("template", "", "Path to the template directory") flag.Parse() if *users == "" { fmt.Println("You must specify at least one user with --users") os.Exit(1) } + if *templatePath == "" { + fmt.Println("You must specify a template directory with --template") + os.Exit(1) + } - return *socketPath, *users + log.Println("opening database at", *database, "with foreign keys enabled") + db, err := sql.Open("sqlite3", *database+"?_foreign_keys=on") + if err != nil { + panic(err) + } + + return &Context{ + db: db, + users: strings.Split(*users, ","), + socketPath: *socketPath, + fruitsPath: *fruitsPath, + templatePath: *templatePath, + } } diff --git a/html/fruitvote/templates/stats.html b/html/fruitvote/templates/stats.html new file mode 100644 index 0000000..c5fea5c --- /dev/null +++ b/html/fruitvote/templates/stats.html @@ -0,0 +1,17 @@ +<table> + <tr> + <th>picture</th> + <th>name</th> + <th>elo</th> + </tr> + {{ range . }} + <tr> + <td><img src="{{ .Img }}" alt="{{ .Name }}" width="100" height="100"></td> + <td>{{ .Name }}</td> + <td>{{ .Elo }}</td> + </tr> + {{ end }} +</table> +<br /> +<a href="/~simponic/fruitvote">back</a> +<br /> diff --git a/html/fruitvote/templates/vote.html b/html/fruitvote/templates/vote.html new file mode 100644 index 0000000..6bc27df --- /dev/null +++ b/html/fruitvote/templates/vote.html @@ -0,0 +1,28 @@ + +<form method="POST"> + <div class="fruitvote"> + {{ range $i, $fruit := . }} + {{ if $i }} + <div class="versus"> + <h1>OR</h1> + </div> + {{ end }} + <label class="contestant"> + <input type="radio" name="winner" value="{{ .Name }}" {{ if eq $i 0 }} checked {{ end }} /> + <div> + <img src="{{ .Img }}" alt="image" /> + <p>{{ .Name }}</p> + </div> + </label> + <input type="hidden" name="contestant[]" value="{{ .Name }}" /> + {{ end }} + </div> + <br /> + <div> + <input type="submit" value="Vote" /> + </div> +</form> +<br /> +<div><a href="/~simponic/fruitvote"><button>Skip</button></a></div> +<br /> +<div><a href="/~simponic/fruitvote/stats">view rankings!</a></div> diff --git a/html/public/css/style.css b/html/public/css/style.css index a4f243f..95829c3 100644 --- a/html/public/css/style.css +++ b/html/public/css/style.css @@ -75,3 +75,100 @@ p { li { margin-left: 20px; } + +.fruitvote { + display: flex; + flex-direction: row; + margin-top: 20px; + gap: 2rem; + max-width: 800px; +} + +.contestant { + display: flex; + flex: 1; + flex-direction: column; + align-items: stretch; + border: 2px solid #ff69b4; + border-radius: 10px; + padding: 0.5rem; +} + +.contestant div { + display: flex; + flex-direction: column; + justify-content: space-between; + border-radius: 10px; + height: 100%; + transition: background-color 0.3s ease; + padding: 1rem; +} + +.contestant > input { + visibility: hidden; + position: absolute; +} + +.contestant div:hover { + background-color: #ff69b4; + color: #2a2a2a; + + cursor: pointer; +} + +.contestant > input:checked + div { + background-color: #ff69b4; + color: #2a2a2a; + + cursor: pointer; +} + +.contestant div img { + width: auto; + height: auto; + max-width: 100%; + max-height: 100%; + + border-radius: 10px; +} + +.versus { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; +} + +table { + width: auto; /* Adjust based on content, not full width */ + border-collapse: collapse; + background-color: #383838; /* Darker background for contrast */ +} + +th, +td { + padding: 12px 20px; /* Good padding for readability */ + border: 1px solid #f4c2c2; /* Soft pink borders */ + color: #f4c2c2; /* Soft pink text */ + text-align: left; +} + +thead th { + background-color: #ff69b4; /* Brighter pink for header */ + color: white; /* White text for contrast */ + font-family: "Comic Sans MS", "Chalkboard SE", sans-serif; +} + +tbody tr:nth-child(odd) { + background-color: #2f2f2f; /* Slightly lighter background for every other row for readability */ +} + +tbody tr { + transition: background-color 0.3s ease; +} + +tbody tr:hover { + background-color: #ff47da; /* Change to a lighter pink on hover for interactivity */ + color: #2a2a2a; /* Dark text for contrast */ +} diff --git a/html/public/fruitvote/GoPage.php b/html/public/fruitvote/GoPage.php index 864c1f7..7e03c35 100644 --- a/html/public/fruitvote/GoPage.php +++ b/html/public/fruitvote/GoPage.php @@ -4,7 +4,7 @@ class GoPage { private $socket; private $template; - public function __construct($page, $socket = "/home/simponic/fruitvote/http.sock", $template = "../template.html") { + public function __construct($page, $socket = "/home/lizzy/fruitvote/http.sock", $template = "../template.html") { $this->page = $page; $this->socket = $socket; $this->template = $template; @@ -24,9 +24,23 @@ class GoPage { curl_setopt($ch, CURLOPT_UNIX_SOCKET_PATH, $this->socket); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + // forward query params + $query = $_SERVER['QUERY_STRING']; + if ($query) { + curl_setopt($ch, CURLOPT_URL, $url."?".$query); + } + + //forward post data + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, file_get_contents('php://input')); + } + + $output = curl_exec($ch); curl_close($ch); - // todo: get headers / cookies, forward back response + + return $output; } diff --git a/html/public/fruitvote/health.php b/html/public/fruitvote/health.php new file mode 100644 index 0000000..8cea676 --- /dev/null +++ b/html/public/fruitvote/health.php @@ -0,0 +1,7 @@ +<?php + +require_once("GoPage.php"); + +$page = new GoPage("/health"); +echo $page->render(); +?> diff --git a/html/public/fruitvote/stats.php b/html/public/fruitvote/stats.php new file mode 100644 index 0000000..5147723 --- /dev/null +++ b/html/public/fruitvote/stats.php @@ -0,0 +1,7 @@ +<?php + +require_once("GoPage.php"); + +$page = new GoPage("/stats"); +echo $page->render(); +?> diff --git a/html/public/img/fruitvote/apricot.jpg b/html/public/img/fruitvote/apricot.jpg Binary files differnew file mode 100644 index 0000000..5ffc321 --- /dev/null +++ b/html/public/img/fruitvote/apricot.jpg diff --git a/html/public/img/fruitvote/avocado.jpg b/html/public/img/fruitvote/avocado.jpg Binary files differnew file mode 100644 index 0000000..a0eb21c --- /dev/null +++ b/html/public/img/fruitvote/avocado.jpg diff --git a/html/public/img/fruitvote/banana.png b/html/public/img/fruitvote/banana.png Binary files differnew file mode 100644 index 0000000..6e69d77 --- /dev/null +++ b/html/public/img/fruitvote/banana.png diff --git a/html/public/img/fruitvote/blackberry.jpg b/html/public/img/fruitvote/blackberry.jpg Binary files differnew file mode 100644 index 0000000..e097829 --- /dev/null +++ b/html/public/img/fruitvote/blackberry.jpg diff --git a/html/public/img/fruitvote/blueberry.jpg b/html/public/img/fruitvote/blueberry.jpg Binary files differnew file mode 100644 index 0000000..9794493 --- /dev/null +++ b/html/public/img/fruitvote/blueberry.jpg diff --git a/html/public/img/fruitvote/cherry.jpg b/html/public/img/fruitvote/cherry.jpg Binary files differnew file mode 100644 index 0000000..277c17c --- /dev/null +++ b/html/public/img/fruitvote/cherry.jpg diff --git a/html/public/img/fruitvote/coconut.jpg b/html/public/img/fruitvote/coconut.jpg Binary files differnew file mode 100644 index 0000000..76044f4 --- /dev/null +++ b/html/public/img/fruitvote/coconut.jpg diff --git a/html/public/img/fruitvote/cranberry.jpg b/html/public/img/fruitvote/cranberry.jpg Binary files differnew file mode 100644 index 0000000..fe8513a --- /dev/null +++ b/html/public/img/fruitvote/cranberry.jpg diff --git a/html/public/img/fruitvote/dock-april.png b/html/public/img/fruitvote/dock-april.png Binary files differnew file mode 100644 index 0000000..1cd82d2 --- /dev/null +++ b/html/public/img/fruitvote/dock-april.png diff --git a/html/public/img/fruitvote/fig.jpg b/html/public/img/fruitvote/fig.jpg Binary files differnew file mode 100644 index 0000000..643a49b --- /dev/null +++ b/html/public/img/fruitvote/fig.jpg diff --git a/html/public/img/fruitvote/grape.jpg b/html/public/img/fruitvote/grape.jpg Binary files differnew file mode 100644 index 0000000..ce341d4 --- /dev/null +++ b/html/public/img/fruitvote/grape.jpg diff --git a/html/public/img/fruitvote/guava.jpg b/html/public/img/fruitvote/guava.jpg Binary files differnew file mode 100644 index 0000000..953ed0b --- /dev/null +++ b/html/public/img/fruitvote/guava.jpg diff --git a/html/public/img/fruitvote/honeydew.jpeg b/html/public/img/fruitvote/honeydew.jpeg Binary files differnew file mode 100644 index 0000000..1e796e5 --- /dev/null +++ b/html/public/img/fruitvote/honeydew.jpeg diff --git a/html/public/img/fruitvote/kiwi.jpg b/html/public/img/fruitvote/kiwi.jpg Binary files differnew file mode 100644 index 0000000..6b048d5 --- /dev/null +++ b/html/public/img/fruitvote/kiwi.jpg diff --git a/html/public/img/fruitvote/lemon.jpg b/html/public/img/fruitvote/lemon.jpg Binary files differnew file mode 100644 index 0000000..9623514 --- /dev/null +++ b/html/public/img/fruitvote/lemon.jpg diff --git a/html/public/img/fruitvote/lime.jpg b/html/public/img/fruitvote/lime.jpg Binary files differnew file mode 100644 index 0000000..10302fa --- /dev/null +++ b/html/public/img/fruitvote/lime.jpg diff --git a/html/public/img/fruitvote/mango.jpg b/html/public/img/fruitvote/mango.jpg Binary files differnew file mode 100644 index 0000000..a9eb35e --- /dev/null +++ b/html/public/img/fruitvote/mango.jpg diff --git a/html/public/img/fruitvote/melon.jpg b/html/public/img/fruitvote/melon.jpg Binary files differnew file mode 100644 index 0000000..dc45f27 --- /dev/null +++ b/html/public/img/fruitvote/melon.jpg diff --git a/html/public/img/fruitvote/nectarine.jpg b/html/public/img/fruitvote/nectarine.jpg Binary files differnew file mode 100644 index 0000000..24fddbe --- /dev/null +++ b/html/public/img/fruitvote/nectarine.jpg diff --git a/html/public/img/fruitvote/orange.jpg b/html/public/img/fruitvote/orange.jpg Binary files differnew file mode 100644 index 0000000..1bb12a1 --- /dev/null +++ b/html/public/img/fruitvote/orange.jpg diff --git a/html/public/img/fruitvote/peach.jpg b/html/public/img/fruitvote/peach.jpg Binary files differnew file mode 100644 index 0000000..9690ca6 --- /dev/null +++ b/html/public/img/fruitvote/peach.jpg diff --git a/html/public/img/fruitvote/pear.jpg b/html/public/img/fruitvote/pear.jpg Binary files differnew file mode 100644 index 0000000..22c2a01 --- /dev/null +++ b/html/public/img/fruitvote/pear.jpg diff --git a/html/public/img/fruitvote/persimmon.jpg b/html/public/img/fruitvote/persimmon.jpg Binary files differnew file mode 100644 index 0000000..6af3afe --- /dev/null +++ b/html/public/img/fruitvote/persimmon.jpg diff --git a/html/public/img/fruitvote/pineapple.jpg b/html/public/img/fruitvote/pineapple.jpg Binary files differnew file mode 100644 index 0000000..bbeeeb2 --- /dev/null +++ b/html/public/img/fruitvote/pineapple.jpg diff --git a/html/public/img/fruitvote/plum.jpg b/html/public/img/fruitvote/plum.jpg Binary files differnew file mode 100644 index 0000000..61a8893 --- /dev/null +++ b/html/public/img/fruitvote/plum.jpg diff --git a/html/public/img/fruitvote/pomegranate.jpg b/html/public/img/fruitvote/pomegranate.jpg Binary files differnew file mode 100644 index 0000000..5be6859 --- /dev/null +++ b/html/public/img/fruitvote/pomegranate.jpg diff --git a/html/public/img/fruitvote/pumpkin.jpg b/html/public/img/fruitvote/pumpkin.jpg Binary files differnew file mode 100644 index 0000000..8a20485 --- /dev/null +++ b/html/public/img/fruitvote/pumpkin.jpg diff --git a/html/public/img/fruitvote/raspberry.jpg b/html/public/img/fruitvote/raspberry.jpg Binary files differnew file mode 100644 index 0000000..07a33b2 --- /dev/null +++ b/html/public/img/fruitvote/raspberry.jpg diff --git a/html/public/img/fruitvote/strawberry.jpg b/html/public/img/fruitvote/strawberry.jpg Binary files differnew file mode 100644 index 0000000..22ee16b --- /dev/null +++ b/html/public/img/fruitvote/strawberry.jpg diff --git a/html/public/img/fruitvote/tomato.jpg b/html/public/img/fruitvote/tomato.jpg Binary files differnew file mode 100644 index 0000000..846fb08 --- /dev/null +++ b/html/public/img/fruitvote/tomato.jpg diff --git a/html/public/img/fruitvote/versus.gif b/html/public/img/fruitvote/versus.gif Binary files differnew file mode 100644 index 0000000..6d45ee6 --- /dev/null +++ b/html/public/img/fruitvote/versus.gif diff --git a/html/public/img/fruitvote/watermelon.jpg b/html/public/img/fruitvote/watermelon.jpg Binary files differnew file mode 100644 index 0000000..1db28d9 --- /dev/null +++ b/html/public/img/fruitvote/watermelon.jpg |