Code Sketch


"sikho world" bhimraj bansode
By: Mhalsakant School
Category: Art
// ============================================================================
// GLOBE-X ? Interactive Global Network & Economic Intelligence
// Environment: Kojo 2.9.x (Scala & CanvasDraw API)
// ============================================================================

import java.awt.Color

cleari()
originBottomLeft()

// ----------------------------------------------------------------------------
// DATA MODELS
// ----------------------------------------------------------------------------

case class Country(
  name: String,
  code: String,
  continent: String,
  lat: Double,
  lon: Double,
  pop: Double,
  gdp: Double,
  gdpGrowth: Double,
  gdpRank: Int,
  humidity: Double,
  status: String,
  connections: List[String]
)

case class NetworkRoute(
  fromCode: String,
  toCode: String,
  routeType: String,
  volume: Double
)

case class DataParticle(
  fromCode: String,
  toCode: String,
  var progress: Double,
  speed: Double,
  routeType: String
)

case class ContinentPoly(
  name: String,
  coords: List[(Double, Double)]
)

// ----------------------------------------------------------------------------
// CONTINENT OUTLINES
// ----------------------------------------------------------------------------

val continents = List(
  ContinentPoly("North America", List(
    (-168.0,65.0),(-160.0,71.0),(-140.0,70.0),(-120.0,75.0),
    (-90.0,73.0),(-80.0,62.0),(-65.0,58.0),(-55.0,52.0),
    (-60.0,44.0),(-70.0,42.0),(-75.0,35.0),(-80.0,25.0),
    (-88.0,16.0),(-84.0,10.0),(-95.0,16.0),(-105.0,20.0),
    (-115.0,30.0),(-124.0,38.0),(-130.0,54.0),(-160.0,58.0),
    (-168.0,65.0)
  )),
  ContinentPoly("South America", List(
    (-77.0,8.0),(-72.0,12.0),(-62.0,10.0),(-50.0,4.0),
    (-35.0,-5.0),(-35.0,-12.0),(-40.0,-22.0),(-48.0,-28.0),
    (-53.0,-34.0),(-65.0,-45.0),(-67.0,-55.0),(-75.0,-45.0),
    (-72.0,-35.0),(-70.0,-20.0),(-81.0,-5.0),(-77.0,8.0)
  )),
  ContinentPoly("Europe", List(
    (-10.0,36.0),(-9.0,43.0),(-4.0,48.0),(2.0,51.0),
    (8.0,54.0),(10.0,58.0),(5.0,62.0),(14.0,68.0),
    (28.0,71.0),(40.0,68.0),(50.0,65.0),(60.0,60.0),
    (40.0,45.0),(30.0,46.0),(20.0,37.0),(12.0,44.0),
    (4.0,43.0),(-2.0,37.0),(-10.0,36.0)
  )),
  ContinentPoly("Africa", List(
    (-17.0,15.0),(-17.0,21.0),(-6.0,36.0),(10.0,37.0),
    (25.0,32.0),(36.0,28.0),(43.0,12.0),(51.0,12.0),
    (45.0,2.0),(40.0,-5.0),(35.0,-15.0),(28.0,-34.0),
    (18.0,-34.0),(12.0,-20.0),(9.0,-5.0),(5.0,4.0),
    (-4.0,5.0),(-17.0,15.0)
  )),
  ContinentPoly("Asia", List(
    (60.0,60.0),(70.0,72.0),(100.0,76.0),(135.0,74.0),
    (170.0,66.0),(160.0,52.0),(140.0,50.0),(130.0,40.0),
    (122.0,30.0),(108.0,12.0),(104.0,1.0),(100.0,8.0),
    (90.0,22.0),(78.0,8.0),(68.0,24.0),(54.0,16.0),
    (36.0,28.0),(36.0,36.0),(50.0,40.0),(60.0,60.0)
  )),
  ContinentPoly("Australia", List(
    (114.0,-22.0),(115.0,-34.0),(122.0,-35.0),(135.0,-35.0),
    (140.0,-38.0),(150.0,-37.0),(153.0,-28.0),(145.0,-15.0),
    (136.0,-12.0),(124.0,-16.0),(114.0,-22.0)
  )),
  ContinentPoly("Antarctica", List(
    (-180.0,-75.0),(-120.0,-72.0),(-60.0,-65.0),(0.0,-70.0),
    (60.0,-67.0),(120.0,-66.0),(180.0,-75.0),(180.0,-88.0),
    (-180.0,-88.0),(-180.0,-75.0)
  ))
)

