diff --git a/apps/optimism/optimistic_score_prototype/global.R b/apps/optimism/optimistic_score_prototype/global.R index 2eef25f..5c2daa2 100644 --- a/apps/optimism/optimistic_score_prototype/global.R +++ b/apps/optimism/optimistic_score_prototype/global.R @@ -5,9 +5,11 @@ library(rjson) #library(devtools) #install_github("flipsidecrypto/user_metrics/apps/optimism/opAttestR") +#install.packages("~/user_metrics/apps/optimism/opAttestR_0.0.0.9000.tar.gz", repos = NULL, type="source") - -load("data.RData") +ifelse(Sys.info()[["user"]] == "rstudio-connect", + load("/rstudio-data/optimist_score_prototype_data.RData"), + load("data.RData")) signerPrivateKey <- fromJSON(file="./secrets.json")$privateKey provider <- fromJSON(file="./secrets.json")$provider diff --git a/apps/optimism/optimistic_score_prototype/server.R b/apps/optimism/optimistic_score_prototype/server.R index d8c9d81..6d65f9a 100644 --- a/apps/optimism/optimistic_score_prototype/server.R +++ b/apps/optimism/optimistic_score_prototype/server.R @@ -1,130 +1,142 @@ function(input, output, session) { - # read the connected address from the metamask connect funciton - output$connectedaddress <- renderText(paste0("connected as: ", input$eth_address)) + # read the connected address from the metamask connect function + # output$connectedaddress <- renderText(paste0("connected as: ", input$eth_address)) # isolate the data for that address so we can use it over and over - thisAddyData <- reactive(op.metrics.w[user_address == tolower(input$eth_address)]) - #thisAddyData <- function() op.metrics.w[user_address == tolower(input$eth_address)] + thisAddyData <- reactive({ + + print("connectpop:") + print(input$connectpop) + print("connect:") + print(input$connect) + print("address") + print(input$eth_address) + op.metrics.w[user_address == tolower(input$eth_address)] + }) - # get the airdrop score and output an empty or full star depending on achievement + observeEvent(input$eth_address, { + if(substr(input$eth_address, 1, 2) == "0x") { + updateActionButton(session = session, inputId = "connect", + label = paste0("connected as ", substr(input$eth_address, 1, 7), "..."), + icon = character(0)) + } else { + + updateActionButton(session = session, inputId = "connect", + icon = icon("wallet"), label = " Connect Wallet") + } + }) + + output$connectedaddress <- renderText({ + if(substr(input$eth_address, 1, 2) == "0x") { + paste0("Connected as ", substr(input$eth_address, 1, 10), "...") + } else { + "" + } + }) + + # get the airdrop score and output an empty or full button_filled depending on achievement # for the connected address - output$airdropscore <- renderImage({ + output$airdropscore <- renderText({ # no address available: if(substr(input$eth_address, 1, 2) != "0x") { - return(list(src = "www/emptystar.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "X")) + return(0) # does not score or no data available } else if (nrow(thisAddyData()) == 0) { - return(list(src = "www/emptystar.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "X")) + return(0) # score! } else if (thisAddyData()$airdrop_score == 0) { - return(list(src = "www/emptystar.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "X")) + return(0) } else { - return(list(src = "www/star.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "⭐️")) + return(1) } - }, deleteFile = FALSE) + }) # repeat ^ for the other 4 scores: - output$nftscore <- renderImage({ + output$nftscore <- renderText({ # no address available: if(substr(input$eth_address, 1, 2) != "0x") { - return(list(src = "www/emptystar.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "X")) + return(0) # does not score or no data available } else if (nrow(thisAddyData()) == 0) { - return(list(src = "www/emptystar.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "X")) + return(0) } else if (thisAddyData()$nft_score == 0) { - return(list(src = "www/emptystar.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "X")) + return(0) # score! } else { - return(list(src = "www/star.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "⭐️")) + return(1) } - }, deleteFile = FALSE) + }) - output$delegatescore <- renderImage({ + output$delegatescore <- renderText({ # no address available: if(substr(input$eth_address, 1, 2) != "0x") { - return(list(src = "www/emptystar.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "X")) + return(0) # does not score or no data available } else if (nrow(thisAddyData()) == 0) { - return(list(src = "www/emptystar.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "X")) + return(0) } else if (thisAddyData()$delegation_score == 0) { - return(list(src = "www/emptystar.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "X")) + return(0) # score! } else { - return(list(src = "www/star.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "⭐️")) + return(1) } - }, deleteFile = FALSE) + }) - output$cexscore <- renderImage({ + output$cexscore <- renderText({ # no address available: if(substr(input$eth_address, 1, 2) != "0x") { - return(list(src = "www/emptystar.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "X")) + return(0) # does not score or no data available } else if (nrow(thisAddyData()) == 0) { - return(list(src = "www/emptystar.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "X")) + return(1) } else if (thisAddyData()$cex_score == 0) { - return(list(src = "www/emptystar.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "X")) + return(1) # score! } else { - return(list(src = "www/star.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "⭐️")) + return(1) } - }, deleteFile = FALSE) + }) - output$dexscore <- renderImage({ + output$dexscore <- renderText({ # no address available: if(substr(input$eth_address, 1, 2) != "0x") { - return(list(src = "www/emptystar.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "X")) + return(0) # does not score or no data available } else if (nrow(thisAddyData()) == 0) { - return(list(src = "www/emptystar.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "X")) + return(0) } else if (thisAddyData()$dex_score == 0) { - return(list(src = "www/emptystar.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "X")) + return(0) # score! } else { - return(list(src = "www/star.svg", contentType = 'image/svg+xml', height = 30, width = 30, alt = "⭐️")) + return(1) } - }, deleteFile = FALSE) - + }) output$totalscore <- renderText({ - - - if(substr(input$eth_address, 1, 2) != "0x") { - - "Connect to get your Optimist Score" - - } else if(nrow(thisAddyData()) == 0) { - - - "You don't qualify for an Optimist Score. Maybe buy an nft? or delegate some OP?" - + ifelse(substr(input$eth_address, 1, 2) == "0x", userScore(), 0) + }) + + + userScore <- reactive({ + if(nrow(thisAddyData()) > 0) { + thisAddyData()$total_score } else { - - paste("You're a ", paste(rep("⭐️", thisAddyData()$total_score), collapse = ""), " Optimist") - + 1 } - }) - observeEvent(input$eth_address, { - print(input$eth_address) - }) - - print(signerPrivateKey) - print(provider) - output$tx_handler <- renderUI({ TransactionHandler( "tx_button", chainId = 420, - label = "Make Attestation", + label = "Attest Your Score On Chain", contract_address = "0xD870A73a32d0b8C34CcF1E6098E9A26977CB605b", contract_abi = abi, contract_method = "attest", provider = provider, - signerPrivateKey = signerPrivateKey, - args = c(input$eth_address, "Flipside_user_scoring", thisAddyData()$total_score), + args = c(input$eth_address, "Flipside_user_scoring", userScore()), enabled = TRUE ) }) diff --git a/apps/optimism/optimistic_score_prototype/ui.R b/apps/optimism/optimistic_score_prototype/ui.R index 8249be8..7e0b22b 100644 --- a/apps/optimism/optimistic_score_prototype/ui.R +++ b/apps/optimism/optimistic_score_prototype/ui.R @@ -2,34 +2,77 @@ fluidPage( tags$head( tags$link(rel = "stylesheet", type = "text/css", href = "shiny.css"), tags$link(rel = "stylesheet", href = "https://fonts.googleapis.com/css?family=Roboto+Mono"), - tags$link(rel = "stylesheet", href = "https://fonts.googleapis.com/css?family=Inter") + tags$link(rel = "stylesheet", href = "https://fonts.googleapis.com/css?family=Open+Sans"), + tags$link(rel = "stylesheet", href = "https://fonts.googleapis.com/css?family=Rubik") ), + # background: linear-gradient(90deg, rgba(255,196,202,0.19511554621848737) 0%, rgba(255,255,255,0) 100%); color: #919EAB; + + fluidRow(class = "titlebar", + column(9, img(src = "app_logo.svg", height = "44px", style = "margin-left: 13px; margin-top: 13px; margin-bottom: 13px;")), + column(3, style = "text-align: right;", actionButton("connect", icon = icon("wallet"), label = " Connect Wallet")), + bsModal(id = "connectpop", title = "", trigger = "connect", + WalletHandler("eth_address", chainId = 420)) + ), fluidRow(class = "wrapper", - fluidRow(img(src = "app_logo.svg")), - fluidRow(class = "walletpart", - actionButton("connect", icon = icon("wallet"), label = "Connect Wallet"), - bsModal(id = "connectpop", title = "", trigger = "connect", - WalletHandler("eth_address", chainId = 420)), - textOutput("connectedaddress") + fluidRow(class = "scoreholder", + fluidRow(class = "description", div("Score up to 5 points by doing things that contribute to the Optimism Network. Then click 'Attest Your Score On Chain' to use the ", + a("AttestationStation", href = "https://community.optimism.io/docs/governance/attestation-station/", target = "_blank"), + " to get your score onchain.")), + br(), + fluidRow(class = "scorebox", + div(class = "alignholder", + div(class = "left", textOutput("airdropscore")), + div(class = "text", "Claimed the original OP airdrop") ) + ), + fluidRow(class = "scorebox", + div(class = "alignholder", + div(class = "left", textOutput("delegatescore")), + div(class = "text", "Delegated OP at least once") ) + ), + fluidRow(class = "scorebox", + div(class = "alignholder", + div(class = "left", textOutput("dexscore")), + div(class = "text", "Swapped at least once on a dex") ) + ), + fluidRow(class = "scorebox", + div(class = "alignholder", + div(class = "left", textOutput("nftscore")), + div(class = "text", "Bought or Sold at least 1 NFT") ) + ), + fluidRow(class = "scorebox", + div(class = "alignholder", + div(class = "left", textOutput("cexscore")), + div(class = "text", "Sent $0 to a cex or bought more than sold") ) + ), + + + br(), + fluidRow(div(class = "totalscorebox", + div(class = "scorecircle", textOutput("totalscore")), + div(class = "scorecolumns", uiOutput("tx_handler")) + ) + ) ), + br(), - - - - fluidRow("Earn 1 star for doing each thing on Optimism in the last 180 days:"), - - fluidRow(class = "scorebox", imageOutput("airdropscore"), "Claimed the original OP airdrop"), - fluidRow(class = "scorebox", imageOutput("nftscore"), "Bought or Sold at least 1 NFT"), - fluidRow(class = "scorebox", imageOutput("delegatescore"), "Delegated OP at least once"), - fluidRow(class = "scorebox", imageOutput("cexscore"), "Sent $0 to an exchange or bought more than you sold"), - fluidRow(class = "scorebox", imageOutput("dexscore"), "Swapped at least once on a dex"), - hr(), - fluidRow(class = "totalscore", textOutput("totalscore")), - uiOutput("tx_handler") - #fluidRow(class = "proveit", actionButton(inputId = "attest", label = "PROVE IT on chain!")) - + fluidRow(class = "bottom", + div(class = "LINKS", "FAQ:"), + div(class = "links", + a(href = "https://community.optimism.io/docs/governance/attestation-station/", + "What is Attestation Station?", target = "_blank")), + div(class = "links", + a(href = "https://github.com/FlipsideCrypto/user_metrics/tree/main/apps/optimism/optimistic_score_prototype", + "Can I have this code?", target = "_blank")), + div(class = "links", + a(href = "https://app.flipsidecrypto.com/dashboard/optimist-score-queries-data-Jp7kIN", + "Can I have this data?", target = "_blank")), + div(class = "links", + a(href = "https://flipsidecrypto.xyz/", "What is Flipside?", target = "_blank")) + ) + + ), ) \ No newline at end of file diff --git a/apps/optimism/optimistic_score_prototype/update_data.R b/apps/optimism/optimistic_score_prototype/update_data.R index e4c5ca7..bac2b2c 100644 --- a/apps/optimism/optimistic_score_prototype/update_data.R +++ b/apps/optimism/optimistic_score_prototype/update_data.R @@ -1,43 +1,36 @@ library(shroomDK) +library(data.table) -#source("~/data_science/util/util_functions.R") -# need to replace query snowflake with shrrom dk's +airdrop.claims <- auto_paginate_query(query = paste(readLines("~/user_metrics/sql/airdrops/optimism/airdrop_claims.sql"), collapse = "\n"), + api_key = readLines("api_key.txt")) +cex.activity <- auto_paginate_query(query = paste(readLines("~/user_metrics/sql/bags/optimism/cex_activity.sql"), collapse = "\n"), + api_key = readLines("api_key.txt")) -airdrop.claims <- QuerySnowflake(paste(readLines("~/user_metrics/sql/airdrops/optimism/airdrop_claims.sql"), collapse = "\n")) -cex.activity <- QuerySnowflake(paste(readLines("~/user_metrics/sql/bags/optimism/cex_activity.sql"), collapse = "\n")) -chain.stakes <- QuerySnowflake(paste(readLines("~/user_metrics/sql/governance/optimism/chain_stakes.sql"), collapse = "\n")) -nft.trades <- QuerySnowflake(paste(readLines("~/user_metrics/sql/nfts/optimism/nft_trades.sql"), collapse = "\n")) -dex.swaps <- QuerySnowflake(paste(readLines("~/user_metrics/sql/defi/optimism/dex_swaps.sql"), collapse = "\n")) +chain.stakes <- auto_paginate_query(query = paste(readLines("~/user_metrics/sql/governance/optimism/chain_stakes.sql"), collapse = "\n"), + api_key = readLines("api_key.txt")) -# dex.swaps <- auto_paginate_query(query = paste(readLines("~/user_metrics/sql/airdrops/optimism/airdrop_claims.sql"), collapse = "\n"), -# api_key = readLines("api_key.txt")) -# -# dex.swaps <- auto_paginate_query(query = paste(readLines("~/user_metrics/sql/airdrops/optimism/airdrop_claims.sql"), collapse = "\n"), -# api_key = readLines("api_key.txt")) -# -# dex.swaps <- auto_paginate_query(query = paste(readLines("~/user_metrics/sql/airdrops/optimism/airdrop_claims.sql"), collapse = "\n"), -# api_key = readLines("api_key.txt")) -# -# dex.swaps <- auto_paginate_query(query = paste(readLines("~/user_metrics/sql/airdrops/optimism/airdrop_claims.sql"), collapse = "\n"), -# api_key = readLines("api_key.txt")) -# -# dex.swaps <- data.table(auto_paginate_query(query = paste(readLines("~/user_metrics/sql/airdrops/optimism/airdrop_claims.sql"), collapse = "\n"), -# api_key = readLines("api_key.txt"))) +nft.trades <- auto_paginate_query(query = paste(readLines("~/user_metrics/sql/nfts/optimism/nft_trades.sql"), collapse = "\n"), + api_key = readLines("api_key.txt")) -op.metrics.w <- MergeDataFrames( - list(airdrop.claims[, list(user_address, airdrop_tokens_claimed = token_volume)], - cex.activity[, list(user_address, net_cex_wdraw = wdraw_usd_volume - dep_usd_volume)], - chain.stakes[, list(user_address, n_delegations = n_stakes)], - nft.trades[, list(n_trades = sum(n_buys + n_sells)), by = user_address], - dex.swaps), - by = "user_address", all = TRUE -) +dex.swaps <- data.table(auto_paginate_query(query = paste(readLines("~/user_metrics/sql/defi/optimism/dex_swaps.sql"), collapse = "\n"), + api_key = readLines("api_key.txt"))) +op.metrics.w <- merge(airdrop.claims[, list(user_address, airdrop_tokens_claimed = token_volume)], + cex.activity[, list(user_address, net_cex_wdraw = wdraw_usd_volume - dep_usd_volume)], + by = "user_address", all = TRUE) +op.metrics.w <- merge(op.metrics.w, + chain.stakes[, list(user_address, n_delegations = n_stakes)], + by = "user_address", all = TRUE) +op.metrics.w <- merge(op.metrics.w, + cnft.trades[, list(n_trades = sum(n_buys + n_sells)), by = user_address], + by = "user_address", all = TRUE) -ReplaceValues(op.metrics.w) +op.metrics.w <- merge(op.metrics.w, + dex.swaps, + by = "user_address", all = TRUE) op.metrics.w[, airdrop_score := ifelse(airdrop_tokens_claimed > 0, 1, 0)] op.metrics.w[, cex_score := ifelse(net_cex_wdraw >= 0, 1, 0)] @@ -47,10 +40,8 @@ op.metrics.w[, dex_score := ifelse(n_swaps > 0, 1, 0)] op.metrics.w[, total_score := airdrop_score + cex_score + delegation_score + nft_score + dex_score] -# op.metrics.w[, .N, by = total_score][order(total_score)] op.metrics.w <- op.metrics.w[total_score > 0] save(op.metrics.w, file = "data.RData") -#op.metrics.w[user_address == tolower("0xf76e2d2bba0292cf88f71934aff52ea54baa64d9")] diff --git a/apps/optimism/optimistic_score_prototype/www/app_logo.svg b/apps/optimism/optimistic_score_prototype/www/app_logo.svg index 977e21e..102ef38 100644 --- a/apps/optimism/optimistic_score_prototype/www/app_logo.svg +++ b/apps/optimism/optimistic_score_prototype/www/app_logo.svg @@ -1,155 +1,131 @@ + viewBox="0 0 606.8 41" style="enable-background:new 0 0 606.8 41;" xml:space="preserve"> - - + + + s-0.2-0.5-0.2-0.8l1-4.9c0.1-0.3,0.2-0.6,0.5-0.8c0.3-0.2,0.6-0.3,0.9-0.3h24.5c0.3,0,0.6,0.1,0.8,0.3c0.2,0.2,0.2,0.5,0.2,0.8 + l-1,4.9c-0.1,0.3-0.2,0.6-0.5,0.8s-0.6,0.3-0.9,0.3H90l-5,23.5c-0.1,0.3-0.2,0.6-0.5,0.8s-0.6,0.3-0.9,0.3H77.7z"/> + c0.3-0.2,0.6-0.3,0.9-0.3h6.1c0.3,0,0.5,0.1,0.7,0.3C114,4.7,114,5,114,5.3l-6.3,29.5c-0.1,0.3-0.2,0.6-0.5,0.8s-0.5,0.3-0.8,0.3 + H100.3z"/> - - + + + + - - - - - - - + + + - + c-0.2-0.3-0.3-0.6-0.4-0.8l-3-10h-4.7l-2.1,10.1c-0.1,0.3-0.2,0.6-0.5,0.8s-0.6,0.3-0.9,0.3h-6V35.9z M353.7,18.3h5.3 + c1.4,0,2.6-0.3,3.4-1s1.4-1.7,1.7-2.9c0.2-1.2,0.1-2.1-0.4-2.9c-0.5-0.7-1.5-1.1-3-1.1h-5.2L353.7,18.3z"/> + - - + - - - - + + + + - + - - + + - + c-0.2,0.7-0.2,1.4-0.2,2.2v0.3C573.8,26.4,573.9,27.1,574.1,27.8z"/> + diff --git a/apps/optimism/optimistic_score_prototype/www/button_empty.svg b/apps/optimism/optimistic_score_prototype/www/button_empty.svg new file mode 100644 index 0000000..fcdc109 --- /dev/null +++ b/apps/optimism/optimistic_score_prototype/www/button_empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/optimism/optimistic_score_prototype/www/button_filled.svg b/apps/optimism/optimistic_score_prototype/www/button_filled.svg new file mode 100644 index 0000000..25b589a --- /dev/null +++ b/apps/optimism/optimistic_score_prototype/www/button_filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/optimism/optimistic_score_prototype/www/shiny.css b/apps/optimism/optimistic_score_prototype/www/shiny.css index 3075c6d..3bf59f2 100644 --- a/apps/optimism/optimistic_score_prototype/www/shiny.css +++ b/apps/optimism/optimistic_score_prototype/www/shiny.css @@ -1,53 +1,202 @@ body { -background: white; -background: linear-gradient(90deg, rgba(255,196,202,0.19511554621848737) 0%, rgba(255,255,255,0) 100%); color: #919EAB; - font-family: 'Inter'; - font-size: 18px; + background: white; + font-family: 'Open Sans', sans-serif; + font-size: 16px; font-weight: 400; + color: black; +} + +a, a:hover, a:visited, a:active { + color: red; } .container-fluid { background-color: transparent; - max-width: 700px; margin: 0 auto; height: 100%; - padding-top: 5%; padding-left: 0; padding-right: 0; } +.titlebar { + height: 70px; + box-shadow: rgb(20 23 26 / 6%) 0px 6px 8px -6px, rgb(20 23 26 / 4%) 0px 8px 16px -6px; + margin-bottom: 25px; + padding-left: 2%; +} + .wrapper { - border-radius: 16px; - border: 1px solid #FF0420; background-color: white; - margin-top: -5%; - padding-top: 25px; - padding-bottom: 3%; - padding-right: 5%; - padding-left: 5%; - margin-bottom: 30px; +} + +#connect { + font-family: Rubik; + color: #F60001; + font-size: 1.02em; + padding: 8px; + background: linear-gradient(white, white) padding-box, + linear-gradient(to right, #F60001, #F60001) border-box; + border-radius: 4px; + border: 3px solid transparent; } .walletpart { padding-top: 2%; padding-bottom: 2%; + text-align: right; +} + +.description { + font-size: 0.85em; + color: #353535; +} + +.alignholder { + display: flex; + width: 85%; + margin-left: 10%; + margin-right: 10%; +} + +.left { + height: 50px; + line-height: 50px; + width: 20%; + background-color: white; + border: 2px red solid; + border-radius: 4px 0 0 4px; + color: red; + text-align: center; + font-family: Rubik; + font-weight: 600; + font-size: 1.2em; } +.text { + height: 50px; + line-height: 50px; + width: 80%; + background-color: red; + border-radius: 0 4px 4px 0; + border: 2px red solid; + color: white; + text-align: left; + padding-left: 10px; +} + + +.scoreholder { + border: 2px solid red; + border-radius: 17px; + padding: 10px 30px 0 37px; + max-width: 630px; + margin: auto; +} + +.bottom { + padding: 10px 30px 0 37px; + max-width: 634px; + margin: auto; + text-align: center; +} + +.links > a { + text-decoration: underline; +} + + .scorebox { display: flex; - border: 1px solid black; - border-radius: 5px; padding: 10px; - margin-left: 10%; - margin-right: 10%; - margin-bottom: 20px; +} + +.totalscorebox { + display: flex; + width: 86%; + margin-left: 12%; + margin-right: 12%; + margin-bottom: 38px; +} + +.scorecircle { + background-color: red; + color: white; + font-family: Rubik; + font-size: 4em; + text-align: center; + border-radius: 50px; + height: 100px; + width: 100px; + line-height: 100px; } .shiny-image-output { - height: 30px !important; - width: 30px !important; + height: 25px !important; + width: 25px !important; margin-right: 10px; } +button { + border: 1px red solid; + padding: 5px; + color: red; + background-color: white; + margin: 10px; + border-radius: 4px; + min-width: 250px; +} + + +#tx_button > button { + font-family: Rubik; + color: white; + background-color: #F60001; + font-size: 1.3em; + padding: 10px; + border-radius: 4px; + border: 3px solid transparent; +} + +.scorecolumns { + padding-left: 30px; + padding-right: 30px; + margin-top: 10px; +} + + +.txbuttondiv { + padding-top: 7%; +} + +.modal-dialog { + margin-left: 30%; + margin-right: 30%; +} + +.modal-content { + background-color: white; + border: 1px solid #F60001; +} +.modal-title { + color: red; + font-weight: bold; + border: none; +} +.modal-body { + border: none; + text-align: center; +} +.modal-footer { + border: none; +} + + + +.links { + color: red; + font-size: 0.95em; + text-align: center; +} +