PageSourceSearch

https://singingsuccess.com/wp-content/plugins/jet-elements/assets/js/lib/peeljs/peeljs.js?ver=1.0.0

js singingsuccess.com collected 2026-10-02 11:05:25 UTC 49,008 bytes, 1,620 lines download raw bytes

1(function(win) {
2
3  // Constants
4
5  var PRECISION       = 1e2; // 2 decimals
6  var VENDOR_PREFIXES = ['webkit','moz', ''];
7  var SVG_NAMESPACE   = 'http://www.w3.org/2000/svg';
8  var CSS_PREFIX      = 'peel-';
9
10  var clipProperty, transformProperty, boxShadowProperty, filterProperty;
11  var backgroundGradientSupport;
12  var docEl = document.documentElement;
13  var style = docEl.style;
14
15
16  // Support
17
18  function getCssProperty(name) {
19    var prefix, str;
20    for (var i = 0; i < VENDOR_PREFIXES.length; i++) {
21      prefix = VENDOR_PREFIXES[i];
22      str = prefix ? prefix + capitalize(name) : name;
23      if (str in style) {
24        return str;
25      }
26    }
27  }
28
29  function setCssProperties() {
30    clipProperty      = getCssProperty('clipPath');
31    transformProperty = getCssProperty('transform');
32    boxShadowProperty = getCssProperty('boxShadow');
33    filterProperty    = getCssProperty('filter');
34    setBackgroundGradientSupport();
35    Peel.supported = !!(clipProperty && transformProperty);
36    Peel.effectsSupported = backgroundGradientSupport;
37  }
38
39  function setBackgroundGradientSupport() {
40    var el = document.createElement('div');
41    var style = el.style;
42    style.cssText = 'background:linear-gradient(45deg,#9f9,white);';
43    backgroundGradientSupport = (style.backgroundImage || '').indexOf('gradient') > -1;
44  }
45
46
47
48  // General helpers
49
50  function round(n) {
51    return Math.round(n * PRECISION) / PRECISION;
52  }
53
54  // Clamps the number to be between 0 and 1.
55  function clamp(n) {
56    return Math.max(0, Math.min(1, n));
57  }
58
59  function normalize(n, min, max) {
60    return (n - min) / (max - min);
61  }
62
63  // Distributes a number between 0 and 1 along a bell curve.
64  function distribute(t, mult) {
65    return (mult || 1) * 2 * (.5 - Math.abs(t - .5));
66  }
67
68  function capitalize(str) {
69    return str.slice(0,1).toUpperCase() + str.slice(1);
70  }
71
72  function camelize(str) {
73    return str.replace(/-(\w)/g, function(a, b) {
74      return b.toUpperCase();
75    });
76  }
77
78  function prefix(str) {
79    return CSS_PREFIX + str;
80  }
81
82  // CSS Helpers
83
84  function setCSSClip(el, clip) {
85    el.style[clipProperty] = clip;
86  }
87
88  function setTransform(el, t) {
89    el.style[transformProperty] = t;
90  }
91
92  function setBoxShadow(el, x, y, blur, spread, intensity) {
93    el.style[boxShadowProperty] = getShadowCss(x, y, blur, spread, intensity);
94  }
95
96  function setDropShadow(el, x, y, blur, intensity) {
97    el.style[filterProperty] = 'drop-shadow(' + getShadowCss(x, y, blur, null, intensity) + ')';
98  }
99
100  function getShadowCss(x, y, blur, spread, intensity) {
101    return round(x) + 'px ' +
102           round(y) + 'px ' +
103           round(blur) + 'px ' +
104           (spread ? round(spread) + 'px ' : '') +
105           'rgba(0,0,0,' + round(intensity) + ')';
106  }
107
108  function setOpacity(el, t) {
109    el.style.opacity = t;
110  }
111
112  function setBackgroundGradient(el, rotation, stops) {
113    if (!backgroundGradientSupport) return;
114    var css;
115    if (stops.length === 0) {
116      css = 'none';
117    } else {
118      css = 'linear-gradient(' + round(rotation) + 'deg,' + stops.join(',') + ')';
119    }
120    el.style.backgroundImage = css;
121  }
122
123  // Event Helpers
124
125  function addEvent(el, type, fn) {
126    el.addEventListener(type, fn)
127  }
128
129  function removeEvent(el, type, fn) {
130    el.removeEventListener(type, fn);
131  }
132
133  function getEventCoordinates(evt, el) {
134    var pos = evt.changedTouches ? evt.changedTouches[0] : evt;
135    return {
136      'x': pos.clientX - el.offsetLeft + window.scrollX,
137      'y': pos.clientY - el.offsetTop + window.scrollY
138    }
139  }
140
141  function bindWithEvent(fn, scope, arg1, arg2) {
142    return function(evt) {
143      fn.call(scope, evt, arg1, arg2);
144    }
145  }
146
147  // Color Helpers
148
149  function getBlackStop(a, pos) {
150    return getColorStop(0, 0, 0, a, pos);
151  }
152
153  function getWhiteStop(a, pos) {
154    return getColorStop(255, 255, 255, a, pos);
155  }
156
157  function getColorStop(r, g, b, a, pos) {
158    a = round(clamp(a));
159    return 'rgba('+ r +','+ g +','+ b +','+ a +') ' + round(pos * 100) + '%';
160  }
161
162
163  // DOM Element Helpers
164
165  function getElement(obj, node) {
166    if (typeof obj === 'string') {
167      obj = (node || document).querySelector(obj);
168    }
169    return obj;
170  }
171
172  function createElement(parent, className) {
173    var el = document.createElement('div');
174    addClass(el, className);
175    parent.appendChild(el);
176    return el;
177  }
178
179  function removeClass(el, str) {
180    el.classList.remove(str);
181  }
182
183  function addClass(el, str) {
184    el.classList.add(str);
185  }
186
187  function getZIndex(el) {
188    return el.style.zIndex;
189  }
190
191  function setZIndex(el, index) {
192    el.style.zIndex = index;
193  }
194
195
196  // SVG Helpers
197
198  function createSVGElement(tag, parent, attributes) {
199    parent = parent || docEl;
200    var el = document.createElementNS(SVG_NAMESPACE, tag);
201    parent.appendChild(el);
202    for (var key in attributes) {
203      if (!attributes.hasOwnProperty(key)) continue;
204      setSVGAttribute(el, key, attributes[key]);
205    }
206    return el;
207  }
208
209  function setSVGAttribute(el, key, value) {
210    el.setAttributeNS(null, key, value);
211  }
212
213
214  /**
215   * Main class that controls the peeling effect.
216   * @param {HTMLElement|string} el The main container element (can be query).
217   * @param {object} options Options for the effect.
218   * @constructor
219   * @public
220   */
221  function Peel (el, opt) {
222    this.setOptions(opt);
223    this.el = getElement(el, docEl);
224    this.constraints = [];
225    this.events = [];
226    this.setupLayers();
227    this.setupDimensions();
228    this.setCorner(this.getOption('corner'));
229    this.setMode(this.getOption('mode'));
230    this.init();
231  }
232
233  /**
234   * Four constants representing the corners of the element from which peeling can occur.
235   * @constant
236   * @public
237   */
238  Peel.Corners = {
239    TOP_LEFT:     0x0,
240    TOP_RIGHT:    0x1,
241    BOTTOM_LEFT:  0x2,
242    BOTTOM_RIGHT: 0x3
243  }
244
245  /**
246   * Defaults
247   * @constant
248   */
249  Peel.Defaults = {
250    'topShadow': true,
251    'topShadowBlur': 5,
252    'topShadowAlpha': .5,
253    'topShadowOffsetX': 0,
254    'topShadowOffsetY': 1,
255    'topShadowCreatesShape': true,
256
257    'backReflection': false,
258    'backReflectionSize': .02,
259    'backReflectionOffset': 0,
260    'backReflectionAlpha': .15,
261    'backReflectionDistribute': true,
262
263    'backShadow': true,
264    'backShadowSize': .04,
265    'backShadowOffset': 0,
266    'backShadowAlpha': .1,
267    'backShadowDistribute': true,
268
269    'bottomShadow': true,
270    'bottomShadowSize': 1.5,
271    'bottomShadowOffset': 0,
272    'bottomShadowDarkAlpha': .7,
273    'bottomShadowLightAlpha': .1,
274    'bottomShadowDistribute': true,
275
276    'setPeelOnInit': true,
277    'clippingBoxScale': 4,
278    'flipConstraintOffset': 5,
279    'dragPreventsDefault': true
280  }
281
282  /**
283   * Sets the corner for the peel effect to happen from (default is bottom right).
284   * @param {Mixed} [...] Either x,y or a corner id.
285   * @public
286   */
287  Peel.prototype.setCorner = function() {
288    var args = arguments;
289    if (args[0] === undefined) {
290      args = [Peel.Corners.BOTTOM_RIGHT];
291    } else if (args[0].length) {
292      args = args[0];
293    }
294    this.corner = this.getPointOrCorner(args);
295  }
296
297  /**
298   * Sets a pre-defined "mode".
299   * @param {string} mode The mode to set.
300   * @public
301   */
302  Peel.prototype.setMode = function(mode) {
303    if (mode === 'book') {
304      // The order of constraints is important here so that the peel line
305      // approaches the horizontal smoothly without jumping.
306      this.addPeelConstraint(Peel.Corners.BOTTOM_LEFT);
307      this.addPeelConstraint(Peel.Corners.TOP_LEFT);
308      // Removing effect distribution will make the book still have some
309      // depth to the effect while fully open.
310      this.setOption('backReflection', false);
311      this.setOption('backShadowDistribute', false);
312      this.setOption('bottomShadowDistribute', false);
313    } else if (mode === 'calendar') {
314      this.addPeelConstraint(Peel.Corners.TOP_RIGHT);
315      this.addPeelConstraint(Peel.Corners.TOP_LEFT);
316    }
317  }
318
319  /**
320   * Sets a path along which the peel will follow.
321   * Can be a flat line segment or a bezier curve.
322   * @param {...number} x/y Points along the path. 4 arguments indicates a
323   *     linear path along 2 points (p1 to p2), while 8 arguments indicates a
324   *     bezier curve from p1 to p2 using control points c1 and c2. The first
325   *     and last two arguments represent p1 and p2, respectively.
326   * @public
327   */
328  Peel.prototype.setPeelPath = function(x1, y1) {
329    var args = arguments, p1, p2, c1, c2;
330    p1 = new Point(x1, y1);
331    if (args.length === 4) {
332      p2 = new Point(args[2], args[3]);
333      this.path = new LineSegment(p1, p2);
334    } else if (args.length === 8) {
335      c1 = new Point(args[2], args[3]);
336      c2 = new Point(args[4], args[5]);
337      p2 = new Point(args[6], args[7]);
338      this.path = new BezierCurve(p1, c1, c2, p2);
339    }
340  }
341
342  /**
343   * Sets a function to be called when the user drags, either with a mouse or
344   * with a finger (touch events).
345   * @param {Function} fn The function to be called on drag. This function will
346   *     be called with the Peel instance as the "this" keyword, the original
347   *     event as the first argument, and the x, y coordinates of the drag as
348   *     the 2nd and 3rd arguments, respectively.
349   * @param {HTMLElement} el The element to initiate the drag on mouse/touch start.
350   *     If not passed, this will be the element associated with the Peel
351   *     instance. Allowing this to be passed lets another element serve as a
352   *     "hit area" that can be larger than the element itself.
353   * @public
354   */
355  Peel.prototype.handleDrag = function(fn, el) {
356    this.dragHandler = fn;
357    this.setupDragEvents(el);
358  }
359
360  /**
361   * Sets a function to be called when the user either clicks with a mouse or
362   * taps with a finger (touch events).
363   * @param {Function} fn The function to be called on press. This function will
364   *     be called with the Peel instance as the "this" keyword, the original
365   *     event as the first argument, and the x, y coordinates of the event as
366   *     the 2nd and 3rd arguments, respectively.
367   * @param {HTMLElement} el The element to initiate the event.
368   *     If not passed, this will be the element associated with the Peel
369   *     instance. Allowing this to be passed lets another element serve as a
370   *     "hit area" that can be larger than the element itself.
371   * @public
372   */
373  Peel.prototype.handlePress = function(fn, el) {
374    this.pressHandler = fn;
375    this.setupDragEvents(el);
376  }
377
378  /**
379   * Sets up the drag events needed for both drag and press handlers.
380   * @param {HTMLElement} el The element to initiate the dragStart event on.
381   * @private
382   */
383  Peel.prototype.setupDragEvents = function(el) {
384    var self = this, isDragging, moveName, endName;
385
386    if (this.dragEventsSetup) {
387      return;
388    }
389
390    el = el || this.el;
391
392    function dragStart (touch, evt) {
393      if (self.getOption('dragPreventsDefault')) {
394        evt.preventDefault();
395      }
396      moveName = touch ? 'touchmove' : 'mousemove';
397      endName  = touch ? 'touchend' : 'mouseup';
398
399      addEvent(docEl, moveName, dragMove);
400      addEvent(docEl, endName, dragEnd);
401      isDragging = false;
402    }
403
404    function dragMove (evt) {
405      if (self.dragHandler) {
406        callHandler(self.dragHandler, evt);
407      }
408      isDragging = true;
409    }
410
411    function dragEnd(evt) {
412      if (!isDragging && self.pressHandler) {
413        callHandler(self.pressHandler, evt);
414      }
415      removeEvent(docEl, moveName, dragMove);
416      removeEvent(docEl, endName, dragEnd);
417    }
418
419    function callHandler(fn, evt) {
420      var coords = getEventCoordinates(evt, self.el);
421      fn.call(self, evt, coords.x, coords.y);
422    }
423
424    this.addEvent(el, 'mousedown', dragStart.bind(this, false));
425    this.addEvent(el, 'touchstart', dragStart.bind(this, true));
426    this.dragEventsSetup = true;
427  }
428
429  /**
430   * Remove all event handlers previously added to the instance.
431   * @public
432   */
433  Peel.prototype.removeEvents = function() {
434    this.events.forEach(function(e, i) {
435      removeEvent(e.el, e.type, e.handler);
436    });
437    this.events = [];
438  }
439
440  /**
441   * Sets the peel effect to a point in time along a previously
442   * specified path. Will throw an error if no path exists.
443   * @param {number} n The time value (between 0 and 1).
444   * @public
445   */
446  Peel.prototype.setTimeAlongPath = function(t) {
447    t = clamp(t);
448    var point = this.path.getPointForTime(t);
449    this.timeAlongPath = t;
450    this.setPeelPosition(point.x, point.y);
451  }
452
453  /**
454   * Sets a threshold above which the top layer (including the backside) layer
455   * will begin to fade out. This is calculated based on the visible clipped
456   * area of the polygon. If a peel path is set, it will use the progress along
457   * the path instead.
458   * @param {number} n A point between 0 and 1.
459   * @public
460   */
461  Peel.prototype.setFadeThreshold = function(n) {
462    this.fadeThreshold = n;
463  }
464
465  /**
466   * Sets the position of the peel effect. This point is the position
467   * of the corner that is being peeled back.
468   * @param {Mixed} [...] Either x,y or a corner id.
469   * @public
470   */
471  Peel.prototype.setPeelPosition = function() {
472    var pos = this.getPointOrCorner(arguments);
473    pos = this.getConstrainedPeelPosition(pos);
474    if (!pos) {
475      return;
476    }
477    this.peelLineSegment = this.getPeelLineSegment(pos);
478    this.peelLineRotation = this.peelLineSegment.getAngle();
479    this.setClipping();
480    this.setBackTransform(pos);
481    this.setEffects();
482  }
483
484  /**
485   * Sets a constraint on the distance of the peel. This can be thought of as a
486   * point on the layers that are connected and cannot be torn apart. Typically
487   * this only makes sense as a point on the outer edge, such as the left edge
488   * of an open book, or the top edge of a calendar. In this case, simply using
489   * 2 constraint points (top-left/bottom-left for a book, etc) will create the
490   * desired effect. An arbitrary point can also be used with an effect like a
491   * thumbtack holding the pages together.
492   * @param {Mixed} [...] Either x,y or a corner id.
493   * @public
494   */
495  /**
496   * Sets the corner for the peel effect to happen from.
497   * @public
498   */
499  Peel.prototype.addPeelConstraint = function() {
500    var p = this.getPointOrCorner(arguments);
501    var radius = this.corner.subtract(p).getLength();
502    this.constraints.push(new Circle(p, radius));
503    this.calculateFlipConstraint();
504  }
505
506  /**
507   * Sets an option to use for the effect.
508   * @param {string} key The option to set.
509   * @param {Mixed} value The value for the option.
510   * @public
511   */
512  Peel.prototype.setOption = function(key, value) {
513    this.options[key] = value;
514  }
515
516  /**
517   * Gets an option set by the user.
518   * @param {string} key The key of the option to get.
519   * @returns {Mixed}
520   * @public
521   */
522  Peel.prototype.getOption = function(key) {
523    return this.options[camelize(key)];
524  }
525
526  /**
527   * Gets the ratio of the area of the clipped top layer to the total area.
528   * @returns {number} A value between 0 and 1.
529   * @public
530   */
531  Peel.prototype.getAmountClipped = function() {
532    var topArea = this.getTopClipArea();
533    var totalArea = this.width * this.height;
534    return normalize(topArea, totalArea, 0);
535  }
536
537  /**
538   * Adds an event listener to the element and keeps track of it for later
539   * removal.
540   * @param {Element} el The element to add the handler to.
541   * @param {string} type The event type.
542   * @param {Function} fn The handler function.
543   * @private
544   */
545  Peel.prototype.addEvent = function(el, type, fn) {
546    addEvent(el, type, fn);
547    this.events.push({
548      el: el,
549      type: type,
550      handler: fn
551    });
552    return fn;
553  }
554  /**
555   * Gets the area of the clipped top layer.
556   * @returns {number}
557   * @private
558   */
559  Peel.prototype.getTopClipArea = function() {
560    var top  = new Polygon();
561    this.elementBox.forEach(function(side) {
562      this.distributeLineByPeelLine(side, top);
563    }, this);
564    return Polygon.getArea(top.getPoints());
565  }
566
567  /**
568   * Determines which of the constraints should be used as the flip constraint
569   * by checking which has a y value closes to the corner (because the
570   * constraint operates relative to the vertical midline). Only one constraint
571   * should be required - changing the order of the constraints can help to
572   * achieve the proper effect and more than one will interfere with each other.
573   * @private
574   */
575  Peel.prototype.calculateFlipConstraint = function() {
576    var corner = this.corner, arr = this.constraints.concat();
577    this.flipConstraint = arr.sort(function(a, b) {
578      var aY = corner.y - a.center.y;
579      var bY = corner.y - b.center.y;
580      return a - b;
581    })[0];
582  }
583
584  /**
585   * Called when the drag event starts.
586   * @param {Event} evt The original DOM event.
587   * @param {string} type The event type, "mouse" or "touch".
588   * @param {Function} fn The handler function to be called on drag.
589   * @private
590   */
591  Peel.prototype.dragStart = function(evt, type, fn) {
592  }
593
594  /**
595   * Calls an event handler using the coordinates of the event.
596   * @param {Event} evt The original event.
597   * @param {Function} fn The handler to call.
598   * @private
599   */
600  Peel.prototype.fireHandler = function(evt, fn) {
601    var coords = getEventCoordinates(evt, this.el);
602    fn.call(this, evt, coords.x, coords.y);
603  }
604
605  /**
606   * Sets the clipping points of the top and back layers based on a line
607   * segment that represents the peel line.
608   * @private
609   */
610  Peel.prototype.setClipping = function() {
611    var top  = new Polygon();
612    var back = new Polygon();
613    this.clippingBox.forEach(function(side) {
614      this.distributeLineByPeelLine(side, top, back);
615    }, this);
616
617    this.topClip.setPoints(top.getPoints());
618    this.backClip.setPoints(back.getPoints());
619  }
620
621  /**
622   * Distributes the first point in the given line segment and its intersect
623   * with the peel line, if there is one.
624   * @param {LineSegment} seg The line segment to check against.
625   * @param {Polygon} poly1 The first polygon.
626   * @param {Polygon} [poly2] The second polygon.
627   * @private
628   */
629  Peel.prototype.distributeLineByPeelLine = function(seg, poly1, poly2) {
630    var intersect = this.peelLineSegment.getIntersectPoint(seg);
631    this.distributePointByPeelLine(seg.p1, poly1, poly2);
632    this.distributePointByPeelLine(intersect, poly1, poly2);
633  }
634
635  /**
636   * Distributes the given point to one of two polygons based on which side of
637   * the peel line it falls upon (if it falls directly on the line segment
638   * it is added to both).
639   * @param {Point} p The point to be distributed.
640   * @param {Polygon} poly1 The first polygon.
641   * @param {Polygon} [poly2] The second polygon.
642   * @private
643   */
644  Peel.prototype.distributePointByPeelLine = function(p, poly1, poly2) {
645    if (!p) return;
646    var d = this.peelLineSegment.getPointDeterminant(p);
647    if (d <= 0) {
648      poly1.addPoint(p);
649    }
650    if (d >= 0 && poly2) {
651      poly2.addPoint(this.flipPointHorizontally(p));
652    }
653  }
654
655  /**
656   * Sets the options for the effect, merging in defaults.
657   * @param {Object} opt User options.
658   * @private
659   */
660  Peel.prototype.setOptions = function(opt) {
661    var options = opt || {}, defaults = Peel.Defaults;
662    for (var key in defaults) {
663      if (!defaults.hasOwnProperty(key) || key in options) {
664        continue;
665      }
666      options[key] = defaults[key];
667    }
668    this.options = options;
669  }
670
671  /**
672   * Finds or creates a layer in the dom.
673   * @param {string} id The internal id of the element to be found or created.
674   * @param {HTMLElement} parent The parent if the element needs to be created.
675   * @param {numer} zIndex The z index of the layer.
676   * @returns {HTMLElement}
677   * @private
678   */
679  Peel.prototype.findOrCreateLayer = function(id, parent, zIndex) {
680    var optId = id + '-element';
681    var domId = prefix(id);
682    var el = getElement(this.getOption(optId) || '.' + domId, parent);
683    if (!el) {
684      el = createElement(parent, domId);
685    }
686    addClass(el, prefix('layer'));
687    setZIndex(el, zIndex);
688    return el;
689  }
690
691  /**
692   * Returns either a point created from 2 arguments (x/y) or a corner point
693   * created from the first argument as a corner id.
694   * @param {Arguments} args The arguments object from the original function.
695   * @returns {Point}
696   * @private
697   */
698  Peel.prototype.getPointOrCorner = function(args) {
699    if (args.length === 2) {
700      return new Point(args[0], args[1]);
701    } else if(typeof args[0] === 'number') {
702      return this.getCornerPoint(args[0]);
703    }
704    return args[0];
705  }
706
707  /**
708   * Returns a corner point based on an id defined in Peel.Corners.
709   * @param {number} id The id of the corner.
710   * @private
711   */
712  Peel.prototype.getCornerPoint = function(id) {
713    var x = +!!(id & 1) * this.width;
714    var y = +!!(id & 2) * this.height;
715    return new Point(x, y);
716  }
717
718  /**
719   * Gets an optional clipping shape that may be set by the user.
720   * @returns {Object}
721   * @private
722   */
723  Peel.prototype.getOptionalShape = function() {
724    var shapes = ['rect', 'polygon', 'path', 'circle'], found;
725    shapes.some(function(type) {
726      var attr = this.getOption(type), obj;
727      if (attr) {
728        obj = {};
729        obj.attributes = attr;
730        obj.type = type;
731        found = obj;
732      }
733      return found;
734    }, this);
735    return found;
736  }
737
738  /**
739   * Sets up the main layers used for the effect that may include a possible
740   * subclip shape.
741   * @private
742   */
743  Peel.prototype.setupLayers = function() {
744    var shape = this.getOptionalShape();
745
746    // The inner layers may be wrapped later, so keep a reference to them here.
747    var topInnerLayer  = this.topLayer  = this.findOrCreateLayer('top', this.el, 2);
748    var backInnerLayer = this.backLayer = this.findOrCreateLayer('back', this.el, 3);
749
750    this.bottomLayer = this.findOrCreateLayer('bottom', this.el, 1);
751
752    if (shape) {
753      // If there is an SVG shape specified in the options, then this needs to
754      // be a separate clipped element because Safari/Mobile Safari can't handle
755      // nested clip-paths. The current top/back element will become the shape
756      // clip, so wrap them with an "outer" clip element that will become the
757      // new layer for the peel effect. The bottom layer does not require this
758      // effect, so the shape clip can be set directly on it.
759      this.topLayer  = this.wrapShapeLayer(this.topLayer, 'top-outer-clip');
760      this.backLayer = this.wrapShapeLayer(this.backLayer, 'back-outer-clip');
761
762      this.topShapeClip    = new SVGClip(topInnerLayer, shape);
763      this.backShapeClip   = new SVGClip(backInnerLayer, shape);
764      this.bottomShapeClip = new SVGClip(this.bottomLayer, shape);
765
766      if (this.getOption('topShadowCreatesShape')) {
767        this.topShadowElement = this.setupDropShadow(shape, topInnerLayer);
768      }
769    } else {
770      this.topShadowElement = this.findOrCreateLayer('top-shadow', topInnerLayer, 1);
771    }
772
773    this.topClip = new SVGClip(this.topLayer);
774    this.backClip = new SVGClip(this.backLayer);
775
776    this.backShadowElement     = this.findOrCreateLayer('back-shadow', backInnerLayer, 1);
777    this.backReflectionElement = this.findOrCreateLayer('back-reflection', backInnerLayer, 2);
778    this.bottomShadowElement   = this.findOrCreateLayer('bottom-shadow', this.bottomLayer, 1);
779
780    this.usesBoxShadow = !shape;
781  }
782
783  /**
784   * Creates an inline SVG element to be used as a layer for a drop shadow filter
785   * effect. Note that drop shadow filters currently have some odd quirks in
786   * Blink such as blur radius changing depending on rotation, etc.
787   * @param {Object} shape A shape describing the SVG element to be used.
788   * @param {HTMLElement} parent The parent element where the layer will be added.
789   * @returns {SVGElement}
790   * @private
791   */
792  Peel.prototype.setupDropShadow = function(shape, parent) {
793    var svg = createSVGElement('svg', parent, {
794      'class': prefix('layer')
795    });
796    createSVGElement(shape.type, svg, shape.attributes);
797    return svg;
798  }
799
800  /**
801   * Wraps the passed element in another layer, preserving its z-index. Also
802   * add a "shape-layer" class to the layer which now becomes a shape clip.
803   * @param {HTMLElement} el The element to become the wrapped shape layer.
804   * @param {string} id The identifier for the new layer that will wrap the element.
805   * @returns {HTMLElement} The new element that wraps the shape layer.
806   * @private
807   */
808  Peel.prototype.wrapShapeLayer = function(el, id) {
809    var zIndex = getZIndex(el);
810    addClass(el, prefix('shape-layer'));
811    var outerLayer = this.findOrCreateLayer(id, this.el, zIndex);
812    outerLayer.appendChild(el);
813    return outerLayer;
814  }
815
816  /**
817   * Sets up the dimensions of the element box and clipping box that area used
818   * in the effect.
819   * @private
820   */
821  Peel.prototype.setupDimensions = function() {
822    this.width  = this.el.offsetWidth;
823    this.height = this.el.offsetHeight;
824    this.center = new Point(this.width / 2, this.height / 2);
825
826    this.elementBox  = this.getScaledBox(1);
827    this.clippingBox = this.getScaledBox(this.getOption('clippingBoxScale'));
828  }
829
830  /**
831   * Gets a box defined by 4 line segments that is at a scale of the main
832   * element.
833   * @param {number} scale The scale for the box to be.
834   * @private
835   */
836  Peel.prototype.getScaledBox = function(scale) {
837
838    // Box scale is equal to:
839    // 1 * the bottom/right scale
840    // 0 * the top/left scale.
841    var brScale = scale;
842    var tlScale = scale - 1;
843
844    var tl = new Point(-this.width * tlScale, -this.height * tlScale);
845    var tr = new Point( this.width * brScale, -this.height * tlScale);
846    var br = new Point( this.width * brScale,  this.height * brScale);
847    var bl = new Point(-this.width * tlScale,  this.height * brScale);
848
849    return [
850      new LineSegment(tl, tr),
851      new LineSegment(tr, br),
852      new LineSegment(br, bl),
853      new LineSegment(bl, tl)
854    ];
855  }
856
857  /**
858   * Returns the peel position adjusted by constraints, if there are any.
859   * @param {Point} point The peel position to be constrained.
860   * @returns {Point}
861   * @private
862   */
863  Peel.prototype.getConstrainedPeelPosition = function(pos) {
864    this.constraints.forEach(function(area) {
865      var offset = this.getFlipConstraintOffset(area, pos);
866      if (offset) {
867        area = new Circle(area.center, area.radius - offset);
868      }
869      pos = area.constrainPoint(pos);
870    }, this);
871    return pos;
872  }
873
874  /**
875   * Returns an offset to "pull" a corner in to prevent the peel effect from
876   * suddenly flipping around its axis. This offset is intended to be applied
877   * on the Y axis when dragging away from the center.
878   * @param {Circle} area The constraint to check against.
879   * @param {Point} point The peel position to be constrained.
880   * @returns {number|undefined}
881   * @private
882   */
883  Peel.prototype.getFlipConstraintOffset = function(area, pos) {
884    var offset = this.getOption('flipConstraintOffset');
885    if (area === this.flipConstraint && offset) {
886      var cornerToCenter = this.corner.subtract(this.center);
887      var cornerToConstraint = this.corner.subtract(area.center);
888      var baseAngle = cornerToConstraint.getAngle();
889
890      // Normalized angles are rotated to be in the same space relative
891      // to the constraint.
892      var nCornerToConstraint = cornerToConstraint.rotate(-baseAngle);
893      var nPosToConstraint = pos.subtract(area.center).rotate(-baseAngle);
894
895      // Flip the vector vertically if the corner is in the bottom left or top
896      // right relative to the center, as the effect should always pull away
897      // from the vertical midline.
898      if (cornerToCenter.x * cornerToCenter.y < 0) {
899        nPosToConstraint.y *= -1;
900      }
901
902      if (nPosToConstraint.x > 0 && nPosToConstraint.y > 0) {
903        return normalize(nPosToConstraint.getAngle(), 45, 0) * offset;
904      }
905
906    }
907  }
908
909  /**
910   * Gets the line segment that represents the current peel line.
911   * @param {Point} point The position of the peel corner.
912   * @returns {LineSegment}
913   * @private
914   */
915  Peel.prototype.getPeelLineSegment = function(point) {
916    // The point midway between the peel position and the corner.
917    var halfToCorner = this.corner.subtract(point).scale(.5);
918    var midpoint = point.add(halfToCorner);
919    if (halfToCorner.x === 0 && halfToCorner.y === 0) {
920      // If the corner is the same as the point, then set half to corner
921      // to be the center, and keep the midpoint where it is. This will
922      // ensure a non-zero peel line.
923      halfToCorner = point.subtract(this.center);
924    }
925    var l = halfToCorner.getLength()
926    var mult = (Math.max(this.width, this.height) / l) * 10;
927    var half = halfToCorner.rotate(-90).scale(mult);
928    var p1 = midpoint.add(half);
929    var p2 = midpoint.subtract(half);
930    return new LineSegment(p1, p2);
931  }
932
933  /**
934   * Sets the transform of the back layer.
935   * @param {Point} pos The position of the peeling corner.
936   * @private
937   */
938  Peel.prototype.setBackTransform = function(pos) {
939    var mirroredCorner = this.flipPointHorizontally(this.corner);
940    var r = (this.peelLineRotation - 90) * 2;
941    var t = pos.subtract(mirroredCorner.rotate(r));
942    var css = 'translate('+ round(t.x) +'px, '+ round(t.y) +'px) rotate('+ round(r) +'deg)';
943    setTransform(this.backLayer, css);
944
945    // Set the top shadow element here as well, as the
946    // position and rotation matches that of the back layer.
947    setTransform(this.topShadowElement, css);
948  }
949
950  /**
951   * Gets the distance of the peel line along an imaginary line that runs
952   * between the corners that it "faces". For example, if the peel line
953   * is rotated 45 degrees, then it can be considered to be between the top left
954   * and bottom right corners. This function will return how far the peel line
955   * has advanced along that line.
956   * @returns {number} A position >= 0.
957   * @private
958   */
959  Peel.prototype.getPeelLineDistance = function() {
960    var cornerId, opposingCornerId, corner, opposingCorner;
961    if (this.peelLineRotation < 90) {
962      cornerId = Peel.Corners.TOP_RIGHT;
963      opposingCornerId = Peel.Corners.BOTTOM_LEFT;
964    } else if (this.peelLineRotation < 180) {
965      cornerId = Peel.Corners.BOTTOM_RIGHT;
966      opposingCornerId = Peel.Corners.TOP_LEFT;
967    } else if (this.peelLineRotation < 270) {
968      cornerId = Peel.Corners.BOTTOM_LEFT;
969      opposingCornerId = Peel.Corners.TOP_RIGHT;
970    } else if (this.peelLineRotation < 360) {
971      cornerId = Peel.Corners.TOP_LEFT;
972      opposingCornerId = Peel.Corners.BOTTOM_RIGHT;
973    }
974    corner = this.getCornerPoint(cornerId);
975    opposingCorner = this.getCornerPoint(opposingCornerId);
976
977    // Scale the line segment past the original corners so that the effects
978    // can have a nice fadeout even past 1.
979    var cornerToCorner = new LineSegment(corner, opposingCorner).scale(2);
980    var intersect = this.peelLineSegment.getIntersectPoint(cornerToCorner);
981    if (!intersect) {
982      // If there is no intersect, then assume that it has run past the opposing
983      // corner and set the distance to well past the full distance.
984      return 2;
985    }
986    var distanceToPeelLine = corner.subtract(intersect).getLength();
987    var totalDistance      = corner.subtract(opposingCorner).getLength();
988    return (distanceToPeelLine / totalDistance);
989  }
990
991  /**
992   * Sets shadows and fade effects.
993   * @private
994   */
995  Peel.prototype.setEffects = function() {
996    var t = this.getPeelLineDistance();
997    this.setTopShadow(t);
998    this.setBackShadow(t);
999    this.setBackReflection(t);
1000    this.setBottomShadow(t);
1001    this.setFade();
1002  }
1003
1004  /**
1005   * Sets the top shadow as either a box-shadow or a drop-shadow filter.
1006   * @param {number} t Position of the peel line from corner to corner.
1007   * @private
1008   */
1009  Peel.prototype.setTopShadow = function(t) {
1010    if (!this.getOption('topShadow')) {
1011      return;
1012    }
1013    var sBlur  = this.getOption('topShadowBlur');
1014    var sX     = this.getOption('topShadowOffsetX');
1015    var sY     = this.getOption('topShadowOffsetY');
1016    var alpha  = this.getOption('topShadowAlpha');
1017    var sAlpha = this.exponential(t, 5, alpha);
1018    if (this.usesBoxShadow) {
1019      setBoxShadow(this.topShadowElement, sX, sY, sBlur, 0, sAlpha);
1020    } else {
1021      setDropShadow(this.topShadowElement, sX, sY, sBlur, sAlpha);
1022    }
1023  }
1024
1025  /**
1026   * Gets a number either distributed along a bell curve or increasing linearly.
1027   * @param {number} n The number to transform.
1028   * @param {boolean} dist Whether or not to use distribution.
1029   * @param {number} mult A multiplier for the result.
1030   * @returns {number}
1031   * @private
1032   */
1033  Peel.prototype.distributeOrLinear = function(n, dist, mult) {
1034    if (dist) {
1035      return distribute(n, mult);
1036    } else {
1037      return n * mult;
1038    }
1039  }
1040
1041  /**
1042   * Gets a number either distributed exponentially, clamped to a range between
1043   * 0 and 1, and multiplied by a multiplier.
1044   * @param {number} n The number to transform.
1045   * @param {number} exp The exponent to be used.
1046   * @param {number} mult A multiplier for the result.
1047   * @returns {number}
1048   * @private
1049   */
1050  Peel.prototype.exponential = function(n, exp, mult) {
1051    return mult * clamp(Math.pow(1 + n, exp) - 1);
1052  }
1053
1054  /**
1055   * Sets reflection of the back face as a linear gradient.
1056   * @param {number} t Position of the peel line from corner to corner.
1057   * @private
1058   */
1059  Peel.prototype.setBackReflection = function(t) {
1060    var stops = [];
1061    if (this.canSetLinearEffect('backReflection', t)) {
1062
1063      var rDistribute = this.getOption('backReflectionDistribute');
1064      var rSize = this.getOption('backReflectionSize');
1065      var rOffset = this.getOption('backReflectionOffset');
1066      var rAlpha = this.getOption('backReflectionAlpha');
1067
1068      var reflectionSize = this.distributeOrLinear(t, rDistribute, rSize);
1069      var rStop  = t - rOffset;
1070      var rMid   = rStop - reflectionSize;
1071      var rStart = rMid - reflectionSize;
1072
1073      stops.push(getWhiteStop(0, 0));
1074      stops.push(getWhiteStop(0, rStart));
1075      stops.push(getWhiteStop(rAlpha, rMid));
1076      stops.push(getWhiteStop(0, rStop));
1077    }
1078    setBackgroundGradient(this.backReflectionElement, 180 - this.peelLineRotation, stops);
1079  }
1080
1081  /**
1082   * Sets shadow of the back face as a linear gradient.
1083   * @param {number} t Position of the peel line from corner to corner.
1084   * @private
1085   */
1086  Peel.prototype.setBackShadow = function(t) {
1087    var stops = [];
1088    if (this.canSetLinearEffect('backShadow', t)) {
1089
1090      var sSize       = this.getOption('backShadowSize');
1091      var sOffset     = this.getOption('backShadowOffset');
1092      var sAlpha      = this.getOption('backShadowAlpha');
1093      var sDistribute = this.getOption('backShadowDistribute');
1094
1095      var shadowSize  = this.distributeOrLinear(t, sDistribute, sSize);
1096      var shadowStop  = t - sOffset;
1097      var shadowMid   = shadowStop - shadowSize;
1098      var shadowStart = shadowMid - shadowSize;
1099
1100      stops.push(getBlackStop(0, 0));
1101      stops.push(getBlackStop(0, shadowStart));
1102      stops.push(getBlackStop(sAlpha, shadowMid));
1103      stops.push(getBlackStop(sAlpha, shadowStop));
1104    }
1105    setBackgroundGradient(this.backShadowElement, 180 - this.peelLineRotation, stops);
1106  }
1107
1108  /**
1109   * Sets the bottom shadow as a linear gradient.
1110   * @param {number} t Position of the peel line from corner to corner.
1111   * @private
1112   */
1113  Peel.prototype.setBottomShadow = function(t) {
1114    var stops = [];
1115    if (this.canSetLinearEffect('bottomShadow', t)) {
1116
1117      // Options
1118      var sSize = this.getOption('bottomShadowSize');
1119      var offset = this.getOption('bottomShadowOffset');
1120      var darkAlpha = this.getOption('bottomShadowDarkAlpha');
1121      var lightAlpha = this.getOption('bottomShadowLightAlpha');
1122      var sDistribute = this.getOption('bottomShadowDistribute');
1123
1124      var darkShadowStart = t - (.025 - offset);
1125      var midShadowStart = darkShadowStart - (this.distributeOrLinear(t, sDistribute, .03) * sSize) - offset;
1126      var lightShadowStart = midShadowStart - ((.02 * sSize) - offset);
1127      stops = [
1128        getBlackStop(0, 0),
1129        getBlackStop(0, lightShadowStart),
1130        getBlackStop(lightAlpha, midShadowStart),
1131        getBlackStop(lightAlpha, darkShadowStart),
1132        getBlackStop(darkAlpha, t)
1133      ];
1134    }
1135    setBackgroundGradient(this.bottomShadowElement, this.peelLineRotation + 180, stops);
1136  }
1137
1138  /**
1139   * Whether a linear effect can be set.
1140   * @param {string} name Name of the effect
1141   * @param {number} t Current position of the linear effect line.
1142   * @returns {boolean}
1143   * @private
1144   */
1145  Peel.prototype.canSetLinearEffect = function(name, t) {
1146    return this.getOption(name) && t > 0;
1147  }
1148
1149  /**
1150   * Sets the fading effect of the top layer, if a threshold is set.
1151   * @private
1152   */
1153  Peel.prototype.setFade = function() {
1154    var threshold = this.fadeThreshold, opacity = 1, n;
1155    if (threshold) {
1156      if (this.timeAlongPath !== undefined) {
1157        n = this.timeAlongPath;
1158      } else {
1159        n = this.getAmountClipped();
1160      }
1161      if (n > threshold) {
1162        opacity = (1 - n) / (1 - threshold);
1163      }
1164      setOpacity(this.topLayer, opacity);
1165      setOpacity(this.backLayer, opacity);
1166      setOpacity(this.bottomShadowElement, opacity);
1167    }
1168  }
1169
1170  /**
1171   * Flips a point along an imaginary vertical midpoint.
1172   * @param {Array} points The points to be flipped.
1173   * @returns {Array}
1174   * @private
1175   */
1176  Peel.prototype.flipPointHorizontally = function(p) {
1177    return new Point(p.x - ((p.x - this.center.x) * 2), p.y);
1178  }
1179
1180  /**
1181   * Post setup initialization.
1182   * @private
1183   */
1184  Peel.prototype.init = function() {
1185    if (this.getOption('setPeelOnInit')) {
1186      this.setPeelPosition(this.corner);
1187    }
1188    addClass(this.el, prefix('ready'));
1189  }
1190
1191  /**
1192   * Class that clips an HTMLElement by an SVG path.
1193   * @param {HTMLElement} el The element to be clipped.
1194   * @param {Object} [shape] An object defining the SVG element to use in the new
1195   *     clip path. Defaults to a polygon.
1196   * @constructor
1197   */
1198  function SVGClip (el, shape) {
1199    this.el = el;
1200    this.shape = SVGClip.createClipPath(el, shape || {
1201      'type': 'polygon'
1202    });
1203    // Chrome needs this for some reason for the clipping to work.
1204    setTransform(this.el, 'translate(0px,0px)');
1205  }
1206
1207  /**
1208   * Sets up the global SVG element and its nested defs object to use for new
1209   * clip paths.
1210   * @returns {SVGElement}
1211   * @public
1212   */
1213  SVGClip.getDefs = function() {
1214    if (!this.defs) {
1215      this.svg  = createSVGElement('svg', null, {
1216        'class': prefix('svg-clip-element')
1217      });
1218      this.defs = createSVGElement('defs', this.svg);
1219    }
1220    return this.defs;
1221  }
1222
1223  /**
1224   * Creates a new <clipPath> SVG element and sets the passed html element to be
1225   * clipped by it.
1226   * @param {HTMLElement} el The html element to be clipped.
1227   * @param {Object} obj An object defining the SVG element to be used in the
1228   *     clip path.
1229   * @returns {SVGElement}
1230   * @public
1231   */
1232  SVGClip.createClipPath = function(el, obj) {
1233    var id = SVGClip.getId();
1234    var clipPath = createSVGElement('clipPath', this.getDefs());
1235    var svgEl = createSVGElement(obj.type, clipPath, obj.attributes);
1236    setSVGAttribute(clipPath, 'id', id);
1237    setCSSClip(el, 'url(#' + id + ')');
1238    return svgEl;
1239  }
1240
1241  /**
1242   * Gets the next svg clipping id.
1243   * @public
1244   */
1245  SVGClip.getId = function() {
1246    if (!SVGClip.id) {
1247      SVGClip.id = 1;
1248    }
1249    return 'svg-clip-' + SVGClip.id++;
1250  }
1251
1252  /**
1253   * Sets the "points" attribute of the clip path shape. This only makes sense
1254   * for polygon shapes.
1255   * @param {Array} points The points to be used.
1256   * @public
1257   */
1258  SVGClip.prototype.setPoints = function(points) {
1259    var str = points.map(function(p) {
1260      return round(p.x) + ',' + round(p.y);
1261    }).join(' ');
1262    setSVGAttribute(this.shape, 'points', str);
1263  }
1264
1265  /**
1266   * A class that represents a circle.
1267   * @param {Point} center The center point.
1268   * @param {Point} radius The radius.
1269   * @constructor
1270   */
1271  function Circle (center, radius) {
1272    this.center = center;
1273    this.radius = radius;
1274  }
1275
1276  /**
1277   * Determines whether a point is contained within the circle.
1278   * @param {Point} p The point.
1279   * @returns {boolean}
1280   * @public
1281   */
1282  Circle.prototype.containsPoint = function(p) {
1283    if(this.boundingRectContainsPoint(p)) {
1284        var dx = this.center.x - p.x;
1285        var dy = this.center.y - p.y;
1286        dx *= dx;
1287        dy *= dy;
1288        var distanceSquared = dx + dy;
1289        var radiusSquared = this.radius * this.radius;
1290        return distanceSquared <= radiusSquared;
1291    }
1292    return false;
1293  }
1294
1295  /**
1296   * Determines whether a point is contained within the bounding box of the circle.
1297   * @param {Point} p The point.
1298   * @returns {boolean}
1299   * @private
1300   */
1301  Circle.prototype.boundingRectContainsPoint = function(p) {
1302    return p.x >= this.center.x - this.radius && p.x <= this.center.x + this.radius &&
1303           p.y >= this.center.y - this.radius && p.y <= this.center.y + this.radius;
1304  }
1305
1306  /**
1307   * Moves a point outside the circle to the closest point on the circumference.
1308   * Rotated angle from the center point should be the same.
1309   * @param {Point} p The point.
1310   * @returns {boolean}
1311   * @public
1312   */
1313  Circle.prototype.constrainPoint = function(p) {
1314    if (!this.containsPoint(p)) {
1315      var rotation = p.subtract(this.center).getAngle();
1316      p = this.center.add(new Point(this.radius, 0).rotate(rotation));
1317    }
1318    return p;
1319  }
1320
1321  /**
1322   * A class that represents a polygon.
1323   * @constructor
1324   */
1325  function Polygon() {
1326    this.points = [];
1327  }
1328
1329  /**
1330   * Gets the area of the polygon.
1331   * @param {Array} points The points describing the polygon.
1332   * @public
1333   */
1334  Polygon.getArea = function(points) {
1335    var sum1 = 0, sum2 = 0;
1336    points.forEach(function(p, i, arr) {
1337      var next = arr[(i + 1) % arr.length];
1338      sum1 += (p.x * next.y);
1339      sum2 += (p.y * next.x);
1340    });
1341    return (sum1 - sum2) / 2;
1342  }
1343
1344  /**
1345   * Adds a point to the polygon.
1346   * @param {Point} point
1347   * @public
1348   */
1349  Polygon.prototype.addPoint = function(point) {
1350    this.points.push(point);
1351  }
1352
1353  /**
1354   * Gets the points of the polygon as an array.
1355   * @returns {Array}
1356   * @public
1357   */
1358  Polygon.prototype.getPoints = function() {
1359    return this.points;
1360  }
1361
1362
1363  /**
1364   * A class representing a bezier curve.
1365   * @param {Point} p1 The starting point.
1366   * @param {Point} c1 The control point of p1.
1367   * @param {Point} c2 The control point of p2.
1368   * @param {Point} p2 The ending point.
1369   * @constructor
1370   */
1371  function BezierCurve (p1, c1, c2, p2) {
1372    this.p1 = p1;
1373    this.c1 = c1;
1374    this.p2 = p2;
1375    this.c2 = c2;
1376  }
1377
1378  /**
1379   * Gets a point along the line segment for a given time.
1380   * @param {number} t The time along the segment, between 0 and 1.
1381   * @returns {Point}
1382   */
1383  BezierCurve.prototype.getPointForTime = function(t) {
1384    var b0 = Math.pow(1 - t, 3);
1385    var b1 = 3 * t * Math.pow(1 - t, 2);
1386    var b2 = 3 * Math.pow(t, 2) * (1 - t);
1387    var b3 = Math.pow(t, 3);
1388
1389    var x = (b0 * this.p1.x) + (b1 * this.c1.x) + (b2 * this.c2.x) + (b3 * this.p2.x)
1390    var y = (b0 * this.p1.y) + (b1 * this.c1.y) + (b2 * this.c2.y) + (b3 * this.p2.y)
1391    return new Point(x, y);
1392  }
1393
1394
1395  /**
1396   * A class that represents a line segment.
1397   * @param {Point} p1 The start of the segment.
1398   * @param {Point} p2 The end of the segment.
1399   * @constructor
1400   */
1401  function LineSegment (p1, p2) {
1402    this.p1 = p1;
1403    this.p2 = p2;
1404  }
1405
1406  /**
1407   * @constant
1408   */
1409  LineSegment.EPSILON = 1e-6;
1410
1411  /**
1412   * Gets a point along the line segment for a given time.
1413   * @param {number} t The time along the segment, between 0 and 1.
1414   * @returns {Point}
1415   * @public
1416   */
1417  LineSegment.prototype.getPointForTime = function(t) {
1418    return this.p1.add(this.getVector().scale(t));
1419  }
1420
1421  /**
1422   * Takes a scalar and returns a new scaled line segment.
1423   * @param {number} n The amount to scale the segment by.
1424   * @returns {LineSegment}
1425   * @public
1426   */
1427  LineSegment.prototype.scale = function(n) {
1428    var half = 1 + (n / 2);
1429    var p1 = this.p1.add(this.p2.subtract(this.p1).scale(n));
1430    var p2 = this.p2.add(this.p1.subtract(this.p2).scale(n));
1431    return new LineSegment(p1, p2);
1432  }
1433
1434  /**
1435   * The determinant is a number that indicates which side of a line a point
1436   * falls on. A positive number means that the point falls inside the area
1437   * "clockwise" of the line, ie. the area that the line would sweep if it were
1438   * rotated 180 degrees. A negative number would mean the point is in the area
1439   * the line would sweep if it were rotated counter-clockwise, or -180 degrees.
1440   * 0 indicates that the point falls exactly on the line.
1441   * @param {Point} p The point to test against.
1442   * @returns {number} A signed number.
1443   * @public
1444   */
1445  LineSegment.prototype.getPointDeterminant = function(p) {
1446    var d = ((p.x - this.p1.x) * (this.p2.y - this.p1.y)) - ((p.y - this.p1.y) * (this.p2.x - this.p1.x));
1447    // Tolerance for near-zero.
1448    if (d > -LineSegment.EPSILON && d < LineSegment.EPSILON) {
1449      d = 0;
1450    }
1451    return d;
1452  }
1453
1454  /**
1455   * Calculates the point at which another line segment intersects, if any.
1456   * @param {LineSegment} seg The second line segment.
1457   * @returns {Point|null}
1458   * @public
1459   */
1460  LineSegment.prototype.getIntersectPoint = function(seg2) {
1461    var seg1 = this;
1462
1463    function crossProduct(p1, p2) {
1464      return p1.x * p2.y - p1.y * p2.x;
1465    }
1466
1467    var r = seg1.p2.subtract(seg1.p1);
1468    var s = seg2.p2.subtract(seg2.p1);
1469
1470    var uNumerator = crossProduct(seg2.p1.subtract(seg1.p1), r);
1471    var denominator = crossProduct(r, s);
1472
1473    if (denominator == 0) {
1474      // ignoring colinear and parallel
1475      return null;
1476    }
1477
1478    var u = uNumerator / denominator;
1479    var t = crossProduct(seg2.p1.subtract(seg1.p1), s) / denominator;
1480
1481    if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) {
1482      return seg1.p1.add(r.scale(t));
1483    }
1484
1485    return null;
1486  }
1487
1488  /**
1489   * Returns the angle of the line segment in degrees.
1490   * @returns {number}
1491   * @public
1492   */
1493  LineSegment.prototype.getAngle = function() {
1494    return this.getVector().getAngle();
1495  }
1496
1497  /**
1498   * Gets the vector that represents the line segment.
1499   * @returns {Point}
1500   * @private
1501   */
1502  LineSegment.prototype.getVector = function() {
1503    if (!this.vector) {
1504      this.vector = this.p2.subtract(this.p1);
1505    }
1506    return this.vector;
1507  }
1508
1509  /**
1510   * A class representing a point or 2D vector.
1511   * @param {number} x The x coordinate.
1512   * @param {number} y The y coordinate.
1513   * @constructor
1514   */
1515  function Point (x, y) {
1516    this.x = x;
1517    this.y = y;
1518  }
1519
1520  /**
1521   * @constant
1522   */
1523  Point.DEGREES_IN_RADIANS = 180 / Math.PI;
1524
1525  /**
1526   * Gets degrees in radians.
1527   * @param {number} deg
1528   * @returns {number}
1529   */
1530  Point.degToRad = function(deg) {
1531    return deg / Point.DEGREES_IN_RADIANS;
1532  };
1533
1534  /**
1535   * Gets radians in degrees.
1536   * @param {number} rad
1537   * @returns {number}
1538   */
1539  Point.radToDeg = function(rad) {
1540    var deg = rad * Point.DEGREES_IN_RADIANS;
1541    while(deg < 0) deg += 360;
1542    return deg;
1543  };
1544
1545  /**
1546   * Creates a new point given a rotation in degrees and a length.
1547   * @param {number} deg The rotation of the vector.
1548   * @param {number} len The length of the vector.
1549   * @returns {Point}
1550   */
1551  Point.vector = function(deg, len) {
1552    var rad = Point.degToRad(deg);
1553    return new Point(Math.cos(rad) * len, Math.sin(rad) * len);
1554  };
1555
1556  /**
1557   * Adds a point.
1558   * @param {Point} p
1559   * @returns {Point}
1560   */
1561  Point.prototype.add = function(p) {
1562    return new Point(this.x + p.x, this.y + p.y);
1563  };
1564
1565  /**
1566   * Subtracts a point.
1567   * @param {Point} p
1568   * @returns {Point}
1569   */
1570  Point.prototype.subtract = function(p) {
1571    return new Point(this.x - p.x, this.y - p.y);
1572  };
1573
1574  /**
1575   * Scales a point by a scalar.
1576   * @param {number} n
1577   * @returns {Point}
1578   */
1579  Point.prototype.scale = function(n) {
1580    return new Point(this.x * n, this.y * n);
1581  };
1582
1583  /**
1584   * Gets the length of the distance to the point.
1585   * @returns {number}
1586   */
1587  Point.prototype.getLength = function() {
1588    return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
1589  };
1590
1591  /**
1592   * Gets the angle of the point in degrees.
1593   * @returns {number}
1594   */
1595  Point.prototype.getAngle = function() {
1596    return Point.radToDeg(Math.atan2(this.y, this.x));
1597  };
1598
1599  /**
1600   * Returns a new point of the same length with a different angle.
1601   * @param {number} deg The angle in degrees.
1602   * @returns {Point}
1603   */
1604  Point.prototype.setAngle = function(deg) {
1605    return Point.vector(deg, this.getLength());
1606  };
1607
1608  /**
1609   * Rotates the point.
1610   * @param {number} deg The amount to rotate by in degrees.
1611   * @returns {Point}
1612   */
1613  Point.prototype.rotate = function(deg) {
1614    return this.setAngle(this.getAngle() + deg);
1615  };
1616
1617  setCssProperties();
1618  win.Peel = Peel;
1619
1620})(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.