// ----------------------------------------------------------------------------
// COUNTRY DATA
// ----------------------------------------------------------------------------

val countries = List(
  Country("United States","USA","North America",37.09,-95.71,339.9,27360.0,2.5,1,55.0,
    "Global Tech Superhub",List("CHN","JPN","DEU","GBR","IND","CAN","MEX")),
  Country("China","CHN","Asia",35.86,104.19,1409.7,17790.0,5.2,2,62.0,
    "Manufacturing Powerhouse",List("USA","JPN","IND","DEU","RUS","SAU","AUS")),
  Country("Germany","DEU","Europe",51.16,10.45,84.4,4456.0,0.2,3,74.0,
    "Industrial Core & Engineering",List("FRA","GBR","USA","CHN","ITA","NLD")),
  Country("Japan","JPN","Asia",36.20,138.25,124.5,4212.0,1.3,4,68.0,
    "Advanced Robotics & Tech",List("USA","CHN","KOR","AUS","IND")),
  Country("India","IND","Asia",20.59,78.96,1428.6,3730.0,7.6,5,65.0,
    "Fastest Major Growth Engine",List("USA","CHN","JPN","SAU","ARE","SGP","GBR")),
  Country("United Kingdom","GBR","Europe",55.37,-3.43,67.7,3332.0,0.5,6,80.0,
    "Fintech & Banking Hub",List("USA","DEU","FRA","IND","SGP")),
  Country("France","FRA","Europe",46.22,2.21,68.0,3052.0,0.9,7,72.0,
    "Aerospace & Nuclear Energy",List("DEU","GBR","ITA","ESP","USA")),
  Country("Italy","ITA","Europe",41.87,12.56,58.8,2255.0,0.7,8,67.0,
    "Design & Precision Machinery",List("FRA","DEU","ESP","GBR")),
  Country("Brazil","BRA","South America",-14.23,-51.92,215.3,2174.0,2.9,9,78.0,
    "Agribusiness & Mineral Giant",List("USA","CHN","ARG","DEU")),
  Country("Canada","CAN","North America",56.13,-106.34,40.5,2140.0,1.2,10,60.0,
    "Critical Minerals & AI",List("USA","GBR","JPN","MEX")),
  Country("Russia","RUS","Europe",61.52,105.31,144.2,2000.0,3.2,11,64.0,
    "Energy & Raw Materials",List("CHN","IND","TUR","SAU")),
  Country("Mexico","MEX","North America",23.63,-102.55,128.5,1790.0,3.1,12,58.0,
    "Nearshoring Assembly Nexus",List("USA","CAN","BRA","ESP")),
  Country("South Korea","KOR","Asia",35.90,127.76,51.7,1713.0,1.4,13,66.0,
    "Semiconductor Fabrication",List("USA","CHN","JPN","VNM")),
  Country("Australia","AUS","Oceania",-25.27,133.77,26.6,1688.0,1.8,14,52.0,
    "Indo-Pacific Mineral Power",List("CHN","JPN","USA","IND","NZL")),
  Country("Spain","ESP","Europe",40.46,-3.74,48.4,1581.0,2.5,15,59.0,
    "Renewable Energy & Gateway",List("FRA","ITA","DEU","MEX")),
  Country("Indonesia","IDN","Asia",-0.78,113.92,277.5,1371.0,5.0,16,82.0,
    "Nickel Refining & ASEAN Core",List("CHN","SGP","MYS","AUS")),
  Country("Turkey","TUR","Asia",38.96,35.24,85.3,1154.0,4.5,17,61.0,
    "Eurasian Crossroads",List("DEU","RUS","SAU","EGY")),
  Country("Netherlands","NLD","Europe",52.13,5.29,17.9,1093.0,0.6,18,81.0,
    "Lithography & Rotterdam Port",List("DEU","GBR","FRA","USA")),
  Country("Saudi Arabia","SAU","Asia",23.88,45.07,36.4,1069.0,1.7,19,32.0,
    "Global Hydrocarbon Leader",List("CHN","IND","USA","ARE","EGY")),
  Country("Switzerland","CHE","Europe",46.81,8.22,8.9,905.0,1.1,20,71.0,
    "Biotech & Wealth Standard",List("DEU","FRA","ITA","USA")),
  Country("Argentina","ARG","South America",-38.41,-63.61,46.0,622.0,-1.6,24,62.0,
    "Lithium Basin & Agriculture",List("BRA","USA","ESP")),
  Country("Sweden","SWE","Europe",60.12,18.64,10.5,593.0,0.4,26,75.0,
    "Clean-Tech Innovation",List("DEU","NOR","GBR")),
  Country("Thailand","THA","Asia",15.87,100.99,71.8,512.0,2.7,29,75.0,
    "Automotive & Logistics",List("JPN","CHN","USA","MYS")),
  Country("Israel","ISR","Asia",31.04,34.85,9.8,509.0,2.0,28,57.0,
    "Cybersecurity & Deep Tech",List("USA","GBR","DEU","ARE")),
  Country("UAE","ARE","Asia",23.42,53.84,9.5,504.0,3.9,30,50.0,
    "Aviation, Logistics & AI",List("IND","SAU","GBR","USA","SGP")),
  Country("Singapore","SGP","Asia",1.35,103.81,5.9,501.0,2.8,31,84.0,
    "Global Maritime Chokepoint",List("CHN","USA","IND","JPN","MYS")),
  Country("Norway","NOR","Europe",60.47,8.46,5.5,485.0,1.0,32,78.0,
    "Sovereign Wealth & Hydro",List("SWE","GBR","DEU")),
  Country("Bangladesh","BGD","Asia",23.68,90.35,173.0,460.0,5.8,33,77.0,
    "Apparel Export & Delta Hub",List("IND","CHN","USA")),
  Country("Vietnam","VNM","Asia",14.05,108.27,98.9,430.0,5.0,34,80.0,
    "Electronics Supply Nexus",List("CHN","USA","KOR","JPN")),
  Country("Malaysia","MYS","Asia",4.21,101.97,34.3,415.0,4.2,35,83.0,
    "Semiconductor Testing Hub",List("SGP","CHN","USA","JPN")),
  Country("Egypt","EGY","Africa",26.82,30.80,112.7,398.0,3.8,36,48.0,
    "Suez Maritime Lifeline",List("SAU","ARE","ITA","TUR")),
  Country("Nigeria","NGA","Africa",9.08,8.67,223.8,395.0,3.0,37,79.0,
    "African Demography & Energy",List("ZAF","CHN","IND","USA")),
  Country("South Africa","ZAF","Africa",-30.55,22.93,60.4,380.0,0.6,38,58.0,
    "Platinum Metals & Finance",List("BRA","GBR","CHN","IND")),
  Country("Iran","IRN","Asia",32.42,53.68,88.5,366.0,3.5,40,40.0,
    "Petrochemical Resources",List("CHN","RUS","TUR","IND")),
  Country("Pakistan","PAK","Asia",30.37,69.34,240.5,340.0,2.1,42,56.0,
    "CPEC Corridor & Transit",List("CHN","SAU","ARE","TUR")),
  Country("New Zealand","NZL","Oceania",-40.90,174.88,5.2,253.0,1.1,50,76.0,
    "Agri-Tech & Pacific Clean",List("AUS","CHN","USA","JPN"))
)

