Browse Source

Project init

Tibo 8 months ago
commit
c5be0588bf
61 changed files with 17690 additions and 0 deletions
  1. 21 0
      .gitignore
  2. 36 0
      README.md
  3. 5 0
      babel.config.js
  4. 79 0
      build_source.js
  5. 12085 0
      package-lock.json
  6. 51 0
      package.json
  7. 11 0
      picture_template.pug
  8. 332 0
      pictures/compassion/bundle.js
  9. 18 0
      pictures/compassion/index.html
  10. 89 0
      pictures/compassion/index.js
  11. 4 0
      pictures/compassion/info.JSON
  12. BIN
      pictures/compassion/vignette.png
  13. 470 0
      pictures/distance/bundle.js
  14. 18 0
      pictures/distance/index.html
  15. 227 0
      pictures/distance/index.js
  16. 4 0
      pictures/distance/info.JSON
  17. BIN
      pictures/distance/vignette.png
  18. 544 0
      pictures/observers/bundle.js
  19. 18 0
      pictures/observers/index.html
  20. 301 0
      pictures/observers/index.js
  21. 4 0
      pictures/observers/info.JSON
  22. BIN
      pictures/observers/vignette.png
  23. 445 0
      pictures/shared/bundle.js
  24. 18 0
      pictures/shared/index.html
  25. 202 0
      pictures/shared/index.js
  26. 4 0
      pictures/shared/info.JSON
  27. BIN
      pictures/shared/vignette.png
  28. BIN
      preview.jpg
  29. BIN
      public/favicon.png
  30. 17 0
      public/index.html
  31. 332 0
      public/pictures/compassion/bundle.html
  32. 332 0
      public/pictures/compassion/bundle.js
  33. 4 0
      public/pictures/compassion/index.html
  34. BIN
      public/pictures/compassion/vignette.png
  35. 470 0
      public/pictures/distance/bundle.js
  36. 4 0
      public/pictures/distance/index.html
  37. BIN
      public/pictures/distance/vignette.png
  38. 544 0
      public/pictures/observers/bundle.js
  39. 4 0
      public/pictures/observers/index.html
  40. BIN
      public/pictures/observers/vignette.png
  41. 445 0
      public/pictures/shared/bundle.js
  42. 4 0
      public/pictures/shared/index.html
  43. BIN
      public/pictures/shared/vignette.png
  44. 188 0
      src/App.vue
  45. BIN
      src/assets/5-dots.png
  46. 4 0
      src/assets/credits_pattern.txt
  47. 5 0
      src/assets/lines_icon.svg
  48. 3 0
      src/assets/pause_icon.svg
  49. 1 0
      src/assets/picturesData.JSON
  50. 3 0
      src/assets/play_icon.svg
  51. 93 0
      src/components/card.vue
  52. BIN
      src/fonts/Quicksand-Bold.ttf
  53. BIN
      src/fonts/Quicksand-Light.ttf
  54. BIN
      src/fonts/Quicksand-Medium.ttf
  55. BIN
      src/fonts/Quicksand-Regular.ttf
  56. BIN
      src/fonts/Quicksand-SemiBold.ttf
  57. 8 0
      src/main.js
  58. 9 0
      src/poem.md
  59. 122 0
      tagada/tagada.js
  60. 28 0
      tagada/util.js
  61. 84 0
      tagada/vector.js

+ 21 - 0
.gitignore

@@ -0,0 +1,21 @@
+.DS_Store
+node_modules
+/dist
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 36 - 0
README.md

