<!DOCTYPE html>
<html ng-app="norobo">

  <head>
    <meta charset="utf-8" />
    <title>Norobo</title>
    <link data-require="bootstrap-css@*" data-semver="3.0.2" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css" />
    <script data-require="angular.js@*" data-semver="1.2.1" src="http://code.angularjs.org/1.2.1/angular.js"></script>
    <script data-require="d3@*" data-semver="3.2.2" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.2.2/d3.v3.min.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script>document.write('<base href="' + document.location + '" />');</script>
    <script src="norobo.js"></script>
  </head>

  <body ng-controller="NoroboController">
    <div norobo="norobo.secret"></div>
    <div class="check" ng-class="{correct: check(guess,norobo.secret)}">
      <p>Type your guess below. You will be told if you get it right.</p>
      <input class="form-input" ng-model="guess" />
      <div class="win" ng-show="check(guess,norobo.secret)">YOU WIN!!</div>
      <p class="hidden reset">
        <button class="btn btn-large btn-default" ng-click="reset()" title="Hint: The secret is {{norobo.secret}}">Reset</button>
      </p>
    </div>
    <div ng-include="'README.html'"></div>
  </body>

</html>
/* Put your css in here */

.norobo {
  text-align: center;
}

.norobo>svg{
  border: 10px solid #333;
  padding: 5px;
  border-collapse: collapse;
  height: 60px;
}
.norobo>svg+svg{
  border-left: 0;
}

.check {
  width: 350px;
  margin: 10px auto;
  text-align: center;
  border: 10px solid #333;
  padding: 10px;
}

.check.correct {
  border-color: green;
  
}
.win {
  font: bold 30px sans-serif;
  padding: 8px;
}
module = angular.module "norobo", []