val countryMap = countries.map(c => c.code -> c).toMap

// ----------------------------------------------------------------------------
// NETWORK ROUTES
// ----------------------------------------------------------------------------

val routes = List(
  NetworkRoute("IND","USA","data",9.0),
  NetworkRoute("IND","ARE","finance",8.0),
  NetworkRoute("IND","SAU","energy",8.0),
  NetworkRoute("IND","CHN","trade",7.0),
  NetworkRoute("IND","JPN","trade",7.0),
  NetworkRoute("IND","SGP","finance",7.0),
  NetworkRoute("CHN","USA","trade",9.0),
  NetworkRoute("CHN","JPN","trade",8.0),
  NetworkRoute("CHN","SAU","energy",8.0),
  NetworkRoute("CHN","RUS","energy",7.0),
  NetworkRoute("JPN","USA","data",8.0),
  NetworkRoute("KOR","USA","trade",7.0),
  NetworkRoute("DEU","FRA","trade",8.0),
  NetworkRoute("DEU","GBR","finance",7.0),
  NetworkRoute("USA","CAN","trade",9.0),
  NetworkRoute("USA","MEX","trade",9.0),
  NetworkRoute("USA","GBR","finance",9.0),
  NetworkRoute("BRA","ARG","trade",7.0),
  NetworkRoute("BRA","USA","trade",7.0),
  NetworkRoute("AUS","CHN","energy",8.0),
  NetworkRoute("SGP","IND","finance",7.0),
  NetworkRoute("SAU","IND","energy",8.0),
  NetworkRoute("SAU","CHN","energy",8.0),
  NetworkRoute("ZAF","GBR","finance",6.0),
  NetworkRoute("NGA","USA","energy",6.0),
  NetworkRoute("EGY","ITA","trade",6.0)
)