@@ -0,0 +1,36 @@
+![preview](preview.jpg)
+
+# Intimacy
+
+## À faire
+
+- Finaliser Distance
+  - Theme de couleur
+  - Ondulation des formes
+- Finaliser Looks
+  - Images oeils
+- Créer Warmth
+- Ajouter une transition entre les images
+
+## Project setup
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+```
+npm run build
+```
+
+### Lints and fixes files
+```
+npm run lint
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).

+ 5 - 0
babel.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  presets: [
+    '@vue/cli-plugin-babel/preset'
+  ]
+}

+ 79 - 0
build_source.js

@@ -0,0 +1,79 @@
+const path = require("path");
+const fs = require("fs");
+const pug = require('pug');
+
+const directoryPath = path.join(__dirname,"pictures");
+
+let compiledFunction = pug.compileFile('picture_template.pug');
+
+
+function fetchDrawings(){
+  return new Promise(resolve=>{
+
+    fs.readdir(directoryPath,function(err, files){
+      if(err){
+        return console.log("AIE !");
+      }
+      let id = -1;
+      let result = files.map(f=>{
+        let filePath = path.join(directoryPath,f,"info.JSON");
+        let entryPath = path.join("pictures",f,"index.html");
+        let vignettePath = path.join("pictures",f,"vignette.png");
+        let sourcePath = path.join(directoryPath,f);
+        let sourceDirName = f;
+
+        let info = fs.readFileSync(filePath);
+        info = JSON.parse(info);
+        id += 1;
+
+        return {
+          id:id,
+          title:info.title,
+          description:info.description,
+          vignette:vignettePath,
+          path:entryPath,
+          sourcePath:sourcePath,
+          sourceDirName:sourceDirName
+        }
+
+      });
+      resolve(result);
+
+    });
+
+
+  });
+}
+
+function makeDir(sourceFolder){
+  fs.mkdir(`public/pictures/${sourceFolder.sourceDirName}`, {recursive:true},(err)=>{
+    if(err) throw err;
+  });
+  let build = compiledFunction({
+    title:sourceFolder.title,
+  });
+  fs.writeFile(`public/pictures/${sourceFolder.sourceDirName}/index.html`,build,function(err){
+    if(err) console.log("ouch");
+  });
+  // add build js and vignette
+  fs.copyFile(`pictures/${sourceFolder.sourceDirName}/bundle.js`,`public/pictures/${sourceFolder.sourceDirName}/bundle.js`,(err)=>{
+    if(err) throw err;
+  });
+  fs.copyFile(`pictures/${sourceFolder.sourceDirName}/vignette.png`,`public/pictures/${sourceFolder.sourceDirName}/vignette.png`,(err)=>{
+    if(err) throw err;
+  });
+}
+
+
+fetchDrawings().then(r=>{
+  // Create folder
+  r.forEach(sourceFolder=>{
+    makeDir(sourceFolder);
+  });
+  // then write files in json
+  let data = JSON.stringify(r);
+  fs.writeFile("src/assets/picturesData.JSON",data,err=>{
+    if(err) throw err;
+    console.log(":)");
+  });
+});

File diff suppressed because it is too large
+ 12085 - 0
package-lock.json


+ 51 - 0
package.json

@@ -0,0 +1,51 @@
+{
+  "name": "intimacy",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "core-js": "^3.3.2",
+    "pug": "^2.0.4",
+    "vue": "^2.6.10",
+    "vue-markdown-v2": "^0.1.7"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "^4.0.0",
+    "@vue/cli-plugin-eslint": "^4.0.0",
+    "@vue/cli-service": "^4.0.0",
+    "babel-eslint": "^10.0.3",
+    "eslint": "^5.16.0",
+    "eslint-plugin-vue": "^5.0.0",
+    "raw-loader": "^3.1.0",
+    "vue-template-compiler": "^2.6.10"
+  },
+  "eslintConfig": {
+    "root": true,
+    "env": {
+      "node": true
+    },
+    "extends": [
+      "plugin:vue/essential",
+      "eslint:recommended"
+    ],
+    "rules": {
+      "no-console": 0
+    },
+    "parserOptions": {
+      "parser": "babel-eslint"
+    }
+  },
+  "postcss": {
+    "plugins": {
+      "autoprefixer": {}
+    }
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions"
+  ]
+}

+ 11 - 0
picture_template.pug

@@ -0,0 +1,11 @@
+doctype html  
+html(lang='en')  
+head
+  title= title
+  style.
+    body{
+      margin:0;
+      overflow:hidden;
+    }
+body
+  script(src="bundle.js")

+ 332 - 0
pictures/compassion/bundle.js

@@ -0,0 +1,332 @@
+(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
+const Tagada = require('../../tagada/tagada.js');
+const Util = require('../../tagada/util.js');
+const Vector = require('../../tagada/vector.js');
+
+
+let app = new Tagada();
+
+let div = {
+  x: {
+    value: 0,
+    min: 1,
+    max: 120,
+    size: 0
+  },
+  y: {
+    value: 0,
+    min: 1,
+    max: 20,
+    size: 0
+  },
+  total: 1
+};
+
+let gap = 6,
+  padding = 10,
+  margin = -gap / 2 + padding;
+
+let target = new Vector(0, 0);
+
+app.divide = function() {
+  div.x.value = Math.round(
+    Util.map(this.W, 0, window.screen.width, div.x.min, div.x.max)
+  );
+  div.y.value = Math.round(
+    Util.map(this.H, 0, window.screen.height, div.y.min, div.y.max)
+  );
+  div.x.size = -gap + (this.W - margin * 2) / div.x.value;
+  div.y.size = -gap + (this.H - margin * 2) / div.y.value;
+  div.total = div.x.value * div.y.value;
+};
+
+app.update = function(dt) {
+  let offset = this.mouse.copy();
+  offset.sub(target);
+  offset.mult(0.1);
+  target.add(offset.copy());
+};
+
+app.render = function() {
+  let $ = this.ctx;
+  for (let i = 0; i < div.total; i++) {
+    let x = gap / 2 + margin + Math.floor(i / div.y.value) * (gap + div.x.size),
+      y = gap / 2 + margin + (i % div.y.value) * (gap + div.y.size);
+
+    let min_size = Math.min(div.x.size, div.y.size);
+    let angle = target.angle(new Vector(x, y));
+
+    let color = Math.sin(
+      Math.cos(angle + this.frame * 0.02 + x * 0.02) *
+        Math.sin(angle + this.frame * 0.02 + y * 0.001)
+    );
+    let s = Util.map(color, -1, 1, 0, 1);
+    $.fillStyle = Util.hsl(
+      Util.map(color, -1, 1, 20, 60),
+      Util.map(color, -1, 1, 90, 60),
+      Util.map(color, -1, 1, 20, 80)
+    );
+    let x_o = x + div.x.size / 2,
+      y_o = y + div.y.size / 2;
+    $.fillRect(
+      -div.x.size / 2 * s + x_o,
+      -div.y.size / 2 + y_o,
+      div.x.size * s,
+      div.y.size
+    );
+  }
+};
+
+app.resize = function() {
+  this.divide();
+};
+
+app.backgroundColor = "black";
+
+app.setSize();
+target = new Vector(app.W / 2, app.H / 2);
+app.mouse = new Vector(app.W / 2, app.H / 2);
+app.divide();
+app.run();
+
+},{"../../tagada/tagada.js":2,"../../tagada/util.js":3,"../../tagada/vector.js":4}],2:[function(require,module,exports){
+const Util = require('./util.js');
+const Vector = require('./vector.js');
+
+class Tagada{
+  constructor(){
+    this.canvas = document.createElement("canvas");
+    this.ctx = this.canvas.getContext('2d');
+    document.body.appendChild(this.canvas);
+    this.W = 0;
+    this.H = 0;
+    this.ressources = {
+      images:[],
+      audio:[],
+    }
+    this.backgroundColor = "gray";
+    this.timeData = {
+      now : 0,
+      dt : 0,
+      last : Util.timeStamp(),
+      step : 1/60,
+    };
+    this.frame = 0;
+    window.addEventListener("resize",()=>{
+      this.setSize();
+      this.resize();
+    });
+    this.mouse = new Vector(0,0);
+    window.addEventListener("pointermove",(event)=>{
+      this.pMove(event);
+    });
+  }
+  addImage(path){
+    let image = new Image();
+    image.src = path;
+    this.ressources.images.push(image);
+    return image;
+  }
+  drawImageCover(image,x,y,sizeX,sizeY){
+    if(sizeX <= 0 || sizeY <= 0) return false;
+    let scaleX,
+        scaleY,
+        ratio = image.width / image.height;
+
+    let sourceScaleX,sourceScaleY;
+
+    let cRation = sizeX / sizeY;
+
+    if(cRation > ratio){
+      scaleX = sizeX;
+      scaleY = scaleX / ratio;
+
+      sourceScaleX = image.width;
+      sourceScaleY = sourceScaleX / cRation;
+    }else{
+      scaleY = sizeY;
+      scaleX = scaleY * ratio;
+
+      sourceScaleY = image.height;
+      sourceScaleX = sourceScaleY * cRation;
+    }
+
+    let offsetX = (sizeX - scaleX);
+    let offsetY = (sizeY - scaleY);
+
+    this.ctx.drawImage(
+    image,(image.width - sourceScaleX) / 2,(image.height - sourceScaleY) / 2,sourceScaleX ,sourceScaleY,
+    x ,y,scaleX + offsetX,scaleY + offsetY
+    );
+
+
+  }
+  run(){
+    Promise.all(
+      this.ressources.images.map(image=>{
+        return new Promise(resolve=>{
+          image.onload = ()=>{
+            resolve();
+          }
+        });
+      })
+    ).then(v=>{
+      this.setSize();
+      this.loop();
+    });
+  }
+  resize(){
+
+  }
+  pMove(event){
+    this.mouse.x = event.clientX - this.canvas.offsetLeft;
+    this.mouse.y = event.clientY - this.canvas.offsetTop;
+  }
+  setSize(){
+    this.W = this.canvas.width = window.innerWidth;
+    this.H = this.canvas.height = window.innerHeight;
+  }
+  update(dt){
+
+  }
+  render(){
+
+  }
+  loop(){
+    this.ctx.fillStyle = this.backgroundColor;
+    this.ctx.fillRect(0,0,this.W,this.H);
+
+    this.timeData.now = Util.timeStamp();
+    this.timeData.dt = this.timeData.dt + Math.min(1, (this.timeData.now - this.timeData.last) / 1000);
+    while(this.timeData.dt > this.timeData.step) {
+      this.timeData.dt = this.timeData.dt - this.timeData.step;
+      this.update(this.timeData.step);
+    }
+    this.render();
+    this.timeData.last = this.timeData.now;
+    this.frame += 1;
+
+    requestAnimationFrame(()=>{
+      this.loop()
+    });
+  }
+}
+module.exports = Tagada;
+
+},{"./util.js":3,"./vector.js":4}],3:[function(require,module,exports){
+const Util = {};
+Util.timeStamp = function() {
+	return window.performance.now();
+};
+Util.random = function(min, max) {
+  return min + Math.random() * (max - min);
+};
+Util.map = function(a, b, c, d, e) {
+	a = this.clamp(a,b,c);
+  return (a - b) / (c - b) * (e - d) + d;
+};
+Util.lerp = function(value1, value2, amount) {
+  return value1 + (value2 - value1) * amount;
+};
+Util.clamp = function(value,min,max){
+	return Math.max(min, Math.min(max, value));
+};
+Util.threeAngle = function(p0,p1,p2){
+    var b = Math.pow(p1.x-p0.x,2) + Math.pow(p1.y-p0.y,2),
+        a = Math.pow(p1.x-p2.x,2) + Math.pow(p1.y-p2.y,2),
+        c = Math.pow(p2.x-p0.x,2) + Math.pow(p2.y-p0.y,2);
+    return Math.acos( (a+b-c) / Math.sqrt(4*a*b) );
+}
+Util.hsl = function(hue,saturation,lightness){
+	return `hsl(${hue},${saturation}%,${lightness}%)`;
+}
+
+module.exports = Util;
+
+},{}],4:[function(require,module,exports){
+class Vector{
+	constructor(x,y){
+		this.x = x || 0;
+		this.y = y || 0;
+	}
+	set(x,y){
+		this.x = x;
+		this.y = y;
+	}
+  reset(){
+		this.x = 0;
+		this.y = 0;
+  }
+	fromAngle(angle){
+		let x = Math.cos(angle),
+			y = Math.sin(angle);
+		return new Vector(x,y);
+	}
+	add(vector){
+		this.x += vector.x;
+		this.y += vector.y;
+	}
+	sub(vector){
+		this.x -= vector.x;
+		this.y -= vector.y;
+	}
+	mult(scalar){
+		this.x *= scalar;
+		this.y *= scalar;
+	}
+	div(scalar){
+		this.x /= scalar;
+		this.y /= scalar;
+	}
+	dot(vector){
+		return vector.x * this.x + vector.y * this.y;
+	}
+	limit(limit_value){
+		if(this.mag() > limit_value) this.setMag(limit_value);
+	}
+	mag(){
+		return Math.hypot(this.x,this.y);
+	}
+	setMag(new_mag){
+		if(this.mag() > 0){
+			this.normalize();
+		}else{
+			this.x = 1;
+			this.y = 0;
+		}
+		this.mult(new_mag);
+	}
+	normalize(){
+		let mag = this.mag();
+		if(mag > 0){
+			this.x /= mag;
+			this.y /= mag;
+		}
+	}
+  normalizedMag(){
+    let copy = this.copy();
+    copy.normalize();
+    return copy.mag();
+  }
+	heading(){
+		return Math.atan2(this.y,this.x);
+	}
+	setHeading(angle){
+		let mag = this.mag();
+		this.x = Math.cos(angle) * mag;
+		this.y = Math.sin(angle) * mag;
+	}
+	dist(vector){
+		return new Vector(this.x - vector.x,this.y - vector.y).mag();
+	}
+	angle(vector){
+		return Math.atan2(vector.y - this.y, vector.x - this.x);
+	}
+	copy(){
+		return new Vector(this.x,this.y);
+	}
+}
+
+module.exports = Vector;
+
+},{}]},{},[1]);

+ 18 - 0
pictures/compassion/index.html

@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+  <meta charset="utf-8">
+  <title>Build</title>
+  <style media="screen">
+    body {
+      margin: 0;
+      overflow: hidden;
+    }
+  </style>
+</head>
+
+<body>
+  <script src="bundle.js" charset="utf-8"></script>
+</body>
+
+</html>

+ 89 - 0
pictures/compassion/index.js

@@ -0,0 +1,89 @@
+const Tagada = require('../../tagada/tagada.js');
+const Util = require('../../tagada/util.js');
+const Vector = require('../../tagada/vector.js');
+
+
+let app = new Tagada();
+
+let div = {
+  x: {
+    value: 0,
+    min: 1,
+    max: 120,
+    size: 0
+  },
+  y: {
+    value: 0,
+    min: 1,
+    max: 20,
+    size: 0
+  },
+  total: 1
+};
+
+let gap = 6,
+  padding = 10,
+  margin = -gap / 2 + padding;
+
+let target = new Vector(0, 0);
+
+app.divide = function() {
+  div.x.value = Math.round(
+    Util.map(this.W, 0, window.screen.width, div.x.min, div.x.max)
+  );
+  div.y.value = Math.round(
+    Util.map(this.H, 0, window.screen.height, div.y.min, div.y.max)
+  );
+  div.x.size = -gap + (this.W - margin * 2) / div.x.value;
+  div.y.size = -gap + (this.H - margin * 2) / div.y.value;
+  div.total = div.x.value * div.y.value;
+};
+
+app.update = function(dt) {
+  let offset = this.mouse.copy();
+  offset.sub(target);
+  offset.mult(0.1);
+  target.add(offset.copy());
+};
+
+app.render = function() {
+  let $ = this.ctx;
+  for (let i = 0; i < div.total; i++) {
+    let x = gap / 2 + margin + Math.floor(i / div.y.value) * (gap + div.x.size),
+      y = gap / 2 + margin + (i % div.y.value) * (gap + div.y.size);
+
+    let min_size = Math.min(div.x.size, div.y.size);
+    let angle = target.angle(new Vector(x, y));
+
+    let color = Math.sin(
+      Math.cos(angle + this.frame * 0.02 + x * 0.02) *
+        Math.sin(angle + this.frame * 0.02 + y * 0.001)
+    );
+    let s = Util.map(color, -1, 1, 0, 1);
+    $.fillStyle = Util.hsl(
+      Util.map(color, -1, 1, 20, 60),
+      Util.map(color, -1, 1, 90, 60),
+      Util.map(color, -1, 1, 20, 80)
+    );
+    let x_o = x + div.x.size / 2,
+      y_o = y + div.y.size / 2;
+    $.fillRect(
+      -div.x.size / 2 * s + x_o,
+      -div.y.size / 2 + y_o,
+      div.x.size * s,
+      div.y.size
+    );
+  }
+};
+
+app.resize = function() {
+  this.divide();
+};
+
+app.backgroundColor = "black";
+
+app.setSize();
+target = new Vector(app.W / 2, app.H / 2);
+app.mouse = new Vector(app.W / 2, app.H / 2);
+app.divide();
+app.run();

+ 4 - 0
pictures/compassion/info.JSON

@@ -0,0 +1,4 @@
+{
+"title":"Compassion",
+"description":"For the others, from oneself"
+}

BIN
pictures/compassion/vignette.png


+ 470 - 0
pictures/distance/bundle.js

@@ -0,0 +1,470 @@
+(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
+const Tagada = require('../../tagada/tagada.js');
+const Util = require('../../tagada/util.js');
+const Vector = require('../../tagada/vector.js');
+
+let app = new Tagada();
+let shapes = [];
+let $ = app.ctx;
+let minSize = 0;
+
+class Particle {
+  constructor(container, x, y, size) {
+    this.container = container;
+    this.position = new Vector(x, y);
+    this.velocity = new Vector(0, 0);
+    this.acceleration = new Vector(0, 0);
+    this.size = size;
+    this.maxSpeed = Util.random(100, 1000);
+    this.start = new Date();
+    this.duration = Util.random(100, 2000);
+    this.spinOffset = Util.random(0.001,0.4);
+    this.gravity = new Vector(0,10);
+    this.colors = ["blue","pink"];
+  }
+  draw() {
+    let time = new Date() - this.start;
+    if (time > this.duration) {
+      // delete particle
+      let id = this.container.indexOf(this);
+      this.container.splice(id, 1);
+    }
+    let sizeOffset = Util.map(time,0,this.duration,1,0);
+
+    $.fillStyle = this.colors[Math.floor(Util.map(Math.sin(app.frame * this.spinOffset),-1,1,0,this.colors.length))];
+    $.beginPath();
+    $.ellipse(this.position.x, this.position.y, this.size * sizeOffset * Math.abs(Math.sin(app.frame * this.spinOffset)),this.size * sizeOffset, this.velocity.heading(), 0, Math.PI * 2);
+    $.fill();
+  }
+  addForce(force) {
+    this.acceleration.add(force);
+  }
+  update(dt) {
+    this.addForce(this.gravity);
+    this.velocity.add(this.acceleration);
+    this.velocity.limit(this.maxSpeed);
+    this.velocity.mult(0.98);
+    let v = this.velocity.copy();
+    v.mult(dt);
+    this.position.add(v);
+    this.acceleration.reset();
+  }
+}
+
+class Bubble {
+  constructor(container, x, y, size) {
+    this.container = container;
+    this.position = new Vector(x, y);
+    this.velocity = new Vector(0, 0);
+    this.acceleration = new Vector(0, 0);
+    this.size = size;
+    this.displayedSize = size;
+    this.maxSpeed = Util.random(100, 400);
+    this.beatSpeed = Util.random(0.01,0.04);
+
+  }
+  draw() {
+    this.displayedSize = this.size * Util.map(Math.sin(app.frame * this.beatSpeed),-1,1,0.9,1.1);
+    let dMouse = this.position.dist(app.mouse);
+    $.strokeStyle = Util.hsl(0,Util.map(dMouse-this.displayedSize,0,200,80,0),60);
+    $.beginPath();
+    $.arc(this.position.x, this.position.y, this.displayedSize, 0, Math.PI * 2);
+    $.stroke();
+  }
+  addForce(force) {
+    this.acceleration.add(force);
+  }
+  update(dt) {
+    this.divide();
+    this.bound();
+    this.velocity.add(this.acceleration);
+    this.velocity.limit(this.maxSpeed);
+    this.velocity.mult(0.98);
+    let v = this.velocity.copy();
+    v.mult(dt);
+    this.position.add(v);
+    this.acceleration.reset();
+  }
+  bound() {
+    let center = new Vector(app.W / 2, app.H / 2);
+    let dist = this.position.dist(center);
+    let smallestSide = Math.min(app.W, app.H) / 2;
+
+    if (dist > smallestSide) {
+      let force = new Vector(0, 0).fromAngle(this.position.angle(center));
+      force.setMag(Util.map(dist, 0, smallestSide, 0, 10));
+      this.addForce(force);
+    }
+  }
+  divide() {
+    let dist = this.position.dist(app.mouse);
+    if (dist > this.displayedSize) return;
+    // shake Screen
+    shake(Util.map(this.size,0,minSize,2,8));
+    // delete bubble
+    let id = this.container.indexOf(this);
+    this.container.splice(id, 1);
+    // add Particles
+    let pCount = Util.map(this.size,0,minSize,10,400);
+
+    for (let i = 0; i < pCount; i++) {
+      let angle = Util.map(i, 0, pCount, 0, Math.PI * 2),
+        pX = this.position.x + Math.cos(angle) * this.size,
+        pY = this.position.y + Math.sin(angle) * this.size;
+
+      let p = new Particle(particles, pX, pY, 4 * Util.random(0.2, 1.2));
+      p.velocity = new Vector().fromAngle(angle);
+      p.velocity.mult(Util.random(100, 800));
+      particles.push(p);
+    }
+    // add new bubles if size is sufficient
+    if (this.size < 20) return;
+    let divisions = Math.floor(Util.random(2, 6)),
+      childSize = this.size / divisions;
+
+    for (let i = 0; i < divisions; i++) {
+      let angle = Util.map(i, 0, divisions, 0, Math.PI * 2),
+        pX = this.position.x + Math.cos(angle) * this.size / 2,
+        pY = this.position.y + Math.sin(angle) * this.size / 2;
+
+      let bubbleChild = new Bubble(
+        this.container,
+        pX,
+        pY,
+        childSize * Util.random(0.8, 1.8)
+      );
+      bubbleChild.velocity = new Vector().fromAngle(angle);
+      bubbleChild.velocity.mult(Util.random(100, 400));
+      this.container.push(bubbleChild);
+    }
+  }
+
+  avoid(others) {
+    others.forEach(other => {
+      if (other !== this) {
+        let dist = this.position.dist(other.position),
+          max_dist = this.size + other.size;
+        if (max_dist - dist >= 0) {
+          let angle = other.position.angle(this.position);
+          let force = new Vector().fromAngle(angle);
+          force.setMag(Util.map(dist, 0, max_dist, 10, 0));
+          this.addForce(force);
+        }
+      }
+    });
+  }
+}
+
+let bubbles = [],
+  particles = [];
+
+// spring for the screen shake
+
+function shake(intencity){
+  let rForce = new Vector().fromAngle(Math.random()*Math.PI * 2);
+  rForce.setMag(intencity);
+  spring.velocity.add(rForce);
+}
+
+let spring = {
+  mass:40,
+  k:2,
+  damping:0.94,
+  restPosition: new Vector(0,0),
+  position: new Vector(0,0),
+  velocity: new Vector(0,0),
+
+  update:function(){
+    let force = this.position.copy();
+    force.sub(this.restPosition);
+    force.mult(-this.k);
+
+    force.div(this.mass);
+
+    this.velocity.add(force);
+    this.velocity.mult(this.damping);
+    this.velocity.limit(100);
+    this.position.add(this.velocity);
+
+  },
+}
+
+app.setSize();
+populate();
+
+function populate() {
+  minSize = Math.min(app.W, app.H) * 0.4;
+  particles = [];
+  bubbles = [];
+  bubbles.push(new Bubble(bubbles, app.W / 2, app.H / 2, minSize));
+}
+
+app.resize = function() {
+  populate();
+};
+
+app.update = function(dt) {
+  spring.update();
+  bubbles.forEach(b => {
+    b.avoid(bubbles);
+    b.update(dt);
+  });
+  particles.forEach(p => p.update(dt));
+};
+
+app.render = function() {
+  $.save();
+  $.translate(spring.position.x,spring.position.y);
+  bubbles.forEach(b => b.draw());
+
+  for (let i = particles.length-1; i > 0; i--) {
+    let p = particles[i];
+    p.draw();
+  }
+  $.restore();
+};
+
+app.backgroundColor = "white";
+app.run();
+
+},{"../../tagada/tagada.js":2,"../../tagada/util.js":3,"../../tagada/vector.js":4}],2:[function(require,module,exports){
+const Util = require('./util.js');
+const Vector = require('./vector.js');
+
+class Tagada{
+  constructor(){
+    this.canvas = document.createElement("canvas");
+    this.ctx = this.canvas.getContext('2d');
+    document.body.appendChild(this.canvas);
+    this.W = 0;
+    this.H = 0;
+    this.ressources = {
+      images:[],
+      audio:[],
+    }
+    this.backgroundColor = "gray";
+    this.timeData = {
+      now : 0,
+      dt : 0,
+      last : Util.timeStamp(),
+      step : 1/60,
+    };
+    this.frame = 0;
+    window.addEventListener("resize",()=>{
+      this.setSize();
+      this.resize();
+    });
+    this.mouse = new Vector(0,0);
+    window.addEventListener("pointermove",(event)=>{
+      this.pMove(event);
+    });
+  }
+  addImage(path){
+    let image = new Image();
+    image.src = path;
+    this.ressources.images.push(image);
+    return image;
+  }
+  drawImageCover(image,x,y,sizeX,sizeY){
+    if(sizeX <= 0 || sizeY <= 0) return false;
+    let scaleX,
+        scaleY,
+        ratio = image.width / image.height;
+
+    let sourceScaleX,sourceScaleY;
+
+    let cRation = sizeX / sizeY;
+
+    if(cRation > ratio){
+      scaleX = sizeX;
+      scaleY = scaleX / ratio;
+
+      sourceScaleX = image.width;
+      sourceScaleY = sourceScaleX / cRation;
+    }else{
+      scaleY = sizeY;
+      scaleX = scaleY * ratio;
+
+      sourceScaleY = image.height;
+      sourceScaleX = sourceScaleY * cRation;
+    }
+
+    let offsetX = (sizeX - scaleX);
+    let offsetY = (sizeY - scaleY);
+
+    this.ctx.drawImage(
+    image,(image.width - sourceScaleX) / 2,(image.height - sourceScaleY) / 2,sourceScaleX ,sourceScaleY,
+    x ,y,scaleX + offsetX,scaleY + offsetY
+    );
+
+
+  }
+  run(){
+    Promise.all(
+      this.ressources.images.map(image=>{
+        return new Promise(resolve=>{
+          image.onload = ()=>{
+            resolve();
+          }
+        });
+      })
+    ).then(v=>{
+      this.setSize();
+      this.loop();
+    });
+  }
+  resize(){
+
+  }
+  pMove(event){
+    this.mouse.x = event.clientX - this.canvas.offsetLeft;
+    this.mouse.y = event.clientY - this.canvas.offsetTop;
+  }
+  setSize(){
+    this.W = this.canvas.width = window.innerWidth;
+    this.H = this.canvas.height = window.innerHeight;
+  }
+  update(dt){
+
+  }
+  render(){
+
+  }
+  loop(){
+    this.ctx.fillStyle = this.backgroundColor;
+    this.ctx.fillRect(0,0,this.W,this.H);
+
+    this.timeData.now = Util.timeStamp();
+    this.timeData.dt = this.timeData.dt + Math.min(1, (this.timeData.now - this.timeData.last) / 1000);
+    while(this.timeData.dt > this.timeData.step) {
+      this.timeData.dt = this.timeData.dt - this.timeData.step;
+      this.update(this.timeData.step);
+    }
+    this.render();
+    this.timeData.last = this.timeData.now;
+    this.frame += 1;
+
+    requestAnimationFrame(()=>{
+      this.loop()
+    });
+  }
+}
+module.exports = Tagada;
+
+},{"./util.js":3,"./vector.js":4}],3:[function(require,module,exports){
+const Util = {};
+Util.timeStamp = function() {
+	return window.performance.now();
+};
+Util.random = function(min, max) {
+  return min + Math.random() * (max - min);
+};
+Util.map = function(a, b, c, d, e) {
+	a = this.clamp(a,b,c);
+  return (a - b) / (c - b) * (e - d) + d;
+};
+Util.lerp = function(value1, value2, amount) {
+  return value1 + (value2 - value1) * amount;
+};
+Util.clamp = function(value,min,max){
+	return Math.max(min, Math.min(max, value));
+};
+Util.threeAngle = function(p0,p1,p2){
+    var b = Math.pow(p1.x-p0.x,2) + Math.pow(p1.y-p0.y,2),
+        a = Math.pow(p1.x-p2.x,2) + Math.pow(p1.y-p2.y,2),
+        c = Math.pow(p2.x-p0.x,2) + Math.pow(p2.y-p0.y,2);
+    return Math.acos( (a+b-c) / Math.sqrt(4*a*b) );
+}
+Util.hsl = function(hue,saturation,lightness){
+	return `hsl(${hue},${saturation}%,${lightness}%)`;
+}
+
+module.exports = Util;
+
+},{}],4:[function(require,module,exports){
+class Vector{
+	constructor(x,y){
+		this.x = x || 0;
+		this.y = y || 0;
+	}
+	set(x,y){
+		this.x = x;
+		this.y = y;
+	}
+  reset(){
+		this.x = 0;
+		this.y = 0;
+  }
+	fromAngle(angle){
+		let x = Math.cos(angle),
+			y = Math.sin(angle);
+		return new Vector(x,y);
+	}
+	add(vector){
+		this.x += vector.x;
+		this.y += vector.y;
+	}
+	sub(vector){
+		this.x -= vector.x;
+		this.y -= vector.y;
+	}
+	mult(scalar){
+		this.x *= scalar;
+		this.y *= scalar;
+	}
+	div(scalar){
+		this.x /= scalar;
+		this.y /= scalar;
+	}
+	dot(vector){
+		return vector.x * this.x + vector.y * this.y;
+	}
+	limit(limit_value){
+		if(this.mag() > limit_value) this.setMag(limit_value);
+	}
+	mag(){
+		return Math.hypot(this.x,this.y);
+	}
+	setMag(new_mag){
+		if(this.mag() > 0){
+			this.normalize();
+		}else{
+			this.x = 1;
+			this.y = 0;
+		}
+		this.mult(new_mag);
+	}
+	normalize(){
+		let mag = this.mag();
+		if(mag > 0){
+			this.x /= mag;
+			this.y /= mag;
+		}
+	}
+  normalizedMag(){
+    let copy = this.copy();
+    copy.normalize();
+    return copy.mag();
+  }
+	heading(){
+		return Math.atan2(this.y,this.x);
+	}
+	setHeading(angle){
+		let mag = this.mag();
+		this.x = Math.cos(angle) * mag;
+		this.y = Math.sin(angle) * mag;
+	}
+	dist(vector){
+		return new Vector(this.x - vector.x,this.y - vector.y).mag();
+	}
+	angle(vector){
+		return Math.atan2(vector.y - this.y, vector.x - this.x);
+	}
+	copy(){
+		return new Vector(this.x,this.y);
+	}
+}
+
+module.exports = Vector;
+
+},{}]},{},[1]);

+ 18 - 0
pictures/distance/index.html

@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+  <meta charset="utf-8">
+  <title>Build</title>
+  <style media="screen">
+    body {
+      margin: 0;
+      overflow: hidden;
+    }
+  </style>
+</head>
+
+<body>
+  <script src="bundle.js" charset="utf-8"></script>
+</body>
+
+</html>

+ 227 - 0
pictures/distance/index.js

@@ -0,0 +1,227 @@
+const Tagada = require('../../tagada/tagada.js');
+const Util = require('../../tagada/util.js');
+const Vector = require('../../tagada/vector.js');
+
+let app = new Tagada();
+let shapes = [];
+let $ = app.ctx;
+let minSize = 0;
+
+class Particle {
+  constructor(container, x, y, size) {
+    this.container = container;
+    this.position = new Vector(x, y);
+    this.velocity = new Vector(0, 0);
+    this.acceleration = new Vector(0, 0);
+    this.size = size;
+    this.maxSpeed = Util.random(100, 1000);
+    this.start = new Date();
+    this.duration = Util.random(100, 2000);
+    this.spinOffset = Util.random(0.001,0.4);
+    this.gravity = new Vector(0,10);
+    this.colors = ["blue","pink"];
+  }
+  draw() {
+    let time = new Date() - this.start;
+    if (time > this.duration) {
+      // delete particle
+      let id = this.container.indexOf(this);
+      this.container.splice(id, 1);
+    }
+    let sizeOffset = Util.map(time,0,this.duration,1,0);
+
+    $.fillStyle = this.colors[Math.floor(Util.map(Math.sin(app.frame * this.spinOffset),-1,1,0,this.colors.length))];
+    $.beginPath();
+    $.ellipse(this.position.x, this.position.y, this.size * sizeOffset * Math.abs(Math.sin(app.frame * this.spinOffset)),this.size * sizeOffset, this.velocity.heading(), 0, Math.PI * 2);
+    $.fill();
+  }
+  addForce(force) {
+    this.acceleration.add(force);
+  }
+  update(dt) {
+    this.addForce(this.gravity);
+    this.velocity.add(this.acceleration);
+    this.velocity.limit(this.maxSpeed);
+    this.velocity.mult(0.98);
+    let v = this.velocity.copy();
+    v.mult(dt);
+    this.position.add(v);
+    this.acceleration.reset();
+  }
+}
+
+class Bubble {
+  constructor(container, x, y, size) {
+    this.container = container;
+    this.position = new Vector(x, y);
+    this.velocity = new Vector(0, 0);
+    this.acceleration = new Vector(0, 0);
+    this.size = size;
+    this.displayedSize = size;
+    this.maxSpeed = Util.random(100, 400);
+    this.beatSpeed = Util.random(0.01,0.04);
+
+  }
+  draw() {
+    this.displayedSize = this.size * Util.map(Math.sin(app.frame * this.beatSpeed),-1,1,0.9,1.1);
+    let dMouse = this.position.dist(app.mouse);
+    $.strokeStyle = Util.hsl(0,Util.map(dMouse-this.displayedSize,0,200,80,0),60);
+    $.beginPath();
+    $.arc(this.position.x, this.position.y, this.displayedSize, 0, Math.PI * 2);
+    $.stroke();
+  }
+  addForce(force) {
+    this.acceleration.add(force);
+  }
+  update(dt) {
+    this.divide();
+    this.bound();
+    this.velocity.add(this.acceleration);
+    this.velocity.limit(this.maxSpeed);
+    this.velocity.mult(0.98);
+    let v = this.velocity.copy();
+    v.mult(dt);
+    this.position.add(v);
+    this.acceleration.reset();
+  }
+  bound() {
+    let center = new Vector(app.W / 2, app.H / 2);
+    let dist = this.position.dist(center);
+    let smallestSide = Math.min(app.W, app.H) / 2;
+
+    if (dist > smallestSide) {
+      let force = new Vector(0, 0).fromAngle(this.position.angle(center));
+      force.setMag(Util.map(dist, 0, smallestSide, 0, 10));
+      this.addForce(force);
+    }
+  }
+  divide() {
+    let dist = this.position.dist(app.mouse);
+    if (dist > this.displayedSize) return;
+    // shake Screen
+    shake(Util.map(this.size,0,minSize,2,8));
+    // delete bubble
+    let id = this.container.indexOf(this);
+    this.container.splice(id, 1);
+    // add Particles
+    let pCount = Util.map(this.size,0,minSize,10,400);
+
+    for (let i = 0; i < pCount; i++) {
+      let angle = Util.map(i, 0, pCount, 0, Math.PI * 2),
+        pX = this.position.x + Math.cos(angle) * this.size,
+        pY = this.position.y + Math.sin(angle) * this.size;
+
+      let p = new Particle(particles, pX, pY, 4 * Util.random(0.2, 1.2));
+      p.velocity = new Vector().fromAngle(angle);
+      p.velocity.mult(Util.random(100, 800));
+      particles.push(p);
+    }
+    // add new bubles if size is sufficient
+    if (this.size < 20) return;
+    let divisions = Math.floor(Util.random(2, 6)),
+      childSize = this.size / divisions;
+
+    for (let i = 0; i < divisions; i++) {
+      let angle = Util.map(i, 0, divisions, 0, Math.PI * 2),
+        pX = this.position.x + Math.cos(angle) * this.size / 2,
+        pY = this.position.y + Math.sin(angle) * this.size / 2;
+
+      let bubbleChild = new Bubble(
+        this.container,
+        pX,
+        pY,
+        childSize * Util.random(0.8, 1.8)
+      );
+      bubbleChild.velocity = new Vector().fromAngle(angle);
+      bubbleChild.velocity.mult(Util.random(100, 400));
+      this.container.push(bubbleChild);
+    }
+  }
+
+  avoid(others) {
+    others.forEach(other => {
+      if (other !== this) {
+        let dist = this.position.dist(other.position),
+          max_dist = this.size + other.size;
+        if (max_dist - dist >= 0) {
+          let angle = other.position.angle(this.position);
+          let force = new Vector().fromAngle(angle);
+          force.setMag(Util.map(dist, 0, max_dist, 10, 0));
+          this.addForce(force);
+        }
+      }
+    });
+  }
+}
+
+let bubbles = [],
+  particles = [];
+
+// spring for the screen shake
+
+function shake(intencity){
+  let rForce = new Vector().fromAngle(Math.random()*Math.PI * 2);
+  rForce.setMag(intencity);
+  spring.velocity.add(rForce);
+}
+
+let spring = {
+  mass:40,
+  k:2,
+  damping:0.94,
+  restPosition: new Vector(0,0),
+  position: new Vector(0,0),
+  velocity: new Vector(0,0),
+
+  update:function(){
+    let force = this.position.copy();
+    force.sub(this.restPosition);
+    force.mult(-this.k);
+
+    force.div(this.mass);
+
+    this.velocity.add(force);
+    this.velocity.mult(this.damping);
+    this.velocity.limit(100);
+    this.position.add(this.velocity);
+
+  },
+}
+
+app.setSize();
+populate();
+
+function populate() {
+  minSize = Math.min(app.W, app.H) * 0.4;
+  particles = [];
+  bubbles = [];
+  bubbles.push(new Bubble(bubbles, app.W / 2, app.H / 2, minSize));
+}
+
+app.resize = function() {
+  populate();
+};
+
+app.update = function(dt) {
+  spring.update();
+  bubbles.forEach(b => {
+    b.avoid(bubbles);
+    b.update(dt);
+  });
+  particles.forEach(p => p.update(dt));
+};
+
+app.render = function() {
+  $.save();
+  $.translate(spring.position.x,spring.position.y);
+  bubbles.forEach(b => b.draw());
+
+  for (let i = particles.length-1; i > 0; i--) {
+    let p = particles[i];
+    p.draw();
+  }
+  $.restore();
+};
+
+app.backgroundColor = "white";
+app.run();

+ 4 - 0
pictures/distance/info.JSON

@@ -0,0 +1,4 @@
+{
+"title":"Distance",
+"description":"Everything needs space"
+}

BIN
pictures/distance/vignette.png


+ 544 - 0
pictures/observers/bundle.js

@@ -0,0 +1,544 @@
+(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
+const Tagada = require('../../tagada/tagada.js');
+const Util = require('../../tagada/util.js');
+const Vector = require('../../tagada/vector.js');
+
+const Tween = {};
+Tween.linear = function(currentTime, start, degreeOfChange, duration) {
+  return degreeOfChange * currentTime / duration + start;
+};
+
+let app = new Tagada();
+let observers = [],
+  min_observers = 10,
+  max_observers = 100;
+
+class Observer {
+  constructor(x, y) {
+    this.position = new Vector(x, y);
+    this.velocity = new Vector().fromAngle(Util.random(0, Math.PI * 2));
+    this.acceleration = new Vector(0, 0);
+    this.maxSpeed = Util.random(100, 200);
+    this.velocity.mult(this.maxSpeed);
+    this.min_size = 10;
+    this.max_size = 26;
+    this.size = Util.random(this.min_size, this.max_size);
+    this.zone = this.size * 2;
+    this.tail = [];
+    this.tail_length = Math.floor(
+      Util.map(this.size, this.min_size, this.max_size, 4, 20)
+    );
+    for (var i = 0; i < this.tail_length; i++) {
+      this.tail.push(new Vector(x, y));
+    }
+    this.wiggle_speed = Util.map(
+      this.size,
+      this.min_size,
+      this.max_size,
+      0.4,
+      0.06
+    );
+    this.headOffset = new Vector(0, 0);
+    this.random_offset = Util.random(0, 100);
+    this.close_speed = Util.random(0.01, 0.6);
+    this.pupil_size = Util.random(0.4, 0.8);
+
+    this.opening = 1;
+
+    this.transition = {
+      duration: 200,
+      active: false,
+      start: new Date(),
+      initial_value: 1
+    };
+    this.target = app.mouse;
+    this.blink();
+
+    this.last_head_pos = 0;
+    this.total_dist = 0;
+    this.eye_height = Util.random(0.4, 1);
+    this.colors = ["black"];
+    this.color = this.colors[Math.floor(Util.random(0, this.colors.length))];
+    this.bound_length = Util.random(0.5, 0.3);
+  }
+  blink() {
+    this.transition.active = true;
+    this.transition.start = new Date();
+    this.transition.initial_value = this.opening;
+    if (this.opening === 1) {
+      this.transition.target = 0;
+    } else {
+      this.transition.target = 1;
+    }
+  }
+  render(ctx) {
+    this.drawTail(ctx);
+
+    this.drawEye(ctx);
+  }
+
+  drawEye(ctx) {
+    if (this.transition.active) {
+      let time = new Date() - this.transition.start;
+      if (time < this.transition.duration) {
+        this.opening = Tween.linear(
+          time,
+          this.transition.initial_value,
+          this.transition.target - this.transition.initial_value,
+          this.transition.duration
+        );
+      } else {
+        this.transition.active = false;
+        this.opening = this.transition.target;
+
+        let next_blink;
+
+        if (this.opening == 1) {
+          next_blink = Util.random(200, 10000);
+        } else {
+          next_blink = Util.random(10, 20);
+        }
+        setTimeout(() => {
+          this.blink();
+        }, next_blink);
+      }
+    }
+
+    let angle = this.position.angle(this.target),
+      eye_to_mouse = this.position.dist(app.mouse);
+    let fear = Util.map(eye_to_mouse, 0, Math.max(app.W, app.H), 0.6, 0.9);
+
+    let final_pupil = this.size * this.pupil_size * fear;
+
+    let reach = new Vector().fromAngle(angle);
+    let reach_mag = this.size - final_pupil;
+    if (eye_to_mouse < this.size) {
+      reach_mag = eye_to_mouse;
+    }
+    reach.setMag(reach_mag);
+    reach.y *= this.eye_height;
+
+    var pup_gradient = ctx.createRadialGradient(
+      this.headOffset.x + reach.x,
+      this.headOffset.y + reach.y,
+      0,
+      this.headOffset.x + reach.x,
+      this.headOffset.y + reach.y,
+      final_pupil
+    );
+
+    pup_gradient.addColorStop(0, "rgba(0,0,0,0.6)");
+    pup_gradient.addColorStop(0.8, "rgba(20,0,0,1)");
+    pup_gradient.addColorStop(1, "rgba(0,0,0,0)");
+
+    var eye_gradient = ctx.createRadialGradient(
+      this.headOffset.x,
+      this.headOffset.y,
+      0,
+      this.headOffset.x,
+      this.headOffset.y,
+      this.size * 0.8
+    );
+
+    eye_gradient.addColorStop(0.2, "white");
+    eye_gradient.addColorStop(1, "rgb(220,220,220)");
+
+    ctx.save();
+    ctx.fillStyle = eye_gradient;
+    ctx.beginPath();
+    ctx.ellipse(
+      this.headOffset.x,
+      this.headOffset.y,
+      this.size * 0.8,
+      this.size * this.eye_height * this.opening * 0.8,
+      0,
+      0,
+      Math.PI * 2
+    );
+    ctx.fill();
+    ctx.clip();
+    ctx.fillStyle = pup_gradient;
+    ctx.beginPath();
+    ctx.arc(
+      this.headOffset.x + reach.x,
+      this.headOffset.y + reach.y,
+      final_pupil,
+      0,
+      Math.PI * 2
+    );
+    ctx.fill();
+
+    ctx.fillStyle = "rgba(255,255,255,0.9)";
+    ctx.beginPath();
+    ctx.arc(
+      this.headOffset.x - this.size * 0.4,
+      this.headOffset.y - this.size * 0.2,
+      this.size * 0.1,
+      0,
+      Math.PI * 2
+    );
+    ctx.fill();
+
+    ctx.restore();
+  }
+
+  drawTail(ctx) {
+    let t = this.tail[0];
+    ctx.lineCap = "round";
+    ctx.lineJoin = "round";
+    ctx.fillStyle = this.color;
+
+    for (var i = 0; i < this.tail.length; i++) {
+      let t = this.tail[i],
+        t_1 = this.tail[i - 1];
+      let c_size = Util.map(i, 0, this.tail.length, this.size * 1.2, 0);
+      ctx.globalAlpha = Util.map(i, 0, this.tail.length, 0.1, 1);
+      ctx.beginPath();
+      ctx.arc(t.x, t.y, c_size, 0, Math.PI * 2);
+      ctx.fill();
+    }
+    ctx.globalAlpha = 1;
+  }
+  update(dt) {
+    let m_d = app.mouse.dist(this.position);
+
+    if (m_d < 200) {
+      let force = new Vector(0, 0).fromAngle(app.mouse.angle(this.position));
+      force.setMag(Util.map(m_d, 0, 200, this.maxSpeed, 0));
+      this.addForce(force);
+    }
+    this.bound();
+    this.velocity.add(this.acceleration);
+    this.velocity.limit(this.maxSpeed);
+    this.velocity.mult(0.98);
+    let v = this.velocity.copy();
+    v.mult(dt);
+    this.position.add(v);
+    this.acceleration.reset();
+
+    let wiggle =
+      Math.sin(app.frame * this.wiggle_speed) *
+      Util.map(this.velocity.mag(), 0, this.maxSpeed, 0, this.size);
+    let w_a = this.velocity.heading() + Math.PI / 2;
+
+    let w_x = this.position.x + Math.cos(w_a) * wiggle,
+      w_y = this.position.y + Math.sin(w_a) * wiggle;
+
+    this.headOffset = new Vector(w_x, w_y);
+
+    this.total_dist = 0;
+    let from = this.tail.length - 1,
+      to = 0;
+    var tail = this.headOffset.copy();
+    this.tail.splice(from, 1);
+    this.tail.splice(to, 0, tail);
+  }
+  addForce(force) {
+    this.acceleration.add(force);
+  }
+  bound() {
+    let center = new Vector(app.W / 2, app.H / 2);
+    let dist = this.position.dist(center);
+    let smallestSide = Math.min(app.W, app.H) * this.bound_length;
+
+    if (dist > smallestSide) {
+      let force = new Vector(0, 0).fromAngle(this.position.angle(center));
+      force.setMag(Util.map(dist, 0, smallestSide, 0, 10));
+      this.addForce(force);
+    }
+  }
+
+  avoid(others) {
+    others.forEach(other => {
+      if (other !== this) {
+        let dist = this.position.dist(other.position),
+          max_dist = this.zone + other.size;
+        if (max_dist - dist >= 0) {
+          let angle = other.position.angle(this.position);
+          let force = new Vector().fromAngle(angle);
+          force.setMag(Util.map(dist, 0, max_dist, 10, 0));
+          this.addForce(force);
+        }
+      }
+    });
+  }
+}
+
+function populate() {
+  observers = [];
+  let smallestSide = Math.min(app.W, app.H);
+  let maxminside = Math.min(screen.width, screen.height);
+  let observers_count = Math.round(
+    Util.map(smallestSide, 0, maxminside, min_observers, max_observers)
+  );
+  for (var i = 0; i < observers_count; i++) {
+    observers.push(new Observer(app.W / 2, app.H / 2));
+  }
+}
+
+app.update = function(dt) {
+  observers.forEach(a => {
+    a.avoid(observers);
+  });
+  observers.forEach(o => {
+    o.update(dt);
+  });
+};
+
+app.render = function() {
+  let $ = this.ctx;
+  observers.forEach(o => {
+    o.render($);
+  });
+};
+
+app.resize = function() {
+  populate();
+};
+
+app.backgroundColor = "white";
+app.run();
+app.setSize();
+populate();
+
+},{"../../tagada/tagada.js":2,"../../tagada/util.js":3,"../../tagada/vector.js":4}],2:[function(require,module,exports){
+const Util = require('./util.js');
+const Vector = require('./vector.js');
+
+class Tagada{
+  constructor(){
+    this.canvas = document.createElement("canvas");
+    this.ctx = this.canvas.getContext('2d');
+    document.body.appendChild(this.canvas);
+    this.W = 0;
+    this.H = 0;
+    this.ressources = {
+      images:[],
+      audio:[],
+    }
+    this.backgroundColor = "gray";
+    this.timeData = {
+      now : 0,
+      dt : 0,
+      last : Util.timeStamp(),
+      step : 1/60,
+    };
+    this.frame = 0;
+    window.addEventListener("resize",()=>{
+      this.setSize();
+      this.resize();
+    });
+    this.mouse = new Vector(0,0);
+    window.addEventListener("pointermove",(event)=>{
+      this.pMove(event);
+    });
+  }
+  addImage(path){
+    let image = new Image();
+    image.src = path;
+    this.ressources.images.push(image);
+    return image;
+  }
+  drawImageCover(image,x,y,sizeX,sizeY){
+    if(sizeX <= 0 || sizeY <= 0) return false;
+    let scaleX,
+        scaleY,
+        ratio = image.width / image.height;
+
+    let sourceScaleX,sourceScaleY;
+
+    let cRation = sizeX / sizeY;
+
+    if(cRation > ratio){
+      scaleX = sizeX;
+      scaleY = scaleX / ratio;
+
+      sourceScaleX = image.width;
+      sourceScaleY = sourceScaleX / cRation;
+    }else{
+      scaleY = sizeY;
+      scaleX = scaleY * ratio;
+
+      sourceScaleY = image.height;
+      sourceScaleX = sourceScaleY * cRation;
+    }
+
+    let offsetX = (sizeX - scaleX);
+    let offsetY = (sizeY - scaleY);
+
+    this.ctx.drawImage(
+    image,(image.width - sourceScaleX) / 2,(image.height - sourceScaleY) / 2,sourceScaleX ,sourceScaleY,
+    x ,y,scaleX + offsetX,scaleY + offsetY
+    );
+
+
+  }
+  run(){
+    Promise.all(
+      this.ressources.images.map(image=>{
+        return new Promise(resolve=>{
+          image.onload = ()=>{
+            resolve();
+          }
+        });
+      })
+    ).then(v=>{
+      this.setSize();
+      this.loop();
+    });
+  }
+  resize(){
+
+  }
+  pMove(event){
+    this.mouse.x = event.clientX - this.canvas.offsetLeft;
+    this.mouse.y = event.clientY - this.canvas.offsetTop;
+  }
+  setSize(){
+    this.W = this.canvas.width = window.innerWidth;
+    this.H = this.canvas.height = window.innerHeight;
+  }
+  update(dt){
+
+  }
+  render(){
+
+  }
+  loop(){
+    this.ctx.fillStyle = this.backgroundColor;
+    this.ctx.fillRect(0,0,this.W,this.H);
+
+    this.timeData.now = Util.timeStamp();
+    this.timeData.dt = this.timeData.dt + Math.min(1, (this.timeData.now - this.timeData.last) / 1000);
+    while(this.timeData.dt > this.timeData.step) {
+      this.timeData.dt = this.timeData.dt - this.timeData.step;
+      this.update(this.timeData.step);
+    }
+    this.render();
+    this.timeData.last = this.timeData.now;
+    this.frame += 1;
+
+    requestAnimationFrame(()=>{
+      this.loop()
+    });
+  }
+}
+module.exports = Tagada;
+
+},{"./util.js":3,"./vector.js":4}],3:[function(require,module,exports){
+const Util = {};
+Util.timeStamp = function() {
+	return window.performance.now();
+};
+Util.random = function(min, max) {
+  return min + Math.random() * (max - min);
+};
+Util.map = function(a, b, c, d, e) {
+	a = this.clamp(a,b,c);
+  return (a - b) / (c - b) * (e - d) + d;
+};
+Util.lerp = function(value1, value2, amount) {
+  return value1 + (value2 - value1) * amount;
+};
+Util.clamp = function(value,min,max){
+	return Math.max(min, Math.min(max, value));
+};
+Util.threeAngle = function(p0,p1,p2){
+    var b = Math.pow(p1.x-p0.x,2) + Math.pow(p1.y-p0.y,2),
+        a = Math.pow(p1.x-p2.x,2) + Math.pow(p1.y-p2.y,2),
+        c = Math.pow(p2.x-p0.x,2) + Math.pow(p2.y-p0.y,2);
+    return Math.acos( (a+b-c) / Math.sqrt(4*a*b) );
+}
+Util.hsl = function(hue,saturation,lightness){
+	return `hsl(${hue},${saturation}%,${lightness}%)`;
+}
+
+module.exports = Util;
+
+},{}],4:[function(require,module,exports){
+class Vector{
+	constructor(x,y){
+		this.x = x || 0;
+		this.y = y || 0;
+	}
+	set(x,y){
+		this.x = x;
+		this.y = y;
+	}
+  reset(){
+		this.x = 0;
+		this.y = 0;
+  }
+	fromAngle(angle){
+		let x = Math.cos(angle),
+			y = Math.sin(angle);
+		return new Vector(x,y);
+	}
+	add(vector){
+		this.x += vector.x;
+		this.y += vector.y;
+	}
+	sub(vector){
+		this.x -= vector.x;
+		this.y -= vector.y;
+	}
+	mult(scalar){
+		this.x *= scalar;
+		this.y *= scalar;
+	}
+	div(scalar){
+		this.x /= scalar;
+		this.y /= scalar;
+	}
+	dot(vector){
+		return vector.x * this.x + vector.y * this.y;
+	}
+	limit(limit_value){
+		if(this.mag() > limit_value) this.setMag(limit_value);
+	}
+	mag(){
+		return Math.hypot(this.x,this.y);
+	}
+	setMag(new_mag){
+		if(this.mag() > 0){
+			this.normalize();
+		}else{
+			this.x = 1;
+			this.y = 0;
+		}
+		this.mult(new_mag);
+	}
+	normalize(){
+		let mag = this.mag();
+		if(mag > 0){
+			this.x /= mag;
+			this.y /= mag;
+		}
+	}
+  normalizedMag(){
+    let copy = this.copy();
+    copy.normalize();
+    return copy.mag();
+  }
+	heading(){
+		return Math.atan2(this.y,this.x);
+	}
+	setHeading(angle){
+		let mag = this.mag();
+		this.x = Math.cos(angle) * mag;
+		this.y = Math.sin(angle) * mag;
+	}
+	dist(vector){
+		return new Vector(this.x - vector.x,this.y - vector.y).mag();
+	}
+	angle(vector){
+		return Math.atan2(vector.y - this.y, vector.x - this.x);
+	}
+	copy(){
+		return new Vector(this.x,this.y);
+	}
+}
+
+module.exports = Vector;
+
+},{}]},{},[1]);

+ 18 - 0
pictures/observers/index.html

@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+  <meta charset="utf-8">
+  <title>Build</title>
+  <style media="screen">
+    body {
+      margin: 0;
+      overflow: hidden;
+    }
+  </style>
+</head>
+
+<body>
+  <script src="bundle.js" charset="utf-8"></script>
+</body>
+
+</html>

+ 301 - 0
pictures/observers/index.js

@@ -0,0 +1,301 @@
+const Tagada = require('../../tagada/tagada.js');
+const Util = require('../../tagada/util.js');
+const Vector = require('../../tagada/vector.js');
+
+const Tween = {};
+Tween.linear = function(currentTime, start, degreeOfChange, duration) {
+  return degreeOfChange * currentTime / duration + start;
+};
+
+let app = new Tagada();
+let observers = [],
+  min_observers = 10,
+  max_observers = 100;
+
+class Observer {
+  constructor(x, y) {
+    this.position = new Vector(x, y);
+    this.velocity = new Vector().fromAngle(Util.random(0, Math.PI * 2));
+    this.acceleration = new Vector(0, 0);
+    this.maxSpeed = Util.random(100, 200);
+    this.velocity.mult(this.maxSpeed);
+    this.min_size = 10;
+    this.max_size = 26;
+    this.size = Util.random(this.min_size, this.max_size);
+    this.zone = this.size * 2;
+    this.tail = [];
+    this.tail_length = Math.floor(
+      Util.map(this.size, this.min_size, this.max_size, 4, 20)
+    );
+    for (var i = 0; i < this.tail_length; i++) {
+      this.tail.push(new Vector(x, y));
+    }
+    this.wiggle_speed = Util.map(
+      this.size,
+      this.min_size,
+      this.max_size,
+      0.4,
+      0.06
+    );
+    this.headOffset = new Vector(0, 0);
+    this.random_offset = Util.random(0, 100);
+    this.close_speed = Util.random(0.01, 0.6);
+    this.pupil_size = Util.random(0.4, 0.8);
+
+    this.opening = 1;
+
+    this.transition = {
+      duration: 200,
+      active: false,
+      start: new Date(),
+      initial_value: 1
+    };
+    this.target = app.mouse;
+    this.blink();
+
+    this.last_head_pos = 0;
+    this.total_dist = 0;
+    this.eye_height = Util.random(0.4, 1);
+    this.colors = ["black"];
+    this.color = this.colors[Math.floor(Util.random(0, this.colors.length))];
+    this.bound_length = Util.random(0.5, 0.3);
+  }
+  blink() {
+    this.transition.active = true;
+    this.transition.start = new Date();
+    this.transition.initial_value = this.opening;
+    if (this.opening === 1) {
+      this.transition.target = 0;
+    } else {
+      this.transition.target = 1;
+    }
+  }
+  render(ctx) {
+    this.drawTail(ctx);
+
+    this.drawEye(ctx);
+  }
+
+  drawEye(ctx) {
+    if (this.transition.active) {
+      let time = new Date() - this.transition.start;
+      if (time < this.transition.duration) {
+        this.opening = Tween.linear(
+          time,
+          this.transition.initial_value,
+          this.transition.target - this.transition.initial_value,
+          this.transition.duration
+        );
+      } else {
+        this.transition.active = false;
+        this.opening = this.transition.target;
+
+        let next_blink;
+
+        if (this.opening == 1) {
+          next_blink = Util.random(200, 10000);
+        } else {
+          next_blink = Util.random(10, 20);
+        }
+        setTimeout(() => {
+          this.blink();
+        }, next_blink);
+      }
+    }
+
+    let angle = this.position.angle(this.target),
+      eye_to_mouse = this.position.dist(app.mouse);
+    let fear = Util.map(eye_to_mouse, 0, Math.max(app.W, app.H), 0.6, 0.9);
+
+    let final_pupil = this.size * this.pupil_size * fear;
+
+    let reach = new Vector().fromAngle(angle);
+    let reach_mag = this.size - final_pupil;
+    if (eye_to_mouse < this.size) {
+      reach_mag = eye_to_mouse;
+    }
+    reach.setMag(reach_mag);
+    reach.y *= this.eye_height;
+
+    var pup_gradient = ctx.createRadialGradient(
+      this.headOffset.x + reach.x,
+      this.headOffset.y + reach.y,
+      0,
+      this.headOffset.x + reach.x,
+      this.headOffset.y + reach.y,
+      final_pupil
+    );
+
+    pup_gradient.addColorStop(0, "rgba(0,0,0,0.6)");
+    pup_gradient.addColorStop(0.8, "rgba(20,0,0,1)");
+    pup_gradient.addColorStop(1, "rgba(0,0,0,0)");
+
+    var eye_gradient = ctx.createRadialGradient(
+      this.headOffset.x,
+      this.headOffset.y,
+      0,
+      this.headOffset.x,
+      this.headOffset.y,
+      this.size * 0.8
+    );
+
+    eye_gradient.addColorStop(0.2, "white");
+    eye_gradient.addColorStop(1, "rgb(220,220,220)");
+
+    ctx.save();
+    ctx.fillStyle = eye_gradient;
+    ctx.beginPath();
+    ctx.ellipse(
+      this.headOffset.x,
+      this.headOffset.y,
+      this.size * 0.8,
+      this.size * this.eye_height * this.opening * 0.8,
+      0,
+      0,
+      Math.PI * 2
+    );
+    ctx.fill();
+    ctx.clip();
+    ctx.fillStyle = pup_gradient;
+    ctx.beginPath();
+    ctx.arc(
+      this.headOffset.x + reach.x,
+      this.headOffset.y + reach.y,
+      final_pupil,
+      0,
+      Math.PI * 2
+    );
+    ctx.fill();
+
+    ctx.fillStyle = "rgba(255,255,255,0.9)";
+    ctx.beginPath();
+    ctx.arc(
+      this.headOffset.x - this.size * 0.4,
+      this.headOffset.y - this.size * 0.2,
+      this.size * 0.1,
+      0,
+      Math.PI * 2
+    );
+    ctx.fill();
+
+    ctx.restore();
+  }
+
+  drawTail(ctx) {
+    let t = this.tail[0];
+    ctx.lineCap = "round";
+    ctx.lineJoin = "round";
+    ctx.fillStyle = this.color;
+
+    for (var i = 0; i < this.tail.length; i++) {
+      let t = this.tail[i],
+        t_1 = this.tail[i - 1];
+      let c_size = Util.map(i, 0, this.tail.length, this.size * 1.2, 0);
+      ctx.globalAlpha = Util.map(i, 0, this.tail.length, 0.1, 1);
+      ctx.beginPath();
+      ctx.arc(t.x, t.y, c_size, 0, Math.PI * 2);
+      ctx.fill();
+    }
+    ctx.globalAlpha = 1;
+  }
+  update(dt) {
+    let m_d = app.mouse.dist(this.position);
+
+    if (m_d < 200) {
+      let force = new Vector(0, 0).fromAngle(app.mouse.angle(this.position));
+      force.setMag(Util.map(m_d, 0, 200, this.maxSpeed, 0));
+      this.addForce(force);
+    }
+    this.bound();
+    this.velocity.add(this.acceleration);
+    this.velocity.limit(this.maxSpeed);
+    this.velocity.mult(0.98);
+    let v = this.velocity.copy();
+    v.mult(dt);
+    this.position.add(v);
+    this.acceleration.reset();
+
+    let wiggle =
+      Math.sin(app.frame * this.wiggle_speed) *
+      Util.map(this.velocity.mag(), 0, this.maxSpeed, 0, this.size);
+    let w_a = this.velocity.heading() + Math.PI / 2;
+
+    let w_x = this.position.x + Math.cos(w_a) * wiggle,
+      w_y = this.position.y + Math.sin(w_a) * wiggle;
+
+    this.headOffset = new Vector(w_x, w_y);
+
+    this.total_dist = 0;
+    let from = this.tail.length - 1,
+      to = 0;
+    var tail = this.headOffset.copy();
+    this.tail.splice(from, 1);
+    this.tail.splice(to, 0, tail);
+  }
+  addForce(force) {
+    this.acceleration.add(force);
+  }
+  bound() {
+    let center = new Vector(app.W / 2, app.H / 2);
+    let dist = this.position.dist(center);
+    let smallestSide = Math.min(app.W, app.H) * this.bound_length;
+
+    if (dist > smallestSide) {
+      let force = new Vector(0, 0).fromAngle(this.position.angle(center));
+      force.setMag(Util.map(dist, 0, smallestSide, 0, 10));
+      this.addForce(force);
+    }
+  }
+
+  avoid(others) {
+    others.forEach(other => {
+      if (other !== this) {
+        let dist = this.position.dist(other.position),
+          max_dist = this.zone + other.size;
+        if (max_dist - dist >= 0) {
+          let angle = other.position.angle(this.position);
+          let force = new Vector().fromAngle(angle);
+          force.setMag(Util.map(dist, 0, max_dist, 10, 0));
+          this.addForce(force);
+        }
+      }
+    });
+  }
+}
+
+function populate() {
+  observers = [];
+  let smallestSide = Math.min(app.W, app.H);
+  let maxminside = Math.min(screen.width, screen.height);
+  let observers_count = Math.round(
+    Util.map(smallestSide, 0, maxminside, min_observers, max_observers)
+  );
+  for (var i = 0; i < observers_count; i++) {
+    observers.push(new Observer(app.W / 2, app.H / 2));
+  }
+}
+
+app.update = function(dt) {
+  observers.forEach(a => {
+    a.avoid(observers);
+  });
+  observers.forEach(o => {
+    o.update(dt);
+  });
+};
+
+app.render = function() {
+  let $ = this.ctx;
+  observers.forEach(o => {
+    o.render($);
+  });
+};
+
+app.resize = function() {
+  populate();
+};
+
+app.backgroundColor = "white";
+app.run();
+app.setSize();
+populate();

+ 4 - 0
pictures/observers/info.JSON

@@ -0,0 +1,4 @@
+{
+"title":"Observers",
+"description":"Looking at you"
+}

BIN
pictures/observers/vignette.png


+ 445 - 0
pictures/shared/bundle.js

@@ -0,0 +1,445 @@
+(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
+const Tagada = require('../../tagada/tagada.js');
+const Util = require('../../tagada/util.js');
+const Vector = require('../../tagada/vector.js');
+
+let app = new Tagada();
+let shapes = [];
+let $ = app.ctx;
+
+class Point {
+  constructor(x, y) {
+    this.position = new Vector(x, y);
+    this.old_position = new Vector(x, y);
+    this.pinned = false;
+  }
+  set(x,y){
+    this.position = new Vector(x, y);
+    this.old_position = new Vector(x, y);
+  }
+  draw() {
+    $.fillStyle = "white";
+    $.beginPath();
+    $.arc(this.position.x, this.position.y, 2, 0, Math.PI * 2);
+    $.fill();
+    $.closePath();
+  }
+}
+
+class Link {
+  constructor(p0, p1) {
+    this.p0 = p0;
+    this.p1 = p1;
+    this.length = p0.position.dist(p1.position);
+  }
+}
+
+
+class Shape {
+  constructor(start, end, corner, attraction) {
+    this.points = [];
+    this.links = [];
+    this.corner = corner;
+    this.attraction = attraction;
+    this.start = start.p;
+    this.start_offset = start.offset;
+    this.end = end.p;
+    this.end_offset = end.offset;
+    this.resolution = Util.clamp(Math.floor(Math.max(app.W,app.H)/100),4,14);
+    let a = this.start.angle(this.end),
+      d = this.start.dist(this.end);
+    for (let i = 0; i < this.resolution; i++) {
+      let p_p = new Vector().fromAngle(a);
+      p_p.setMag(
+        Util.map(i, 0, this.resolution - 1, 0, d)
+      );
+      p_p.add(this.start);
+      this.points.push(new Point(p_p.x, p_p.y));
+    }
+    this.points[0].pinned = true;
+    this.points[this.points.length-1].pinned = true;
+    for (var i = 1; i < this.points.length; i++) {
+      let p_0 = this.points[i-1],
+          p_1 = this.points[i];
+      let l = new Link(p_0,p_1);
+      l.length *= 1.4;
+      this.links.push(l);
+    }
+  }
+  update(dt) {
+    // edges
+    let s = this.points[0].position;
+    s.x = this.start.x + Math.cos(app.frame*0.02) * this.start_offset.x;
+    s.y = this.start.y + Math.sin(app.frame*0.02) * this.start_offset.y;
+
+    let e = this.points[this.points.length-1].position;
+    e.x = this.end.x + Math.cos(app.frame*0.02) * this.end_offset.x;
+    e.y = this.end.y + Math.sin(app.frame*0.02) * this.end_offset.y;
+
+    this.points.forEach(p => {
+      if (p.pinned) return;
+      let velocity = p.position.copy();
+      velocity.sub(p.old_position);
+      velocity.mult(0.98);
+      p.old_position = p.position.copy();
+      p.position.add(velocity);
+      let a = p.position.angle(this.attraction);
+
+      let g = new Vector().fromAngle(a);
+      g.setMag(0.2);
+      p.position.add(g);
+      // mouse
+      let d_m = app.mouse.dist(p.position);
+      let min_dist = Math.min(app.W,app.H)*0.3;
+      if(d_m < min_dist){
+        let a = app.mouse.angle(p.position);
+        let f = new Vector().fromAngle(a);
+        f.setMag(Util.map(d_m,0,min_dist,1,0));
+        p.position.add(f);
+      }
+    });
+
+    this.links.forEach(link => {
+      let distance = link.p0.position.dist(link.p1.position),
+        difference = link.length - distance,
+        percent = difference / distance / 2;
+      let offset = new Vector(
+        (link.p1.position.x - link.p0.position.x) * percent,
+        (link.p1.position.y - link.p0.position.y) * percent
+      );
+      if (!link.p0.pinned) {
+        link.p0.position.sub(offset);
+      }
+      if (!link.p1.pinned) {
+        link.p1.position.add(offset);
+      }
+    });
+
+  }
+  draw() {
+
+    $.beginPath();
+    let points = this.points.map(p=>{
+      return p.position
+    });
+    smooth(points);
+    $.lineTo(this.corner.x,this.corner.y);
+    $.closePath();
+
+  }
+}
+
+app.update = function(dt) {
+  shapes.forEach(s=>{
+    s.update();
+  });
+}
+
+app.render = function() {
+  let $ = this.ctx;
+
+  $.globalCompositeOperation = "xor";
+  shapes.forEach(s=>{
+    $.fillStyle = "#ff3500";
+    s.draw();
+    $.fill();
+  });
+
+  $.globalCompositeOperation = "destination-over";
+  shapes.forEach(s=>{
+    $.fillStyle = s.color;
+    s.draw();
+    $.lineWidth = 20;
+    $.fill();
+  });
+  $.globalCompositeOperation = "source-over";
+
+}
+
+function populate(){
+  shapes = [];
+  let s1 =   new Shape(
+      {p:new Vector(0, app.H * 0.4),offset:{x:0,y:-30}},
+      {p:new Vector(app.W * 0.6, app.H),offset:{x:40,y:0}},
+      new Vector(0,app.H),
+      new Vector(app.W,0),
+    );
+    s1.color = "#ffff00";
+  shapes.push(s1);
+  let s2 =   new Shape(
+      {p:new Vector(app.W * 0.4,0 ),offset:{x:-60,y:0}},
+      {p:new Vector(app.W, app.H * 0.8),offset:{x:0,y:20}},
+      new Vector(app.W,0),
+      new Vector(0,app.H),
+    );
+    s2.color = "#002fff";
+  shapes.push(s2);
+}
+
+
+function smooth(points) {
+  $.moveTo(points[0].x, points[0].y);
+  for (i = 1; i < points.length - 2; i++) {
+    var xc = (points[i].x + points[i + 1].x) / 2;
+    var yc = (points[i].y + points[i + 1].y) / 2;
+    $.quadraticCurveTo(points[i].x, points[i].y, xc, yc);
+  }
+
+  $.quadraticCurveTo(
+    points[i].x,
+    points[i].y,
+    points[i + 1].x,
+    points[i + 1].y
+  );
+}
+
+app.resize = function(old_W,old_H){
+  populate();
+}
+
+app.setSize();
+populate();
+app.backgroundColor = "white";
+app.run();
+
+},{"../../tagada/tagada.js":2,"../../tagada/util.js":3,"../../tagada/vector.js":4}],2:[function(require,module,exports){
+const Util = require('./util.js');
+const Vector = require('./vector.js');
+
+class Tagada{
+  constructor(){
+    this.canvas = document.createElement("canvas");
+    this.ctx = this.canvas.getContext('2d');
+    document.body.appendChild(this.canvas);
+    this.W = 0;
+    this.H = 0;
+    this.ressources = {
+      images:[],
+      audio:[],
+    }
+    this.backgroundColor = "gray";
+    this.timeData = {
+      now : 0,
+      dt : 0,
+      last : Util.timeStamp(),
+      step : 1/60,
+    };
+    this.frame = 0;
+    window.addEventListener("resize",()=>{
+      this.setSize();
+      this.resize();
+    });
+    this.mouse = new Vector(0,0);
+    window.addEventListener("pointermove",(event)=>{
+      this.pMove(event);
+    });
+  }
+  addImage(path){
+    let image = new Image();
+    image.src = path;
+    this.ressources.images.push(image);
+    return image;
+  }
+  drawImageCover(image,x,y,sizeX,sizeY){
+    if(sizeX <= 0 || sizeY <= 0) return false;
+    let scaleX,
+        scaleY,
+        ratio = image.width / image.height;
+
+    let sourceScaleX,sourceScaleY;
+
+    let cRation = sizeX / sizeY;
+
+    if(cRation > ratio){
+      scaleX = sizeX;
+      scaleY = scaleX / ratio;
+
+      sourceScaleX = image.width;
+      sourceScaleY = sourceScaleX / cRation;
+    }else{
+      scaleY = sizeY;
+      scaleX = scaleY * ratio;
+
+      sourceScaleY = image.height;
+      sourceScaleX = sourceScaleY * cRation;
+    }
+
+    let offsetX = (sizeX - scaleX);
+    let offsetY = (sizeY - scaleY);
+
+    this.ctx.drawImage(
+    image,(image.width - sourceScaleX) / 2,(image.height - sourceScaleY) / 2,sourceScaleX ,sourceScaleY,
+    x ,y,scaleX + offsetX,scaleY + offsetY
+    );
+
+
+  }
+  run(){
+    Promise.all(
+      this.ressources.images.map(image=>{
+        return new Promise(resolve=>{
+          image.onload = ()=>{
+            resolve();
+          }
+        });
+      })
+    ).then(v=>{
+      this.setSize();
+      this.loop();
+    });
+  }
+  resize(){
+
+  }
+  pMove(event){
+    this.mouse.x = event.clientX - this.canvas.offsetLeft;
+    this.mouse.y = event.clientY - this.canvas.offsetTop;
+  }
+  setSize(){
+    this.W = this.canvas.width = window.innerWidth;
+    this.H = this.canvas.height = window.innerHeight;
+  }
+  update(dt){
+
+  }
+  render(){
+
+  }
+  loop(){
+    this.ctx.fillStyle = this.backgroundColor;
+    this.ctx.fillRect(0,0,this.W,this.H);
+
+    this.timeData.now = Util.timeStamp();
+    this.timeData.dt = this.timeData.dt + Math.min(1, (this.timeData.now - this.timeData.last) / 1000);
+    while(this.timeData.dt > this.timeData.step) {
+      this.timeData.dt = this.timeData.dt - this.timeData.step;
+      this.update(this.timeData.step);
+    }
+    this.render();
+    this.timeData.last = this.timeData.now;
+    this.frame += 1;
+
+    requestAnimationFrame(()=>{
+      this.loop()
+    });
+  }
+}
+module.exports = Tagada;
+
+},{"./util.js":3,"./vector.js":4}],3:[function(require,module,exports){
+const Util = {};
+Util.timeStamp = function() {
+	return window.performance.now();
+};
+Util.random = function(min, max) {
+  return min + Math.random() * (max - min);
+};
+Util.map = function(a, b, c, d, e) {
+	a = this.clamp(a,b,c);
+  return (a - b) / (c - b) * (e - d) + d;
+};
+Util.lerp = function(value1, value2, amount) {
+  return value1 + (value2 - value1) * amount;
+};
+Util.clamp = function(value,min,max){
+	return Math.max(min, Math.min(max, value));
+};
+Util.threeAngle = function(p0,p1,p2){
+    var b = Math.pow(p1.x-p0.x,2) + Math.pow(p1.y-p0.y,2),
+        a = Math.pow(p1.x-p2.x,2) + Math.pow(p1.y-p2.y,2),
+        c = Math.pow(p2.x-p0.x,2) + Math.pow(p2.y-p0.y,2);
+    return Math.acos( (a+b-c) / Math.sqrt(4*a*b) );
+}
+Util.hsl = function(hue,saturation,lightness){
+	return `hsl(${hue},${saturation}%,${lightness}%)`;
+}
+
+module.exports = Util;
+
+},{}],4:[function(require,module,exports){
+class Vector{
+	constructor(x,y){
+		this.x = x || 0;
+		this.y = y || 0;
+	}
+	set(x,y){
+		this.x = x;
+		this.y = y;
+	}
+  reset(){
+		this.x = 0;
+		this.y = 0;
+  }
+	fromAngle(angle){
+		let x = Math.cos(angle),
+			y = Math.sin(angle);
+		return new Vector(x,y);
+	}
+	add(vector){
+		this.x += vector.x;
+		this.y += vector.y;
+	}
+	sub(vector){
+		this.x -= vector.x;
+		this.y -= vector.y;
+	}
+	mult(scalar){
+		this.x *= scalar;
+		this.y *= scalar;
+	}
+	div(scalar){
+		this.x /= scalar;
+		this.y /= scalar;
+	}
+	dot(vector){
+		return vector.x * this.x + vector.y * this.y;
+	}
+	limit(limit_value){
+		if(this.mag() > limit_value) this.setMag(limit_value);
+	}
+	mag(){
+		return Math.hypot(this.x,this.y);
+	}
+	setMag(new_mag){
+		if(this.mag() > 0){
+			this.normalize();
+		}else{
+			this.x = 1;
+			this.y = 0;
+		}
+		this.mult(new_mag);
+	}
+	normalize(){
+		let mag = this.mag();
+		if(mag > 0){
+			this.x /= mag;
+			this.y /= mag;
+		}
+	}
+  normalizedMag(){
+    let copy = this.copy();
+    copy.normalize();
+    return copy.mag();
+  }
+	heading(){
+		return Math.atan2(this.y,this.x);
+	}
+	setHeading(angle){
+		let mag = this.mag();
+		this.x = Math.cos(angle) * mag;
+		this.y = Math.sin(angle) * mag;
+	}
+	dist(vector){
+		return new Vector(this.x - vector.x,this.y - vector.y).mag();
+	}
+	angle(vector){
+		return Math.atan2(vector.y - this.y, vector.x - this.x);
+	}
+	copy(){
+		return new Vector(this.x,this.y);
+	}
+}
+
+module.exports = Vector;
+
+},{}]},{},[1]);

+ 18 - 0
pictures/shared/index.html

@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+  <meta charset="utf-8">
+  <title>Build</title>
+  <style media="screen">
+    body {
+      margin: 0;
+      overflow: hidden;
+    }
+  </style>
+</head>
+
+<body>
+  <script src="bundle.js" charset="utf-8"></script>
+</body>
+
+</html>

+ 202 - 0
pictures/shared/index.js

@@ -0,0 +1,202 @@
+const Tagada = require('../../tagada/tagada.js');
+const Util = require('../../tagada/util.js');
+const Vector = require('../../tagada/vector.js');
+
+let app = new Tagada();
+let shapes = [];
+let $ = app.ctx;
+
+class Point {
+  constructor(x, y) {
+    this.position = new Vector(x, y);
+    this.old_position = new Vector(x, y);
+    this.pinned = false;
+  }
+  set(x,y){
+    this.position = new Vector(x, y);
+    this.old_position = new Vector(x, y);
+  }
+  draw() {
+    $.fillStyle = "white";
+    $.beginPath();
+    $.arc(this.position.x, this.position.y, 2, 0, Math.PI * 2);
+    $.fill();
+    $.closePath();
+  }
+}
+
+class Link {
+  constructor(p0, p1) {
+    this.p0 = p0;
+    this.p1 = p1;
+    this.length = p0.position.dist(p1.position);
+  }
+}
+
+
+class Shape {
+  constructor(start, end, corner, attraction) {
+    this.points = [];
+    this.links = [];
+    this.corner = corner;
+    this.attraction = attraction;
+    this.start = start.p;
+    this.start_offset = start.offset;
+    this.end = end.p;
+    this.end_offset = end.offset;
+    this.resolution = Util.clamp(Math.floor(Math.max(app.W,app.H)/100),4,14);
+    let a = this.start.angle(this.end),
+      d = this.start.dist(this.end);
+    for (let i = 0; i < this.resolution; i++) {
+      let p_p = new Vector().fromAngle(a);
+      p_p.setMag(
+        Util.map(i, 0, this.resolution - 1, 0, d)
+      );
+      p_p.add(this.start);
+      this.points.push(new Point(p_p.x, p_p.y));
+    }
+    this.points[0].pinned = true;
+    this.points[this.points.length-1].pinned = true;
+    for (var i = 1; i < this.points.length; i++) {
+      let p_0 = this.points[i-1],
+          p_1 = this.points[i];
+      let l = new Link(p_0,p_1);
+      l.length *= 1.4;
+      this.links.push(l);
+    }
+  }
+  update(dt) {
+    // edges
+    let s = this.points[0].position;
+    s.x = this.start.x + Math.cos(app.frame*0.02) * this.start_offset.x;
+    s.y = this.start.y + Math.sin(app.frame*0.02) * this.start_offset.y;
+
+    let e = this.points[this.points.length-1].position;
+    e.x = this.end.x + Math.cos(app.frame*0.02) * this.end_offset.x;
+    e.y = this.end.y + Math.sin(app.frame*0.02) * this.end_offset.y;
+
+    this.points.forEach(p => {
+      if (p.pinned) return;
+      let velocity = p.position.copy();
+      velocity.sub(p.old_position);
+      velocity.mult(0.98);
+      p.old_position = p.position.copy();
+      p.position.add(velocity);
+      let a = p.position.angle(this.attraction);
+
+      let g = new Vector().fromAngle(a);
+      g.setMag(0.2);
+      p.position.add(g);
+      // mouse
+      let d_m = app.mouse.dist(p.position);
+      let min_dist = Math.min(app.W,app.H)*0.3;
+      if(d_m < min_dist){
+        let a = app.mouse.angle(p.position);
+        let f = new Vector().fromAngle(a);
+        f.setMag(Util.map(d_m,0,min_dist,1,0));
+        p.position.add(f);
+      }
+    });
+
+    this.links.forEach(link => {
+      let distance = link.p0.position.dist(link.p1.position),
+        difference = link.length - distance,
+        percent = difference / distance / 2;
+      let offset = new Vector(
+        (link.p1.position.x - link.p0.position.x) * percent,
+        (link.p1.position.y - link.p0.position.y) * percent
+      );
+      if (!link.p0.pinned) {
+        link.p0.position.sub(offset);
+      }
+      if (!link.p1.pinned) {
+        link.p1.position.add(offset);
+      }
+    });
+
+  }
+  draw() {
+
+    $.beginPath();
+    let points = this.points.map(p=>{
+      return p.position
+    });
+    smooth(points);
+    $.lineTo(this.corner.x,this.corner.y);
+    $.closePath();
+
+  }
+}
+
+app.update = function(dt) {
+  shapes.forEach(s=>{
+    s.update();
+  });
+}
+
+app.render = function() {
+  let $ = this.ctx;
+
+  $.globalCompositeOperation = "xor";
+  shapes.forEach(s=>{
+    $.fillStyle = "#ff3500";
+    s.draw();
+    $.fill();
+  });
+
+  $.globalCompositeOperation = "destination-over";
+  shapes.forEach(s=>{
+    $.fillStyle = s.color;
+    s.draw();
+    $.lineWidth = 20;
+    $.fill();
+  });
+  $.globalCompositeOperation = "source-over";
+
+}
+
+function populate(){
+  shapes = [];
+  let s1 =   new Shape(
+      {p:new Vector(0, app.H * 0.4),offset:{x:0,y:-30}},
+      {p:new Vector(app.W * 0.6, app.H),offset:{x:40,y:0}},
+      new Vector(0,app.H),
+      new Vector(app.W,0),
+    );
+    s1.color = "#ffff00";
+  shapes.push(s1);
+  let s2 =   new Shape(
+      {p:new Vector(app.W * 0.4,0 ),offset:{x:-60,y:0}},
+      {p:new Vector(app.W, app.H * 0.8),offset:{x:0,y:20}},
+      new Vector(app.W,0),
+      new Vector(0,app.H),
+    );
+    s2.color = "#002fff";
+  shapes.push(s2);
+}
+
+
+function smooth(points) {
+  $.moveTo(points[0].x, points[0].y);
+  for (i = 1; i < points.length - 2; i++) {
+    var xc = (points[i].x + points[i + 1].x) / 2;
+    var yc = (points[i].y + points[i + 1].y) / 2;
+    $.quadraticCurveTo(points[i].x, points[i].y, xc, yc);
+  }
+
+  $.quadraticCurveTo(
+    points[i].x,
+    points[i].y,
+    points[i + 1].x,
+    points[i + 1].y
+  );
+}
+
+app.resize = function(old_W,old_H){
+  populate();
+}
+
+app.setSize();
+populate();
+app.backgroundColor = "white";
+app.run();

+ 4 - 0
pictures/shared/info.JSON

@@ -0,0 +1,4 @@
+{
+"title":"Shared",
+"description":"When two things meets"
+}

BIN
pictures/shared/vignette.png


BIN
preview.jpg


BIN
public/favicon.png


+ 17 - 0
public/index.html

@@ -0,0 +1,17 @@