module.controller "NoroboController", ["$scope", ($scope) ->
  genid = (len = 16, prefix = "", keyspace = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") ->
    prefix += keyspace.charAt(Math.floor(Math.random() * keyspace.length)) while len-- > 0
    prefix
  
  $scope.secret = genid(7)
      
]

module.directive "norobo", ->
  scope:
    secret: "=norobo"
  link: ($scope, $el, $attrs) ->
    $el.addClass("norobo")
    
    class Character
      offsets = "A1,A2,B,C,D1,D2,E,F,G1,G2,H,I,J,K,L,M".split(",")
      
      A1 = 0
      A2 = 1
      B = 2
      C = 3
      D1 = 4
      D2 = 5
      E = 6
      F = 7
      G1 = 8
      G2 = 9
      H = 10
      I = 11
      J = 12
      K = 13
      L = 14
      M = 15
      
      a1 = 1 << A1
      a2 = 1 << A2
      b = 1 << B
      c = 1 << C
      d1 = 1 << D1
      d2 = 1 << D2
      e = 1 << E
      f = 1 << F
      g1 = 1 << G1
      g2 = 1 << G2
      h = 1 << H
      i = 1 << I 
      j  = 1 << J
      k  = 1 << K
      l = 1 << L
      m = 1 << M
      
      points = {}
      
      points.A1 = [
        x: 0.0, y: 0.0
      , x: 0.5, y: 0.0
      ]
      
      points.A2 = [
        x: 0.5, y: 0.0
      , x: 1.0, y: 0.0
      ]
        
      points.B = [
        x: 1.0, y: 0.0
      , x: 1.0, y: 0.5
      ]
      
      points.C = [
        x: 1.0, y: 0.5
      , x: 1.0, y: 1.0
      ]
    
      points.D1 = [
        x: 0.0, y: 1.0
      , x: 0.5, y: 1.0
      ]
    
      points.D2 = [
        x: 0.5, y: 1.0
      , x: 1.0, y: 1.0
      ]
    
      points.E = [
        x: 0.0, y: 0.5
      , x: 0.0, y: 1.0
      ]
    
      points.F = [
        x: 0.0, y: 0.0
      , x: 0.0, y: 0.5
      ]
    
      points.G1 = [
        x: 0.0, y: 0.5
      , x: 0.5, y: 0.5
      ]
    
      points.G2 = [
        x: 0.5, y: 0.5
      , x: 1.0, y: 0.5
      ]
    
      points.H = [
        x: 0.0, y: 0.0
      , x: 0.5, y: 0.5
      ]
    
      points.I = [
        x: 0.5, y: 0.0
      , x: 0.5, y: 0.5
      ]
    
      points.J = [
        x: 0.5, y: 0.5
      , x: 1.0, y: 0.0
      ]
    
      points.K = [
        x: 0.5, y: 0.5
      , x: 1.0, y: 1.0
      ]
    
      points.L = [
        x: 0.5, y: 0.5
      , x: 0.5, y: 1.0
      ]
    
      points.M = [
        x: 0.0, y: 1.0
      , x: 0.5, y: 0.5
      ]
    
      characterMasks = 
        ' ' : 0
        ''  : 0
        '0' : a1|a2|b|c|d1|d2|e|f|j|m
        '1' : b|c|j
        '2' : a1|a2|b|d1|d2|e|g1|g2
        '3' : a1|a2|b|c|d1|d2|g2
        '4' : b|c|f|g1|g2
        '5' : a1|a2|c|d1|d2|f|g1|g2
        '6' : a1|a2|c|d1|d2|e|f|g1|g2
        '7' : a1|a2|b|c
        '8' : a1|a2|b|c|d1|d2|e|f|g1|g2
        '9' : a1|a2|b|c|f|g1|g2
        'A' : e|f|a1|a2|b|c|g1|g2
        'B' : a1|a2|b|c|d1|d2|g2|i|l
        'C' : a1|a2|f|e|d1|d2
        'D' : a1|a2|b|c|d1|d2|i|l
        'E' : a1|a2|f|e|d1|d2|g1|g2
        'F' : a1|a2|e|f|g1
        'G' : a1|a2|c|d1|d2|e|f|g2
        'H' : b|c|e|f|g1|g2
        'I' : a1|a2|d1|d2|i|l
        'J' : b|c|d1|d2|e
        'K' : e|f|g1|j|k
        'L' : d1|d2|e|f
        'M' : b|c|e|f|h|j
        'N' : b|c|e|f|h|k
        'O' : a1|a2|b|c|d1|d2|e|f
        'P' : a1|a2|b|e|f|g1|g2
        'Q' : a1|a2|b|c|d1|d2|e|f|k
        'R' : a1|a2|b|e|f|g1|g2|k
        'S' : a1|a2|c|d1|d2|f|g1|g2
        'T' : a1|a2|i|l
        'U' : b|c|d1|d2|e|f
        'V' : e|f|j|m
        'W' : b|c|e|f|k|m
        'X' : h|j|k|m
        'Y' : b|f|g1|g2|l
        'Z' : a1|a2|d1|d2|j|m
        '-' : g1|g2
        '?' : a1|a2|b|g2|l
        '+' : g1|g2|i|l
        '*' : g1|g2|h|i|j|k|l|m
    
      constructor: (character) ->
        mask = characterMasks[character.toUpperCase()]
        
        @segments = []
        for i in [0...16] when mask & (1 << i)
          @segments.push(points[offsets[i]]) 
          
    
    class Characters
      constructor: (str) ->
        @characters = []
        @characters.push new Character(chr) for chr in str.split("")
      
      getSegments: (charWidth = 10, charHeight = 20, xSpace = charWidth / 4, yOff = charHeight / 4) ->
        segments = []
        for character, c in @characters
          for segment, s in character.segments
            segments.push
              v: segment
              r: Math.sqrt((segment[1].x - segment[0].x)^2 + (segment[1].y - segment[0].y)^2)
              x1: segment[0].x * charWidth + c * (xSpace + charWidth)
              y1: segment[0].y * charHeight + yOff
              x2: segment[1].x * charWidth + c * (xSpace + charWidth)
              y2: segment[1].y * charHeight + yOff
              rot: Math.random() * 2 * Math.PI
              xT: (Math.random() - 0.5)
              yT: (Math.random() - 0.5)
              xT2: (Math.random() - 0.5)
              yT2: (Math.random() - 0.5)
              nx1: (Math.random() - 0.5) * charWidth / 6
              ny1: (Math.random() - 0.5) * charHeight / 6
              nx2: (Math.random() - 0.5) * charWidth / 6
              ny2: (Math.random() - 0.5) * charHeight / 6
        segments
    
    canvasW = 150
    canvasH = 30
    frozen = false
    
    update = ->
      [mousePos.x, mousePos.y] = d3.mouse(@)
      unless frozen then lines
        .attr("x1", (l) -> l.x1 + l.nx1 + l.xT * (mousePos.x - mouseOrigin.x))
        .attr("y1", (l) -> l.y1 + l.ny1 + l.yT * (mousePos.y - mouseOrigin.y))
        #.attr("x2", (l) -> l.x1 + Math.cos(l.rot * (mousePos.x - mouseOrogin.x) / canvasW)) / l.r
        #.attr("y2", (l) -> l.y1 + Math.cos(l.rot * (mousePos.y - mouseOrogin.y) / canvasH)) / l.r
        .attr("x2", (l) -> l.x2 + l.nx2 + l.xT2 * (mousePos.x - mouseOrigin.x))
        .attr("y2", (l) -> l.y2 + l.ny2 + l.yT2 * (mousePos.y - mouseOrigin.y))
        .attr("stroke-width", 2)
        .attr("stroke", "black")
        
      hintText.text -> if frozen then "Click to unfreeze" else "Click to freeze"
    
    toggleFreeze = ->
      frozen = !frozen
      
      update()
    
    display = d3.select($el[0])
                .append("svg")
                .attr("width", canvasW)
                .attr("height", canvasH)
                
    mousearea = d3.select($el[0])
                .append("svg")
                .attr("width", canvasW)
                .attr("height", canvasH)
                .attr("fill", "rgb(33,33,33)")
                .on("mousemove", update)
                .on("mousedown", toggleFreeze)
    hintText = mousearea.append("text")
                  .attr("x", 8)
                  .attr("y", 3 * canvasH / 4)
                  .attr("font-family", "sans-serif")
                  .attr("font-size", canvasH / 2 + "px")
                  .text("Move mouse here")
                  
                
    mouseOrigin = x: Math.random() * canvasW , y: Math.random() * canvasH
    mousePos = x: 0, y: 0
    text = lines = lineAtts = null
    
    $scope.$watch "secret", (secret) ->
      
      text = new Characters(secret)
      
      lines = display.selectAll("line")
                     .data(text.getSegments(10, 20, 10))
                     .enter()
                     .append("line")
      
      lineAttrs = lines
                    .attr("x1", (l) -> l.x1 + l.xT * (mousePos.x - mouseOrigin.x))
                    .attr("y1", (l) -> l.y1 + l.yT * (mousePos.y - mouseOrigin.y))
                    .attr("x2", (l) -> l.x2 + l.xT * (mousePos.x - mouseOrigin.x))
                    .attr("y2", (l) -> l.y2 + l.yT * (mousePos.y - mouseOrigin.y))
                    .attr("stroke-width", 2)
                    .attr("stroke", "black")



# Norobo

CAPTCHA experiment by Geoff Goodman.

Here, the idea is that a human would need to determine the correct spot on the
x, y axis that would allow the secret text to become readable.

This plunk is a rough proof of concept of the ideas described below.

# Concept

** Server **

The server would come up with a random secret. That secret is then encoded
visually a a series of geometric shapes. This could be:

* Circles of varying sizes filling up the are with different colors in a manner
  similar to an Ishihara Test
* Line segments of a 16-segment display (the approach used in this demo)
* Other clever geometric encodings of text

Once the text is encoded into a set of geometric primitives, the server will
allocate each vertex / radius / etc. a random transformation/translation
function (including randomized parameters to that function).

The server will then determine a random x and y offset that will result in the
correct alignment per the transformation functions. Each vertex / radius / 
other will then be transformed / translated by the transformation based on the
random point's offset from the origin.

The server will then save the correct string under a random hash and send:
1. The transformed starting position / radius / etc. of each geometric primitive
2. The transformation functions for each vertex / radius / etc.
3. The random hash id

** Client ** 

Given this information, the client will have no knowledge of the correct string.
The user will need to move the cursor until a readable string is revealed.

The user can freeze the cursor once the string is readable and type in the 
completed CAPTCHA.

# Reasoning

I have absolutely no background in cryptography or in OCR, so I have had to make
a few assumptions in creating this system:

1. A string can be encoded as recognizable geometric primitives in such a way
   that a human can recognize text but a computer will find it difficult [1]
2. A non-static CAPTCHA means that many more OCR attempts per test will need to
   be made
3. The grometric encoding can be done so that bot optimizations like trying to
   align 16-segment segments is very difficult (not the case w/ the demo)

[1] I don't think it needs to be as hard as ReCAPTCHA given the potential number
of tries (#2) a bot would need to make at different positions because of #3.

>   In other words, I realize that a bot could use its understanding of a 16-segment
    display to determine the correct text easily without needing to consider the
    concept of text at all. This is not the point. I think that some of the ideas
    presented above would resolve that issue. 16-segment was just an easy way for
    me to throw up this demo.
var module;

module = angular.module("norobo", []);

module.controller("NoroboController", [
  "$scope", function($scope) {
    var genid;
    genid = function(len, prefix, keyspace) {
      if (len == null) {
        len = 16;
      }
      if (prefix == null) {
        prefix = "";
      }
      if (keyspace == null) {
        keyspace = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
      }
      while (len-- > 0) {
        prefix += keyspace.charAt(Math.floor(Math.random() * keyspace.length));
      }
      return prefix;
    };
    
    $scope.norobo = {};
    
    $scope.check = function (a, b) { return (a || "").toLowerCase() == (b || "").toLowerCase(); };
    
    $scope.reset = function() { $scope.norobo.secret = genid(7); }
    
    $scope.reset();
  }
]);

module.directive("norobo", function() {
  return {
    scope: {
      secret: "=norobo"
    },
    link: function($scope, $el, $attrs) {
      var Character, Characters, canvasH, canvasW, display, frozen, hintText, lineAtts, lines, mouseOrigin, mousePos, mousearea, text, toggleFreeze, update;
      $el.addClass("norobo");
      Character = (function() {
        var A1, A2, B, C, D1, D2, E, F, G1, G2, H, I, J, K, L, M, a1, a2, b, c, characterMasks, d1, d2, e, f, g1, g2, h, i, j, k, l, m, offsets, points;

        offsets = "A1,A2,B,C,D1,D2,E,F,G1,G2,H,I,J,K,L,M".split(",");

        A1 = 0;

        A2 = 1;

        B = 2;

        C = 3;

        D1 = 4;

        D2 = 5;

        E = 6;

        F = 7;

        G1 = 8;

        G2 = 9;

        H = 10;

        I = 11;

        J = 12;

        K = 13;

        L = 14;

        M = 15;

        a1 = 1 << A1;

        a2 = 1 << A2;

        b = 1 << B;

        c = 1 << C;

        d1 = 1 << D1;

        d2 = 1 << D2;

        e = 1 << E;

        f = 1 << F;

        g1 = 1 << G1;

        g2 = 1 << G2;

        h = 1 << H;

        i = 1 << I;

        j = 1 << J;

        k = 1 << K;

        l = 1 << L;

        m = 1 << M;

        points = {};

        points.A1 = [
          {
            x: 0.0,
            y: 0.0
          }, {
            x: 0.5,
            y: 0.0
          }
        ];

        points.A2 = [
          {
            x: 0.5,
            y: 0.0
          }, {
            x: 1.0,
            y: 0.0
          }
        ];

        points.B = [
          {
            x: 1.0,
            y: 0.0
          }, {
            x: 1.0,
            y: 0.5
          }
        ];

        points.C = [
          {
            x: 1.0,
            y: 0.5
          }, {
            x: 1.0,
            y: 1.0
          }
        ];

        points.D1 = [
          {
            x: 0.0,
            y: 1.0
          }, {
            x: 0.5,
            y: 1.0
          }
        ];

        points.D2 = [
          {
            x: 0.5,
            y: 1.0
          }, {
            x: 1.0,
            y: 1.0
          }
        ];

        points.E = [
          {
            x: 0.0,
            y: 0.5
          }, {
            x: 0.0,
            y: 1.0
          }
        ];

        points.F = [
          {
            x: 0.0,
            y: 0.0
          }, {
            x: 0.0,
            y: 0.5
          }
        ];

        points.G1 = [
          {
            x: 0.0,
            y: 0.5
          }, {
            x: 0.5,
            y: 0.5
          }
        ];

        points.G2 = [
          {
            x: 0.5,
            y: 0.5
          }, {
            x: 1.0,
            y: 0.5
          }
        ];

        points.H = [
          {
            x: 0.0,
            y: 0.0
          }, {
            x: 0.5,
            y: 0.5
          }
        ];

        points.I = [
          {
            x: 0.5,
            y: 0.0
          }, {
            x: 0.5,
            y: 0.5
          }
        ];

        points.J = [
          {
            x: 0.5,
            y: 0.5
          }, {
            x: 1.0,
            y: 0.0
          }
        ];

        points.K = [
          {
            x: 0.5,
            y: 0.5
          }, {
            x: 1.0,
            y: 1.0
          }
        ];

        points.L = [
          {
            x: 0.5,
            y: 0.5
          }, {
            x: 0.5,
            y: 1.0
          }
        ];

        points.M = [
          {
            x: 0.0,
            y: 1.0
          }, {
            x: 0.5,
            y: 0.5
          }
        ];

        characterMasks = {
          ' ': 0,
          '': 0,
          '0': a1 | a2 | b | c | d1 | d2 | e | f | j | m,
          '1': b | c | j,
          '2': a1 | a2 | b | d1 | d2 | e | g1 | g2,
          '3': a1 | a2 | b | c | d1 | d2 | g2,
          '4': b | c | f | g1 | g2,
          '5': a1 | a2 | c | d1 | d2 | f | g1 | g2,
          '6': a1 | a2 | c | d1 | d2 | e | f | g1 | g2,
          '7': a1 | a2 | b | c,
          '8': a1 | a2 | b | c | d1 | d2 | e | f | g1 | g2,
          '9': a1 | a2 | b | c | f | g1 | g2,
          'A': e | f | a1 | a2 | b | c | g1 | g2,
          'B': a1 | a2 | b | c | d1 | d2 | g2 | i | l,
          'C': a1 | a2 | f | e | d1 | d2,
          'D': a1 | a2 | b | c | d1 | d2 | i | l,
          'E': a1 | a2 | f | e | d1 | d2 | g1 | g2,
          'F': a1 | a2 | e | f | g1,
          'G': a1 | a2 | c | d1 | d2 | e | f | g2,
          'H': b | c | e | f | g1 | g2,
          'I': a1 | a2 | d1 | d2 | i | l,
          'J': b | c | d1 | d2 | e,
          'K': e | f | g1 | j | k,
          'L': d1 | d2 | e | f,
          'M': b | c | e | f | h | j,
          'N': b | c | e | f | h | k,
          'O': a1 | a2 | b | c | d1 | d2 | e | f,
          'P': a1 | a2 | b | e | f | g1 | g2,
          'Q': a1 | a2 | b | c | d1 | d2 | e | f | k,
          'R': a1 | a2 | b | e | f | g1 | g2 | k,
          'S': a1 | a2 | c | d1 | d2 | f | g1 | g2,
          'T': a1 | a2 | i | l,
          'U': b | c | d1 | d2 | e | f,
          'V': e | f | j | m,
          'W': b | c | e | f | k | m,
          'X': h | j | k | m,
          'Y': b | f | g1 | g2 | l,
          'Z': a1 | a2 | d1 | d2 | j | m,
          '-': g1 | g2,
          '?': a1 | a2 | b | g2 | l,
          '+': g1 | g2 | i | l,
          '*': g1 | g2 | h | i | j | k | l | m
        };

        function Character(character) {
          var mask, _i;
          mask = characterMasks[character.toUpperCase()];
          this.segments = [];
          for (i = _i = 0; _i < 16; i = ++_i) {
            if (mask & (1 << i)) {
              this.segments.push(points[offsets[i]]);
            }
          }
        }

        return Character;

      })();
      Characters = (function() {
        function Characters(str) {
          var chr, _i, _len, _ref;
          this.characters = [];
          _ref = str.split("");
          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
            chr = _ref[_i];
            this.characters.push(new Character(chr));
          }
        }

        Characters.prototype.getSegments = function(charWidth, charHeight, xSpace, yOff) {
          var c, character, s, segment, segments, _i, _j, _len, _len1, _ref, _ref1;
          if (charWidth == null) {
            charWidth = 10;
          }
          if (charHeight == null) {
            charHeight = 20;
          }
          if (xSpace == null) {
            xSpace = charWidth / 4;
          }
          if (yOff == null) {
            yOff = charHeight / 4;
          }
          segments = [];
          _ref = this.characters;
          for (c = _i = 0, _len = _ref.length; _i < _len; c = ++_i) {
            character = _ref[c];
            _ref1 = character.segments;
            for (s = _j = 0, _len1 = _ref1.length; _j < _len1; s = ++_j) {
              segment = _ref1[s];
              segments.push({
                v: segment,
                r: Math.sqrt((segment[1].x - segment[0].x) ^ 2 + (segment[1].y - segment[0].y) ^ 2),
                x1: segment[0].x * charWidth + c * (xSpace + charWidth),
                y1: segment[0].y * charHeight + yOff,
                x2: segment[1].x * charWidth + c * (xSpace + charWidth),
                y2: segment[1].y * charHeight + yOff,
                rot: Math.random() * 2 * Math.PI,
                xT: Math.random() - 0.5,
                yT: Math.random() - 0.5,
                xT2: Math.random() - 0.5,
                yT2: Math.random() - 0.5,
                nx1: 0,// (Math.random() - 0.5) * charWidth / 6,
                ny1: 0,// (Math.random() - 0.5) * charHeight / 6,
                nx2: 0,// (Math.random() - 0.5) * charWidth / 6,
                ny2: 0// (Math.random() - 0.5) * charHeight / 6
              });
            }
          }
          return segments;
        };

        return Characters;

      })();
      canvasW = 150;
      canvasH = 30;
      frozen = false;
      update = function() {
        var _ref;
        _ref = d3.mouse(this), mousePos.x = _ref[0], mousePos.y = _ref[1];
        if (!frozen) {
          lines.attr("x1", function(l) {
            return l.x1 + l.nx1 + l.xT * (mousePos.x - mouseOrigin.x);
          }).attr("y1", function(l) {
            return l.y1 + l.ny1 + l.yT * (mousePos.y - mouseOrigin.y);
          }).attr("x2", function(l) {
            return l.x2 + l.nx2 + l.xT2 * (mousePos.x - mouseOrigin.x);
          }).attr("y2", function(l) {
            return l.y2 + l.ny2 + l.yT2 * (mousePos.y - mouseOrigin.y);
          }).attr("stroke-width", 2).attr("stroke", "black");
        }
        return hintText.text(function() {
          if (frozen) {
            return "Click to unfreeze";
          } else {
            return "Click to freeze";
          }
        });
      };
      toggleFreeze = function() {
        frozen = !frozen;
        return update();
      };
      display = d3.select($el[0]).append("svg").attr("width", canvasW).attr("height", canvasH);
      mousearea = d3.select($el[0]).append("svg").attr("width", canvasW).attr("height", canvasH).attr("fill", "rgb(33,33,33)").on("mousemove", update).on("mousedown", toggleFreeze);
      hintText = mousearea.append("text").attr("x", 8).attr("y", 3 * canvasH / 4).attr("font-family", "sans-serif").attr("font-size", canvasH / 2 + "px").text("Move mouse here");
      mouseOrigin = {
        x: Math.random() * canvasW,
        y: Math.random() * canvasH
      };
      mousePos = {
        x: 0,
        y: 0
      };
      text = lines = lineAtts = null;
      return $scope.$watch("secret", function(secret) {
        var lineAttrs;
        text = new Characters(secret);
        lines = display.selectAll("line").data(text.getSegments(10, 20, 10)).enter().append("line");
        return lineAttrs = lines.attr("x1", function(l) {
          return l.x1 + l.xT * (mousePos.x - mouseOrigin.x);
        }).attr("y1", function(l) {
          return l.y1 + l.yT * (mousePos.y - mouseOrigin.y);
        }).attr("x2", function(l) {
          return l.x2 + l.xT * (mousePos.x - mouseOrigin.x);
        }).attr("y2", function(l) {
          return l.y2 + l.yT * (mousePos.y - mouseOrigin.y);
        }).attr("stroke-width", 2).attr("stroke", "black");
      });
    }
  };
});