var particles = routes.zipWithIndex.map {
  case (r,i) =>
    DataParticle(
      r.fromCode,
      r.toCode,
      (i * 0.13) % 1.0,
      0.004 + (i % 4) * 0.0015,
      r.routeType
    )
}

// ----------------------------------------------------------------------------
// STATE
// ----------------------------------------------------------------------------

var animationTime = 0.0
var selectedCode: Option[String] = Some("IND")
var hoveredCode: Option[String] = None

var showGrid = true
var showNetwork = true
var showDashboard = true
var showContinents = true
var isPaused = false

val mapX = 15.0
val mapY = 55.0
val mapW = 680.0
val mapH = 480.0

val dashX = 705.0
val dashY = 15.0
val dashW = 280.0
val dashH = 570.0

// ----------------------------------------------------------------------------
// HELPERS
// ----------------------------------------------------------------------------

def geoToScreen(lat: Double, lon: Double): (Double,Double) = {
  val sx = mapX + ((lon + 180.0) / 360.0) * mapW
  val sy = mapY + ((90.0 - lat) / 180.0) * mapH
  (sx,sy)
}

def distance(ax: Double, ay: Double, bx: Double, by: Double): Double = {
  Math.hypot(ax - bx, ay - by)
}

def updateParticles(): Unit = {
  if (!isPaused) {
    for (p <- particles) {
      p.progress += p.speed
      if (p.progress >= 1.0) p.progress = 0.0
    }
  }
}

def updateState(): Unit = {
  if (!isPaused) animationTime += 0.016
  updateParticles()
}

// ----------------------------------------------------------------------------
// MOUSE INTERACTION ROUTINES
// ----------------------------------------------------------------------------

def updateHover(mx: Double, my: Double): Unit = {
  var found: Option[String] = None
  for (c <- countries) {
    val (sx, sy) = geoToScreen(c.lat, c.lon)
    if (distance(mx, my, sx, sy) < 18.0) {
      found = Some(c.code)
    }
  }
  hoveredCode = found
}

def handleMouseClick(mx: Double, my: Double): Unit = {
  var clickedCountry: Option[String] = None
  for (c <- countries) {
    val (sx, sy) = geoToScreen(c.lat, c.lon)
    if (distance(mx, my, sx, sy) < 18.0) {
      clickedCountry = Some(c.code)
    }
  }
  
  if (clickedCountry.isDefined) {
    selectedCode = clickedCountry
  } else if (mx >= mapX && mx <= mapX + mapW && my >= mapY && my <= mapY + mapH) {
    selectedCode = None
  }
}

// ----------------------------------------------------------------------------
// DRAW BACKGROUND
// ----------------------------------------------------------------------------

def drawBackground(surface: CanvasDraw): Unit = {
  import surface._

  background(6,12,28)

  fill(10,22,44)
  stroke(24,48,80)
  strokeWeight(2)
  rect(mapX,mapY,mapW,mapH)

  fill(8,18,38)
  noStroke()
  rect(dashX,dashY,dashW,dashH)
}

// ----------------------------------------------------------------------------
// DRAW GRID
// ----------------------------------------------------------------------------

