Chicha is a digital platform that helps people become food entrepreneurs by crowdfunding their food dish, finding and renting commercial kitchen space, and creating the marketplace to to sell and market their food dishes.
The name Chicha is derived from a sweet Peruvian drink made with purple corn called chicha morada. We chose the color purple for the logo as an homage to the drink.
Below are the User Personas created for the site, along with sketches, and some wireframes.
I worked on creating a responsive infinite message list. Maintained a material design aesthetic as well as many of the Google Design patterns.
Delved deep in the user experience of infinite lists.
Pros:
Cons:
Small module created for the lab that tracks movement inside the Havas Lab. A kinect V2 above the lab is used for tracking. This is then projected against a large wall for people to see movement in the lab during a given day. It has the ability to track up to 6 people. The view is top down with the outline showing the footprint of the lab. This is part of a dashboard that is still in process. Code below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 | <!doctype html> <html> <head> <title>Socket Test</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.js"></script> <script type="text/javascript" src="js/sb-1.4.1.js"></script> <script type="text/javascript" src="class.locationPerson.js"></script> <script type="text/javascript"> // when page loads call spacebrew setup function $(window).on("load", setupSpacebrew); // wher the jquery mobile is ready to initialize the UI call the setUI function $(document).bind("pageinit", setupUI); // Spacebrew Object var sb , app_name = "Dashboard Client" , values = {} ; /** * setupSpacebrew Function that creates and configures the connection to the Spacebrew server. * It is called when the page loads. */ function setupSpacebrew (){ var random_id = "0000" + Math.floor(Math.random() * 10000); app_name = app_name + ' ' + random_id.substring(random_id.length-4); console.log("Setting up spacebrew connection"); sb = new Spacebrew.Client(); sb.name(app_name); sb.description("Dashboard Client."); //List subscribers sb.addSubscribe("location_pos", "string"); // override Spacebrew events - this is how you catch events coming from Spacebrew sb.onStringMessage = onStringMessage; sb.onOpen = onOpen; // connect to spacbrew sb.connect(); }; /** * Function that is called when Spacebrew connection is established */ function onOpen() { var message = "Connected as <strong>" + sb.name() + "</strong>. "; if (sb.name() === app_name) { message += "<br>You can customize this app's name in the query string by adding <strong>name=your_app_name</strong>." } $("#name").html( message ); canvasApp(); } /** * setupUI Function that create the event listeners for the sliders. It creates an callback * function that sends a spacebrew message whenever an slide event is received. */ function setupUI() { console.log("Setting up the UI listeners"); // when the slider state changes it sends a message to spacebrew $(".slider").bind( "change", function(event, ui) { if (values[event.target.id] != event.target.value) { sb.send(event.target.id, "range", event.target.value); values[event.target.id] = event.target.value; } }); } /** * onRangeMessage Function that is called whenever new spacebrew range messages are received. * It accepts two parameters: * @param {String} name Holds name of the subscription feed channel * @param {Integer} value Holds value received from the subscription feed */ function onStringMessage(name, value){ console.log("Received new string ", value); arrVals = value.split(','); _i = parseInt(arrVals[0]); arrLocPerson[_i].set(arrVals[1], arrVals[2]); }; </script> <style> .square { border-style: solid; border-width: 3px; border-color: #7f7f7f; width: 400px; height: 400px; } body { background-color: #f0f0f0; padding: 0px; margin: 0px; border: 0px none; } #frame { position:relative; background-color: #ff0000; top:0px; left:0px; } .widget_small { width:460px;height:335px;background-color:#000000;position:absolute; } .widget_med { width:685px;height:460px;background-color:#000000;position:absolute; } .widget_med2 { width:1040px;height:460px;background-color:#000000;position:absolute; } .widget_lrg { width:930px;height:1040px;background-color:#000000;position:absolute; } .txtSmallWhite { padding: 20px; font-family: "Helvetica Neue"; color: #ffffff; font-size: 16px; } .txtSmallGreen { font-family: "Helvetica Neue"; color: #00ff00; font-size: 16px; } .txtTitleGreen { font-family: "Helvetica Neue"; color: #00ff00; font-size: 46px; text-align: center; } #labmovement_canvas { } </style> </head> <body> <script type="text/javascript"> function canvasApp() { console.log(window.innerWidth + " " + window.innerHeight); FRAME_SIZE_W = window.innerWidth; FRAME_SIZE_H = window.innerHeight; //var COLORS = [ '#ffff00', '#ff00ff', '#00ffff']; var COLORS = [ '#ffff00', '#ff00ff', '#00ffff', '#ff0000', '#00ff00', '#0000ff' ]; var COLOR_NUM = COLORS.length; var _colorIndex = 0; context = labmovement_canvas.getContext('2d'); if (!context) { return; } context.strokeStyle = '#ffffff'; context.lineWidth = 2; setInterval(run,intervalTime); //Test vals arrLocPerson[0] = new LocationPerson(150,150,COLORS[0],1); arrLocPerson[1] = new LocationPerson(150,150,COLORS[1],2); arrLocPerson[2] = new LocationPerson(150,150,COLORS[2],3); arrLocPerson[3] = new LocationPerson(150,150,COLORS[3],4); arrLocPerson[4] = new LocationPerson(150,150,COLORS[4],5); arrLocPerson[5] = new LocationPerson(150,150,COLORS[5],6); function run() { context.clearRect(0, 0, labmovement_canvas.width, labmovement_canvas.height); render_lab(); render_lab_ppl(); } function render_lab_ppl() { for (var i=0; i<6; i++) { //arrLocPerson[i].move(); arrLocPerson[i].drawLines(); } for (var i=0; i<6; i++) { arrLocPerson[i].render(); } } function render_lab() { context.strokeStyle = "#ffffff"; context.lineWidth = 4; context.beginPath(); context.moveTo(131,348); context.lineTo(375,6); context.lineTo(733,266); context.lineTo(675,351); context.lineTo(675,387); context.lineTo(797,387); context.lineTo(797,757); context.lineTo(730,757); context.lineTo(730,836); context.lineTo(187,836); context.lineTo(187,390); context.lineTo(131,348); context.stroke(); } } const FRAME_RATE = 10; const SQUARE_SIZE = 400; var FRAME_SIZE_W; var FRAME_SIZE_H; var intervalTime= 1000 / FRAME_RATE; //light window var _lightY = 0; var _lastLightY = 0; var _lightX = 0; //Arr Loc Person var arrLocPerson = new Array(); </script> <div id="frame"> <div class="widget_lrg" style="left:20px;top:20px" > <div class="txtTitleGreen" style="padding-top:350px"> <div style="font-size:120px;padding-bottom:10px">[ ]</div> HAVAS<br>LAB </div> </div> <div class="widget_lrg" style="left:970px;top:20px"> <div class="txtSmallWhite"> LAB<br> MOVEMENT<br> - </div> <canvas id="labmovement_canvas" width="920" height="1040"> your browser does not support HTML 5 Canvas </canvas> </div> </div> </body> </html> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | /********************** * LOCATION PERSON **********************/ function LocationPerson(_x, _y, _color, _index) { this.x = _x; this.y = _y; this.color = _color; this.arrPts = []; this.active = false; this.last_update = Date.now(); this.index = _index; this._last_x; this._last_y; this.kx = 0; this.kz = 0; this.move = function() { this.x += Math.floor(Math.random() * 20 - 10); this.y += Math.floor(Math.random() * 20 - 10); this.arrPts.push([this.x,this.y]); }; this.render = function() { if (this.active) { context.beginPath(); context.arc(this.x, this.y, 15, 0, 2 * Math.PI, false); context.fillStyle = this.color; context.fill(); if (Date.now() > this.last_update + 1000) { this.active = false; } } //Write location context.fillStyle = this.color; context.font = "35px Arial"; context.fillText(this.kx+","+this.kz, 20, (this.index*35)); }; this.drawLines = function() { context.beginPath(); context.strokeStyle = this.color; context.lineWidth = 1; _length = this.arrPts.length; if (_length>0) { context.moveTo(this.arrPts[0][0],this.arrPts[0][1]); if (_length > 300) { this.arrPts.shift(); } } for (var i=1; i<this.arrPts.length; i++) { context.lineTo(this.arrPts[i][0],this.arrPts[i][1]); } context.stroke(); }; this.set = function(_x, _z) { this.kx = _x; this.kz = _z; this.active = true; this.last_update = Date.now(); //CENTIMETERS TO PIXELS var CENTIMETER_TO_PIXELS = .9; var CENTIMETER_TO_PIXELS_VERT = 1.2; var CENTER_POINT = 450; //CONVERT CENTIMETERS TO PIXELS this.x = CENTER_POINT - _x * CENTIMETER_TO_PIXELS; this.y = 800 - _z * CENTIMETER_TO_PIXELS; if (this.x != this._last_x || this.y != this._last_y) { this.arrPts.push([this.x,this.y]); } this._last_x = this.x; this._last_y = this.y; } } |
This sketch uses your webcam to create a polygonal representation of the image in real time
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | //inspiration: //http://robbietilton.com //http://vormplus.be/blog/article/voronoi-paintings-with-toxiclibs import processing.video.*; import toxi.processing.*; import toxi.geom.*; import toxi.geom.mesh2d.*; import toxi.util.*; Capture video; float size = 11000; Voronoi voronoi; PImage imgFrame; int pd = 70; int cols, rows; int x, y; int numPix; float colorThresh = 30; void setup() { size(640,480); video = new Capture(this, width, height); video.start(); //if you want to add a pic, uncomment this line and add the image //imgFrame = loadImage("test.png"); imgFrame = createImage(640,480,RGB); smooth(); cols = width/pd; rows = height/pd; numPix = imgFrame.width * imgFrame.height; voronoi = new Voronoi(size); } void addPoint() { imgFrame.copy(video,0,0,640,480,0,0,640,480); imgFrame.updatePixels(); for (x=1; x<imgFrame.width; x+=cols) { for (y=1; y<imgFrame.height; y+=rows) { int loc = x + y*imgFrame.width; color pix = imgFrame.pixels[loc]; int leftLoc = (x-1) + (y-1)*imgFrame.width; color leftPix = imgFrame.pixels[leftLoc]; // compare ver: color float red1 = red(pix); float green1 = green(pix); float blue1 = blue(pix); float red2 = red(leftPix); float green2 = green(leftPix); float blue2 = blue(leftPix); float diff = dist(red1,green1,blue1,red2,green2,blue2); if (diff > colorThresh) { // voronoi.addPoint(new Vec2D(x+random(-2, 2), y+random(-2, 2))); voronoi.addPoint(new Vec2D(x, y)); } } } } void drawTriangle(){ for (Triangle2D tri : voronoi.getTriangles()) { color pix; // use Centroid's color Vec2D locVec = tri.computeCentroid(); int loc = (int)((locVec.x) + (locVec.y)*imgFrame.width); pix = imgFrame.pixels[constrain(loc,0,numPix-1)]; // stroke(pix,100); fill(pix); noStroke(); beginShape(TRIANGLES); stroke(pix); vertex(tri.a.x, tri.a.y); vertex(tri.b.x, tri.b.y); vertex(tri.c.x, tri.c.y); endShape(); } } void draw() { background(255); video.read(); if(shoImage) { image(imgFrame, 0, 0); } else { addPoint(); drawTriangle(); } //fill(0); //rect(15,5,30,20); // show frame rate //fill(255); //text(int(frameRate), 20, 20); updatePixels(); if (frameCount%10 == 0) voronoi = new Voronoi(size); } boolean shoImage = false; //boolean saveImg = false; |
Drum sequencer made with P5, server.io, and tone.js. You can save your sequence and play it back later.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 | var bpm = 90; var current = 0; // drums var kick = new Tone.Sampler('audio/kick.mp3'); var snare = new Tone.Sampler('audio/snare.mp3'); var hh = new Tone.Sampler('audio/hh.mp3'); var hho =new Tone.Sampler('audio/agogoHigh.mp3'); var drumArray = [kick, snare, hh, hho]; for (var i in drumArray) { drumArray[i].toMaster(); } var socket = io(); socket.on('initSequencer', function(data) { parseSeqObj(data); console.log(data); }); // how many blocks fit horizontally and vertically: var hDiv = drumArray.length; var wDiv = 16; // array of Blocks var blocks = []; // =========================== // Drum Pattern Array for i/o // =========================== var kickArray = new Array(wDiv); var snareArray = new Array(wDiv); var hhArray = new Array(wDiv); var hhoArray = new Array(wDiv); var drumPatterns = [kickArray, snareArray, hhArray, hhoArray]; // set all the drums to 0 empty to start function clearBlocks() { for (var i in drumPatterns) { for (var j = 0; j < wDiv; j++) { drumPatterns[i][j] = false; } } blocks = []; } function parseSeqObj(data) { console.log(data); clearBlocks(); drumPatterns = data; for (var i in drumPatterns) { console.log('doin it'); for (var j = 0; j < wDiv; j++) { if (drumPatterns[i][j] === true) { var bX = width/wDiv * j; var bY = height/hDiv * i; // reverse? blocks.push( new Block(bX, bY)); }; } } } function savePattern() { saveJSON(drumPatterns, 'myPattern.json'); } function setup() { createCanvas(800, 400); // set up tone transport Tone.Transport.setInterval(function(time){ increment(time); }, "16n"); Tone.Transport.start(); Tone.Transport.setBpm(bpm); // ============ // UI w/ p5.dom // ============ createP(''); var volumeSlider = createSlider(); var vol = createP('Volume'); volumeSlider.mouseMoved( function() { Tone.Master.setVolume( map(volumeSlider.value(), 0, 100, -80, 1)); }); volumeSlider.value(10); Tone.Master.setVolume( map(volumeSlider.value(), 0, 100, -80, 1)); var clear = createButton('clear'); clear.mousePressed( function() { clearBlocks(); }); var save = createButton('save'); save.mousePressed( function() { savePattern(); }); var tempoSlider = createSlider(); tempoSlider.mouseMoved( function() { Tone.Transport.setBpm( map(tempoSlider.value(), 0, 100, 40, 200)); }); tempoSlider.value(map(bpm, 40, 200, 0, 100)); // ================== // Import Saved Files // ================== var dropZone = createDiv('Drop your saved JSON file here'); dropZone.id('drop_zone'); // Add some events dropZone.elt.addEventListener('dragover', handleDragOver, false); dropZone.elt.addEventListener('drop', handleFileSelect, false); dropZone.elt.addEventListener('dragleave', handleDragLeave, false); // When you drag a file on top function handleDragOver(evt) { // Stop the default browser behavior evt.stopPropagation(); evt.preventDefault(); dropZone.style('background','#fff000'); } // If mouse leaves function handleDragLeave(evt) { evt.stopPropagation(); evt.preventDefault(); dropZone.style('background','#fff'); } // If you drop the file function handleFileSelect(evt) { evt.stopPropagation(); evt.preventDefault(); dropZone.style('background',''); // A FileList var files = evt.dataTransfer.files; // Show some properties for (var i = 0, f; f = files[i]; i++) { // Read the file and process the result var reader = new FileReader(); reader.readAsText(f); reader.onload = function(e) { parseSeqObj(JSON.parse(e.target.result)); sendDrumPattern(); } } } } // end setup // ======================== // keep time and play drums // ======================== var step = 0; function increment(time) { step++; for (var i in blocks) { var stepColumn = map( (step%wDiv), 0, wDiv, 0, width); if (blocks[i].x === stepColumn) { var whichDrum = Math.round( map(blocks[i].y, 0, height, hDiv, 0) ) - 1; playDrum(whichDrum, time); blocks[i].c[1] = 0; blocks[i].c[2] = 0; } } } function playDrum(whichDrum, time) { drumArray[whichDrum].triggerAttackRelease(1, time); current++; } // ========================== // draw and mouse interaction // ========================== function draw() { background(0); // draw four boxes fill(20); rect(0, 0, width/4, height); fill(40); rect(width/4, 0, width/4, height); fill(20); rect(width/4 * 2, 0, width/4, height); fill(40); rect(width/4 * 3, 0, width/4, height); for (var i in blocks) { blocks[i].update(); } } var pressedX, pressedY; function mousePressed() { // if it is touching a preexisting block, remove that block pressedX = null; pressedY = null; for (var i in blocks) { if (blocks[i].isTouching(mouseX, mouseY) ) { pressedX = blocks[i].x; pressedY = blocks[i].y; blocks[i].remove(); blocks.splice(i, 1); } } } function mouseDragged() { var halfBlockWidth = width/wDiv/2; var halfBlockHeight = height/hDiv/2; pressedX = null; pressedY = null; rect(mouseX - halfBlockWidth, mouseY - halfBlockHeight, width/wDiv, height/hDiv); } function mouseReleased() { // map block position to grid var halfBlockWidth = width/wDiv/2; var halfBlockHeight = height/hDiv/2; var bX = Math.round(map(mouseX - halfBlockWidth, 0, width, 0, wDiv))/wDiv * width; var bY = Math.round(map(mouseY - halfBlockHeight, 0, height, 0, hDiv))/hDiv * height; // do not re-add block if mouse is in same place if (bX === pressedX && bY === pressedY) { return; } if (pressedX !== null && Math.abs(pmouseX - mouseX) < 3 && Math.abs(pmouseY - mouseY) < 3) { return; } // only add block if mouse is within width/height of canvas if (bX < width && bX >= 0 && bY < height && bY >= 0) { blocks.push( new Block(bX, bY)); } // update socket.io sendDrumPattern(); } // ============ // Block class // ============ var Block = function(x, y) { this.x = x; this.y = y; this.w = width/wDiv; this.h = height/hDiv; this.c = [255, 255, 255]; // update drumPattern array var whichDrum = drumArray.length - Math.round( map(this.y, 0, height, hDiv, 0) ); var whichStep = Math.round(map(this.x, 0, width, 0, wDiv)); drumPatterns[whichDrum][whichStep] = true; } Block.prototype.update = function() { this.c[2] += 20; this.c[1] += 20; fill(this.c); rect(this.x, this.y, this.w, this.h); } Block.prototype.isTouching = function(x, y) { if ( (this.x <= x) && (x <= (this.x + this.w) ) && (this.y <= y) && (y <= (this.y + this.h) ) ) { return true; } else { return false; } } Block.prototype.remove = function() { // update drumPattern array to zero var whichDrum = drumArray.length - Math.round( map(this.y, 0, height, hDiv, 0) ); var whichStep = Math.round(map(this.x, 0, width, 0, wDiv)); drumPatterns[whichDrum][whichStep] = null; // update socket.io sendDrumPattern(); } // ============ // SOCKET STUFF // ============ function sendDrumPattern() { socket.emit('changedPattern', drumPatterns); } socket.on('updateSeq', function (data) { console.log(data); parseSeqObj(data); }); |