From b50230e062be274ed3469219a39215a0a4f73fa8 Mon Sep 17 00:00:00 2001 From: matsem Date: Mon, 19 Sep 2022 21:33:24 +0200 Subject: [PATCH 01/13] Add barebone raspberrypi module app --- raspberrypi/.gitignore | 3 + raspberrypi/build.gradle.kts | 41 ++++++ .../matsem/astral/raspberrypi/RaspberryApp.kt | 35 +++++ .../astral/raspberrypi/RaspberryModule.kt | 9 ++ .../astral/raspberrypi/sketches/Blank.kt | 21 +++ .../raspberrypi/sketches/FuturedLogo.kt | 122 ++++++++++++++++++ settings.gradle.kts | 1 + 7 files changed, 232 insertions(+) create mode 100644 raspberrypi/.gitignore create mode 100644 raspberrypi/build.gradle.kts create mode 100644 raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/RaspberryApp.kt create mode 100644 raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/RaspberryModule.kt create mode 100644 raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/Blank.kt create mode 100644 raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/FuturedLogo.kt diff --git a/raspberrypi/.gitignore b/raspberrypi/.gitignore new file mode 100644 index 00000000..f46534a2 --- /dev/null +++ b/raspberrypi/.gitignore @@ -0,0 +1,3 @@ +build/ +out/ +*.log \ No newline at end of file diff --git a/raspberrypi/build.gradle.kts b/raspberrypi/build.gradle.kts new file mode 100644 index 00000000..db4e8f81 --- /dev/null +++ b/raspberrypi/build.gradle.kts @@ -0,0 +1,41 @@ +import java.util.* + +plugins { + kotlin("jvm") + application +} + +apply() + +application { + val props = Properties().apply { + load(file("${rootDir}/local.properties").inputStream()) + } + val nativesDir = props["processing.core.natives"] + + mainClass.set("dev.matsem.astral.raspberrypi.RaspberryApp") + applicationDefaultJvmArgs = listOf( + "-Djava.library.path=$nativesDir" + ) +} + +repositories { + mavenCentral() + jcenter() +} + +dependencies { + implementation(project(":core")) +} + +group = ProjectSettings.group +version = ProjectSettings.version + +tasks { + compileKotlin { + kotlinOptions.jvmTarget = "11" + } + compileTestKotlin { + kotlinOptions.jvmTarget = "11" + } +} \ No newline at end of file diff --git a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/RaspberryApp.kt b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/RaspberryApp.kt new file mode 100644 index 00000000..9a608b21 --- /dev/null +++ b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/RaspberryApp.kt @@ -0,0 +1,35 @@ +package dev.matsem.astral.raspberrypi + +import dev.matsem.astral.core.di.coreModule +import dev.matsem.astral.raspberrypi.sketches.Blank +import org.koin.core.KoinComponent +import org.koin.core.context.startKoin +import org.koin.core.inject +import org.koin.core.logger.Level +import processing.core.PApplet + +class RaspberryApp : KoinComponent { + + companion object { + @JvmStatic + fun main(args: Array) { + RaspberryApp().run(args) + } + } + + private val sketch: PApplet by inject() + + /** + * Launches PApplet with specified arguments. Be sure to include --sketch-path argument for proper data + * folder resolution (dir containing your Processing data folder), + * @see https://processing.github.io/processing-javadocs/core/ + */ + fun run(processingArgs: Array) { + startKoin { + printLogger(Level.ERROR) + modules(coreModule + raspberryModule { Blank() }) + } + + PApplet.runSketch(processingArgs + arrayOf("RaspberryVisuals"), sketch) + } +} \ No newline at end of file diff --git a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/RaspberryModule.kt b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/RaspberryModule.kt new file mode 100644 index 00000000..4e5be79b --- /dev/null +++ b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/RaspberryModule.kt @@ -0,0 +1,9 @@ +package dev.matsem.astral.raspberrypi + +import org.koin.dsl.bind +import org.koin.dsl.module +import processing.core.PApplet + +fun raspberryModule(providePApplet: () -> PApplet) = module { + single { providePApplet() } bind PApplet::class +} \ No newline at end of file diff --git a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/Blank.kt b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/Blank.kt new file mode 100644 index 00000000..2ab90a94 --- /dev/null +++ b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/Blank.kt @@ -0,0 +1,21 @@ +package dev.matsem.astral.raspberrypi.sketches + +import dev.matsem.astral.core.tools.extensions.colorModeHsb +import org.koin.core.KoinComponent +import processing.core.PApplet +import processing.core.PConstants + +class Blank : PApplet(), KoinComponent { + + override fun settings() { + size(420, 420, PConstants.P2D) + } + + override fun setup() { + colorModeHsb() + } + + override fun draw() { + background(0f, 100f, 100f) + } +} \ No newline at end of file diff --git a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/FuturedLogo.kt b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/FuturedLogo.kt new file mode 100644 index 00000000..6319e382 --- /dev/null +++ b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/FuturedLogo.kt @@ -0,0 +1,122 @@ +package dev.matsem.astral.raspberrypi.sketches + +import ch.bildspur.postfx.builder.PostFX +import dev.matsem.astral.core.tools.animations.AnimationHandler +import dev.matsem.astral.core.tools.animations.radianSeconds +import dev.matsem.astral.core.tools.extensions.colorModeHsb +import dev.matsem.astral.core.tools.extensions.quantize +import dev.matsem.astral.core.tools.extensions.shorterDimension +import dev.matsem.astral.core.tools.extensions.translateCenter +import dev.matsem.astral.core.tools.extensions.withAlpha +import extruder.extruder +import geomerative.RG +import geomerative.RShape +import org.koin.core.KoinComponent +import org.koin.core.inject +import processing.core.PApplet +import processing.core.PConstants +import processing.core.PShape +import kotlin.properties.Delegates + +/** + * Uses geomerative library to convert the futuredlogo.svg into 2D shape. Extruder library is then used to extrude the + * logo into 3D shape. Renders with oldskool playstation-one-like effect using PostFX shaders. + */ +class FuturedLogo : PApplet(), AnimationHandler, KoinComponent { + + override fun provideMillis(): Int = millis() + + override fun settings() { + size(640, 640, PConstants.P3D) + } + + private lateinit var originalShape: RShape + private lateinit var extrudedShape: Array + private lateinit var fx: PostFX + private val ex: extruder by inject() + + private val logoToScreenScale = 0.4f + private var shapeWidth by Delegates.notNull() + private var shapeHeight by Delegates.notNull() + private val shapeDepth = 100 + private var sclOff = 0f + private var rotYOff = 0f + + override fun setup() { + colorModeHsb() + surface.setTitle("Futured") + surface.setResizable(true) + + RG.init(this) + originalShape = RG.loadShape("images/futuredlogo.svg") + RG.setPolygonizer(RG.UNIFORMSTEP) + RG.setPolygonizerStep(1f) + val rshape = RG.polygonize(originalShape) + + shapeWidth = rshape.width + shapeHeight = rshape.height + + extrudedShape = rshape.children + .asSequence() + .map { it.points } + .map { points -> + createShape().apply { + beginShape() + for (point in points) { + vertex(point.x, point.y) + } + endShape(PApplet.CLOSE) + } + } + .flatMap { ex.extrude(it, 100, "box").toList() } + .onEach { + it.disableStyle() + it.translate(-shapeWidth / 2f, -shapeHeight / 2f, -shapeDepth / 2f) + } + .toList() + .toTypedArray() + + fx = PostFX(this) + } + + override fun draw() { + ortho() + background(0) + strokeWeight(6f) + stroke(0x00ffc8.withAlpha()) + when { + mouseX.toFloat() in (0f..width / 3f) -> noFill() + mouseX.toFloat() in (width / 3f..width / 3f * 2f) -> fill(0xffffff.withAlpha(0)) + else -> fill(0) + } +// if (mouseX > width / 2f) { +// noFill() +// } else { +// fill(0) +// } + + if (random(0f, 10f).quantize(0.1f) == 0f) { + sclOff = random(-0.1f, 0.1f) + rotYOff = random(-PI * .2f, PI * .2f) + } + + translateCenter() + scale( + width / shapeWidth * logoToScreenScale + sclOff, + width / shapeWidth * logoToScreenScale + sclOff + ) + + rotateX(-PI * 0.1f) + rotateY(radianSeconds(30f) + rotYOff) + extrudedShape.forEach { + shape(it) + } + + fx.render().apply { + bloom(0.3f, 100, 10f) + bloom(0.3f, 100, 10f) + noise(0.2f, 0.1f) + pixelate(shorterDimension() / 2.4f) + }.compose() + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 47e15258..9011ccf3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,3 +2,4 @@ rootProject.name = "astral-visuals" include("core") include("visuals") include("playground") +include("raspberrypi") From 05665b721595f70c72c4587849d9525a49b2e23c Mon Sep 17 00:00:00 2001 From: matsem Date: Tue, 20 Sep 2022 00:38:48 +0200 Subject: [PATCH 02/13] Update neon logo sketch with SEM logo --- .../dev/matsem/astral/CommonDependencies.kt | 2 +- data/images/semlogo_vector_bot.svg | 14 +++ data/images/semlogo_vector_top.svg | 14 +++ .../matsem/astral/raspberrypi/RaspberryApp.kt | 4 +- .../sketches/{FuturedLogo.kt => NeonLogo.kt} | 100 ++++++++++-------- 5 files changed, 84 insertions(+), 50 deletions(-) create mode 100644 data/images/semlogo_vector_bot.svg create mode 100644 data/images/semlogo_vector_top.svg rename raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/{FuturedLogo.kt => NeonLogo.kt} (52%) diff --git a/buildSrc/src/main/kotlin/dev/matsem/astral/CommonDependencies.kt b/buildSrc/src/main/kotlin/dev/matsem/astral/CommonDependencies.kt index b13e7e15..8e86f85e 100644 --- a/buildSrc/src/main/kotlin/dev/matsem/astral/CommonDependencies.kt +++ b/buildSrc/src/main/kotlin/dev/matsem/astral/CommonDependencies.kt @@ -41,7 +41,7 @@ internal fun Project.configureCommonDependencies() { fileTree( mapOf( "dir" to "$processingLibsDir/$libName/library", - "include" to listOf("*.jar") + "include" to listOf("*.jar", "shader/*.glsl") ) ) ) diff --git a/data/images/semlogo_vector_bot.svg b/data/images/semlogo_vector_bot.svg new file mode 100644 index 00000000..f828482f --- /dev/null +++ b/data/images/semlogo_vector_bot.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/data/images/semlogo_vector_top.svg b/data/images/semlogo_vector_top.svg new file mode 100644 index 00000000..6f0ef324 --- /dev/null +++ b/data/images/semlogo_vector_top.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/RaspberryApp.kt b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/RaspberryApp.kt index 9a608b21..5c01482b 100644 --- a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/RaspberryApp.kt +++ b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/RaspberryApp.kt @@ -1,7 +1,7 @@ package dev.matsem.astral.raspberrypi import dev.matsem.astral.core.di.coreModule -import dev.matsem.astral.raspberrypi.sketches.Blank +import dev.matsem.astral.raspberrypi.sketches.NeonLogo import org.koin.core.KoinComponent import org.koin.core.context.startKoin import org.koin.core.inject @@ -27,7 +27,7 @@ class RaspberryApp : KoinComponent { fun run(processingArgs: Array) { startKoin { printLogger(Level.ERROR) - modules(coreModule + raspberryModule { Blank() }) + modules(coreModule + raspberryModule { NeonLogo() }) } PApplet.runSketch(processingArgs + arrayOf("RaspberryVisuals"), sketch) diff --git a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/FuturedLogo.kt b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt similarity index 52% rename from raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/FuturedLogo.kt rename to raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt index 6319e382..1cbc30ab 100644 --- a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/FuturedLogo.kt +++ b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt @@ -4,6 +4,7 @@ import ch.bildspur.postfx.builder.PostFX import dev.matsem.astral.core.tools.animations.AnimationHandler import dev.matsem.astral.core.tools.animations.radianSeconds import dev.matsem.astral.core.tools.extensions.colorModeHsb +import dev.matsem.astral.core.tools.extensions.pushPop import dev.matsem.astral.core.tools.extensions.quantize import dev.matsem.astral.core.tools.extensions.shorterDimension import dev.matsem.astral.core.tools.extensions.translateCenter @@ -16,65 +17,73 @@ import org.koin.core.inject import processing.core.PApplet import processing.core.PConstants import processing.core.PShape -import kotlin.properties.Delegates /** * Uses geomerative library to convert the futuredlogo.svg into 2D shape. Extruder library is then used to extrude the * logo into 3D shape. Renders with oldskool playstation-one-like effect using PostFX shaders. */ -class FuturedLogo : PApplet(), AnimationHandler, KoinComponent { +class NeonLogo : PApplet(), AnimationHandler, KoinComponent { override fun provideMillis(): Int = millis() override fun settings() { - size(640, 640, PConstants.P3D) + fullScreen(PConstants.P3D) } - private lateinit var originalShape: RShape - private lateinit var extrudedShape: Array private lateinit var fx: PostFX private val ex: extruder by inject() private val logoToScreenScale = 0.4f - private var shapeWidth by Delegates.notNull() - private var shapeHeight by Delegates.notNull() private val shapeDepth = 100 private var sclOff = 0f private var rotYOff = 0f + data class Chunk( + val originalShape: RShape, + val extrudedShape: List, + val shapeWidth: Float, + val shapeHeight: Float + ) + + private lateinit var chunks: List override fun setup() { colorModeHsb() surface.setTitle("Futured") surface.setResizable(true) + surface.hideCursor() RG.init(this) - originalShape = RG.loadShape("images/futuredlogo.svg") RG.setPolygonizer(RG.UNIFORMSTEP) RG.setPolygonizerStep(1f) - val rshape = RG.polygonize(originalShape) - - shapeWidth = rshape.width - shapeHeight = rshape.height - - extrudedShape = rshape.children - .asSequence() - .map { it.points } - .map { points -> - createShape().apply { - beginShape() - for (point in points) { - vertex(point.x, point.y) - } - endShape(PApplet.CLOSE) - } - } - .flatMap { ex.extrude(it, 100, "box").toList() } - .onEach { - it.disableStyle() - it.translate(-shapeWidth / 2f, -shapeHeight / 2f, -shapeDepth / 2f) + + chunks = listOf("images/semlogo_vector_top.svg", "images/semlogo_vector_bot.svg") + .map { uri -> RG.loadShape(uri) } + .map { rshape -> RG.polygonize(rshape) } + .map { rshape -> + Chunk( + originalShape = rshape, + shapeWidth = rshape.width, + shapeHeight = rshape.height, + extrudedShape = rshape.children + .asSequence() + .map { it.points } + .map { points -> + createShape().apply { + beginShape() + for (point in points) { + vertex(point.x, point.y) + } + endShape(PApplet.CLOSE) + } + } + .flatMap { ex.extrude(it, 100, "box").toList() } + .onEach { + it.disableStyle() + it.translate(-rshape.width / 2f, -rshape.height / 2f, -shapeDepth / 2f) + } + .toList() + ) } - .toList() - .toTypedArray() fx = PostFX(this) } @@ -89,11 +98,6 @@ class FuturedLogo : PApplet(), AnimationHandler, KoinComponent { mouseX.toFloat() in (width / 3f..width / 3f * 2f) -> fill(0xffffff.withAlpha(0)) else -> fill(0) } -// if (mouseX > width / 2f) { -// noFill() -// } else { -// fill(0) -// } if (random(0f, 10f).quantize(0.1f) == 0f) { sclOff = random(-0.1f, 0.1f) @@ -101,20 +105,22 @@ class FuturedLogo : PApplet(), AnimationHandler, KoinComponent { } translateCenter() - scale( - width / shapeWidth * logoToScreenScale + sclOff, - width / shapeWidth * logoToScreenScale + sclOff - ) - - rotateX(-PI * 0.1f) - rotateY(radianSeconds(30f) + rotYOff) - extrudedShape.forEach { - shape(it) + for (chunk in chunks) { + pushPop { + scale( + width / chunk.shapeWidth * logoToScreenScale + sclOff, + width / chunk.shapeWidth * logoToScreenScale + sclOff + ) + + rotateX(-PI * 0.1f) + rotateY(radianSeconds(30f) + rotYOff) + chunk.extrudedShape.forEach { + shape(it) + } + } } fx.render().apply { - bloom(0.3f, 100, 10f) - bloom(0.3f, 100, 10f) noise(0.2f, 0.1f) pixelate(shorterDimension() / 2.4f) }.compose() From d48deee7a89cf33742aaadf98c418092cf761cd8 Mon Sep 17 00:00:00 2001 From: matsem Date: Tue, 20 Sep 2022 00:39:10 +0200 Subject: [PATCH 03/13] Set up gradle distribution plugin for Raspberry Pi (aarch64) --- raspberrypi/build.gradle.kts | 11 ++++++++++- raspberrypi/src/dist/images/semlogo_vector_bot.svg | 14 ++++++++++++++ raspberrypi/src/dist/images/semlogo_vector_top.svg | 14 ++++++++++++++ raspberrypi/src/dist/setup.sh | 5 +++++ 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 raspberrypi/src/dist/images/semlogo_vector_bot.svg create mode 100644 raspberrypi/src/dist/images/semlogo_vector_top.svg create mode 100755 raspberrypi/src/dist/setup.sh diff --git a/raspberrypi/build.gradle.kts b/raspberrypi/build.gradle.kts index db4e8f81..df53c61c 100644 --- a/raspberrypi/build.gradle.kts +++ b/raspberrypi/build.gradle.kts @@ -11,12 +11,13 @@ application { val props = Properties().apply { load(file("${rootDir}/local.properties").inputStream()) } - val nativesDir = props["processing.core.natives"] + val nativesDir = props["processing.core.natives.rpi"] mainClass.set("dev.matsem.astral.raspberrypi.RaspberryApp") applicationDefaultJvmArgs = listOf( "-Djava.library.path=$nativesDir" ) + applicationName = "visuals" } repositories { @@ -38,4 +39,12 @@ tasks { compileTestKotlin { kotlinOptions.jvmTarget = "11" } +} + +tasks.getByName("distZip") { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + +tasks.getByName("installDist") { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE } \ No newline at end of file diff --git a/raspberrypi/src/dist/images/semlogo_vector_bot.svg b/raspberrypi/src/dist/images/semlogo_vector_bot.svg new file mode 100644 index 00000000..f828482f --- /dev/null +++ b/raspberrypi/src/dist/images/semlogo_vector_bot.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/raspberrypi/src/dist/images/semlogo_vector_top.svg b/raspberrypi/src/dist/images/semlogo_vector_top.svg new file mode 100644 index 00000000..6f0ef324 --- /dev/null +++ b/raspberrypi/src/dist/images/semlogo_vector_top.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/raspberrypi/src/dist/setup.sh b/raspberrypi/src/dist/setup.sh new file mode 100755 index 00000000..bb5e346b --- /dev/null +++ b/raspberrypi/src/dist/setup.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +mkdir lib/shader +mv lib/*.glsl lib/shader/ +mv images/ bin/images \ No newline at end of file From ae565ba003a48e434cd34f280ce841a30185444c Mon Sep 17 00:00:00 2001 From: matsem Date: Tue, 20 Sep 2022 23:14:32 +0200 Subject: [PATCH 04/13] Pimp up the NeonLogo sketch, add lineup for upcoming party --- data/images/semlogo_bottom.svg | 5 + data/images/semlogo_top.svg | 5 + data/images/semlogo_vector_bot.svg | 14 -- data/images/semlogo_vector_top.svg | 14 -- .../src/dist/fonts/JetBrainsMono-Bold.ttf | Bin 0 -> 142612 bytes .../src/dist/images/semlogo_bottom.svg | 5 + raspberrypi/src/dist/images/semlogo_top.svg | 5 + .../src/dist/images/semlogo_vector_bot.svg | 14 -- .../src/dist/images/semlogo_vector_top.svg | 14 -- raspberrypi/src/dist/setup.sh | 3 +- .../astral/raspberrypi/sketches/NeonLogo.kt | 164 +++++++++++++++--- 11 files changed, 160 insertions(+), 83 deletions(-) create mode 100644 data/images/semlogo_bottom.svg create mode 100644 data/images/semlogo_top.svg delete mode 100644 data/images/semlogo_vector_bot.svg delete mode 100644 data/images/semlogo_vector_top.svg create mode 100644 raspberrypi/src/dist/fonts/JetBrainsMono-Bold.ttf create mode 100644 raspberrypi/src/dist/images/semlogo_bottom.svg create mode 100644 raspberrypi/src/dist/images/semlogo_top.svg delete mode 100644 raspberrypi/src/dist/images/semlogo_vector_bot.svg delete mode 100644 raspberrypi/src/dist/images/semlogo_vector_top.svg diff --git a/data/images/semlogo_bottom.svg b/data/images/semlogo_bottom.svg new file mode 100644 index 00000000..b82efc39 --- /dev/null +++ b/data/images/semlogo_bottom.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/data/images/semlogo_top.svg b/data/images/semlogo_top.svg new file mode 100644 index 00000000..ebf13435 --- /dev/null +++ b/data/images/semlogo_top.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/data/images/semlogo_vector_bot.svg b/data/images/semlogo_vector_bot.svg deleted file mode 100644 index f828482f..00000000 --- a/data/images/semlogo_vector_bot.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - diff --git a/data/images/semlogo_vector_top.svg b/data/images/semlogo_vector_top.svg deleted file mode 100644 index 6f0ef324..00000000 --- a/data/images/semlogo_vector_top.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - diff --git a/raspberrypi/src/dist/fonts/JetBrainsMono-Bold.ttf b/raspberrypi/src/dist/fonts/JetBrainsMono-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c2075fc34df6e0933e83d90c80f7e493aba4376a GIT binary patch literal 142612 zcmeFadw^8Ml|Nopx8Kv#(>>GO)AR1fbkBRHXS(OX@M;ioL_mUwfD;@Z@)8koL_{F7 zMv)Lg2oi(D5JHGy4I#)HmJp(Z5Q2mdLJT29T_h|^T$i}U5SNwa_c>Mf_B_Ph@Av!n zZ!UH3txuggRdwprsb^mpXN=|IW@5onciny)e4L3oC!V(3M%_BvbC;(LP%ZrRw@tY7 zt_Mdh|8Mv^;a_*#U3ZU6F6#al_%Abd$CNwoYG}I4^p3{ZxD>*>W<5A#@q~Zb(+dAK zu)8*EX?L*V3pqQPDCtJ{&N++cKDf2TcbbXPV#Zv)xicPF4BUo}+go z{^S=J|JRsF@6c@SwDDi#gVr-;4NH)-Yy%dhtV#bpLqyiPstb z(9KxYy^ELJzj*DhUMOXvHVOJOkPKra8Tu2~^FdI0L%at5Ba>})@N@^~mdR{uTll}4%mU)S1(iEWA`uK>mkvBnQW|mnq z7Iw23-Fb@cV%qZimpr6(%lmS9?*W%D%9K$8#78VFLteVw8a&@epHLy5qWKAr=lB}7v zuvXT_2C;TFm2lgcUJbQ{g%~rE#*c$c)w)X#*%>OT$ z{|BAndV5l>>Wf?xHsKfLA}L0TDPoT37Hh?3@uJu#j*2tlviL}IYC$co4b#SJo!UZe zg|<%Hs_oW}Xm4njv=2?fb=K z%`?qQ%ukv(n75htn2(yznBO&Dvp6gPOU%+@8D*JdnQiH|JY{*#veUB9a@2Cha>;Vl zYO@Bc)z&s^hjp@bwsnd1N$W=IHtTNdLF)H+XUN8+Y;N8woSGj zwtcpvwllU%wySoF-EXh7x7j=FlkKzZOYA-N4fd_}UG{_aJK~OE zjdKmzwK!{8R!`QNtPNS4v$kdJ%-WN+KkHD|v8+>BXS2>{UCw$x>mw&~+MI4@ zz!`K_I^)hZXUf^(oZy_|>~hX?E_N<+_Bhu#H#j#tw>ft@_c-@E4>^xHPdU#z&pR(W z-*_gedvQK57%|4%fIs5(Wk6g@UbGcmsSI|}Iio4oeDOZPUf@_Ma%Qern*tHCH6+Jid zu?Bf~N%!q`zUn;bJTHA_=SAmLbFFZ#bG_i&=VB!JK$bDrchfnR6!R z%*k1rvpQ$9?%$HLE9cdm(>a%P|GPQY+zxl4yH@%w?s|91J<&bez0AE%QMlK;x4HMa zkGjuEzt#P=`-(g5ad}ET^`4Z%Ji|QWJ<~mlJgYqGJ=;_~&sNVK&tcCQ&%4rZ@?7zx zb6vSX6`EU~+mhRn+nKvqh30nWK9##E_eDMQrQ8F#$8*o+zNh=I9^#q&)c52FYiR&g}je+ijTc6Z_u0cc1XX?JJ#FjUFcoqUGLrM-R(W3 zsJsWgC%xyrANp*HkI(4~`jWmezAoQViI{xLeQSMNe7kht9^WC~Dc=R@v-&RiuKF#0 zzdz=0@vC&q{?Yy^{ssP3{*98x?0?R`(|^E!(y!u}{g?b7#YH_u8;Z6S?Nxk>_7)u}I#YDH=wn4u%!+f1%Zpoz#}rRj6vbV|ONyT= z-dwDtZ7$wZe7N|H;>*PGD!tcBN}dH<#`#-CufKQdvt+l)hd1ewirq zmQ|K1>6y!hl#MUzD(fy=qd1qXD|?}ASJ}a`lhSW0J5_eR?ERpsEmP1E^ao?XHbom8 z5*!=s3@!|=QnbOI;D+F~pe~c(zTmOox!{$MUeh5<$RCP@T0(kFhsK0DLyJOBhMrU0 zLR&(+La&BShg9pegx(FM!@1$g@UZYCNnr_32`>n*2(J%s5ARbchWCe$h0lgnD>R3% zL`1|Jsf_4li=-lxB6A|kBkNTPk&ThhTBIp@B>G15V)UvW^IFUiD~#2un6Y>)6`K(2iY?Lo-LciN&9PlEJ#Oqs z?2XvPm>xHFt-?`JSfS#YD&iHXiU}1nRm_Sx70W8tR&1@%>!D&_#j%QW6;~?tdZ@Hi z1}dv7hgD9L^p?uWl?y6YRc@@@S^27t9Ikw$@>1nTRnDrQ;#n20N>+`onpU+)_b;h> zs_MC_m#S0@bJgLhH>xgGU8{Df@ao*^^6HlAG1W@@E!8utmsCGly|H?G^(#8XE7eD< z&sM)z!<4_qRO78FuSwP@Nm^<;YNpi8t65gFre=#yv885L&8s!1YjpjndAH_Tt)sTE zwz^iY@7f`?<7>NWyK8k_u3cBVwRTVKk=nD0L+zWjSL#@ux2{t8>uT$U)lI0IUAIj4 zuc%vJx2^7#x?{TkMBUqU@7J5^b^WL>uCK38)laOSQ@>nM)UT*tSN}r&u6kV#^@r+D z*I%svsKF`2O%3jbU_-oNr0yHtFu7q)gG$ZRu%cmI!wU_&^{~AShZ{~eTvU7;E;n3@ zJLAE4QiaCb;vMlR@ddhXQG8W=eSDjujqix>iyw=>8Gm07{jky0=xvNCUt@J+d*hhK zPDR_;)wrbbsm9HXyY$dKjfWafHD1ttmm04&S(;R7%}vEk^-Za!Nlo)qXw$-`6;11! zw(7p^O?#UTH>s9nYC79=x#^>XLxm+=iPA)UBBlFAB_<_iCzk5I<%zY4EeVyrIk6{k zC~+!rKJkGb`eD+P^d|MZC##d~$uUWlcT=(}xj5OA+$br{$>)+glLwL~lX~8hmy%bT zEzJQr8|BT#`m<1fmVOFu(EA6_g>`>Se^w6gmk;m{46oM1YxU=+`Hgr3=?vUR--utY z^NZ`xrvJr{6$+XyH@E1|Cb`wl#Y+mpQH6>hyENhz;A>b^hWqtoo>AT zH2jlv{}lZ>d4PYK?(fu}(+BvC@K1|x@H65Y^oHNyH&f@kKz}aOpG)-TQvKQeDSm^_ z2w$PotCXfD^N9XDraw>U&(r$zjQ)I6f1cN$m-Oem`txJ;Y@szG-Xe5ogYL@J zuI3MzX#S}ABkAYOSD?|CNw4A7=@h4csniJJ&E!|k;Bg2%4mBUrdAx-1Jp*_^O3g}A zUy?jfE6wW=YaQ+em*q@rUQU-pMDxsM=uGoGozr?558B;4NjczO)^nHwr&P0QNrds{ zTIsarTDmeR1emEgpe~7N$uwP{rS&o@^9IBg$;-*hj3*%razMXAJd==o@+?Bn;%=ab zl|0O};z zgk$+sy2OVDkHq^-BrYZ{Djo@xH}QtfW24GT;v_I72SRw_pze-kctD8sBsTJdWUQ_+Nqlm8JtaPiTJ=$-&@2scuvb=vO7r9{76@s|R<3%Oa*V zEuu>zqN%W{5LC4KPn@2U@le-I9m)awnqKN8I3=5sl8!Lm6x3Zz^5RW?_}y@R>8BiF zRUIQ6TGIf1<8HXCc&C(fu8nU4yO=?EBPdqH$8=B%m+AN4z)7)KWm6gjxjJiQ}-pc*YepHV?4{^8RopPe58s7{IR#QiKd^!B&R_k$=;vIG&zEt-w z!8^(wpCkR?6(55%CdVgd{O$0Mj_Veo0i}<(AdFJ@x+-ZL)*xPa0HZ{VT*e@eW9X8IXs|WdK!rUr8Sk4EuO8)ZKv~HF z-=^mg`&yW3K;w0cHxz<~To8!VhX#jELw;UQrAJUE2Hxr~!(GWBy#5UQ%IQ4nk1|nz z6nDeF5B`1i8>OGuPXYalI?r#ZRO>eZg9Exv7buYl7Ez}*&@Pb6@&S1atOdWbjJxyM>6)0-3?quB?b=0d!+19OPrn&=lhv<@+ zRyUR|(C&iYh%M?`>X6sE=afUpcVyZ@_?IK4p1K}MODLtDL^`wU7Sy4=WN1r)W7n{* zv~ChGMdt*avuY&()qs9GQMC`oh}7xEp@-g|&NYXJ`EA3v0K74~6sECOFa< zO7puq2h?h9PX^(&ZSc1t)}q=)87?uT+ew#11ZMxJuUcv=#0hqo@fv7rEhJ11*gq=n zHSd8_xE7kBW4z`pXwNdO=9OAktqZ6uN6ists@Ya^jV_64HLK|Y?Irjr_Di4?HH&K& z1A~mo0sWp#8&<5Q3!w*Ul&v5Xv1-uI)r_gxQM02DEm}$qxhI=ZCdwEnN|GJZPQ+*Zw zL((b#NxbioPS0ocHYTdK;cob0`KmWm>vdZ_8MHkJqg?J%C98(jBM0RBB&X_5=|puW z-V83Vd{`^OB@t2W!a5SKHZ0$6idS_F2&^7CQ2jvX4$D{Vu6AcIUiC3(KL%P@J#)fU8qe5x%O453wfn5a5g^+5*XRWAU20qE(fw=?|L zGOg-R)rqPTI{)!i<3V*m=f6khza0pr@uK8K81bI~POGanW-wkg1n3a(E2k@yLM1a* zxz!~xt@2&E7_a=ePbvcIc5eW?q#QzCrnIrH$16`UQF#h?Dlwr*z4A2EDi6?=L3!mt z;0Hmwqmo8-iVk{@^+8-21S6Zu9$e=nA}Tv8I}wjsEAiheQ|$mUO*vryr1P2%-lHo= zOFF`M;#ZnI9O8Pgi*`@I0Y(H3sQ84ftujlk{6GC zh`4YS@Jc@K!w<>D-q-!B@eU0`ihB8Dm>tAU#Z>8e>?wr30(VG;^Vlf7@4-9eV!z5o z4B8&snn8GM9sJ4}Jkf*1R>W3F9*7%54-#7#>&W;!;qQ!1kbWMk1jShCRGxG3j#dOG z{n0~suf;p%;YWHNpb0TJAO~bQZSdQ`)rPyC3#`*?(W`VxL`0WGmoXlto{-}GnBqle z!=X))19nhxkD~oV58`fMJh~G!6Cwr z7^CoRa3l_}w$Y{xLgC6OD@qiYDMj0(?K+SBK&l7ufUKf{K0GKETsgG_N*8rm4jm|e zLH9$8%g@8D)BW4u3uQQQf(0$_Ebr9mlR(Dey-zvH*8^+shhWTK zKBZi>eqN4NEM2Dl%L@_SR!;qc)a!DzBrdY3%G$Kpkw!y)k>KH;J82?1pMIf0B9rPrE`indl zc@CJ&V`L7}dy1|MG6~2MT#80S0uj_iqzmE1={1?62?%tX9I!(=FO)yh7HP|1Jc6>p z3G9Gnq!j*;Y2h~`EfJ+d0()iqK~=8vCtf_f18yHscZL^I4<7)h?P2Jgo+b~YYTCfz>g3 z3qjY(;r(YaowwnCn~Bhg&0aHBeE#691o5ydcUS8V!ds>li{qLGW+F zov@>l?gCIJTpS3+L!FWf1)21RQ16nLTls@8!v7}1;RQW|?*tRU_i;D;ux-Kf!SmA3 zgR?!9{lry>!hCtDNj@IPNn#TPJyxp z;eZ?i!h{!`WJP8qHqsm5sib_WD zektR{foz9cmcfWWdik>TxDy>T49Eny3dk`OP2&Xyr469aFhAHu1T6>;UI&?)J*7(7Zbk1SnV zx>ovmDe_&qd;kx~s&rn4D=*y&zjAs$O4ozOdfW~FD)?8Gs`k%IptGgM=<=k>d1)sw zIK;roSiDoZ${9RT;E^g#NuIo9Kf*~?Mhq%jsd7M1NgfvXVMj|Xxa(X>U`I{>A5+R(!sAU-3Sje`hfyP`pFuui|w8 zLD`Ep^yOdtBKW^pyds0~VvJ$nv|?;H7b_cp)Qd}*skju^vSKA0t>`1V5JLS8m4Z?c zMHh;ok44bY;_(@j7o7kGyIu5lFzPNju&EG7pJ366`snZuo}o}Tn8m0 z3TGD11l>#hQb0`HbOl8eMfNuGr8f{V%(78*PY-T)6rVSUCAOI~oTK-DZS*be%42k<29Y#*M` z-~#1zn^4dL9zD2AeJOx87c7>}2=798SHT=Td>Fze=xJcwNpA&&!QuV)GT)Fz!E(e} zUZAAG3sBp-Gq_Gc_>=;&2PkRaGQvA_uEc}5wkU^ptZ#wl0{rDnD=5cBwvQx)dWRDQ zZrpXw0rZQ3G+h!Afps{4#{=)dM>+h1z4V^M(m09n!iYeQf8Tr@>9$WLb>O4?C`RfPpfQIG60ezF? zlMmY?ohm=&6k~z>@_cIPQa<@(;P>XYDW3j=pfKq?i37!`WZeHjAD(_-7!Bn6aW}Y_ zn3iv%OCrMG?eCVcl$740y!gA|-c}A&Z!7Np<4pLE<4$=djQdyXu1E3$2meygEcP#! ze(nzd>B74agD47#z@lp|bd1kxuw_juUDEl(&gZSjQ>7=2=Rr5%MBXUejr>mr z|E9djd6O0Y+_Sl7K}9Wrc>Np2%RK@FHZL!p!6^U5OyquqJJDgp0K_Jp$g|~Ml-xj- zdzERqS94G2o|cHnU6#8H=ppzr@p5MaIR&>XgHcS(5pxgbLYknJ{Bw5#-3fGO?hBF| zQ9TF#&*iSorM8B8&kf`TfG+FD-vnea9MaKwQOr(IcjivWU_7@2D7hgS?ror{&8^M& zF|x=l&dt<5(h|8&kJ5hbd4X}y$9l?tMsARb=PI0PGlX!@Md^g+EZ+1w@L&w;IqEr$ zr!0eKl4laA4pDr#cV)a3kZo{EMuZXf%}jXK;ZAh_F6m&`JmfMG^1!x%CNn1XV1Daa z;hBvX$dCInXcrPModaqGFb#xH5!l!$QcckO4~b(}aD@mxC)4{5meWiW)g zFmH9e@7kWhxa&orn7O%*xM*ae_-|sGYcGDavqI-T#We*~3v~W}rTn`_!L5UvEO`+| z{4rK>EyJ&XC?ARrWpvd8o$Xp9xe-+{_)l~#buE>M$o?q%BcP=HnRwY3fz-k&TcGpL zz5q@E7c>sEkOJe`C#4gvVpm4fvk!v5!{vARb^e>PH-nQ&=da>Dg?LCK`~3_rq@MjG z6ZkFFn;DE}V{8K_@LQ+zk{eNVGcEfhT^VF1kb}5(NJM1EvSXlo3BKN(<^p+6IpBX& z63a#_&3+Q;5*=YYdyMWDNnVs*J7}h7PtW+P;UAyfBK_QX02IUUP8?v_=`Gs^2kGeu z&xS_9iR{8`0nDJ$vTgXq+4~X^&LvKa)17Zqd?TK7I*Y&8%mI&)PHHzuBkMGf zrU5*lh0b6f9!-*lXZ7X-mNjc*mY$C+wCJpX`9Mlpee#j@A^ha_8u`e&2p-#Yo;(Zn zBl(e(^s;0f2G-M?k1S{{oXDD~r%Y)KV;X)(*aJE$kI2FWlxmqceMDt+Tme!IH!Xuv zOpI@`{8^(j7LX@AdD;XYG zDR8{)APu1KnQ)}xlskh5TD;?+M6OAidGOCetVIseb(IcAFOE*fX2)iU2nVLRtKw99SaXLmh#OMXT(S*)r=%B|AaxNf}yZs|3>}h*i`netXw^RNp#((P+XMm{| zObE9_cgZ;{86L2L_N(@*I*;WDzchdc?1lXe$@OE&qX&Mh4A|G%4`;Y6Vw!!qeUE*Q zM1;M`4(+pV)j6qnr9dXZtS}Xc@Z12W&9xoxx-=Bx|9555{z(2^! z3O$xi*#+XUf(iWUm}~=u&w*Q_c*qz;F%7P(504J`$#uy%M1kJhI>D;S#;su>sR2Cv z!0P+(K%T7R)*C!5AA-j!>niEzmbVeMs6P+OX<(hbJS>M8CuddljC@!yO0lFZRM#kr zWeePu0X$GA7THFWJS;2VC%0I}As!3Cqs!8j@pr;M!9wkXc#eik;T_Qp`B>V4^(mXB z5`J=O6hS-#;IR;#6%SZHOJ{!`=BvP58zR0ArWc0<1H9t#Arj2}<(f%#Ez8Lv1 z?*%6HMIzk14Rqwl9!eWx-oS+UwE493bMtb9QL6^IjDd%F5!{{(!p)F3oG?!?D+v(I z7^azrncqN;h_5g=nbH0;oH)tJjC_~_aJwZh!YC%%w%KGxy}+aBTtMMWA4^_B2sd{y2e@`HP27pEi(Eq3i~Kz9+Zg0&+9KaK;Y$|6 zT+ht-J^w=OlBffnFxN5-@AaY<@746KJt%7Mj+QB=N=}+{QFSSXJxz> ziAvkg=F5P8QdPymS{_*4{ILU0<9bGT;$^WEkb)6{KC>dfkVs17<}DD zXs3XVk}*!A{3FFJNaE<#Sg&6o7U@Z}ew9ify(TzpnEOZvk~KHBFIc1VBUh-P*W zwJyXJpqI4o;BBW2`?ie3B>Fi}o#j~wLm8DE1(oxX_66Kmf&Pp5A_fqFRd4Mn`Lm&yjc7Lr`YR`(yHs)&s;W@3Z9H zChy&J=hx&r-tbOeNaDgR-_Oc-SRLRW%lmb@YvU!3UJsP3c{SrL zTew7y%$tU{$MiR+jtOCt2%ds+q6xG9Cg=?aJt^-Rzb1I*%`K=@8owdbo6w(T{xQ;Y zi=`?Y-)T{ACWYx~8My$Rj^GXaOo*LBab&vPl80G%6&FcKDZ)?Tc#}_q9&yP}Jd9KY z|Cp1!G+ccLDCRZYbr%02b0RLOiRgWg`TdWB~D9aERrmhZG~7R>sJ%AGV+}z7VGa?=FLQP zmqE=LTB>8@MCQnNb3j%%AR7z99Ks1*G0I_q-wRrSupH!kia3ZbT-opio+;?n?@&)Z zo!!n;Fh*H6yGD9$3SuzGSOC4g`WrJc7-ZWpn7*MMPh+lU8p>T=jDL+nMNH6`JqW(= zz|mL(tr)4&(`3WQg$sDBGSq=DYBk{NSF8Bv_?P)c{w=Y#SW3`vjjyG5sEXC=aa(rJR9!vz=gQJ3rgO3N72fq;972F;CNhlBBj|hkA zL(hjr5nTky1rno7I zuOkgMjWT`4G~KkpwA*yKvgfKPKZ7T8T>=^3(>EB zLI#(m47Q!$BW1v~-`@BewBoxYjP!4CI-uWQdS7}K=55z8M*U9&Jd7`x?PcF$Tkz|& zwfHXC$n?;36*iWA6zTdbILscdKXSdh9~1V@PqD>hen&~1z4NDc&R&@Mr^q}1`p({W zR=l&~g8!YF@BHYU1Mlp*a2x0vFElWAev853Jmx{?XPjSh{^9de&);*t>-n; z!`}KU9Nymg4>*jK-}-mBcj0~oR}L3`Yrs!gU*qhhiyn1fwSzF}o$nZ#$ zHbb@yVuMjP5AbdDs!hYUucm8V+DvUWMt*(%wAp<;175XBM2k@t(>76%wh<%LSG7ah zVSGL7DEuc0HLWo9VD$Ju{9(jMbl`>WYppP?GWFnXKn&9|6^4-2rggZlhua1B65LL> z7vXlm!5Uz!{J;HCZxDo4nkCj~<3$K|DvK;0|8L=jRko4!hrK0>Nqx78(HHpg@x?v* zzFrNhhXtXp=h0X5Zex=eeG_jQzJWKJ(UP z4&co2A)LZ|gPp|LuhZC%`xE3DA7gHo#t_2Bxp;(Ico{F^CA^f+=2?6Wtj$4u z;bK3&X8049i<6^1oPqS?)MFk_eiq;aVIfWjM%eH0CBt8{5Y8QyezW!i{0ur>@KyiKjVvr7g;NIq>|XlZD5yS&91P2;N{q}7>a#`CwY_& z!&>}ste2;-!h8!>8b8ltY!p`F{)^p;RkJ&}8DBZP!NzkFy9;xKDcr@L;uUNv&tdms z<7qngvd{87*2#0()4Y<+;stCb4qH6L?_~>ln9bqEY!RQv9^fJNAfL(};rFq}`TcA; zTgm6M&+!M)$1LD}HjZm-0S~fDoR(P4tJpKV8eeq$Dfk$;!}h#$Zl;K%$Y{8j!_ewZKPzu+hMulP=WlJDS0 z`Oo<={yIO-f5wmSU-I32KYxvP^Y8Gj{MY=O{1khX&tOZ?LwEBowwBkiFJgAK4nMX0 z66Tcac_aHWZ(}vW>i%JoA%G=m;d=UE@Z)cnNVD>y8!nW|C?CX3O zdw~yU-$0MQm5*TG_9DNXeV316JNX^#ANg4J z5+BFD$H%i>{7&|LK7sA#pJ6}X6WJbq7kimcVte`B>Dng@f-0saatS`H#7_u#c>TyP&0|w zHM3^Xtm2oN4Pyw0__a7CekD$d6XGIz)%V1Ii1YaR;P1pA#J`Jo#lMI*#UI5vj5*#G ze-ghHZ;0RH%Y=Ux7sMs;Z{i*C7jZ@WtN6b7p?F37lh`X>7JI}G@CC!e;%DNB*bQT+ z!TvCw7N_uLL7R`K>a1p=35boGxIoV4B7|XdsA>3MC*JQgnd~-`#boig&6Yq-DC7y5 zJ+MI`o=$i29qIMF)|-BV*NRh3*MBV<2Veg+*;IkHiqAe^%w>f&iKfC%uty>$a5P~y z$a&j18f^_?<30Fuo9#t4iAY`|FH#X{O;p4xEY@%L?Elt}dMXd+Z>lXYn+wFb>#>*T z9C79!ViL-m53AIgN)}_~66M8cl3T?*8@DjXdloa9+Qyk7ZaxSK$_fG?_mcp8 zA-lb}#_WrPW6jByR!G4rrD3)By#54N5)qTS+HbAGruE@Ul1+YAQ&E2U*GfE3P99WW zGPvO7mq|{J+E8AAKd*$9Df@v;o@Kw6!F$X22fZv#6exwVrCpEC}Z0=lCl^^#Ox9XGDRhFnA+)sjkJI$;vr)B}ANCM=MkkDY-G4I781Wu9T- zX7g|}bO6M~xzTW5)QTqHk2QOeEy$DK7eQvi7M};zk1Qp;7E9#ls#Obao!`EyHIZm* zOEk5uYM+0rETtF2W#Af&3rHxvzXOto&jYTHqK7b zFnZ*}`S_v~KICbc8JS{F(&mq8}BG znr=pwd!PUj&mDibebJC1i*9d8rCK`0xwd(u(tjB>Z_p#dlH8LVng$!>u~1J_&>Q?R z53=6#DNmZJUl}U%mII}0Pqmu4X5qrB!RAwIv7r^CL0he|4Iz`96%_>XA?;jOwlfPm zjX6BWZui&V10#AqBOX+P$4og@Z5;XLHcecQo7&+(l5AmTFlS7X` z^y6Vk=p1JF7z2~Ms!}nsG-k3i{p|}1p?4G>w*_{RIw7Ba30Hay-i!Q$M) z*B1VsWFYxU85Btw82y97see$o(LW5NmomEvAEW*iJ@p%kkI`=^ob*EBQZH`8$LLQK zJ@qGwUiBxC3)&*drHCa{jgXV-oAKRmvtT?j0&>RKQ=h)fq(3w0qQWUrDw%lC;z5Z*%Bd#LUN>62ZUfI9Ny-OeJZAk`7; zUq_kh(fe_uc7C;zA5H&|H>Q6h&UGJoMDj9hWQet;nqebhz|B3Fdzdt{toL3cX_;Z= z7Rzu88Oaa}dh*Ch+ED$UY9*VYjj#=$w3I8ZeTu0Zvv40-OVp2Q&!iVIq$e~(FM4gI z!VSF`h#PvLaH@Yruj~SseOH#$^H8dkdMxY%&DCHH+QymC@cQX@0{xP_(7ukZTzLfl z#JThzdGvY=V_|{05)Cm=RBo@kDUtXpnDg7TJr?Z^=^`=0B6`r$0u zwrZrTWZ9u7)8yEa+Hxcnq_nmBprB;>I5&D@H8xPK7`7wQjPZddL4(O>pXEEAeRdH~ zFItqQ6*_@=8xs*ZR`swkckF3IFGNf7%8EkysY|t(K?~^>rTnn1C_LB5eDnrPO}R`a zlgH%tSam1d9wSU`p>VPpivApOMeTIjjY^E| z$!652YN(xRrdd`fmu@U9s0MK#^+V7J)PuAaZfPwb5#K;dL8cw@;pu0e{dQKuuoF9} zn#97iSmdY+<`yVBL6ru*7?@u*p5(0b#@}SVJThMx5@U0_2bH9&2gVAf8D?mAu+VC3t_TJyEP0p(O)HCb_>(vR^lUn=dN!- zUT07|nODjr#%9~_0B-OLW8DZ__1e#;zs~G0bH4w^>wB81xecTtIg)8bUc6uwL|(F4 zEEPeQZL?aij1GBYh%C@vK+}aRqHFhgJe09K>P0=)=2qmd)%PxMNspU4b>(*6^3F4;mhga7>sNK(&yLALDC}-$Qi>ONO6T zw6wO8Heqgqb#e?Jd~KU<%eC8bM{Ro4(Vr(KVU@n7mpe;sm^d}SSsL^^%O$CO?lT#cV-d^}d>!_OL0wb>RkrptURZX{cX zl?1F5d8kxmKl6D#ehlskYx1SJq3n?Dk(r}@>gurCri;!cl*2)DN51k{yhg++Z%{J_ z@+z(m9^Bakt~bf6F;}Dh#*H~gHd;N6H_7xN#xNLNSjrhR-o!xMsBp56ir%n~1L>t5gys=`tJVQ+E2kRqa%?Vgs06il z{1+jbCT2h$m~r96gFYjP#-denl!6Wr^FBF_rIC@GcICG;Tl-DB`1$)|p^gBVGpJ9yxb&8OY;4gD4Lvh3>Y*_!~*C7i_y~gYLrRLu#ozre3*n&r4OfWFo(@mn?a^z*tjgaFqIQr7WC)24wl$L(!42XjIH8M6Xi`(sn2f4(w+?X@nBrS?~A47R$ zH0ulpMuo$z;~|KtsX%ctW5rd)m5~qxTqwt7-l)@F(rX#=6Er&OUA=+D>Tf3=zVpt} z@m8yu2X=n>%RAGT&DPfVm{=?ng0sxKm(RX;UNSb}fx`=bw{TJ{xuBt%&#sEctI{u2 z#i@zXII>Ch%RX9(gyLdCpkWZJFW#t0GkO$PHcgkaeJ&sRaAz!JwFiv(GMUkcC$nrb z?yns*U+!4CG&y`&^HR(^=iGA3oHoo2*9=P}hh8AP!~a#(o35H&nN8e07xO46r3uqv z>dk01X^=J`>GF_=R*%rbV8KYOL-;ajk6*cZ-s6wYTm4we;K41AiF03CJY&YtNGt_Vq6WyX47SS@p?#UWU1tJcp_ ziQqGHEm#~HG=DVRlkP!HO@rF7Q1r@6Ewe`O_7SsM@Jt^aIjfNWSE9W=kv^IjG$?_D zkT0^Ab*KkgSHr9fO$_;x(tr(`OV)*!(jXFj<EpcbdRrx{2bJcoVg+9Ja z#wscxVY&F0udls%{rUN_M3%^_Pw|x_+FFLzR!q8k`ilAUSMrr@iN==d*gca!JLJI* z$3yqkB@?yLmaKgLlFxkhp$9u_8tbcLEsj9`;=6|3NBx1!t1KHv$$iQ;5brHpr7YVZ z_QjOLi&G$2Zezw;Tobd}Og5_ti;HHP$&Axis6`FO)zE(UESM_G47BeDrn*x&nF~WR0o%U@ivAG!oX(KVtfURoMHW+H&Z|Fiw&K_VT=Nc_>D+Q?A#1 zW}Pw8+$tAjBN!^QBoaQp;?BSeQ~XnjeFhEHa;fN z)RbJgeE$6>nwp#A;^L#T(uZd*ZJ9CRm)!{r_+~wtp0a2#{*Z}!$hI1?wX&jAK$9~| zKKdwWvYuV0gE$!aq3YUHq7TA8@7ee|fg-Lh&h&AWQX+` zW#rkd(O#I!+ga39FWy^s#@Yo+D)%uRq%T-I$xJC z_dmHP$K`ZAxN1KpbJ1aU-Z_l_@j5LDqTCpNiw?-aPxEvK_Fuu27DXTk%%Kg<#~j+@ z^Qk$sWDLG}iaU;)VTV+^_l||NW-WWj{5H2{u6f|jwJjF>VG%qn>Zd+~H8t_f)Ow!A zjF&Y{o!XS9a>}%2yLcFR>Fr|&;)X6NoaSVT{uCAACVUKC#Gn#qqiJmtWpig)FoMX4 ztj}zn{_ih;uAHr7Pf%YVz-o)!`n47xe|24_Cp#GzHY9^mF<~_vkbHUIT8#; z{yF{4^fSl9kx2MBWSNijMj$;Wi==``4(DKu#ev=$!l2QgD{IKpo0#W6&U}7}-|6%} z^yC?C=fAn0Xl_niPdB9hPUVw)rJNq?C#Qk9At!~CoD{twr-Af_obE)v?Z~&Chuhr9=Z*(TUrJ zEyBv1rotf*`rUxUxmEkIn1^pc+g=uY&r5PKlH!79 zi*}8LwGXBIOt^#Rji1Q4!1ePPe^a=mA8l#^{nfto5^pxOW$6EI&{N)I8P^iMX0$hH zmrb?|{rd(zrP~Mp&wluj3_Z>V_mWdH;7q5Yp8}lr<7#%1twFk9c)mvUQrXfDGj@8SD za8)j~zLhDLT;ri~DcmR*(W`PP+$fjiFUzHHqg*mSvRvExm5bZ9>dj|9gf&9)f2<#Wg_9fz7n&Lm3DyQVXNx$r zFvdRjLo>HpxV|olZ*^ng(IjoP1*>Q{Nrrd3ohE}Zp+riSWf^K$#+(@RD^6RS_wi}=i`=`VIQ4w^c4;?(-8+DLGGXSzID zcS~(FRtJ5M<<&fp!#*j8pTqEwo(#la2YfU97yWQYhR@-CcrX7W`Z+Tt=c^g|6GV^x zsSW)TGL6&4Eo|ct9USYMy8Po7og)@4!_0~f)BfsPQ9Px(ZMcmVk!jI7iJ1G?lO6HB|Lg9f1yjT&^z zupuqUMk-T1stD&Na>G$KR&g+|rIju;7h@@)$)BG|aLK4ib-`fWq|sXzmPSg8Mou6fpE$lW5-CmpC{nsGRMJvZN_8mfM7F&{ zr2Cp|duRjWP**grq;=Gp``lQ#$9ko)IU=XLCX*VjjZk|d9Ft77Iig0_eS+n_-GI%J zU}-@AK@=II#>E{Jeg8t_P$pE4zjq`%W zl4UGLb*Zw`nn0!feC5go^HyjpwYG+)wzj5*HoUHwhn>8&vmSk*v!iI*I0qd3_cqaa6FM@Mb|y3wWO;$q}AKclDJcL~;{Z z8Ma^6v&NyVO15+-TTVEg2_il2+LwzntbO!YE{l+&qW(uum1 zOt9R3CYoL&?eh72VPB{^TCPsLU^OMvSL#ZWsGtVjtH^x)%H;yudzVsX&D1P)I2ory z#q(Q+J`7b}GPLFM>F^-va9e9~E&dxjlp}fLW1P#qgms-7HZnE5j20mC{Z^b09b3Y+ zF)(9L4O0)a#)SRZ1t<`0ujq{q1A<^MSQD(S%Fm5qGqNbN^hyhswAM<-3Cr_1rcjZ{ zZ}nM`X_aT{+ke#3JoMo)V-^ofP9Iu(OP<}DYTr8VwlBrLG1oWpme$r=Mz#rS$NG^t zlQ?Mpts`f*R+R=OjT?Sn_4LH>`>N8mQG?n>jcOY-3i8zC8vWM#*VGI6K=um7murbX*#o?^W)KpslCH0IJu|y!hAoX^FBBSy4>{meipKD>OWS-b zO1H_)#6HSc^=WicD`~^BS1SW0B{=e2T~dYr<^ca&I1V8}KRvYTh^5_|C@3B2!|AjE zWu7t~dOB9==~x_1fByNEFQdHiL?SMpo^<`!Qdg%+U9FpR_ubdu8k87M`Z`|eE44c} zY*&DVSW~J28cb`EbP5qif>2hAMOG0^FZJxGH(HkTV%sQ;^J677(U8xJWpim|<)XS$ zQoqmKsw<{&r}r$XZnCa$EuS)aZd=>DTW`f4U7*z2g`=oGbpKij( z7@I2j(b!bc8)H+ob`ob%tiBIU;VTuoT&*aKRq7B1sI!gjDJfprhHwx*;*EJjl@KuP zGW8MgC)ZMRlW;R>J5r5aj1@;>EP2cQOFt>~_-%Q)^wS6&T5D<49_{L10T8)vlSw!7 zVa;$x`ct=Eko(^K8K^XlN+XWn6ZTf9Jr@k6Xm8rsb%})Kw1oCt>3{Mq9ny}xag6x!xpTDDJa+dTdx98PmOwZ46+<(I zIO<^J;Bj|g)x|* zGorux+SfYAeRqey;J3f!-+8!t^rS@rf91pJdzGxlqW_9Z+wnlkQ3UIljWgvmVy1&C zWk?C~vuN{a3mGG1#s<@&P2PoeHI%xsNH2v9!%-KqjgW0G74lh9LKdScS`mt1oez6+ zaxX^h%wbnf&Hrw09{HD7Eb98sx2vl)Yu4@U)UmbSo@LD#$k_FBcg>hrSy6RwQ}dD; z4|FYQZc6`bP@;ukvS6~=XCMpQd!@=fZ_Jg&$)9`1@H3{s)W1a68Hx{sM zSm)z>&o*`FP~dl;Ow@|&I6juoW~pgSh|ZSES$c|wB62%*usk;*H4U31MX56I!;U4# zuSIW4F*W7G8ooN8?9Or7NQ0a<`jLS?rB|mc@SBa2IvyvJla^{;TUy^4=vdk4}Vq1wTkY3jzk_^k%XyNH!x zl)X*KJn9g3jiiqK0-i!kH=EI>aQ>gnk?lTA`E5hxyfcIb7y*%_C;5LGX)4&4idn$I zV#BdvgE7V_Q&XW}FvrDNur^pz5sQS%N=u6Ud9I?ILRDc-R9IfW3Jdk~;XH_O;+mSS zCGt$LqOz=zYLfdhuj$7gvwWs5v9fLU2)#;6OUh-{;(7fU5xd^JsIF4ju75OWY9o!e zrR)UtLrSJ6r0k%pn42hFtzeHUx#3_WOp!JVQ|OG!Vuo-Ze(o}O9Ia8A?z5|(Q52;r z@Hsrrptke%ND%GKD5s{1IID<7%Ol}XkVKM?pUNnKWPMVD*?8aK7jIm9AAdYX87eO452h9$SKC?_yLus4VC-! zILL~wi?$He=~{GN@-Y8Iga0dim`YiJFMf)LwKnY+UNmcb$Cv)p?_21%s!lqU)&G1r zQkOgEWI5E4?g3}STj`8=MdVj2SKhUH-HZSD<<)m#`?dRQ`fB>(MbgpFAfaZIMey&- zw94RbKw8LOAegeHo+8To1sY5mQ>;V^4!V9Svz?&Qu4i-6Zk(r zeEn=m`NK#@@|89mJ07s%KSyf)Y`9LdQ2Z6TV$kfnaX~*_et`@*#lE;$85z0)6r+wlIrIFW9>}<O&CJA_YNssMV+VQ@|(ULwG1|$kV4k zeJV=Z-26Y^bMBo?mX_-OSD2a1oqNynJ?DJS_xt%SN00NsL$q5t`2Yp@w6B2%2YKEL zS93D~6>`OstF~{a&fI&iKljj~ZG-z8CK~?pkUBFoF?2e&fY)1%_5>hf_@#AE=Vu{U zNY_p>i30*(Hk%Y;Rq#FM#LJ1gAZ;}D(XS@_3Oueo_9J(FMNz&28}KlHI=Ph0+em3g z6&tDm#5gX--fcOM<#c9k$j&M~u+wM^2Ylrge1zo6zNosHeKODE$y0aIXREqX4z385 zl@^A|Xl`NFa=bi4>U}DWZ|_1lO2^nMqukuzr0?)KQ72X%Ci;TCe>j>9Y7iuKq3xeq z8{7T4b~JDECj}nk%iF{Ev%jIln2s@nZ;$~pG4A*a^XF^qlgi~{ZYY;)b7TIqIu_KA zb(MrM%9Tc*%I5ATvtXQoB6K*PA@vtMtR6ZbZADm)H1dN|j!R!tC^Lkr@af)+aO)b+!8 zKK1RF;!`igP?G)0S2hk@(S7RSSrw+2K;kanb}RR&zlAKjc#GjkLJypdA0v(z#b4KD zj#)iW+^@?V;yTG3;<_$#%)g)Ob(-!|kNj;;XmGH7lRrJ{g+CDzE8y3dLp=s6&Y?au z&!JvGr2=Pjf&`jJy$+PVzSWnPB|Pd^H>B9Rs^M`~Qx&e3?;Hs=rIodS92}wy$eC$x zYDB366CmD1?JnnYSM=xGxU64K<8pwwF6013%ANTIGWGR?5xJ!TiTl(eCOaQa^@cc~ zdMhL)H#v^LXbS@rsWEfc zJGp6P@ao;YwFSHT{a5c^R$HY0`5~u2T=Vk7*+DpC^-t9Ffg<^l1Jx^lH@X|u>wcY5 zS8e{CO{WR(Bj3|VGL@lm#nD8exh-fS=Ju?eqd*G7i{lpZk2gymhNJ}0+Q?W@j09ZR z`VT{LmJJE=IQfhVQow#>!B56*i6*$pLut=}r#o)Oo_g#O?uNJ&2!Kb|RYwBhKviim z#>CI<5`knWb+LGq`IZO`XW^EJx$ft6x?SS%z(5BXAxw=m+z64C$HtBQEj5i0+#0J3 zb8}s;ygb*Z4(7SC^Q>#SkfBvGLEOIwCW!3z0Wv`h`|_D7-v=Ya**yQ>2k0-%`aT}> zQ^_&f1-6D*3ax6P&Pf=+oG91a=PGG9Eie`~Varf)r*k z@wmQltRaS-FSrHUj=;`~0)aT}`~!f7kHhq1hyx|-b?iJi9i$Fub|8VkVoW%8&LntQ z$cXwvKQ_#f4_w%VVg7?PJx)%*Kyj@)FZSa+aY5X$n6%(P@ZBs}z<0}k-+6sqWo7Nu z5tiKBDpwvDEzGZJY$Bi_xVwSyNZvN$EsUg(n5?kix-JK_gFidbmlEl2ku$^H$-3N^|ok{s_SGF@F z!DLXR5>{-6ByEHZFOmr<8cxr%9{*%uAbSu&DKVdaKXYuEP^RRHU};%JadGF!(C)F| z#skJFrNkdBDfESwZQrp&tCu=+^73CV^d`MuX18^d+qr8mNmd|ne+*?kubzW zNyH#);W~mDq;njD&+$q1rSQAo!JSm)09-6N&p*I25G9h28Qpt>*%YmM(!T! zXK9D-zWb2+$Ld3%cU*lgsR6-n36_K#5uT0}4X0fMV!?(sV#Nf;{s~!RD=3Ww7cN*8 z5etA0Y1xICh)tuq2v*^?B4ICz$hKD>emHXX0|Ud8lhg+G)WL0S)rW@q+qNAWH=xObH147+OaN2%13<2t1exC9zoKtq6=F{%bTi(n$jrL@;C;Gi_;B zxPLRGu2O`qK8Fl%jIP=HgBU5~eJ9VqzmOh1;GN0931V>xs1uz365|rSdF2$$U)56| zf`NYrTc?(0QX#RCwpO}B0uZ37S&?lHEN7pCj-!JqD z#_Jt}KCxI_0v}nn4Sl?{xCEx|tSJ1@uHB-eJ%0JlzT)OY_3Vzz<2wuySZgQO6SQ{f zdIE1J=?U6(T~FY$1n8qk9$2oXqSkyQ?Xoc==exu`Lo{V!%|coY3tXlw#@%4H2)`9; z1Cqp6OjDpzBoO0H*NP0bmzJElRP72 z23%(}OTb@%$r69@6MeIojHq0rO#50o*EuX^%Zj-(a+#ET$H_Oci1cUP?>OIaKKSw|*zyH@$7w5A5bi>HqvkuVNpOq$ zjtf{pt^}Yk-*p^{qxvr~RT}Fe+i*Y8t4tX-Ft-{C1-pZPlRzPRfE9%J`+DUZbN z|9#x`WaZh|^*_X}qdlC@5$$C~=UbvZ6mw7Qk(J-X?x%WZufIFz`d4E2|M#5hr()NC z8@oQYUY(B$-(XrJFX7Hv`Uw1m=p(K_7w3DN#&veOE-PGT=i{Z*OF50VzsXng z@tW-~VGfJ+S6tW2q4tRRATWxB{Swy~_Dft}*e`K?VZWBl=@->o&*zE4uj})F{`Gi& zhkBv|qx%)aLCpG!*VB0M>+$1NEEA5V->>^%_we-+#C6&~XXzvMPbyzr*UP8tW4xW7 zIqjT(U2mtjPUX=3v+WeushxCvww=@-+Mo6N^>!BGdL?8LAD9)s+ z_26rZ9ti1 zbm~NznM9BDM3F;>2mUq95B!6dG*6xr3;fgK3jGQN{_R?`tK4j|RgXq231&;>*t%;g zZ3&hzYWz}F_f>2qK31%#Vk@WLV5_fKR;hkL(16#^=hJ3fnAPX!U)TAjxK8q(xc>}w z1GJofzb@|uQ79U*NbDc%Gqag`E*|?AeBp_Yu1-!$N?!BPC;siWElDZK$s^3otT#FG z@-mN;UF{x6e!k-#(38(c)R!pvqWL6@X`z2&kXk4}3%@0b&_8EgEIN}t+W*SUJ1|Pg zTW(ONPybN1sP7cEcZ8S=5t^}iPW({JXPrNY`9FJIr<1r&bQ1ULbeey^PA9YrnZ(IA zA-Ev+kITin5p9v!Kdaiw^3~rW_V1^x>=0Xa;E?((wV9X6#}<2S+}O^)&T)p={S0ju z_kYt$gDZSet&joIC9edXNOMXB7XXT4z_JC<#sEr*q$9h5odJg#)*&>byfOm{e}<=s zd!3s2f?nWtI@g(x-G0?o3Jf)?fdn?gnGd*G1$X3wZw^3Az-C+Fb2=9`LpvWumg>(# zFQ+(^TVio2RFPwI*iwRJ|C{g1L<+x5F-BiNe(uNQUvS?LA!o`+0I{1aHB1?fC4N%a z%K3Sp5}hS>;rvgo{tIkWf9~pPXu!+m^}6WAz8(#?M%vmUt>I{cC%ZK}KOb)%ns0tg z7Sb1F5#Pt>pPrkSyxFy!ypY`aWOl(-1$q*+X$41R^v}s4lI(^1nVGVxP|`}Eag;?w zBta$6#L#V*{18H40^X$n>7ry74T#hy}MM46no@#n~n&fs5xb;M&T_41Sk?vv_Bb=cDIKnhCEuvyJH zbC`r)M*tbnU;%LkGGWH zqlk^?g|??z%{(hKcrj0LsioMXlrdNCKQMk^xL{*&c;AkL`?nS<8U#0nhao4fUk&2Ax|mNfl&e*^C%Sde33B{ z&G$(CK09DKgi%<`dE^2hTs1i$$+6*htd2Wwaz8XPdt*iG;oki>o2HDNHT6rEG;ZHk z)81BB)n+(qI=*NBhdUbCm0Op#kJVEKn3l?_#zQUTWs&OAEeA)|wr-#kmzINW6uXiv zAqPSdRA6n-a)})>hi_V1E<3&^fCZRAE2JH%Q-U@ugb1M2WRt_k_=BV+cKdWY@!tsZTq{DrdA&f7|Bn&KlkQb&Enm{TmO%wtHI*z@o^ z&p|TVD40#eBA;DvRQ>m+*z;3oD3n3oi8i7zgq?hN8iiwJH<=O z(kLEaF`hSH-f(fDPz;@rH_S)|5hrhaq35-m57?}h)Ezgf=bw}F)fcLonk!icC4@jd zoR{*kJC1s^vBMTyIP)9G+u#jSz7&%7^_;R{T;R#Z`HoSNjEBJR6BzJg(;lQvp}mc= zr*KzWzPE)0D>6#P;}^XXFi9wgoyG=y)ybD=UCgl^tco}?Um*^Ex)x+a78HUb{?@Xk z8j%-!v=2i?U0uaCdMm3PZF{?YTWwbtvOT@sHd@;yACA_(UES7J{dR3s&|l9LA>%9* zqHi-gZvnmS5^_POFpML#1Z49A6G0YY18i4(sH$_y1DWU}bCc{2Tq?wJoTJ=c$EgBdsBn+a=sPwql>iVU!;g z{MuwjR(^6>;>^kxky|Fz8>T)#)pOzmCenTK`1E~fV;JqG^Q97{{HR-FeN>{f72V?7 zjb4`5iv;+3SwS_K?cKI<+a44(C0|c3+>i3A(7rB|mr8m9Z=Z=4ANEk}3JQyFA=*eB zEWV9YzTCB6T|c&EXk>dMZ|O1l*ff9f^8075=W9Jt>gqW~?hje0l}U#L6=)%CCGa0q zAde(8==j24hNB5{NCXd0{vW0Kd0^_y)B{sf=+-CYot)@%bFPuh0xb~Cc^vI=37271 z$@$BiTyKfUPD*Quiq_?Vkj9`L{ZkX&2U=PVbj!8v?X^hCufA0~2C1O#Kyy{N`Awpw zJaZ0jw8km7S-f0=ZV8FtQsffgf{WCD<_3!GSjkc*<@Y7e6o6N3A6p@5*wWLBTQR;en)tpXLx+)2DWS8 zKK0(B^710^PxY;x!;Ql`K>k}QOX^E20qDvze+PeG0XjG#*B=rb-iw7ryEPfIbSZ;2 zY-N$Ro8Df%4@sW&A!5r-)$UgsFdyMTbl>*pM162x8>ki zalytw@!!!esSIz_uXOl{L2d$%07>D_x|?5$xsF^O_z-~L zX-mWs7gLvyF{d$FXPa$W*{);bXx7N;>yI(TA3~Er<6p8|^_N=)w+(J)o7K;Z6a_|u zML0H0dob`7s;@f|C@?6meui-Y-gcC=)K6kNIFWGbkC% zLkEQ5sXWSsjCjukbFOv-0PKQM7IJ7&0ofa#QY|ylyind)$dZK2{5uZfc?8x(n)=}FaLuu)xw2XS( z8YgU>lxz&ins~G7!=hP{Iy6;e7!wVvkL*6QBZBDI?U&P@26pky)wl7)iF6Xp4=&=5-Hlfl9#1If;Q zM;(ucCcz7S6K1n**Df}6ICbej&&H{MzoH^2k55dzl-jjtz4|KiRaS*V>W?uWD?!s* z(A0rl{eW<7@S<|btRM!*WRejEL!6v+P)6%aW3xCYLc524#f=6EgaOI%JHJ^hG7n#5 zna~3>{h7Yp98{A*?M|mfW*`$N+KYLtc_Gq>%ypS!E$dh8-MeC_XK-X>&{I(0p_g_0 z+WXXJF{!_QeC_t_Yt`%4ZXYcPvRi^BB|-H_utZ;HR*jFsOBxzKWDBDl%hgFT5bsNB zDjl-|ql+zQMx)ZTAWo8CA>^|f;bjr|YU{G5eaqjO8b10FwskkDb*$RCzZ((*gj${# z@~6$!5y-4GrobI(Og*%Q9PsN+f^2{>g`G_{G17Y#t{8!EnmlSo-U+ig&rEB`Bjs}v z1-vv*jocWok15&|Kh~rLu=HqYm=Ew&Y{2V9a%N@_!#q3k@5b^oW1r;uo6PfNC^1@R1%xv+{ZH27?iiF)Vf`3UKB-^Kx7n_EgBv$kJ>v&(G4V=nijo zf)Z%Sf8#@xtL>^i``-X=Qvd0Cd{~d{U=3qOj*O|_8M}g=-bC3*)y>T{`}Wm9z@oJz za!XXAMNi{`zmEBYx$|!~eC(?Jp3X@9 zYc=)tHQn@v#X+*V3z*plo&DqHKm*4dAbBl3`$setEyX$!I^7~?|GW`BI6D28k^K$f zH?9hlmIe&Pl~qMYSz1Tciq&#(U};%f;umfz$j=X`_XObha2w|6htR%sv~LdH;6|&3 z02qfiyf?)@ss2(93cP_o$8o#Q&{$|W4)pRqCh!Kz6gdxX(2JgrH|WXcp9wu{kAd|N8YuhK(GwMV%aZ<#f9l`VUrtYx9f?UbGw);0l_B~He5dr1 zFa~ct#t^nwdkgRu!x;QnA8}4kvmgVQB1ajtDal^%m>8|ua3pEU91hk{Fu~Ce1qurT z75*yIRMO;7#g+ptH7sjs{fhOgmuI^J`JRF?Uv4h^FPE=f*;UbI5p=^UV2``t|_XS=b$Kz3-lVcV%hv#D!d=)H;S>Ts{ z1hc?=_B*qeVmjY8bvJ~x^~S>!tP8aG@=x{wAY>Y55rjxqp0!FY)$DND7k43<#2zPg;Cs@+k@19IveRy4p9Q;93RaNsV({F6K{!NIS?w3Ed z1$FAPv zMhYc|Z_H38pJ(qq&z_u^Kv}xIM`qi3*|YYQuf)wmczZ%#p!|aA;Jto{GJFe>3t%eT zf*Av|fOH@tk^x^Cjd=(__YwX<{|ksx+sHWj!FRTW*vH?u?msZHi@zDk1A7a-9T@rO z$o|u(4{Z79$br-HmG$9|h8r62MzO>|d!UO_EZ8=*mr1SoZo>C;iO0F&`W&nSL=jj= z22RlJ zgwVvcUMkiRRQuSIDx^_OpCy}{j&;za`v!TDGRw()-s>jzMn?rC=RFwV!>N8+s9Ysg1K0BRd!CaL>g$l7OPy(-PB@ z(qU&#h+!RM10i85hIFi&x^3*x;cceUii*;Y$idUwht_UC5Gp7L-Hkp%1J~+H!Vb9z z#vw$anC)dgz@ph+fN?CS_t>%Nt+9$XVim^MtYaHwBiGRtH4FuUCgQxYZ}TFEG4o_ja`3C$*RCGLhSXG2SXBOb>NdQG&T-k` zVz>sRnGeJ@^n3m}T!Z(3hK!UPY#FoQ0ZVt~!$?|MwqoOk6?cIBtnFrZ=6Jn1WybNk z3by6hb<66Pu4dn!kv1O~%XihgyyJJ&F)`re4gRg>eTEgrl}_wARtZy4el!Pi~j>z9OU zcs~f&&|-W<)R^$b*Ah^0Tw{BkGG(}Wd=FAujbCk;QtB!qb&o#|Y=iPw#bvazjb(CZ z1AL`}V;g$=J_y^GBetQ#z+L8I8$!3y?L)KJ2AA4k&p^X)A~_lsriO(p>Xf5~YsTS@ zr>EPhk7SP7B6T9+7U;PG<4ZnNGWk&9yYRJw?8~KInR{H(bv=$-##!LRFY%A(ZpbCS z>1-h6q^TL;s%(L!i`-Kg(hpI>4VS)W;|U6(q<;t=wF-Ne25a!#5t`Q{AP=6|j6J?} zipNj(_fOq-ALWr~W`98*iC3A2hfn@Wu0l?oHOpX}N9#*K=P>5MMezFsA7z-MmvvKb-|yt$ub203+|={C z-c6ee{lD`S7W#hYFC_BvJS*g%LAv{5SchB3I#N3wF}D6td3AO9ckSz%hqg_>@!WGP|G|=ApqR!LG7IJ82uMq!Q7@0Y z1bT%7SQBtAqLm>SPf3s|{SrV9LS8{A4kP4|G>E5=MTrJD;0+|g!m6Qh7+DQq6$Z;Qg|NBI#ZzoI$v2z4_3y(|@@4=W9|^Qc?lR2lulBoxQzF z@E~YOd_DxcFeH6K*kVesoM9G5&=h5S!!Q?8>B?4wvMWjv0Sd~9_#61VaG%DJPX?F+ z;~ydPTYrXdd@sMnXh3vBtk}8tAZ&*`CqhyPegn2pAsotbOR#y=U{c4E)9fJUg4thFG(e|a1@F{D}@MbFqH9=+BE#vWP!aJ z@F{wP{?Q*KaEJVe4|DcTMJkb3=g_v+-efb7p0v}zOCO=*mf3psk+I2eL5K5m=uZmd zk>z~fMjQ=?CO9;SdSw}-M>(S?yrq=lXzXcmaWv^H-D2S(Y68U-z$BW2>}R1hW1qTe z1)lfi=NfmeJx{XcGA(3a?a_*P;FbbN2_UD%lrdK(Aa~p{|mj zoSu|VU^R4N56HkAwqf5aljy{717ttgItV-DT&fid7os)?I}z{U@!0K>kGlAKbt}!f)@o!6&UEJFIIXqY zI#^@Jwpx6u-^Kw*`L@%geEge=6`wo3k}2jT)48EIn( zj3tdx+KQVH`vgkq$@n95ZX%R;xlk>A;cJiFdEWsxecKHmn!XcvVjcjw=60WK(myff zc%lr@iYdqNyG+&|sE@P~dK9w-Vow$$#rx_WEdk`H&^b%?it%Rp~PdBjU|Tn^NRKH&|$ zI|l3=dK;aSq@RPMjTK@tZFC~JpN_!^2*()x9L^9m2`AyuK?D@gC#N;;S=P0u@n}PJ zq>y}yX~XVO*$}Qhbl2WP_aq)10$tkCc?EP%l!~JvGor_N zlkkHAM?@_SS$BW5+3hxwpb3ki2J?}GSy`DBRi?SDa+QbTK2Pjqsz7*-Z2y`stqp-*SMVQ zjzd54!zDwiLtDLQs{>U+U$e$$2NJ04K|?4zCRqf@B92fIZtn4BdfYBNkx}5J)6X)w zs8I*^Ddbq-I~@&FY|4Ob{PJLnFaHa-zHZ9sEN&dCY&qCn*V$P|FDw48J{)FF^>IVK zr*3CQ*WRW*%Nm-NEo*96M)QXJFvt(bBCU9eSTdztAqd(DgaX7wz$sJ2$-ZbTRs~J~ z62DOF+}xiu7@8ji#E?E0^M-kkGs|zM*stWBdwzO^+#3Wv$d@xDMTmdoB!fxO^5Z5G zc$pIKK+(iT^dNevzp&8lE{qh0%S+uMcd(=shaGV{gg#|)*eIuxP9Nlgq0I`c9V2Pi zR~N3ZH>ctFLcut7C`9mtayyMp~EbPDxqc z*f?BOHH`OUW^9O5H#Jp98m52T-p~+@HZ)+8gM?BY-n`(8+Bk!F+^%B4Wdl#Jq;-nM z8KF&c#@P`p;HTWBhcK?>jH635-~vi~;~$9|VRQjwUa5^UTN4{zG2-ffvny$gX{?Fv z0)MBoLmf~NuyeSw?KXJBQqm9XzA#lXmd_Cj&ctE%$;_mkBQrPCo#IHz;8rR#tW>kc zUouRakU9{QClVve1;~4sew=8->#QLh;qT&<3o`wZWqMCz^*ir^fbWP;=sma2+{|8L zOW|z`y$5)F!V{AG%=ENKM~C0c4B|sP_9` z@cW;s|H?Dde~$J47Ciql?;lQ5m`@+fy<6F>>?LJ6hG@>Yu&d&7*sNZ7F6;;R2mfMd zXvGS={6$56da+vvyY5&zIJoqVuEE|y-yObC2yalLm~U+J2ho z!g2M$GR1z%UQ@q*!`0gkjI+CXnSK3+ZuK)%-W@3K74#?T!u#o}+5L1S-%qpPOhfxA zDo9_jpMtrY%@N1HJeuJQdOX23Y|YYTYsy2H|B&6a`HGa{OiEbRG0@);Sm7Gzq4mt9 z4}+%9pieGj&~Q2U*41+=0gMp`iqfV^c@ETT@&)b0_(|J`?M;5%Fs9!s9lSYK^x$tCXP+}qAK-|a4@K3J&;lrE1baXAdYyJ3d|DLv0lzR;H zcoya6(w>`hNqg>`zM>S4Oi+Id5)!q5c@nuPNrT|iWEkTndbp5GE?u{*eXVJYA(V>~ zMm@HC%W9VKwd^MO(<@p!`rGR=b3lwxO7eTXR}~Zg6>Fds{)Bn7&!%v#8mtZDAY-Ft zY>;oX`Us_`BOhvBdM>03>VLeIFAHv&j?09Qqi>;#;6u{N9?e$leMuniDM5{+hP78h^Fr9*;W@_qjdl*8(07=C&t*vAhLcdKvxD z#uBNxwY`)!RZ3PU#3k?mPPVyYS&$hUOEV5Qo(<0cAi$+M4i#Na1;Jy4#QT1!@?AK z7cAjsM11W@M1o3Y9l{5W^MC`1l?~)X!|~5pk6p?`!%;Zq+lp;PK94u23fjmtJK4hRg)V=KkJ-L2oPOdZ3)>PRZF0`77o`Oas8>b;> z*Je(mRf0x2S+W(Wg|$v5z;KpeitD7w%1lHMpr2ODE{OOz=Y+v*JAgj9ZO8;e5eOIW zU^>{U&h*&VSE#qGcech=qbpyDZhG`9X&5U98c*$Z zf!7qXOfkAn8#alepp3j(z3@Y0Jp$rspWQAfElwhsL89dtxm3YL%`grsNQv-wmMlqT z%TNkSlC0(=>n^0AKs4s=K@BYpu!9X1|NKokxC%Fj5gf@%$#I1pi`LA z64wOUNq~u|eUxxFKFO{L4P~79>J3%qd}F>qMP)%oL1jfCBlZ`&xw0ToQ4uJpR1eTQ zedFuwB>HkIZ-b32qm<<$b6!K}C_)5L=0wxcV<_$0XMd-Rcc};(iKT3&Jf-s*N1dK+ zQW?5G+nDLsi4vrPM4$@?q*I(0MH0?~<%vAWHPWoWmPpw_+-_1j#8Ksxc-E1QyOW@# zbN4tJI1LSgugnF%t@e0aWOn=Pyicpw9d&yz_{M4V`ph|Y3~Sp6doCh2agBq$JZr0P z=!EuWHl-w*(#>`|5R0smfM3C6o;N@nHf7$tIzJf9&%>MNzuEXLw@g14$Q8M(F)v<6 z-rHkx33`_Jof_kt}u> z;jetXfv)h##figWp7)%hYNe%_su2}PM?B+u3>z&gQkSv+-io(pH9 zI5JYasb0Z&5*98Qq36S|!>M`6Le5`JqsWIc4`evF;h-`ISfcta_PGGYv>-PxF#RPU zZNrzz8(yp`Ev>p7;{pBnMQMiJ&Ypn&c@~XHApH^gZi3Js!KfaS{)c^m{Q;X#UNi@H zFliVMGH^#gJaD+e57q(&ORSdLv;=4T^YY~l4S4;J_-H`T8J@w8HzYU8+oVF`X4zR8 z$q5!@Z(+Rwug3t}cQ-G7h!>B$*-Cc~@x@F`fx6nd5RP`KgC|rS7B7N$%sAn;&S{3w z0npuyGue^%(fq=uBHxg_t6@o~qj9Cte_4TXP2G}EXT!KW`8-3>rm8lR z?8w@amDyp~hjzA(D@!u))ghZY%A2B1?scL46wm;(k=%-TPnp%bqg^RhSx!#Gwji@{ z!k6m%(oz!?f@TrFNXeg)FrI+9+-`Ti8+pESC=nHgGM$GsLcXx2*&sAUAXwH!_zQ{x zDGLrif8lyME44IZ^Y#zVvlh`(tP1Mw&l75%H#UIITyIsh(tNio0;^vnd{lF zl|K03n4UxgbhyL_yorQ-2SGVViN}6@@57JYH9S5(JXQF;JHB@NE1|11ZyW#Gj*ntx zqa71?{t4~*R12Ob>>7e%s76@e%oFtJU5`F|FLQi!$JfSh%e*@D%I#mf<9lRo#q$F* zABT9`3Jqmhv@_QLa17Q0gTpEV_v^))g@|tiUWV1i!#N3?8YaF35S_^@ndI}}^o>k= zT57W7XMRfb$9yKIGt&gY0?8!EgaWOluC7)%T!unH?DY=z<3OQoH8fOLHyEs}upqK@ z?M|cBwEl`K$_f>$VQk$}`Fs9?`@@}9YiHzsZ(wqC<-MyXii$opxMgB~yAdhxamYr+ zjSY&C({R?h`DHegA`>fe+Z%SyX$b1@+{7@H29b>SI6Eo=L(py_fJz z@ATJYy`JWoBeHwuH|URt%l{YX6G_5G>X%#fu(xl4sfPSKG+M|qtg!g5%bGGn1RHo( z>&#ZUfXkj{sanWu$=otE0>&le&vbmJF^F9z5mbboW4}d&O9cKOk;nEAK5#i+kVVz+ z3VF1e{fF#QENHh`%8j}fDMlYmDu7niFpxG{A?I9rD9gUOZ56UinvyRjfb6s1@@5G@8m<;ekVge zD}?Ti>}}ZCAj2gh^aiPDSnnd8wL^w8fMNlE2wdFA-`U{JPoBQ%8TJ_eDE#9e|M0(f zVO;)+a_jtbkeQRe-+%n6r*D3;jlZK*{*SM*6H+1jK5sAiS%RYo1LB+|X6K|3&1By{ z{?)Hu_YBGao=xlIA9$Xc!|c~R`)|j;TKIqe1iTyl`Y`(<`w5qM6D3bHH%2d-ZES?5 zSUt`r*C>S(5U_VU`TL)CBp>|&UhIpv-+%w@c%eA7=eH;iXCQ+9(8KipP*La@l_W-$ zm%ujfOh)zesH|cgvcK?kc+t5DpJ4?{ik4NG+Q7aed+8l(c@kU$eC-_gN30y~Ye2Sn z)IY>+^f-HgNX)q(_#cJ+66c}f=rw(dTFibd*UY~2l116<+@~c!;^+=RZilxgC<6lu zEP0eq97P`Ie1C}e{#N4qgJ=%f+ilO)VaZ1ZuEW|>h379(;=_U4e_8rM1 z9eLWBNvATxuuJjZki%#-9H!&A800PZ4=PCOqvfv^cXdWsC0jELu`<3?G?j1hMKrafM_t1u_KusKw=%%Qf+iq)Iz z!qLymu$=HX(~)cF~#?zErX@Pylc00-PXP0D;U$fr5++iO|C?B1kg$2&wNI z#)fYBP#`2*k-Mn6L9xn(1s}R)-K;Fhel#WD4xw=|VRHHbN) z@P60iGm68*Iv^XkKKm^eK}oaej>^1J2B-*J!e&Urw3+21dywU-5}l zarRiM*t`|B0(ws?+MEv!AtY^$ZpNZdK<~$~YcNWU0JVD@AS#g2?SM;@Tz%;_!ca#t zQj?X$L=o(OaCUgNX{R-zC_1nyrpbYZ;-3S3!%397-UedzKFBYlNaF zx5lY+>YGBmWji$eZh9lD_Nkv|x1I2vkP9^ZPW>-&q?gfv1euWX}Z2zx*P3M*Yojkqw+2Z4sT}V?OgfwD1*2fdoXUej&OxY0~`8pM6DK5Dno$rr9PELUPDX*p>~)265H zD6o=eOdNU^z-Bl9yZWI>q^JllrF!}eIe7jvbH1`0DhrDvk>bKi^Mty&s<1d5E-tKU zLM)tm1Z(FJtR0_xPSw z^HMyOge=nr2@~_YTvR&D7e$VQ?tC3yk!RWL{$!BaT`!SxI& zi*wL*(bd`L6P)mnp#-y^hEy;svjrXmRx^Ys$gpITKtuxZnIz&f3H@G=Bnd`BOM-w> zouMROh@sHJV2EcVy3AoH>fM5A=oSF^UA2yVclwO0z{t)`pZVj33>sNYT~a87W; z08AeRXgmVN?gmb6gT(EZjpwe!rI2jIx_A)#t?@r;Rm7cl3R$EcxxkJ|MN+jCiB?9! zRpkQNwvr}ZT~Z8io0+9<=w6ymJ_?BqG0-ko0p%79Tq3q@GRLt~b?>H@g0%p;M}wm= zpnEj5jRV~+n_2*Ln-hH*?9?ppY={m=T8*;9x5vj(@0~k2>W!})^&XB61N9EB%iS=` zG>I0|f7h^NqQM(8r$7S;mD=8ZiVI%K^cf8o4uA^f(#Gg|Fd7T2>WEAWFv|*X!S2)~ zh|{Dx1K^|s8(e*9Dan*{a3m3jr<-GuG9|@jrN!kX|pwCOEW$F@Fj*V7^1~faM z1=HF<_{=;o-HZ9bAar@LglgEY2~fWF!A$kx`Di-6v)t?luReb-$z2xpr#`T;A8Kgz zQQrfHV=(*EPainxnf}_DGtXgWqpt#kj)04{OSeUDH85);cG(0O2OK&w94zCQl;%X< z!!#h#n9>Q3jB!YruynwkCV?~xs8GrdA$|gkR;<>N@?&mh!rt=1$8+WW*dVUfFe8(3(nE_&s$?pad?fdx(SK6LFN>2lYm6 zKXWED8RE1QIJ1z~cSN^kuvAI1rb;BRTPU!XSrh_{0pD*=W{HWeKAfnbBqUfzQj%n= zm7-*Mv&Tkr2jr+>ubw=qzN_B1SjR)tXGEXZ zE3Lq~Cs;vT7m*zJWiGHArq5u>-wa+!{>mkoFMFfAq!jego|3vdO*1B?(=KSwWa*MU zou9pnKHB1}N^Wjd!X{Z-N`B0xb)~i8%EFRDOb7C2hDl?gmC1>Idgm{UI4e^@ys!aP zC0^K2`{PVb=zPrlB;Tcp9McmpM6uVkZ*weC|D%f`rDx8lkLhv<_yuTzeFMjhffimV zAk|0@3n8~Ao(>svJ1(W97)MggI71FQ2OPCV9QGvWxS&N&wBZt;BasP+YXSW%w*I2CW!SVz<4Vq^qZLAWmn81Ke! z?I99=G$+5x59b$VD)Y=CV}vaJ*MWp(Zl}N8ot0puor!qQImFWb3Oh}`)Wr@8b)yq| zvk{mKL3k!iru0PFXtltFo$`e#O3H}So~ERvxcV}ZV5CeQv8Bq%$?m=^=5UxtGKu-- zaFv5F!lhIeGV&h#|63U8FEHm`xUe)vO+x$Zqkxp{`4?|p6yAFObXgQqn#22>IVxpl z=43*z8ID#qHb_!?YeQG#(t5C1+P$kPE6V5cS=xId1zwvY8&d(#k}t5suj@KK7(M+I z;{hCCv8{+7o(6Wpgi#zY7I~epu_Ms~8xJb2Egj3E(ParUlJDTrMYzmS>n#VTchTl_ zL(H--|EhNTl940w`ft~T!*#V))s{GO@Y7EZkE?}e)F*U%KVNrmgK`w3Q6g0!W};5I zB0A~H%1klH;H0SlNiky*6U;ao*VUJolbh{!CYwo4O|k;iF&Nx^8JInM%~j*zpwiOn zx@u6XGD3n*X+ELZzXSK(W8bTbOT4Br2t)5S>6Mcb^+n&42|L+^M&B`SQ-rXtcSxcc6Em zzi)ZZ@}3ploy$6xbuEo9i7x4AZ*FUD;He9=chjDqETJ${I2 zzZ$l?jW8{5hXMT6=nuk`<)y`i!Ay%Bz%lYP8Wuhln6xw+7!FroW2B*~zM`(Iwxp)0 zI^;;D5y5ojV`8B(p|Qlcz+)7nGB&n#>!wZX*RNSKIM~}eHa<4KbH~>0Teoi;-88ak zWXtCD8`p2#FuZ1H&Ct5FgR2KuuUaWa4@0*c1J{XRi(=54A(H*0qt>+8sOc!->{QnV zl$Xo|Rkua39&=Ie`~5Xn&}&&v||&7*&0KdOAU z`ZxHPf;akOTKiNNEHN><^9GwP#t%3jbS@J?M;AUdgwpPeZkHefr4P}DZck2B_%M@u z4R$O|9!Ryo(+uMYfxs}Lf8jg3gb+~@iamZ0B)#$wF%Wa4<_UR+C4}Hvqtg|xO58cO*47&+pKE{y&1^;iS zJ;~K)OG^afHI2}AZ9?~WZr8lLJZ~N}pG8FyJGx@@rY}@`bS~;;;GMGX`~wYhe1|Z; zd>IGiKcAmM7j9wE*l?i(udv?;z9^sDGjoLfTA3XOVkJ3Ca0bMEZqEe8*d|j3N=J|7@1m zQzx!Jte#gMSHI42tN(}foc(j9vE}T2sAu!MdQ>a=^E31(Me;{I$yPd+5%I4)ke*35 z0@CEU`{|$n1(2i>N6ayYjyfo+m2JMhU9MH5%;zsD^{Gq#4r9%i)c2=ez5IBp$&~iW z^bX)gu)(nZKpj5W@)TrNQ1{Oui+t`~T>mkyE5KLb1%;gzH4kAOz{F-o!~$F?;mbA7 zEpQ_^Q4h@;K(vu$NHypuD5AZ%O~DBUym2g^zUSUWch%1r_f6X?mAv!Mi0-3-=hw*l zVzh=W!GLyr7VW^epL-E4)5afb&jOKvo%dn>ALn-S2H`!EB0N8&F+#A&%h^d|ePg^o z$W}Zk4aQl_!AM~7^RnY-@0TZz$`iL7Qq1Sc8pAN}bl;!FeVLF5gFFdj_qi7h_2lz| zqNip)3ctGwAZ$3d6Os9d4hP-_3c+zeI+70OC_Ds5VmMN>U++7Aey95G1iR}h_6fC& zKRrX3n<@Eh$ZQ}ZJ0qRD$pD!_%qIkBEmZ{k zR{>Pek-8x>oCj^dcG#Ny0(nXqU*EoE|NbrQ>&J0|YeD|U3WLGIaZ9Oh&8~zSuD48V zx@FUZ<@y^EcCGQ1TK-g8V_};tSg^dU#nK3Jy6r|=L(-CHl-&IgBAlH{h7bhLkkcTX z1rcz;Rv=PF>c*_YG{rF>XzwWK(k_$#ric?ZQk<|E-lYgf2$@6hJgtT+suO*S4+Ay}hfev2oe5Mi!{V9l5#qy&~Eg z!5!{={2p$+b{YS@aTztF0kqCnR)NyRQdo*cTS9rx3^cten4gTcmf$D|_#?q>kTw(^ zo{#A$5z!@1bn?!VrGX5$AbNpD?@$C3u7R5@xoc{4hvTKg38hBDFQ9!oj|vZkk2B&|HtaQY&ENiBz?sUHqm9O39=_ z`(?^Fo|{X`U2bu1Q4q=daPE*TEt$@F#Lh{1czE=*78c-)glgbSi5voRas)YPU0VD5 zuR^M$7CMHg1;-E>#>c0gfNoS3j#U0et!FRFH?C0=k@M)X5&zc9T57K^TeEihTXo?q zII%~vBDH5X(LAO6AMf#8#bwe^bPf5Nn~cgac#|Z;ngE{(c&Nz?-jXmQz@PwGhmt}Y zDAzL_>=P4{VXaF}Zc8T1Eb@*P7li^o$PAQan-ctFq@rgO`p>&TT@g{?{P=28ThVF$ z5*`&;mHxl)Yg&STP5YPay3TM^ZVuP9oW#H8nn<&J)OhV~Yu#8!=IZsUGdsrWCPyo3 z8zcN%q_MVQ+XVVUd5P$312>`Ih@C{V8R<@FFn2aWtC6=FF7#$|3(vOfmU3`HFYx!nzWy2h_I zepqg-sH)G%%u0OtBYpJ~6F0fLyoqVMQdvQLQT_VxflWo#;o_X2J1>2gy>V3rss{~2 zXnz%G=p>t&=1h?$j!#x#>PL65>%_t=MEIaBTO=Cs*C6_izeE^pox|+Fi;W-o9RB?u z+4_Iz^ZwFprK`)fm!6)OxCoSo*V^l9cCm=4a(2`xTs!0F&TfGAI|(XPuTg8Ame_q0+T54#&1W{CoSr zb=Mx;{`%|NYnq#Dwtr+t*Dlt2cy#n|*N%^{`x|hsH7qSe6VxKpc}Fht`U0+=zD|~tHwZKBY;;H+5)2|wS|VwXdr)W@>nzpk45g%NBqn}x{Ur}Z>(4UhyE9|r`&TA zXfMjwqYhC%O%19qK8S6$d@st?|C1$k1#7@LbeZaR*wY7obl~{|RJJ1J&U{O5QIfDv z`w$QEv8c5Wg{IIZjr|}I`$44yB-Ko|TjOyHGbKRWF-d!p;Ow7B8y^v!%&J())Ik_k zbD|Y^#4HTAm-GZbo~^*|_PLQS$!06G(-cliHes>|&mJ0}5VUT6z__k%tmE9tNqNf`;ua9(W+T_lX{WtBpX+N8~Eqh-#4dgY~ z{N*M-mY!;uJ3wNc7PS7FqYe3Boj5|AGS{Q?y3PUPxrE&jfmU;s4z#(@=Edbp<^duA-547O;yB-TP5b%7?AS6$b)dDrL0q+E~XJ& zLm3Dezfgv0p^U=8!)REUj_3@R)1IH6HygJ|N7u)r{K)o5sg`(*~9v6kwJ+EHI zZhsAb<-76=3q9=n6n{my?6Zf|GX;5CBp;3aD$RF_*0C>|Kf8+Ib4Zh0hH9zEv1kkO zHm0>JVjXzA!>tqQy%Z?HkOT5tvI%P{U83At{Pb(mSFHf~(Pw6s;5o`?4?eJsV3>r` zii0-!>ZP|tn*Fm7ln7Kp`XJf#xzbn9E+1IF_m0vR-q|#;2`cP$6F>YR-dKu?+$E^L z7q;*uc_X~;Oiv=gB;tE!J%DTaC9CH~jpsK6e;!z|c_!G^_7%$J!XB0JRbFY=}K$d;!jPM@B5iq=|K z+5ow&Q3}e}NMk@juE7dCDA^DNcxsG1>KJ=w<{-E8Qw~C8!(=}O&mN_?72#v!0!c^@ z!~qcSaNz-0$~H8RDXVheFiht@$_8W;#1)coWB03js-9_NKGwAbOMKu!gUAb1w7JNwn}L;C)6H~KfEDr%F-w>9pxnyd2c3&xu~`vaR-Nxm0B7R4`p9z$#I~9f6of|@hxs2ydDMa$ z;dxzL>~=$ztt+mrst}>jD8>~C1QL6i#f`SxFwka(d+DD<2CV#7p5KC>Nh{|fpQNn!2mWW=sqn0B;uiEcberc(6fhj4asdSa4&EX@9bxDR)v zwKc5%a(;P~zl9S_mH^hsGBSu^psblT1CN5Q% zS6rCnMm^13&MJs{v6SXcLkc_rjKVoIm*1RP(tAp*|Hjd%?1>)z>}ka{IeGpa1a;u) zI+TaAr=U$jF0>|qYf%}580pd~1x&=Y=5$*|n!)5E+=(&x*?ZF+3I(0zutN29_G|UY zzrD`}PG8Oj*_z8I)ZbmLzKfC|xyWl#2ajcezDn~R?YxoGC4pnBh@uwIZHbaPUIeY13E^5gxowf?rTQc z364(1K91@r;8QXn2*E%enFcxqf&x8BD+vTm7;FZNL(9&i&wdIzWg?>siQdpmSo84J zQ~SoR+&0wKW0ddN$CT=DRSg|DBfn99YWK4{Cy(IRnM3sv-{8MS)wbCqXV8is(3{RL z*CIBcr12c02pbptO=;sfL|b^WDKyKU?&GI+^Bkg7`P>wuJyT=h@YJF0t$jR$sEub3 z-CI{p2}E~HhyNn}Q3enDOYBryb*VzSWLZPF;j z9wZG+h6A2Fnqz%3lZ_X-TT!?&MHIyoKvEj`V`g~onavGw4UPth(@=WO;_Bi^ zWx3!raIkPWaoVhQGEoZR!snhNtN;u*fC&*F&xp+ZjRymZAGWMo6^*W38C|wPHf&I@ zX=rG8b8xV|edWsbTsOY3+q+xaTf6z2dKq$gG`2V5e>HDKOMA--{??z9N7rEFH_f~Y zI<42}l#hLMDXfjBo~|t-XH94uhuy$~Ejjj};O_NQ_%tOy~c6K&4EM3~Lrh|R1L;aS^<)nnD*SED+6_uGx z6-AXTEtN&(CKHYe%&v51RlJau<;-%qve+}NHQ0jNs@tpE)aCLUKhJTt)Ae>|jw&}r zR)(7cIXQvmu>OZDYh_q_(QPZU$orLa>4VUvM}a>edp5q$=T2aS&>WbQ0lOgdX9uhi zi|NlocTQb?pcxtsD_4I+sTf)5!G`fAq{)yTQ`0<&@^esM6R(f>Ik>q{U>5Ro`0Z)@ zyspNpkqn!EEKzh0E2TQrAF*;#>p`s&txA-g3;x(5e+7M;hZGPnKxe$oQ_q=1$pU73 zx+qe>dkS32%i?-l7p2}#|6kem?{j%}QR+7G8fv~HFPzvPXWOA0iC>vX#8D3gSVHq7EEVd(7u<>44k&XL-f074nU zeMaCYIF*+6*la0_!-Zy?7O%AlZJ@~T@z zxb6l);P|CI!hwunf#2u$y1g95fV2?eK!(`+O#XP2F%is(q~o*;nw@|;X#aaMx&oxy z3h3gxN6LTve!2P+R%HjI;c;gdZMn9*G14I)&D!5h2>9<>PWNz!}NySBy%=vdnKLIk!^QOt{ zJwA_KlzxMJCHDcAJPX?YiDDGksXuXht@L{ozZWveg1i?H0x)+XI3?&Pe#Sppx&in8 zh~I0L98o)&^B7E^lqUlMKZ!&11P*(IO>Jrl5hRo`ia%_Ss)5}XAl@kv=p<#!LsmmP znSh`lGG8E*g8^W|F!`n$M-$Cfq<1i`q(pg1NpMq*CCkfmCpnW~oS~09R2Q;j`U5ZZq4`(X@^FZL35E4f^^5vn`X|u?x*f2CAh3g0 z>9^6mDrQcqW{GAWvm`Q>Z1G}eq%crB7Nc34*aMO^!Fr>VlweLwI*c&GWF(*iwgLhm zBRy0;Bl73~e2@@(2!|cmWMHu{B_{8IKw^+g&=w_l8V=J~Y{PKKP!?CmN=!&HAEP1@ zEy>5IOpp>9ByKap)BYbSRRmhJN-c1thK-@33?Zg=qf~t0ghGXRtlnm7D5t;qqX6VZC$1z#$vI zg$<_m^c*Mu%bm^4>QkxwZyFcyZ;VS5#)Ys!*j`0ki>GZioZg|Gh4&ccDuZ$$pzazKOu zZ~l59gYxQGNRU}4A5$+2)+DPl%{9Dzel(KKIxqu+!Ttgn3hm>J3=K4}+D6H3~HPt_ruO(C*EyMFe9`R>IcjME3k^8?m=8VlJ2rARC+bCI3h7&!03S5?Z@G> zerd*NwFRGLe&+pZFwJV*AX|b+96)XH75^iXf() z-D-0e<*HA!74BkZ=BIbR`k;IhsJtBHl`4(V_H@w+lgtdKv>YWMe~~f1I#@ zl&W=g4pyT6Obi+yL#sAo@G|+k7(+fk%A_8YzX#|qalr=%1^$9A@;_w&I-xwB)4 z{McmtOu<@|St{NzhEy?BRpN)U)zipo6!iw|q!|Z9$i73EhC&Xcwqj`*yHDHP#csAj zeLA-Yqv>Y|)_-YsIOFDagH`E@F=`YZp}PwPg~K(;)&-v^^#9fvp1z z(ELR{bnF2!m~6C#Js}2N zLTV8yMQu^5wTRZDB2}x_XRSr*vrf&)@Ln`LUQx_u6@qA_a+bsw!i-S z|9}4I&OP_sefC*-1(9aAtUAm z(YBZ4;!p?`YeHlV)PT?r4M9wZ%sHC)HvnQ9m?t(#yYW`YCmy#Ak)T8dLp5ten1!iy zq?5r^1QEcI=x}h0NQeS2U?VC)<^@0C1S#_rFY|`K9aX8bX9$1U6QcL>KBA}{tPJYVsZ|{_JCTMHOu29*WY*f`Y%V+$HSLvUpx?5 z|F4&=4;Li-Q{czLmup`-rPhB%Y{$cwssD;*m0BM-mA<-M{Zra3r9NvOL48zG`zsYH z^+zyfQlUC#Lc=gHk(Al6Y*|nSh~q~%c!|0Pj~*RV_v9t&o;-azvM$RTrY_M9%cEde zN_Bah*yc;s-yFP}-8#7biodVe6*BPoM@~-Tsl&996suykw+^Se}duMyCJwo2nXQ@ z4C)0MEg24Fo&yfK4~of_Kp`b-P#^FdJ$*_%5W??e=cEd&+~+f)u^wrkl@S8Oto%}a zz7*X3y)y1qxJwXwu}EAh{RY@3zl=ES#kydFkxw|PXK*6VoJ5{`STAf4x`fTbwZcx} zdf_JFR^i*i9f;+2w{VYezwjf(;{1v5gz!`0XTr~gUkJZK6nYraT|N=H)jT8JKUQ>mbp!L`hn<7lZ&gooO zpD?F81{I47jw+Ew`~UEFacTX9|7w+g_4N_6BzWi_^F{j2xDcI%E<)p}`ADTlbuId) zpAe=PB65|8E5yT)UP$&N4PHGP6eq-HNHA5G!@p2=DW@~hI^VzDHgo1}^XK!wC7~;P zk7(~6$V9p)U}!JN#mS2m!BbrDTEqWVpq?lc;ZK(MIwU8T0Lz}sZi|bK(&NAjkENoB zvFd>r4?pl?bLe3g!oddku;}#;G9X_6(9xq0{qUS4e#VS=ht}nsJ{>p9SFp<9$>pCF z=Y40!cXCTgbMTQeG|^Ha;^)? z&nt5JpraV-^ZZB3CTKS=J!c8M%1^DvxvfAA|n0HFE% z2RhZO;v8O=zH5!=ejTSap64a?|JLUtfS)(_CD8)^3j{!yVF zu|W1;zMa0Mh|Ev@)p~<)}u#T zPXgdq#H&D<=YFfrR^fgTcrgm_?u21(FaaU!phA^qE%tcPdISO)_S;XujX#- z^KooRw@lFKb@J{fa6sEK*oILsAaS3rbHT5Uw!YGe|Br%j?*f9)y{pZFGH$>NMd2WL zg=}9LZqJe|NmS0Ya&$r$rv~IP!n5c<#kI}}WCEKSqI?8o66s9LuxAB01<25`7hAzR z-Z@FY0lBZY_95^zjztc9F1*R%Qrd=hmIjszP#5;NFL6$Q!g*Or5sn!+U}PHVY&}T` zc(aw5Bd0FVF8CYu*(mq|ZW+H9o%~1u3nGB2RM}3Z29FYr5~u4w0&a)404L1@nFh#_ z7Sm_g!wkGZf>EV^;+&lP-we^dvVNdVd2WFsVcyVk%<^+7IC`PwDU~UctVSEL_pNdl zI{rbERc@OYpEfHPEt8l$=8CP6&n@&_!}qD_9mBS*k^y!zFdK*zWQ!pY1Lt6%CE-nF z-Qx6s$)pA7@CbpgAo*boS;AC~;ODCtjT@QIf;oIOY(4s>Mosg!;X)J zFCUzN@_kpl1Ots7LAhIsW&SprLcDW$C7(t82l#?6(HvLCTj444h$F&}o<>{{vDt~U zfM6#GN{R4kbmV0)Pa)>ddx%-fF{zBvQ5l&;0n|ghmWvSb{8jpR`7;Q#E}~Q1!JY3( zD2S60rU}^|i5FNP$KlkLJVHinsOhjBOyfic$`A!kkF;eJ3=ToyC}7+L@q@8LFLOw_ zKaq%x7&!=35!hs3hq`BIRj|tWpF%gZMuGo@r3hg5Qv8ql3R+S7%O#x=V zJZpeLlb7^;39~AiN&UUVtd54b6$BQ=j>~CJfjNTka1H=;2M1W(Z6WE5V1ToJ{6(L*~_d8mf$h_nsI(v1^)p;H}D-$^4r0Rva&)<9?V30`EOF zs*zoa6n;&kMLboJw25JZI}t~f+a!pAQ$-G-MG%Y$JHZ#6Ckq40`I5q?iCYk>=3Gf> zlh4@*Ic2aC!HI!SQ4XQZ2xK{hy@OHbODR!mcucUK*yMAxBuQbmK=PsZ)g-6UC%n{* zddT{0dSvKSnl3a%$_hx#UlE5mZON}{SC&I80~dKG^1p}WLohYk!^Rp;hmrAWe9)V@ z+%@FIGQG(wrHp??mRFX{@ahJUfh0$Hi31lEYT=uE@x4GSHXeELMjg=W8Pp}cR21R@ z(b!tXhQ82aX3$6d9wx5nKfuaS0XKm!LD6AF=$rt)B$qJ@svKreI610$d!*+cKzr~2 zgC;QrMWAy6*l7{rX^PY5Tul`Z^dDB*1q{W2mtfF6fpI~*76up(i--H~V5G*$ImFM@JL=}?dB*Bk)R?77S;%1PIhj`iXK%0?%c9Hu8_wU$o zlK&50L;rYt8nj37{!Q&^PSQWLa&90Aa@M^{d!$zaQ-gM(w2%+^*+z|q{O!W(Mky$k z7ZQo+pV-7^^}AS8zmqZ{NyyWf1U&tXZ3CC|Ph7+%_D8d}eiNHWY$O4A9TUQ9oRAqv zN634uS5P(i1(Z!pKe*;7$W_JoAzLonTU!sR|0`Gqo~eBe+E?-XcWPh8I`FOm-l6tM z-&gP+N9QGICZGcmr-B8Io)R0-;o4!e68O7f2*O1j3Jk`}zwJM({EzpY#QXOBUwEG~ zJQ$$J_bIYA@OP!Q8cDF;l=lbTpF#MN%9xFBAKg}NCE40Fwh{ufZ{gKb}*8_j&h_8W1U7@W@ zBmR+s?*o5|d@!=-NxxWv9w??V{ye?^&m(-l`n*Q`Q$O6@WWLPxKl%M5wv}m4;%txn zd^zXQBY7T+FxwYgavFWcimTusx*PHMD|=pfJ>j3m63_oF_N(`Zln9rgYii3#zEBW= z$hCk73St{S8^&uTYsz*=aEKd8dx6sz zK2)!-NU@3}6{sj6)DkEsa8warr}e&-!*}Iw=hO>U2H7HW?K*w#>kMzrCM?w|<<1i)`<2>L+jTy@>X{X+Px=<#6$JE5i0k8V`RL4$lBgP2F;z7b#C)wzx#<(qn( zx$#Xjz^TX}O8Z1Bkd0vFM93~qFnn(UTN4+tok!3i?T9KHkn;W!t*nFL|GVsM%6vJJ z$8g+SVcRkp$Y{Z^aiqvbnK0kE@^C?2-D^yYv!OsD^{0;=dP-Inv5J7}1 zbqhzUWjHO0Z4AX6uD*kx-lVB4ORm%lnG)ssGGshsgyKL!xF3*Fu$_f4S^^HD>Q<8- zGh(TP{;zaI#=jiUh}14fEDHN4K`(O6z$(+*D{ zI($RB%iPCyR%y-PQI$9be?C-W=@Bkug(}w^p!5Q)^isAY>I4Al43*tg7w>*xA zC?KJIv`MEGVpxm@#%e?-lTXUwJZq^#oYHz|c5C(ei9S{iV5wz-8%+}qa1N?nS_P(t z^DAnOWqE*w9yJ_r86u9E7Lj~Nw)e}wxI9R7%X3Et;n{EX0k z1pJErhA{>&rzk^eL!#8EehVCj5HL3>P!51lMeM!sN%#bq#97(iIFin8KD0FmeMr(6AA zi#;0A;V7Gp1sOr$yotOtYFwmDB{+-)kw%D@5=$YzYLf%@$dQuvbk{A@*$kCgVV-Gb*ERP`sPgQoHi%JGtn@9&e~xK!M+d}t zxE`@?d%?ukMXjTXi`UIt)ZU%%PtPs9wfvg(&zD$l&CJcHZ&oDB9J3Qt0-Vq(Mm0_{?uuk5Pfg_=$ew^44=oH znq;Ib(N(O{gixAVZ$@S*EmVj+f*1q{vH)yVP+WM5oTfxsrub z#_`javKFx{&r_n05zBIYB|6N*e4zv9=Rby@iw9oxkH|3*U7)xs1oSrO5CMZ+Ts9J3 z;kZL{9T^vNYXv<5o1@Wni24E>I}iTCS`e>GVo01`BR|WLts`V#!P5%^Uayy8a(PF4 zYboLdvgqVyXIed0cY+IGnlob%Ns0myk^?tzO%%$5qbX6M5bHwDu;?t6D^QR?g@Tnp zX{Crx@?lpX(AC(;|1Rq2SX8sPq-1eT`Q*tpHIt^L#K)(m#>b~jn^eBJ~1U0s?vir}`*{u%BS025cxBV2+Tm$}9x^ku)a>GmGFr`TDqzU5!fn+Dd zUZiBEfWYu@V|t+%=_Jw5-IP+3Oz89py%f|04clNaq#4p27AIx0Gn>fGlv~psKAS}e zcum{8qbl@z3Pp*&KR&jF>05i}rok1TI*}le+W($Cd))Y0v&Q@L^88fTiwFQmLEvW86lG)<7G`E(WXb2~4R)hs zz))XY=0o63=0jo(O~;ITQ57B2O@@6p&wG!sL&!t|)ZpFWXD;t+2LM9K724Rb1Nh$7H-~LQzo#O9O)TEc z{mo=qi`yzNFSzkNENy`ZD5xqRfi{Css$`#DzvKS<@qdT7tn0n^5Q$bq{GW5!1pF9r zxsby(keiy~q;R@=>=RI64tTOjYf(#8K7C$62|~U0<75x!5-Sd0y`0Y*d$^gAI_`zf=i^mUab+A7vqqbz?-1?qrpG@ni5A-zsHY4f%_meWn z9}+OPi~okPs1j-dl|+Q(V2Dtlu=`2S50FiZ^L{NNd1IlN85@giTUD`@#f9l!mxHu$ zzX6y>{!>V`w6q~St53(JjtYEtLC}C-3=mG;;F1ZLll+T(&WSnPZkG#X!Tx#0x$&v? z*qHdlZwgjIqA-Fri_g{1N%@1_uCC0|slM|C0 z@@kitmoKl)Yrq$KOl)FqS783WIpk~1`_~RX<20;;lLHgT5{^h(y$19Ke%T>e0B(82 zUGV|#%KJXk?WXMV?tFJ%rr(@qPK|e>nb-^>G)e@bF7wDrBc ze;&X`_wcu7WmVP68u>4Enu-a10krbpWi@TJwQV)>--*72#S!5U^= zAh;tS24PtY3W2}nY%>m^3HchRaTDLQ(9FyO75e6;H z4cILDaMUB#<1e5n=!gLgQM3fv2f{Rb}J<6knQ|!Lp zfpqrnz?1kL{x^LZIcs>IM*dw8`(L44Sl~gF8~GJPP1I@CWIzS5i<40tdNC%r0qQMO zVx(RdR)Vc^4J>V%U^Hr5plu`@o01xiB|_{j1C7C|r0ESmEoclT&1D{jmz?^X7FxL$ z!er>}!#s}wxLmU?D=o|i_&Hp!wq|;w^{Kg9B#pqF;(D|DGCFpoUM(i~y|Lg1QlJ?& zGF1IKw-WkwLlf!O-zL4<=SkQ!eeXgR5AFHXVC_0k1nv6MrK{MjA*EW=-v^!oRUCPF zMhla$7JU$KWW|UksbOk;V05)E%9Muf*(g=FE-}C#zy#$Q^bGA9BBuyMn@mCr#s(5+ z(zMv<7zDx%eGe(wrOK10-Ya-CP&n*U@M;ggjx#yt=1O<+Vr@-X zDU@%dJ<9P~b~T_YyR$35vGs6B`SRkvgL@$ z7ria*8HQ}mn-p@X8uF;axva*T`<8-xf*L|Ed0N_bzfM9QEOKb*NboLy2+_Ga|x3k0*hy??`8X*(A1|zbI=%~64kIB3E~BENT-lbx5jbb*{{j-ew|Rliq+K=l)@#zUm`PJLYE+fR z#4`QY+&ZsSW0JD+p?n8dcFjICv!&bb`CjRGlWBa(_tJe`%`?8AoelW!gC4((!_T#8 ziWsTE*;Uz=1P6l4NEu30ph((4@tPf&~a}Y`k8*< z0bb#h6;7!HfViPSp^SbrOa+euGspWIK^}e2sU#vuea8m&3xDN(rL!K`*WhPIa8L{T zihOs4ItXP>ZI28P{rTIsvp=Bx*Y@qaf0S008#FZMemd~5@CV)(J>+UTv=)=yW{!#0 z($=2bw>aK50&ByHAoogsDZ0gP3CpO$a2+B5m?INopXs-pFOz%5P5|eVpYXZe-rzc= zi@ck6??^`aHpiL%cN9E#aoFO7g)%HDgl8SOE$N_|>-0nM?Tf&7WVOL~59iq-d5-`@ zp-m+EFNFOk`x`_Q1-vhjDGB(031dH+!(3*F zs1rno8a;A$C@&9TsDCUl^aRIFy4w@RPA~#9XfDK$qJ&v8W)M6ATOab-I!=j$_>w!! zmj=}#YD9BGh>>QmiVlTMDXfZP0dpY4t0-F)&WOf|B63tU;t(R_KJIR#*Xq`AhJmC+ z@VmiFXjULAa7;{$Eym`csc7ZA0wy{zE`@Emh)_I^@7)2;web9$?b|g+7iGr9WuFVm ztgEX_Z0h<;X>@GaE}DzM$1xt8F&$2vt~AAIgW>~h!|Vp< z0fsdbf1uD|iU89{?V6d9nBcHWdTjZJvNLDMnOtd`gtF^iknPo_ak?nStIHvqHAF*j zB1`tBrg?&Kl{xNtOS_hjS+R+2FUYpeZC)TpN{(6;h!$wp3Q)SUlBjxLvm-Iy$Ncl>r3nmIn*eA#x zXA|bgx#JS(03)4VBr_e3HX?l-S&5Tr^-IGQsF0s=RIAXB0uqOUTY}@oMw;^=_8~i` zY|4Wt8JY6Xea}xRp&aX=zrp=HtsD}|9X}3mutxqB@cabuBnfRYJShh3ONlYa8_OX= zf+uT0GOEr2bHcob#0d_21c0#s&>sn41ds%1f0hEN3Tzl& z4zUcg2q)NlF&?XC6rzCRgeKkrKPkv)qBABM{<>1*W$nLMAVQb|mei!)Wm?CtxBdS2 z_@DV0_H5I=!4KFA`TySi@BiNYZa5zr=^ytBiy8D|8f$yLzaYCF00~eN+F;IN9Df7sZ^1^dJ z);`P^oB7(A^VRi+)*6rxa=)MZXM(j~E6pZY`+g$+^#WMM2)rf;Qvws67MOJixkkMK zJFt}HHiRo+4y>I25K4T@jk*cu1e-O4Hj_RnSK=#m7;PR|<$$VT`0x*REIj`ony#$a zxUBQHX%ZK(Uz75YXEqlqT1Quxj7qM7RL8`PejjoK#-E+zI!_YuMyG+30JMz=7~!KX z!-(4<2(3;GCH^-+gcDuR4l$aU75GDL8hQ=x$nnUd3y$2e;QTM7PfAMq5;b)T_8-lU zX0-(m9RR=&=S8#8qu=l9s;}?r`k*>CcJ%uQm6?evzSi{8dM(}weo1>(l+z`gigOLL z4Ddqe$#hgENov9qgYs~KFEFUB5Gaw(mB5lg6NUcFIZ}S#EiKzy`n$z<=JdTIUER99 zm8au8co0J7-FFkM{SmaL174zOHxIkUw2wz`LnsE=@lYZh_wnjzAsYD5>X9*lzmVuU zv(zQ(`ioAW$216cJjr%uE0Ylb6VQUVyK-by^y(Uxa2|BQYE{bu`O?h^hIBsx8on9fI z?E$`hr~`93Ht+gic{V)8#A6k*QCbZCX5evF40k_T=MZfSo1hY#O3O|ZMPQi%I>J5^ zX-YPd`;ey;ZY$nQKUPu_#*lO9m!8}lZ?J}94TviaUVR%9Q=9g6zrFM0w>G{7dR1{| zfrTDBJn?oU0wT%R5LVPdeY!PRO7Je^@1`F@3Mlz@SQD%X$k0TZJCQI=E5@f)1R67K zoG~FgE@|)!&;M|Nipd3<1@j-Hd4K*#B_%40lCepP2F_5Q6f784!}X_Sxr9xK&T!nfJr9EbF##t0BITuq)VBb3h54p zG`^%n`t==Fj(gg zDg5GZeTS$!H$5)=j!m|~?_j6Vk2uOvfyJ6=C8Y3yX2dm-dLA5^6k`h`e-%%t8y>c7 z`}T8x4fRB86gTxh{zbSq#G9x;WD9c;Z-QbK;!P5QSpsob>%oA@2E~?B;uFG}Lx4Tm zI@lN%eru#b?7S}YcFnm#oGAnaXH6UqzCPLIJ`)%uFGiT;@HI5Q!5FO;D;bysyf`zW z&wN#4KJdch=N}Bee94pQA{6dFcr?}@%;D`Jez!STd{KU<`D*|&t&YpIvOGI0Fr9Li zV6`Ck1}*$eH3p+WW9)^eg9+{+m^f>QD7nRQ8ab*OTd)(CWQ1^QwaVhH)q(%e5wFz} z4O?Nd9)}j&?~rTBBnv6G?*%6W-2N$`%VWV=a{t7GnrF}5KvHcA z$+zN+;GK|egHM~Z3grgx>F&PWpOTtRQZDur{{!=A5+J5bn8;{TT#%oal<15#8hHHB zQMozT^v2XDCa!4lEu0o+CMKs#So+9U1qq)rGb`Y8h}1SP7AxsskZBwqElwnR!Ow^^ ziqte}g6g}L)OzQOYpPl@R=69|v}W6;bj|!xEtxIVtE7c#qnC_M^XgN4!Pg?wSXTuW z76-JV!@JHKRhpib<|HzIciVQ(TnzzIy$JQ z5n5w#){@bdaem(ImWnxA-^O$;b$UhBYH_{?owj?U^if`W@N{Ii#W%xBCQy(tI!VTX zeY~cmvbAZ48U9R``J%-?C5_r5(qMHh5^A8)STM@SngC{QEac)xY$9^T&eBV z#;w~jN;p(xUz8#19~B0Ttjm3=L%|yiqICX4$Ydv_Xsk)GkaltdS!5xxL5Yuop+KwQ z^ctRvvK=vOPm?e#N)q@GX5ef9cC>U zv3+MTT(+-o`u3mkX+L{2Y;c0qe-^ekW61Uf&Aq^%nq-1MX=Q$fJvPc{p#2Ig4pL(Y zcwYlH0kC<(J?_zkl~|5h3#J%$C+!Kz*e`|PMUG7{xIz9?*#YcqsyA~fxh%tZR<_Lv zvIWj28{jfUW9RZQ-E;e22_?6GY0NSkjBiWEELp!j)0Z}XN$^4JbY$F|otK*_ZQN+Q z{(7zN^X+eS-3V9nm?sjYcT~&U2E+Qfz5>H3qjS@2Hfj{N!38J62A5H8B9;66Qv==X zOZ2-K{02FmXXKw(-ta^Ujl0F{sl8`RPc|=CErnVR;-D z5kn1->`O2PSe*QxpuVI?Hb4%>Mxfa`cNwrTX>#y0*%S#pkSUUwfX@6s3A(T;au~!6 zF-Za)m^K2|Jcw4b<|PT4aNZ~st`2niSzIjEJPXLt!g>WWtQTf&ubss@9b%jm8@I-4 z6eWXB)R-YIMgyyf(`+X0bQ==|%q8RCfmWNOZOY5bE6f8&&dtfr%0xy^k2@{pB9=iH zX?LZ1nBsWBISh!rFl6$A``|_4#S(Y5STOs=#hzI$-j$v1#h$$=q3ixkteRQD_vBTR zZQASI%dXcTq4H!Rl6d@%!3uwqxl#00_HuF$R+GH{Yz zF+300B-yYSY>r$36w_klvc}i7e5`9S93Ijn+-a!*Mkh*1HhA}Wz%^o`VH5ubhR!fR zAs(W2e5!ypI})_W#pwbtBUhdabVOnkLP$qsNVkTF>0lH+7AMycWf**p5*L(2e;F$# z%`jNw&B^t!U-ln9_}0$1ul^R2Y3~1Q!`mA`nk2h&IhpoGCIrS>jFEdI@CC%a6*fn> z7IWd|2o4)!djvk7Q0d77+`<(^9?i=m+cZE-e5r#JeSY42zEPrip+D zb&(OH>o2=XV`7lxF{~SqjSWN($xyYiL3z?h8@W*pCN|2|tI^~vCAzT-h;b4IXm@}xVfOCo9 zhLmP=n2HMn=GfR+S1huglQk)FLrQ^L-jSld$eU7sT_c09JH$7p&OSxGQZ;cI^Q47y zf6l+4&`^N)=e+rH{ZZ_72{hAPHiJawkFZX}tsk`U$E_cc$`Mowm_8J< z9XSaoI+9EtTtR{hx=h=oD9u0rpaR(v=;3+M%Zp3S-_NO>+w3^1$!a&YzFwwu&=2fF z$?R2e3kKXG7@o95!A6G|kHpxtdeZd$TujumSA(O1qu8tL)x+MTWOwk5;5(j_6c5X0 zQGfc=_AkGjXVq%0_Z_xs^_F|NE>;KG{r9k^_{7cqUlFB>o8MCAI=9mqp?$(@!;H-_ zt`>6(3QZ`opg=ER1ldyQSJf-%Dt%=z*m3)5wD$e;H^s{4N~{y; zI&~b~u8PpjWuEgF=*vb(Y8V%LG`O0c#)X0LBGfQV5uG0Pb4Uf)B*uLhOfW^b!Ninc zN+9x&w_4S4Q9v3xg~MR6br^~)P0RWJSwN=Lg>cyj!xk7kuLlOse-8Y;EaMM*4+6*I zk7YbQ15QIW5-}R^2ZTEVCN~2Zj2T76YpERY>tMDsh(@7P(2HQGMkEww+8eP6g%Q>p zL>$J#(E%e4V;PNX1!ldGAtiA|=s|-3L2$%RE(}=fYAef2iqgGSXL@?N&m7HHeA0N~ zY0JH@6|YoRu#+UaA8DwAz4v)N@$t|%(1NU|j0+H6lx zdXSHIp%(dw<4bdkDw3_dR#HV#A=R?j9Bf}f?UWpUg543Hk)7jrIW1OalKyv`9!A?7 z$*I{H@eW74KPTJoa>0Q-taC2L8AAs$#5@p4vtt3!#gQez0nR8%jW#PHS%_0{q#Gv& zrpRYB^8_6TMTdmY5+-sdP)r-RqQK7u>E&}l`W0jim-Ik0+VA4GW29L5&6g; zT^LAhUD3Q`!Ms^B8mCSiAE>Lu0U$fbom0H^auk1&ncyQE3!eutz+6E8k#iyPx{N*A z+Aun=JjI@9u_W13YYGc%Qmx4rYoaZ!vaqN!4auM_3D)Gw!otdAYl1~IM$6psup`y# zz%%@}{Hj!YvelAkPpK}c*nDNM*>AWk9Y9>bLCaB7rZJ#NZ_y14tkY7>|-MJf1KBUn$%T~*W+am5!0{FxIFJrIRS50Vf;)6PmNK4@1srzC%hbjBY+%|gxYK<$oO(CFsEWNU ze$?UOtf0(Q#?A%DI40$1)@CInWhEwi)-9P_HOZfxk(lB!!C;=2p6E+SOZ6nzP8d74 zFxQ>pcQc$`!8+dmp-{ok`j5f6>CbW2AMPaR934L|1H{pKDS9o|Rz0$nuN6#i5{NP3 zpqP62pKL}kNV=ufMzzGUsHik8dCsQd;acDgKXN0)N2z9im+0rxQKmy$39!8E~0}1H#v0`YN58=G!6?Nhz0t@hi z4>Kp1HI@0&D`r*{P0dfuvb&Pj%^qLenD6!5QVP;jawc`ovAT!ssm#h{oZ(cMrB#kipddy2U!2tiq23k+!MgbVjZ11(S`VGG=nJ<;i~C0T&G~d zuAzzWi6HOLz#u4?^lTjl6y&@RA3AXbf13De_({DM1UuqK7b5C~Ak+)>qai~;c(8$= zW?0Engm3gku1O*|kXS7``rrX`62d9!W{JLufE3SRfs!l7cO{vS`fowGJC( zg`|o?h!(N{0Wl-DG~Slv&_-n{JMr)7lX1G(>4KyHiGfz9DYXR|&Uly054nP5h>Yyi zWQQ%f1beV7xKwzb{gge8z1FS&81VqRq790m(~~~2DmNXA zx5Jfer)AuM#Xp1p1S2Md0NhD7z6Rt$hb+RW^n1j!u)vtfCxwnAE9*LZ4iNemJ8KuD z9Kjh#co~q<1|}Ol`C-^f$@qc&ChRP}68jJ2S-t99 z*S{qv#Hak*U%Id|9%Og28xc-O5;7W&m0$&d8z${`GM2;Of`>C3iUY$!a$PkHq1gsdYr4LI1=Hr_55j3C}4MrMg0AG!Ox#g1dpW0@d1?)iy2f# z^wF%r@pfuLJ!5}2&ez{gXYA2Z_>6t{eE$`|Lg)P9t#DYpgFPdjfxh}G#)SpD#p?&^ zq+0AhPbA1>lMn(7VazrS?P5~Efgj{VKZ87epLBTAC1ZlzdmgUWC-#Nd7&!=fd`WufsvY?esB!{^enveFXq$Ndg%vu30u`)O}^-hg&s zmVEZJ|H#m@pTzSA%maae`#^`69XGA7qBQtM_p;qz-cIv}_3wdDmC8Cs@+|TLc~bk^ z#RJUT%*Qxo;H80E`JPh(?=Lo)6oLo2D=5SER%x((I!l{gD^12IcZm-s%C2o@zAckfW%n-NF90yl7&p3KS6do1lv!jxq)8F4cj>?}WOsJ|tN%DA-licnk zaXzWRRLYWE@kz-pR}zhRuXr7MSF$4h#~SKm!?6Oa9Z4V`#2J75po0aK^ zu|>t2r&SiXGaa#3`BJ>j<%o^8n$tYR)pTW!^Wq9<1Q05}9|6xH_hR-;@MD%N-m#LN zeQ6+zJ%aN~7NMMvaV~^0@`8~)N7q6TmH3{91fwUSeN4v-5l_it_o!p-;(Qn(J79>0 zN`&!#snMBMJIXiCUO8=Q^}7LAj3aSjl|DZ+eH7mJW4v!KMjScpWSSy94_hRl2Oi^b zIe3C7%wR%nv|a~)#yHI&_Dt4p3Jih(iTTwNM-|U>l_t6B->oX}1d<&2Zfn9kTJNOP zfm-RekV5Ld;aUf|hb9_}T<0K;_=0a>p=(-JSZy--TBSwSXsmSk{EmqtbcisYkr693H1bia&3{8aKs@=&@4K3|hkz(?=H1WXHDdW{>Fe@ZD4 z{PX)#4m%({29h2&wh!l<2exkCBjw!0>F;0g%oF^XA+Zv95F+B2S9k5?mBBBiOz=x; z|MD@22J=ZNbI+bV{C#VH=P%KQUO>EjV3~5+5>KK7N)&dLDYS~k_Km4S+ zsIUfRzUs=v`1qux_;^@5zz@-v2i;RD~s_`;a zN7Ud#9P*@>({Vu^wiCqOSQZVVSaeLUV0GAR)=mqHGn?Z!39!fL7XpT41odXx)pTUZ zgp&i&Iw)o_E@qF9v9C>J@kuPf5udOY?#&LX)3I8xna#F^g3V@LA((CESxN_1jo>Xt zdJE1x84#TTn^1K8l-{=X%H9@*&Mn8=NX!^npEQU(2-k@#?3Mi=i^O!ij$D5)GG-2| zt@rOdXv2|*`l`4a|4!twkp2a#?e_cbx%vwM7eD$V{?dw`swk%t^ zq^WVzgu2?&l46Qlke8k5b=yi{Gl~|%16pCP%N5c}V3v`k6Y^U_dzs|EmQg0h0n+JtI8K>SVjo6U zG~`%OI5^6+TnjMvr88vDQ73TSQ`Y16_oF2RnTUe=7##B`?e^pg*Qg&Xv zL9|*Ok;`8&)&2$BR(-PK4ax3-_u46%9S&Z(;t>uzdQK;ruN`f!T(h`VM66` z`V+jnqM{=Bv$^qFw@G7lIjkCEOqA0Z73)fNSq)mVU5uYIrfrWaFGG?t@?5vHrA;@) zWm-3^u9;L-m{4f)(ne`|eb%HJe=-4}zCM}Ev{ zhlHV#^oY5j({X=y-7=#AS%h`dbvj+5F41P;M^<^DNG9hEM)*0Wt8^4%81bNVnLZfQ z*umgiGv5F2rPnzurnrVBy$-v{jAU}31zY-82zR&jAw zYBu*DgaWFJ_n_ROh(67l`wwW|r7;QDxkQY0n~z~HucwE76inZUKVOOK#E$+0gP$Lg zXY`14>U@^iuGaZ8=VOS8thC}r8hL1LYaux4G%Yw?QmLb@JVI^y;Hnw~PQFOpK=yFe_yk^v zCUuC*h50)Zi%T5-CC4QJ*A9=5B2bg2CTA_+21(suyk|rwkDpUmw5L}#ny z;n*_K;rvV07xbu4K{Abqc5c)gdF10E_5?hyg{Pw)xDCAy0-K~=cfr%NNtu!&2q`%! z+3>rC?>-<;vXZ$W8Z>xu5nHjE4nfk4OnbC78g_^30#Bw5m#NdL3ir1x$<55lThh`b z`JMLY7)xAAN?{dUrMPkNV8iH?G&d(-zzW-wc&D@z9571o2fWzRA^Sg!CWufeP?94# zZY58lVd3$)X^xN?lq^|dQcus0;1t!K_2<3k$fAXZ243KHu`6lUMkXz#-Qztwc04eo zRTdQzeNZiF$3?5j zC%ZwS6o_^RiNf)jcbok_CI!Su$b%(a8!@a?@(}{*!7-<-r*O1{$x6+ zk>I8Il_h{S5qAL5GIhG$!1M){E2Cn$YB)yZSr{^3UHDv>;9&4lA43fOAp{CRks_go z-Z<3y1*xB`Ux-U32tF@-!BYXZYy>k6OO>iIKZF<%WRxvLkn&|!;cY6CM%k@V(Uv%u ztFVf$;$jn=1=T-M@s*F*UKFF;1s(0uI&?fj-Nu*3mt>@)vNK1H!-=R%h=8hWA#-sO zVu}xkhe&jhBe!G8<+6($s=f5TS2_wYHykT#YFFBotN2V~R&-QUbe1tQKJwZ$zG8br zX>rqzit*zsb~F{2Hf*mLzcD4T!q|vlD z@HfBjf<B+VKeZ{1#8R&3j$2CgR6$um<#G& z@!H{wPOhKH*N)_si;Ht}ix;n)BfS_C<7mkDrzGa@1kTdZvsNdNUJie6E!9ORBOR+R4AV|o4A;gn7>x#ZNM%2OjF6%g6HH#ARElzsKcS? zu*3kS4>uiIWT1@a#$^KJjp>leXp5sr<>~T+7_kV!=AzHRDR{^-aKWPt!SWO#CE|@( z2cR&@A#Ea7f~YH631rcvst}@yx)SW)2=ds_V3dx7|3_J3<0Dsss(Wv`X#y?V=vjT> ziMM{gEUxx1f?oO*cZA;+zx)gN^H;C@wKjN%cm$jWDj8vc|04y^lVr4HYJO4}FgqSLjuPzDNERx>lc8KKV0|_Xy{N z--vq`)QR{lRPR##$wGT-iL956`^0mGmW+t{>T~o-V-{H^KSrT(QOC3m*J`18 zpw6`@T<4?Eb5vCNvf!HVNO-3EYCCjYfTGTeEL;;WsYRhWB`C$pHNL|yir+)zdig!- z9HFsN=Q#0RT3?EkI&@7i(z^%-`Xo56i6{xmefrIaQiekBr$RJOh1#I!=^oXiPikip z3c;AA6rvfzZygHNiR=T-C4vX{1_qSh2zF{`EK20Oqk7a9&FRQK5U!|A`Xv0uqEO#y z4VaBmqZDd)B1#1c;gde85bU?2dt918s$DnGgN;YN+AmH z8~hh>LEk2nW?XMn?!Aof&!YhM!6W!|;}f_K_9*2Jd_If!oW*W}vrz+R9`P(EE~6 zh#uom2&M+iiGCEo&flFB@kwpb`++p)Q<%tW#7`Yv~zF2$w{u%i?b8sP{OoH7JRA?yqR;J$z!WQc-yx@miI?()D?I?;r8` z6Fk>~g73lKDBsa%x=*FA74^Qv^`XHfxE;JOfZzTD_aDZyx1jDCz_$V4Kg9jB_K8t`ubsP$m>%-Xj z>xOp>T(yHeDDzO33PL};v4X_Mx=~(30UqE(y8u413t+F7%Qk#Iiqea67zKD1aDEv+ z=?es`JN&ute!-vO`u9K8?Mh_Enl3|jYE?5c+UT_ezc+l4g)FVG1NWRTj3 z|E?Cc3LOK_Azw%buR02`vFN{6{I*TFSvVlvD?Ea1(>i8lvygrGQAT-2V#Pz^1L869 zGw~}iC{32SrCw>TbVzzYIwn0Uos{0zBx(Yhy_!Rs2QdbI`GdD^?Q4{9IR zKCeBceMkF|_K&&(UA3-5w^esU_mb{)-AB4V>XY@0^f&7F>ksQs>pwN*7|IP}4NZnc zhE;~+h8GO48taTxjq^+sO}k9bN139Uq83H1ih4HcWYpWy>!N$2pN~n885`3b(;M?> z%rh}B$GjQyLCj~d8L=g?b+Ns%dt(p9zKAHT(Q)x{>2XDIYvK;X-5d8v+*5Hc#r-kv zoLOhKnp4d==5q5`bCY?W`4jV3=AgxdFp620QI>$E(XznOYT0PnX1UpNz;dtU5zAAS zmn^SaPFp^;d~Fr1(bjlty0yq!Yn^PJV_j}-w{EfSw(hfj&w9jq-1>s`RqMOfkF8%? z`)mfA-R8C(vOQosW_#9l()PCPLwmQq*S^;=&#}VsvNPHl?@V_->U_revMa}RTf8BD zSNv=7@5O%-|5bbthv}UO-h_gLeF+~VW+Wa;d?4{y;`2*@@>I$TDW9hVQ_ZQ#sS8q{N)yte5f?Zo?Z&kIX>X=| zkoH;H8Mo+;bvL`$xqIB7y1(`ao#sD&*}5}3VcubUh=)3zBm0)`e*)V|9Jn6 z{#X6yGISZ%jMR*rjPi`mj2#(!GNUu&Gt)DRGHWv*%@VU>vl6p1vP!b*vZiLu%UY4u zk+n7J#;pBWhqI1moydAI>$R-+vOdZBDl3?6%64XZvkS7TvnOWH%5KhHm)(=SEBm(W zyRsk5emwj6>{HqAWPgUy}1Rst8(|} z9?m_Qdm{J6+|P5*=4tZGd98UL<)`Q8<+tZ|=kLq^UjC~E=7RWw^n%uc7YaTvI9n(b zMi({}&MRy#Tvd2u;cbNn3lA5bDtxDCVo_7kuA-wwZx)NiwZ&VCpDOMvSyOVX73FfrE5wzmL40GJZjOXM@Ahl6U$6xOUfQ8J6ZO2xu$$#c~kkK@`L4PD!dg16^klX zRUE1KsM1hrt}LpotPE5(SFWn;tbD%m<;t^F?yAXETdE$f`m8#mdQJ7)HOV#UHQhBY z)x28sZq0|aS+)CXUmfio-8A~}=#zDm>$>ZX)qOT5XUyI)$H$xr1On}W7wfI{Q|nKR z)s3Aqwsmax*xh3fjD2A2@v$$BeS7T3W4|6ZYTS}>hsT{8pE15?{0rmHPFOPG$b{!7 z&Y1Yfq%D&UPkM2(X7c38cTIk6if+orDPK?3OwF0PYw9~wKbZP;gQ+2>VQRyJ4Z%il zB{w6-=8ot$o_IX-B5LJ?-lzdsA)Gf~IXvN1HyJZk;}P z`o8JUP7ls#pYiHUVdmtSi)Nmhd3xrjGtbU4&2rAlnl)T)J!N ziKU-4CpTv`w>NKVex&*3=1-RC@ZXYU&o0kdeyknp{TMJrFV z>RRhsC$_e>9&dfGEwQb?`Z1i?Ksi#)rNu% z%^MDFcz2_9#;A2$wtjl8^V;#(uDkZYwJ%-!<+jvqjoWr_dv@EI?aABM zY(KR9#qD41NZc`YNBfS0JD%NfX6LA#Q+F=k*|~G?&ci#O+WFefGrg(3jlElYkM_QI zo%On@*R8v5&vi$yJ9XV>yG*-sc1_*2Zr7e&M|Yjt_2u>P*9Wd|y?*cY$FG0)`rz)2 z-A%hYci*-9#oeFWV7{UHhUOc3Z#Z(p>oX-QMQCyY~LzTh-rs^ILtl?f-V|x1Zl<-8W}n$G-jh z9@+Q$zO%PC-oE?xFZWN~|I8ikJJ#K??~c<4tOvXYN)CMdos$R8eD}zA-#g?y)N|+u zhaNrj`JMJVSKRr?onPKnbXWIXkKCPpcl+Hhf6wr}J>UE6`{wV@`To(v!r?iGe{lH2 zd#v{?xM$rx+wOVfUg2Krz0LQYxcB9I&)%1F-}3t&xbLI;zW%|YADp^>*8O|$fAazF z1M436y8~c_VHup$J~$AJ+|brp2v z^A9}#`t#?0KI`XXSBfnCiL5SU*B>uG!T-kM zjD~FQhaZ42OSzV?--6QxsDW5&@BpS$hxAX2|KNYluXTbI{=o9JUMLp6uUs2&2L3hW z+K3(e-zwM0L?iSm*EnUv_=!Wb6f5Mgae$I(jQDmlyGprch|=>H-mvNdh(k@xShKc$?Z%DnrmH)z2A~~z z?uHE=UEM7m-D^ACW3O&+>sY^`XH9qa#;)qZ!p)mE7p!aRUcvibuyVsyjcZr7b#$TI zS9i3wb-KIPw7Kgywyea5@=Kn3UR!6^+6^6U^pu)WT4~|63O01EE^L?E=qhZSGHycC z>&A@UaS|MK}2eRB&_5j&w<7>l?kEf9)3K$32h0(>tJHlVmc$ZbLoN;|%9 z#Ahq+ZARU02+D4J>%=wv?nVn8{MRY??B-9jGm=Ef#7}e=MGN^Q)nds?X(zp1f4+?oh38`xn-~(l*y}jI{v5ZjQrN^!qAcqElFp zdm9jN=JGhc5Q-VV?ONbsEmuU`LKBpcPLy_juS3W~s|_4$U3f3yy&H8qaCa=P)rRZ! z_`L`1bmPB`fU+8g4GJ-G^k0Fn4sdo0D>%&ZxECN~$d?3h?*;fAZmT?zv~*{qzQ(hxeJz zXP58=>}F=-oknlq3De*U`4Oy{FJdpU5Aelki#dMhmAiQ(zJ>H*-ozfkL_W%oP5_KaQ{AYk4PMhbP$$d?WAT$Ma3Ro5%1au_y2o`AK-b?cs5r;JrM_`*@0P z;r)Do5Asv^RzAds`3O(*ZTwW8!J$6LN3r`2u~YaMmz`&rUBkCylSSFboUt_D!FS>p zRsY6!;qBhb**3nLpN5S-ieG2P4=M4}`5D-bE7|+}i~LM}7F)&7=I3DJKgP!JHo)!J zeaHE^{5-ygpU;lt6Z`_U2A>C9%P-^?@r&6D_!jqdczg9F{8D}y+lg!MF8)KI#_}-xJ^PBKBK0o9?!c+gv+~iq4$@lVG_^tdld|$^teh0sk z-^G8z@89Sza_s` zbc%Iiz1Sc&iY{@y*o0S^V`8&7L7XT~5+{ou5f=&e6ZQ^!7oWVEflr7Y$_`^cz`fS> zY#zIg-OtX$=YQ}$Gq}e(7I!BV>|_>aOW88~4L`{y*wc3d`Z zV)w9naer1OQeunf7XxAtZ#LhGUsE3zBO)!fiBm;JjEXTaF1CvuVyD<8c8k*lKKU$8 z7iS23I#`?~&KBq3ovr7J^TZx;zL*dfh%bu^#YN&`@fC52xKvywzA7#kSBS6S)uV5S zE5$d(RpMLXYVmDxjks1^C$4Abu)FaJ<#)sl>_&Xa)s49KxJiX;u-O*cuqVo{vuuwFN&AM%i@@s4;`yeIx8{w>}Y|0O;UABvB}$Kn9q@yqcAv4HOo zD8?@Zl)w}yg)%6I3YZF&`0?K9FaxUa!#FiC6KY`=%!Wf?4t^14E*uJn!QtS9dYA_; zm=6nJAshjV;B&AT8o&*W&;&<<2fW||e4YdhXoePOg*G?}j)o<03@n9XVHvbT5JC`! z2t=U+ujZ|Qm9Pp{!*Q?%))I2n2%4hiUmB=kWF zwm?4&z#yCgTVV)>VFc2!4Nhe)+%fOL-Twt_f_<5t4H+24uhqTE&Vn&^AB@9x*a16X z7wm@9;PdbWI33QwD~e~rS#UO-17Cu3;XK#_=febCfY%u>gp1%}_zGMCm%?T6Rk$3k zfUn`z$8W%u@J+Z1z6Dpqx8WMN7OsQq@tWifa3g#dz6aljo8SlVL--N=7;eTZm06gC zy>JWM3b(=Sun+ElJK-+8et9?C1NXvy_$k~6_rnA5AUp&=!>gLVfM3F|;MedQ_$~Yn zeh+_uKf<5jVR!@{g~#A=cmkee7qO>s`M!`n%N}RXvp=(^*%RzZ_6&Ot{tQpS)9?&D z3(vvx@E3RiUWAw6Wq1W%g}=hz;5B$1-hemZEqELL4*!6E!aMLTya)e+f5ZFmU+@8Z z2p_@6aG;14aeQtMiq>?kUhQb_&EPSjq5)shwq^zVuikiF@xcM>v^4ZUmWhV$2ayIvf#2EQ{pWN+P5+qP03Y6i0c7O=(At zyJ&fDB2&IRUnf?qbgaQh(P1?++yqVHXeS@h? zYIJbav8g|k*q$nnZlYE%u!z3Rj`3T8JNIpXH5t5IPyw0OH5+V5r z$wx_E=hqvFl3cXakDSM3I}N{4ytaedx^iL{FJ;< z#n%2zDm5~M*CYm$jv*C>V@TO88bVu+5gpk`Vq1E2ER)_gkaCQuYDnRDNb3QVRs$$q zFo4q9hw`*-ux7OWj8fnj)v8DH-V_g|NBT#L#^ewk(@f*q!nm?9USMIIqEsW>=U3r* z{VfDlqk6mn)p#DSLG)(gi_jp8>Oto7w(0bE6&(kMSC$bfeuRn-q0)m;@gubO!^9ue zFi7%2k`I!6kmQ3TA0+u8$p=Y3MDih$50QL`he$q5@?nw>lYE%u!z3Rj z`7p_cNj^gI5t5IPe1zm9B(L*`gNWoKBp)StonIV8Bo{?Fno`Hrlsc}b)E&Ar?8tY9 z(jB=eb%&l(cjYS`yY!U0TZxtJ&JBy=L{nmDOtodes|K}~6@TwTFUQHkfRSpoUi3s_1p9|mmIfvgaW? z9@68p{3ClF%5OW-^+f9l_$f|5#px$LKjq)B+QF(PKm6pEVbRGS8sh;!`QxW}{1lHt zar?I~RpEN1%GFf1^VAj6dhB)6sctAWI;s@Rv=Mr8)LNn@ za4FPFwPLQq>*>kVP-@FqkL5s(ByCNN6%ZF3M3me;M9U2J4~%KkcCMW1xpuyuk@4YP zd0D=HL_4r8l^IO;*^^a8Bn=eTAqAu!Tg|M(nw-%ZqMtgR8Xd#m0F;^;L`B%(i2YS?BMX0qAK8u_euh!fJM(PS_g^i`_@(QXn!QP@~7qslKaZ>TEK$eyATIFesJ0}|wvh{B zo@1-XqsqX7eA(8+ZK&e%{3ou11!md=@_DxVp?>@tO{Oy`WnsQeAYXM*4y#q7bB&|~ zr)yf?NUe=v>o--JY;QmkRTWAXiP{u=lAPy0%;u+@Rq{n8H78#_xJpjRz81X$K&V!L zXe1gf>ccI5QDi)mRvaF^SMYgo|BKya7_VHEW1NHA6Zi{bGF6`1If(rhe{@U?m5tzp zuUSeF9Ubo-O^uZerTYhy33+}gB{WUTLC~n!h6fg$&lG2+Rgjd_SVbl!ub5DsRnT=M zql3ePLy63k(edQK*g#@TyV|0bX`fGT^L+Tq9!2-*?IM=-GUwCVH=j>$*RZIUFQ48P z_f7|nZnWu@-DuOPG1~OXZnWtX8f_%sM*7;+4hql2dWkpm z^&BimONPeqQcz#2Sp6;O+p3}LeTF^@AtYExk4SzwtW^H9Y4q=9tGls*w* znfjqV+4&58vO`Fo(~va0lp%d`!+O%MPi$DG2)(3VpU{v_`t=D7dj!R!PiR;sJ2b=% zeG)@D#iLJTSSEY=1cqg@=OcUi1ch|6r%y`Q6Um-FDPfuH=@Su_DK45?41E$pI@!}F zAuN+UKiShKAEc8#ee&@c`s9O9@9+(M^1(8-n?CVine6J550!@~egHw~+mosAG6=1h1+)MpMc3NS~fxsaJWu&82d~I*jK7UE_~xeg3G{7o?Ho zk7~Vs^3xwx`aQm=@*iPKRbuSmC3ALSSgt>~l=b8=wam`T74sE&OtIADS@PS2X>!Td zXA8wND_<_y`Bqy82HYU#j|-o6h^SDU)@g_eb`#Sr8bW1rwvA9=w92Z;`&(m?>^qoB zNzC)q=86T{lwG@6bzgc2m+%6n%56jAqdoGEa;-X7%+qElnq5(?vaOFBHa})*vV8}y zlujplZj(>)2a{*p2n9}7=?c3Ga|%cW+%>vN9!PBGNliR>k5^4P?d?@pNGs5Nh*f7l zBdQ>)teX6OJx5Zu71tb#tH6C#Rby2VRK%PwR;8`$=5$$gc{6h@(m_z^oUSrsQ#8v) zuqUF%PRBl`q-WcSq+V6bv{y)6QKuKq_dBsjABI zY=%|JT^i6X$e?yZbsTdW0x89q6YFnR&{N?cnmsFe(*GZdBFgiUlThZt|^G zt=M$Wu!7PdXj-20P#dSNvxlPF^OJPlo|-Nds4#Wb?Bd$_9E0{i8MHN&)|Gcd>Cyox zVoqE$9&{OJuHwlP3(|7XB`aqc$&R@6}Z{azY={XW2-04RdVt zJgS_SmoMYN)VfAh5Qny}ug}|NNx6P{XchGM=(?Xz|8R(9wQ72Np|+_5>Gak_Z+bhf zy2HI%${Vb}OMJa}|Ll%DkuHJb3-vV<{buU-w^kLXXi6qVQ&X)U5LBr$_ZxvKmZ={Q zG^&0?Q0NT%FHDLuE&ro~W>&vTX;l3-rO;COTZSg8-!l|iu799Xc!kP@uek{iePd-> zheTBv9=+d<;Elwx9P#L%a1pBg4??y7iFou+vj~Z zWk)k9CvBlAAzN8VR_8;{iQ9OTj#7Ea*e9|Z5$ke3%hiQ`*m&|8tI1o;w~39I z*ET!Pne$Dlv~lP4%DX8!eh)@|5AJ{e-xu)yx?whq-w9`8AwF@pTD*u)#(ECCcVIQ% z(WU<3d({};Ewu2!2eLMgqhwwd>vPf{kvu*CgZBci=11cf`XUGRU`dr>DZd<+^9xMo zcABiKI}&p`SM6h!>sFbL4V$~oK%KcL7T@BW=;}7bg2WwD@D`|K($$OaGB;VwWFc30 z5@QX;gN-J4o6h)_MpL+5^{)Cx6Wq?ey-+ig1w&?a$Qh3Zv!W&x%r1bCDMA}}I?W0f zBB4Z|S=7065^t+RHD-NkZoQ;VR?g(XxlTk~!O3b~jdd=Qb#|v>leJuOHkw6l6B^8! zp>AoTq0ZUoH1~I!MGH4iF5*)|kz~YlM7rxuSP)x(LN_wkO>{fW&Q2sPi`6;J zR*AL7V$Q7QMvoRDiAqk>BkMhqbAM;I6SFaqaLPA{$C2Wcb!8H3mRNJVE{-o@uET7Z z6``cb)_0psy>`4F%lHZ?{Kj6rdDyDNzUeF}IrbH^-dL?Pefifcjz^Ma z@#1={3^^y96KE^zDPDjXTGt)#tV^tqb-QBqF{im~LpRpd$-Iy&jb@44EDbeG;_bpJ z>r=4k3c9enxPl2&^lmYE5*;y17B`xuZm0CN62mHDy~u+8E{n$`OFXQ+EptznRH zEUm}Vbk$xnP|y;{+0+*wu?>gvu`RfSBR2%0k*1tQ?1! zuHXc=Fq)mdxZZ_g7LNTv)=Lo_$+9+vk#wN{9Y`%~tj}n|EMwI!OmfI%ODFN;TB<+H zbmL9IBC@gDta1gN5wjAz-&7ZNIqZrxNQt8lx6hu<@y60>77PYuCTh?TtjpF+X)rHn zsGEoVycUC<+0bata%Z`OvoY}!9^%e|gmc{4A_?o<*2x!5TEk}rEMvV@ z$0`H0$5)T>Inl3q7@veLj8DS(7@vd-Fg^(vVtf)Ff$>SW2;-CRa~Pk5i!nY4o7~PN zYD69B#uU$vJ8|~oaoIT%2+bzhc|C6P$OiLB9GG4l6CK!fKXqif60I&d!~c&Q*e@GR zpVijcN=HO?J=425Tg+!hx^Z@tt?0L<=d*YMZl|Fj+%(=RSft(?!S!3-aTlUS`e3)zZrUR|KrvK1_7mWLWr6HP9sbIAni zJi3t0*`)QE4jc`{?KI zsH;O7m94ncs#C$}Oq*0hU%^Rc;fb8}XApFOCnTtJcCL zpwT?mvZk_6%@vnBor6QRns^z`A5AigQFB(PyR!}#SLc#gQ`W<4aNM>R)UL1VET|0@ z)LOE3eFsZ~+~!dYb_PqxG8lH7M>kAhK(dcb;7I>0->^ZOOb@ykQ6bB2yijL8fjhOJ z4qbL<7ml=~U)+3@Q8zfH$R&A6#66umW z5;rk4xH#l22hLNj`<0Ecq1HBW*^I zEiP?JBq41|q*vOMNK*1FN2E{kNF*hBB(g>FNTgr+6-I18`6aPI<(I@xQGQ8mtMW@? zL&`6S4J*GSHlqBJSQ>M@B-eo3RLNY16{l*n9np+Tx@0qhSQ^E(7E#GFrcuc=u6SgH zHCr@DShW^RI;C^QOUjsC6Cqm&ey19n9!(XxWGMG zuC}!aLVkG;xZzw$LErk;`>+*FIJ?A;62s*XY8N4zjVK289o?P zj%+i6_poKTf0A|wK5WRs+4+3WIy^b->6YcbaCVU_@0-FjfrVpr*&`(B&M9BSr>B?g yNp4h}SfRdvQ_+dfMYTE>Ip!1>S1#Vi4}8fix)9g$@ZRD+{0v}N28II#|NC#Mk!y(n literal 0 HcmV?d00001 diff --git a/raspberrypi/src/dist/images/semlogo_bottom.svg b/raspberrypi/src/dist/images/semlogo_bottom.svg new file mode 100644 index 00000000..b82efc39 --- /dev/null +++ b/raspberrypi/src/dist/images/semlogo_bottom.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/raspberrypi/src/dist/images/semlogo_top.svg b/raspberrypi/src/dist/images/semlogo_top.svg new file mode 100644 index 00000000..ebf13435 --- /dev/null +++ b/raspberrypi/src/dist/images/semlogo_top.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/raspberrypi/src/dist/images/semlogo_vector_bot.svg b/raspberrypi/src/dist/images/semlogo_vector_bot.svg deleted file mode 100644 index f828482f..00000000 --- a/raspberrypi/src/dist/images/semlogo_vector_bot.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - diff --git a/raspberrypi/src/dist/images/semlogo_vector_top.svg b/raspberrypi/src/dist/images/semlogo_vector_top.svg deleted file mode 100644 index 6f0ef324..00000000 --- a/raspberrypi/src/dist/images/semlogo_vector_top.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - diff --git a/raspberrypi/src/dist/setup.sh b/raspberrypi/src/dist/setup.sh index bb5e346b..30fac8e8 100755 --- a/raspberrypi/src/dist/setup.sh +++ b/raspberrypi/src/dist/setup.sh @@ -2,4 +2,5 @@ mkdir lib/shader mv lib/*.glsl lib/shader/ -mv images/ bin/images \ No newline at end of file +mv images/ bin/images +mv fonts/ bin/fonts \ No newline at end of file diff --git a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt index 1cbc30ab..afde7cff 100644 --- a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt +++ b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt @@ -1,22 +1,33 @@ package dev.matsem.astral.raspberrypi.sketches import ch.bildspur.postfx.builder.PostFX +import dev.matsem.astral.core.Files import dev.matsem.astral.core.tools.animations.AnimationHandler import dev.matsem.astral.core.tools.animations.radianSeconds import dev.matsem.astral.core.tools.extensions.colorModeHsb +import dev.matsem.astral.core.tools.extensions.draw +import dev.matsem.astral.core.tools.extensions.heightF import dev.matsem.astral.core.tools.extensions.pushPop -import dev.matsem.astral.core.tools.extensions.quantize import dev.matsem.astral.core.tools.extensions.shorterDimension +import dev.matsem.astral.core.tools.extensions.translate import dev.matsem.astral.core.tools.extensions.translateCenter +import dev.matsem.astral.core.tools.extensions.widthF import dev.matsem.astral.core.tools.extensions.withAlpha import extruder.extruder import geomerative.RG import geomerative.RShape +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import org.koin.core.KoinComponent import org.koin.core.inject import processing.core.PApplet import processing.core.PConstants +import processing.core.PFont +import processing.core.PGraphics import processing.core.PShape +import processing.core.PVector /** * Uses geomerative library to convert the futuredlogo.svg into 2D shape. Extruder library is then used to extrude the @@ -27,16 +38,31 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { override fun provideMillis(): Int = millis() override fun settings() { - fullScreen(PConstants.P3D) +// fullScreen(PConstants.P3D) + size(1024, 768, PConstants.P3D) } private lateinit var fx: PostFX private val ex: extruder by inject() + private lateinit var font: PFont + private val coroutineScope = CoroutineScope(Dispatchers.Default) - private val logoToScreenScale = 0.4f + private val logoToScreenScale = 0.35f private val shapeDepth = 100 private var sclOff = 0f private var rotYOff = 0f + private val starCount = 3000 + private val fps = 15f + private lateinit var starsCanvas: PGraphics + private lateinit var stars: List + + private val logoPosition = PVector(0f, 0f) + private val logoPositionTarget = PVector(0f, 0f) + private val textPosition = PVector(0f, 0f) + private val textPositionTarget = PVector(0f, 0f) + private var lerpSpeed = 0.2f + + private val mainColor = 0x00ffc8.withAlpha() data class Chunk( val originalShape: RShape, @@ -51,12 +77,13 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { surface.setTitle("Futured") surface.setResizable(true) surface.hideCursor() + frameRate(fps) RG.init(this) RG.setPolygonizer(RG.UNIFORMSTEP) RG.setPolygonizerStep(1f) - chunks = listOf("images/semlogo_vector_top.svg", "images/semlogo_vector_bot.svg") + chunks = listOf("images/semlogo_top.svg", "images/semlogo_bottom.svg") .map { uri -> RG.loadShape(uri) } .map { rshape -> RG.polygonize(rshape) } .map { rshape -> @@ -85,43 +112,128 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { ) } + starsCanvas = createGraphics(width, height, PConstants.P3D) + stars = generateSequence { + PVector( + random(-width.toFloat(), width.toFloat()), + random(-width.toFloat(), width.toFloat()), + random(-width.toFloat(), width.toFloat()) + ) + }.take(starCount).toList() + + font = createFont(Files.Font.JETBRAINS_MONO, height / 20f, false) fx = PostFX(this) + + coroutineScope.launch { + while (isActive) { + logoPositionTarget.set(0f, 0f) + textPositionTarget.set(widthF, 0f) + kotlinx.coroutines.delay(30_000L) + + logoPositionTarget.set(-widthF * 0.4f, 0f) + textPositionTarget.set(-widthF * 0.2f, 0f) + kotlinx.coroutines.delay(30_000L) + } + } } override fun draw() { - ortho() background(0) - strokeWeight(6f) - stroke(0x00ffc8.withAlpha()) - when { - mouseX.toFloat() in (0f..width / 3f) -> noFill() - mouseX.toFloat() in (width / 3f..width / 3f * 2f) -> fill(0xffffff.withAlpha(0)) - else -> fill(0) - } - if (random(0f, 10f).quantize(0.1f) == 0f) { - sclOff = random(-0.1f, 0.1f) - rotYOff = random(-PI * .2f, PI * .2f) + // Update props + + stars.forEach { + it.z += 2f + if (it.z > width) { + it.z = random(-width.toFloat(), 0f) + } } - translateCenter() - for (chunk in chunks) { + logoPosition.lerp(logoPositionTarget, lerpSpeed) + textPosition.lerp(textPositionTarget, lerpSpeed) + + // endregion + + // region Starfield + + starsCanvas.draw { + fill(0x000000.withAlpha(32)) + rect(0f, 0f, widthF, heightF) + pushPop { - scale( - width / chunk.shapeWidth * logoToScreenScale + sclOff, - width / chunk.shapeWidth * logoToScreenScale + sclOff - ) + noStroke() + fill(mainColor.withAlpha(128)) + translateCenter() + + stars.forEach { + pushPop { + translate(it.x, it.y, it.z) + circle(0f, 0f, 3f) + } + } + } + } + + pushPop { + translate(0f, 0f, -400f) + image(starsCanvas, 0f, 0f) + } - rotateX(-PI * 0.1f) - rotateY(radianSeconds(30f) + rotYOff) - chunk.extrudedShape.forEach { - shape(it) + // endregion + + ortho() + + // region Logo + + pushPop { + translateCenter() + translate(logoPosition) + strokeWeight(10f) + stroke(0) + fill(mainColor) + + for (chunk in chunks) { + pushPop { + scale( + width / chunk.shapeWidth * logoToScreenScale + sclOff, + width / chunk.shapeWidth * logoToScreenScale + sclOff + ) + + rotateX(PI * 0.1f * sin(radianSeconds(60f))) + rotateY(-radianSeconds(30f) + rotYOff) + chunk.extrudedShape.forEach { + shape(it) + } } } } + // endregion + + // region Info text + + val text = """ + 07:00pm matsem + 08:30pm attempt + 10:00pm SEBA + 11:30pm NEED FOR MIRRORS + 01:00am sbu & rough:result + """.trimIndent() + + pushPop { + translateCenter() + translate(textPosition) + textFont(font) + textAlign(LEFT, CENTER) + noStroke() + fill(mainColor) + text(text, 0f, 0f) + } + + // endregion + fx.render().apply { - noise(0.2f, 0.1f) + noise(0.3f, 0.1f) pixelate(shorterDimension() / 2.4f) }.compose() } From 4bc65d32ba396e038be52e23132968954f5faa73 Mon Sep 17 00:00:00 2001 From: matsem Date: Tue, 20 Sep 2022 23:43:17 +0200 Subject: [PATCH 05/13] Cycle logo through various render styles --- .../astral/raspberrypi/sketches/NeonLogo.kt | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt index afde7cff..5fe5df5a 100644 --- a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt +++ b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt @@ -38,8 +38,8 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { override fun provideMillis(): Int = millis() override fun settings() { -// fullScreen(PConstants.P3D) - size(1024, 768, PConstants.P3D) + fullScreen(PConstants.P3D) +// size(1024, 768, PConstants.P3D) } private lateinit var fx: PostFX @@ -47,6 +47,9 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { private lateinit var font: PFont private val coroutineScope = CoroutineScope(Dispatchers.Default) + private val mainColor = 0x00ffc8.withAlpha() + private val bgColor = 0x000000 + private val logoToScreenScale = 0.35f private val shapeDepth = 100 private var sclOff = 0f @@ -55,14 +58,21 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { private val fps = 15f private lateinit var starsCanvas: PGraphics private lateinit var stars: List - private val logoPosition = PVector(0f, 0f) private val logoPositionTarget = PVector(0f, 0f) private val textPosition = PVector(0f, 0f) private val textPositionTarget = PVector(0f, 0f) private var lerpSpeed = 0.2f + private val renderStyles = listOf( + RenderStyle(fillColor = mainColor, strokeColor = bgColor, strokeWidth = 10f), + RenderStyle(fillColor = bgColor, strokeColor = mainColor, strokeWidth = 10f), + RenderStyle(fillColor = null, strokeColor = mainColor, strokeWidth = 10f) + ) + private var renderStyle = renderStyles.first() - private val mainColor = 0x00ffc8.withAlpha() + private val logoActiveIntervalMs = 10_000L + private val textActiveIntervalMs = 30_000L + private val renderStyleSwitchIntervalMs = 5 * 60 * 1000L data class Chunk( val originalShape: RShape, @@ -71,6 +81,12 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { val shapeHeight: Float ) + data class RenderStyle( + val fillColor: Int?, + val strokeColor: Int, + val strokeWidth: Float + ) + private lateinit var chunks: List override fun setup() { colorModeHsb() @@ -128,11 +144,11 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { while (isActive) { logoPositionTarget.set(0f, 0f) textPositionTarget.set(widthF, 0f) - kotlinx.coroutines.delay(30_000L) + kotlinx.coroutines.delay(logoActiveIntervalMs) logoPositionTarget.set(-widthF * 0.4f, 0f) - textPositionTarget.set(-widthF * 0.2f, 0f) - kotlinx.coroutines.delay(30_000L) + textPositionTarget.set(-widthF * 0.15f, 0f) + kotlinx.coroutines.delay(textActiveIntervalMs) } } } @@ -152,12 +168,16 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { logoPosition.lerp(logoPositionTarget, lerpSpeed) textPosition.lerp(textPositionTarget, lerpSpeed) + if (millis() % renderStyleSwitchIntervalMs in 0 until 1000) { + renderStyle = renderStyles.random() + } + // endregion // region Starfield starsCanvas.draw { - fill(0x000000.withAlpha(32)) + fill(bgColor.withAlpha(32)) rect(0f, 0f, widthF, heightF) pushPop { @@ -188,9 +208,13 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { pushPop { translateCenter() translate(logoPosition) - strokeWeight(10f) - stroke(0) - fill(mainColor) + strokeWeight(renderStyle.strokeWidth) + stroke(renderStyle.strokeColor) + renderStyle.fillColor?.let { + fill(it) + } ?: run { + noFill() + } for (chunk in chunks) { pushPop { From 74d29c74aaa60d9c23f8f4ef6a54d69a06999512 Mon Sep 17 00:00:00 2001 From: matsem Date: Wed, 21 Sep 2022 17:44:14 +0200 Subject: [PATCH 06/13] NeonLogo: Load & watch lineup from file --- data/lineup.txt | 5 +++ raspberrypi/src/dist/lineup.txt | 5 +++ raspberrypi/src/dist/setup.sh | 3 +- .../astral/raspberrypi/sketches/NeonLogo.kt | 35 ++++++++++--------- 4 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 data/lineup.txt create mode 100644 raspberrypi/src/dist/lineup.txt diff --git a/data/lineup.txt b/data/lineup.txt new file mode 100644 index 00000000..1e019125 --- /dev/null +++ b/data/lineup.txt @@ -0,0 +1,5 @@ +07:00pm matsem +08:30pm attempt +10:00pm SEBA +11:30pm NEED FOR MIRRORS +01:00am sbu & rough:result \ No newline at end of file diff --git a/raspberrypi/src/dist/lineup.txt b/raspberrypi/src/dist/lineup.txt new file mode 100644 index 00000000..1e019125 --- /dev/null +++ b/raspberrypi/src/dist/lineup.txt @@ -0,0 +1,5 @@ +07:00pm matsem +08:30pm attempt +10:00pm SEBA +11:30pm NEED FOR MIRRORS +01:00am sbu & rough:result \ No newline at end of file diff --git a/raspberrypi/src/dist/setup.sh b/raspberrypi/src/dist/setup.sh index 30fac8e8..a3a11abc 100755 --- a/raspberrypi/src/dist/setup.sh +++ b/raspberrypi/src/dist/setup.sh @@ -3,4 +3,5 @@ mkdir lib/shader mv lib/*.glsl lib/shader/ mv images/ bin/images -mv fonts/ bin/fonts \ No newline at end of file +mv fonts/ bin/fonts +mv lineup.txt bin/lineup.txt \ No newline at end of file diff --git a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt index 5fe5df5a..ab70292d 100644 --- a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt +++ b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt @@ -28,6 +28,7 @@ import processing.core.PFont import processing.core.PGraphics import processing.core.PShape import processing.core.PVector +import java.io.File /** * Uses geomerative library to convert the futuredlogo.svg into 2D shape. Extruder library is then used to extrude the @@ -37,15 +38,12 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { override fun provideMillis(): Int = millis() - override fun settings() { - fullScreen(PConstants.P3D) -// size(1024, 768, PConstants.P3D) - } - private lateinit var fx: PostFX private val ex: extruder by inject() private lateinit var font: PFont private val coroutineScope = CoroutineScope(Dispatchers.Default) + private lateinit var textFile: File + private lateinit var displayedText: String private val mainColor = 0x00ffc8.withAlpha() private val bgColor = 0x000000 @@ -70,9 +68,10 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { ) private var renderStyle = renderStyles.first() - private val logoActiveIntervalMs = 10_000L - private val textActiveIntervalMs = 30_000L + private val logoActiveIntervalMs = 5_000L + private val textActiveIntervalMs = 60_000L private val renderStyleSwitchIntervalMs = 5 * 60 * 1000L + private val textFileReadIntervalMs = 5_000L data class Chunk( val originalShape: RShape, @@ -88,6 +87,10 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { ) private lateinit var chunks: List + override fun settings() { + fullScreen(PConstants.P3D) +// size(1024, 768, PConstants.P3D) + } override fun setup() { colorModeHsb() surface.setTitle("Futured") @@ -138,6 +141,7 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { }.take(starCount).toList() font = createFont(Files.Font.JETBRAINS_MONO, height / 20f, false) + textFile = dataFile("other/lineup.txt") fx = PostFX(this) coroutineScope.launch { @@ -151,6 +155,13 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { kotlinx.coroutines.delay(textActiveIntervalMs) } } + + coroutineScope.launch(Dispatchers.IO) { + while(isActive) { + displayedText = textFile.readText().trimIndent() + kotlinx.coroutines.delay(textFileReadIntervalMs) + } + } } override fun draw() { @@ -236,14 +247,6 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { // region Info text - val text = """ - 07:00pm matsem - 08:30pm attempt - 10:00pm SEBA - 11:30pm NEED FOR MIRRORS - 01:00am sbu & rough:result - """.trimIndent() - pushPop { translateCenter() translate(textPosition) @@ -251,7 +254,7 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { textAlign(LEFT, CENTER) noStroke() fill(mainColor) - text(text, 0f, 0f) + text(displayedText, 0f, 0f) } // endregion From 8b2ec253a7d238a05ebe7797f9571a8f8ba79020 Mon Sep 17 00:00:00 2001 From: matsem Date: Thu, 22 Sep 2022 23:11:07 +0200 Subject: [PATCH 07/13] NeonLogo: Load lineup from desktop file --- data/lineup.txt | 5 ----- raspberrypi/src/dist/lineup.txt | 5 ----- raspberrypi/src/dist/setup.sh | 3 +-- .../dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt | 4 ++-- 4 files changed, 3 insertions(+), 14 deletions(-) delete mode 100644 data/lineup.txt delete mode 100644 raspberrypi/src/dist/lineup.txt diff --git a/data/lineup.txt b/data/lineup.txt deleted file mode 100644 index 1e019125..00000000 --- a/data/lineup.txt +++ /dev/null @@ -1,5 +0,0 @@ -07:00pm matsem -08:30pm attempt -10:00pm SEBA -11:30pm NEED FOR MIRRORS -01:00am sbu & rough:result \ No newline at end of file diff --git a/raspberrypi/src/dist/lineup.txt b/raspberrypi/src/dist/lineup.txt deleted file mode 100644 index 1e019125..00000000 --- a/raspberrypi/src/dist/lineup.txt +++ /dev/null @@ -1,5 +0,0 @@ -07:00pm matsem -08:30pm attempt -10:00pm SEBA -11:30pm NEED FOR MIRRORS -01:00am sbu & rough:result \ No newline at end of file diff --git a/raspberrypi/src/dist/setup.sh b/raspberrypi/src/dist/setup.sh index a3a11abc..30fac8e8 100755 --- a/raspberrypi/src/dist/setup.sh +++ b/raspberrypi/src/dist/setup.sh @@ -3,5 +3,4 @@ mkdir lib/shader mv lib/*.glsl lib/shader/ mv images/ bin/images -mv fonts/ bin/fonts -mv lineup.txt bin/lineup.txt \ No newline at end of file +mv fonts/ bin/fonts \ No newline at end of file diff --git a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt index ab70292d..86d58003 100644 --- a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt +++ b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt @@ -68,7 +68,7 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { ) private var renderStyle = renderStyles.first() - private val logoActiveIntervalMs = 5_000L + private val logoActiveIntervalMs = 35_000L private val textActiveIntervalMs = 60_000L private val renderStyleSwitchIntervalMs = 5 * 60 * 1000L private val textFileReadIntervalMs = 5_000L @@ -141,7 +141,7 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { }.take(starCount).toList() font = createFont(Files.Font.JETBRAINS_MONO, height / 20f, false) - textFile = dataFile("other/lineup.txt") + textFile = desktopFile("lineup.txt") fx = PostFX(this) coroutineScope.launch { From 9c8e7779be020d329695514a3134b9b046821811 Mon Sep 17 00:00:00 2001 From: matsem Date: Fri, 23 Sep 2022 01:00:17 +0200 Subject: [PATCH 08/13] NeonLogo: Fix PShape rendering issues --- .../astral/raspberrypi/sketches/NeonLogo.kt | 79 ++++++++++++------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt index 86d58003..ca850cd0 100644 --- a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt +++ b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt @@ -29,6 +29,7 @@ import processing.core.PGraphics import processing.core.PShape import processing.core.PVector import java.io.File +import java.util.* /** * Uses geomerative library to convert the futuredlogo.svg into 2D shape. Extruder library is then used to extrude the @@ -45,15 +46,15 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { private lateinit var textFile: File private lateinit var displayedText: String - private val mainColor = 0x00ffc8.withAlpha() - private val bgColor = 0x000000 +// private val mainColor = 0x00ffc8.withAlpha() +// private val bgColor = 0x000000 private val logoToScreenScale = 0.35f private val shapeDepth = 100 private var sclOff = 0f private var rotYOff = 0f private val starCount = 3000 - private val fps = 15f + private val fps = 20f private lateinit var starsCanvas: PGraphics private lateinit var stars: List private val logoPosition = PVector(0f, 0f) @@ -61,17 +62,23 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { private val textPosition = PVector(0f, 0f) private val textPositionTarget = PVector(0f, 0f) private var lerpSpeed = 0.2f - private val renderStyles = listOf( - RenderStyle(fillColor = mainColor, strokeColor = bgColor, strokeWidth = 10f), - RenderStyle(fillColor = bgColor, strokeColor = mainColor, strokeWidth = 10f), - RenderStyle(fillColor = null, strokeColor = mainColor, strokeWidth = 10f) + private val renderStyles = arrayOf( + RenderStyle(fillColor = 0x00ffc8, strokeColor = 0x000000, strokeWeight = 10f), + RenderStyle(fillColor = 0x000000, strokeColor = 0x00ffc8, strokeWeight = 10f), + RenderStyle(fillColor = null, strokeColor = 0x00ffc8, strokeWeight = 10f) ) private var renderStyle = renderStyles.first() - private val logoActiveIntervalMs = 35_000L - private val textActiveIntervalMs = 60_000L - private val renderStyleSwitchIntervalMs = 5 * 60 * 1000L - private val textFileReadIntervalMs = 5_000L + private val props: Properties = Properties() + private var logoActiveIntervalMs = 1000L + private var textActiveIntervalMs = 1000L + private var renderStyleSwitchIntervalMs = 1000L + private val fileReadIntervalMs = 5_000L + + companion object { + const val LineupFile = "lineup.txt" + const val PropsFile = "render.properties" + } data class Chunk( val originalShape: RShape, @@ -83,19 +90,21 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { data class RenderStyle( val fillColor: Int?, val strokeColor: Int, - val strokeWidth: Float + val strokeWeight: Float ) private lateinit var chunks: List override fun settings() { - fullScreen(PConstants.P3D) -// size(1024, 768, PConstants.P3D) +// fullScreen(PConstants.P3D) + size(1024, 768, PConstants.P3D) } + override fun setup() { colorModeHsb() surface.setTitle("Futured") surface.setResizable(true) surface.hideCursor() + surface.setAlwaysOnTop(true) frameRate(fps) RG.init(this) @@ -124,7 +133,7 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { } .flatMap { ex.extrude(it, 100, "box").toList() } .onEach { - it.disableStyle() + it.enableStyle() it.translate(-rshape.width / 2f, -rshape.height / 2f, -shapeDepth / 2f) } .toList() @@ -141,7 +150,7 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { }.take(starCount).toList() font = createFont(Files.Font.JETBRAINS_MONO, height / 20f, false) - textFile = desktopFile("lineup.txt") + textFile = desktopFile(LineupFile) fx = PostFX(this) coroutineScope.launch { @@ -157,15 +166,23 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { } coroutineScope.launch(Dispatchers.IO) { - while(isActive) { + while (isActive) { displayedText = textFile.readText().trimIndent() - kotlinx.coroutines.delay(textFileReadIntervalMs) + + props.load(desktopFile(PropsFile).inputStream()) + logoActiveIntervalMs = props["visuals.logo.duration_ms"].toString().toLongOrNull() ?: 35_000L + textActiveIntervalMs = props["visuals.text.duration_ms"].toString().toLongOrNull() ?: 60_000L + renderStyleSwitchIntervalMs = + props["visuals.logo.style.duration_ms"].toString().toLongOrNull() ?: 120_000L + + kotlinx.coroutines.delay(fileReadIntervalMs) } } } override fun draw() { background(0) + ortho() // Update props @@ -188,12 +205,12 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { // region Starfield starsCanvas.draw { - fill(bgColor.withAlpha(32)) + fill(0x000000.withAlpha(32)) rect(0f, 0f, widthF, heightF) pushPop { noStroke() - fill(mainColor.withAlpha(128)) + fill(0x00ffc8.withAlpha(128)) translateCenter() stars.forEach { @@ -212,20 +229,13 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { // endregion - ortho() - // region Logo pushPop { translateCenter() translate(logoPosition) - strokeWeight(renderStyle.strokeWidth) - stroke(renderStyle.strokeColor) - renderStyle.fillColor?.let { - fill(it) - } ?: run { - noFill() - } + + val renderStyle = renderStyle for (chunk in chunks) { pushPop { @@ -237,6 +247,15 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { rotateX(PI * 0.1f * sin(radianSeconds(60f))) rotateY(-radianSeconds(30f) + rotYOff) chunk.extrudedShape.forEach { + it.setFill(true) + if (renderStyle.fillColor != null) { + it.setFill(renderStyle.fillColor.withAlpha()) + } else { + it.setFill(0x00000000) + } + it.setStroke(true) + it.setStroke(renderStyle.strokeColor.withAlpha()) + it.setStrokeWeight(renderStyle.strokeWeight) shape(it) } } @@ -253,7 +272,7 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { textFont(font) textAlign(LEFT, CENTER) noStroke() - fill(mainColor) + fill(0x00ffc8.withAlpha()) text(displayedText, 0f, 0f) } From 71be1675df375985d52c288cf69a8ffa67d8d5a5 Mon Sep 17 00:00:00 2001 From: matsem Date: Fri, 23 Sep 2022 01:01:35 +0200 Subject: [PATCH 09/13] NeonLogo: Cleanup --- .../dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt index ca850cd0..f16f7af0 100644 --- a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt +++ b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt @@ -46,9 +46,6 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { private lateinit var textFile: File private lateinit var displayedText: String -// private val mainColor = 0x00ffc8.withAlpha() -// private val bgColor = 0x000000 - private val logoToScreenScale = 0.35f private val shapeDepth = 100 private var sclOff = 0f @@ -95,8 +92,8 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { private lateinit var chunks: List override fun settings() { -// fullScreen(PConstants.P3D) - size(1024, 768, PConstants.P3D) + fullScreen(PConstants.P3D) +// size(1024, 768, PConstants.P3D) } override fun setup() { From dfd46718c5e3047cfd36a24f1e97f8bd96956e6b Mon Sep 17 00:00:00 2001 From: matsem Date: Fri, 23 Sep 2022 01:07:10 +0200 Subject: [PATCH 10/13] NeonLogo: Make lineup and props files if they do not exist --- .../astral/raspberrypi/sketches/NeonLogo.kt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt index f16f7af0..3389f05b 100644 --- a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt +++ b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt @@ -150,6 +150,8 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { textFile = desktopFile(LineupFile) fx = PostFX(this) + makeFiles() + coroutineScope.launch { while (isActive) { logoPositionTarget.set(0f, 0f) @@ -177,6 +179,30 @@ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { } } + private fun makeFiles() { + val textFile = desktopFile(LineupFile) + val propsFile = desktopFile(PropsFile) + if (textFile.exists().not()) { + textFile.createNewFile() + textFile.writeText( + """ + ~/Desktop/lineup.txt + """.trimIndent() + ) + } + + if (propsFile.exists().not()) { + propsFile.createNewFile() + propsFile.writeText( + """ + visuals.logo.style.duration_ms=5000 + visuals.logo.duration_ms=5000 + visuals.text.duration_ms=5000 + """.trimIndent() + ) + } + } + override fun draw() { background(0) ortho() From 05a644e26e8240e2248aa3b6b4a15cf60059a021 Mon Sep 17 00:00:00 2001 From: matsem Date: Wed, 28 Sep 2022 14:49:46 +0200 Subject: [PATCH 11/13] Update NeonLogo doc --- .../dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt index 3389f05b..df4ad319 100644 --- a/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt +++ b/raspberrypi/src/main/kotlin/dev/matsem/astral/raspberrypi/sketches/NeonLogo.kt @@ -32,8 +32,13 @@ import java.io.File import java.util.* /** - * Uses geomerative library to convert the futuredlogo.svg into 2D shape. Extruder library is then used to extrude the + * The standalone raspberry pi sketch. + * + * Uses geomerative library to convert an SVG logo into 2D shape. Extruder library is then used to extrude the * logo into 3D shape. Renders with oldskool playstation-one-like effect using PostFX shaders. + * + * This sketch creates lineup.txt and render.properties files on your desktop. Use them to display text and modify render + * settings of the scene. The files are watched and the sketch content will be updated upon modification of these files. */ class NeonLogo : PApplet(), AnimationHandler, KoinComponent { From 19977ff48c756baf88a30adec9b5da9196a09b45 Mon Sep 17 00:00:00 2001 From: matsem Date: Wed, 28 Sep 2022 14:56:31 +0200 Subject: [PATCH 12/13] Update README --- README.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2c2ed7be..07e05448 100644 --- a/README.md +++ b/README.md @@ -5,19 +5,24 @@ Just some Processing sketches. Source code for visuals we use at [Soul Ex Machin ![](demo-gif.gif) -The project is divided into 3 modules, `:core` module cointains the core stuff like audio processing, tools, remote control handlers, extensions, etc. Then there are two application modules - the `:playground` and `:visuals` module. +The project is divided into multiple modules. -The `:playground` module serves as, well... playground. You can quickly create a new sketch and play around. I'm using the [Koin](https://insert-koin.io/) DI framework, so you can inject here whatever is defined in the `CoreModule`. Have a look around. +The `:core` module contains the core stuff like audio processing, tools, remote control handlers, extensions, etc. + +The `:playground` module serves as, well... playground. Used to quickly create a new sketch and play around. I'm using the [Koin](https://insert-koin.io/) DI framework, so you can inject here whatever is defined in the `CoreModule`. Have a look around. The `:visuals` module is meant to be used in live environment at the parties. There is an abstraction layer in form of `Mixer` and `Layer`s, which allows me to blend multiple scenes together. Also, have a look around, proceed at your own risk, ignore `legacy` package 😅 (I like to change things, API is generally unstable). +The `:raspberrypi` module contains standalone RPi application that can be distributed using the [Application Gradle plugin](https://docs.gradle.org/current/userguide/application_plugin.html). + ## How to build This project depends on local [Processing 4](https://processing.org) installation, so go ahead and install it if you haven't already. Then create a `local.properties` file in project's root directory and configure the core library and contributed libraries' paths: ``` -processing.core.jars=/path/to/your/processing/libraries/dir -processing.core.natives=/path/to/your/processing/libraries/dir/ +processing.core.jars=/path/to/core/processing/libraries +processing.core.natives=/path/to/core/processing/libraries/ +processing.core.natives.rpi=/path/to/core/processing/libraries/ processing.libs.jars=/path/to/core/processing/libraries ``` @@ -26,8 +31,11 @@ On macOS it might look like this: ``` processing.core.jars=/Applications/Processing.app/Contents/Java/core/library processing.core.natives=/Applications/Processing.app/Contents/Java/core/library/macos-x86_64 +processing.core.natives.rpi=/Applications/Processing.app/Contents/Java/core/library/linux-aarch64 processing.libs.jars=/Users/matsem/Documents/Processing/libraries ``` +Note the difference between `processing.core.natives` and `processing.core.natives.rpi`. +The Raspberry Pi libs have to be configured if you wish to use the `:raspberrypi` module. The Gradle buildscript will look for Processing dependencies at these two paths. Dependencies are defined in CommonDependencies gradle plugin. Open it up, and you can notice that this project depends on some 3rd party libraries, which need to be installed at `processing.libs.jars` path. Open your Processing library manager (Sketch > Import Library > Add library) and install whatever libraries are specified in the `build.gradle` file. @@ -49,6 +57,11 @@ val processingLibs = listOf( ) ``` + +### :raspberrypi module +The Raspberry Pi app can be installed using `./gradlew raspberrypi:installDist` task and zipped using `./gradlew raspberrypi:distZip` task. +See the [Application Plugin](https://docs.gradle.org/current/userguide/application_plugin.html) docs for more info. + ## How to run You can run the project with Gradle `run` task. Be sure to include the `--sketch-path` argument so sketches can properly resolve the data folder containing resources needed by some Sketches. From 23ae496482fec1939aefe7a2bae810e54e0fd678 Mon Sep 17 00:00:00 2001 From: matsem Date: Wed, 28 Sep 2022 14:59:34 +0200 Subject: [PATCH 13/13] Bump project version --- buildSrc/src/main/kotlin/ProjectSettings.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/ProjectSettings.kt b/buildSrc/src/main/kotlin/ProjectSettings.kt index c10358eb..651f0883 100644 --- a/buildSrc/src/main/kotlin/ProjectSettings.kt +++ b/buildSrc/src/main/kotlin/ProjectSettings.kt @@ -1,4 +1,4 @@ object ProjectSettings { - const val version = "2.1.0" + const val version = "2.2.0" const val group = "dev.matsem" } \ No newline at end of file