def drawGrid(surface: CanvasDraw): Unit = {
  import surface._

  if (!showGrid) return

  stroke(30,60,90,80)
  strokeWeight(1)

  var lon = -150.0
  while (lon <= 180.0) {
    val (x1,y1) = geoToScreen(85.0,lon)
    val (x2,y2) = geoToScreen(-85.0,lon)
    line(x1,y1,x2,y2)
    lon += 30.0
  }

  val lats = List(66.5,23.5,0.0,-23.5,-66.5)

  for (lat <- lats) {
    val (x1,y1) = geoToScreen(lat,-180.0)
    val (x2,y2) = geoToScreen(lat,180.0)

    if (lat == 0.0) {
      stroke(0,200,255,120)
      strokeWeight(2)
    } else {
      stroke(30,60,90,80)
      strokeWeight(1)
    }

    line(x1,y1,x2,y2)
  }

  fill(80,130,180)
  text("0 DEG EQUATOR",mapX + 8,mapY + mapH/2 - 3)
  text("PRIME MERIDIAN",mapX + mapW/2 + 4,mapY + 14)
}

// ----------------------------------------------------------------------------
// DRAW CONTINENTS
// ----------------------------------------------------------------------------

def drawContinents(surface: CanvasDraw): Unit = {
  import surface._

  if (!showContinents) return

  for (continent <- continents) {
    val pts = continent.coords.map {
      case (lon,lat) => geoToScreen(lat,lon)
    }

    stroke(20,110,140,160)
    strokeWeight(1)

    fill(15,38,62,170)

    var i = 0
    while (i < pts.length - 1) {
      val (x1,y1) = pts(i)
      val (x2,y2) = pts(i+1)
      line(x1,y1,x2,y2)
      i += 1
    }

    val center = pts(pts.length/2)

    fill(60,130,160)
    text(continent.name.toUpperCase,center._1 - 22,center._2)
  }
}

// ----------------------------------------------------------------------------
// DRAW NETWORK
// ----------------------------------------------------------------------------

def drawConnections(surface: CanvasDraw): Unit = {
  import surface._

  if (!showNetwork) return

  for (route <- routes) {
    (countryMap.get(route.fromCode),countryMap.get(route.toCode)) match {

      case (Some(from),Some(to)) =>

        val (x1,y1) = geoToScreen(from.lat,from.lon)
        val (x2,y2) = geoToScreen(to.lat,to.lon)

        val selected =
          selectedCode.contains(from.code) ||
          selectedCode.contains(to.code)

        if (selected) {
          stroke(0,255,220,220)
          strokeWeight(2)
        } else {
          stroke(20,80,110,80)
          strokeWeight(1)
        }

        val midX = (x1+x2)/2.0
        val midY = (y1+y2)/2.0 - 25.0

        line(x1,y1,midX,midY)
        line(midX,midY,x2,y2)

      case _ =>
    }
  }
}

// ----------------------------------------------------------------------------
// DRAW PARTICLES
// ----------------------------------------------------------------------------

def drawParticles(surface: CanvasDraw): Unit = {
  import surface._

  if (!showNetwork || isPaused) return

  for (p <- particles) {

    (countryMap.get(p.fromCode),countryMap.get(p.toCode)) match {

      case (Some(from),Some(to)) =>

        val (x1,y1) = geoToScreen(from.lat,from.lon)
        val (x2,y2) = geoToScreen(to.lat,to.lon)

        val midX = (x1+x2)/2.0
        val midY = (y1+y2)/2.0 - 25.0

        val t = p.progress

        val px =
          (1-t)*(1-t)*x1 +
          2*(1-t)*t*midX +
          t*t*x2

        val py =
          (1-t)*(1-t)*y1 +
          2*(1-t)*t*midY +
          t*t*y2

        val active =
          selectedCode.contains(from.code) ||
          selectedCode.contains(to.code)

        if (active) {
          fill(255,255,255)
          stroke(0,255,220)
          strokeWeight(1)
          ellipse(px-3,py-3,6,6)
        } else {
          fill(0,220,255,180)
          noStroke()
          ellipse(px-2,py-2,4,4)
        }

      case _ =>
    }
  }
}

// ----------------------------------------------------------------------------
// COUNTRY MARKERS
// ----------------------------------------------------------------------------

