anchor.diff:    Camera zoom(num scaleFactor) => new Camera(center, radius * scaleFactor);
   
     @override
  +  operator==(other) => (other is Camera) && center == other.center && radius == other.radius;
  +
  +  @override
  +  get hashCode => center.hashCode ^ radius.hashCode;
  +
  +  @override
  +  toString() => "Camera(${center}, ${radius})";
  +
  +  @override
     clone() => this;
  -
  -  @override
  -  operator==(other) => (other is Camera) && center == other.center && radius == other.radius;
  -
  -  @override
  -  get hashCode => center.hashCode ^ radius.hashCode;
  -
  -  @override
  -  toString() => "Camera(${center}, ${radius})";
   
     static const start = const Camera(const Complex(0, 0), 2.0);
   }
  
positive.code: // Views
  //
  
  typedef SaveCameraHandler(Camera next);
  
  class MandelbrotApp extends AnimatedTag {
    final Camera startCamera;
    final SaveCameraHandler save;
  
    const MandelbrotApp({this.startCamera: Camera.start, this.save});
  
    @override
    Place start() => new Place(startCamera);
  
    @override
    bool shouldRestart(Place p, AnimatedTag prev) {
      if (prev is MandelbrotApp) {
        return prev.startCamera != startCamera;
      } else {
        return true;
      }
    }
  
    @override
    Tag renderAt(Place<Camera> p) {
  
      void onClick(Complex newCenter) {
        p.nextState = p.nextState.pan(newCenter);
      }
  
      void zoom(num scaleAmount) {
        p.nextState = p.nextState.zoom(scaleAmount);
        if (save != null) {
          save(p.nextState);
        }
      }
  
      var camera = p.state;
      return $.Div(inner: [
        new MandelbrotView(center: camera.center, radius: camera.radius, onClick: onClick),
        $.Div(inner: [
          $.Button(onClick: (_) => zoom(0.5), inner: "Zoom In"),
          $.Button(onClick: (_) => zoom(2.0), inner: "Zoom Out"),
        ]),
        $.Div(inner: [
          "Center: ${camera.center} Radius: ${camera.radius}"
        ]),
        $.Div(inner: "Click image to recenter")
      ]);
  
negative.code:   }
  
    if (params.containsKey("x") && params.containsKey("y") && params.containsKey("r")) {
      return new Camera(new Complex(params["x"], params["y"]), params["r"]);
    } else {
      return Camera.start;
    }
  }
  
  Camera lastSaved;
  
  updateCamera(Camera next) {
    var newHash = "#x=${next.center.real}&y=${next.center.imag}&r=${next.radius}";
    var url = window.location.href;
    if (url.contains("#")) {
      url = url.substring(0, url.indexOf("#"));
    }
    url += newHash;
    if (lastSaved != null && lastSaved.radius == next.radius) {
      window.history.replaceState(null,  "", url);
    } else {
      window.history.pushState(null,  "", url);
    }
    lastSaved = next;
  }
  
  render() {
    var camera = loadCamera();
    getRoot("#container")
      .mount(new MandelbrotApp(startCamera: camera, onCameraChange: updateCamera));
  }
  
  main() {
    render();
    window.onHashChange.listen((_) {
      lastSaved = null;
      render();
    });
  }
  