From fd448b185ad20c994421d55eebcb62b8e5959950 Mon Sep 17 00:00:00 2001 From: Paul Wisbey Date: Tue, 28 Mar 2017 19:36:10 +0100 Subject: [PATCH] Port the sparkle effect demo (Based on original demo by Xiangyin Ma) Change-Id: I3afe255cea69df3794ffdbafa7171f17f5518dec --- com.samsung.dali-demo.xml | 3 + demo/dali-demo.cpp | 1 + examples/sparkle/sparkle-effect-example.cpp | 568 +++++++++++++++++++++++++ examples/sparkle/sparkle-effect.h | 393 +++++++++++++++++ resources/images/sparkle_normal_background.png | Bin 0 -> 32234 bytes resources/images/sparkle_particle.png | Bin 0 -> 1095 bytes resources/po/as.po | 3 + resources/po/de.po | 3 + resources/po/en_GB.po | 3 + resources/po/en_US.po | 3 + resources/po/es.po | 3 + resources/po/ko.po | 3 + resources/po/ml.po | 3 + resources/po/ur.po | 3 + resources/po/zn_CH.po | 3 + shared/dali-demo-strings.h | 2 + 16 files changed, 994 insertions(+) create mode 100644 examples/sparkle/sparkle-effect-example.cpp create mode 100644 examples/sparkle/sparkle-effect.h create mode 100755 resources/images/sparkle_normal_background.png create mode 100755 resources/images/sparkle_particle.png diff --git a/com.samsung.dali-demo.xml b/com.samsung.dali-demo.xml index 3db69ee..843f862 100644 --- a/com.samsung.dali-demo.xml +++ b/com.samsung.dali-demo.xml @@ -181,4 +181,7 @@ + + + diff --git a/demo/dali-demo.cpp b/demo/dali-demo.cpp index cebd1d0..c3d5a6d 100644 --- a/demo/dali-demo.cpp +++ b/demo/dali-demo.cpp @@ -80,6 +80,7 @@ int DALI_EXPORT_API main(int argc, char **argv) demo.AddExample(Example("mesh-visual.example", DALI_DEMO_STR_TITLE_MESH_VISUAL)); demo.AddExample(Example("primitive-shapes.example", DALI_DEMO_STR_TITLE_PRIMITIVE_SHAPES)); demo.AddExample(Example("styling.example", DALI_DEMO_STR_TITLE_STYLING)); + demo.AddExample(Example("sparkle.example", DALI_DEMO_STR_TITLE_SPARKLE)); demo.AddExample(Example("rendering-line.example", DALI_DEMO_STR_TITLE_RENDERING_DRAW_LINE)); demo.AddExample(Example("rendering-triangle.example", DALI_DEMO_STR_TITLE_RENDERING_DRAW_TRIANGLE)); demo.AddExample(Example("rendering-cube.example", DALI_DEMO_STR_TITLE_RENDERING_DRAW_CUBE)); diff --git a/examples/sparkle/sparkle-effect-example.cpp b/examples/sparkle/sparkle-effect-example.cpp new file mode 100644 index 0000000..1cc465a --- /dev/null +++ b/examples/sparkle/sparkle-effect-example.cpp @@ -0,0 +1,568 @@ +/* + * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include + +#include +#include +#include + +#include "shared/utility.h" +#include "sparkle-effect.h" + +using namespace Dali; +using Dali::Toolkit::ImageView; + +using namespace SparkleEffect; + +namespace // unnamed namespace +{ + +//background image for normal status +const char * const CIRCLE_BACKGROUND_IMAGE( DEMO_IMAGE_DIR "sparkle_normal_background.png" ); +//particle shape image +const char * const PARTICLE_IMAGE( DEMO_IMAGE_DIR "sparkle_particle.png" ); + +float EaseOutSquare( float progress ) +{ + return 1.0f - (1.0f-progress) * (1.0f-progress); +} + +float CustomBounce( float progress ) +{ + float p = 1.f-progress; + p *=p; + return 17.68f*p*p*p*progress; +} + +float Mix( const Vector2& range, float a ) +{ + return range.x * a + range.y*(1.f-a)-0.001f; +} + +const Vector4 BACKGROUND_COLOR( 0.f, 0.f, 0.05f, 1.f ); + +} // unnamed namespace + +// This example shows a sparkle particle effect +// +class SparkleEffectExample : public ConnectionTracker +{ +public: + + /** + * Create the SparkleEffectExample + * @param[in] application The DALi application instance + */ + SparkleEffectExample( Application& application ) + : mApplication( application ), + mAnimationIndex( 0u ), + mShaking( false ) + { + mApplication.InitSignal().Connect( this, &SparkleEffectExample::OnInit ); + } + +private: + + /** + * Initialize the SparkleEffectExample + * @param[in] application The DALi application instance + */ + void OnInit( Application& application ) + { + Stage stage = Stage::GetCurrent(); + stage.KeyEventSignal().Connect(this, &SparkleEffectExample::OnKeyEvent); + stage.SetBackgroundColor( BACKGROUND_COLOR ); + + mCircleBackground = ImageView::New( CIRCLE_BACKGROUND_IMAGE ); + mCircleBackground.SetParentOrigin( ParentOrigin::CENTER ); + mCircleBackground.SetAnchorPoint( AnchorPoint::CENTER ); + + stage.Add( mCircleBackground ); + + mEffect = SparkleEffect::New(); + + mMeshActor = CreateMeshActor(); + + stage.Add( mMeshActor ); + + mMeshActor.SetPosition( ACTOR_POSITION ); + mMeshActor.SetScale( ACTOR_SCALE ); + + mTapDetector = TapGestureDetector::New(); + mTapDetector.Attach(mCircleBackground); + mTapDetector.DetectedSignal().Connect( this, &SparkleEffectExample::OnTap ); + + mPanGestureDetector = PanGestureDetector::New(); + mPanGestureDetector.DetectedSignal().Connect( this, &SparkleEffectExample::OnPan ); + mPanGestureDetector.Attach( mCircleBackground ); + + PlayWanderAnimation( 35.f ); + } + + /** + * Create the mesh representing all the particles + */ + Actor CreateMeshActor() + { + // shuffling to assign the color in random order + unsigned int* shuffleArray = new unsigned int[NUM_PARTICLE]; + for( unsigned int i = 0; i vertices; + std::vector< unsigned short > faces; + + for( unsigned int i = 0; i(thereshold-particleIndex)/PARTICLE_COLORS[i].numParticle ); + } + } + return NUM_COLOR-1; + } + + /** + * All a particle to the mesh by giving the moving path and color index + * + * Two triangles per particle + * 0---------3 + * |\ | + * | \ | + * | \ | + * | \| + * 1---------2 + * + * The information we need to pass in through attribute include: + * + * path which contains 12 integer + * ---- passed in 6 Vector2 attributes + * + * color index, particle index and textureCoor( (0,0) or (1,0) or (0,1) or (1,1) ) + * ---- package these info into texCood attribute as: (+-colorIndex, +-particleIndex) + */ + void AddParticletoMesh( std::vector< Vertex >& vertices, + std::vector< unsigned short >& faces, + MovingPath& movingPath, + float colorIndex ) + { + unsigned int idx = vertices.size(); + + // store the path into position and normal, which would be decoded inside the shader + Vector2 particlePath0( movingPath[0], movingPath[1] ); + Vector2 particlePath1( movingPath[2], movingPath[3] ); + Vector2 particlePath2( movingPath[4], movingPath[5] ); + Vector2 particlePath3( movingPath[6], movingPath[7] ); + Vector2 particlePath4( movingPath[8], movingPath[9] ); + Vector2 particlePath5( movingPath[10], movingPath[11] ); + + float particleIdx = static_cast(idx/4 + 1); // count from 1 + float colorIdx = colorIndex+1.f; // count from 1 + vertices.push_back( Vertex( Vector2(-colorIdx, -particleIdx), particlePath0, particlePath1, particlePath2, particlePath3, particlePath4, particlePath5 ) ); + vertices.push_back( Vertex( Vector2(-colorIdx, particleIdx), particlePath0, particlePath1, particlePath2, particlePath3, particlePath4, particlePath5 ) ); + vertices.push_back( Vertex( Vector2( colorIdx, particleIdx), particlePath0, particlePath1, particlePath2, particlePath3, particlePath4, particlePath5 ) ); + vertices.push_back( Vertex( Vector2( colorIdx, -particleIdx), particlePath0, particlePath1, particlePath2, particlePath3, particlePath4, particlePath5 ) ); + + faces.push_back(idx); + faces.push_back(idx+1); + faces.push_back(idx+2); + + faces.push_back(idx); + faces.push_back(idx+2); + faces.push_back(idx+3); + } + + /* + * Main key event handler + */ + void OnKeyEvent(const KeyEvent& event) + { + if(event.state == KeyEvent::Down) + { + if( IsKey( event, Dali::DALI_KEY_ESCAPE) || IsKey( event, Dali::DALI_KEY_BACK) ) + { + mApplication.Quit(); + } + } + } + + /** + * Callback of the TapGesture + */ + void OnTap( Actor actor, const TapGesture& tap ) + { + { + PlayTapAnimation(5.f, tap.localPoint); + } + } + + /** + * Callback of the PanGesture + */ + void OnPan( Actor actor, const PanGesture& gesture ) + { + if( gesture.state == Gesture::Finished ) + { + switch(mAnimationIndex) + { + case 0: + { + PlayParticleFadeAnimation(0, NUM_PARTICLE, 0.f, 3.f ); + break; + } + case 1: + { + PlayBreakAnimation(2.0f); + break; + } + case 2: + { + PlayShakeAnimation(0.5f, 2.5f); + break; + } + default: + { + break; + } + } + + mAnimationIndex = (mAnimationIndex+1)%3; + } + } + + /** + * Animate the particle position to make them wandering on the screen with 'seemingly' random fade in/out + * @param[in] duration The duration for the particle to move a cycle on the path. the bigger this value the slower the floating movement. + * @param[in] looping Infinite playing or not + */ + void PlayWanderAnimation( float duration, bool looping = true ) + { + Animation wanderAnimation= Animation::New(duration); + wanderAnimation.AnimateTo( Property( mEffect, PERCENTAGE_UNIFORM_NAME ), 1.f ); + wanderAnimation.SetLooping(looping); // infinite playing + + wanderAnimation.Play(); + } + + /** + * Accelerate the particle moving speed + * @param[in] cycle How many extra cycles to move during the animation + * @param[in] duration The duration for the animation + */ + void PlayShakeAnimation( float cycle, float duration ) + { + if( mShaking ) + { + return; + } + DestroyAnimation( mTapAnimationAux ); + + float accelaration = GetFloatUniformValue( ACCELARATION_UNIFORM_NAME ); + mEffect.SetProperty( mEffect.GetPropertyIndex(ACCELARATION_UNIFORM_NAME), accelaration - int( accelaration) ); // Set the value as its fractional part + Animation shakeAnimation = Animation::New(duration); + shakeAnimation.AnimateBy( Property( mEffect, ACCELARATION_UNIFORM_NAME ), cycle, AlphaFunction::EASE_OUT ); + shakeAnimation.FinishedSignal().Connect( this, &SparkleEffectExample::OnShakeAnimationFinished ); + + shakeAnimation.Play(); + mShaking = true; + } + + /** + * Animate the particles to appear from center and spread all over around + * @param[in] duration The duration for the animation + */ + void PlayBreakAnimation( float duration ) + { + if( GetFloatUniformValue(BREAK_UNIFORM_NAME) > 0.f ) + { + return; + } + + // Stop the fading / tap animation before the breaking + DestroyAnimation( mFadeAnimation); + mTapIndices.x = mTapIndices.y; + mEffect.SetProperty( mEffect.GetPropertyIndex( TAP_INDICES_UNIFORM_NAME ), mTapIndices ); + mEffect.SetProperty( mEffect.GetPropertyIndex( ACCELARATION_UNIFORM_NAME ), 0.f ); + + // prepare the animation by setting the uniform to the required value + mEffect.SetProperty( mEffect.GetPropertyIndex( BREAK_UNIFORM_NAME ), 1.f ); + mMeshActor.SetScale(0.01f); + mEffect.SetProperty( mEffect.GetPropertyIndex( "uScale" ), 0.01f ); + mMeshActor.SetPosition( 0.f, 0.f, 1.f ); + + Animation breakAnimation = Animation::New(duration*1.5f); + breakAnimation.AnimateTo( Property(mMeshActor, Actor::Property::SCALE), Vector3(ACTOR_SCALE,ACTOR_SCALE,ACTOR_SCALE), EaseOutSquare); + breakAnimation.AnimateTo( Property( mEffect, "uScale" ), ACTOR_SCALE, EaseOutSquare); + breakAnimation.AnimateTo( Property(mMeshActor, Actor::Property::POSITION), ACTOR_POSITION, EaseOutSquare); + breakAnimation.FinishedSignal().Connect( this, &SparkleEffectExample::OnBreakAnimationFinished ); + + float timeUnit = duration/ (NUM_PARTICLE+1) /(NUM_PARTICLE+1) ; + std::ostringstream oss; + for(unsigned int i = 0; i 0.f ) + { + return; + } + + // start the opacity animation one particle after another gradually + float timeSlice = duration / (numParticle+1); + float fadeDuration = timeSlice>0.5f ? timeSlice : 0.5f; + + Animation fadeAnimation= Animation::New(duration+fadeDuration*2.f); + std::ostringstream oss; + for(unsigned int i = startIndex; i=NUM_PARTICLE ) break; // out of bound + + oss.str(""); + oss<< OPACITY_UNIFORM_NAME<< i << "]"; + fadeAnimation.AnimateTo(Property( mEffect, oss.str()), targetValue, TimePeriod( timeSlice*i, fadeDuration*2.f )); + } + + fadeAnimation.Play(); + mFadeAnimation = fadeAnimation; + mFadeAnimation.FinishedSignal().Connect( this, &SparkleEffectExample::OnFadeAnimationFinished ); + } + + /** + * Push the particles to the edge all around the circle then bounce back + * @param[in] duration The duration for the animation + * @param[in] tapPoint The position of the tap point + */ + void PlayTapAnimation(float duration, const Vector2& tapPoint ) + { + if( mTapIndices.y > mTapIndices.x && mTapAnimation.GetCurrentProgress() < 0.2f) + { + return; + } + + Animation animation= Animation::New(duration); + int idx = int(mTapIndices.y)%MAXIMUM_ANIMATION_COUNT; + mTapIndices.y += 1.f; + + std::ostringstream oss; + oss<< TAP_OFFSET_UNIFORM_NAME<< idx << "]"; + mEffect.SetProperty( mEffect.GetPropertyIndex( oss.str() ), 0.f); + animation.AnimateTo( Property( mEffect, oss.str() ), 0.75f, CustomBounce); + + oss.str(""); + oss<< TAP_POINT_UNIFORM_NAME<< idx << "]"; + mEffect.SetProperty( mEffect.GetPropertyIndex( oss.str() ), tapPoint/ACTOR_SCALE); + + mEffect.SetProperty( mEffect.GetPropertyIndex( TAP_INDICES_UNIFORM_NAME ), mTapIndices); + + if(!mShaking) + { + mTapAnimationAux = Animation::New(duration*0.2f); + mTapAnimationAux.AnimateBy( Property( mEffect, ACCELARATION_UNIFORM_NAME ), 0.15f, AlphaFunction::EASE_IN_OUT ); + mTapAnimationAux.Play(); + } + animation.Play(); + mTapAnimationIndexPair[animation] = static_cast(mTapIndices.y -1.f); + animation.FinishedSignal().Connect( this, &SparkleEffectExample::OnTapAnimationFinished ); + mTapAnimation = animation; + } + + /** + * Callback of the animation finished signal + */ + void OnShakeAnimationFinished( Animation& animation) + { + mShaking = false; + } + + /** + * Callback of the animation finished signal + */ + void OnFadeAnimationFinished( Animation& animation) + { + mFadeAnimation.Clear(); + mFadeAnimation.Reset(); + } + + /** + * Callback of the animation finished signal + */ + void OnBreakAnimationFinished( Animation& animation) + { + mEffect.SetProperty( mEffect.GetPropertyIndex( BREAK_UNIFORM_NAME ), 0.f ); + } + + /** + * Callback of the animation finished signal + */ + void OnTapAnimationFinished( Animation& animation ) + { + if( mTapAnimationIndexPair[animation] == static_cast(mTapIndices.x) ) + { + mTapIndices.x += 1.f; + if( mTapIndices.x >= mTapIndices.y ) + { + mTapIndices = Vector2::ZERO; + } + mEffect.SetProperty( mEffect.GetPropertyIndex( TAP_INDICES_UNIFORM_NAME ), mTapIndices); + } + + mTapAnimationIndexPair.erase( animation ); + if( mTapAnimationIndexPair.size() < 1 && mTapIndices != Vector2::ZERO) + { + mTapIndices = Vector2::ZERO; + mEffect.SetProperty( mEffect.GetPropertyIndex( TAP_INDICES_UNIFORM_NAME ), mTapIndices); + } + + animation.Clear(); + animation.Reset(); + } + + /** + * Helper retrieve a uniform value from the Sparkle effect shader + * @param[in] uniformName The uniform + * @return The float value + */ + float GetFloatUniformValue( const std::string& uniformName ) + { + float value; + mEffect.GetProperty(mEffect.GetPropertyIndex(uniformName)).Get(value); + return value; + } + + /** + * Terminate the given animation + */ + void DestroyAnimation( Animation& animation ) + { + if( animation ) + { + animation.Clear(); + animation.Reset(); + } + } + +private: + + Application& mApplication; + Shader mEffect; + ImageView mCircleBackground; + Actor mMeshActor; + + PanGestureDetector mPanGestureDetector; + TapGestureDetector mTapDetector; + + Animation mFadeAnimation; + Animation mTapAnimation; + Animation mTapAnimationAux; + + Vector2 mTapIndices; + unsigned int mAnimationIndex; + bool mShaking; + + std::map< Animation, int > mTapAnimationIndexPair; +}; + +void RunTest( Application& application ) +{ + SparkleEffectExample theApp( application ); + + application.MainLoop(); +} + +// Entry point for Linux & Tizen applications +// +int DALI_EXPORT_API main( int argc, char **argv ) +{ + Application application = Application::New( &argc, &argv ); + + RunTest( application ); + + return 0; +} + diff --git a/examples/sparkle/sparkle-effect.h b/examples/sparkle/sparkle-effect.h new file mode 100644 index 0000000..f37e1e6 --- /dev/null +++ b/examples/sparkle/sparkle-effect.h @@ -0,0 +1,393 @@ +#ifndef DALI_SPARKLE_EFFECT_H +#define DALI_SPARKLE_EFFECT_H + +/* + * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include + +using namespace Dali; +using Dali::Toolkit::ImageView; + +/************************************************************/ +/* Custom sparkle effect shader******************************/ +/************************************************************/ + +namespace SparkleEffect +{ + // uniform which controls the position of particle on the path + const std::string PERCENTAGE_UNIFORM_NAME( "uPercentage" ); + // uniform array of particle color, set their value as the PARTICLE_COLORS given below + const std::string PARTICLE_COLOR_UNIFORM_NAME("uParticleColors["); + // uniform array of particle opacity + const std::string OPACITY_UNIFORM_NAME("uOpacity["); + // uniform which offsets the path control point, with this values >=0, the paths are squeezed towards the GatheringPoint + const std::string ACCELARATION_UNIFORM_NAME("uAcceleration"); + // uniform which indicates the ongoing tap animations + const std::string TAP_INDICES_UNIFORM_NAME("uTapIndices"); + // uniform which controls how much the offset of the midpoints relative to the start/end points of the cubic bezier curve when the path is squeezed for tap animation + const std::string TAP_OFFSET_UNIFORM_NAME("uTapOffset["); + // uniform which gives the position of the tapping, in this way the particles will be pushed away from this point + const std::string TAP_POINT_UNIFORM_NAME("uTapPoint["); + // uniform which trigger the break animation, set to 1.0 when break animation is playing, otherwise set to 0.0 + const std::string BREAK_UNIFORM_NAME("uBreak"); + + /****************particle colors******************/ + + struct ParticleColor + { + Vector3 RGB; + Vector2 AlphaRange; + unsigned int numParticle; + }; + + ParticleColor PARTICLE_COLORS[]= + { + { Vector3( 0.f, 240.f, 255.f )/255.f, Vector2( 0.2f, 1.f ), 22 }, // 00f0ff, opacity 20%~100% + { Vector3( 89.f, 151.f, 239.f )/255.f, Vector2( 0.2f, 0.5f ), 12 }, // 5997ef, opacity 20%~50% + { Vector3( 181.f, 181.f, 207.f )/255.f, Vector2( 0.5f, 1.f ), 22 }, // b5b5cf, opacity 50%~100% + { Vector3( 147.f, 147.f, 170.f )/255.f, Vector2( 0.5f, 0.5f ), 22 }, // 9393aa, opacity 50%~50% + { Vector3( 145.f, 145.f, 201.f )/255.f, Vector2( 1.f, 1.f ), 12 }, // 91bdc9, opacity 100%~100% + { Vector3( 145.f, 145.f, 201.f )/255.f, Vector2( 0.2f, 0.2f ), 21 } // 91bdc9, opacity 20%~20% + }; + const unsigned int NUM_COLOR( sizeof( PARTICLE_COLORS ) / sizeof( PARTICLE_COLORS[0] ) ); + + /***************particle moving paths********************/ + + typedef int MovingPath[12]; + + // these paths are defined inside the circle which has the center at (250, 250) and the radius as 250 + MovingPath PATHS[]= + { // each path is composed of two cubic b-curves: (p0, p1, p2, p3) & (p3, p4, p5, p0) + // p0 p1 p2 p3 p4 p5 + { 280,273, 386,41, 489,141, 491,199, 494,256, 230,394 }, + { 129,226, 357,120, 150,491, 291,406, 433,320, 47,283 }, + { 96,264, 356,133, 446,196, 370,297, 294,399, -169,384 }, + { 345,110, 359,186, 14,393, 4,247, -6,101, 321,-28 }, + { 166,161, 128,353, 566,200, 487,304, 413,403, 203,-32 }, + { 193,286, 106,331, 206,569, 334,477, 462,385, 279,240 }, + { 336,247, 293,232, 301,465, 346,479, 390,493, 374,261 }, + { 250,72, 314,72, 332,495, 250,497, 168,499, 161,72 }, + { 48,387, 32,241, 452,558, 433,358, 411,121, 62,523 }, + { 300,32, 159,27, 442,568, 186,492, -70,415, 551,41 }, + { 479,150, 503,203, 216,403, 163,298, 110,193, 448,78 }, + { 346,75, 311,97, 336,196, 389,160, 442,123, 383,51 }, + { 90,61, 54,96, 218,373, 294,300, 370,227, 141,11 }, + { 126,225, 240,280, 378,29, 221,16, 64,4, 11,170 }, + { 308,101, 243,22, -10,271, 22,352, 49,422, 396,208 }, + { 193,188, 174,302, 502,389, 500,250, 498,111, 212,72 }, + { 227,3, 16,35, 577,309, 428,423, 279,537, 438,-28 }, + { 410,58, 387,18, 22,179, 154,277, 286,374, 459,142 }, + { 178,272, 109,299, 144,429, 218,396, 293,362, 221,254 }, + { 247,46, 98,5, -91,357, 160,431, 412,505, 397,88 }, + { 41,112, 22,144, 123,273, 158,187, 192,101, 75,56 }, + { 8,300, 23,340, 267,294, 238,218, 209,142, -20,226 }, + { 112,256, 24,270, -1,470, 154,433, 308,396, 201,242 }, + { 212,277, 267,346, 509,202, 452,103, 398,8, 150,199 }, + { 154,205, 146,287, 496,282, 492,194, 488,107, 160,140 }, + { 281,350, 365,318, 415,476, 332,482, 248,489, 204,379 }, + { 327,23, 346,81, 154,319, 123,207, 92,95, 313,-21 }, + { 323,233, 283,307, 454,420, 478,354, 501,288, 374,136 }, + { 318,186, 311,252, 488,248, 481,168, 474,87, 328,76 }, + { 7,192, -10,270, 249,398, 269,307, 290,216, 25,111 }, + { 148,22, 98,22, 25,458, 125,458, 225,458, 198,22 }, + { 349,32, 307,39, 492,416, 399,446, 305,477, 460,16 }, + { 147,474, 222,554, 392,154, 486,240, 581,325, 73,394 }, + { 57,186, 13,200, 51,398, 114,374, 178,349, 97,174 }, + { 257,192, 198,188, 162,345, 240,349, 319,354, 316,197 }, + { 242,4, 283,21, 30,172, 81,215, 133,257, 209,-10 }, + { 149,408, 165,442, 472,340, 444,275, 416,210, 120,348 }, + { 106,271, 136,359, 483,370, 422,186, 360,2, 76,186 }, + { 120,146, 29,224, 469,262, 346,390, 222,518, 393,-87 }, + { 318,265, 415,280, 398,537, 247,491, 96,446, 222,249 }, + { 171,275, 207,246, 274,469, 237,497, 199,525, 139,300 }, + { 196,84, 135,105, 256,510, 334,486, 412,462, 280,55 }, + { 485,314, 452,170, 158,606, 111,411, 55,179, 515,446 }, + { 134,54, 266,4, 175,607, 392,451, 609,296, -100,144 }, + { 3,229, -1,287, 334,383, 350,267, 366,150, 10,151 }, + { 105,115, 146,125, 154,227, 92,209, 30,192, 62,105 }, + { 343,20, 388,42, 323,357, 228,313, 132,269, 278,-10 }, + { 362,186, 271,274, 60,82, 204,19, 349,-44, 453,97 }, + { 145,128, 181,32, 501,185, 498,272, 495,347, 97,257 }, + { 286,172, 342,274, 59,463, 16,331, -27,198, 231,69 }, + { 194,7, 404,-32, -38,410, 140,469, 317,528, -16,45 }, + { 39,120, 48,74, 445,109, 352,244, 259,379, 20,215 }, + { 328,247, 402,250, 411,384, 330,377, 248,370, 281,244 }, + { 189,56, 317,-31, 610,240, 396,392, 183,543, 61,144 }, + { 402,53, 430,77, 376,231, 315,161, 255,91, 351,10 }, + { 496,218, 494,260, 249,296, 251,214, 254,133, 498,139 }, + { 381,210, 469,195, 557,376, 399,391, 241,407, 292,226 }, + { 297,263, 267,346, -8,289, 14,176, 35,69, 331,168 }, + { 329,187, 363,263, 30,371, 5,287, -19,203, 302,128 }, + { 257,354, 168,351, 171,516, 252,496, 333,475, 340,356 }, + { 106,60, 107,121, 366,284, 359,168, 352,52, 105,14 }, + { 178,257, 240,314, 115,476, 71,421, 28,367, 98,182 }, + { 163,213, 191,273, 22,327, 3,248, -17,170, 118,113 }, + { 459,117, 500,185, 297,390, 248,311, 199,232, 416,46 }, + { 270,3, 317,-14, 528,375, 434,407, 339,440, 223,19 }, + { 88,76, 130,68, 78,485, 176,483, 274,482, -22,96 }, + { 422,428, 378,528, 88,205, 26,317, -36,428, 467,328 }, + { 414,127, 460,125, 489,325, 421,322, 353,320, 372,128 }, + { 227,197, 281,174, 367,311, 294,340, 221,370, 173,220 }, + { 180,14, 147,44, 436,104, 401,161, 366,219, 207,-10 }, + { 400,367, 395,404, 71,406, 77,336, 82,265, 407,300 }, + { 396,222, 396,316, 71,439, 70,245, 68,51, 396,132 }, + { 342,109, 454,153, 49,332, 208,413, 367,494, 8,-23 }, + { 147,167, 222,137, 266,169, 231,199, 197,229, 129,178 }, + { 227,272, 310,243, 277,313, 322,266, 367,219, 207,313 }, + { 279,192, 339,233, 396,211, 367,182, 338,152, 228,194 }, + { 236,20, 283,75, 346,26, 338,71, 330,116, 207,17 }, + { 129,83, 164,23, 158,14, 179,11, 200,8, 91,78 }, + { 86,231, 129,293, 164,421, 104,348, 44,275, 66,200 }, + { 193,328, 197,278, 240,348, 276,305, 311,263, 199,354 }, + { 231,364, 241,209, 309,104, 326,236, 342,367, 225,424 }, + { 414,230, 398,328, 446,445, 467,363, 489,281, 373,254 }, + { 289,122, 332,123, 348,161, 322,158, 297,156, 275,125 }, + { 142,235, 199,308, 402,229, 283,218, 164,206, 130,206 }, + { 174,396, 210,387, 328,501, 246,455, 165,409, 138,394 }, + { 288,388, 366,357, 372,458, 393,400, 414,343, 249,431 }, + { 351,278, 409,369, 497,316, 437,288, 376,260, 351,243 }, + { 87,134, 181,77, 311,121, 206,140, 101,160, 61,159 }, + { 95,195, 126,208, 133,258, 110,236, 88,215, 95,195 }, + { 140,293, 158,330, 169,275, 184,299, 198,323, 126,313 }, + { 336,319, 383,357, 388,278, 393,333, 397,388, 311,325 }, + { 338,107, 434,209, -37,469, 151,287, 338,104, 285,50 }, + { 403,134, 446,182, 378,318, 386,233, 393,149, 360,98 }, + { 366,82, 413,93, 416,158, 390,118, 364,78, 336,75 }, + { 448,188, 448,230, 465,269, 470,225, 474,181, 448,177 }, + { 121,398, 142,418, 126,475, 111,436, 96,396, 100,382 }, + { 40,296, 90,352, 170,310, 143,350, 116,391, 7,300 }, + { 25,203, 45,241, 70,204, 45,248, 19,293, 4,204 }, + { 243,222, 225,275, 345,256, 296,237, 247,218, 249,199 }, + { 159,149, 282,133, 284,199, 226,191, 169,184, 147,160 }, + { 149,257, 290,322, 151,374, 166,338, 182,302, 116,263 }, + { 255,285, 354,327, 234,287, 279,327, 323,367, 193,290 }, + { 188,220, 353,190, 290,354, 348,293, 407,231, 152,248 }, + { 305,122, 382,174, 402,229, 366,198, 329,167, 297,127 }, + { 378,260, 406,267, 390,330, 384,293, 377,257, 366,263 }, + { 178,396, 357,365, 273,461, 248,431, 223,401, 157,412 }, + { 180,89, 258,88, 302,94, 255,115, 207,136, 166,96 }, + { 81,197, 139,232, 39,257, 94,259, 150,261, 58,200 }, + { 314,89, 378,40, 383,38, 389,42, 395,45, 267,90 }, + { 371,141, 482,233, 508,244, 498,272, 488,299, 307,157 }, + { 339,348, 361,465, 382,477, 406,442, 430,406, 269,369 } + }; + const unsigned int NUM_PARTICLE( sizeof( PATHS ) / sizeof( PATHS[0] ) ); + + const float PARTICLE_SIZE = 13.f; + + const float ACTOR_SCALE = 0.704f; // resize 500*500 to 352*352, a bit smaller than 360*360 + const Vector3 ACTOR_POSITION( -176.f, -176.f, 1.f); + + const int MAXIMUM_ANIMATION_COUNT = 30; + + // Geometry format used by the SparkeEffect + struct Vertex + { + Vertex( const Vector2& texCoord, + const Vector2& aParticlePath0, + const Vector2& aParticlePath1, + const Vector2& aParticlePath2, + const Vector2& aParticlePath3, + const Vector2& aParticlePath4, + const Vector2& aParticlePath5 ) + : aTexCoord( texCoord ), + aParticlePath0( aParticlePath0 ), + aParticlePath1( aParticlePath1 ), + aParticlePath2( aParticlePath2 ), + aParticlePath3( aParticlePath3 ), + aParticlePath4( aParticlePath4 ), + aParticlePath5( aParticlePath5 ) + { + } + + Vector2 aTexCoord; + Vector2 aParticlePath0; + Vector2 aParticlePath1; + Vector2 aParticlePath2; + Vector2 aParticlePath3; + Vector2 aParticlePath4; + Vector2 aParticlePath5; + }; + + /** + * Create a SparkleEffect object. + * @return A handle to a newly allocated SparkleEffect + */ + Shader New() + { + std::string vertexShader = DALI_COMPOSE_SHADER( + precision highp float;\n + \n + attribute vec2 aTexCoord;\n + uniform mat4 uMvpMatrix;\n + varying vec2 vTexCoord;\n + \n + attribute vec2 aParticlePath0;\n + attribute vec2 aParticlePath1;\n + attribute vec2 aParticlePath2;\n + attribute vec2 aParticlePath3;\n + attribute vec2 aParticlePath4;\n + attribute vec2 aParticlePath5;\n + \n + uniform float uPercentage;\n + uniform float uPercentageMarked;\n + uniform vec3 uParticleColors[NUM_COLOR];\n + uniform float uOpacity[NUM_PARTICLE];\n + uniform vec2 uTapIndices; + uniform float uTapOffset[MAXIMUM_ANIMATION_COUNT];\n + uniform vec2 uTapPoint[MAXIMUM_ANIMATION_COUNT];\n + uniform float uAcceleration;\n + uniform float uRadius;\n + uniform float uScale;\n + uniform float uBreak;\n + \n + varying lowp vec4 vColor;\n + \n + void main()\n + {\n + // we store the particle index inside texCoord attribute + float idx = abs(aTexCoord.y)-1.0;\n + \n + // early out if the particle is invisible + if(uOpacity[int(idx)]<1e-5)\n + {\n + gl_Position = vec4(0.0);\n + vColor = vec4(0.0);\n + return;\n + }\n + \n + // As the movement along the b-curve has nonuniform speed with a uniform increasing parameter 'uPercentage' + // we give different particles the different 'percentage' to make them looks more random + float increment = idx / float(NUM_PARTICLE)*5.0; + float percentage = mod(uPercentage +uAcceleration+increment, 1.0); + \n + vec2 p0; vec2 p1; vec2 p2; vec2 p3; + // calculate the particle position by using the cubic b-curve equation + if(percentage<0.5)\n // particle on the first b-curve + {\n + p0 = aParticlePath0;\n + p1 = aParticlePath1;\n + p2 = aParticlePath2;\n + p3 = aParticlePath3;\n + }\n + else\n + {\n + p0 = aParticlePath3;\n + p1 = aParticlePath4;\n + p2 = aParticlePath5;\n + p3 = aParticlePath0;\n + }\n + float t = mod( percentage*2.0, 1.0);\n + vec2 position = (1.0-t)*(1.0-t)*(1.0-t)*p0 + 3.0*(1.0-t)*(1.0-t)*t*p1+3.0*(1.0-t)*t*t*p2 + t*t*t*p3;\n + \n + vec2 referencePoint = mix(p0,p3,0.5);\n + float maxAnimationCount = float(MAXIMUM_ANIMATION_COUNT);\n + \n + for( float i=uTapIndices.x; ib+c>;2x|D82O1ehpKw3b_5z=e}B%~WhDJ3N!qoqU|28c9BGZ-xm9|`G} z6nyyH_x-(}=a1j(_s6r>&g-1_+0}K<-q#iLLQjK?n2{I&0FY^Esu}h6_4PeA03fgA|Jv6670jE>4u)`YQ(!-A>0oDba!_E0O6ZE{zJ3aGbkYp; zgc%0v8QBNEvX^mSS5jn?_m{mV;0p7$W%GA+ar2V(S784yy0Z7z|K1j6XZtS@?^g=! z|1FfM?hCf3?w&9<2_dkcy@;qNo1~T$PX;!?uif_;5~$(SAd(h zt-qj~7sr1nsKUJLJ)K^AJGr~D{Y%l-&fUjbf&E_6|5m~EwXW{}5_a?YA4A=nOxWM{ zwXmp=h_I{czk2-_w3oL5?Ee+x{|N166!01*Yyk6e_wls9_XnKgKgjpC`@b{#7w}#h z+2@{4_fD~OQFXWXafP{gYpE)*-~S@y;N&1Hts)_=4px;>S5;L}6BiYgkyKGv7n1?s zFYnK)D*vJJf5KG}RaRD&l2R6x6q6JcRg+Mbyg#H>z|x|kA`;4~692)~a`W=Gb+d>4 zhp*E;-~Yjt`M=`IKJ|pzdb@iXxx2gkCjwqLx_i5OIl8}Qduk}j#shV7b8z?b;{De> z|E;Ym%+tvi=AiEB?#lLG_LX(|Kk%255tR~`krYu^7gbS{5EYeHkpWA~NU5qwsEI$7 zR1s(YA6$q3KQ8%`{ZQQ zQdKtcpWh3y^j(*oc!^V->lfs8ZoY>&>@B}pR``lHtI(dE_+A~Z%Gqh4#g5&cOo>$s zS0cp4Nk(mS{Q3IV_QKcw`8@Hl(mdi%z#j|0(WRUf#9u$Vyr7n^8~aLk7k`PB-n6)n zw^`lT#UWOTcs#~o4M{dYtuEXz>?y?_tb&+|s{cBJQ@Ortlk~}{c z-sjU@!t0U1H# zPc8x$=T#|DgY5ssEad52fgbXTgTZ4i_-^l*KFMTs-)Xb|HdNYEvt=WRDgSUkDD1?Z z9VW6m{oz;6Adcfd)laatxI6{76#POY7ZD8fH5}J;f zQLNjgC~~i9zgJQbXc0=7|B|-Lo&fT(x3R+N8C@csAfYecaxBNbG6O* z;%^WniI{Ml8(G&jyHmdm2Z*-(UKeflcW*wP|0>HSk59B70@(d8%P9g<5%VW*CM`EN z{CSp-xk|suX;V&-$Ux3|jz=jqmBM_p{XCA*x-arZHNys_q{aN}UoiO<|LRFFzyT_)TuFUV&oa zZ(5(ZKS@%&TUsC+yHLk5VnKd$b0|^6eK>P1d8&YR-HAEGjQ$wxiq!QpXBL4Sgglq` zdwxF4JAOu0t_X)8M*Q(`mx8QV;AHqYgbcGjAz6jhRt{akyF@rjeSexNjMGXIi+-Pi zu4iopCr9ML`R*RPGaYdU~TTW48-hU(ieU!1X=lA~V55r{bl#6Sc19bet7^%OWxQDFio|=yS z?kwHLGh7C~cnrGSJEyHzA71>vV;Ch3Y-~0)REdo%sW_j=+T9mvFNt;u`*lfp1mTl27{d zoVa%1nMBy6b|LXDT<-SPzP0CYCI&q|#@~95hw-r)AwEj8(XP0Ymn-2?gf4s;B^CR% z*jwNSl$SHDiSsQ}3M`&~-(7+QZCtGYC;m14ZOQ`5jCqeeYq&sK!26w-okT{@t3xyk zW4SW3Tngp>eClggNjow7R}L)PFr2B5fdT6`px-%bYxr9qLBA+6pgyZvq^A5B(qkhZ$!m|W0Y~1|l%hS^5d32QSN%sl*{p%_FDu;gGKUKA%!{1b7D6L%D$BrI&q{sWCpP}-qZM9@ChPhKJc+lT^8rBZJ>NzASk?wveFD?#NJL>AZ%`3LhcR5G}HI=t3|+U{kvM zs@(O|&u@TRr;h8v7WG&{>u|gaEnZ2DiQ@C05Qu`diV}W)yYe;u*8YOb!pnx2n%?T+ zkgIHTesJ$k+qcDa6$eAQBREhYSwGyFEilOQZGm92lLxmfs*2f{L_5%Q#3iWVtvboLBeiy53Tr&mEgsS%RM`o)>^;T z=s=xGn#Uo0f#HSKbP#lOaCtZSo8{vi9vQ*ZNbN#)WeJ1X}=XVTrwq#|7Qn#pjP&+EWa&7K3%we@95w5cleqYu=fUPXG|(117tyviZpkil205hgWI zwbe`7atLtI$0-^3{wTz+ujIbqJ8Dn(N5}hfv6P;8J=f;Zv4Cz6QXQhC@J2<}S+1o( zF6DbKVL$jkExIg|uhZ(YOAU9vP0ni}MNe6rGy&&xJXtEPk{#X(tft|%$fL>Aj@cKO zc3xXLT$eueJ!bSZmO$l58TKbmXJc)OOTd=8?M2A*1l8y9sg!~$xs%7sDg9()9j*^< zmR7P+o8W)iL&99)5Z8Eb!-kz>dai)y#4;}`Mnh#~cuK`|#8=rgRPnGPueR-jXU1(X zt>tqS0eG5ED73DRFYY=Z*9#WM_Zgle@sW%z%rwyVAD+p^oj(}wfj}GtjZU8nq6H|k zmOQ$tqz=vxZ63w%B&OL@pue~T<20l@-VEV5<_yroPsc8_+ocNEZJ+$>822WYyG`_R zh3R6(k^S$R`1Mcz<>dee*aRxpN%mA=D(I`^-cpNO03AsRQ^pa3bb@mKfVk9Spo{i`gR*CW@N#<#NNejgp4tRyeKLAY(vSQG|Y(Yjoh z>?-R;i`gzKc;v}FRuJ(dnq^)#Dnz&%0voA$*!4`9|O6EE?IZ=l)9usn^~^A zk7n$k(WD82YGmS7=8;iv215c@TPay{(~x3B8L2^X=jHCdjxL1&w>2x@8_}fi$8({f z!h@1E@eIIUJM1Rh85*zwN2KbnUCuT2MtLqoT?)t^-&AM%oD$I39hg?6&8lf}^i@w$ z;}G>%`u5L;^0gd5oMe>WW^bcQ;-3ud%ijt1Y@N`_&j+JRwdTCpGK#0<-jqgYWg+bt zF#geayUwjT$Axmgnn^q1QQ={G2wl?M4nQINw5mvnuh1Cvxj+=1keC*!4Mlp+aGhY( zzL(Rk(Br=kH&0qz)gq7HuLBc_4~YKQAY;`L?0tH0`3s7_T}?XnE1QvF3^to7){CH{ zZ*huCu3&BB|IN$)-TQT3E7tf4u0=<3$hkc(+eEhhG-Xdu8+M$1RUk8&0aw=Jmghr` zxp9sqvp}D)K`!d-qi+3~G4Oi7C)?Q9&)6m^g&*XU4<5TH8Kn`)($jua_{eXjQFd=w z>;eZQ9JBJDmRF7+%+{;34698L=l9id&5OMoE0W-?|RXsQu^8*i)B z!JZW2^LiOrv*~?lMv&X~QoEg8qTB-4WV)u$EPMB~3XS_?)Ok541ExyZjoN6;XNj-x zzVv<%;C+DM7-Isx=vzUZW0p~Kfa%^{zV()f6y2OW{T9u}z+mH~J7>UtG4Y5C|F~~I zV!Gy5OP6Iwi_vG|nBko|5%Mc8=z}wvxJW7Q4eg0(nQHfSF(RL7ka&DbVg4QLYAqyy zz~u)rI08aH(wb2~iplREf^L+HZCB%tX|p1tU^Kw|xe#cCPTjX6R3kV7WREaBFou76 zFNlsrngnPBc3RP}Nl=cF0n(+Krg}ouv~Co%qLM9MFv(^_)#;~MlNWUmL-OL9+259% z@0)Z6^h5He_J*6Erute^ylu~fMkE7MAKIpR=q0Ndb)F;Z!f)w?PlZSx)eWMq(x82f zyp0ODn+ToVbG$_sYheBZ{-p=ClL&rS@EdE}kzRi~4SWHb63oEkmSLaAbINlBqOu?X zt8rJqk1Z!IzR|T8$bZoBf!rPSHk$<^_U@gAv<%}nXVU+4ea7F$l8*^Ub0itha)43? zD7x^wO20wqow$O?DqKD!C)0mXOXjWQ7MU1`eOpN`R=!%!90!{u zef1R}ZT72|w5gxa=s_#>OxXY)s`;(H6`7YqIw6K3$MOsFo$vJ@!|*c%-j>%bq=dsE zo3gCuA&_gh3WWzr+yA)U_Tta*9^h?Bjq?VL(WfGk{YQs-G2oTEALl?UUjq9tuBmT;^$6sz5jM4m`-izVWT;y+{%fiVbz znq8)BfjPRr(wlzT?Mq8Rk@9mu_;-i`Q9QVOFy6rDK| z-g){lqxas-KN)^g_#C%amt ziHQLf)b)n37u}~H{Qxd&ZOkA^xE@(G_v7+o#d!2qEt-%6lB&yPjuQ|mh?7tH@YgH( zjI+<$*MCufYfN_eQ4ae7ox-{{!1h5#?UMlSu>`)F#knilu1$0v+LHpu!iRP$>R0)C z0c3Swuz+Fw7^|PjAt(49eE zP=97ozMf3QUh4ot1c+zI`V9=tef3$l2G9MGBSNwM@n6HcI(7U)L|n{?C=CFaTEU04 z%4c`t-_uIxc$(cjYG(*v_wBkTV7&3SX^~dmmdVCg=A^{y=RT16EEM(6d0)VezTz%m zn<-zakxGr|pzJNP?q9PKD*(xew6II}&+%`Cf0)bT@-U&XQ=jBN@~2qWy24bF5Tq>@ z%u#bhn^pt%FbDQxZxfUV%$pP)95_#@DX25jM{)E(_xcwC(2niQUcrC8j1u|)?Aya( z`QR*$&Zh@@b9Ls$TtB@a(jw)N8sMW66DdE+@RQtQ3i(X1cT(|0iXhbtfqR|p;mAf5 zP2_Qblpi{F>C<3RU$IAYvuY~oUECG#`&E;Vr2!CW`++xn6ycM&M%j#h16#QZ&ay1nL%%n|K11}g}9}I4L zJ^=*vrSyAE0aVqGDyhD$2Mf?Tq(=U!Xm}2^YaCN98suNTaP+tYY=89eZ54wgu3`YL zJ^9l~n*)Edh%N_)=qvKf@GdNuT}^HM#%zG0)ft3 zmrc((W-pr@FH=SnptxIWZ`+9K-Wuo;i&{!_C%}e=erB`traEL{G5nsm@#ke*FALv& zC9z@M|A4n?IcXyKqmPhMM!NLFKWZc@_uK_;rywm8pY`6`u=G9S_2L4CfGYt%8(A>);DOp zO4R0v`mhgMi<+9*cm*I#X`&=KyG#8^fwNfrNJ5Ux`vu^HFb(;gHb7x>)>M~ku+C1r zSxi82B9LJaPt-{bpBFjJ@rv0nx0KRALrO_r{zeDoBsHl8J)yGzWR~Jcp#hu44_K64 zB;Qw4osZ6AUBnD`aLW@92%6tv{|LAFOS8|pJ>)w__l<)m|<1DAqO?!ku^gpG25xg00?lvLR{g4W&4 zVtk2koq%3D?8>W{_8!s#Gt%qn+>b{Z_s^(DQYD!tO$N2FG_nV|wP?}0!GP(uB+S(! z5dx$|gPeP|)g!C?kOz%^PYQx7T4*&T%E}NA9*_^HS4`pq#J^9g%i4V7xSSr$|Kzx% zsU35mVCXo*#5vbK_r6@#&E|z{QNq8Ba3GH+`kwA3MHA6@WX4%BN*+@bb`;=sY_(YR zy7sDt*X_aTj|jJ@XE0)5sCRHHk~fiQo!=W-o2BXCtflxEsZ&c< z%uid7c?zeYvfL#B-#C#d=Q)wvr#qLg-<)lD#`XY$@wNFDf>sYzt(=XuMb=ukvZ!R$>mGy1UYabwR!|elv+>@Rr$HQyWM>QoXEP6=V z8Fp23EB&l#^UDcGEBYcG-p@TVeR5-ZB-K;KkzPK#oZ9+`lpkJ{Sb^y(d2LKQT88}y zq&2cnQqXu}dRoC#5tF|VM!v{>c~FW{Q-v#5fb6*%DE4$QCc!%9l-F3JHP4vs#l@qrD#k;g2|h9Y)SL~ic#!SEE*}K! zD-ITt5;dG&0Fep*tR{dgfqa=QpOjCIW}J_zRHA)rjV=1xVRkKLuWvEFriXWdw3)b789rabnn+rcyjoRFM@kPU3cdS@PrI2I|hq;MLz5%xl&O z@Tlc(n|dB;&>>I!&CB()ZNGPap*|rZ5uD{DUR^HyGB(KAq`wABt#4fEd@fKyG z67!`lyw3d-=Wp%pC+R5l#-`>0-(hu|gg*nuHWOC;4$jJ%Y%x3*ba$*#K0$#S7?mG{ zZF9_)0VeZ&p1!I!jCillkXj4yq=fE8j%|l?9b!>?JHFiv4YidlF-=lLoBP z*2WzdZFBL_t|11E4cT$HM;PZP;HbhU;q{Lun)}uIP-_LUo&yzxuIpdm zNR}{bs)X&@1w9--j<`L5-MKtLRofdlk`+8%nObd-!=&A?RY8ZcUDk}qWfA_agJ6_Q zF*i94W-D>&vB@7`VhG8!WK$K8#&Lb%4|gNQr#J7MZQ*zqKbi~=j%1tv5p7>;-tbW> z0%a@GEVPY!#9R!+nN#2&f-X8PaO7e5QmH4ZIIJg#j}VnWup%0;lLuA^a>rH zme(#O*!Rf6a`gfi@OB}}efShot5-zjP@V;RgI-iA#+j%Hb$z=8jxU0hIIVpYrne#* z;|LOp(JM)(-;v}Iu&J%hf((9KH zL&rX<5Jk9``2K}56N}G=-J7?O;5`qAzhLfV9JV+JW3hF^pp8S{!Y|^a@-e&Bid!q! z-eJf!AJW9t$EAP;G@(X!fheXeBGP~~KK)_hWrcw>oA2#oIQ0!a@QZ>f%ypn?M6(^( zgcZ_{S+U}~0};$lU%glR$;GHFHoAA{hxlGkKRR4KVXp9 z$z-j~B^C=9n?Ab^-#R2+gL6JO|1?tNw23wT-uEiyQ(}J&l9$8R0w+h7Q2^tubfzId z4L6l46PC6}%G$Sxwig||Vkq!{4^D435F++vt)D(Si;%pska~G|{|}a?br9*f^2h47 zdm^vJ?92^mVi;35X})3W``uR*pXVXEst?b_!TSU!;ZewEkk(T&OiW+oz@+sACqGz%%x!9}Z*utX!l56cgUUWy`U zy#?sq1jSpJ@)Om|j17$TO_E75;@iqGDTeDrjV?w#?SeKPcv~aRog{_Ei4n?q1NYTv zO9x!(-|dEk-;U+d&6Ajj#y!na82N^0E;QjkH?-JW-z(|mF8o0{^jC9%lKgTKxh*n3mIbVWju*MCB3Cg7P*6HnfL#41U?LIqh}Ak} z1uN!`rKwL-JH*+FP*Bp$&k?--0wm=2^__l-(SEGHg^tC}#gkxJfo+tXl}LZGs;`t0 zLUK=Sy*FJNXwe{{lk-*PauA(9wM1(VDJMM&oQ?pq&c<2*C2T?4fGykAPa{3dDJQ+dDD znU;imk!94+<*KP}xJ!(6P78!E0G2D2_-XQQaBRLpPF`s11-Se`V@0(tcGoXN8IJ|r zF%pKMX5> z`O#koSC_QqBHf(TdH9SdLxjmJMlli4oPwnRwonEe2v{+TYcyxvRyagu4N|-ZFbmUj z4RL=qpH+u5MtNo(e;S{z;m4&ZhZQ}ne!%fOkki#4k3N-8A`uGQ5_PU&qU-t`E@^MS z+h^8p`b&cw3nm$z_LLPNnI-h*2TZbPWnjcDip_~$CU+!NbZvSL=zZvyXSCARPgGFS zBUPA)eV@dJ`NQ*6I5lXq3gXfzJ3$QU4w>{a-r-2^@n9eH+hlsf2g#d^VI=A-FTZq_ zaDVnkp^%2ZqUnjUqL+JI@8gv{n`EaaO!%XutNaG<4Aka9d$dUJ)E4{m5KhEd7*`OU znMQhGx;#AXOB;`RPv@g^L@^!qSRYe;T_6_Hh@tsj@jfZn7x??z$xw=LAOS?l#l8L` zm$Hl(vkzbNBB@Jyy|(;fvMY2f-MKcah_Ue$hLq(95&?MvCx6f&8zId{vS{d+pv~Eg zFhdFERWXaPa$qNRCYU|uF{6<>Z|`Iy;&A8Yt)c~U$l^@}Rm5`H{%gE)a^&5WKEP5y zFWN{F=vqRX-z_xq`UUZVP>H8D(8l6Pe+4H892@5CbtvuJWqyVdYcW_Cp`ted+Pu&3 z;MAo)<@d1w3~{Xe2JBvrnh6Cw>h!{Fh-!0kbk4-w{c&re{vkl|hD1DMrX`;EnD3k9 zwK`kE=n}z|RE}TZ6G*jVBq5~5BMxZ{cn=2YD|D8hyhBwf`59cipS^Hldl9^#y-Z%T z1_s&pag5YCsc9TQ_KvkgEX%!&t8L9bEhi=#}YXn z?g1b23Xvynh)ICTJ$&Uinsu5IbOz+W9Qx=@GZ9^rlKQ29AF`i*kJilx{4==pCzh*X z@}~eDhMC#^ZSx%D=iINpt{MB~-ryTcAYgwaI*2Vl8VzcXl3t3s!CO8}xV zkM0fx%L->@G2A91f4q{SVlx0OF#ur_%djwSrw5pEI~$)|_r6~JuKaR&4IdFWkWtq@ z=rx8%jVBA&i5r3ORL%ljj{t`m9T#{i7VIl~e-_lbL{S&16|R+CNwf}0lLiSD9UKfA zX{7$fB7oi!igq=eVbY*^QYvU!I!|(V{8X4jjC1D7H_A7Lg1*zQ~0c?e&Mua66`0wR$exsNt2$2rq@0>;0=)c7A z%gAB=^ezylqMfwk1aYfc1(9BoZ*VMBQ*F`QI^T`SUg9k{OHXCtJuP~oVdGxZSb^JG zQ>VsNOu~_C2v-PCp3czQG*&*I{Y^^Wnxf~*{tQzvtzH?5-z4aGb0sH1|1hJg(u}#C zWxGE5aTaKgf=c7Ci?~HSLifp;m+;5v3N^%iUGq>0OxTxLPdBs|7o&q;c)k`Gvj8KC$yu6+Z}3%E^MJgB&F7j{4-H$0|EhFn>iBkxSI{ zC?TXuwZ&9MGg)dj^Mk$y_&aUc!JqqXo3KPFDsORMvYW%n7Pon>ds(L{T5L+yv4 zOQJ6vcKNJm1TeEQB6^0S07x*>Nk3SacU?l=RDg44>MoKMuX!b4n4{(aU^`OnEDlG1 zB$&6KaT$%LU_;ov?Cm0k&71A7w3xHr?dpjjNXdGLeigpX?{y_wrZTM1j%Y`EQK1LaB8Hv$*ir=ArfH;rElx1<*YT*6T| z>~PC?;Wxb|RM17G<-3%u2t3E0T$;1Po-GmE z`_`WL6(-}8DjaDIv4OoSbVQOEm8iw@+=qduPRnBEE|GOA<9VzKnxMG8ZD-1d(h~alXP{R*n{a1s z-n*QwlFBQ*kid!LPvR`FH~Gh};Uta-=~{yKrBs==Y>J|GSwAylV_rlQ zPhJ?SWonv{O%^fFrva~*$sif$){S%-v~;cVNl8lfD&7M9XwiWpUS%zS=8U|ouzYrsU%B`q4^@)D{#vAJmP;)i*9?+ zf?x@Jfci`^akNd3W52*N_sHbY%I^p zukjne3BUd;G#EeqsV%#Aq&{|;!jK>GiZyXx?MiJ|$cpy9hatwx(T<=+e2Y7vC?}zt zJh5TZQiF}B<03eVU$`Ym{fs(cCxG&cTtwFYqx+>W{{zBtSPnUD|9}T<$tC`h%OV%* z1Ur|)u8dSr85x8dbfNYgc|1CPjF)kEs7}u7%hG@D$~}AShRDiZK&J;ND~{Y@N`L*{ zCM@dTjV-y0$K9|54KdlR*DAzs;U0mb?B`aqj&r-^$4MdgJ7O-lSvcJ;pI`FE7!g%w zUWMSlaGA=Y45r-f+SG|EKc;Oh6|(bQE+@v!cefx&f(Jg%c<)Y8|;f^$>g`{;az-VWoi1U+Xv)F z2s61g?H5%1D34F`@Y!-^W7Xp#O6&tm-BV$7n82K07%I&BL;+$rk7{%4OHpW4@Uq8v zPn)Uc(GhJ8@>2}7fxZ<=3#2E@!1G4he{E0c1ina3B6j*)rZ5^USt>n66z_w;cVGVq zE~GGw)dYHiGCkiXrfycNhvYXdW2zo+mnw5RHil<%(`$pv{32z1dp4Vyo$U-nUSi3I<<}6C3+SF z4u43hKu0;Bi`Cu6M)oAdX#ZJyR=FK^I*^H&m(>2V7WECQcnX*S>7!?zD3Y@sA$CC7 zvK2&0ae^AMT!0)wGF7&syfO4rO2bmw&md*i*xKz~eZjRfN=!c;7Xq?ToGee)W4@~5 zA1l+>x2P8x+`=S(H5m_L5d0QyAIQj8p2(rlX3orG6ypOD=RXNP(_swbtFX@~3_(~v zb56))-K!q@vL~<-jw;3j^Z8WnSg#y=Z%KJj=-LW0T&RDYT^-GSCt6uvMKxpo86Yzp z3-;8e>73h5;^{4&4^;~q^)51=Su<`<5b(=QV1z}*Ej}9AOnASB7M%);2QUl1mK@JX zv{yYLTYCl94jNgfz<#2vBvC~jOlcZ$zRzzUHZE7EF?a4MSOEkr16xt#ktNnB_CoN( zZJ?oY=0&WDOUXAju@zw?P<5|`k(>k?*H_TWtFCRmgUp-ji0BAPZ`I#9nn+8al`C1C za*TQP;Vzah)BhE&WfGOVSuL*TGu&A;uHN$19H$2Sr7`V>rE-_B!lze-&KD2!=jyYa z$&_0Ff8;U@_#%LX`iOcvbs<{Rnf(`BbThr>SrVzrpT_n z9(S=2UCY%y|GiGw&&eg+SL^mbyv^POhnc^NvF!T^-0(|0m}8*Qmli}nU20PnfhE`=6Yc`XFS0S1SP=1|kzS=eaf5DOhDcoa9Pkr;aa$o5 zg98OClqY=&l`it5dv6P0l~OGUeDH$Uo?-}x;%j<;ddDwcz||0_FuoLN_?z89-*g8A zV)jK*|ETz&Sx?s2Es1hRF@DKi(OJzs5FA>;wf`0W# z*9yu>9WR4DoJp2u&vdn~g!$Ea*@-2Uu*u`OHQoj?27EDJ%A!xT4G$=W#oiT*Rwa;U zv?l)uqvbAiLffH-tKRs>sesv7m78TIx8~62DZdi;9c==YxJ@(7jxxO}Su{!X@zV4& z1EiE9I6+pW)M*r8>k&jP} zB%|Fd`zxwpNX>Rezvq#sv+C*|kh3guvTL=-XwD4yw{&R;a@=1bX*cGa5V3R;WtROT zY`m2Wu#~WVi!c6)15y!dV?}rbCrBaoc1H(5p1lR4BO)2T)>%uo2bDfejAxjh&YI>t z+qnpEf)xH4K(kltzt#dm6rXL+b=|ODeO9mre|R=W;v9oq8h-L~gl}klHr9o5?yjK7 zJpSdB0!tn4{JIct0ZE-US^IarcT*|Qop5}TR0aTShY;@S4$eWLh(wBO3h;!vdU>g| zDbF-_=GQzBiQ_NvuhVEyqN6X2_gNN@-QghRmmJf>&i{xBX;ZTXk1F=#Rya`c z!d^b<5Ikt7*qf7Vy&M;RRlEjUr|qT5&p1Qvu;W8gD?JSW&x|v4xEL636x12ZdH~UI zSq0#-m3RI}s`&-%@IseK`?;sG2Cq^%hrM?=b3lqZiMXjz;~?ox4&vg?OB>fd=#hF_ zJ;fGym94cm=tpv^%-~F~~08hh^FMLSWB!@@!7hJiJ5EO|TbpVq@ z?9`ggRF~-I_+;pes8G?@t5DaeJ;B%(EU&0T;LIG$0do!)y-x3Nn&Hx43Ca%Mmwm3m zR#1UnJ;{BHGnLk(wRdWCaaAbXs;lb!yZHTC9Vv-Pdqhc!%T301BhIKVMNMX}6=I|P z3khrseadz?U110p<1#gTvHX>Mc8=$2Jcff5fX$kZ60UJ4 z5}*vc9Lj7mppkU;MyqJ$K&fYc_e0)1tBKEPJ|kzDZ;`HUmXHIz_^zLvUVYh(K1o7# zWMY}i@nuB~aRlrm9Jq2EZD0{(#8=Dyos*r^DmCG26}g;l_=9D* z+Tw#TM+}!2X2sL96)%2JHRq7H(}eOXIMdpDkzHO#z4?WeKo4gcPSf0JO@{_cxw+eT zI#HupNIzWXaPdn)EIV1N1p`muMYm! zZt+42KXQTm%emrZy8<~7>h(w?K|0#@?z`#k2GSRZ&ar2-hpZAF1CCAtxwFyi@zQ+!gVg^4gQU{CNx1$}! zFGVx`DOSSte1&H4y6@V|vw^P6oLL|}bbeYXnr_X-V?Ze(T)7}hO_Kj5Zkawrhcir- zfzTL|_nS=8Pvbc`;EjBQRPwaq_`GC2q>e1^W5ITY&s4@!RjOX1#sO>1?UUY}KuHlf1 z>`&6CeB6W42Rx(0f3z-X+c_U^@nSCvK#g~OMuMDO`1}D3vcPjZ$Fqh>lnZD~$B=2V zM_2O9%Qrs~2!bnNjozpS`unoI$i>Ud5mnO;I=MY%zZ0N;k#&3}Me5l9t0NC&BCD*P z;n>9kG@#?8*bJ>3JXWx9kP;Gs`|d$WwZrbs;%5R<7!ySs^1d~*@}NpH>(maV^cEg| z+c8QXPowHFYo4J_vNS#b5t^+tHK#~TrEb6X3+C(-wfwz`1;8<4pC)`t^ zP<~hw-V|QTPMiCMAmgzbzcSC6-wI_un)s#5pTExC>{P&L1rw}U zLb}BYBO+V;J_9+7f}kdzFK^Vz2Zz*1so>y6QBVBu;UoT1F%%JbMEhcsDdv@%8_|$J zQj;ynJbk%WVx2B%qI>Vo=XGA4L0JF6ND~`v-~12P2c6)i@y@ncqMjDB-$p+N!nGah zR)9Ka@*V+k)#5;3oiu2rR~cvctPl6~^JJZj)cifR0#e2b=91Af>ia>DA|a4@r9lF} z-R(fc-k=oXo#qS|Z1AwUX5Q|q665#mN_1@ejj#csk!0AIN|EwN_M6gau6MX9jOurdz6T#*KVt`+dobS7-hA}& z%576F_eThyhXW&ZE#=HjQ^Y5<+Dj-`42>D*TD5TsX+WH|4&hgnRWPKoyAnffst^pL zogN6G`BG?>T}QY5H5s>n+Uo$9XbVXL=6#9=GV(CKo*r$^)OVXnX#AuA59T@x2c$N9 zRZSrzj%(8<*T0{}{|FNGGcnh%o7>MI67kg4!sONwrH#2j((>y-B`GVoTH==en+)$L zv(!J1D0PRv8!7gTS{yWogGq*4%#QhDvL<%ZO25RX)))>ir|qin>K50RyNBx2K!mfx z;`apv7Dh~v`kjfWhFSwdiYht#X`>Hem9u4-gS z^nM85|7yd?;`0+4GDp4H#*y^-3AP&dBikIpNB@|CB^S2T0(a7UHZsL5@G}VgOl(%h z%lN|uqDwLkFULQTdfJAcJ7kKmg9_I#!KTG@F5wWN@Pps5-^J%7CqD@lj<#|!?&IsC zR+NSIZ70<#WIB!4Kr3%j-}g5Kx;ppU6xi52zUew^@+nNOqsrIrTun=wo__Z`*05!o zG;;0`yB-}s$~^tc!Y5NTN0B3o2b^}yTDyw2hg8P>aA&$6Mmt37H_S;j#$69FnZTHpvsU zHqZWY%{Z>Wal`+S(cN_R&Dyrs4KeXIzP(pqGv1|FPO1>WyT@wGwk#oXF!rc0)03fd zw%GpT`yK2Hi(`@EeMY{ak+J3{5(7|WU00O(qoFr+Jmn1E%gPKqiB;p57#?S6=*KJ$r zr%FmB_EXucr03KWQy(|d((nq~x7FdSdwYKr=A3eW+kGUv;Un#NM)>A8TMAx~@55G4 z^+p3K(V+r*iuW`m*0hA$KM$d29zL7qk~ycwVoP0(Q@W0SryCP-Ys+)ho`*-s(gfpM z>}2I>n6)MnU+%}s?>-NHqF^R$n8TBGmmmBPe=nXo?L*+SF7ia5t@#=Gr4Al7tvyO+tXBi} zS#hta4(G`8JRaRCtX*H@9qtO0Pe;U;)RA))@43?_*M$%?YuvCi)pKeQp_epU6( zSmxLhprV%(5n41uEf>jP3J1fREfkifLN2SO0}tC&9sz5(DtJ0KyM^$XwKUA-FHu-J z*9EoNmy__Lb}t3cg_d!3P>?-!SYxx}HRj=d`SsH`6LJ&QTg!idV{)>vx!1tzRqv)u zJf)TqSI_0>^7p44xPB-0gEB{YPXBigN_qE{(QbGn8U}!1n8oEVFLF$Q1k!~0|q(MO%NlDRne*gFTxvss>K4$#t(`}G37E|nI( z%x8Q8=Us>hyL*q`Hh#0~_lJAbeD`%NCuQq8#!J78Q0kp)ji?^s zgWxffmF|BI>Q!3Yn&|)@Du=%~7{&*iy!!laNh4>!Z2r(bl|r6AeSOl+SMC(*l8Dc0 z0c;3lTizC#fA{3Bh~(SW=&@k|QR&6n7BEs?;^wPi zPd#`EB)il7c(K`QM&KQ|HWY>r?=!~_QugS7`={HYk+E9zlcz_u9ioGLF{E(E3@?M} zsHZZ;4w5PKG{Rs=1E;`FTv&|`g3ekm%X>}U6)yh$Q?#RcYy;w`PZ(vQXZm8x&nGl? z)vZR1keN);^YoBBB z`QH`4k?s(3!ic&3exI8}etC;OamA9-LNz20AOjr{rYeobKPDm7KHq z&{G3H;_1^A`-;ktf`o7=t@;990?~SeSGlA5Vek2+!%$(`7TZqJ^ebqVL=QiNLDixc z)fv&g5_S2`^TfO)gydGsQ$*8)Vu6QPF2o`#@Et0VSM&?7OEJ)HC;8vsKcnX@W?I&| zvtY{mB>L+DkpqSVc?#+K-$>4yu9$@nmB7J4T|*@36edaD%F?}T9akSt`A5{)Nr`7C zv-k>X^n-XX5jZ+NHg%@UKPk&LadK%hd2satZTyu|H~}%i_c8(-uBYY6Qyl5b30Qot z@T$s95n>{=>{I}P8PZ0N=8^X(G{IZ#3okaoGD!c(qG<)T61Uk?Z5hJ}J)=Fo z=l!pZs9E8mf!r1aHQEZ?7|y1JGa4rTGYL)2G2@SM9(?RAING)nVJVGwQv% zD!cX}Wiq;p_oFKDIC%d#pdDXSiMUgW%40D@0vE1qvB-4#jW+Jy z^GZTE9anL%@y>2{IoV+(jU%j(k&MDjt_V1RL{et}WDyoRbnPuMK0^F-%LW9oOwq;Y z2#Sk4hi~*%hJweX;W>f|97zk+A=3`e!w>GYF9sQTvsu{{w~f{b?=joOk6!gv3xX`3 z2A>_sjn--VOH>iF57{DUOp6W*ZyQEWf$z$U`Zb>{?0Hys z{4+lVM+?FYsKiH|vw&a@RvWLTO$7>^jz@D=RE*rrRvBt|q9 zC;7u*DtZ2?J#zX>8B^qLtHd)NVJI7=Cqrwac4G@((a*mgO_qm#0!7_)H;s3!RW%Mp z;kJc|W_?G6>g?W1iBVQELLa;%t>D?*Y({*Mv3ug&V1x5urcTq*OSTx)*j084V@GSL z2J&3F_b-K%XD(IDkrTU;d=EK%_Zj@AI`{VI_+A+^QdMSgrTswGu3@1s&T@_db3Ol9<&`vzw+F0K;A>*t%1{G$&nBG^08fh^yA0 zXDEmI(0CS=(wQs>vV7VuDT`?Peh?0{Q=^n(u{L~Q(!XCGV1P&QD_X2`7?z3d!pl+t za&n7CO0OBeEZZW?ckjfzXYpq*L4lYaD~B}ng*06d0xHP@zvkmYjw{+u9yAl9+|X4a2m@60+s zx>!hl`YrA6jlh3@)t-N=a$ud(bAsSO!0J=M((!~og9qIYe01(56C|$R__CT|qs432 zKw`rht8|1-gcj5h?G<6eUx+&~B;}K52u6nT`!W%+=&>|oqY+XXENg$Y$=1=mkzaTd zCGEeoTGldZM>>K({blj}1xJ}P5gVD&4KeAsWK^60oWX>pSWe}aL|eYqDPyu>BXTuAbWWG4{xsf z&2hKLmozHhm`9ZrYrRC|wiqT1tCp#&T*Hi6G78={RhDDGZN=NFGF)AdJ-?0IHKJ;4 zd3S5WmWG-C#NwoqA(vL$ydMxF#qY({R#E;}{U3&3g7;HDfxefkGMAqT@wnu+nbjHE z=y+v^e&%m^U7+XNEu<6K6Gk0M52#*M!%oKdOZ;^#L7(uGqqwy%@`Tpl^Ql0eU4_Z= zb<%Dpz%;1v$Lv*iYFxT70g38maS(Zl!Q-L%ed@xUyH){&`(D^Kfy|hegh<*p+_L^>VnQze zAfhy%drAw@>BB=1RcHF)Z=yw!na%cSQ}r8Lm+BB;C$_P*Mlo+}YzujTYLq(dx$-66 zj7{O%HoLvLgd`wYj=(7u#|Hm1E`Ekth>TQt#F3s1&U>A)Hi#?=?+2a?J?iZX}f9ev@Wh z|3+e54ahDIo`>Wy+kOxQuE2I;-o{}Vp~i!n!otylDaO?6TC2%q?m81u51ncf+u)o9 z3HGg`E9%mf?EMQ|Ax_}a5MbMa7Uo3U)u4vbY-so?BHgx%BybIX5q536SSZP%3wBn` z$RPqOh2F_GL>3MZxO1v0d?H9Lj?#4HqUN7^NMB9gVg^DmMg6hO_2dhA1Ed$fN69mG z_j)!nWqkg4FMi^4VmF?a=~ut$(YjDYOged`=&7S%G=H9LK{a3F57H*>A3LCGa}j^x z&S`gl&#>Lq1VGc20oysk=l=OgD%cIZ{wz(t;3QQUIRFtqe>-m7I}Q5`Fya;(_L zuz0EvX^$6vS#cy3@Olm-cIn5@YDV_D`EvHqay^1<^f^6`3EJS6fi&~_ikOtQFRVq+ zYg|8FDyzEoV}0eyj85P9Qt-b+Q#xKCi{Fb?oS$ZURmSXis=c`|a!U{^;diGHE0CD3 za3$cNANd*zWg`KgL&v+;&iETTKe@B_@LhGBKyN&5Xb zdrZ6rEn(h*H$um4=O!U^dgEsNvYnj%;`-`+ES!Gc)6rBs{mP-v)%;C}Hz6mc8 z-y}h8hB5bz&r+w&xtvuiJe=^H>rk_zUF=B-e2Bwi0w5ws=gD5aPl?X$!mDw9FGY4U zI|=mHG>fXofE7k#B{RCS&KFOFa00Z?_-{Q}HamCn6^8LXs94So1|HloEnH#k2M*|F zw=&mY8$whR3gENWyIuJhrMVBcZ^LNq;^tuTkKK&k-S3n?Rg&y^PXQ;sNd#5yjYoJy z-N5-ugdv%jfZ==w{ilAmz+~W2i5OI5Cn=W6fTS_^j?K70+Q$x03E?JG<0)5oRXR|) zgm-|w%Ku&rx^&bLiBPslQLEdL7PUX}+P+Ku{`=!|hcWIu?v{5jc>~+91q;ad# zizhN5yiKxm8Rk)`;#xbdo$<(aUYtQbnONR4!!7)gFH=i6yDCJm6mQxFOmEAfqjDg= zH=_f610O4-1$tB4O#$5`K31qT*Wv06bvP{D@}nTfb$ion38-m8u@2Rlalj1LxipmP%R-$l`QgGq(iO#nr$nY&K}Px?00Q>N@A{u zI|WU`GdwYa?L%@6NuV9%O7dVJ?3?^_7DUNc zrp_~$&7DA>`DEVl!TH{Qf+0&S|8^~jR`U7F1AOjag}tD2eyig@XO6<$L0^NE8VIuY z7q%nvE?=7kM-iVH?EMq3&bl|xykX8m&ETDv=F+St+Gm>xjEPItPGryzo`;4O4}LtF z40zZ3g+=AzW0{(%$y|CbQfGL}g?G#AF69u|bB}R@QMGG<8P)D^VD<-1jrbI@cFYBf zIALp^bXlU{uriY8(^w$_Wz9kM6b0W;Hj0zLdiiG7T9l+`0pHM|kpl+PDfHekhHmk` zN0Yu{#jGgApNWNMzh7WFE2yQ#r}vn19RrQ~htQo)-F`?KNqC^8Yaw6EK=RLcwR(~^ zB8~jVSR<{g zWvvl`T+J4~u_%oa4UwG%yEL-tXu2G>IF*|-{;3KgdQmtC;R@x$ar1_NPb3g7Z^@_^ zBsHlpu7;St?u8#YFmuLpHoSqOS8{1v7S)k2V18&FXNP=wSsyDYZ~3y_ps<-032u-3*n z%I;iGJU^Fx7c+!}!*c1#2L!ihA>sUP=83;{npL|b4vt`IwkRInC%1{`8SvPs+(Wvh zWwecI$y0eUNz-9Xe4cz`hGZSHI0g5j^DVR67w&Qd*~Te(?*LELs$PvUM0MlBD7{$t zEg@53Dhu6@XS-^FXGHUPz6E4h&varr?2CqTI)Z`$JiDdMA_;^PpAj@K{gOfP;)_Y>OvnuhVZB$Py9zHMrvW8>voz2K>Q^dM>lSI{` z2ldq0Ox`(ynSg^Fu0T-`J}w`zd@w}*kslGkDHtz?KrI2*C3LK~b9v zW_syrn8=7Fj9;^Y0>YnA78)m!JgiTxl(p-{*_8l9NT}xPD%FMXI765TteUp$A`@*| zn9?8m1R)%oWop6T-qAnBU7{KfSN@VZKZ=i~{V_V@RcAq=h5@S~Z7yDd^y%z7xw7p) ztYRGo%Crn8f9Tt@_aGwKTZOZehe|&Z5G9~VM<-I_yj!6t1XU0_z>rGd!82W+xIT`! z%lW$aaWI*TogRyT6Wn*n>$UvlN5Vxdrf$k=WN)D?aK?OwmBG1J!;H&swTmsfg^p%- z05PA@#ZOHS+|g2qfw>`RaBvHwp=J?Z1+r>K)j)%Mfpc=(HOwKvzwl?`j7yvcwgt`x zK;)9-t>wWwxoPRVnau10Jv%*;Ogk9~VH3QwPTDz!7vUcE%w^as~C z1X39A7Y}0|OtTrbt{*Y{+8peQqT-+OVblqbdfXEn?u7@j=3h_@D*;+YG0* zw24@RNlF1z3;gr8B-KQx#~$~Bya_ehosa#IcLL=+g_D?4%CYyRZf2^icOkZLE~K^#kx5?UaYy zUcgs>)`Bu7hwl%IGWUTaWm*lNPN2tWXzeFPo!b!-i?O3K-?U7LY>sHb!H%hu%AziH zVO>VMMkS8D6CRvORR(|B+5EokehTB$C zN2LtWl-Nx{b~=_+NpH%tgj@07QC+ zGj6W)j{vj_G*u$@Lzi3!>;)cQ4^}+eJ)miZ) zj>TpDo7A_UV+3J6FC{m zlAW&kM_hCzG>*8Agu7fawJ3_c62a2kLH5!FG zdO-3r4v$1!gtU9LOvN0d55D7cJAe5%O$46jD`t)#yf9remQ=rJJ~={28F9K+zN!}q zrJwIE#EvuV%*MS8nJm!R@p!s7wm_sL2DaQ}z$ri(S$5<{IOeUK&&=-339|EiLLLEBE9BspRyo8SCk z%hgMCcu)OKXXy4Xe}`z=5xw!OTC5rg0rN;EQ(*a1Bd~wVeMiYs^S^J;wA?b^D)@S6 z{5Yn2p8^OxUn!=_4pQEoLA+{!7lt5R&5CcwBgRB?)`(QnCJMO*SxR5!Zc6CXIT;+O z`KMf8%d@{L2b|X!T@m129%$$}W(Mn?$mexeYyCcgRBLCFH>5_wj}M4Ycs>Ed(_6GmXoR$5svN)cFORKpSkPQYOS_%-zU*| zbmoILC!Fm1_w10CH{gBI=go!>KWC`{51Wd>l=BQ~hl155^lA~<$;(iN8NZ3${RcJyQsuklo z05reh*&R^IFaSGJyUU6Wj=C4gHCq=w|!krw(Z$ykO;6mLi`zB!Hi` zJ&&PU36DNTHQ9iGrFn|#LwS00<&;1@(8zvrltlzWN)kiw|4pO%cm7AjD=9CvFz#%5 zgw%&s@T5l1N&y3=kAMylfEeG!KZ2qZG#k(+=VPF1Y*du1uL|V{SXMms5cQ;3+vig) zj?co_fCMqRW*dX`#Gx+BC!g}3RkjghI5$Jac195=nhI%vK_WG$LgTafmXFvYqVzlo zY8`fi_qJfAPx+s*cdyLC5&20772`5K;LVEFJ7+vARlIfV`TIg~-*pO+z7%AW6zkq~ zSX(H8UR1B+uhQ_%XqQJ5)>RwV4y@kQ(ALqbJs!;XvjiCrM>N4Gph{QNp+8{s$HZHb zi%;=4G3(`9v*9+h9B$m#!e7TDcPmN6Kk0C3Z|QnlL$k;ETNZ!YR#2FZEq~>=R`+N$ zPD7B#>pTvam&?DMzl3P0_ALs+KG09ZMI$OtWF?(*X!2JEi-XcWnZ}*6j0DZ0cV6S(W5oEok{8z|Lblb>m<2`M*}F z1NFD6*Z=4s5nYXdPY+xTvM(-z%wp(WG0l&2!JqO-2hxAeAB$PtpOBXx!hF+D6C!#* zP@!R=^$^LnO6T3fXslea6;v9eP8?5{#)3f&hk1hT-V$>o>D?OV zr(a>mr@xErKPS~9B`Rw+sx{wb78WU-=6eJ^idJKS7XNl7|OQe8E6E}iiwCi*CfoT+Z;*xn7u|h9cCv zLLUZ(=7-||rAtFCd&!@mfy^~)gd{s>IYZaHH&4)3Kas=GJ{s6G{R zn$D~O>?jEDXa(TYXzOH>^(K!t(R-PX%7+fGmcIa)J0&x|bHpIOsLCpnFCQ1r+6DXd+c%4w`f zP2IJ1IemSRIMJ-5x!?#T^`KxpVkvzn?OO$Lc3+};GRVKFifttpjm&jIf?m#_!MyK7 zxyn((nv1<(-;_k~j1$=P=E9|=wABScrm2S?HDBR$!R`UJT~YP0(fF7~6qzqdyp<&! zIIcW`JXN!Ke_mSZe$1Q=qCm8F&_VH(f|w$DMI3r|jvVMmwep!a@(^WlNKVqQ6nH#n zW3wrje&pcp>diync2M?xfrBFOJ^nX{vq$5A3JTUC4ueyi&_G4by9RQ`b@9X)Kxi( z^n}ysj#gm&eA0JEU1+vbX?V%2y!YWaz-j+NmnaK+XJ&~;l*0kf`V3Kts*7>;L0k%F zxn`{FrK;}C)q=iGn4;50_c|QVvasy>(Wn|U#pHg><`+}H8TIYT`TR&@BU){@IDhWM zIAdo8vwy~0jehxua%XMhEcxe$i*y!@Rn4UMmtIVaO)(T;Np&Lph@Vd>3YGOf8cX?5 z^+GPDWGsG*j~oJj>mq*k$EZW}r5!;2NTI9c1^xuGWP^nbrDh^J%)>Oq=t#)KNml6U zQI{>@_PB0(Jygu#h2W!wpUHj7X*=+1>&9zkCPNi|$M4Dbjumg{*#G6pjvZEC)gM=XFPrmPB zAy?}3-gxGdPYt?Ga^13T*SfU&B43qv-4#DxlE+ooU1(y5(aBRZ%94U(mq_U3=pSdl zgq?P3h!~91kvnM*`C6Jwwomm2SvcBzvzJKcgObfrzc&oHQ2ets>TBl|*y&q54n2#lR2bXySw>YSTtOhjsOx7VcWel$uf57q^CItLvFxKtwMk;Fzc)+3QL zx$~jTO- zCqTyI20i7;Yw@<9uwLb+4Ai3KOP7tKucj=7rgT`0av3T+{tc&dd`+A^o;rHJN5_`? zo!DMEnk1K~9PI+7jug=1V`W;nL*%T^N(aI#o8EcilAdP1Bq6F^YFt>f_In0>`XPP_ z9>>MO692jSShM^$#Lfouf~DI7q++B}dD|6RSJ8_%V$J~)N?A^x_iHw)S<9OgCctXM z33?OA!rz1TIzfNnH<@{EYCzsT%R}xDt$5{yL~vF_5CvI)!>*_SrR7t0+xRgIWeBGY zIXwMi$|$%31&7Pp5=s{;R&0J(>RrF%1D{`mbQCtAYBRuW5ishDUNM1Iye-l;w?yrE zB%C2Ls0KKsN|u#XaR0YL3>!gPsH!-t>JXtB9;?B;7?r@V1_13bOz<^%m!FbzAm)7E zG2C5@#J=mc=))Ju4Y?7l%P^k?0}K8H!fh#|u_?4WPzOrQrC3L-R5&OI8+`>tyPQzXnJ#N4T)@NPlP9@qKJ!%ncnn}T2DD?Xck}-HzozW7 z5@~5s>@0Zqp1BHM?VyYvqlFWV?DH1(k64=skdYg8?kalZA39eGV!BHFL#tFL>AI$% zhHlGYb4@f3t*Nk$9#2WsFrrZVbcKm0l-YWH@d1^9!vamtes30Ki&b5<0Uykz)y4>E za6U@39auO|CLLk!x+HBGb^ePGA_|QfKF@qiJrz}20LPR<2`UW!s-emNQwNtx7EyT( zZ<5X=`DR~HM5{q@KXR zNa*_g5-O3wlj|ZTK%`UohkQ6m<)=#H_b%mIs4;^}iB6e37<-wbua7|5sDKQD-KZ>n zpN;*Ejr$pq)_^}f@gC$%VczZEFTl!+|K@hiSUMq5-cFOwzoAm!?rG#HL2cO2YZrM) zzHop4H(A2+85Rn=h|Wy`Iy{tpsq>A2o_Ow_5(BXmVt;5?pMjY@gF7;yY~68-kj8uW zaiaDOl<7vuSGlld95gWydG>7$z5i?`TEK$QG2UCP)*_x0mlGTX+K-<~GO#)v=X!Su ziv!Y(bva@Zy)x~^jC|t5kayPkO<1OBQ>qV%zp+e5KPu)aaOgze7^ z5J8FYKkcU|9_)3jgo2)Dfa-sH^1o-^RSaQusHn1OWPmNs(qN^XKHSg0p~{X&l6IQI_>4d$?_g;;!9 zXY89SAt7Frh}+8Ts1QXQ9d#_;y|`8S){n!=(*CyF0;cnMc%H$#L;z#Ke-D~6e;oq% zQigJA;Z{@T!3B1Y^vHO3xAvDO(fGFck4ZYpB`T7Pb^rGJhV*D-x1a`P1z_ESYk+>UD>2THj<>NQD;9s}Um3Umt>+8>ciGx(h+e^>d* ze@dK*yMyEL*=5nY!f#wHbY=2*y}57258Ha;8-KlqaQJ~HpbAF`<%7C>4e#GQ-CqHOZ4EtxN7i45f%)xy0SA<6`4(SsrfWex%gbO#z>dg(SvH7f*fvR+i^@$I0 zdDaFgN4!5mkm0)p;Ef-9%!Oz)q26>gxNu-^7SUY(9&x5z;do-o{)qAC-`-bJz6PZ^lP4dtDz0+>yu$Owt2qsk?&y%Y@+Uww zd{%ScjE(NBk>O{MW-@RfM|6}?11ywuU4oN$LtWT+KLFo^S+dVzZxTn?=BuaQ61+0H zC{h`$5S)PqiDN_`>)ojj@l^;T&(q~(S8xnGzbyRhaV;Mj`$4>56$=9#EKwC4Tjz{Q zY_#||zfJd@F0m+8T1HODJ;8a1ztT|zZ0d1r$ybyeH1=UE^}3u05CVV#Giv7zi0nB* zepEk0*)+%?#WWka5{W^R!71ZURqq9Gq7=Ri-&h7vuU~abM4my*nCkI_ic|CG+Frr^ zJOaNzk#b{^I<1=2u(o+AOlUU#Z#@L}9@V4c!MC+8fI z^mUf~5q#c(6Bgp4u3Qdo^-BDozh+f*|N67ng zHBGjiYz}uE;Ird*&?~94R_K8ZVHg;#N%SVgi`G%Z;eJqhx1QWCmGiG?xhY0iZ!2?x zA;XH~QL~=Q1$<1<$aK|VM+9s^R80Mbp6UrLnZl%}sPT1z1w&W@cSu1a_q-y*yuTVs z;e}y<(?8bv3_=hjyH+PGrIZ`4Z4EZfbyF?TYO^jKquN@;TK-;L&)eynMEBoTrlu># zoB2>W&xbY&R(o^Sv=6VdON=Jrxb=8q!qu3>9T*d~*l9KR1gIhc42~6xl9PEXRsl*q`G0z0tVL(lMM=_1 zATEAsJ?icUER2l^Iyd*So(kX zWLAH>sdw3!2LNr+u^f{y0)_F-tK$O{>{B0jgZvj1L$SSLNOP5(LJ~osFS@{^6@&i} znr)1$k5SbSqpQ|$F}8o`6DH7AIEmhwhHvA14hF!|%ySIDVMI$34jq{ag!1|qpkmp4 zy|fkvkAxkiO%7D_XW~@yYl}$fkmEUf`K1Ce_{-3w5xfE6?4`c5h3-2Gf@U0 zhAPkmGFBm%3?c=e1hE$iJz zq-E6ko1g+66;;vmC5~5bMUsKIiIPcbtS~SDs(q^oz?u96&L2Jaz6!3)?t!yr*e)ad zpw91D)C`pR>_-=bOr#iPE!hzd%6&O{;^B$!>9Te6$qfpX)<=gQI3!XftOeV5eFs`_c)ShP>tnR?wcZsD4A6mMiB`vT7__0*9J|Aad)nqn+OE z?=ZlrC*}KwknxBt0P9wJV1< zW<(ariy>O}e0b|x%#r10WMpcqx2bV~)b^vTo=LG4W4A1|{kMjAwN>|UBP=-f9#^ZM z5HYE<8e!FBp`^D~DI21a&k~aXx9Wv&Nkj^&WVF2KY$LKLwy9gH@g8r29JIw*_*J^f zbCo;vwoag5KQxL{pK*55&lTBdo7y}0{#1Y{KoX_EY~fj%=%UZ+o4Gd}-oDf&GFhX; z{T^a-AK~KC->swiOBwZ%^4xk89@!}c_R5gTr0>l!us!Ld`FX5OQjUlu$=)tsz7=vR zxE%vH1uAy_JG*@R7ucB;g|;5Cdp~mhJ}Uk6sPtw!5~MH6S~`EI3Hq9> zqEWgWi+_^goo$?LR+9H+=zTgdYDh+mZjO1BD}lNU`m9|s0rgnLL74`t zY;uh#)$ty+niEn?Ln0-fcSD#YP4>N$_)76G_O?5~-zD&;Ps;vyp%)XfT^*qL@2$me zCg6?NqcNbs{ooZCK-XYEATm47e!MBhl8%l3Yl7x1tiwGC zf^m&Bu}`y~UQqki!LIFZh+=ilKsmB)~Mwlq$q;Y%I^Ysz&s z;!a4zS*HMAqTO=D;&9Cdr`}KpEtHGtKcHiiwHW=?M`t(v$+sv|arpe3%7R4!8(B|m zO6Y`Rfd|1vN9e#cf!(w1y@%PA3^DSNUu8dj@?}kX|DyQTn`wOC&syabWQ?ljn(g_} zWH1M~R%@pw#lS-gD95QZ7{2foxGn_*9$;TT?k^W%QnSeiFJDnl{)x+g6S_TL;WG{xX?-1cjz z>cHRj&<`2!u0by;wFWr!NWzih3ZQ0-8Wy^xHLOqA7`|GeRNn)*%BeV($Z67J-Rt57 z#d^?&Vy~epo*eClKf{^8-?XN#_QgK8;fw}*4!4QRk@1A4)w7ITX_K|1P27*EUY{z9 zGUA`Z*9Y^(^^Nk`_Ju=~k`j?!W|l?9ZPK!x#fRqs9h^}N*>(IE*`oX z3})Db)Qs{E!oBmL)%-y=oucjpVhgI3=-zmIahgGWZ7ex?%Buyfh=r`8!2kN+hBx)` z;6ykOD6*e2Ry~b=|D5c-fw2g~_hOFy7=ka)&0ne+HuTCH0!?gNaa+YYOHQ$sNO04C zZ40eL)9zvOSx~NkeVZ(DQpOQJS);VRXo z2~3N;`)v9x$qLy-TBGOf%P*DdwurA-$m8WMQkZe)2xphZ11)45FYy#qCqe{PbBjgxZw}Mo<==Tret=TZr zmFIWW?H^1u)o(FmzkVzGW|}ZB0RH@GJ+TI#?jZSVIB%QyEkmBr7mhR)W;>7AN2(Ma zVZle-AN3tqWHaKd4V3=HioTum1D_XJOm7Dgrj7X|Cmx>=?HEst^A9KDp@gw@$;{DT z?{zmkGS}{M=FHKVgLN}Ef|XqXXNPZaqyEzAs3;Iwr2fp~fQU3D^g)b2Ce%5vLp5i$ z!K;mT(5usDHlg+1)osM+GigL&aFeIv_$2B-ThbzeJqf^j&V_SLn+NPU#RGhw#>xp= z%QZ+1-?b`TE+{zsHj0zoSotM<$dKJJ??LYTXw{y8Rp+;}nwsUoo3ft`)M4n-KzQ~z zmLJ{pOW$H;x5#|RrJ{P{`PYskkB5>iz#EcxNT-`Sys7(u;XNdpw3dB`W8`h5u#-^a zwiV@8nMEMJ?CJx3;m&2MqkZEy?YKzo2t9E5<2xTn&{7==m|v$(!be6B6t83GEox0u zjY{U?Ve1}0watNk1!UV6pBa+hq5d`~tvpw{qXEcXdNFWraW|;OO6SY6#mcaGn_EwG z@2#EP3fL~=p7N#pJq|2xOQ*xv0JUm5@nBQ%k({qG$ch)F2M-WcvItTKQ#JMeM4;S% zpRN(Ox3QMPo_XfS9r=MExh~Z&-10!HMPGuh2!H!=q@!_J$~|;St%WQ*5Sf(`)Vzkq z{ocN<(K=Zv6AyyC`W&I1(XK)^%dCj;s{HuL4a1c8g=Drpon2S0f)hHgMwI*Pm^TBI0w`v!jRuJ)kcKZ zc@a#n7Ci9k%C#;RQh+1zo;y?Ja~*u1XJ~<}N7qRr{~7ht#wlV0E?<6|?)YeLe6C5! zZKQ2DjH(bmFYvo=7}*pwS>NltwIilbUf+Zc3fRbcj6AfiS^44DPIDVYu4TH%G-|9|Cs|{ zw07KB1mrR5a{V(x={BZda}2&AP=Vrs8iZg~!gy7d`P1udoEI|g9R(5yR|$FYNwejL zAEYo&YLG9abei8if9<$Ww2ldFOM9^PpFwRylx0@e73uQ*&RPuV7yRF5vsNB>pU@<} zNY-B@hyQP>yP9u*mt*#iQAs*pHMZ}CZBwhOV{v7K0#<75wAZoY|C>8}th9vrF$$2m zG37`}AX$t81QIkco3;mC{m)4G1=%v^RR;=(CI9d+CtE4;mj*ioV0!$oZDpxo=^xYr Z;KuOwuf~^vkpJA*pQt`psZ_QI{y(VxwBP^$ literal 0 HcmV?d00001 diff --git a/resources/images/sparkle_particle.png b/resources/images/sparkle_particle.png new file mode 100755 index 0000000000000000000000000000000000000000..836153e662336278364aedec1dc8bbda2151002f GIT binary patch literal 1095 zcmbVL&ui0A98cYtI-H1zsEqlD9u#epG)>lI>uS;@TCsCHQU9h zcu*7+!GAzcDjvOf(1RCm9>s%5Q4tYjmmxCHm#v*1#tw!g-}mGD`FwxhoSHm6Gi=^4eTM}Al7H`G^*jY89++0Q5AxsYt~^In)brwH*lPy`f5&P z7SEQ>$rf@`W~`HnT%VvRYCIqLrqzTPRAJrma`cZEFKOV|IeJDYu_eC%8_t=<08TGX zR;yo+8m~;&@i9Y#H5J^<(d$mlmZm@f1rP`+K54NW z2ShQ&Wdu>=PJ%Scr5TpIVv@_qf+(j)LHD7FwZN{)WzFc?BD);jz}S}=rrmC*+I$KH zb%v89DdtG0lSCsKE_m3Cl3sYE$DqN`3LGCh$OAE>Sw(X=M-xxiLva04X@l4cyNMzx zV0V5)&I3LD4=g=rBS zn{hlFwZr3adDf%VVBpL{+X#>g*77Smo7m6rQkGW~HZ#g*b%Ep5bXHYH^)!i_)m5IS zyIA{wa>fv6n7BFqX_np*>A-lo(fVYwF+9*C6BCfpcz9sq4jHR`|7XXzw>>2 z_YYKg@Sw22UR|O_z(wjcbM@!ZJ^B95^;1v#AKZBSVdd~xwCnXMxc`0W-TlPa-9)E# zmp(DD`fB*m*X;>CuiTnXEa%5>J-@o|LSJWOcyMLg^4?GE*+i##YIXa!C%eHH>P@2m Y0OfpcWNtrydnF$2qCTlTQ!icn1Cjh$8vp