def drawCountryMarkers(surface: CanvasDraw): Unit = {
  import surface._

  val pulse = (Math.sin(animationTime*4.0)+1.0)/2.0

  for (c <- countries) {

    val (x,y) = geoToScreen(c.lat,c.lon)

    val selected = selectedCode.contains(c.code)
    val hovered = hoveredCode.contains(c.code)

    val baseRadius =
      3.5 + Math.min(14.0,Math.sqrt(c.pop)*0.45)

    val ringColor =
      if (c.gdpGrowth >= 3.0) (0,240,120,210)
      else if (c.gdpGrowth >= 0.0) (240,200,40,210)
      else (255,60,60,210)

    if (selected) {

      stroke(0,255,240,220)
      strokeWeight(2)
      noFill()

      val expand = pulse*8.0

      ellipse(
        x-baseRadius-expand-4,
        y-baseRadius-expand-4,
        (baseRadius+expand+4)*2,
        (baseRadius+expand+4)*2
      )

      stroke(0,255,240,150)
      strokeWeight(1)

      line(x-18,y,x+18,y)
      line(x,y-18,x,y+18)
    }

    stroke(ringColor._1,ringColor._2,ringColor._3,ringColor._4)
    strokeWeight(2)
    noFill()

    ellipse(
      x-baseRadius-2,
      y-baseRadius-2,
      (baseRadius+2)*2,
      (baseRadius+2)*2
    )

    val intensity =
      Math.min(255,(80+(c.gdp/27360.0)*175).toInt)

    if (selected || hovered)
      fill(255,255,255)
    else
      fill(0,intensity,255)

    noStroke()

    ellipse(
      x-baseRadius,
      y-baseRadius,
      baseRadius*2,
      baseRadius*2
    )

    val angle = animationTime + c.gdpRank

    val hx = x + Math.cos(angle)*(baseRadius+5)
    val hy = y + Math.sin(angle)*(baseRadius+5)

    fill(50,180,255,200)
    ellipse(hx-1.5,hy-1.5,3,3)

    if (selected || hovered || c.gdpRank <= 10) {
      fill(0,255,240)
      text(c.code,x+baseRadius+4,y+3)
    }
  }
}

// ----------------------------------------------------------------------------
// HEADER
// ----------------------------------------------------------------------------

def drawHeader(surface: CanvasDraw): Unit = {
  import surface._

  fill(12,26,52)
  stroke(0,160,220,130)
  strokeWeight(1)

  rect(mapX,10,mapW,38)

  fill(0,255,220)
  text(
    "GLOBE-X // GLOBAL NETWORK & ECONOMIC INTELLIGENCE",
    mapX+12,34
  )

  if (isPaused) fill(255,180,0)
  else fill(0,255,120)

  ellipse(mapX+mapW-130,24,8,8)

  fill(180,210,240)

  if (isPaused)
    text("SIM PAUSED",mapX+mapW-116,32)
  else
    text("LIVE NETWORK",mapX+mapW-116,32)
}

// ----------------------------------------------------------------------------
// DASHBOARD FRAME & PROFILE
// ----------------------------------------------------------------------------

def drawDashboard(surface: CanvasDraw): Unit = {
  import surface._

  if (!showDashboard) return

  fill(8,18,38)
  stroke(0,160,220,160)
  strokeWeight(2)

  rect(dashX,dashY,dashW,dashH)

  fill(14,32,64)
  noStroke()

  rect(dashX+1,dashY+1,dashW-2,38)

  fill(0,240,255)
  text("INTELLIGENCE HUD",dashX+14,dashY+25)

  selectedCode match {
    case Some(code) =>
      countryMap.get(code) match {
        case Some(c) => drawCountryProfile(surface,c)
        case None => drawGlobalStatistics(surface)
      }

    case None =>
      drawGlobalStatistics(surface)
  }
}

def drawCountryProfile(surface: CanvasDraw, c: Country): Unit = {
  import surface._

  var cy = dashY+58.0

  fill(255,255,255)
  text(c.name.toUpperCase,dashX+14,cy)

  cy += 16

  fill(0,200,240)
  text(
    c.continent+" // RANK #"+c.gdpRank+" IN GDP",
    dashX+14,cy
  )

  cy += 10

  stroke(0,120,180,100)
  strokeWeight(1)

  line(
    dashX+14,cy,
    dashX+dashW-14,cy
  )

  cy += 24

  fill(150,180,210)
  text("NOMINAL GDP",dashX+14,cy)

  fill(255,255,255)

  val gdpString = "$"+c.gdp.toInt+" BILLION"
  text(gdpString,dashX+14,cy+18)

  val gdpBarW =
    Math.min(
      dashW-28,
      (c.gdp/27360.0)*(dashW-28)
    )

  fill(20,50,80)
  rect(dashX+14,cy+24,dashW-28,6)

  fill(0,220,255)
  rect(dashX+14,cy+24,gdpBarW,6)

  cy += 48

  fill(150,180,210)

  text("GDP GROWTH",dashX+14,cy)
  text("POPULATION",dashX+150,cy)

  if (c.gdpGrowth >= 0)
    fill(0,240,120)
  else
    fill(255,80,80)

  val sign =
    if (c.gdpGrowth > 0) "+" else ""

  text(
    sign+c.gdpGrowth+"%",
    dashX+14,cy+18
  )

  fill(255,255,255)

  text(
    c.pop+" M",
    dashX+150,cy+18
  )

  cy += 42

  fill(150,180,210)

  text("HUMIDITY INDEX",dashX+14,cy)
  text("COORDINATES",dashX+150,cy)

  fill(60,200,255)

  text(
    c.humidity.toInt+"% RELATIVE",
    dashX+14,cy+18
  )

  fill(200,220,240)

  text(
    c.lat+" deg, "+c.lon+" deg",
    dashX+150,cy+18
  )

  cy += 38

  fill(12,36,68)

  stroke(0,180,220,120)
  strokeWeight(1)

  rect(dashX+14,cy,dashW-28,44)

  fill(0,240,220)
  text("STRATEGIC PROFILE:",dashX+22,cy+16)

  fill(230,240,250)
  text(c.status,dashX+22,cy+32)

  cy += 60

  fill(150,180,210)

  text(
    "ACTIVE CORRIDORS ("+c.connections.length+")",
    dashX+14,cy
  )

  cy += 14

  var tagX = dashX+14.0
  var tagY = cy

  for (connCode <- c.connections) {

    if (tagX+44 > dashX+dashW-14) {
      tagX = dashX+14
      tagY += 24
    }

    fill(18,48,80)

    stroke(0,160,220,150)
    strokeWeight(1)

    rect(tagX,tagY,40,18)

    fill(0,255,240)

    text(connCode,tagX+8,tagY+13)

    tagX += 46
  }

  cy = dashY+dashH-42

  stroke(0,100,160,80)
  strokeWeight(1)

  line(
    dashX+14,cy,
    dashX+dashW-14,cy
  )

  fill(120,160,200)

  text(
    "[ESC] GLOBAL | [1-9] COUNTRY",
    dashX+14,cy+18
  )

  text(
    "[R] RESET | [G] GRID | [N] NET",
    dashX+14,cy+32
  )
}

// ----------------------------------------------------------------------------
// GLOBAL STATISTICS
// ----------------------------------------------------------------------------

def drawGlobalStatistics(surface: CanvasDraw): Unit = {
  import surface._

  var cy = dashY+58

  val totalGdp = countries.map(_.gdp).sum
  val totalPop = countries.map(_.pop).sum

  val topGdp = countries.minBy(_.gdpRank)
  val topPop = countries.maxBy(_.pop)
  val topGrowth = countries.maxBy(_.gdpGrowth)

  fill(255,255,255)
  text("GLOBAL OVERVIEW",dashX+14,cy)

  cy += 16

  fill(0,200,240)

  text(
    "AGGREGATE INTELLIGENCE & METRICS",
    dashX+14,cy
  )

  cy += 10

  stroke(0,120,180,100)
  strokeWeight(1)

  line(
    dashX+14,cy,
    dashX+dashW-14,cy
  )

  cy += 24

  fill(150,180,210)

  text(
    "TOTAL TRACKED GDP",
    dashX+14,cy
  )

  fill(255,255,255)

  text(
    "$"+totalGdp.toInt+" BILLION",
    dashX+14,cy+18
  )

  cy += 38

  fill(150,180,210)

  text(
    "TOTAL POPULATION",
    dashX+14,cy
  )

  fill(255,255,255)

  text(
    (totalPop/1000.0)+" BILLION",
    dashX+14,cy+18
  )

  cy += 38

  fill(150,180,210)

  text(
    "LEADERS & BENCHMARKS",
    dashX+14,cy
  )

  cy += 18

  fill(0,220,255)

  text(
    "#1 Economy: "+topGdp.name+" ($"+topGdp.gdp.toInt+"B)",
    dashX+14,cy
  )

  cy += 16

  text(
    "Largest Pop: "+topPop.name+" ("+topPop.pop.toInt+"M)",
    dashX+14,cy
  )

  cy += 16

  text(
    "Fastest Growth: "+topGrowth.name+
      " (+"+topGrowth.gdpGrowth+"%)",
    dashX+14,cy
  )

  cy += 28

  fill(150,180,210)

  text(
    "NETWORK: "+routes.length+
      " ROUTES / "+countries.length+" NODES",
    dashX+14,cy
  )

  cy += 18

  val top5 = countries.sortBy(_.gdpRank).take(5)

  for (item <- top5) {

    val barW =
      (item.gdp/top5.head.gdp)*(dashW-80)

    fill(180,210,240)

    text(
      item.code,
      dashX+14,cy+10
    )

    fill(20,50,80)

    rect(
      dashX+46,cy,
      dashW-60,12
    )

    fill(0,180,240)

    rect(
      dashX+46,cy,
      barW,12
    )

    cy += 18
  }

  cy = dashY+dashH-42

  stroke(0,100,160,80)
  strokeWeight(1)

  line(
    dashX+14,cy,
    dashX+dashW-14,cy
  )

  fill(120,160,200)

  text(
    "CLICK MAP MARKER TO INSPECT",
    dashX+14,cy+18
  )

  text(
    "[1] USA [2] CHN [3] DEU [4] JPN [5] IND",
    dashX+14,cy+32
  )
}

// ----------------------------------------------------------------------------
// FOOTER
// ----------------------------------------------------------------------------

def drawFooter(surface: CanvasDraw): Unit = {
  import surface._

  fill(8,16,32)

  stroke(0,120,180,100)
  strokeWeight(1)

  rect(mapX,545,mapW,35)

  fill(140,180,220)

  text(
    "KEYBOARD: [1-9] COUNTRY | [ESC] GLOBAL | [R] RESET",
    mapX+12,567
  )

  text(
    "[G] GRID | [N] NETWORK | [D] DASHBOARD | [A] PAUSE",
    mapX+12,554
  )
}

// ----------------------------------------------------------------------------
// KEYBOARD HANDLING
// ----------------------------------------------------------------------------

def handleKeyboard(): Unit = {
  if (isKeyPressed('1')) selectedCode = Some("USA")
  else if (isKeyPressed('2')) selectedCode = Some("CHN")
  else if (isKeyPressed('3')) selectedCode = Some("DEU")
  else if (isKeyPressed('4')) selectedCode = Some("JPN")
  else if (isKeyPressed('5')) selectedCode = Some("IND")
  else if (isKeyPressed('6')) selectedCode = Some("GBR")
  else if (isKeyPressed('7')) selectedCode = Some("FRA")
  else if (isKeyPressed('8')) selectedCode = Some("BRA")
  else if (isKeyPressed('9')) selectedCode = Some("RUS")
}

// ----------------------------------------------------------------------------
// MAIN SKETCH
// ----------------------------------------------------------------------------

class GlobeXSketch {

  def setup(surface: CanvasDraw): Unit = {
    import surface._

    background(6,12,28)
    noStroke()

    activateCanvas()

    // Register Mouse Click Event Listener in Kojo
    onMouseClick { (x, y) =>
      handleMouseClick(x, y)
    }
  }

  def drawLoop(surface: CanvasDraw): Unit = {
    import surface._

    // Continuously update hover code using Kojo's current mouse coordinates
    updateHover(mouseX, mouseY)

    handleKeyboard()
    updateState()

    drawBackground(surface)
    drawGrid(surface)
    drawContinents(surface)
    drawConnections(surface)
    drawParticles(surface)
    drawCountryMarkers(surface)
    drawHeader(surface)
    drawDashboard(surface)
    drawFooter(surface)
  }
}

val globeX = new GlobeXSketch

canvasSketch(globeX)