├── Hook.coffee
├── LICENSE
├── README.md
├── hook-example-gravity.framer
├── .gitignore
├── .preview.html
├── .viewer.html
├── app.coffee
├── framer
│ ├── coffee-script.js
│ ├── config.json
│ ├── framer.generated.js
│ ├── framer.init.js
│ ├── framer.js
│ ├── framer.js.map
│ ├── framer.modules.js
│ ├── images
│ │ ├── cursor-active.png
│ │ ├── cursor-active@2x.png
│ │ ├── cursor.png
│ │ ├── cursor@2x.png
│ │ ├── icon-120.png
│ │ ├── icon-152.png
│ │ ├── icon-180.png
│ │ ├── icon-192.png
│ │ └── icon-76.png
│ ├── manifest.txt
│ ├── metadata.json
│ ├── preview.png
│ ├── style.css
│ └── version
├── images
│ └── .gitkeep
├── index.html
└── modules
│ └── Hook.coffee
├── hook-example-modulator.framer
├── .gitignore
├── .viewer.html
├── app.coffee
├── framer
│ ├── coffee-script.js
│ ├── config.json
│ ├── framer.generated.js
│ ├── framer.init.js
│ ├── framer.js
│ ├── framer.js.map
│ ├── framer.modules.js
│ ├── images
│ │ ├── cursor-active.png
│ │ ├── cursor-active@2x.png
│ │ ├── cursor.png
│ │ ├── cursor@2x.png
│ │ ├── icon-120.png
│ │ ├── icon-152.png
│ │ ├── icon-180.png
│ │ ├── icon-192.png
│ │ └── icon-76.png
│ ├── manifest.txt
│ ├── preview.png
│ ├── style.css
│ └── version
├── images
│ ├── .gitkeep
│ └── spinner.svg
├── imported
│ └── app@2x
│ │ ├── images
│ │ └── Layer-page-meneqzew.png
│ │ ├── layers.json
│ │ └── layers.json.js
├── index.html
└── modules
│ └── Hook.coffee
└── hook-example-spring.framer
├── .gitignore
├── .viewer.html
├── app.coffee
├── framer
├── coffee-script.js
├── config.json
├── framer.generated.js
├── framer.init.js
├── framer.js
├── framer.js.map
├── framer.modules.js
├── images
│ ├── cursor-active.png
│ ├── cursor-active@2x.png
│ ├── cursor.png
│ ├── cursor@2x.png
│ ├── icon-120.png
│ ├── icon-152.png
│ ├── icon-180.png
│ ├── icon-192.png
│ └── icon-76.png
├── manifest.txt
├── metadata.json
├── preview.png
├── style.css
└── version
├── images
└── .gitkeep
├── index.html
├── modules
└── Hook.coffee
├── spring-example-720.gif
└── spring-example.gif
/Hook.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | --------------------------------------------------------------------------------
3 | Hook module for Framer
4 | --------------------------------------------------------------------------------
5 |
6 | by: Sigurd Mannsåker
7 | github: https://github.com/sigtm/framer-hook
8 |
9 | ················································································
10 |
11 |
12 | The Hook module simply expands the Layer prototype, and lets you make any
13 | numeric Layer property follow another property - either its own or another
14 | object's - via a spring or gravity attraction.
15 |
16 |
17 | --------------------------------------------------------------------------------
18 | Example: Layered animation (eased + spring)
19 | --------------------------------------------------------------------------------
20 |
21 | myLayer = new Layer
22 |
23 | # Make our own custom property for the x property to follow
24 | myLayer.easedX = 0
25 |
26 | # Hook x to easedX via a spring
27 | myLayer.hook
28 | property: "x"
29 | targetProperty: "easedX"
30 | type: "spring(150, 15)"
31 |
32 | # Animate easedX
33 | myLayer.animate
34 | properties:
35 | easedX: 200
36 | time: 0.15
37 | curve: "cubic-bezier(0.2, 0, 0.4, 1)"
38 |
39 | NOTE:
40 | To attach both the x and y position, use "pos", "midPos" or "maxPos" as the
41 | property/targetProperty.
42 |
43 |
44 | --------------------------------------------------------------------------------
45 | Example: Hooking property to another layer
46 | --------------------------------------------------------------------------------
47 |
48 | target = new Layer
49 | hooked = new Layer
50 |
51 | hooked.hook
52 | property: "scale"
53 | to: target
54 | type: "spring(150, 15)"
55 |
56 | The "hooked" layer's scale will now continuously follow the target layer's scale
57 | with a spring animation.
58 |
59 |
60 | --------------------------------------------------------------------------------
61 | layer.hook(options)
62 | --------------------------------------------------------------------------------
63 |
64 | Options are passed as a single object, like you would for a new Layer.
65 | The options object takes the following properties:
66 |
67 |
68 | property [String]
69 | -----------------
70 | The property you'd like to hook onto another object's property
71 |
72 |
73 | type [String]
74 | -------------
75 | Either "spring(strength, friction)" or "gravity(strength, drag)". Only the last
76 | specified drag value is used for each property, since it is only applied to
77 | each property once (and only if it has a gravity hook applied to it.)
78 |
79 |
80 | to [Object] (Optional)
81 | ----------------------
82 | The object to attach it to. Defaults to itself.
83 |
84 |
85 | targetProperty [String] (Optional)
86 | ----------------------------------
87 | Specify the target object's property to follow, if you don't want to follow
88 | the same property that the hook is applied to.
89 |
90 |
91 | modulator [Function] (Optional)
92 | -------------------------------
93 | The modulator function receives the target property's value, and lets you
94 | modify it before it is fed into the physics calculations. Useful for anything
95 | from standard Utils.modulate() type stuff to snapping and conditional values.
96 |
97 |
98 | zoom [Number] (Optional)
99 | ------------------------
100 | This factor defines the distance that 1px represents in regards to gravity and
101 | drag calculations. Only one value is stored per layer, so specifying it
102 | overwrites its existing value. Default is 100.
103 |
104 |
105 | --------------------------------------------------------------------------------
106 | layer.unHook(property, object)
107 | --------------------------------------------------------------------------------
108 |
109 | This removes all hooks for a given property and target object. Example:
110 |
111 | # Hook it
112 | layer.hook
113 | property: "x"
114 | to: "otherlayer"
115 | targetProperty: "y"
116 | type: "spring(200,20)"
117 |
118 | # Unhook it
119 | layer.unHook "x", otherlayer
120 |
121 |
122 | --------------------------------------------------------------------------------
123 | layer.onHookUpdate(delta)
124 | --------------------------------------------------------------------------------
125 |
126 | After a layer is done applying accelerations to its hooked properties, it calls
127 | onHookUpdate() at the end of each frame, if it is defined. This is an easy way
128 | to animate or trigger other stuff, perhaps based on your layer's updated
129 | properties or velocities.
130 |
131 | The delta value from the Framer loop is passed on to onHookUpdate() as well,
132 | which is the time in seconds since the last animation frame.
133 |
134 | Note that if you unhook all your hooks, onHookUpdate() will of course no longer
135 | be called for that layer.
136 |
137 | ###
138 |
139 |
140 | # Since older versions of Safari seem to be missing String.prototype.includes()
141 |
142 | unless String.prototype.includes
143 | String::includes = (search, start) ->
144 | 'use strict'
145 | start = 0 if typeof start is 'number'
146 |
147 | if start + search.length > this.length
148 | return false;
149 | else
150 | return @indexOf(search, start) isnt -1
151 |
152 | # Expand layer
153 |
154 | Layer::hook = (config) ->
155 |
156 | throw new Error 'layer.hook() needs a property, a hook type and either a target object or target property to work' unless config.property and config.type and (config.to or config.targetProperty)
157 |
158 | # Single array for all hooks, as opposed to nested arrays per property, because performance
159 | @hooks ?=
160 | hooks: []
161 | velocities: {}
162 | defs:
163 | zoom: 100
164 | getDrag: (velocity, drag, zoom) =>
165 | velocity /= zoom
166 | # Dividing by 10 is unscientific, but it means a value of 2 equals roughly a 100g ball with 15cm radius in air
167 | drag = -(drag / 10) * velocity * velocity * velocity / Math.abs(velocity)
168 | if _.isNaN(drag) then return 0 else return drag
169 | getGravity: (strength, distance, zoom) =>
170 | dist = Math.max(1, distance / zoom)
171 | return strength * zoom / (dist * dist)
172 |
173 | # Update the zoom value if given
174 | @hooks.zoom = config.zoom if config.zoom
175 |
176 | # Parse physics config string
177 | f = Utils.parseFunction config.type
178 | config.type = f.name
179 | config.strength = f.args[0]
180 | config.friction = f.args[1] or 0
181 |
182 | # Default to same targetProperty on same object (hopefully you've set at least one of these to something else)
183 | config.targetProperty ?= config.property
184 | config.to ?= @
185 |
186 | # All position accelerations are added to a single 'pos' velocity. Store actual properties so we don't have to do it again every frame
187 |
188 | if config.property.toLowerCase().includes 'pos'
189 | config.prop = 'pos'
190 |
191 | if config.property.toLowerCase().includes 'mid'
192 | config.thisX = 'midX'
193 | config.thisY = 'midY'
194 |
195 | else if config.property.toLowerCase().includes 'max'
196 | config.thisX = 'maxX'
197 | config.thisY = 'maxY'
198 |
199 | else
200 | config.thisX = 'x'
201 | config.thisY = 'y'
202 |
203 | if config.targetProperty.toLowerCase().includes 'mid'
204 | config.toX = 'midX'
205 | config.toY = 'midY'
206 |
207 | else if config.targetProperty.toLowerCase().includes 'max'
208 | config.toX = 'maxX'
209 | config.toY = 'maxY'
210 | else
211 | config.toX = 'x'
212 | config.toY = 'y'
213 |
214 | else
215 | config.prop = config.property
216 |
217 | # Save hook to @hooks array
218 | @hooks.hooks.push(config)
219 |
220 | # Create velocity property if necessary
221 | @hooks.velocities[config.prop] ?= if config.prop is 'pos' then { x: 0, y: 0 } else 0
222 |
223 | # Use Framer's animation loop, slightly more robust than requestAnimationFrame directly
224 | # Save the returned AnimationLoop reference to make sure @hookLoop isn't added multiple times per layer
225 | @hooks.emitter ?= Framer.Loop.on('render', @hookLoop, this)
226 |
227 | Layer::unHook = (property, object) ->
228 |
229 | return unless @hooks
230 |
231 | prop = if property.toLowerCase().includes 'pos' then 'pos' else property
232 |
233 | # Remove all matches
234 | @hooks.hooks = @hooks.hooks.filter (hook) ->
235 | hook.to isnt object or hook.property isnt property
236 |
237 | # If there are no hooks left, shut it down
238 | if @hooks.hooks.length is 0
239 | delete @hooks
240 | Framer.Loop.removeListener 'render', @hookLoop
241 | return
242 |
243 | # Still here? Check if there are any remaining hooks affecting same velocity
244 | remaining = @hooks.hooks.filter (hook) ->
245 | prop is hook.prop
246 |
247 | # If not, delete velocity (otherwise it won't be reset if you make new hook for same property)
248 | delete @hooks.velocities[prop] if remaining.length is 0
249 |
250 | Layer::hookLoop = (delta) ->
251 |
252 | if @hooks
253 |
254 | # Multiple hooks can affect the same property. Add accelerations to temporary object so the property's velocity is the same for all calculations within the same animation frame
255 | acceleration = {}
256 |
257 | # Save drag for each property to this object, since only most recently specified value is used for each property
258 | drag = {}
259 |
260 | # Add accelerations
261 | for hook in @hooks.hooks
262 |
263 | if hook.prop is 'pos'
264 |
265 | acceleration.pos ?= { x: 0, y: 0 }
266 |
267 | target = { x: hook.to[hook.toX], y: hook.to[hook.toY] }
268 |
269 | target = hook.modulator(target) if hook.modulator
270 |
271 | vector =
272 | x: target.x - @[hook.thisX]
273 | y: target.y - @[hook.thisY]
274 |
275 | vLength = Math.sqrt((vector.x * vector.x) + (vector.y * vector.y))
276 |
277 | if hook.type is 'spring'
278 |
279 | damper =
280 | x: -hook.friction * @hooks.velocities.pos.x
281 | y: -hook.friction * @hooks.velocities.pos.y
282 |
283 | vector.x *= hook.strength
284 | vector.y *= hook.strength
285 |
286 | acceleration.pos.x += (vector.x + damper.x) * delta
287 | acceleration.pos.y += (vector.y + damper.y) * delta
288 |
289 | else if hook.type is 'gravity'
290 |
291 | drag.pos = hook.friction
292 |
293 | gravity = @hooks.defs.getGravity(hook.strength, vLength, @hooks.defs.zoom)
294 |
295 | vector.x *= gravity / vLength
296 | vector.y *= gravity / vLength
297 |
298 | acceleration.pos.x += vector.x * delta
299 | acceleration.pos.y += vector.y * delta
300 |
301 | else
302 |
303 | acceleration[hook.prop] ?= 0
304 |
305 | target = hook.to[hook.targetProperty]
306 |
307 | target = hook.modulator(target) if hook.modulator
308 |
309 | vector = target - @[hook.prop]
310 |
311 | if hook.type is 'spring'
312 |
313 | force = vector * hook.strength
314 | damper = -hook.friction * @hooks.velocities[hook.prop]
315 |
316 | acceleration[hook.prop] += (force + damper) * delta
317 |
318 |
319 | else if hook.type is 'gravity'
320 |
321 | drag[hook.prop] = hook.friction
322 |
323 | force = @hooks.defs.getGravity(hook.strength, vector, @hooks.defs.zoom)
324 |
325 | acceleration[hook.prop] += force * delta
326 |
327 |
328 | # Add velocities to properties. Doing this at the end in case there are multiple hooks affecting the same velocity
329 | for prop, velocity of @hooks.velocities
330 |
331 | if prop is 'pos'
332 |
333 | # Add drag, if it exists
334 | if drag.pos
335 | velocity.x += @hooks.defs.getDrag(velocity.x, drag.pos, @hooks.defs.zoom)
336 | velocity.y += @hooks.defs.getDrag(velocity.y, drag.pos, @hooks.defs.zoom)
337 |
338 | # Add acceleration to velocity
339 | velocity.x += acceleration.pos.x
340 | velocity.y += acceleration.pos.y
341 |
342 | # Add velocity to position
343 | @x += velocity.x * delta
344 | @y += velocity.y * delta
345 |
346 | else
347 |
348 | # Add drag, if it exists
349 | if drag[prop]
350 | @hooks.velocities[prop] += @hooks.defs.getDrag(@hooks.velocities[prop], drag[prop], @hooks.defs.zoom)
351 |
352 | # Add acceleration to velocity
353 | @hooks.velocities[prop] += acceleration[prop]
354 |
355 | # Add velocity to property
356 | @[prop] += @hooks.velocities[prop] * delta
357 |
358 | @onHookUpdate?(delta)
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 sigtm
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Hook module for Framer
2 | 
3 |
4 | The Hook module expands Framer's Layer prototype, and lets you make any numeric Layer property follow another property - either its own or another object's - via a spring or gravity attraction.
5 |
6 | Enough chat. Examples:
7 |
8 | [Example 1: Easing + spring](http://www.sigurd.io/framer-hook/hook-example-spring.framer/)
9 |
10 | [Example 2: Modulation from one property type to another](http://www.sigurd.io/framer-hook/hook-example-modulator.framer/)
11 |
12 | [Example 3: Gravity, too](http://www.sigurd.io/framer-hook/hook-example-gravity.framer/)
13 |
14 | The original use case was to layer a spring animation on top of an eased animation to give more control over the timing and feel of a transition, as seen in Example 1. You do not need two layers to achieve this though, as shown in the first code snippet below.
15 |
16 | I'm not a developer nor a mathematician, so much of this is improvised, particularly the physics. Please do let me know, or create a pull request, if you have any suggestions for improvements.
17 |
18 | For a more detailed documentation, check the comments at the top of Hook.coffee.
19 |
20 |
21 | ### Example: Layered animation (eased + spring)
22 |
23 | ```
24 | myLayer = new Layer
25 |
26 | # Make our own custom property for the x property to follow
27 | myLayer.easedX = 0
28 |
29 | # Hook x to easedX via a spring
30 | myLayer.hook
31 | property: "x"
32 | targetProperty: "easedX"
33 | type: "spring(150, 15)"
34 |
35 | # Animate easedX
36 | myLayer.animate
37 | properties:
38 | easedX: 200
39 | time: 0.15
40 | curve: "cubic-bezier(0.2, 0, 0.4, 1)"
41 | ```
42 |
43 | NOTE:
44 | To attach both the x and y position, use "pos", "midPos" or "maxPos" as the
45 | property/targetProperty.
46 |
47 |
48 | ### Example: Hooking property to another layer
49 |
50 | ```
51 | target = new Layer
52 | hooked = new Layer
53 |
54 | hooked.hook
55 | property: "scale"
56 | to: target
57 | type: "spring(150, 15)"
58 | ```
59 |
60 | The "hooked" layer's scale will now continuously follow the target layer's scale
61 | with a spring animation.
62 |
63 |
64 | ### Example: Adding a modulator function
65 |
66 | The modulator function allows you to do anything you want with the target property's value before it is applied. As a very basic example, let's say you want to convert one layer's y position into a corresponding scale value for another layer:
67 |
68 | ```
69 | target = new Layer
70 | hooked = new Layer
71 |
72 | hooked.hook
73 | property: "scale"
74 | to: target
75 | targetProperty: "y"
76 | type: "spring(200,20)"
77 | modulator: (input) -> Utils.modulate(input, [0, 400], [0.5, 1])
78 | ```
79 |
80 | ### Documentation
81 |
82 | A more thorough documentation is included in the comments at the top of Hook.coffee.
--------------------------------------------------------------------------------
/hook-example-gravity.framer/.gitignore:
--------------------------------------------------------------------------------
1 | # Framer Git Ignore
2 |
3 | # General OSX
4 |
5 | .DS_Store
6 | .AppleDouble
7 | .LSOverride
8 |
9 | # Icon must end with two \r
10 | Icon
11 |
12 |
13 | # Thumbnails
14 | ._*
15 |
16 | # Files that might appear in the root of a volume
17 | .DocumentRevisions-V100
18 | .fseventsd
19 | .Spotlight-V100
20 | .TemporaryItems
21 | .Trashes
22 | .VolumeIcon.icns
23 |
24 | # Directories potentially created on remote AFP share
25 | .AppleDB
26 | .AppleDesktop
27 | Network Trash Folder
28 | Temporary Items
29 | .apdisk
30 |
31 |
32 | # Framer Specific
33 | .temp.html
34 | framer/*.old*
35 | framer/backup.coffee
36 | framer/backups/*
37 | framer/.*.hash
38 |
--------------------------------------------------------------------------------
/hook-example-gravity.framer/.preview.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
"
39 | html += "Error: Not A WebKit Browser"
40 | html += "Your browser is not supported. Please use Safari or Chrome. "
41 | html += "Try anyway"
42 | html += "
"
43 |
44 | showAlert(html)
45 | }
46 |
47 | function showFileLoadingAlert() {
48 | var html = ""
49 | html += "
"
50 | html += "Error: Local File Restrictions"
51 | html += "Preview this prototype with Framer Mirror or learn more about "
52 | html += "file restrictions. "
53 | html += "Try anyway"
54 | html += "
"
55 |
56 | showAlert(html)
57 | }
58 |
59 | function loadProject() {
60 | CoffeeScript.load("app.coffee")
61 | }
62 |
63 | function setDefaultPageTitle() {
64 | // If no title was set we set it to the project folder name so
65 | // you get a nice name on iOS if you bookmark to desktop.
66 | document.addEventListener("DOMContentLoaded", function() {
67 | if (document.title == "") {
68 | if (window.FramerStudioInfo && window.FramerStudioInfo.documentTitle) {
69 | document.title = window.FramerStudioInfo.documentTitle
70 | } else {
71 | document.title = window.location.pathname.replace(/\//g, "")
72 | }
73 | }
74 | })
75 | }
76 |
77 | function init() {
78 |
79 | if (Utils.isFramerStudio()) {
80 | return
81 | }
82 |
83 | setDefaultPageTitle()
84 |
85 | if (!isCompatibleBrowser()) {
86 | return showBrowserAlert()
87 | }
88 |
89 | if (!isFileLoadingAllowed()) {
90 | return showFileLoadingAlert()
91 | }
92 |
93 | loadProject()
94 |
95 | }
96 |
97 | init()
98 |
99 | })()
100 |
--------------------------------------------------------------------------------
/hook-example-gravity.framer/framer/framer.modules.js:
--------------------------------------------------------------------------------
1 | require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o this.length) {
146 | return false;
147 | } else {
148 | return this.indexOf(search, start) !== -1;
149 | }
150 | };
151 | }
152 |
153 | Layer.prototype.hook = function(config) {
154 | var base, base1, f, name;
155 | if (!(config.property && config.type && (config.to || config.targetProperty))) {
156 | throw new Error('layer.hook() needs a property, a hook type and either a target object or target property to work');
157 | }
158 | if (this.hooks == null) {
159 | this.hooks = {
160 | hooks: [],
161 | velocities: {},
162 | defs: {
163 | zoom: 100,
164 | getDrag: (function(_this) {
165 | return function(velocity, drag, zoom) {
166 | velocity /= zoom;
167 | drag = -(drag / 10) * velocity * velocity * velocity / Math.abs(velocity);
168 | if (_.isNaN(drag)) {
169 | return 0;
170 | } else {
171 | return drag;
172 | }
173 | };
174 | })(this),
175 | getGravity: (function(_this) {
176 | return function(strength, distance, zoom) {
177 | var dist;
178 | dist = Math.max(1, distance / zoom);
179 | return strength * zoom / (dist * dist);
180 | };
181 | })(this)
182 | }
183 | };
184 | }
185 | if (config.zoom) {
186 | this.hooks.zoom = config.zoom;
187 | }
188 | f = Utils.parseFunction(config.type);
189 | config.type = f.name;
190 | config.strength = f.args[0];
191 | config.friction = f.args[1] || 0;
192 | if (config.targetProperty == null) {
193 | config.targetProperty = config.property;
194 | }
195 | if (config.to == null) {
196 | config.to = this;
197 | }
198 | if (config.property.toLowerCase().includes('pos')) {
199 | config.prop = 'pos';
200 | if (config.property.toLowerCase().includes('mid')) {
201 | config.thisX = 'midX';
202 | config.thisY = 'midY';
203 | } else if (config.property.toLowerCase().includes('max')) {
204 | config.thisX = 'maxX';
205 | config.thisY = 'maxY';
206 | } else {
207 | config.thisX = 'x';
208 | config.thisY = 'y';
209 | }
210 | if (config.targetProperty.toLowerCase().includes('mid')) {
211 | config.toX = 'midX';
212 | config.toY = 'midY';
213 | } else if (config.targetProperty.toLowerCase().includes('max')) {
214 | config.toX = 'maxX';
215 | config.toY = 'maxY';
216 | } else {
217 | config.toX = 'x';
218 | config.toY = 'y';
219 | }
220 | } else {
221 | config.prop = config.property;
222 | }
223 | this.hooks.hooks.push(config);
224 | if ((base = this.hooks.velocities)[name = config.prop] == null) {
225 | base[name] = config.prop === 'pos' ? {
226 | x: 0,
227 | y: 0
228 | } : 0;
229 | }
230 | return (base1 = this.hooks).emitter != null ? base1.emitter : base1.emitter = Framer.Loop.on('render', this.hookLoop, this);
231 | };
232 |
233 | Layer.prototype.unHook = function(property, object) {
234 | var prop, remaining;
235 | if (!this.hooks) {
236 | return;
237 | }
238 | prop = property.toLowerCase().includes('pos') ? 'pos' : property;
239 | this.hooks.hooks = this.hooks.hooks.filter(function(hook) {
240 | return hook.to !== object || hook.property !== property;
241 | });
242 | if (this.hooks.hooks.length === 0) {
243 | delete this.hooks;
244 | Framer.Loop.removeListener('render', this.hookLoop);
245 | return;
246 | }
247 | remaining = this.hooks.hooks.filter(function(hook) {
248 | return prop === hook.prop;
249 | });
250 | if (remaining.length === 0) {
251 | return delete this.hooks.velocities[prop];
252 | }
253 | };
254 |
255 | Layer.prototype.hookLoop = function(delta) {
256 | var acceleration, damper, drag, force, gravity, hook, i, len, name, prop, ref, ref1, target, vLength, vector, velocity;
257 | if (this.hooks) {
258 | acceleration = {};
259 | drag = {};
260 | ref = this.hooks.hooks;
261 | for (i = 0, len = ref.length; i < len; i++) {
262 | hook = ref[i];
263 | if (hook.prop === 'pos') {
264 | if (acceleration.pos == null) {
265 | acceleration.pos = {
266 | x: 0,
267 | y: 0
268 | };
269 | }
270 | target = {
271 | x: hook.to[hook.toX],
272 | y: hook.to[hook.toY]
273 | };
274 | if (hook.modulator) {
275 | target = hook.modulator(target);
276 | }
277 | vector = {
278 | x: target.x - this[hook.thisX],
279 | y: target.y - this[hook.thisY]
280 | };
281 | vLength = Math.sqrt((vector.x * vector.x) + (vector.y * vector.y));
282 | if (hook.type === 'spring') {
283 | damper = {
284 | x: -hook.friction * this.hooks.velocities.pos.x,
285 | y: -hook.friction * this.hooks.velocities.pos.y
286 | };
287 | vector.x *= hook.strength;
288 | vector.y *= hook.strength;
289 | acceleration.pos.x += (vector.x + damper.x) * delta;
290 | acceleration.pos.y += (vector.y + damper.y) * delta;
291 | } else if (hook.type === 'gravity') {
292 | drag.pos = hook.friction;
293 | gravity = this.hooks.defs.getGravity(hook.strength, vLength, this.hooks.defs.zoom);
294 | vector.x *= gravity / vLength;
295 | vector.y *= gravity / vLength;
296 | acceleration.pos.x += vector.x * delta;
297 | acceleration.pos.y += vector.y * delta;
298 | }
299 | } else {
300 | if (acceleration[name = hook.prop] == null) {
301 | acceleration[name] = 0;
302 | }
303 | target = hook.to[hook.targetProperty];
304 | if (hook.modulator) {
305 | target = hook.modulator(target);
306 | }
307 | vector = target - this[hook.prop];
308 | if (hook.type === 'spring') {
309 | force = vector * hook.strength;
310 | damper = -hook.friction * this.hooks.velocities[hook.prop];
311 | acceleration[hook.prop] += (force + damper) * delta;
312 | } else if (hook.type === 'gravity') {
313 | drag[hook.prop] = hook.friction;
314 | force = this.hooks.defs.getGravity(hook.strength, vector, this.hooks.defs.zoom);
315 | acceleration[hook.prop] += force * delta;
316 | }
317 | }
318 | }
319 | ref1 = this.hooks.velocities;
320 | for (prop in ref1) {
321 | velocity = ref1[prop];
322 | if (prop === 'pos') {
323 | if (drag.pos) {
324 | velocity.x += this.hooks.defs.getDrag(velocity.x, drag.pos, this.hooks.defs.zoom);
325 | velocity.y += this.hooks.defs.getDrag(velocity.y, drag.pos, this.hooks.defs.zoom);
326 | }
327 | velocity.x += acceleration.pos.x;
328 | velocity.y += acceleration.pos.y;
329 | this.x += velocity.x * delta;
330 | this.y += velocity.y * delta;
331 | } else {
332 | if (drag[prop]) {
333 | this.hooks.velocities[prop] += this.hooks.defs.getDrag(this.hooks.velocities[prop], drag[prop], this.hooks.defs.zoom);
334 | }
335 | this.hooks.velocities[prop] += acceleration[prop];
336 | this[prop] += this.hooks.velocities[prop] * delta;
337 | }
338 | }
339 | return typeof this.onHookUpdate === "function" ? this.onHookUpdate(delta) : void 0;
340 | }
341 | };
342 |
343 |
344 | },{}]},{},[])
345 | //# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCIvVXNlcnMvc2lndXJkL1JlcG9zL2ZyYW1lci1ob29rL2hvb2stZXhhbXBsZS1ncmF2aXR5LmZyYW1lci9tb2R1bGVzL0hvb2suY29mZmVlIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztBQ0FBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBNklBLElBQUEsQ0FBTyxNQUFNLENBQUMsU0FBUyxDQUFDLFFBQXhCO0VBQ0MsTUFBTSxDQUFBLFNBQUUsQ0FBQSxRQUFSLEdBQW1CLFNBQUMsTUFBRCxFQUFTLEtBQVQ7SUFDbEI7SUFDQSxJQUFhLE9BQU8sS0FBUCxLQUFnQixRQUE3QjtNQUFBLEtBQUEsR0FBUSxFQUFSOztJQUVBLElBQUcsS0FBQSxHQUFRLE1BQU0sQ0FBQyxNQUFmLEdBQXdCLElBQUksQ0FBQyxNQUFoQztBQUNDLGFBQU8sTUFEUjtLQUFBLE1BQUE7QUFHQyxhQUFPLElBQUMsQ0FBQSxPQUFELENBQVMsTUFBVCxFQUFpQixLQUFqQixDQUFBLEtBQTZCLENBQUMsRUFIdEM7O0VBSmtCLEVBRHBCOzs7QUFZQSxLQUFLLENBQUEsU0FBRSxDQUFBLElBQVAsR0FBYyxTQUFDLE1BQUQ7QUFFYixNQUFBO0VBQUEsSUFBQSxDQUFBLENBQTBILE1BQU0sQ0FBQyxRQUFQLElBQW9CLE1BQU0sQ0FBQyxJQUEzQixJQUFvQyxDQUFDLE1BQU0sQ0FBQyxFQUFQLElBQWEsTUFBTSxDQUFDLGNBQXJCLENBQTlKLENBQUE7QUFBQSxVQUFVLElBQUEsS0FBQSxDQUFNLGtHQUFOLEVBQVY7OztJQUdBLElBQUMsQ0FBQSxRQUNBO01BQUEsS0FB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QUssQ0FBQyxJQUFJLENBQUMsSUFBdEQsRUFGZjs7UUFLQSxRQUFRLENBQUMsQ0FBVCxJQUFjLFlBQVksQ0FBQyxHQUFHLENBQUM7UUFDL0IsUUFBUSxDQUFDLENBQVQsSUFBYyxZQUFZLENBQUMsR0FBRyxDQUFDO1FBRy9CLElBQUMsQ0FBQSxDQUFELElBQU0sUUFBUSxDQUFDLENBQVQsR0FBYTtRQUNuQixJQUFDLENBQUEsQ0FBRCxJQUFNLFFBQVEsQ0FBQyxDQUFULEdBQWEsTUFicEI7T0FBQSxNQUFBO1FBa0JDLElBQUcsSUFBSyxDQUFBLElBQUEsQ0FBUjtVQUNDLElBQUMsQ0FBQSxLQUFLLENBQUMsVUFBVyxDQUFBLElBQUEsQ0FBbEIsSUFBMkIsSUFBQyxDQUFBLEtBQUssQ0FBQyxJQUFJLENBQUMsT0FBWixDQUFvQixJQUFDLENBQUEsS0FBSyxDQUFDLFVBQVcsQ0FBQSxJQUFBLENBQXRDLEVBQTZDLElBQUssQ0FBQSxJQUFBLENBQWxELEVBQXlELElBQUMsQ0FBQSxLQUFLLENBQUMsSUFBSSxDQUFDLElBQXJFLEVBRDVCOztRQUlBLElBQUMsQ0FBQSxLQUFLLENBQUMsVUFBVyxDQUFBLElBQUEsQ0FBbEIsSUFBMkIsWUFBYSxDQUFBLElBQUE7UUFHeEMsSUFBRSxDQUFBLElBQUEsQ0FBRixJQUFXLElBQUMsQ0FBQSxLQUFLLENBQUMsVUFBVyxDQUFBLElBQUEsQ0FBbEIsR0FBMEIsTUF6QnRDOztBQUZEO3FEQTZCQSxJQUFDLENBQUEsYUFBYyxnQkExR2hCOztBQUZpQiIsImZpbGUiOiJnZW5lcmF0ZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlc0NvbnRlbnQiOlsiKGZ1bmN0aW9uIGUodCxuLHIpe2Z1bmN0aW9uIHMobyx1KXtpZighbltvXSl7aWYoIXRbb10pe3ZhciBhPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7aWYoIXUmJmEpcmV0dXJuIGEobywhMCk7aWYoaSlyZXR1cm4gaShvLCEwKTt2YXIgZj1uZXcgRXJyb3IoXCJDYW5ub3QgZmluZCBtb2R1bGUgJ1wiK28rXCInXCIpO3Rocm93IGYuY29kZT1cIk1PRFVMRV9OT1RfRk9VTkRcIixmfXZhciBsPW5bb109e2V4cG9ydHM6e319O3Rbb11bMF0uY2FsbChsLmV4cG9ydHMsZnVuY3Rpb24oZSl7dmFyIG49dFtvXVsxXVtlXTtyZXR1cm4gcyhuP246ZSl9LGwsbC5leHBvcnRzLGUsdCxuLHIpfXJldHVybiBuW29dLmV4cG9ydHN9dmFyIGk9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtmb3IodmFyIG89MDtvPHIubGVuZ3RoO28rKylzKHJbb10pO3JldHVybiBzfSkiLCIjIyNcbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5Ib29rIG1vZHVsZSBmb3IgRnJhbWVyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuXG5ieTogICAgICBTaWd1cmQgTWFubnPDpWtlclxuZ2l0aHViOiAgaHR0cHM6Ly9naXRodWIuY29tL3NpZ3RtL2ZyYW1lci1ob29rXG5cbsK3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrdcblxuXG5UaGUgSG9vayBtb2R1bGUgc2ltcGx5IGV4cGFuZHMgdGhlIExheWVyIHByb3RvdHlwZSwgYW5kIGxldHMgeW91IG1ha2UgYW55XG5udW1lcmljIExheWVyIHByb3BlcnR5IGZvbGxvdyBhbm90aGVyIHByb3BlcnR5IC0gZWl0aGVyIGl0cyBvd24gb3IgYW5vdGhlclxub2JqZWN0J3MgLSB2aWEgYSBzcHJpbmcgb3IgZ3Jhdml0eSBhdHRyYWN0aW9uLlxuXG5cbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5FeGFtcGxlOiBMYXllcmVkIGFuaW1hdGlvbiAoZWFzZWQgKyBzcHJpbmcpXG4tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuXG5teUxheWVyID0gbmV3IExheWVyXG5cbiMgTWFrZSBvdXIgb3duIGN1c3RvbSBwcm9wZXJ0eSBmb3IgdGhlIHggcHJvcGVydHkgdG8gZm9sbG93XG5teUxheWVyLmVhc2VkWCA9IDBcblxuIyBIb29rIHggdG8gZWFzZWRYIHZpYSBhIHNwcmluZ1xubXlMYXllci5ob29rXG5cdHByb3BlcnR5OiBcInhcIlxuXHR0YXJnZXRQcm9wZXJ0eTogXCJlYXNlZFhcIlxuXHR0eXBlOiBcInNwcmluZygxNTAsIDE1KVwiXG5cbiMgQW5pbWF0ZSBlYXNlZFhcbm15TGF5ZXIuYW5pbWF0ZVxuXHRwcm9wZXJ0aWVzOlxuXHRcdGVhc2VkWDogMjAwXG5cdHRpbWU6IDAuMTVcblx0Y3VydmU6IFwiY3ViaWMtYmV6aWVyKDAuMiwgMCwgMC40LCAxKVwiXG5cbk5PVEU6IFxuVG8gYXR0YWNoIGJvdGggdGhlIHggYW5kIHkgcG9zaXRpb24sIHVzZSBcInBvc1wiLCBcIm1pZFBvc1wiIG9yIFwibWF4UG9zXCIgYXMgdGhlXG5wcm9wZXJ0eS90YXJnZXRQcm9wZXJ0eS5cblxuXG4tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuRXhhbXBsZTogSG9va2luZyBwcm9wZXJ0eSB0byBhbm90aGVyIGxheWVyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuXG50YXJnZXQgPSBuZXcgTGF5ZXJcbmhvb2tlZCA9IG5ldyBMYXllclxuXG5ob29rZWQuaG9va1xuXHRwcm9wZXJ0eTogXCJzY2FsZVwiXG5cdHRvOiB0YXJnZXRcblx0dHlwZTogXCJzcHJpbmcoMTUwLCAxNSlcIlxuXG5UaGUgXCJob29rZWRcIiBsYXllcidzIHNjYWxlIHdpbGwgbm93IGNvbnRpbnVvdXNseSBmb2xsb3cgdGhlIHRhcmdldCBsYXllcidzIHNjYWxlXG53aXRoIGEgc3ByaW5nIGFuaW1hdGlvbi5cblxuXG4tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxubGF5ZXIuaG9vayhvcHRpb25zKVxuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuT3B0aW9ucyBhcmUgcGFzc2VkIGFzIGEgc2luZ2xlIG9iamVjdCwgbGlrZSB5b3Ugd291bGQgZm9yIGEgbmV3IExheWVyLlxuVGhlIG9wdGlvbnMgb2JqZWN0IHRha2VzIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcblxuXG5wcm9wZXJ0eSBbU3RyaW5nXVxuLS0tLS0tLS0tLS0tLS0tLS1cblRoZSBwcm9wZXJ0eSB5b3UnZCBsaWtlIHRvIGhvb2sgb250byBhbm90aGVyIG9iamVjdCdzIHByb3BlcnR5XG5cblxudHlwZSBbU3RyaW5nXVxuLS0tLS0tLS0tLS0tLVxuRWl0aGVyIFwic3ByaW5nKHN0cmVuZ3RoLCBmcmljdGlvbilcIiBvciBcImdyYXZpdHkoc3RyZW5ndGgsIGRyYWcpXCIuIE9ubHkgdGhlIGxhc3RcbnNwZWNpZmllZCBkcmFnIHZhbHVlIGlzIHVzZWQgZm9yIGVhY2ggcHJvcGVydHksIHNpbmNlIGl0IGlzIG9ubHkgYXBwbGllZCB0b1xuZWFjaCBwcm9wZXJ0eSBvbmNlIChhbmQgb25seSBpZiBpdCBoYXMgYSBncmF2aXR5IGhvb2sgYXBwbGllZCB0byBpdC4pXG5cblxudG8gW09iamVjdF0gKE9wdGlvbmFsKVxuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuVGhlIG9iamVjdCB0byBhdHRhY2ggaXQgdG8uIERlZmF1bHRzIHRvIGl0c2VsZi5cblxuXG50YXJnZXRQcm9wZXJ0eSBbU3RyaW5nXSAoT3B0aW9uYWwpXG4tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5TcGVjaWZ5IHRoZSB0YXJnZXQgb2JqZWN0J3MgcHJvcGVydHkgdG8gZm9sbG93LCBpZiB5b3UgZG9uJ3Qgd2FudCB0byBmb2xsb3dcbnRoZSBzYW1lIHByb3BlcnR5IHRoYXQgdGhlIGhvb2sgaXMgYXBwbGllZCB0by5cblxuXG5tb2R1bGF0b3IgW0Z1bmN0aW9uXSAoT3B0aW9uYWwpXG4tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5UaGUgbW9kdWxhdG9yIGZ1bmN0aW9uIHJlY2VpdmVzIHRoZSB0YXJnZXQgcHJvcGVydHkncyB2YWx1ZSwgYW5kIGxldHMgeW91XG5tb2RpZnkgaXQgYmVmb3JlIGl0IGlzIGZlZCBpbnRvIHRoZSBwaHlzaWNzIGNhbGN1bGF0aW9ucy4gVXNlZnVsIGZvciBhbnl0aGluZ1xuZnJvbSBzdGFuZGFyZCBVdGlscy5tb2R1bGF0ZSgpIHR5cGUgc3R1ZmYgdG8gc25hcHBpbmcgYW5kIGNvbmRpdGlvbmFsIHZhbHVlcy5cblxuXG56b29tIFtOdW1iZXJdIChPcHRpb25hbClcbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuVGhpcyBmYWN0b3IgZGVmaW5lcyB0aGUgZGlzdGFuY2UgdGhhdCAxcHggcmVwcmVzZW50cyBpbiByZWdhcmRzIHRvIGdyYXZpdHkgYW5kXG5kcmFnIGNhbGN1bGF0aW9ucy4gT25seSBvbmUgdmFsdWUgaXMgc3RvcmVkIHBlciBsYXllciwgc28gc3BlY2lmeWluZyBpdFxub3ZlcndyaXRlcyBpdHMgZXhpc3RpbmcgdmFsdWUuIERlZmF1bHQgaXMgMTAwLlxuXG5cbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5sYXllci51bkhvb2socHJvcGVydHksIG9iamVjdClcbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cblRoaXMgcmVtb3ZlcyBhbGwgaG9va3MgZm9yIGEgZ2l2ZW4gcHJvcGVydHkgYW5kIHRhcmdldCBvYmplY3QuIEV4YW1wbGU6XG5cbiMgSG9vayBpdFxubGF5ZXIuaG9va1xuXHRwcm9wZXJ0eTogXCJ4XCJcblx0dG86IFwib3RoZXJsYXllclwiXG5cdHRhcmdldFByb3BlcnR5OiBcInlcIlxuXHR0eXBlOiBcInNwcmluZygyMDAsMjApXCJcblxuIyBVbmhvb2sgaXRcbmxheWVyLnVuSG9vayBcInhcIiwgb3RoZXJsYXllclxuXG5cbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5sYXllci5vbkhvb2tVcGRhdGUoZGVsdGEpXG4tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuXG5BZnRlciBhIGxheWVyIGlzIGRvbmUgYXBwbHlpbmcgYWNjZWxlcmF0aW9ucyB0byBpdHMgaG9va2VkIHByb3BlcnRpZXMsIGl0IGNhbGxzXG5vbkhvb2tVcGRhdGUoKSBhdCB0aGUgZW5kIG9mIGVhY2ggZnJhbWUsIGlmIGl0IGlzIGRlZmluZWQuIFRoaXMgaXMgYW4gZWFzeSB3YXlcbnRvIGFuaW1hdGUgb3IgdHJpZ2dlciBvdGhlciBzdHVmZiwgcGVyaGFwcyBiYXNlZCBvbiB5b3VyIGxheWVyJ3MgdXBkYXRlZFxucHJvcGVydGllcyBvciB2ZWxvY2l0aWVzLlxuXG5UaGUgZGVsdGEgdmFsdWUgZnJvbSB0aGUgRnJhbWVyIGxvb3AgaXMgcGFzc2VkIG9uIHRvIG9uSG9va1VwZGF0ZSgpIGFzIHdlbGwsXG53aGljaCBpcyB0aGUgdGltZSBpbiBzZWNvbmRzIHNpbmNlIHRoZSBsYXN0IGFuaW1hdGlvbiBmcmFtZS5cblxuTm90ZSB0aGF0IGlmIHlvdSB1bmhvb2sgYWxsIHlvdXIgaG9va3MsIG9uSG9va1VwZGF0ZSgpIHdpbGwgb2YgY291cnNlIG5vIGxvbmdlclxuYmUgY2FsbGVkIGZvciB0aGF0IGxheWVyLlxuXG4jIyNcblxuXG4jIFNpbmNlIG9sZGVyIHZlcnNpb25zIG9mIFNhZmFyaSBzZWVtIHRvIGJlIG1pc3NpbmcgU3RyaW5nLnByb3RvdHlwZS5pbmNsdWRlcygpXG5cbnVubGVzcyBTdHJpbmcucHJvdG90eXBlLmluY2x1ZGVzXG5cdFN0cmluZzo6aW5jbHVkZXMgPSAoc2VhcmNoLCBzdGFydCkgLT5cblx0XHQndXNlIHN0cmljdCdcblx0XHRzdGFydCA9IDAgaWYgdHlwZW9mIHN0YXJ0IGlzICdudW1iZXInXG5cblx0XHRpZiBzdGFydCArIHNlYXJjaC5sZW5ndGggPiB0aGlzLmxlbmd0aFxuXHRcdFx0cmV0dXJuIGZhbHNlO1xuXHRcdGVsc2Vcblx0XHRcdHJldHVybiBAaW5kZXhPZihzZWFyY2gsIHN0YXJ0KSBpc250IC0xXG5cbiMgRXhwYW5kIGxheWVyXG5cbkxheWVyOjpob29rID0gKGNvbmZpZykgLT5cblxuXHR0aHJvdyBuZXcgRXJyb3IgJ2xheWVyLmhvb2soKSBuZWVkcyBhIHByb3BlcnR5LCBhIGhvb2sgdHlwZSBhbmQgZWl0aGVyIGEgdGFyZ2V0IG9iamVjdCBvciB0YXJnZXQgcHJvcGVydHkgdG8gd29yaycgdW5sZXNzIGNvbmZpZy5wcm9wZXJ0eSBhbmQgY29uZmlnLnR5cGUgYW5kIChjb25maWcudG8gb3IgY29uZmlnLnRhcmdldFByb3BlcnR5KVxuXG5cdCMgU2luZ2xlIGFycmF5IGZvciBhbGwgaG9va3MsIGFzIG9wcG9zZWQgdG8gbmVzdGVkIGFycmF5cyBwZXIgcHJvcGVydHksIGJlY2F1c2UgcGVyZm9ybWFuY2Vcblx0QGhvb2tzID89XG5cdFx0aG9va3M6IFtdXG5cdFx0dmVsb2NpdGllczoge31cblx0XHRkZWZzOlxuXHRcdFx0em9vbTogMTAwXG5cdFx0XHRnZXREcmFnOiAodmVsb2NpdHksIGRyYWcsIHpvb20pID0+XG5cdFx0XHRcdHZlbG9jaXR5IC89IHpvb21cblx0XHRcdFx0IyBEaXZpZGluZyBieSAxMCBpcyB1bnNjaWVudGlmaWMsIGJ1dCBpdCBtZWFucyBhIHZhbHVlIG9mIDIgZXF1YWxzIHJvdWdobHkgYSAxMDBnIGJhbGwgd2l0aCAxNWNtIHJhZGl1cyBpbiBhaXJcblx0XHRcdFx0ZHJhZyA9IC0oZHJhZyAvIDEwKSAqIHZlbG9jaXR5ICogdmVsb2NpdHkgKiB2ZWxvY2l0eSAvIE1hdGguYWJzKHZlbG9jaXR5KVxuXHRcdFx0XHRpZiBfLmlzTmFOKGRyYWcpIHRoZW4gcmV0dXJuIDAgZWxzZSByZXR1cm4gZHJhZ1xuXHRcdFx0Z2V0R3Jhdml0eTogKHN0cmVuZ3RoLCBkaXN0YW5jZSwgem9vbSkgPT5cblx0XHRcdFx0ZGlzdCA9IE1hdGgubWF4KDEsIGRpc3RhbmNlIC8gem9vbSlcblx0XHRcdFx0cmV0dXJuIHN0cmVuZ3RoICogem9vbSAvIChkaXN0ICogZGlzdClcblxuXHQjIFVwZGF0ZSB0aGUgem9vbSB2YWx1ZSBpZiBnaXZlblxuXHRAaG9va3Muem9vbSA9IGNvbmZpZy56b29tIGlmIGNvbmZpZy56b29tXG5cblx0IyBQYXJzZSBwaHlzaWNzIGNvbmZpZyBzdHJpbmdcblx0ZiA9IFV0aWxzLnBhcnNlRnVuY3Rpb24gY29uZmlnLnR5cGVcblx0Y29uZmlnLnR5cGUgPSBmLm5hbWVcblx0Y29uZmlnLnN0cmVuZ3RoID0gZi5hcmdzWzBdXG5cdGNvbmZpZy5mcmljdGlvbiA9IGYuYXJnc1sxXSBvciAwXG5cblx0IyBEZWZhdWx0IHRvIHNhbWUgdGFyZ2V0UHJvcGVydHkgb24gc2FtZSBvYmplY3QgKGhvcGVmdWxseSB5b3UndmUgc2V0IGF0IGxlYXN0IG9uZSBvZiB0aGVzZSB0byBzb21ldGhpbmcgZWxzZSlcblx0Y29uZmlnLnRhcmdldFByb3BlcnR5ID89IGNvbmZpZy5wcm9wZXJ0eVxuXHRjb25maWcudG8gPz0gQFxuXG5cdCMgQWxsIHBvc2l0aW9uIGFjY2VsZXJhdGlvbnMgYXJlIGFkZGVkIHRvIGEgc2luZ2xlICdwb3MnIHZlbG9jaXR5LiBTdG9yZSBhY3R1YWwgcHJvcGVydGllcyBzbyB3ZSBkb24ndCBoYXZlIHRvIGRvIGl0IGFnYWluIGV2ZXJ5IGZyYW1lXG5cblx0aWYgY29uZmlnLnByb3BlcnR5LnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMgJ3Bvcydcblx0XHRjb25maWcucHJvcCA9ICdwb3MnXG5cdFx0XG5cdFx0aWYgY29uZmlnLnByb3BlcnR5LnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMgJ21pZCdcblx0XHRcdGNvbmZpZy50aGlzWCA9ICdtaWRYJ1xuXHRcdFx0Y29uZmlnLnRoaXNZID0gJ21pZFknXG5cdFx0XG5cdFx0ZWxzZSBpZiBjb25maWcucHJvcGVydHkudG9Mb3dlckNhc2UoKS5pbmNsdWRlcyAnbWF4J1xuXHRcdFx0Y29uZmlnLnRoaXNYID0gJ21heFgnXG5cdFx0XHRjb25maWcudGhpc1kgPSAnbWF4WSdcblx0XHRcblx0XHRlbHNlXG5cdFx0XHRjb25maWcudGhpc1ggPSAneCdcblx0XHRcdGNvbmZpZy50aGlzWSA9ICd5J1xuXHRcdFxuXHRcdGlmIGNvbmZpZy50YXJnZXRQcm9wZXJ0eS50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzICdtaWQnXG5cdFx0XHRjb25maWcudG9YID0gJ21pZFgnXG5cdFx0XHRjb25maWcudG9ZID0gJ21pZFknXG5cdFx0XG5cdFx0ZWxzZSBpZiBjb25maWcudGFyZ2V0UHJvcGVydHkudG9Mb3dlckNhc2UoKS5pbmNsdWRlcyAnbWF4J1xuXHRcdFx0Y29uZmlnLnRvWCA9ICdtYXhYJ1xuXHRcdFx0Y29uZmlnLnRvWSA9ICdtYXhZJ1x0XHRcblx0XHRlbHNlXG5cdFx0XHRjb25maWcudG9YID0gJ3gnXG5cdFx0XHRjb25maWcudG9ZID0gJ3knXG5cdFx0XG5cdGVsc2Vcblx0XHRjb25maWcucHJvcCA9IGNvbmZpZy5wcm9wZXJ0eVxuXG5cdCMgU2F2ZSBob29rIHRvIEBob29rcyBhcnJheVx0XG5cdEBob29rcy5ob29rcy5wdXNoKGNvbmZpZylcblxuXHQjIENyZWF0ZSB2ZWxvY2l0eSBwcm9wZXJ0eSBpZiBuZWNlc3Nhcnlcblx0QGhvb2tzLnZlbG9jaXRpZXNbY29uZmlnLnByb3BdID89IGlmIGNvbmZpZy5wcm9wIGlzICdwb3MnIHRoZW4geyB4OiAwLCB5OiAwIH0gZWxzZSAwXG5cblx0IyBVc2UgRnJhbWVyJ3MgYW5pbWF0aW9uIGxvb3AsIHNsaWdodGx5IG1vcmUgcm9idXN0IHRoYW4gcmVxdWVzdEFuaW1hdGlvbkZyYW1lIGRpcmVjdGx5XG5cdCMgU2F2ZSB0aGUgcmV0dXJuZWQgQW5pbWF0aW9uTG9vcCByZWZlcmVuY2UgdG8gbWFrZSBzdXJlIEBob29rTG9vcCBpc24ndCBhZGRlZCBtdWx0aXBsZSB0aW1lcyBwZXIgbGF5ZXJcblx0QGhvb2tzLmVtaXR0ZXIgPz0gRnJhbWVyLkxvb3Aub24oJ3JlbmRlcicsIEBob29rTG9vcCwgdGhpcylcblxuTGF5ZXI6OnVuSG9vayA9IChwcm9wZXJ0eSwgb2JqZWN0KSAtPlxuXHRcblx0cmV0dXJuIHVubGVzcyBAaG9va3NcblxuXHRwcm9wID0gaWYgcHJvcGVydHkudG9Mb3dlckNhc2UoKS5pbmNsdWRlcyAncG9zJyB0aGVuICdwb3MnIGVsc2UgcHJvcGVydHlcblxuXHQjIFJlbW92ZSBhbGwgbWF0Y2hlc1xuXHRAaG9va3MuaG9va3MgPSBAaG9va3MuaG9va3MuZmlsdGVyIChob29rKSAtPlxuXHRcdGhvb2sudG8gaXNudCBvYmplY3Qgb3IgaG9vay5wcm9wZXJ0eSBpc250IHByb3BlcnR5XG5cblx0IyBJZiB0aGVyZSBhcmUgbm8gaG9va3MgbGVmdCwgc2h1dCBpdCBkb3duXG5cdGlmIEBob29rcy5ob29rcy5sZW5ndGggaXMgMFxuXHRcdGRlbGV0ZSBAaG9va3Ncblx0XHRGcmFtZXIuTG9vcC5yZW1vdmVMaXN0ZW5lciAncmVuZGVyJywgQGhvb2tMb29wXG5cdFx0cmV0dXJuXG5cblx0IyBTdGlsbCBoZXJlPyBDaGVjayBpZiB0aGVyZSBhcmUgYW55IHJlbWFpbmluZyBob29rcyBhZmZlY3Rpbmcgc2FtZSB2ZWxvY2l0eVxuXHRyZW1haW5pbmcgPSBAaG9va3MuaG9va3MuZmlsdGVyIChob29rKSAtPlxuXHRcdHByb3AgaXMgaG9vay5wcm9wXG5cdFx0XG5cdCMgSWYgbm90LCBkZWxldGUgdmVsb2NpdHkgKG90aGVyd2lzZSBpdCB3b24ndCBiZSByZXNldCBpZiB5b3UgbWFrZSBuZXcgaG9vayBmb3Igc2FtZSBwcm9wZXJ0eSlcblx0ZGVsZXRlIEBob29rcy52ZWxvY2l0aWVzW3Byb3BdIGlmIHJlbWFpbmluZy5sZW5ndGggaXMgMFxuXG5MYXllcjo6aG9va0xvb3AgPSAoZGVsdGEpIC0+XG5cblx0aWYgQGhvb2tzXG5cblx0XHQjIE11bHRpcGxlIGhvb2tzIGNhbiBhZmZlY3QgdGhlIHNhbWUgcHJvcGVydHkuIEFkZCBhY2NlbGVyYXRpb25zIHRvIHRlbXBvcmFyeSBvYmplY3Qgc28gdGhlIHByb3BlcnR5J3MgdmVsb2NpdHkgaXMgdGhlIHNhbWUgZm9yIGFsbCBjYWxjdWxhdGlvbnMgd2l0aGluIHRoZSBzYW1lIGFuaW1hdGlvbiBmcmFtZVxuXHRcdGFjY2VsZXJhdGlvbiA9IHt9XG5cdFx0XG5cdFx0IyBTYXZlIGRyYWcgZm9yIGVhY2ggcHJvcGVydHkgdG8gdGhpcyBvYmplY3QsIHNpbmNlIG9ubHkgbW9zdCByZWNlbnRseSBzcGVjaWZpZWQgdmFsdWUgaXMgdXNlZCBmb3IgZWFjaCBwcm9wZXJ0eVxuXHRcdGRyYWcgPSB7fVxuXHRcdFxuXHRcdCMgQWRkIGFjY2VsZXJhdGlvbnNcblx0XHRmb3IgaG9vayBpbiBAaG9va3MuaG9va3Ncblx0XHRcblx0XHRcdGlmIGhvb2sucHJvcCBpcyAncG9zJ1xuXG5cdFx0XHRcdGFjY2VsZXJhdGlvbi5wb3MgPz0geyB4OiAwLCB5OiAwIH1cblxuXHRcdFx0XHR0YXJnZXQgPSB7IHg6IGhvb2sudG9baG9vay50b1hdLCB5OiBob29rLnRvW2hvb2sudG9ZXSB9XG5cblx0XHRcdFx0dGFyZ2V0ID0gaG9vay5tb2R1bGF0b3IodGFyZ2V0KSBpZiBob29rLm1vZHVsYXRvclxuXG5cdFx0XHRcdHZlY3RvciA9XG5cdFx0XHRcdFx0eDogdGFyZ2V0LnggLSBAW2hvb2sudGhpc1hdXG5cdFx0XHRcdFx0eTogdGFyZ2V0LnkgLSBAW2hvb2sudGhpc1ldXG5cdFx0XHRcdFxuXHRcdFx0XHR2TGVuZ3RoID0gTWF0aC5zcXJ0KCh2ZWN0b3IueCAqIHZlY3Rvci54KSArICh2ZWN0b3IueSAqIHZlY3Rvci55KSlcblxuXHRcdFx0XHRpZiBob29rLnR5cGUgaXMgJ3NwcmluZydcblxuXHRcdFx0XHRcdGRhbXBlciA9XG5cdFx0XHRcdFx0XHR4OiAtaG9vay5mcmljdGlvbiAqIEBob29rcy52ZWxvY2l0aWVzLnBvcy54XG5cdFx0XHRcdFx0XHR5OiAtaG9vay5mcmljdGlvbiAqIEBob29rcy52ZWxvY2l0aWVzLnBvcy55XG5cblx0XHRcdFx0XHR2ZWN0b3IueCAqPSBob29rLnN0cmVuZ3RoXG5cdFx0XHRcdFx0dmVjdG9yLnkgKj0gaG9vay5zdHJlbmd0aFxuXHRcdFx0XHRcdFxuXHRcdFx0XHRcdGFjY2VsZXJhdGlvbi5wb3MueCArPSAodmVjdG9yLnggKyBkYW1wZXIueCkgKiBkZWx0YVxuXHRcdFx0XHRcdGFjY2VsZXJhdGlvbi5wb3MueSArPSAodmVjdG9yLnkgKyBkYW1wZXIueSkgKiBkZWx0YVxuXHRcdFx0XHRcblx0XHRcdFx0ZWxzZSBpZiBob29rLnR5cGUgaXMgJ2dyYXZpdHknXG5cdFx0XHRcdFxuXHRcdFx0XHRcdGRyYWcucG9zID0gaG9vay5mcmljdGlvblxuXHRcdFx0XHRcdFxuXHRcdFx0XHRcdGdyYXZpdHkgPSBAaG9va3MuZGVmcy5nZXRHcmF2aXR5KGhvb2suc3RyZW5ndGgsIHZMZW5ndGgsIEBob29rcy5kZWZzLnpvb20pXG5cblx0XHRcdFx0XHR2ZWN0b3IueCAqPSBncmF2aXR5IC8gdkxlbmd0aFxuXHRcdFx0XHRcdHZlY3Rvci55ICo9IGdyYXZpdHkgLyB2TGVuZ3RoXG5cdFx0XHRcdFx0XG5cdFx0XHRcdFx0YWNjZWxlcmF0aW9uLnBvcy54ICs9IHZlY3Rvci54ICogZGVsdGFcblx0XHRcdFx0XHRhY2NlbGVyYXRpb24ucG9zLnkgKz0gdmVjdG9yLnkgKiBkZWx0YVxuXHRcdFx0XHRcdFx0XHRcdFx0XG5cdFx0XHRlbHNlXG5cdFx0XHRcdFxuXHRcdFx0XHRhY2NlbGVyYXRpb25baG9vay5wcm9wXSA/PSAwXG5cblx0XHRcdFx0dGFyZ2V0ID0gaG9vay50b1tob29rLnRhcmdldFByb3BlcnR5XVxuXG5cdFx0XHRcdHRhcmdldCA9IGhvb2subW9kdWxhdG9yKHRhcmdldCkgaWYgaG9vay5tb2R1bGF0b3JcblxuXHRcdFx0XHR2ZWN0b3IgPSB0YXJnZXQgLSBAW2hvb2sucHJvcF1cblx0XHRcdFx0XG5cdFx0XHRcdGlmIGhvb2sudHlwZSBpcyAnc3ByaW5nJ1xuXG5cdFx0XHRcdFx0Zm9yY2UgPSB2ZWN0b3IgKiBob29rLnN0cmVuZ3RoXG5cdFx0XHRcdFx0ZGFtcGVyID0gLWhvb2suZnJpY3Rpb24gKiBAaG9va3MudmVsb2NpdGllc1tob29rLnByb3BdXG5cblx0XHRcdFx0XHRhY2NlbGVyYXRpb25baG9vay5wcm9wXSArPSAoZm9yY2UgKyBkYW1wZXIpICogZGVsdGFcblxuXHRcdFx0XHRcblx0XHRcdFx0ZWxzZSBpZiBob29rLnR5cGUgaXMgJ2dyYXZpdHknXG5cdFxuXHRcdFx0XHRcdGRyYWdbaG9vay5wcm9wXSA9IGhvb2suZnJpY3Rpb25cblx0XHRcdFx0XHRcblx0XHRcdFx0XHRmb3JjZSA9IEBob29rcy5kZWZzLmdldEdyYXZpdHkoaG9vay5zdHJlbmd0aCwgdmVjdG9yLCBAaG9va3MuZGVmcy56b29tKVxuXHRcdFx0XHRcdFxuXHRcdFx0XHRcdGFjY2VsZXJhdGlvbltob29rLnByb3BdICs9IGZvcmNlICogZGVsdGFcblx0XHRcblx0XHRcblx0XHQjIEFkZCB2ZWxvY2l0aWVzIHRvIHByb3BlcnRpZXMuIERvaW5nIHRoaXMgYXQgdGhlIGVuZCBpbiBjYXNlIHRoZXJlIGFyZSBtdWx0aXBsZSBob29rcyBhZmZlY3RpbmcgdGhlIHNhbWUgdmVsb2NpdHlcblx0XHRmb3IgcHJvcCwgdmVsb2NpdHkgb2YgQGhvb2tzLnZlbG9jaXRpZXNcblx0XHRcblx0XHRcdGlmIHByb3AgaXMgJ3BvcydcblxuXHRcdFx0XHQjIEFkZCBkcmFnLCBpZiBpdCBleGlzdHNcblx0XHRcdFx0aWYgZHJhZy5wb3Ncblx0XHRcdFx0XHR2ZWxvY2l0eS54ICs9IEBob29rcy5kZWZzLmdldERyYWcodmVsb2NpdHkueCwgZHJhZy5wb3MsIEBob29rcy5kZWZzLnpvb20pXG5cdFx0XHRcdFx0dmVsb2NpdHkueSArPSBAaG9va3MuZGVmcy5nZXREcmFnKHZlbG9jaXR5LnksIGRyYWcucG9zLCBAaG9va3MuZGVmcy56b29tKVxuXHRcdFx0XHRcdFxuXHRcdFx0XHQjIEFkZCBhY2NlbGVyYXRpb24gdG8gdmVsb2NpdHlcblx0XHRcdFx0dmVsb2NpdHkueCArPSBhY2NlbGVyYXRpb24ucG9zLnhcblx0XHRcdFx0dmVsb2NpdHkueSArPSBhY2NlbGVyYXRpb24ucG9zLnlcblx0XHRcdFx0XG5cdFx0XHRcdCMgQWRkIHZlbG9jaXR5IHRvIHBvc2l0aW9uXG5cdFx0XHRcdEB4ICs9IHZlbG9jaXR5LnggKiBkZWx0YVxuXHRcdFx0XHRAeSArPSB2ZWxvY2l0eS55ICogZGVsdGFcblx0XHRcdFxuXHRcdFx0ZWxzZVxuXHRcdFx0XG5cdFx0XHRcdCMgQWRkIGRyYWcsIGlmIGl0IGV4aXN0c1xuXHRcdFx0XHRpZiBkcmFnW3Byb3BdXG5cdFx0XHRcdFx0QGhvb2tzLnZlbG9jaXRpZXNbcHJvcF0gKz0gQGhvb2tzLmRlZnMuZ2V0RHJhZyhAaG9va3MudmVsb2NpdGllc1twcm9wXSwgZHJhZ1twcm9wXSwgQGhvb2tzLmRlZnMuem9vbSlcblx0XHRcdFx0XG5cdFx0XHRcdCMgQWRkIGFjY2VsZXJhdGlvbiB0byB2ZWxvY2l0eVxuXHRcdFx0XHRAaG9va3MudmVsb2NpdGllc1twcm9wXSArPSBhY2NlbGVyYXRpb25bcHJvcF1cblx0XHRcdFx0XG5cdFx0XHRcdCMgQWRkIHZlbG9jaXR5IHRvIHByb3BlcnR5XG5cdFx0XHRcdEBbcHJvcF0gKz0gQGhvb2tzLnZlbG9jaXRpZXNbcHJvcF0gKiBkZWx0YVxuXG5cdFx0QG9uSG9va1VwZGF0ZT8oZGVsdGEpIl19
346 |
--------------------------------------------------------------------------------
/hook-example-gravity.framer/framer/images/cursor-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-gravity.framer/framer/images/cursor-active.png
--------------------------------------------------------------------------------
/hook-example-gravity.framer/framer/images/cursor-active@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-gravity.framer/framer/images/cursor-active@2x.png
--------------------------------------------------------------------------------
/hook-example-gravity.framer/framer/images/cursor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-gravity.framer/framer/images/cursor.png
--------------------------------------------------------------------------------
/hook-example-gravity.framer/framer/images/cursor@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-gravity.framer/framer/images/cursor@2x.png
--------------------------------------------------------------------------------
/hook-example-gravity.framer/framer/images/icon-120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-gravity.framer/framer/images/icon-120.png
--------------------------------------------------------------------------------
/hook-example-gravity.framer/framer/images/icon-152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-gravity.framer/framer/images/icon-152.png
--------------------------------------------------------------------------------
/hook-example-gravity.framer/framer/images/icon-180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-gravity.framer/framer/images/icon-180.png
--------------------------------------------------------------------------------
/hook-example-gravity.framer/framer/images/icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-gravity.framer/framer/images/icon-192.png
--------------------------------------------------------------------------------
/hook-example-gravity.framer/framer/images/icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-gravity.framer/framer/images/icon-76.png
--------------------------------------------------------------------------------
/hook-example-gravity.framer/framer/manifest.txt:
--------------------------------------------------------------------------------
1 | app.coffee
2 | framer/coffee-script.js
3 | framer/config.json
4 | framer/framer.generated.js
5 | framer/framer.init.js
6 | framer/framer.js
7 | framer/framer.js.map
8 | framer/framer.modules.js
9 | framer/images/cursor-active.png
10 | framer/images/cursor-active@2x.png
11 | framer/images/cursor.png
12 | framer/images/cursor@2x.png
13 | framer/images/icon-120.png
14 | framer/images/icon-152.png
15 | framer/images/icon-180.png
16 | framer/images/icon-192.png
17 | framer/images/icon-76.png
18 | framer/manifest.txt
19 | framer/metadata.json
20 | framer/preview.png
21 | framer/style.css
22 | framer/version
23 | index.html
24 | modules/Hook.coffee
--------------------------------------------------------------------------------
/hook-example-gravity.framer/framer/metadata.json:
--------------------------------------------------------------------------------
1 | {"title":"hook-example-gravity","date":"2016-09-09T12:28:41Z"}
--------------------------------------------------------------------------------
/hook-example-gravity.framer/framer/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-gravity.framer/framer/preview.png
--------------------------------------------------------------------------------
/hook-example-gravity.framer/framer/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | border: none;
5 | -webkit-user-select: none;
6 | -webkit-tap-highlight-color: rgba(0,0,0,0);
7 | }
8 |
9 | body {
10 | background-color: #fff;
11 | font: 28px/1em "Helvetica";
12 | color: gray;
13 | overflow: hidden;
14 | }
15 |
16 | a {
17 | color: gray;
18 | }
19 |
20 | body {
21 | cursor: url('images/cursor.png') 32 32, auto;
22 | cursor: -webkit-image-set(
23 | url('images/cursor.png') 1x,
24 | url('images/cursor@2x.png') 2x
25 | ) 32 32, auto;
26 | }
27 |
28 | body:active {
29 | cursor: url('images/cursor-active.png') 32 32, auto;
30 | cursor: -webkit-image-set(
31 | url('images/cursor-active.png') 1x,
32 | url('images/cursor-active@2x.png') 2x
33 | ) 32 32, auto;
34 | }
35 |
36 | .framerAlertBackground {
37 | position: absolute; top:0px; left:0px; right:0px; bottom:0px;
38 | z-index: 1000;
39 | background-color: #fff;
40 | }
41 |
42 | .framerAlert {
43 | font:400 14px/1.4 "Helvetica Neue", Helvetica, Arial, sans-serif;
44 | -webkit-font-smoothing:antialiased;
45 | color:#616367; text-align:center;
46 | position: absolute; top:40%; left:50%; width:260px; margin-left:-130px;
47 | }
48 | .framerAlert strong { font-weight:500; color:#000; margin-bottom:8px; display:block; }
49 | .framerAlert a { color:#28AFFA; }
50 | .framerAlert .btn {
51 | font-weight:500; text-decoration:none; line-height:1;
52 | display:inline-block; padding:6px 12px 7px 12px;
53 | border-radius:3px; margin-top:12px;
54 | background:#28AFFA; color:#fff;
55 | }
56 |
57 | ::-webkit-scrollbar {
58 | display: none;
59 | }
--------------------------------------------------------------------------------
/hook-example-gravity.framer/framer/version:
--------------------------------------------------------------------------------
1 | 4
--------------------------------------------------------------------------------
/hook-example-gravity.framer/images/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-gravity.framer/images/.gitkeep
--------------------------------------------------------------------------------
/hook-example-gravity.framer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/hook-example-gravity.framer/modules/Hook.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | --------------------------------------------------------------------------------
3 | Hook module for Framer
4 | --------------------------------------------------------------------------------
5 |
6 | by: Sigurd Mannsåker
7 | github: https://github.com/sigtm/framer-hook
8 |
9 | ················································································
10 |
11 |
12 | The Hook module simply expands the Layer prototype, and lets you make any
13 | numeric Layer property follow another property - either its own or another
14 | object's - via a spring or gravity attraction.
15 |
16 |
17 | --------------------------------------------------------------------------------
18 | Example: Layered animation (eased + spring)
19 | --------------------------------------------------------------------------------
20 |
21 | myLayer = new Layer
22 |
23 | # Make our own custom property for the x property to follow
24 | myLayer.easedX = 0
25 |
26 | # Hook x to easedX via a spring
27 | myLayer.hook
28 | property: "x"
29 | targetProperty: "easedX"
30 | type: "spring(150, 15)"
31 |
32 | # Animate easedX
33 | myLayer.animate
34 | properties:
35 | easedX: 200
36 | time: 0.15
37 | curve: "cubic-bezier(0.2, 0, 0.4, 1)"
38 |
39 | NOTE:
40 | To attach both the x and y position, use "pos", "midPos" or "maxPos" as the
41 | property/targetProperty.
42 |
43 |
44 | --------------------------------------------------------------------------------
45 | Example: Hooking property to another layer
46 | --------------------------------------------------------------------------------
47 |
48 | target = new Layer
49 | hooked = new Layer
50 |
51 | hooked.hook
52 | property: "scale"
53 | to: target
54 | type: "spring(150, 15)"
55 |
56 | The "hooked" layer's scale will now continuously follow the target layer's scale
57 | with a spring animation.
58 |
59 |
60 | --------------------------------------------------------------------------------
61 | layer.hook(options)
62 | --------------------------------------------------------------------------------
63 |
64 | Options are passed as a single object, like you would for a new Layer.
65 | The options object takes the following properties:
66 |
67 |
68 | property [String]
69 | -----------------
70 | The property you'd like to hook onto another object's property
71 |
72 |
73 | type [String]
74 | -------------
75 | Either "spring(strength, friction)" or "gravity(strength, drag)". Only the last
76 | specified drag value is used for each property, since it is only applied to
77 | each property once (and only if it has a gravity hook applied to it.)
78 |
79 |
80 | to [Object] (Optional)
81 | ----------------------
82 | The object to attach it to. Defaults to itself.
83 |
84 |
85 | targetProperty [String] (Optional)
86 | ----------------------------------
87 | Specify the target object's property to follow, if you don't want to follow
88 | the same property that the hook is applied to.
89 |
90 |
91 | modulator [Function] (Optional)
92 | -------------------------------
93 | The modulator function receives the target property's value, and lets you
94 | modify it before it is fed into the physics calculations. Useful for anything
95 | from standard Utils.modulate() type stuff to snapping and conditional values.
96 |
97 |
98 | zoom [Number] (Optional)
99 | ------------------------
100 | This factor defines the distance that 1px represents in regards to gravity and
101 | drag calculations. Only one value is stored per layer, so specifying it
102 | overwrites its existing value. Default is 100.
103 |
104 |
105 | --------------------------------------------------------------------------------
106 | layer.unHook(property, object)
107 | --------------------------------------------------------------------------------
108 |
109 | This removes all hooks for a given property and target object. Example:
110 |
111 | # Hook it
112 | layer.hook
113 | property: "x"
114 | to: "otherlayer"
115 | targetProperty: "y"
116 | type: "spring(200,20)"
117 |
118 | # Unhook it
119 | layer.unHook "x", otherlayer
120 |
121 |
122 | --------------------------------------------------------------------------------
123 | layer.onHookUpdate(delta)
124 | --------------------------------------------------------------------------------
125 |
126 | After a layer is done applying accelerations to its hooked properties, it calls
127 | onHookUpdate() at the end of each frame, if it is defined. This is an easy way
128 | to animate or trigger other stuff, perhaps based on your layer's updated
129 | properties or velocities.
130 |
131 | The delta value from the Framer loop is passed on to onHookUpdate() as well,
132 | which is the time in seconds since the last animation frame.
133 |
134 | Note that if you unhook all your hooks, onHookUpdate() will of course no longer
135 | be called for that layer.
136 |
137 | ###
138 |
139 |
140 | # Since older versions of Safari seem to be missing String.prototype.includes()
141 |
142 | unless String.prototype.includes
143 | String::includes = (search, start) ->
144 | 'use strict'
145 | start = 0 if typeof start is 'number'
146 |
147 | if start + search.length > this.length
148 | return false;
149 | else
150 | return @indexOf(search, start) isnt -1
151 |
152 | # Expand layer
153 |
154 | Layer::hook = (config) ->
155 |
156 | throw new Error 'layer.hook() needs a property, a hook type and either a target object or target property to work' unless config.property and config.type and (config.to or config.targetProperty)
157 |
158 | # Single array for all hooks, as opposed to nested arrays per property, because performance
159 | @hooks ?=
160 | hooks: []
161 | velocities: {}
162 | defs:
163 | zoom: 100
164 | getDrag: (velocity, drag, zoom) =>
165 | velocity /= zoom
166 | # Dividing by 10 is unscientific, but it means a value of 2 equals roughly a 100g ball with 15cm radius in air
167 | drag = -(drag / 10) * velocity * velocity * velocity / Math.abs(velocity)
168 | if _.isNaN(drag) then return 0 else return drag
169 | getGravity: (strength, distance, zoom) =>
170 | dist = Math.max(1, distance / zoom)
171 | return strength * zoom / (dist * dist)
172 |
173 | # Update the zoom value if given
174 | @hooks.zoom = config.zoom if config.zoom
175 |
176 | # Parse physics config string
177 | f = Utils.parseFunction config.type
178 | config.type = f.name
179 | config.strength = f.args[0]
180 | config.friction = f.args[1] or 0
181 |
182 | # Default to same targetProperty on same object (hopefully you've set at least one of these to something else)
183 | config.targetProperty ?= config.property
184 | config.to ?= @
185 |
186 | # All position accelerations are added to a single 'pos' velocity. Store actual properties so we don't have to do it again every frame
187 |
188 | if config.property.toLowerCase().includes 'pos'
189 | config.prop = 'pos'
190 |
191 | if config.property.toLowerCase().includes 'mid'
192 | config.thisX = 'midX'
193 | config.thisY = 'midY'
194 |
195 | else if config.property.toLowerCase().includes 'max'
196 | config.thisX = 'maxX'
197 | config.thisY = 'maxY'
198 |
199 | else
200 | config.thisX = 'x'
201 | config.thisY = 'y'
202 |
203 | if config.targetProperty.toLowerCase().includes 'mid'
204 | config.toX = 'midX'
205 | config.toY = 'midY'
206 |
207 | else if config.targetProperty.toLowerCase().includes 'max'
208 | config.toX = 'maxX'
209 | config.toY = 'maxY'
210 | else
211 | config.toX = 'x'
212 | config.toY = 'y'
213 |
214 | else
215 | config.prop = config.property
216 |
217 | # Save hook to @hooks array
218 | @hooks.hooks.push(config)
219 |
220 | # Create velocity property if necessary
221 | @hooks.velocities[config.prop] ?= if config.prop is 'pos' then { x: 0, y: 0 } else 0
222 |
223 | # Use Framer's animation loop, slightly more robust than requestAnimationFrame directly
224 | # Save the returned AnimationLoop reference to make sure @hookLoop isn't added multiple times per layer
225 | @hooks.emitter ?= Framer.Loop.on('render', @hookLoop, this)
226 |
227 | Layer::unHook = (property, object) ->
228 |
229 | return unless @hooks
230 |
231 | prop = if property.toLowerCase().includes 'pos' then 'pos' else property
232 |
233 | # Remove all matches
234 | @hooks.hooks = @hooks.hooks.filter (hook) ->
235 | hook.to isnt object or hook.property isnt property
236 |
237 | # If there are no hooks left, shut it down
238 | if @hooks.hooks.length is 0
239 | delete @hooks
240 | Framer.Loop.removeListener 'render', @hookLoop
241 | return
242 |
243 | # Still here? Check if there are any remaining hooks affecting same velocity
244 | remaining = @hooks.hooks.filter (hook) ->
245 | prop is hook.prop
246 |
247 | # If not, delete velocity (otherwise it won't be reset if you make new hook for same property)
248 | delete @hooks.velocities[prop] if remaining.length is 0
249 |
250 | Layer::hookLoop = (delta) ->
251 |
252 | if @hooks
253 |
254 | # Multiple hooks can affect the same property. Add accelerations to temporary object so the property's velocity is the same for all calculations within the same animation frame
255 | acceleration = {}
256 |
257 | # Save drag for each property to this object, since only most recently specified value is used for each property
258 | drag = {}
259 |
260 | # Add accelerations
261 | for hook in @hooks.hooks
262 |
263 | if hook.prop is 'pos'
264 |
265 | acceleration.pos ?= { x: 0, y: 0 }
266 |
267 | target = { x: hook.to[hook.toX], y: hook.to[hook.toY] }
268 |
269 | target = hook.modulator(target) if hook.modulator
270 |
271 | vector =
272 | x: target.x - @[hook.thisX]
273 | y: target.y - @[hook.thisY]
274 |
275 | vLength = Math.sqrt((vector.x * vector.x) + (vector.y * vector.y))
276 |
277 | if hook.type is 'spring'
278 |
279 | damper =
280 | x: -hook.friction * @hooks.velocities.pos.x
281 | y: -hook.friction * @hooks.velocities.pos.y
282 |
283 | vector.x *= hook.strength
284 | vector.y *= hook.strength
285 |
286 | acceleration.pos.x += (vector.x + damper.x) * delta
287 | acceleration.pos.y += (vector.y + damper.y) * delta
288 |
289 | else if hook.type is 'gravity'
290 |
291 | drag.pos = hook.friction
292 |
293 | gravity = @hooks.defs.getGravity(hook.strength, vLength, @hooks.defs.zoom)
294 |
295 | vector.x *= gravity / vLength
296 | vector.y *= gravity / vLength
297 |
298 | acceleration.pos.x += vector.x * delta
299 | acceleration.pos.y += vector.y * delta
300 |
301 | else
302 |
303 | acceleration[hook.prop] ?= 0
304 |
305 | target = hook.to[hook.targetProperty]
306 |
307 | target = hook.modulator(target) if hook.modulator
308 |
309 | vector = target - @[hook.prop]
310 |
311 | if hook.type is 'spring'
312 |
313 | force = vector * hook.strength
314 | damper = -hook.friction * @hooks.velocities[hook.prop]
315 |
316 | acceleration[hook.prop] += (force + damper) * delta
317 |
318 |
319 | else if hook.type is 'gravity'
320 |
321 | drag[hook.prop] = hook.friction
322 |
323 | force = @hooks.defs.getGravity(hook.strength, vector, @hooks.defs.zoom)
324 |
325 | acceleration[hook.prop] += force * delta
326 |
327 |
328 | # Add velocities to properties. Doing this at the end in case there are multiple hooks affecting the same velocity
329 | for prop, velocity of @hooks.velocities
330 |
331 | if prop is 'pos'
332 |
333 | # Add drag, if it exists
334 | if drag.pos
335 | velocity.x += @hooks.defs.getDrag(velocity.x, drag.pos, @hooks.defs.zoom)
336 | velocity.y += @hooks.defs.getDrag(velocity.y, drag.pos, @hooks.defs.zoom)
337 |
338 | # Add acceleration to velocity
339 | velocity.x += acceleration.pos.x
340 | velocity.y += acceleration.pos.y
341 |
342 | # Add velocity to position
343 | @x += velocity.x * delta
344 | @y += velocity.y * delta
345 |
346 | else
347 |
348 | # Add drag, if it exists
349 | if drag[prop]
350 | @hooks.velocities[prop] += @hooks.defs.getDrag(@hooks.velocities[prop], drag[prop], @hooks.defs.zoom)
351 |
352 | # Add acceleration to velocity
353 | @hooks.velocities[prop] += acceleration[prop]
354 |
355 | # Add velocity to property
356 | @[prop] += @hooks.velocities[prop] * delta
357 |
358 | @onHookUpdate?(delta)
--------------------------------------------------------------------------------
/hook-example-modulator.framer/.gitignore:
--------------------------------------------------------------------------------
1 | # Framer Git Ignore
2 |
3 | # General OSX
4 |
5 | .DS_Store
6 | .AppleDouble
7 | .LSOverride
8 |
9 | # Icon must end with two \r
10 | Icon
11 |
12 |
13 | # Thumbnails
14 | ._*
15 |
16 | # Files that might appear in the root of a volume
17 | .DocumentRevisions-V100
18 | .fseventsd
19 | .Spotlight-V100
20 | .TemporaryItems
21 | .Trashes
22 | .VolumeIcon.icns
23 |
24 | # Directories potentially created on remote AFP share
25 | .AppleDB
26 | .AppleDesktop
27 | Network Trash Folder
28 | Temporary Items
29 | .apdisk
30 |
31 |
32 | # Framer Specific
33 | .temp.html
34 | framer/*.old*
35 | framer/backup.coffee
36 | framer/backups/*
37 | framer/.*.hash
38 |
--------------------------------------------------------------------------------
/hook-example-modulator.framer/.viewer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/hook-example-modulator.framer/app.coffee:
--------------------------------------------------------------------------------
1 | ###
2 |
3 | Hook example
4 | --------------
5 |
6 | The Hook module simply expands the Layer prototype, and lets you make any
7 | numeric Layer property follow a property on another object via a spring or a
8 | gravity attraction. Check the comments in modules/Hook.coffee for a more thorough
9 | documentation.
10 |
11 | ###
12 |
13 |
14 |
15 | # Require Hook. No exports, it just adds methods to the Layer prototype
16 | # --------------------------------------------------------------------------------
17 |
18 | require "Hook"
19 |
20 |
21 |
22 | # Import sketch file
23 | # --------------------------------------------------------------------------------
24 | sketch = Framer.Importer.load("imported/app@2x")
25 | Utils.globalLayers(sketch)
26 |
27 |
28 |
29 | # Settings
30 | # --------------------------------------------------------------------------------
31 |
32 | colors =
33 | background: "#260355"
34 | orange: "#f90"
35 | blue: "#63ffff"
36 | primary: "white"
37 | secondary: "rgba(255,255,255,0.1)"
38 |
39 | margin = 60
40 |
41 | easeInOut = "cubic-bezier(0.2, 0, 0.4, 1)"
42 |
43 |
44 |
45 | # Set background and defaults
46 | # --------------------------------------------------------------------------------
47 |
48 | Framer.Device.viewport.backgroundColor = colors.background
49 |
50 | Framer.Defaults.Animation =
51 | curve: easeInOut
52 | time: 0.15
53 |
54 |
55 |
56 | # Set up example
57 | # --------------------------------------------------------------------------------
58 |
59 | # Make the page layer draggable
60 | page.draggable.enabled = true
61 | page.draggable.horizontal = false
62 | page.draggable.constraints = { height: Screen.height }
63 | page.draggable.bounceOptions.tension = 400
64 |
65 | # Make the spinner layer
66 | spinner = new Layer
67 | index: 0
68 | x: Align.center
69 | midY: 40
70 | width: 96
71 | height: 96
72 | borderRadius: 40
73 | image: "images/spinner.svg"
74 |
75 | # Attach the spinner's scale to the page's y property
76 | spinner.hook
77 | property: "scale"
78 | to: page
79 | targetProperty: "y"
80 | type: "spring(200,10)"
81 |
82 | # Add a modulator function, since we don't want the scale to be the same as page.y
83 | modulator: (inputvalue) ->
84 |
85 | # If page.y is under 300, translate to a scale between 0.5 and 0.75
86 | if inputvalue < 300
87 | return Utils.modulate(inputvalue, [0, 400], [0.5, 0.75])
88 |
89 | # Otherwise, snap to 1
90 | else
91 | return 1
92 |
93 | # Now attach spinner.midY to page.y with similar conditional modulation
94 | spinner.hook
95 | property: "midY"
96 | to: page
97 | targetProperty: "y"
98 | type: "spring(100,8)"
99 | modulator: (inputvalue) ->
100 | if inputvalue < 300
101 | inputvalue / 6 + 40
102 | else
103 | inputvalue / 2
104 |
105 | # onHookUpdate() is called at the end of each frame, after all of the layer's hooks are applied
106 | # It receives the delta value from the Framer loop, which is the time in seconds since the last frame
107 | spinner.onHookUpdate = (delta) ->
108 | if page.y < 300
109 | @doSpin = false
110 | @opacity = 0.2
111 | @rotation = page.y / 2
112 | else
113 | @doSpin = true
114 | @opacity = 1
115 | @rotation += delta * 300 if spinner.doSpin
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "propertyPanelToggleStates" : {
3 | "Filters" : false
4 | },
5 | "deviceOrientation" : 0,
6 | "sharedPrototype" : 0,
7 | "contentScale" : 1,
8 | "deviceType" : "apple-iphone-6s-silver",
9 | "selectedHand" : "",
10 | "updateDelay" : 0.3,
11 | "deviceScale" : "fit",
12 | "foldedCodeRanges" : [
13 |
14 | ],
15 | "orientation" : 0,
16 | "fullScreen" : false
17 | }
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/framer.generated.js:
--------------------------------------------------------------------------------
1 | // This is autogenerated by Framer
2 |
3 |
4 | if (!window.Framer && window._bridge) {window._bridge('runtime.error', {message:'[framer.js] Framer library missing or corrupt. Select File → Update Framer Library.'})}
5 | window.__imported__ = window.__imported__ || {};
6 | window.__imported__["app@2x/layers.json.js"] = [
7 | {
8 | "objectId": "0CDC1067-9F1A-4B22-8CF3-3B8FEE516032",
9 | "kind": "group",
10 | "name": "page",
11 | "maskFrame": null,
12 | "layerFrame": {
13 | "x": 0,
14 | "y": 0,
15 | "width": 375,
16 | "height": 667
17 | },
18 | "visible": true,
19 | "metadata": {
20 | "opacity": 1
21 | },
22 | "image": {
23 | "path": "images/Layer-page-meneqzew.png",
24 | "frame": {
25 | "x": 0,
26 | "y": 0,
27 | "width": 375,
28 | "height": 667
29 | }
30 | },
31 | "children": [],
32 | "time": 123
33 | }
34 | ]
35 | if (DeviceComponent) {DeviceComponent.Devices["iphone-6-silver"].deviceImageJP2 = false};
36 | if (window.Framer) {window.Framer.Defaults.DeviceView = {"deviceScale":"fit","selectedHand":"","deviceType":"apple-iphone-6s-silver","contentScale":1,"orientation":0};
37 | }
38 | if (window.Framer) {window.Framer.Defaults.DeviceComponent = {"deviceScale":"fit","selectedHand":"","deviceType":"apple-iphone-6s-silver","contentScale":1,"orientation":0};
39 | }
40 | window.FramerStudioInfo = {"deviceImagesUrl":"\/_server\/resources\/DeviceImages","documentTitle":"hook-example-modulator.framer"};
41 |
42 | Framer.Device = new Framer.DeviceView();
43 | Framer.Device.setupContext();
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/framer.init.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | function isFileLoadingAllowed() {
4 | return (window.location.protocol.indexOf("file") == -1)
5 | }
6 |
7 | function isHomeScreened() {
8 | return ("standalone" in window.navigator) && window.navigator.standalone == true
9 | }
10 |
11 | function isCompatibleBrowser() {
12 | return Utils.isWebKit()
13 | }
14 |
15 | var alertNode;
16 |
17 | function dismissAlert() {
18 | alertNode.parentElement.removeChild(alertNode)
19 | loadProject()
20 | }
21 |
22 | function showAlert(html) {
23 |
24 | alertNode = document.createElement("div")
25 |
26 | alertNode.classList.add("framerAlertBackground")
27 | alertNode.innerHTML = html
28 |
29 | document.addEventListener("DOMContentLoaded", function(event) {
30 | document.body.appendChild(alertNode)
31 | })
32 |
33 | window.dismissAlert = dismissAlert;
34 | }
35 |
36 | function showBrowserAlert() {
37 | var html = ""
38 | html += "
"
39 | html += "Error: Not A WebKit Browser"
40 | html += "Your browser is not supported. Please use Safari or Chrome. "
41 | html += "Try anyway"
42 | html += "
"
43 |
44 | showAlert(html)
45 | }
46 |
47 | function showFileLoadingAlert() {
48 | var html = ""
49 | html += "
"
50 | html += "Error: Local File Restrictions"
51 | html += "Preview this prototype with Framer Mirror or learn more about "
52 | html += "file restrictions. "
53 | html += "Try anyway"
54 | html += "
"
55 |
56 | showAlert(html)
57 | }
58 |
59 | function loadProject() {
60 | CoffeeScript.load("app.coffee")
61 | }
62 |
63 | function setDefaultPageTitle() {
64 | // If no title was set we set it to the project folder name so
65 | // you get a nice name on iOS if you bookmark to desktop.
66 | document.addEventListener("DOMContentLoaded", function() {
67 | if (document.title == "") {
68 | if (window.FramerStudioInfo && window.FramerStudioInfo.documentTitle) {
69 | document.title = window.FramerStudioInfo.documentTitle
70 | } else {
71 | document.title = window.location.pathname.replace(/\//g, "")
72 | }
73 | }
74 | })
75 | }
76 |
77 | function init() {
78 |
79 | if (Utils.isFramerStudio()) {
80 | return
81 | }
82 |
83 | setDefaultPageTitle()
84 |
85 | if (!isCompatibleBrowser()) {
86 | return showBrowserAlert()
87 | }
88 |
89 | if (!isFileLoadingAllowed()) {
90 | return showFileLoadingAlert()
91 | }
92 |
93 | loadProject()
94 |
95 | }
96 |
97 | init()
98 |
99 | })()
100 |
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/framer.modules.js:
--------------------------------------------------------------------------------
1 | require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o this.length) {
146 | return false;
147 | } else {
148 | return this.indexOf(search, start) !== -1;
149 | }
150 | };
151 | }
152 |
153 | Layer.prototype.hook = function(config) {
154 | var base, base1, f, name;
155 | if (!(config.property && config.type && (config.to || config.targetProperty))) {
156 | throw new Error('layer.hook() needs a property, a hook type and either a target object or target property to work');
157 | }
158 | if (this.hooks == null) {
159 | this.hooks = {
160 | hooks: [],
161 | velocities: {},
162 | defs: {
163 | zoom: 100,
164 | getDrag: (function(_this) {
165 | return function(velocity, drag, zoom) {
166 | velocity /= zoom;
167 | drag = -(drag / 10) * velocity * velocity * velocity / Math.abs(velocity);
168 | if (_.isNaN(drag)) {
169 | return 0;
170 | } else {
171 | return drag;
172 | }
173 | };
174 | })(this),
175 | getGravity: (function(_this) {
176 | return function(strength, distance, zoom) {
177 | var dist;
178 | dist = Math.max(1, distance / zoom);
179 | return strength * zoom / (dist * dist);
180 | };
181 | })(this)
182 | }
183 | };
184 | }
185 | if (config.zoom) {
186 | this.hooks.zoom = config.zoom;
187 | }
188 | f = Utils.parseFunction(config.type);
189 | config.type = f.name;
190 | config.strength = f.args[0];
191 | config.friction = f.args[1] || 0;
192 | if (config.targetProperty == null) {
193 | config.targetProperty = config.property;
194 | }
195 | if (config.to == null) {
196 | config.to = this;
197 | }
198 | if (config.property.toLowerCase().includes('pos')) {
199 | config.prop = 'pos';
200 | if (config.property.toLowerCase().includes('mid')) {
201 | config.thisX = 'midX';
202 | config.thisY = 'midY';
203 | } else if (config.property.toLowerCase().includes('max')) {
204 | config.thisX = 'maxX';
205 | config.thisY = 'maxY';
206 | } else {
207 | config.thisX = 'x';
208 | config.thisY = 'y';
209 | }
210 | if (config.targetProperty.toLowerCase().includes('mid')) {
211 | config.toX = 'midX';
212 | config.toY = 'midY';
213 | } else if (config.targetProperty.toLowerCase().includes('max')) {
214 | config.toX = 'maxX';
215 | config.toY = 'maxY';
216 | } else {
217 | config.toX = 'x';
218 | config.toY = 'y';
219 | }
220 | } else {
221 | config.prop = config.property;
222 | }
223 | this.hooks.hooks.push(config);
224 | if ((base = this.hooks.velocities)[name = config.prop] == null) {
225 | base[name] = config.prop === 'pos' ? {
226 | x: 0,
227 | y: 0
228 | } : 0;
229 | }
230 | return (base1 = this.hooks).emitter != null ? base1.emitter : base1.emitter = Framer.Loop.on('render', this.hookLoop, this);
231 | };
232 |
233 | Layer.prototype.unHook = function(property, object) {
234 | var prop, remaining;
235 | if (!this.hooks) {
236 | return;
237 | }
238 | prop = property.toLowerCase().includes('pos') ? 'pos' : property;
239 | this.hooks.hooks = this.hooks.hooks.filter(function(hook) {
240 | return hook.to !== object || hook.property !== property;
241 | });
242 | if (this.hooks.hooks.length === 0) {
243 | delete this.hooks;
244 | Framer.Loop.removeListener('render', this.hookLoop);
245 | return;
246 | }
247 | remaining = this.hooks.hooks.filter(function(hook) {
248 | return prop === hook.prop;
249 | });
250 | if (remaining.length === 0) {
251 | return delete this.hooks.velocities[prop];
252 | }
253 | };
254 |
255 | Layer.prototype.hookLoop = function(delta) {
256 | var acceleration, damper, drag, force, gravity, hook, i, len, name, prop, ref, ref1, target, vLength, vector, velocity;
257 | if (this.hooks) {
258 | acceleration = {};
259 | drag = {};
260 | ref = this.hooks.hooks;
261 | for (i = 0, len = ref.length; i < len; i++) {
262 | hook = ref[i];
263 | if (hook.prop === 'pos') {
264 | if (acceleration.pos == null) {
265 | acceleration.pos = {
266 | x: 0,
267 | y: 0
268 | };
269 | }
270 | target = {
271 | x: hook.to[hook.toX],
272 | y: hook.to[hook.toY]
273 | };
274 | if (hook.modulator) {
275 | target = hook.modulator(target);
276 | }
277 | vector = {
278 | x: target.x - this[hook.thisX],
279 | y: target.y - this[hook.thisY]
280 | };
281 | vLength = Math.sqrt((vector.x * vector.x) + (vector.y * vector.y));
282 | if (hook.type === 'spring') {
283 | damper = {
284 | x: -hook.friction * this.hooks.velocities.pos.x,
285 | y: -hook.friction * this.hooks.velocities.pos.y
286 | };
287 | vector.x *= hook.strength;
288 | vector.y *= hook.strength;
289 | acceleration.pos.x += (vector.x + damper.x) * delta;
290 | acceleration.pos.y += (vector.y + damper.y) * delta;
291 | } else if (hook.type === 'gravity') {
292 | drag.pos = hook.friction;
293 | gravity = this.hooks.defs.getGravity(hook.strength, vLength, this.hooks.defs.zoom);
294 | vector.x *= gravity / vLength;
295 | vector.y *= gravity / vLength;
296 | acceleration.pos.x += vector.x * delta;
297 | acceleration.pos.y += vector.y * delta;
298 | }
299 | } else {
300 | if (acceleration[name = hook.prop] == null) {
301 | acceleration[name] = 0;
302 | }
303 | target = hook.to[hook.targetProperty];
304 | if (hook.modulator) {
305 | target = hook.modulator(target);
306 | }
307 | vector = target - this[hook.prop];
308 | if (hook.type === 'spring') {
309 | force = vector * hook.strength;
310 | damper = -hook.friction * this.hooks.velocities[hook.prop];
311 | acceleration[hook.prop] += (force + damper) * delta;
312 | } else if (hook.type === 'gravity') {
313 | drag[hook.prop] = hook.friction;
314 | force = this.hooks.defs.getGravity(hook.strength, vector, this.hooks.defs.zoom);
315 | acceleration[hook.prop] += force * delta;
316 | }
317 | }
318 | }
319 | ref1 = this.hooks.velocities;
320 | for (prop in ref1) {
321 | velocity = ref1[prop];
322 | if (prop === 'pos') {
323 | if (drag.pos) {
324 | velocity.x += this.hooks.defs.getDrag(velocity.x, drag.pos, this.hooks.defs.zoom);
325 | velocity.y += this.hooks.defs.getDrag(velocity.y, drag.pos, this.hooks.defs.zoom);
326 | }
327 | velocity.x += acceleration.pos.x;
328 | velocity.y += acceleration.pos.y;
329 | this.x += velocity.x * delta;
330 | this.y += velocity.y * delta;
331 | } else {
332 | if (drag[prop]) {
333 | this.hooks.velocities[prop] += this.hooks.defs.getDrag(this.hooks.velocities[prop], drag[prop], this.hooks.defs.zoom);
334 | }
335 | this.hooks.velocities[prop] += acceleration[prop];
336 | this[prop] += this.hooks.velocities[prop] * delta;
337 | }
338 | }
339 | return typeof this.onHookUpdate === "function" ? this.onHookUpdate(delta) : void 0;
340 | }
341 | };
342 |
343 |
344 | },{}]},{},[])
345 | //# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCIvVXNlcnMvc2lndXJkL1JlcG9zL2ZyYW1lci1ob29rL2hvb2stZXhhbXBsZS1tb2R1bGF0b3IuZnJhbWVyL21vZHVsZXMvSG9vay5jb2ZmZWUiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0FDQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUE2SUEsSUFBQSxDQUFPLE1BQU0sQ0FBQyxTQUFTLENBQUMsUUFBeEI7RUFDQyxNQUFNLENBQUEsU0FBRSxDQUFBLFFBQVIsR0FBbUIsU0FBQyxNQUFELEVBQVMsS0FBVDtJQUNsQjtJQUNBLElBQWEsT0FBTyxLQUFQLEtBQWdCLFFBQTdCO01BQUEsS0FBQSxHQUFRLEVBQVI7O0lBRUEsSUFBRyxLQUFBLEdBQVEsTUFBTSxDQUFDLE1BQWYsR0FBd0IsSUFBSSxDQUFDLE1BQWhDO0FBQ0MsYUFBTyxNQURSO0tBQUEsTUFBQTtBQUdDLGFBQU8sSUFBQyxDQUFBLE9BQUQsQ0FBUyxNQUFULEVBQWlCLEtBQWpCLENBQUEsS0FBNkIsQ0FBQyxFQUh0Qzs7RUFKa0IsRUFEcEI7OztBQVlBLEtBQUssQ0FBQSxTQUFFLENBQUEsSUFBUCxHQUFjLFNBQUMsTUFBRDtBQUViLE1BQUE7RUFBQSxJQUFBLENBQUEsQ0FBMEgsTUFBTSxDQUFDLFFBQVAsSUFBb0IsTUFBTSxDQUFDLElBQTNCLElBQW9DLENBQUMsTUFBTSxDQUFDLEVBQVAsSUFBYSxNQUFNLENBQUMsY0FBckIsQ0FBOUosQ0FBQTtBQUFBLFVBQVUsSUFBQSxLQUFBLENBQU0sa0dBQU4sRUFBVjs7O0lBR0EsSUFBQyxDQUFBLFFBQ0E7TUFBQSxL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S0FBSyxDQUFDLElBQUksQ0FBQyxJQUF0RCxFQUZmOztRQUtBLFFBQVEsQ0FBQyxDQUFULElBQWMsWUFBWSxDQUFDLEdBQUcsQ0FBQztRQUMvQixRQUFRLENBQUMsQ0FBVCxJQUFjLFlBQVksQ0FBQyxHQUFHLENBQUM7UUFHL0IsSUFBQyxDQUFBLENBQUQsSUFBTSxRQUFRLENBQUMsQ0FBVCxHQUFhO1FBQ25CLElBQUMsQ0FBQSxDQUFELElBQU0sUUFBUSxDQUFDLENBQVQsR0FBYSxNQWJwQjtPQUFBLE1BQUE7UUFrQkMsSUFBRyxJQUFLLENBQUEsSUFBQSxDQUFSO1VBQ0MsSUFBQyxDQUFBLEtBQUssQ0FBQyxVQUFXLENBQUEsSUFBQSxDQUFsQixJQUEyQixJQUFDLENBQUEsS0FBSyxDQUFDLElBQUksQ0FBQyxPQUFaLENBQW9CLElBQUMsQ0FBQSxLQUFLLENBQUMsVUFBVyxDQUFBLElBQUEsQ0FBdEMsRUFBNkMsSUFBSyxDQUFBLElBQUEsQ0FBbEQsRUFBeUQsSUFBQyxDQUFBLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBckUsRUFENUI7O1FBSUEsSUFBQyxDQUFBLEtBQUssQ0FBQyxVQUFXLENBQUEsSUFBQSxDQUFsQixJQUEyQixZQUFhLENBQUEsSUFBQTtRQUd4QyxJQUFFLENBQUEsSUFBQSxDQUFGLElBQVcsSUFBQyxDQUFBLEtBQUssQ0FBQyxVQUFXLENBQUEsSUFBQSxDQUFsQixHQUEwQixNQXpCdEM7O0FBRkQ7cURBNkJBLElBQUMsQ0FBQSxhQUFjLGdCQTFHaEI7O0FBRmlCIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsIiMjI1xuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbkhvb2sgbW9kdWxlIGZvciBGcmFtZXJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbmJ5OiAgICAgIFNpZ3VyZCBNYW5uc8Ola2VyXG5naXRodWI6ICBodHRwczovL2dpdGh1Yi5jb20vc2lndG0vZnJhbWVyLWhvb2tcblxuwrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt1xuXG5cblRoZSBIb29rIG1vZHVsZSBzaW1wbHkgZXhwYW5kcyB0aGUgTGF5ZXIgcHJvdG90eXBlLCBhbmQgbGV0cyB5b3UgbWFrZSBhbnlcbm51bWVyaWMgTGF5ZXIgcHJvcGVydHkgZm9sbG93IGFub3RoZXIgcHJvcGVydHkgLSBlaXRoZXIgaXRzIG93biBvciBhbm90aGVyXG5vYmplY3QncyAtIHZpYSBhIHNwcmluZyBvciBncmF2aXR5IGF0dHJhY3Rpb24uXG5cblxuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbkV4YW1wbGU6IExheWVyZWQgYW5pbWF0aW9uIChlYXNlZCArIHNwcmluZylcbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbm15TGF5ZXIgPSBuZXcgTGF5ZXJcblxuIyBNYWtlIG91ciBvd24gY3VzdG9tIHByb3BlcnR5IGZvciB0aGUgeCBwcm9wZXJ0eSB0byBmb2xsb3dcbm15TGF5ZXIuZWFzZWRYID0gMFxuXG4jIEhvb2sgeCB0byBlYXNlZFggdmlhIGEgc3ByaW5nXG5teUxheWVyLmhvb2tcblx0cHJvcGVydHk6IFwieFwiXG5cdHRhcmdldFByb3BlcnR5OiBcImVhc2VkWFwiXG5cdHR5cGU6IFwic3ByaW5nKDE1MCwgMTUpXCJcblxuIyBBbmltYXRlIGVhc2VkWFxubXlMYXllci5hbmltYXRlXG5cdHByb3BlcnRpZXM6XG5cdFx0ZWFzZWRYOiAyMDBcblx0dGltZTogMC4xNVxuXHRjdXJ2ZTogXCJjdWJpYy1iZXppZXIoMC4yLCAwLCAwLjQsIDEpXCJcblxuTk9URTogXG5UbyBhdHRhY2ggYm90aCB0aGUgeCBhbmQgeSBwb3NpdGlvbiwgdXNlIFwicG9zXCIsIFwibWlkUG9zXCIgb3IgXCJtYXhQb3NcIiBhcyB0aGVcbnByb3BlcnR5L3RhcmdldFByb3BlcnR5LlxuXG5cbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5FeGFtcGxlOiBIb29raW5nIHByb3BlcnR5IHRvIGFub3RoZXIgbGF5ZXJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbnRhcmdldCA9IG5ldyBMYXllclxuaG9va2VkID0gbmV3IExheWVyXG5cbmhvb2tlZC5ob29rXG5cdHByb3BlcnR5OiBcInNjYWxlXCJcblx0dG86IHRhcmdldFxuXHR0eXBlOiBcInNwcmluZygxNTAsIDE1KVwiXG5cblRoZSBcImhvb2tlZFwiIGxheWVyJ3Mgc2NhbGUgd2lsbCBub3cgY29udGludW91c2x5IGZvbGxvdyB0aGUgdGFyZ2V0IGxheWVyJ3Mgc2NhbGVcbndpdGggYSBzcHJpbmcgYW5pbWF0aW9uLlxuXG5cbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5sYXllci5ob29rKG9wdGlvbnMpXG4tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuXG5PcHRpb25zIGFyZSBwYXNzZWQgYXMgYSBzaW5nbGUgb2JqZWN0LCBsaWtlIHlvdSB3b3VsZCBmb3IgYSBuZXcgTGF5ZXIuXG5UaGUgb3B0aW9ucyBvYmplY3QgdGFrZXMgdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuXG5cbnByb3BlcnR5IFtTdHJpbmddXG4tLS0tLS0tLS0tLS0tLS0tLVxuVGhlIHByb3BlcnR5IHlvdSdkIGxpa2UgdG8gaG9vayBvbnRvIGFub3RoZXIgb2JqZWN0J3MgcHJvcGVydHlcblxuXG50eXBlIFtTdHJpbmddXG4tLS0tLS0tLS0tLS0tXG5FaXRoZXIgXCJzcHJpbmcoc3RyZW5ndGgsIGZyaWN0aW9uKVwiIG9yIFwiZ3Jhdml0eShzdHJlbmd0aCwgZHJhZylcIi4gT25seSB0aGUgbGFzdFxuc3BlY2lmaWVkIGRyYWcgdmFsdWUgaXMgdXNlZCBmb3IgZWFjaCBwcm9wZXJ0eSwgc2luY2UgaXQgaXMgb25seSBhcHBsaWVkIHRvXG5lYWNoIHByb3BlcnR5IG9uY2UgKGFuZCBvbmx5IGlmIGl0IGhhcyBhIGdyYXZpdHkgaG9vayBhcHBsaWVkIHRvIGl0LilcblxuXG50byBbT2JqZWN0XSAoT3B0aW9uYWwpXG4tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5UaGUgb2JqZWN0IHRvIGF0dGFjaCBpdCB0by4gRGVmYXVsdHMgdG8gaXRzZWxmLlxuXG5cbnRhcmdldFByb3BlcnR5IFtTdHJpbmddIChPcHRpb25hbClcbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblNwZWNpZnkgdGhlIHRhcmdldCBvYmplY3QncyBwcm9wZXJ0eSB0byBmb2xsb3csIGlmIHlvdSBkb24ndCB3YW50IHRvIGZvbGxvd1xudGhlIHNhbWUgcHJvcGVydHkgdGhhdCB0aGUgaG9vayBpcyBhcHBsaWVkIHRvLlxuXG5cbm1vZHVsYXRvciBbRnVuY3Rpb25dIChPcHRpb25hbClcbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblRoZSBtb2R1bGF0b3IgZnVuY3Rpb24gcmVjZWl2ZXMgdGhlIHRhcmdldCBwcm9wZXJ0eSdzIHZhbHVlLCBhbmQgbGV0cyB5b3Vcbm1vZGlmeSBpdCBiZWZvcmUgaXQgaXMgZmVkIGludG8gdGhlIHBoeXNpY3MgY2FsY3VsYXRpb25zLiBVc2VmdWwgZm9yIGFueXRoaW5nXG5mcm9tIHN0YW5kYXJkIFV0aWxzLm1vZHVsYXRlKCkgdHlwZSBzdHVmZiB0byBzbmFwcGluZyBhbmQgY29uZGl0aW9uYWwgdmFsdWVzLlxuXG5cbnpvb20gW051bWJlcl0gKE9wdGlvbmFsKVxuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5UaGlzIGZhY3RvciBkZWZpbmVzIHRoZSBkaXN0YW5jZSB0aGF0IDFweCByZXByZXNlbnRzIGluIHJlZ2FyZHMgdG8gZ3Jhdml0eSBhbmRcbmRyYWcgY2FsY3VsYXRpb25zLiBPbmx5IG9uZSB2YWx1ZSBpcyBzdG9yZWQgcGVyIGxheWVyLCBzbyBzcGVjaWZ5aW5nIGl0XG5vdmVyd3JpdGVzIGl0cyBleGlzdGluZyB2YWx1ZS4gRGVmYXVsdCBpcyAxMDAuXG5cblxuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbmxheWVyLnVuSG9vayhwcm9wZXJ0eSwgb2JqZWN0KVxuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuVGhpcyByZW1vdmVzIGFsbCBob29rcyBmb3IgYSBnaXZlbiBwcm9wZXJ0eSBhbmQgdGFyZ2V0IG9iamVjdC4gRXhhbXBsZTpcblxuIyBIb29rIGl0XG5sYXllci5ob29rXG5cdHByb3BlcnR5OiBcInhcIlxuXHR0bzogXCJvdGhlcmxheWVyXCJcblx0dGFyZ2V0UHJvcGVydHk6IFwieVwiXG5cdHR5cGU6IFwic3ByaW5nKDIwMCwyMClcIlxuXG4jIFVuaG9vayBpdFxubGF5ZXIudW5Ib29rIFwieFwiLCBvdGhlcmxheWVyXG5cblxuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbmxheWVyLm9uSG9va1VwZGF0ZShkZWx0YSlcbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbkFmdGVyIGEgbGF5ZXIgaXMgZG9uZSBhcHBseWluZyBhY2NlbGVyYXRpb25zIHRvIGl0cyBob29rZWQgcHJvcGVydGllcywgaXQgY2FsbHNcbm9uSG9va1VwZGF0ZSgpIGF0IHRoZSBlbmQgb2YgZWFjaCBmcmFtZSwgaWYgaXQgaXMgZGVmaW5lZC4gVGhpcyBpcyBhbiBlYXN5IHdheVxudG8gYW5pbWF0ZSBvciB0cmlnZ2VyIG90aGVyIHN0dWZmLCBwZXJoYXBzIGJhc2VkIG9uIHlvdXIgbGF5ZXIncyB1cGRhdGVkXG5wcm9wZXJ0aWVzIG9yIHZlbG9jaXRpZXMuXG5cblRoZSBkZWx0YSB2YWx1ZSBmcm9tIHRoZSBGcmFtZXIgbG9vcCBpcyBwYXNzZWQgb24gdG8gb25Ib29rVXBkYXRlKCkgYXMgd2VsbCxcbndoaWNoIGlzIHRoZSB0aW1lIGluIHNlY29uZHMgc2luY2UgdGhlIGxhc3QgYW5pbWF0aW9uIGZyYW1lLlxuXG5Ob3RlIHRoYXQgaWYgeW91IHVuaG9vayBhbGwgeW91ciBob29rcywgb25Ib29rVXBkYXRlKCkgd2lsbCBvZiBjb3Vyc2Ugbm8gbG9uZ2VyXG5iZSBjYWxsZWQgZm9yIHRoYXQgbGF5ZXIuXG5cbiMjI1xuXG5cbiMgU2luY2Ugb2xkZXIgdmVyc2lvbnMgb2YgU2FmYXJpIHNlZW0gdG8gYmUgbWlzc2luZyBTdHJpbmcucHJvdG90eXBlLmluY2x1ZGVzKClcblxudW5sZXNzIFN0cmluZy5wcm90b3R5cGUuaW5jbHVkZXNcblx0U3RyaW5nOjppbmNsdWRlcyA9IChzZWFyY2gsIHN0YXJ0KSAtPlxuXHRcdCd1c2Ugc3RyaWN0J1xuXHRcdHN0YXJ0ID0gMCBpZiB0eXBlb2Ygc3RhcnQgaXMgJ251bWJlcidcblxuXHRcdGlmIHN0YXJ0ICsgc2VhcmNoLmxlbmd0aCA+IHRoaXMubGVuZ3RoXG5cdFx0XHRyZXR1cm4gZmFsc2U7XG5cdFx0ZWxzZVxuXHRcdFx0cmV0dXJuIEBpbmRleE9mKHNlYXJjaCwgc3RhcnQpIGlzbnQgLTFcblxuIyBFeHBhbmQgbGF5ZXJcblxuTGF5ZXI6Omhvb2sgPSAoY29uZmlnKSAtPlxuXG5cdHRocm93IG5ldyBFcnJvciAnbGF5ZXIuaG9vaygpIG5lZWRzIGEgcHJvcGVydHksIGEgaG9vayB0eXBlIGFuZCBlaXRoZXIgYSB0YXJnZXQgb2JqZWN0IG9yIHRhcmdldCBwcm9wZXJ0eSB0byB3b3JrJyB1bmxlc3MgY29uZmlnLnByb3BlcnR5IGFuZCBjb25maWcudHlwZSBhbmQgKGNvbmZpZy50byBvciBjb25maWcudGFyZ2V0UHJvcGVydHkpXG5cblx0IyBTaW5nbGUgYXJyYXkgZm9yIGFsbCBob29rcywgYXMgb3Bwb3NlZCB0byBuZXN0ZWQgYXJyYXlzIHBlciBwcm9wZXJ0eSwgYmVjYXVzZSBwZXJmb3JtYW5jZVxuXHRAaG9va3MgPz1cblx0XHRob29rczogW11cblx0XHR2ZWxvY2l0aWVzOiB7fVxuXHRcdGRlZnM6XG5cdFx0XHR6b29tOiAxMDBcblx0XHRcdGdldERyYWc6ICh2ZWxvY2l0eSwgZHJhZywgem9vbSkgPT5cblx0XHRcdFx0dmVsb2NpdHkgLz0gem9vbVxuXHRcdFx0XHQjIERpdmlkaW5nIGJ5IDEwIGlzIHVuc2NpZW50aWZpYywgYnV0IGl0IG1lYW5zIGEgdmFsdWUgb2YgMiBlcXVhbHMgcm91Z2hseSBhIDEwMGcgYmFsbCB3aXRoIDE1Y20gcmFkaXVzIGluIGFpclxuXHRcdFx0XHRkcmFnID0gLShkcmFnIC8gMTApICogdmVsb2NpdHkgKiB2ZWxvY2l0eSAqIHZlbG9jaXR5IC8gTWF0aC5hYnModmVsb2NpdHkpXG5cdFx0XHRcdGlmIF8uaXNOYU4oZHJhZykgdGhlbiByZXR1cm4gMCBlbHNlIHJldHVybiBkcmFnXG5cdFx0XHRnZXRHcmF2aXR5OiAoc3RyZW5ndGgsIGRpc3RhbmNlLCB6b29tKSA9PlxuXHRcdFx0XHRkaXN0ID0gTWF0aC5tYXgoMSwgZGlzdGFuY2UgLyB6b29tKVxuXHRcdFx0XHRyZXR1cm4gc3RyZW5ndGggKiB6b29tIC8gKGRpc3QgKiBkaXN0KVxuXG5cdCMgVXBkYXRlIHRoZSB6b29tIHZhbHVlIGlmIGdpdmVuXG5cdEBob29rcy56b29tID0gY29uZmlnLnpvb20gaWYgY29uZmlnLnpvb21cblxuXHQjIFBhcnNlIHBoeXNpY3MgY29uZmlnIHN0cmluZ1xuXHRmID0gVXRpbHMucGFyc2VGdW5jdGlvbiBjb25maWcudHlwZVxuXHRjb25maWcudHlwZSA9IGYubmFtZVxuXHRjb25maWcuc3RyZW5ndGggPSBmLmFyZ3NbMF1cblx0Y29uZmlnLmZyaWN0aW9uID0gZi5hcmdzWzFdIG9yIDBcblxuXHQjIERlZmF1bHQgdG8gc2FtZSB0YXJnZXRQcm9wZXJ0eSBvbiBzYW1lIG9iamVjdCAoaG9wZWZ1bGx5IHlvdSd2ZSBzZXQgYXQgbGVhc3Qgb25lIG9mIHRoZXNlIHRvIHNvbWV0aGluZyBlbHNlKVxuXHRjb25maWcudGFyZ2V0UHJvcGVydHkgPz0gY29uZmlnLnByb3BlcnR5XG5cdGNvbmZpZy50byA/PSBAXG5cblx0IyBBbGwgcG9zaXRpb24gYWNjZWxlcmF0aW9ucyBhcmUgYWRkZWQgdG8gYSBzaW5nbGUgJ3BvcycgdmVsb2NpdHkuIFN0b3JlIGFjdHVhbCBwcm9wZXJ0aWVzIHNvIHdlIGRvbid0IGhhdmUgdG8gZG8gaXQgYWdhaW4gZXZlcnkgZnJhbWVcblxuXHRpZiBjb25maWcucHJvcGVydHkudG9Mb3dlckNhc2UoKS5pbmNsdWRlcyAncG9zJ1xuXHRcdGNvbmZpZy5wcm9wID0gJ3Bvcydcblx0XHRcblx0XHRpZiBjb25maWcucHJvcGVydHkudG9Mb3dlckNhc2UoKS5pbmNsdWRlcyAnbWlkJ1xuXHRcdFx0Y29uZmlnLnRoaXNYID0gJ21pZFgnXG5cdFx0XHRjb25maWcudGhpc1kgPSAnbWlkWSdcblx0XHRcblx0XHRlbHNlIGlmIGNvbmZpZy5wcm9wZXJ0eS50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzICdtYXgnXG5cdFx0XHRjb25maWcudGhpc1ggPSAnbWF4WCdcblx0XHRcdGNvbmZpZy50aGlzWSA9ICdtYXhZJ1xuXHRcdFxuXHRcdGVsc2Vcblx0XHRcdGNvbmZpZy50aGlzWCA9ICd4J1xuXHRcdFx0Y29uZmlnLnRoaXNZID0gJ3knXG5cdFx0XG5cdFx0aWYgY29uZmlnLnRhcmdldFByb3BlcnR5LnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMgJ21pZCdcblx0XHRcdGNvbmZpZy50b1ggPSAnbWlkWCdcblx0XHRcdGNvbmZpZy50b1kgPSAnbWlkWSdcblx0XHRcblx0XHRlbHNlIGlmIGNvbmZpZy50YXJnZXRQcm9wZXJ0eS50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzICdtYXgnXG5cdFx0XHRjb25maWcudG9YID0gJ21heFgnXG5cdFx0XHRjb25maWcudG9ZID0gJ21heFknXHRcdFxuXHRcdGVsc2Vcblx0XHRcdGNvbmZpZy50b1ggPSAneCdcblx0XHRcdGNvbmZpZy50b1kgPSAneSdcblx0XHRcblx0ZWxzZVxuXHRcdGNvbmZpZy5wcm9wID0gY29uZmlnLnByb3BlcnR5XG5cblx0IyBTYXZlIGhvb2sgdG8gQGhvb2tzIGFycmF5XHRcblx0QGhvb2tzLmhvb2tzLnB1c2goY29uZmlnKVxuXG5cdCMgQ3JlYXRlIHZlbG9jaXR5IHByb3BlcnR5IGlmIG5lY2Vzc2FyeVxuXHRAaG9va3MudmVsb2NpdGllc1tjb25maWcucHJvcF0gPz0gaWYgY29uZmlnLnByb3AgaXMgJ3BvcycgdGhlbiB7IHg6IDAsIHk6IDAgfSBlbHNlIDBcblxuXHQjIFVzZSBGcmFtZXIncyBhbmltYXRpb24gbG9vcCwgc2xpZ2h0bHkgbW9yZSByb2J1c3QgdGhhbiByZXF1ZXN0QW5pbWF0aW9uRnJhbWUgZGlyZWN0bHlcblx0IyBTYXZlIHRoZSByZXR1cm5lZCBBbmltYXRpb25Mb29wIHJlZmVyZW5jZSB0byBtYWtlIHN1cmUgQGhvb2tMb29wIGlzbid0IGFkZGVkIG11bHRpcGxlIHRpbWVzIHBlciBsYXllclxuXHRAaG9va3MuZW1pdHRlciA/PSBGcmFtZXIuTG9vcC5vbigncmVuZGVyJywgQGhvb2tMb29wLCB0aGlzKVxuXG5MYXllcjo6dW5Ib29rID0gKHByb3BlcnR5LCBvYmplY3QpIC0+XG5cdFxuXHRyZXR1cm4gdW5sZXNzIEBob29rc1xuXG5cdHByb3AgPSBpZiBwcm9wZXJ0eS50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzICdwb3MnIHRoZW4gJ3BvcycgZWxzZSBwcm9wZXJ0eVxuXG5cdCMgUmVtb3ZlIGFsbCBtYXRjaGVzXG5cdEBob29rcy5ob29rcyA9IEBob29rcy5ob29rcy5maWx0ZXIgKGhvb2spIC0+XG5cdFx0aG9vay50byBpc250IG9iamVjdCBvciBob29rLnByb3BlcnR5IGlzbnQgcHJvcGVydHlcblxuXHQjIElmIHRoZXJlIGFyZSBubyBob29rcyBsZWZ0LCBzaHV0IGl0IGRvd25cblx0aWYgQGhvb2tzLmhvb2tzLmxlbmd0aCBpcyAwXG5cdFx0ZGVsZXRlIEBob29rc1xuXHRcdEZyYW1lci5Mb29wLnJlbW92ZUxpc3RlbmVyICdyZW5kZXInLCBAaG9va0xvb3Bcblx0XHRyZXR1cm5cblxuXHQjIFN0aWxsIGhlcmU/IENoZWNrIGlmIHRoZXJlIGFyZSBhbnkgcmVtYWluaW5nIGhvb2tzIGFmZmVjdGluZyBzYW1lIHZlbG9jaXR5XG5cdHJlbWFpbmluZyA9IEBob29rcy5ob29rcy5maWx0ZXIgKGhvb2spIC0+XG5cdFx0cHJvcCBpcyBob29rLnByb3Bcblx0XHRcblx0IyBJZiBub3QsIGRlbGV0ZSB2ZWxvY2l0eSAob3RoZXJ3aXNlIGl0IHdvbid0IGJlIHJlc2V0IGlmIHlvdSBtYWtlIG5ldyBob29rIGZvciBzYW1lIHByb3BlcnR5KVxuXHRkZWxldGUgQGhvb2tzLnZlbG9jaXRpZXNbcHJvcF0gaWYgcmVtYWluaW5nLmxlbmd0aCBpcyAwXG5cbkxheWVyOjpob29rTG9vcCA9IChkZWx0YSkgLT5cblxuXHRpZiBAaG9va3NcblxuXHRcdCMgTXVsdGlwbGUgaG9va3MgY2FuIGFmZmVjdCB0aGUgc2FtZSBwcm9wZXJ0eS4gQWRkIGFjY2VsZXJhdGlvbnMgdG8gdGVtcG9yYXJ5IG9iamVjdCBzbyB0aGUgcHJvcGVydHkncyB2ZWxvY2l0eSBpcyB0aGUgc2FtZSBmb3IgYWxsIGNhbGN1bGF0aW9ucyB3aXRoaW4gdGhlIHNhbWUgYW5pbWF0aW9uIGZyYW1lXG5cdFx0YWNjZWxlcmF0aW9uID0ge31cblx0XHRcblx0XHQjIFNhdmUgZHJhZyBmb3IgZWFjaCBwcm9wZXJ0eSB0byB0aGlzIG9iamVjdCwgc2luY2Ugb25seSBtb3N0IHJlY2VudGx5IHNwZWNpZmllZCB2YWx1ZSBpcyB1c2VkIGZvciBlYWNoIHByb3BlcnR5XG5cdFx0ZHJhZyA9IHt9XG5cdFx0XG5cdFx0IyBBZGQgYWNjZWxlcmF0aW9uc1xuXHRcdGZvciBob29rIGluIEBob29rcy5ob29rc1xuXHRcdFxuXHRcdFx0aWYgaG9vay5wcm9wIGlzICdwb3MnXG5cblx0XHRcdFx0YWNjZWxlcmF0aW9uLnBvcyA/PSB7IHg6IDAsIHk6IDAgfVxuXG5cdFx0XHRcdHRhcmdldCA9IHsgeDogaG9vay50b1tob29rLnRvWF0sIHk6IGhvb2sudG9baG9vay50b1ldIH1cblxuXHRcdFx0XHR0YXJnZXQgPSBob29rLm1vZHVsYXRvcih0YXJnZXQpIGlmIGhvb2subW9kdWxhdG9yXG5cblx0XHRcdFx0dmVjdG9yID1cblx0XHRcdFx0XHR4OiB0YXJnZXQueCAtIEBbaG9vay50aGlzWF1cblx0XHRcdFx0XHR5OiB0YXJnZXQueSAtIEBbaG9vay50aGlzWV1cblx0XHRcdFx0XG5cdFx0XHRcdHZMZW5ndGggPSBNYXRoLnNxcnQoKHZlY3Rvci54ICogdmVjdG9yLngpICsgKHZlY3Rvci55ICogdmVjdG9yLnkpKVxuXG5cdFx0XHRcdGlmIGhvb2sudHlwZSBpcyAnc3ByaW5nJ1xuXG5cdFx0XHRcdFx0ZGFtcGVyID1cblx0XHRcdFx0XHRcdHg6IC1ob29rLmZyaWN0aW9uICogQGhvb2tzLnZlbG9jaXRpZXMucG9zLnhcblx0XHRcdFx0XHRcdHk6IC1ob29rLmZyaWN0aW9uICogQGhvb2tzLnZlbG9jaXRpZXMucG9zLnlcblxuXHRcdFx0XHRcdHZlY3Rvci54ICo9IGhvb2suc3RyZW5ndGhcblx0XHRcdFx0XHR2ZWN0b3IueSAqPSBob29rLnN0cmVuZ3RoXG5cdFx0XHRcdFx0XG5cdFx0XHRcdFx0YWNjZWxlcmF0aW9uLnBvcy54ICs9ICh2ZWN0b3IueCArIGRhbXBlci54KSAqIGRlbHRhXG5cdFx0XHRcdFx0YWNjZWxlcmF0aW9uLnBvcy55ICs9ICh2ZWN0b3IueSArIGRhbXBlci55KSAqIGRlbHRhXG5cdFx0XHRcdFxuXHRcdFx0XHRlbHNlIGlmIGhvb2sudHlwZSBpcyAnZ3Jhdml0eSdcblx0XHRcdFx0XG5cdFx0XHRcdFx0ZHJhZy5wb3MgPSBob29rLmZyaWN0aW9uXG5cdFx0XHRcdFx0XG5cdFx0XHRcdFx0Z3Jhdml0eSA9IEBob29rcy5kZWZzLmdldEdyYXZpdHkoaG9vay5zdHJlbmd0aCwgdkxlbmd0aCwgQGhvb2tzLmRlZnMuem9vbSlcblxuXHRcdFx0XHRcdHZlY3Rvci54ICo9IGdyYXZpdHkgLyB2TGVuZ3RoXG5cdFx0XHRcdFx0dmVjdG9yLnkgKj0gZ3Jhdml0eSAvIHZMZW5ndGhcblx0XHRcdFx0XHRcblx0XHRcdFx0XHRhY2NlbGVyYXRpb24ucG9zLnggKz0gdmVjdG9yLnggKiBkZWx0YVxuXHRcdFx0XHRcdGFjY2VsZXJhdGlvbi5wb3MueSArPSB2ZWN0b3IueSAqIGRlbHRhXG5cdFx0XHRcdFx0XHRcdFx0XHRcblx0XHRcdGVsc2Vcblx0XHRcdFx0XG5cdFx0XHRcdGFjY2VsZXJhdGlvbltob29rLnByb3BdID89IDBcblxuXHRcdFx0XHR0YXJnZXQgPSBob29rLnRvW2hvb2sudGFyZ2V0UHJvcGVydHldXG5cblx0XHRcdFx0dGFyZ2V0ID0gaG9vay5tb2R1bGF0b3IodGFyZ2V0KSBpZiBob29rLm1vZHVsYXRvclxuXG5cdFx0XHRcdHZlY3RvciA9IHRhcmdldCAtIEBbaG9vay5wcm9wXVxuXHRcdFx0XHRcblx0XHRcdFx0aWYgaG9vay50eXBlIGlzICdzcHJpbmcnXG5cblx0XHRcdFx0XHRmb3JjZSA9IHZlY3RvciAqIGhvb2suc3RyZW5ndGhcblx0XHRcdFx0XHRkYW1wZXIgPSAtaG9vay5mcmljdGlvbiAqIEBob29rcy52ZWxvY2l0aWVzW2hvb2sucHJvcF1cblxuXHRcdFx0XHRcdGFjY2VsZXJhdGlvbltob29rLnByb3BdICs9IChmb3JjZSArIGRhbXBlcikgKiBkZWx0YVxuXG5cdFx0XHRcdFxuXHRcdFx0XHRlbHNlIGlmIGhvb2sudHlwZSBpcyAnZ3Jhdml0eSdcblx0XG5cdFx0XHRcdFx0ZHJhZ1tob29rLnByb3BdID0gaG9vay5mcmljdGlvblxuXHRcdFx0XHRcdFxuXHRcdFx0XHRcdGZvcmNlID0gQGhvb2tzLmRlZnMuZ2V0R3Jhdml0eShob29rLnN0cmVuZ3RoLCB2ZWN0b3IsIEBob29rcy5kZWZzLnpvb20pXG5cdFx0XHRcdFx0XG5cdFx0XHRcdFx0YWNjZWxlcmF0aW9uW2hvb2sucHJvcF0gKz0gZm9yY2UgKiBkZWx0YVxuXHRcdFxuXHRcdFxuXHRcdCMgQWRkIHZlbG9jaXRpZXMgdG8gcHJvcGVydGllcy4gRG9pbmcgdGhpcyBhdCB0aGUgZW5kIGluIGNhc2UgdGhlcmUgYXJlIG11bHRpcGxlIGhvb2tzIGFmZmVjdGluZyB0aGUgc2FtZSB2ZWxvY2l0eVxuXHRcdGZvciBwcm9wLCB2ZWxvY2l0eSBvZiBAaG9va3MudmVsb2NpdGllc1xuXHRcdFxuXHRcdFx0aWYgcHJvcCBpcyAncG9zJ1xuXG5cdFx0XHRcdCMgQWRkIGRyYWcsIGlmIGl0IGV4aXN0c1xuXHRcdFx0XHRpZiBkcmFnLnBvc1xuXHRcdFx0XHRcdHZlbG9jaXR5LnggKz0gQGhvb2tzLmRlZnMuZ2V0RHJhZyh2ZWxvY2l0eS54LCBkcmFnLnBvcywgQGhvb2tzLmRlZnMuem9vbSlcblx0XHRcdFx0XHR2ZWxvY2l0eS55ICs9IEBob29rcy5kZWZzLmdldERyYWcodmVsb2NpdHkueSwgZHJhZy5wb3MsIEBob29rcy5kZWZzLnpvb20pXG5cdFx0XHRcdFx0XG5cdFx0XHRcdCMgQWRkIGFjY2VsZXJhdGlvbiB0byB2ZWxvY2l0eVxuXHRcdFx0XHR2ZWxvY2l0eS54ICs9IGFjY2VsZXJhdGlvbi5wb3MueFxuXHRcdFx0XHR2ZWxvY2l0eS55ICs9IGFjY2VsZXJhdGlvbi5wb3MueVxuXHRcdFx0XHRcblx0XHRcdFx0IyBBZGQgdmVsb2NpdHkgdG8gcG9zaXRpb25cblx0XHRcdFx0QHggKz0gdmVsb2NpdHkueCAqIGRlbHRhXG5cdFx0XHRcdEB5ICs9IHZlbG9jaXR5LnkgKiBkZWx0YVxuXHRcdFx0XG5cdFx0XHRlbHNlXG5cdFx0XHRcblx0XHRcdFx0IyBBZGQgZHJhZywgaWYgaXQgZXhpc3RzXG5cdFx0XHRcdGlmIGRyYWdbcHJvcF1cblx0XHRcdFx0XHRAaG9va3MudmVsb2NpdGllc1twcm9wXSArPSBAaG9va3MuZGVmcy5nZXREcmFnKEBob29rcy52ZWxvY2l0aWVzW3Byb3BdLCBkcmFnW3Byb3BdLCBAaG9va3MuZGVmcy56b29tKVxuXHRcdFx0XHRcblx0XHRcdFx0IyBBZGQgYWNjZWxlcmF0aW9uIHRvIHZlbG9jaXR5XG5cdFx0XHRcdEBob29rcy52ZWxvY2l0aWVzW3Byb3BdICs9IGFjY2VsZXJhdGlvbltwcm9wXVxuXHRcdFx0XHRcblx0XHRcdFx0IyBBZGQgdmVsb2NpdHkgdG8gcHJvcGVydHlcblx0XHRcdFx0QFtwcm9wXSArPSBAaG9va3MudmVsb2NpdGllc1twcm9wXSAqIGRlbHRhXG5cblx0XHRAb25Ib29rVXBkYXRlPyhkZWx0YSkiXX0=
346 |
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/images/cursor-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-modulator.framer/framer/images/cursor-active.png
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/images/cursor-active@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-modulator.framer/framer/images/cursor-active@2x.png
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/images/cursor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-modulator.framer/framer/images/cursor.png
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/images/cursor@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-modulator.framer/framer/images/cursor@2x.png
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/images/icon-120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-modulator.framer/framer/images/icon-120.png
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/images/icon-152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-modulator.framer/framer/images/icon-152.png
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/images/icon-180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-modulator.framer/framer/images/icon-180.png
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/images/icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-modulator.framer/framer/images/icon-192.png
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/images/icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-modulator.framer/framer/images/icon-76.png
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/manifest.txt:
--------------------------------------------------------------------------------
1 | app.coffee
2 | framer/coffee-script.js
3 | framer/config.json
4 | framer/framer.generated.js
5 | framer/framer.init.js
6 | framer/framer.js
7 | framer/framer.js.map
8 | framer/framer.modules.js
9 | framer/images/cursor-active.png
10 | framer/images/cursor-active@2x.png
11 | framer/images/cursor.png
12 | framer/images/cursor@2x.png
13 | framer/images/icon-120.png
14 | framer/images/icon-152.png
15 | framer/images/icon-180.png
16 | framer/images/icon-192.png
17 | framer/images/icon-76.png
18 | framer/preview.png
19 | framer/style.css
20 | framer/version
21 | images/spinner.svg
22 | imported/app@2x/images/Layer-page-meneqzew.png
23 | imported/app@2x/layers.json
24 | imported/app@2x/layers.json.js
25 | index.html
26 | modules/Hook.coffee
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-modulator.framer/framer/preview.png
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | border: none;
5 | -webkit-user-select: none;
6 | -webkit-tap-highlight-color: rgba(0,0,0,0);
7 | }
8 |
9 | body {
10 | background-color: #fff;
11 | font: 28px/1em "Helvetica";
12 | color: gray;
13 | overflow: hidden;
14 | }
15 |
16 | a {
17 | color: gray;
18 | }
19 |
20 | body {
21 | cursor: url('images/cursor.png') 32 32, auto;
22 | cursor: -webkit-image-set(
23 | url('images/cursor.png') 1x,
24 | url('images/cursor@2x.png') 2x
25 | ) 32 32, auto;
26 | }
27 |
28 | body:active {
29 | cursor: url('images/cursor-active.png') 32 32, auto;
30 | cursor: -webkit-image-set(
31 | url('images/cursor-active.png') 1x,
32 | url('images/cursor-active@2x.png') 2x
33 | ) 32 32, auto;
34 | }
35 |
36 | .framerAlertBackground {
37 | position: absolute; top:0px; left:0px; right:0px; bottom:0px;
38 | z-index: 1000;
39 | background-color: #fff;
40 | }
41 |
42 | .framerAlert {
43 | font:400 14px/1.4 "Helvetica Neue", Helvetica, Arial, sans-serif;
44 | -webkit-font-smoothing:antialiased;
45 | color:#616367; text-align:center;
46 | position: absolute; top:40%; left:50%; width:260px; margin-left:-130px;
47 | }
48 | .framerAlert strong { font-weight:500; color:#000; margin-bottom:8px; display:block; }
49 | .framerAlert a { color:#28AFFA; }
50 | .framerAlert .btn {
51 | font-weight:500; text-decoration:none; line-height:1;
52 | display:inline-block; padding:6px 12px 7px 12px;
53 | border-radius:3px; margin-top:12px;
54 | background:#28AFFA; color:#fff;
55 | }
56 |
57 | ::-webkit-scrollbar {
58 | display: none;
59 | }
--------------------------------------------------------------------------------
/hook-example-modulator.framer/framer/version:
--------------------------------------------------------------------------------
1 | 4
--------------------------------------------------------------------------------
/hook-example-modulator.framer/images/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-modulator.framer/images/.gitkeep
--------------------------------------------------------------------------------
/hook-example-modulator.framer/images/spinner.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/hook-example-modulator.framer/imported/app@2x/images/Layer-page-meneqzew.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-modulator.framer/imported/app@2x/images/Layer-page-meneqzew.png
--------------------------------------------------------------------------------
/hook-example-modulator.framer/imported/app@2x/layers.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "objectId": "0CDC1067-9F1A-4B22-8CF3-3B8FEE516032",
4 | "kind": "group",
5 | "name": "page",
6 | "maskFrame": null,
7 | "layerFrame": {
8 | "x": 0,
9 | "y": 0,
10 | "width": 375,
11 | "height": 667
12 | },
13 | "visible": true,
14 | "metadata": {
15 | "opacity": 1
16 | },
17 | "image": {
18 | "path": "images/Layer-page-meneqzew.png",
19 | "frame": {
20 | "x": 0,
21 | "y": 0,
22 | "width": 375,
23 | "height": 667
24 | }
25 | },
26 | "children": [],
27 | "time": 123
28 | }
29 | ]
--------------------------------------------------------------------------------
/hook-example-modulator.framer/imported/app@2x/layers.json.js:
--------------------------------------------------------------------------------
1 | window.__imported__ = window.__imported__ || {};
2 | window.__imported__["app@2x/layers.json.js"] = [
3 | {
4 | "objectId": "0CDC1067-9F1A-4B22-8CF3-3B8FEE516032",
5 | "kind": "group",
6 | "name": "page",
7 | "maskFrame": null,
8 | "layerFrame": {
9 | "x": 0,
10 | "y": 0,
11 | "width": 375,
12 | "height": 667
13 | },
14 | "visible": true,
15 | "metadata": {
16 | "opacity": 1
17 | },
18 | "image": {
19 | "path": "images/Layer-page-meneqzew.png",
20 | "frame": {
21 | "x": 0,
22 | "y": 0,
23 | "width": 375,
24 | "height": 667
25 | }
26 | },
27 | "children": [],
28 | "time": 123
29 | }
30 | ]
--------------------------------------------------------------------------------
/hook-example-modulator.framer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/hook-example-modulator.framer/modules/Hook.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | --------------------------------------------------------------------------------
3 | Hook module for Framer
4 | --------------------------------------------------------------------------------
5 |
6 | by: Sigurd Mannsåker
7 | github: https://github.com/sigtm/framer-hook
8 |
9 | ················································································
10 |
11 |
12 | The Hook module simply expands the Layer prototype, and lets you make any
13 | numeric Layer property follow another property - either its own or another
14 | object's - via a spring or gravity attraction.
15 |
16 |
17 | --------------------------------------------------------------------------------
18 | Example: Layered animation (eased + spring)
19 | --------------------------------------------------------------------------------
20 |
21 | myLayer = new Layer
22 |
23 | # Make our own custom property for the x property to follow
24 | myLayer.easedX = 0
25 |
26 | # Hook x to easedX via a spring
27 | myLayer.hook
28 | property: "x"
29 | targetProperty: "easedX"
30 | type: "spring(150, 15)"
31 |
32 | # Animate easedX
33 | myLayer.animate
34 | properties:
35 | easedX: 200
36 | time: 0.15
37 | curve: "cubic-bezier(0.2, 0, 0.4, 1)"
38 |
39 | NOTE:
40 | To attach both the x and y position, use "pos", "midPos" or "maxPos" as the
41 | property/targetProperty.
42 |
43 |
44 | --------------------------------------------------------------------------------
45 | Example: Hooking property to another layer
46 | --------------------------------------------------------------------------------
47 |
48 | target = new Layer
49 | hooked = new Layer
50 |
51 | hooked.hook
52 | property: "scale"
53 | to: target
54 | type: "spring(150, 15)"
55 |
56 | The "hooked" layer's scale will now continuously follow the target layer's scale
57 | with a spring animation.
58 |
59 |
60 | --------------------------------------------------------------------------------
61 | layer.hook(options)
62 | --------------------------------------------------------------------------------
63 |
64 | Options are passed as a single object, like you would for a new Layer.
65 | The options object takes the following properties:
66 |
67 |
68 | property [String]
69 | -----------------
70 | The property you'd like to hook onto another object's property
71 |
72 |
73 | type [String]
74 | -------------
75 | Either "spring(strength, friction)" or "gravity(strength, drag)". Only the last
76 | specified drag value is used for each property, since it is only applied to
77 | each property once (and only if it has a gravity hook applied to it.)
78 |
79 |
80 | to [Object] (Optional)
81 | ----------------------
82 | The object to attach it to. Defaults to itself.
83 |
84 |
85 | targetProperty [String] (Optional)
86 | ----------------------------------
87 | Specify the target object's property to follow, if you don't want to follow
88 | the same property that the hook is applied to.
89 |
90 |
91 | modulator [Function] (Optional)
92 | -------------------------------
93 | The modulator function receives the target property's value, and lets you
94 | modify it before it is fed into the physics calculations. Useful for anything
95 | from standard Utils.modulate() type stuff to snapping and conditional values.
96 |
97 |
98 | zoom [Number] (Optional)
99 | ------------------------
100 | This factor defines the distance that 1px represents in regards to gravity and
101 | drag calculations. Only one value is stored per layer, so specifying it
102 | overwrites its existing value. Default is 100.
103 |
104 |
105 | --------------------------------------------------------------------------------
106 | layer.unHook(property, object)
107 | --------------------------------------------------------------------------------
108 |
109 | This removes all hooks for a given property and target object. Example:
110 |
111 | # Hook it
112 | layer.hook
113 | property: "x"
114 | to: "otherlayer"
115 | targetProperty: "y"
116 | type: "spring(200,20)"
117 |
118 | # Unhook it
119 | layer.unHook "x", otherlayer
120 |
121 |
122 | --------------------------------------------------------------------------------
123 | layer.onHookUpdate(delta)
124 | --------------------------------------------------------------------------------
125 |
126 | After a layer is done applying accelerations to its hooked properties, it calls
127 | onHookUpdate() at the end of each frame, if it is defined. This is an easy way
128 | to animate or trigger other stuff, perhaps based on your layer's updated
129 | properties or velocities.
130 |
131 | The delta value from the Framer loop is passed on to onHookUpdate() as well,
132 | which is the time in seconds since the last animation frame.
133 |
134 | Note that if you unhook all your hooks, onHookUpdate() will of course no longer
135 | be called for that layer.
136 |
137 | ###
138 |
139 |
140 | # Since older versions of Safari seem to be missing String.prototype.includes()
141 |
142 | unless String.prototype.includes
143 | String::includes = (search, start) ->
144 | 'use strict'
145 | start = 0 if typeof start is 'number'
146 |
147 | if start + search.length > this.length
148 | return false;
149 | else
150 | return @indexOf(search, start) isnt -1
151 |
152 | # Expand layer
153 |
154 | Layer::hook = (config) ->
155 |
156 | throw new Error 'layer.hook() needs a property, a hook type and either a target object or target property to work' unless config.property and config.type and (config.to or config.targetProperty)
157 |
158 | # Single array for all hooks, as opposed to nested arrays per property, because performance
159 | @hooks ?=
160 | hooks: []
161 | velocities: {}
162 | defs:
163 | zoom: 100
164 | getDrag: (velocity, drag, zoom) =>
165 | velocity /= zoom
166 | # Dividing by 10 is unscientific, but it means a value of 2 equals roughly a 100g ball with 15cm radius in air
167 | drag = -(drag / 10) * velocity * velocity * velocity / Math.abs(velocity)
168 | if _.isNaN(drag) then return 0 else return drag
169 | getGravity: (strength, distance, zoom) =>
170 | dist = Math.max(1, distance / zoom)
171 | return strength * zoom / (dist * dist)
172 |
173 | # Update the zoom value if given
174 | @hooks.zoom = config.zoom if config.zoom
175 |
176 | # Parse physics config string
177 | f = Utils.parseFunction config.type
178 | config.type = f.name
179 | config.strength = f.args[0]
180 | config.friction = f.args[1] or 0
181 |
182 | # Default to same targetProperty on same object (hopefully you've set at least one of these to something else)
183 | config.targetProperty ?= config.property
184 | config.to ?= @
185 |
186 | # All position accelerations are added to a single 'pos' velocity. Store actual properties so we don't have to do it again every frame
187 |
188 | if config.property.toLowerCase().includes 'pos'
189 | config.prop = 'pos'
190 |
191 | if config.property.toLowerCase().includes 'mid'
192 | config.thisX = 'midX'
193 | config.thisY = 'midY'
194 |
195 | else if config.property.toLowerCase().includes 'max'
196 | config.thisX = 'maxX'
197 | config.thisY = 'maxY'
198 |
199 | else
200 | config.thisX = 'x'
201 | config.thisY = 'y'
202 |
203 | if config.targetProperty.toLowerCase().includes 'mid'
204 | config.toX = 'midX'
205 | config.toY = 'midY'
206 |
207 | else if config.targetProperty.toLowerCase().includes 'max'
208 | config.toX = 'maxX'
209 | config.toY = 'maxY'
210 | else
211 | config.toX = 'x'
212 | config.toY = 'y'
213 |
214 | else
215 | config.prop = config.property
216 |
217 | # Save hook to @hooks array
218 | @hooks.hooks.push(config)
219 |
220 | # Create velocity property if necessary
221 | @hooks.velocities[config.prop] ?= if config.prop is 'pos' then { x: 0, y: 0 } else 0
222 |
223 | # Use Framer's animation loop, slightly more robust than requestAnimationFrame directly
224 | # Save the returned AnimationLoop reference to make sure @hookLoop isn't added multiple times per layer
225 | @hooks.emitter ?= Framer.Loop.on('render', @hookLoop, this)
226 |
227 | Layer::unHook = (property, object) ->
228 |
229 | return unless @hooks
230 |
231 | prop = if property.toLowerCase().includes 'pos' then 'pos' else property
232 |
233 | # Remove all matches
234 | @hooks.hooks = @hooks.hooks.filter (hook) ->
235 | hook.to isnt object or hook.property isnt property
236 |
237 | # If there are no hooks left, shut it down
238 | if @hooks.hooks.length is 0
239 | delete @hooks
240 | Framer.Loop.removeListener 'render', @hookLoop
241 | return
242 |
243 | # Still here? Check if there are any remaining hooks affecting same velocity
244 | remaining = @hooks.hooks.filter (hook) ->
245 | prop is hook.prop
246 |
247 | # If not, delete velocity (otherwise it won't be reset if you make new hook for same property)
248 | delete @hooks.velocities[prop] if remaining.length is 0
249 |
250 | Layer::hookLoop = (delta) ->
251 |
252 | if @hooks
253 |
254 | # Multiple hooks can affect the same property. Add accelerations to temporary object so the property's velocity is the same for all calculations within the same animation frame
255 | acceleration = {}
256 |
257 | # Save drag for each property to this object, since only most recently specified value is used for each property
258 | drag = {}
259 |
260 | # Add accelerations
261 | for hook in @hooks.hooks
262 |
263 | if hook.prop is 'pos'
264 |
265 | acceleration.pos ?= { x: 0, y: 0 }
266 |
267 | target = { x: hook.to[hook.toX], y: hook.to[hook.toY] }
268 |
269 | target = hook.modulator(target) if hook.modulator
270 |
271 | vector =
272 | x: target.x - @[hook.thisX]
273 | y: target.y - @[hook.thisY]
274 |
275 | vLength = Math.sqrt((vector.x * vector.x) + (vector.y * vector.y))
276 |
277 | if hook.type is 'spring'
278 |
279 | damper =
280 | x: -hook.friction * @hooks.velocities.pos.x
281 | y: -hook.friction * @hooks.velocities.pos.y
282 |
283 | vector.x *= hook.strength
284 | vector.y *= hook.strength
285 |
286 | acceleration.pos.x += (vector.x + damper.x) * delta
287 | acceleration.pos.y += (vector.y + damper.y) * delta
288 |
289 | else if hook.type is 'gravity'
290 |
291 | drag.pos = hook.friction
292 |
293 | gravity = @hooks.defs.getGravity(hook.strength, vLength, @hooks.defs.zoom)
294 |
295 | vector.x *= gravity / vLength
296 | vector.y *= gravity / vLength
297 |
298 | acceleration.pos.x += vector.x * delta
299 | acceleration.pos.y += vector.y * delta
300 |
301 | else
302 |
303 | acceleration[hook.prop] ?= 0
304 |
305 | target = hook.to[hook.targetProperty]
306 |
307 | target = hook.modulator(target) if hook.modulator
308 |
309 | vector = target - @[hook.prop]
310 |
311 | if hook.type is 'spring'
312 |
313 | force = vector * hook.strength
314 | damper = -hook.friction * @hooks.velocities[hook.prop]
315 |
316 | acceleration[hook.prop] += (force + damper) * delta
317 |
318 |
319 | else if hook.type is 'gravity'
320 |
321 | drag[hook.prop] = hook.friction
322 |
323 | force = @hooks.defs.getGravity(hook.strength, vector, @hooks.defs.zoom)
324 |
325 | acceleration[hook.prop] += force * delta
326 |
327 |
328 | # Add velocities to properties. Doing this at the end in case there are multiple hooks affecting the same velocity
329 | for prop, velocity of @hooks.velocities
330 |
331 | if prop is 'pos'
332 |
333 | # Add drag, if it exists
334 | if drag.pos
335 | velocity.x += @hooks.defs.getDrag(velocity.x, drag.pos, @hooks.defs.zoom)
336 | velocity.y += @hooks.defs.getDrag(velocity.y, drag.pos, @hooks.defs.zoom)
337 |
338 | # Add acceleration to velocity
339 | velocity.x += acceleration.pos.x
340 | velocity.y += acceleration.pos.y
341 |
342 | # Add velocity to position
343 | @x += velocity.x * delta
344 | @y += velocity.y * delta
345 |
346 | else
347 |
348 | # Add drag, if it exists
349 | if drag[prop]
350 | @hooks.velocities[prop] += @hooks.defs.getDrag(@hooks.velocities[prop], drag[prop], @hooks.defs.zoom)
351 |
352 | # Add acceleration to velocity
353 | @hooks.velocities[prop] += acceleration[prop]
354 |
355 | # Add velocity to property
356 | @[prop] += @hooks.velocities[prop] * delta
357 |
358 | @onHookUpdate?(delta)
--------------------------------------------------------------------------------
/hook-example-spring.framer/.gitignore:
--------------------------------------------------------------------------------
1 | # Framer Git Ignore
2 |
3 | # General OSX
4 |
5 | .DS_Store
6 | .AppleDouble
7 | .LSOverride
8 |
9 | # Icon must end with two \r
10 | Icon
11 |
12 |
13 | # Thumbnails
14 | ._*
15 |
16 | # Files that might appear in the root of a volume
17 | .DocumentRevisions-V100
18 | .fseventsd
19 | .Spotlight-V100
20 | .TemporaryItems
21 | .Trashes
22 | .VolumeIcon.icns
23 |
24 | # Directories potentially created on remote AFP share
25 | .AppleDB
26 | .AppleDesktop
27 | Network Trash Folder
28 | Temporary Items
29 | .apdisk
30 |
31 |
32 | # Framer Specific
33 | .temp.html
34 | framer/*.old*
35 | framer/backup.coffee
36 | framer/backups/*
37 | framer/.*.hash
38 |
--------------------------------------------------------------------------------
/hook-example-spring.framer/.viewer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/hook-example-spring.framer/app.coffee:
--------------------------------------------------------------------------------
1 | ###
2 |
3 | Hook example
4 | --------------
5 |
6 | Note: This example uses two layers to illustrate the concept more clearly, but
7 | the separate layer for the eased animation is unnecessary. We could just as easily
8 | have created another property, say layer.easedX, and hooked the x property to that.
9 | Then you'd just run layer.animate() on the easedX property like you would any
10 | other property.
11 |
12 | The Hook module simply expands the Layer prototype, and lets you make any
13 | numeric Layer property follow a property on another object via a spring or a
14 | gravity attraction. Check the comments in modules/Hook.coffee for a more thorough
15 | documentation.
16 |
17 | ###
18 |
19 |
20 |
21 | # Require Hook. No exports, it just adds methods to the Layer prototype
22 | # --------------------------------------------------------------------------------
23 |
24 | require "Hook"
25 |
26 |
27 |
28 | # Settings
29 | # --------------------------------------------------------------------------------
30 |
31 | colors =
32 | background: "#260355"
33 | orange: "#f90"
34 | blue: "#63ffff"
35 | primary: "white"
36 | secondary: "rgba(255,255,255,0.1)"
37 |
38 | margin = 60
39 |
40 | easeInOut = "cubic-bezier(0.2, 0, 0.4, 1)"
41 |
42 |
43 |
44 | # Set background and defaults
45 | # --------------------------------------------------------------------------------
46 |
47 | Framer.Device.viewport.backgroundColor = colors.background
48 |
49 | Framer.Defaults.Animation =
50 | curve: easeInOut
51 | time: 0.15
52 |
53 |
54 |
55 | # Set up example
56 | # --------------------------------------------------------------------------------
57 |
58 | eased = new Layer
59 | x: margin
60 | y: 300
61 | width: 100
62 | height: 100
63 | backgroundColor: colors.blue
64 | borderRadius: 12
65 |
66 | eased.states.add
67 | right:
68 | maxX: Screen.width - margin
69 |
70 | hooked = new Layer
71 | x: eased.x
72 | y: eased.maxY + margin
73 | width: 100
74 | height: 100
75 | backgroundColor: colors.orange
76 | borderRadius: 12
77 |
78 |
79 | # We run hook() in the slider change callback
80 | runAnim = () ->
81 |
82 | # Remove any existing hooks, since we want updated spring values
83 | hooked.unHook 'x', eased
84 |
85 | # Attach the x property to the eased layer with the sliders' spring values
86 | hooked.hook
87 | property: 'x'
88 | to: eased
89 | type: 'spring(' + spring.value + ', ' + friction.value + ')'
90 |
91 | # Animate the eased layer
92 | eased.states.animationOptions =
93 | curve: easeInOut
94 | time: speed.value / 1000
95 |
96 | eased.states.next()
97 |
98 |
99 |
100 | # Styled slider class
101 | # --------------------------------------------------------------------------------
102 |
103 | class Slider extends SliderComponent
104 |
105 | constructor: (config) ->
106 | super config
107 |
108 | # Basics
109 | @x = margin
110 | @width = Screen.width - margin * 2
111 | @height = 4
112 | @fill.backgroundColor = colors.primary
113 | @backgroundColor = colors.secondary
114 | @value = config.value
115 | @unitName = config.unit or ''
116 |
117 | @baseStyle =
118 | fontFamily: "Roboto"
119 | fontWeight: 500
120 | fontSize: "26px"
121 |
122 | # Knob
123 | @knobSize = 24
124 | @knob.backgroundColor = colors.primary
125 | @knob.borderRadius = 30
126 | @knob.shadowColor = colors.background
127 | @knob.shadowBlur = 0
128 | @knob.shadowSpread = 6
129 |
130 | # Name label
131 | @txtLabel = new Layer
132 | parent: @
133 | name: "label"
134 | y: -50
135 | backgroundColor: ""
136 | html: config.label
137 | color: colors.primary
138 | style: @baseStyle
139 |
140 | @txtLabel.style.fontStyle = "italic"
141 |
142 | # Value label
143 | @txtValue = new Layer
144 | parent: @
145 | name: "value"
146 | maxX: @width
147 | y: -50
148 | backgroundColor: ""
149 | html: Math.round(@value) + ' ' + @unitName
150 | color: config.valueColor or colors.orange
151 | style: @baseStyle
152 |
153 | @txtValue.style.textAlign = "right"
154 |
155 | # Events
156 |
157 | @onValueChange ->
158 | @txtValue.html = Math.round(@value) + ' ' + @unitName
159 |
160 | @onTouchStart ->
161 | @knob.animate
162 | properties:
163 | scale: 1.4
164 |
165 | @onTouchEnd ->
166 | runAnim()
167 | @knob.animate
168 | properties:
169 | scale: 1
170 |
171 |
172 |
173 | # Instantiate sliders
174 | # --------------------------------------------------------------------------------
175 |
176 | friction = new Slider
177 | label: "Friction"
178 | maxY: Screen.height - margin * 1.5
179 | min: 0
180 | max: 50
181 | value: 18
182 |
183 | spring = new Slider
184 | label: "Spring"
185 | y: friction.y - margin * 2
186 | min: 0
187 | max: 200
188 | value: 150
189 |
190 | speed = new Slider
191 | label: "Time"
192 | unit: "ms"
193 | valueColor: colors.blue
194 | y: spring.y - margin * 2
195 | min: 0
196 | max: 1000
197 | value: 200
198 |
199 |
200 |
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "propertyPanelToggleStates" : {
3 |
4 | },
5 | "deviceOrientation" : 0,
6 | "sharedPrototype" : 0,
7 | "contentScale" : 1,
8 | "deviceType" : "apple-iphone-6s-silver",
9 | "selectedHand" : "",
10 | "updateDelay" : 0.3,
11 | "deviceScale" : "fit",
12 | "foldedCodeRanges" : [
13 |
14 | ],
15 | "orientation" : 0,
16 | "fullScreen" : false
17 | }
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/framer.generated.js:
--------------------------------------------------------------------------------
1 | // This is autogenerated by Framer
2 |
3 |
4 | if (!window.Framer && window._bridge) {window._bridge('runtime.error', {message:'[framer.js] Framer library missing or corrupt. Select File → Update Framer Library.'})}
5 | if (DeviceComponent) {DeviceComponent.Devices["iphone-6-silver"].deviceImageJP2 = false};
6 | if (window.Framer) {window.Framer.Defaults.DeviceView = {"deviceScale":"fit","selectedHand":"","deviceType":"apple-iphone-6s-silver","contentScale":1,"orientation":0};
7 | }
8 | if (window.Framer) {window.Framer.Defaults.DeviceComponent = {"deviceScale":"fit","selectedHand":"","deviceType":"apple-iphone-6s-silver","contentScale":1,"orientation":0};
9 | }
10 | window.FramerStudioInfo = {"deviceImagesUrl":"\/_server\/resources\/DeviceImages","documentTitle":"hook-example-spring.framer"};
11 |
12 | Framer.Device = new Framer.DeviceView();
13 | Framer.Device.setupContext();
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/framer.init.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | function isFileLoadingAllowed() {
4 | return (window.location.protocol.indexOf("file") == -1)
5 | }
6 |
7 | function isHomeScreened() {
8 | return ("standalone" in window.navigator) && window.navigator.standalone == true
9 | }
10 |
11 | function isCompatibleBrowser() {
12 | return Utils.isWebKit()
13 | }
14 |
15 | var alertNode;
16 |
17 | function dismissAlert() {
18 | alertNode.parentElement.removeChild(alertNode)
19 | loadProject()
20 | }
21 |
22 | function showAlert(html) {
23 |
24 | alertNode = document.createElement("div")
25 |
26 | alertNode.classList.add("framerAlertBackground")
27 | alertNode.innerHTML = html
28 |
29 | document.addEventListener("DOMContentLoaded", function(event) {
30 | document.body.appendChild(alertNode)
31 | })
32 |
33 | window.dismissAlert = dismissAlert;
34 | }
35 |
36 | function showBrowserAlert() {
37 | var html = ""
38 | html += "
"
39 | html += "Error: Not A WebKit Browser"
40 | html += "Your browser is not supported. Please use Safari or Chrome. "
41 | html += "Try anyway"
42 | html += "
"
43 |
44 | showAlert(html)
45 | }
46 |
47 | function showFileLoadingAlert() {
48 | var html = ""
49 | html += "
"
50 | html += "Error: Local File Restrictions"
51 | html += "Preview this prototype with Framer Mirror or learn more about "
52 | html += "file restrictions. "
53 | html += "Try anyway"
54 | html += "
"
55 |
56 | showAlert(html)
57 | }
58 |
59 | function loadProject() {
60 | CoffeeScript.load("app.coffee")
61 | }
62 |
63 | function setDefaultPageTitle() {
64 | // If no title was set we set it to the project folder name so
65 | // you get a nice name on iOS if you bookmark to desktop.
66 | document.addEventListener("DOMContentLoaded", function() {
67 | if (document.title == "") {
68 | if (window.FramerStudioInfo && window.FramerStudioInfo.documentTitle) {
69 | document.title = window.FramerStudioInfo.documentTitle
70 | } else {
71 | document.title = window.location.pathname.replace(/\//g, "")
72 | }
73 | }
74 | })
75 | }
76 |
77 | function init() {
78 |
79 | if (Utils.isFramerStudio()) {
80 | return
81 | }
82 |
83 | setDefaultPageTitle()
84 |
85 | if (!isCompatibleBrowser()) {
86 | return showBrowserAlert()
87 | }
88 |
89 | if (!isFileLoadingAllowed()) {
90 | return showFileLoadingAlert()
91 | }
92 |
93 | loadProject()
94 |
95 | }
96 |
97 | init()
98 |
99 | })()
100 |
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/framer.modules.js:
--------------------------------------------------------------------------------
1 | require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o this.length) {
146 | return false;
147 | } else {
148 | return this.indexOf(search, start) !== -1;
149 | }
150 | };
151 | }
152 |
153 | Layer.prototype.hook = function(config) {
154 | var base, base1, f, name;
155 | if (!(config.property && config.type && (config.to || config.targetProperty))) {
156 | throw new Error('layer.hook() needs a property, a hook type and either a target object or target property to work');
157 | }
158 | if (this.hooks == null) {
159 | this.hooks = {
160 | hooks: [],
161 | velocities: {},
162 | defs: {
163 | zoom: 100,
164 | getDrag: (function(_this) {
165 | return function(velocity, drag, zoom) {
166 | velocity /= zoom;
167 | drag = -(drag / 10) * velocity * velocity * velocity / Math.abs(velocity);
168 | if (_.isNaN(drag)) {
169 | return 0;
170 | } else {
171 | return drag;
172 | }
173 | };
174 | })(this),
175 | getGravity: (function(_this) {
176 | return function(strength, distance, zoom) {
177 | var dist;
178 | dist = Math.max(1, distance / zoom);
179 | return strength * zoom / (dist * dist);
180 | };
181 | })(this)
182 | }
183 | };
184 | }
185 | if (config.zoom) {
186 | this.hooks.zoom = config.zoom;
187 | }
188 | f = Utils.parseFunction(config.type);
189 | config.type = f.name;
190 | config.strength = f.args[0];
191 | config.friction = f.args[1] || 0;
192 | if (config.targetProperty == null) {
193 | config.targetProperty = config.property;
194 | }
195 | if (config.to == null) {
196 | config.to = this;
197 | }
198 | if (config.property.toLowerCase().includes('pos')) {
199 | config.prop = 'pos';
200 | if (config.property.toLowerCase().includes('mid')) {
201 | config.thisX = 'midX';
202 | config.thisY = 'midY';
203 | } else if (config.property.toLowerCase().includes('max')) {
204 | config.thisX = 'maxX';
205 | config.thisY = 'maxY';
206 | } else {
207 | config.thisX = 'x';
208 | config.thisY = 'y';
209 | }
210 | if (config.targetProperty.toLowerCase().includes('mid')) {
211 | config.toX = 'midX';
212 | config.toY = 'midY';
213 | } else if (config.targetProperty.toLowerCase().includes('max')) {
214 | config.toX = 'maxX';
215 | config.toY = 'maxY';
216 | } else {
217 | config.toX = 'x';
218 | config.toY = 'y';
219 | }
220 | } else {
221 | config.prop = config.property;
222 | }
223 | this.hooks.hooks.push(config);
224 | if ((base = this.hooks.velocities)[name = config.prop] == null) {
225 | base[name] = config.prop === 'pos' ? {
226 | x: 0,
227 | y: 0
228 | } : 0;
229 | }
230 | return (base1 = this.hooks).emitter != null ? base1.emitter : base1.emitter = Framer.Loop.on('render', this.hookLoop, this);
231 | };
232 |
233 | Layer.prototype.unHook = function(property, object) {
234 | var prop, remaining;
235 | if (!this.hooks) {
236 | return;
237 | }
238 | prop = property.toLowerCase().includes('pos') ? 'pos' : property;
239 | this.hooks.hooks = this.hooks.hooks.filter(function(hook) {
240 | return hook.to !== object || hook.property !== property;
241 | });
242 | if (this.hooks.hooks.length === 0) {
243 | delete this.hooks;
244 | Framer.Loop.removeListener('render', this.hookLoop);
245 | return;
246 | }
247 | remaining = this.hooks.hooks.filter(function(hook) {
248 | return prop === hook.prop;
249 | });
250 | if (remaining.length === 0) {
251 | return delete this.hooks.velocities[prop];
252 | }
253 | };
254 |
255 | Layer.prototype.hookLoop = function(delta) {
256 | var acceleration, damper, drag, force, gravity, hook, i, len, name, prop, ref, ref1, target, vLength, vector, velocity;
257 | if (this.hooks) {
258 | acceleration = {};
259 | drag = {};
260 | ref = this.hooks.hooks;
261 | for (i = 0, len = ref.length; i < len; i++) {
262 | hook = ref[i];
263 | if (hook.prop === 'pos') {
264 | if (acceleration.pos == null) {
265 | acceleration.pos = {
266 | x: 0,
267 | y: 0
268 | };
269 | }
270 | target = {
271 | x: hook.to[hook.toX],
272 | y: hook.to[hook.toY]
273 | };
274 | if (hook.modulator) {
275 | target = hook.modulator(target);
276 | }
277 | vector = {
278 | x: target.x - this[hook.thisX],
279 | y: target.y - this[hook.thisY]
280 | };
281 | vLength = Math.sqrt((vector.x * vector.x) + (vector.y * vector.y));
282 | if (hook.type === 'spring') {
283 | damper = {
284 | x: -hook.friction * this.hooks.velocities.pos.x,
285 | y: -hook.friction * this.hooks.velocities.pos.y
286 | };
287 | vector.x *= hook.strength;
288 | vector.y *= hook.strength;
289 | acceleration.pos.x += (vector.x + damper.x) * delta;
290 | acceleration.pos.y += (vector.y + damper.y) * delta;
291 | } else if (hook.type === 'gravity') {
292 | drag.pos = hook.friction;
293 | gravity = this.hooks.defs.getGravity(hook.strength, vLength, this.hooks.defs.zoom);
294 | vector.x *= gravity / vLength;
295 | vector.y *= gravity / vLength;
296 | acceleration.pos.x += vector.x * delta;
297 | acceleration.pos.y += vector.y * delta;
298 | }
299 | } else {
300 | if (acceleration[name = hook.prop] == null) {
301 | acceleration[name] = 0;
302 | }
303 | target = hook.to[hook.targetProperty];
304 | if (hook.modulator) {
305 | target = hook.modulator(target);
306 | }
307 | vector = target - this[hook.prop];
308 | if (hook.type === 'spring') {
309 | force = vector * hook.strength;
310 | damper = -hook.friction * this.hooks.velocities[hook.prop];
311 | acceleration[hook.prop] += (force + damper) * delta;
312 | } else if (hook.type === 'gravity') {
313 | drag[hook.prop] = hook.friction;
314 | force = this.hooks.defs.getGravity(hook.strength, vector, this.hooks.defs.zoom);
315 | acceleration[hook.prop] += force * delta;
316 | }
317 | }
318 | }
319 | ref1 = this.hooks.velocities;
320 | for (prop in ref1) {
321 | velocity = ref1[prop];
322 | if (prop === 'pos') {
323 | if (drag.pos) {
324 | velocity.x += this.hooks.defs.getDrag(velocity.x, drag.pos, this.hooks.defs.zoom);
325 | velocity.y += this.hooks.defs.getDrag(velocity.y, drag.pos, this.hooks.defs.zoom);
326 | }
327 | velocity.x += acceleration.pos.x;
328 | velocity.y += acceleration.pos.y;
329 | this.x += velocity.x * delta;
330 | this.y += velocity.y * delta;
331 | } else {
332 | if (drag[prop]) {
333 | this.hooks.velocities[prop] += this.hooks.defs.getDrag(this.hooks.velocities[prop], drag[prop], this.hooks.defs.zoom);
334 | }
335 | this.hooks.velocities[prop] += acceleration[prop];
336 | this[prop] += this.hooks.velocities[prop] * delta;
337 | }
338 | }
339 | return typeof this.onHookUpdate === "function" ? this.onHookUpdate(delta) : void 0;
340 | }
341 | };
342 |
343 |
344 | },{}]},{},[])
345 | //# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCIvVXNlcnMvc2lndXJkL1JlcG9zL2ZyYW1lci1ob29rL2hvb2stZXhhbXBsZS1zcHJpbmcuZnJhbWVyL21vZHVsZXMvSG9vay5jb2ZmZWUiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0FDQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUE2SUEsSUFBQSxDQUFPLE1BQU0sQ0FBQyxTQUFTLENBQUMsUUFBeEI7RUFDQyxNQUFNLENBQUEsU0FBRSxDQUFBLFFBQVIsR0FBbUIsU0FBQyxNQUFELEVBQVMsS0FBVDtJQUNsQjtJQUNBLElBQWEsT0FBTyxLQUFQLEtBQWdCLFFBQTdCO01BQUEsS0FBQSxHQUFRLEVBQVI7O0lBRUEsSUFBRyxLQUFBLEdBQVEsTUFBTSxDQUFDLE1BQWYsR0FBd0IsSUFBSSxDQUFDLE1BQWhDO0FBQ0MsYUFBTyxNQURSO0tBQUEsTUFBQTtBQUdDLGFBQU8sSUFBQyxDQUFBLE9BQUQsQ0FBUyxNQUFULEVBQWlCLEtBQWpCLENBQUEsS0FBNkIsQ0FBQyxFQUh0Qzs7RUFKa0IsRUFEcEI7OztBQVlBLEtBQUssQ0FBQSxTQUFFLENBQUEsSUFBUCxHQUFjLFNBQUMsTUFBRDtBQUViLE1BQUE7RUFBQSxJQUFBLENBQUEsQ0FBMEgsTUFBTSxDQUFDLFFBQVAsSUFBb0IsTUFBTSxDQUFDLElBQTNCLElBQW9DLENBQUMsTUFBTSxDQUFDLEVBQVAsSUFBYSxNQUFNLENBQUMsY0FBckIsQ0FBOUosQ0FBQTtBQUFBLFVBQVUsSUFBQSxLQUFBLENBQU0sa0dBQU4sRUFBVjs7O0lBR0EsSUFBQyxDQUFBLFFBQ0E7TUFBQSxLQUFB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SyxDQUFDLElBQUksQ0FBQyxJQUF0RCxFQUZmOztRQUtBLFFBQVEsQ0FBQyxDQUFULElBQWMsWUFBWSxDQUFDLEdBQUcsQ0FBQztRQUMvQixRQUFRLENBQUMsQ0FBVCxJQUFjLFlBQVksQ0FBQyxHQUFHLENBQUM7UUFHL0IsSUFBQyxDQUFBLENBQUQsSUFBTSxRQUFRLENBQUMsQ0FBVCxHQUFhO1FBQ25CLElBQUMsQ0FBQSxDQUFELElBQU0sUUFBUSxDQUFDLENBQVQsR0FBYSxNQWJwQjtPQUFBLE1BQUE7UUFrQkMsSUFBRyxJQUFLLENBQUEsSUFBQSxDQUFSO1VBQ0MsSUFBQyxDQUFBLEtBQUssQ0FBQyxVQUFXLENBQUEsSUFBQSxDQUFsQixJQUEyQixJQUFDLENBQUEsS0FBSyxDQUFDLElBQUksQ0FBQyxPQUFaLENBQW9CLElBQUMsQ0FBQSxLQUFLLENBQUMsVUFBVyxDQUFBLElBQUEsQ0FBdEMsRUFBNkMsSUFBSyxDQUFBLElBQUEsQ0FBbEQsRUFBeUQsSUFBQyxDQUFBLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBckUsRUFENUI7O1FBSUEsSUFBQyxDQUFBLEtBQUssQ0FBQyxVQUFXLENBQUEsSUFBQSxDQUFsQixJQUEyQixZQUFhLENBQUEsSUFBQTtRQUd4QyxJQUFFLENBQUEsSUFBQSxDQUFGLElBQVcsSUFBQyxDQUFBLEtBQUssQ0FBQyxVQUFXLENBQUEsSUFBQSxDQUFsQixHQUEwQixNQXpCdEM7O0FBRkQ7cURBNkJBLElBQUMsQ0FBQSxhQUFjLGdCQTFHaEI7O0FBRmlCIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsIiMjI1xuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbkhvb2sgbW9kdWxlIGZvciBGcmFtZXJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbmJ5OiAgICAgIFNpZ3VyZCBNYW5uc8Ola2VyXG5naXRodWI6ICBodHRwczovL2dpdGh1Yi5jb20vc2lndG0vZnJhbWVyLWhvb2tcblxuwrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt8K3wrfCt1xuXG5cblRoZSBIb29rIG1vZHVsZSBzaW1wbHkgZXhwYW5kcyB0aGUgTGF5ZXIgcHJvdG90eXBlLCBhbmQgbGV0cyB5b3UgbWFrZSBhbnlcbm51bWVyaWMgTGF5ZXIgcHJvcGVydHkgZm9sbG93IGFub3RoZXIgcHJvcGVydHkgLSBlaXRoZXIgaXRzIG93biBvciBhbm90aGVyXG5vYmplY3QncyAtIHZpYSBhIHNwcmluZyBvciBncmF2aXR5IGF0dHJhY3Rpb24uXG5cblxuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbkV4YW1wbGU6IExheWVyZWQgYW5pbWF0aW9uIChlYXNlZCArIHNwcmluZylcbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbm15TGF5ZXIgPSBuZXcgTGF5ZXJcblxuIyBNYWtlIG91ciBvd24gY3VzdG9tIHByb3BlcnR5IGZvciB0aGUgeCBwcm9wZXJ0eSB0byBmb2xsb3dcbm15TGF5ZXIuZWFzZWRYID0gMFxuXG4jIEhvb2sgeCB0byBlYXNlZFggdmlhIGEgc3ByaW5nXG5teUxheWVyLmhvb2tcblx0cHJvcGVydHk6IFwieFwiXG5cdHRhcmdldFByb3BlcnR5OiBcImVhc2VkWFwiXG5cdHR5cGU6IFwic3ByaW5nKDE1MCwgMTUpXCJcblxuIyBBbmltYXRlIGVhc2VkWFxubXlMYXllci5hbmltYXRlXG5cdHByb3BlcnRpZXM6XG5cdFx0ZWFzZWRYOiAyMDBcblx0dGltZTogMC4xNVxuXHRjdXJ2ZTogXCJjdWJpYy1iZXppZXIoMC4yLCAwLCAwLjQsIDEpXCJcblxuTk9URTogXG5UbyBhdHRhY2ggYm90aCB0aGUgeCBhbmQgeSBwb3NpdGlvbiwgdXNlIFwicG9zXCIsIFwibWlkUG9zXCIgb3IgXCJtYXhQb3NcIiBhcyB0aGVcbnByb3BlcnR5L3RhcmdldFByb3BlcnR5LlxuXG5cbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5FeGFtcGxlOiBIb29raW5nIHByb3BlcnR5IHRvIGFub3RoZXIgbGF5ZXJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbnRhcmdldCA9IG5ldyBMYXllclxuaG9va2VkID0gbmV3IExheWVyXG5cbmhvb2tlZC5ob29rXG5cdHByb3BlcnR5OiBcInNjYWxlXCJcblx0dG86IHRhcmdldFxuXHR0eXBlOiBcInNwcmluZygxNTAsIDE1KVwiXG5cblRoZSBcImhvb2tlZFwiIGxheWVyJ3Mgc2NhbGUgd2lsbCBub3cgY29udGludW91c2x5IGZvbGxvdyB0aGUgdGFyZ2V0IGxheWVyJ3Mgc2NhbGVcbndpdGggYSBzcHJpbmcgYW5pbWF0aW9uLlxuXG5cbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5sYXllci5ob29rKG9wdGlvbnMpXG4tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuXG5PcHRpb25zIGFyZSBwYXNzZWQgYXMgYSBzaW5nbGUgb2JqZWN0LCBsaWtlIHlvdSB3b3VsZCBmb3IgYSBuZXcgTGF5ZXIuXG5UaGUgb3B0aW9ucyBvYmplY3QgdGFrZXMgdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuXG5cbnByb3BlcnR5IFtTdHJpbmddXG4tLS0tLS0tLS0tLS0tLS0tLVxuVGhlIHByb3BlcnR5IHlvdSdkIGxpa2UgdG8gaG9vayBvbnRvIGFub3RoZXIgb2JqZWN0J3MgcHJvcGVydHlcblxuXG50eXBlIFtTdHJpbmddXG4tLS0tLS0tLS0tLS0tXG5FaXRoZXIgXCJzcHJpbmcoc3RyZW5ndGgsIGZyaWN0aW9uKVwiIG9yIFwiZ3Jhdml0eShzdHJlbmd0aCwgZHJhZylcIi4gT25seSB0aGUgbGFzdFxuc3BlY2lmaWVkIGRyYWcgdmFsdWUgaXMgdXNlZCBmb3IgZWFjaCBwcm9wZXJ0eSwgc2luY2UgaXQgaXMgb25seSBhcHBsaWVkIHRvXG5lYWNoIHByb3BlcnR5IG9uY2UgKGFuZCBvbmx5IGlmIGl0IGhhcyBhIGdyYXZpdHkgaG9vayBhcHBsaWVkIHRvIGl0LilcblxuXG50byBbT2JqZWN0XSAoT3B0aW9uYWwpXG4tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5UaGUgb2JqZWN0IHRvIGF0dGFjaCBpdCB0by4gRGVmYXVsdHMgdG8gaXRzZWxmLlxuXG5cbnRhcmdldFByb3BlcnR5IFtTdHJpbmddIChPcHRpb25hbClcbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblNwZWNpZnkgdGhlIHRhcmdldCBvYmplY3QncyBwcm9wZXJ0eSB0byBmb2xsb3csIGlmIHlvdSBkb24ndCB3YW50IHRvIGZvbGxvd1xudGhlIHNhbWUgcHJvcGVydHkgdGhhdCB0aGUgaG9vayBpcyBhcHBsaWVkIHRvLlxuXG5cbm1vZHVsYXRvciBbRnVuY3Rpb25dIChPcHRpb25hbClcbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblRoZSBtb2R1bGF0b3IgZnVuY3Rpb24gcmVjZWl2ZXMgdGhlIHRhcmdldCBwcm9wZXJ0eSdzIHZhbHVlLCBhbmQgbGV0cyB5b3Vcbm1vZGlmeSBpdCBiZWZvcmUgaXQgaXMgZmVkIGludG8gdGhlIHBoeXNpY3MgY2FsY3VsYXRpb25zLiBVc2VmdWwgZm9yIGFueXRoaW5nXG5mcm9tIHN0YW5kYXJkIFV0aWxzLm1vZHVsYXRlKCkgdHlwZSBzdHVmZiB0byBzbmFwcGluZyBhbmQgY29uZGl0aW9uYWwgdmFsdWVzLlxuXG5cbnpvb20gW051bWJlcl0gKE9wdGlvbmFsKVxuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5UaGlzIGZhY3RvciBkZWZpbmVzIHRoZSBkaXN0YW5jZSB0aGF0IDFweCByZXByZXNlbnRzIGluIHJlZ2FyZHMgdG8gZ3Jhdml0eSBhbmRcbmRyYWcgY2FsY3VsYXRpb25zLiBPbmx5IG9uZSB2YWx1ZSBpcyBzdG9yZWQgcGVyIGxheWVyLCBzbyBzcGVjaWZ5aW5nIGl0XG5vdmVyd3JpdGVzIGl0cyBleGlzdGluZyB2YWx1ZS4gRGVmYXVsdCBpcyAxMDAuXG5cblxuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbmxheWVyLnVuSG9vayhwcm9wZXJ0eSwgb2JqZWN0KVxuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuVGhpcyByZW1vdmVzIGFsbCBob29rcyBmb3IgYSBnaXZlbiBwcm9wZXJ0eSBhbmQgdGFyZ2V0IG9iamVjdC4gRXhhbXBsZTpcblxuIyBIb29rIGl0XG5sYXllci5ob29rXG5cdHByb3BlcnR5OiBcInhcIlxuXHR0bzogXCJvdGhlcmxheWVyXCJcblx0dGFyZ2V0UHJvcGVydHk6IFwieVwiXG5cdHR5cGU6IFwic3ByaW5nKDIwMCwyMClcIlxuXG4jIFVuaG9vayBpdFxubGF5ZXIudW5Ib29rIFwieFwiLCBvdGhlcmxheWVyXG5cblxuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbmxheWVyLm9uSG9va1VwZGF0ZShkZWx0YSlcbi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbkFmdGVyIGEgbGF5ZXIgaXMgZG9uZSBhcHBseWluZyBhY2NlbGVyYXRpb25zIHRvIGl0cyBob29rZWQgcHJvcGVydGllcywgaXQgY2FsbHNcbm9uSG9va1VwZGF0ZSgpIGF0IHRoZSBlbmQgb2YgZWFjaCBmcmFtZSwgaWYgaXQgaXMgZGVmaW5lZC4gVGhpcyBpcyBhbiBlYXN5IHdheVxudG8gYW5pbWF0ZSBvciB0cmlnZ2VyIG90aGVyIHN0dWZmLCBwZXJoYXBzIGJhc2VkIG9uIHlvdXIgbGF5ZXIncyB1cGRhdGVkXG5wcm9wZXJ0aWVzIG9yIHZlbG9jaXRpZXMuXG5cblRoZSBkZWx0YSB2YWx1ZSBmcm9tIHRoZSBGcmFtZXIgbG9vcCBpcyBwYXNzZWQgb24gdG8gb25Ib29rVXBkYXRlKCkgYXMgd2VsbCxcbndoaWNoIGlzIHRoZSB0aW1lIGluIHNlY29uZHMgc2luY2UgdGhlIGxhc3QgYW5pbWF0aW9uIGZyYW1lLlxuXG5Ob3RlIHRoYXQgaWYgeW91IHVuaG9vayBhbGwgeW91ciBob29rcywgb25Ib29rVXBkYXRlKCkgd2lsbCBvZiBjb3Vyc2Ugbm8gbG9uZ2VyXG5iZSBjYWxsZWQgZm9yIHRoYXQgbGF5ZXIuXG5cbiMjI1xuXG5cbiMgU2luY2Ugb2xkZXIgdmVyc2lvbnMgb2YgU2FmYXJpIHNlZW0gdG8gYmUgbWlzc2luZyBTdHJpbmcucHJvdG90eXBlLmluY2x1ZGVzKClcblxudW5sZXNzIFN0cmluZy5wcm90b3R5cGUuaW5jbHVkZXNcblx0U3RyaW5nOjppbmNsdWRlcyA9IChzZWFyY2gsIHN0YXJ0KSAtPlxuXHRcdCd1c2Ugc3RyaWN0J1xuXHRcdHN0YXJ0ID0gMCBpZiB0eXBlb2Ygc3RhcnQgaXMgJ251bWJlcidcblxuXHRcdGlmIHN0YXJ0ICsgc2VhcmNoLmxlbmd0aCA+IHRoaXMubGVuZ3RoXG5cdFx0XHRyZXR1cm4gZmFsc2U7XG5cdFx0ZWxzZVxuXHRcdFx0cmV0dXJuIEBpbmRleE9mKHNlYXJjaCwgc3RhcnQpIGlzbnQgLTFcblxuIyBFeHBhbmQgbGF5ZXJcblxuTGF5ZXI6Omhvb2sgPSAoY29uZmlnKSAtPlxuXG5cdHRocm93IG5ldyBFcnJvciAnbGF5ZXIuaG9vaygpIG5lZWRzIGEgcHJvcGVydHksIGEgaG9vayB0eXBlIGFuZCBlaXRoZXIgYSB0YXJnZXQgb2JqZWN0IG9yIHRhcmdldCBwcm9wZXJ0eSB0byB3b3JrJyB1bmxlc3MgY29uZmlnLnByb3BlcnR5IGFuZCBjb25maWcudHlwZSBhbmQgKGNvbmZpZy50byBvciBjb25maWcudGFyZ2V0UHJvcGVydHkpXG5cblx0IyBTaW5nbGUgYXJyYXkgZm9yIGFsbCBob29rcywgYXMgb3Bwb3NlZCB0byBuZXN0ZWQgYXJyYXlzIHBlciBwcm9wZXJ0eSwgYmVjYXVzZSBwZXJmb3JtYW5jZVxuXHRAaG9va3MgPz1cblx0XHRob29rczogW11cblx0XHR2ZWxvY2l0aWVzOiB7fVxuXHRcdGRlZnM6XG5cdFx0XHR6b29tOiAxMDBcblx0XHRcdGdldERyYWc6ICh2ZWxvY2l0eSwgZHJhZywgem9vbSkgPT5cblx0XHRcdFx0dmVsb2NpdHkgLz0gem9vbVxuXHRcdFx0XHQjIERpdmlkaW5nIGJ5IDEwIGlzIHVuc2NpZW50aWZpYywgYnV0IGl0IG1lYW5zIGEgdmFsdWUgb2YgMiBlcXVhbHMgcm91Z2hseSBhIDEwMGcgYmFsbCB3aXRoIDE1Y20gcmFkaXVzIGluIGFpclxuXHRcdFx0XHRkcmFnID0gLShkcmFnIC8gMTApICogdmVsb2NpdHkgKiB2ZWxvY2l0eSAqIHZlbG9jaXR5IC8gTWF0aC5hYnModmVsb2NpdHkpXG5cdFx0XHRcdGlmIF8uaXNOYU4oZHJhZykgdGhlbiByZXR1cm4gMCBlbHNlIHJldHVybiBkcmFnXG5cdFx0XHRnZXRHcmF2aXR5OiAoc3RyZW5ndGgsIGRpc3RhbmNlLCB6b29tKSA9PlxuXHRcdFx0XHRkaXN0ID0gTWF0aC5tYXgoMSwgZGlzdGFuY2UgLyB6b29tKVxuXHRcdFx0XHRyZXR1cm4gc3RyZW5ndGggKiB6b29tIC8gKGRpc3QgKiBkaXN0KVxuXG5cdCMgVXBkYXRlIHRoZSB6b29tIHZhbHVlIGlmIGdpdmVuXG5cdEBob29rcy56b29tID0gY29uZmlnLnpvb20gaWYgY29uZmlnLnpvb21cblxuXHQjIFBhcnNlIHBoeXNpY3MgY29uZmlnIHN0cmluZ1xuXHRmID0gVXRpbHMucGFyc2VGdW5jdGlvbiBjb25maWcudHlwZVxuXHRjb25maWcudHlwZSA9IGYubmFtZVxuXHRjb25maWcuc3RyZW5ndGggPSBmLmFyZ3NbMF1cblx0Y29uZmlnLmZyaWN0aW9uID0gZi5hcmdzWzFdIG9yIDBcblxuXHQjIERlZmF1bHQgdG8gc2FtZSB0YXJnZXRQcm9wZXJ0eSBvbiBzYW1lIG9iamVjdCAoaG9wZWZ1bGx5IHlvdSd2ZSBzZXQgYXQgbGVhc3Qgb25lIG9mIHRoZXNlIHRvIHNvbWV0aGluZyBlbHNlKVxuXHRjb25maWcudGFyZ2V0UHJvcGVydHkgPz0gY29uZmlnLnByb3BlcnR5XG5cdGNvbmZpZy50byA/PSBAXG5cblx0IyBBbGwgcG9zaXRpb24gYWNjZWxlcmF0aW9ucyBhcmUgYWRkZWQgdG8gYSBzaW5nbGUgJ3BvcycgdmVsb2NpdHkuIFN0b3JlIGFjdHVhbCBwcm9wZXJ0aWVzIHNvIHdlIGRvbid0IGhhdmUgdG8gZG8gaXQgYWdhaW4gZXZlcnkgZnJhbWVcblxuXHRpZiBjb25maWcucHJvcGVydHkudG9Mb3dlckNhc2UoKS5pbmNsdWRlcyAncG9zJ1xuXHRcdGNvbmZpZy5wcm9wID0gJ3Bvcydcblx0XHRcblx0XHRpZiBjb25maWcucHJvcGVydHkudG9Mb3dlckNhc2UoKS5pbmNsdWRlcyAnbWlkJ1xuXHRcdFx0Y29uZmlnLnRoaXNYID0gJ21pZFgnXG5cdFx0XHRjb25maWcudGhpc1kgPSAnbWlkWSdcblx0XHRcblx0XHRlbHNlIGlmIGNvbmZpZy5wcm9wZXJ0eS50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzICdtYXgnXG5cdFx0XHRjb25maWcudGhpc1ggPSAnbWF4WCdcblx0XHRcdGNvbmZpZy50aGlzWSA9ICdtYXhZJ1xuXHRcdFxuXHRcdGVsc2Vcblx0XHRcdGNvbmZpZy50aGlzWCA9ICd4J1xuXHRcdFx0Y29uZmlnLnRoaXNZID0gJ3knXG5cdFx0XG5cdFx0aWYgY29uZmlnLnRhcmdldFByb3BlcnR5LnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMgJ21pZCdcblx0XHRcdGNvbmZpZy50b1ggPSAnbWlkWCdcblx0XHRcdGNvbmZpZy50b1kgPSAnbWlkWSdcblx0XHRcblx0XHRlbHNlIGlmIGNvbmZpZy50YXJnZXRQcm9wZXJ0eS50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzICdtYXgnXG5cdFx0XHRjb25maWcudG9YID0gJ21heFgnXG5cdFx0XHRjb25maWcudG9ZID0gJ21heFknXHRcdFxuXHRcdGVsc2Vcblx0XHRcdGNvbmZpZy50b1ggPSAneCdcblx0XHRcdGNvbmZpZy50b1kgPSAneSdcblx0XHRcblx0ZWxzZVxuXHRcdGNvbmZpZy5wcm9wID0gY29uZmlnLnByb3BlcnR5XG5cblx0IyBTYXZlIGhvb2sgdG8gQGhvb2tzIGFycmF5XHRcblx0QGhvb2tzLmhvb2tzLnB1c2goY29uZmlnKVxuXG5cdCMgQ3JlYXRlIHZlbG9jaXR5IHByb3BlcnR5IGlmIG5lY2Vzc2FyeVxuXHRAaG9va3MudmVsb2NpdGllc1tjb25maWcucHJvcF0gPz0gaWYgY29uZmlnLnByb3AgaXMgJ3BvcycgdGhlbiB7IHg6IDAsIHk6IDAgfSBlbHNlIDBcblxuXHQjIFVzZSBGcmFtZXIncyBhbmltYXRpb24gbG9vcCwgc2xpZ2h0bHkgbW9yZSByb2J1c3QgdGhhbiByZXF1ZXN0QW5pbWF0aW9uRnJhbWUgZGlyZWN0bHlcblx0IyBTYXZlIHRoZSByZXR1cm5lZCBBbmltYXRpb25Mb29wIHJlZmVyZW5jZSB0byBtYWtlIHN1cmUgQGhvb2tMb29wIGlzbid0IGFkZGVkIG11bHRpcGxlIHRpbWVzIHBlciBsYXllclxuXHRAaG9va3MuZW1pdHRlciA/PSBGcmFtZXIuTG9vcC5vbigncmVuZGVyJywgQGhvb2tMb29wLCB0aGlzKVxuXG5MYXllcjo6dW5Ib29rID0gKHByb3BlcnR5LCBvYmplY3QpIC0+XG5cdFxuXHRyZXR1cm4gdW5sZXNzIEBob29rc1xuXG5cdHByb3AgPSBpZiBwcm9wZXJ0eS50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzICdwb3MnIHRoZW4gJ3BvcycgZWxzZSBwcm9wZXJ0eVxuXG5cdCMgUmVtb3ZlIGFsbCBtYXRjaGVzXG5cdEBob29rcy5ob29rcyA9IEBob29rcy5ob29rcy5maWx0ZXIgKGhvb2spIC0+XG5cdFx0aG9vay50byBpc250IG9iamVjdCBvciBob29rLnByb3BlcnR5IGlzbnQgcHJvcGVydHlcblxuXHQjIElmIHRoZXJlIGFyZSBubyBob29rcyBsZWZ0LCBzaHV0IGl0IGRvd25cblx0aWYgQGhvb2tzLmhvb2tzLmxlbmd0aCBpcyAwXG5cdFx0ZGVsZXRlIEBob29rc1xuXHRcdEZyYW1lci5Mb29wLnJlbW92ZUxpc3RlbmVyICdyZW5kZXInLCBAaG9va0xvb3Bcblx0XHRyZXR1cm5cblxuXHQjIFN0aWxsIGhlcmU/IENoZWNrIGlmIHRoZXJlIGFyZSBhbnkgcmVtYWluaW5nIGhvb2tzIGFmZmVjdGluZyBzYW1lIHZlbG9jaXR5XG5cdHJlbWFpbmluZyA9IEBob29rcy5ob29rcy5maWx0ZXIgKGhvb2spIC0+XG5cdFx0cHJvcCBpcyBob29rLnByb3Bcblx0XHRcblx0IyBJZiBub3QsIGRlbGV0ZSB2ZWxvY2l0eSAob3RoZXJ3aXNlIGl0IHdvbid0IGJlIHJlc2V0IGlmIHlvdSBtYWtlIG5ldyBob29rIGZvciBzYW1lIHByb3BlcnR5KVxuXHRkZWxldGUgQGhvb2tzLnZlbG9jaXRpZXNbcHJvcF0gaWYgcmVtYWluaW5nLmxlbmd0aCBpcyAwXG5cbkxheWVyOjpob29rTG9vcCA9IChkZWx0YSkgLT5cblxuXHRpZiBAaG9va3NcblxuXHRcdCMgTXVsdGlwbGUgaG9va3MgY2FuIGFmZmVjdCB0aGUgc2FtZSBwcm9wZXJ0eS4gQWRkIGFjY2VsZXJhdGlvbnMgdG8gdGVtcG9yYXJ5IG9iamVjdCBzbyB0aGUgcHJvcGVydHkncyB2ZWxvY2l0eSBpcyB0aGUgc2FtZSBmb3IgYWxsIGNhbGN1bGF0aW9ucyB3aXRoaW4gdGhlIHNhbWUgYW5pbWF0aW9uIGZyYW1lXG5cdFx0YWNjZWxlcmF0aW9uID0ge31cblx0XHRcblx0XHQjIFNhdmUgZHJhZyBmb3IgZWFjaCBwcm9wZXJ0eSB0byB0aGlzIG9iamVjdCwgc2luY2Ugb25seSBtb3N0IHJlY2VudGx5IHNwZWNpZmllZCB2YWx1ZSBpcyB1c2VkIGZvciBlYWNoIHByb3BlcnR5XG5cdFx0ZHJhZyA9IHt9XG5cdFx0XG5cdFx0IyBBZGQgYWNjZWxlcmF0aW9uc1xuXHRcdGZvciBob29rIGluIEBob29rcy5ob29rc1xuXHRcdFxuXHRcdFx0aWYgaG9vay5wcm9wIGlzICdwb3MnXG5cblx0XHRcdFx0YWNjZWxlcmF0aW9uLnBvcyA/PSB7IHg6IDAsIHk6IDAgfVxuXG5cdFx0XHRcdHRhcmdldCA9IHsgeDogaG9vay50b1tob29rLnRvWF0sIHk6IGhvb2sudG9baG9vay50b1ldIH1cblxuXHRcdFx0XHR0YXJnZXQgPSBob29rLm1vZHVsYXRvcih0YXJnZXQpIGlmIGhvb2subW9kdWxhdG9yXG5cblx0XHRcdFx0dmVjdG9yID1cblx0XHRcdFx0XHR4OiB0YXJnZXQueCAtIEBbaG9vay50aGlzWF1cblx0XHRcdFx0XHR5OiB0YXJnZXQueSAtIEBbaG9vay50aGlzWV1cblx0XHRcdFx0XG5cdFx0XHRcdHZMZW5ndGggPSBNYXRoLnNxcnQoKHZlY3Rvci54ICogdmVjdG9yLngpICsgKHZlY3Rvci55ICogdmVjdG9yLnkpKVxuXG5cdFx0XHRcdGlmIGhvb2sudHlwZSBpcyAnc3ByaW5nJ1xuXG5cdFx0XHRcdFx0ZGFtcGVyID1cblx0XHRcdFx0XHRcdHg6IC1ob29rLmZyaWN0aW9uICogQGhvb2tzLnZlbG9jaXRpZXMucG9zLnhcblx0XHRcdFx0XHRcdHk6IC1ob29rLmZyaWN0aW9uICogQGhvb2tzLnZlbG9jaXRpZXMucG9zLnlcblxuXHRcdFx0XHRcdHZlY3Rvci54ICo9IGhvb2suc3RyZW5ndGhcblx0XHRcdFx0XHR2ZWN0b3IueSAqPSBob29rLnN0cmVuZ3RoXG5cdFx0XHRcdFx0XG5cdFx0XHRcdFx0YWNjZWxlcmF0aW9uLnBvcy54ICs9ICh2ZWN0b3IueCArIGRhbXBlci54KSAqIGRlbHRhXG5cdFx0XHRcdFx0YWNjZWxlcmF0aW9uLnBvcy55ICs9ICh2ZWN0b3IueSArIGRhbXBlci55KSAqIGRlbHRhXG5cdFx0XHRcdFxuXHRcdFx0XHRlbHNlIGlmIGhvb2sudHlwZSBpcyAnZ3Jhdml0eSdcblx0XHRcdFx0XG5cdFx0XHRcdFx0ZHJhZy5wb3MgPSBob29rLmZyaWN0aW9uXG5cdFx0XHRcdFx0XG5cdFx0XHRcdFx0Z3Jhdml0eSA9IEBob29rcy5kZWZzLmdldEdyYXZpdHkoaG9vay5zdHJlbmd0aCwgdkxlbmd0aCwgQGhvb2tzLmRlZnMuem9vbSlcblxuXHRcdFx0XHRcdHZlY3Rvci54ICo9IGdyYXZpdHkgLyB2TGVuZ3RoXG5cdFx0XHRcdFx0dmVjdG9yLnkgKj0gZ3Jhdml0eSAvIHZMZW5ndGhcblx0XHRcdFx0XHRcblx0XHRcdFx0XHRhY2NlbGVyYXRpb24ucG9zLnggKz0gdmVjdG9yLnggKiBkZWx0YVxuXHRcdFx0XHRcdGFjY2VsZXJhdGlvbi5wb3MueSArPSB2ZWN0b3IueSAqIGRlbHRhXG5cdFx0XHRcdFx0XHRcdFx0XHRcblx0XHRcdGVsc2Vcblx0XHRcdFx0XG5cdFx0XHRcdGFjY2VsZXJhdGlvbltob29rLnByb3BdID89IDBcblxuXHRcdFx0XHR0YXJnZXQgPSBob29rLnRvW2hvb2sudGFyZ2V0UHJvcGVydHldXG5cblx0XHRcdFx0dGFyZ2V0ID0gaG9vay5tb2R1bGF0b3IodGFyZ2V0KSBpZiBob29rLm1vZHVsYXRvclxuXG5cdFx0XHRcdHZlY3RvciA9IHRhcmdldCAtIEBbaG9vay5wcm9wXVxuXHRcdFx0XHRcblx0XHRcdFx0aWYgaG9vay50eXBlIGlzICdzcHJpbmcnXG5cblx0XHRcdFx0XHRmb3JjZSA9IHZlY3RvciAqIGhvb2suc3RyZW5ndGhcblx0XHRcdFx0XHRkYW1wZXIgPSAtaG9vay5mcmljdGlvbiAqIEBob29rcy52ZWxvY2l0aWVzW2hvb2sucHJvcF1cblxuXHRcdFx0XHRcdGFjY2VsZXJhdGlvbltob29rLnByb3BdICs9IChmb3JjZSArIGRhbXBlcikgKiBkZWx0YVxuXG5cdFx0XHRcdFxuXHRcdFx0XHRlbHNlIGlmIGhvb2sudHlwZSBpcyAnZ3Jhdml0eSdcblx0XG5cdFx0XHRcdFx0ZHJhZ1tob29rLnByb3BdID0gaG9vay5mcmljdGlvblxuXHRcdFx0XHRcdFxuXHRcdFx0XHRcdGZvcmNlID0gQGhvb2tzLmRlZnMuZ2V0R3Jhdml0eShob29rLnN0cmVuZ3RoLCB2ZWN0b3IsIEBob29rcy5kZWZzLnpvb20pXG5cdFx0XHRcdFx0XG5cdFx0XHRcdFx0YWNjZWxlcmF0aW9uW2hvb2sucHJvcF0gKz0gZm9yY2UgKiBkZWx0YVxuXHRcdFxuXHRcdFxuXHRcdCMgQWRkIHZlbG9jaXRpZXMgdG8gcHJvcGVydGllcy4gRG9pbmcgdGhpcyBhdCB0aGUgZW5kIGluIGNhc2UgdGhlcmUgYXJlIG11bHRpcGxlIGhvb2tzIGFmZmVjdGluZyB0aGUgc2FtZSB2ZWxvY2l0eVxuXHRcdGZvciBwcm9wLCB2ZWxvY2l0eSBvZiBAaG9va3MudmVsb2NpdGllc1xuXHRcdFxuXHRcdFx0aWYgcHJvcCBpcyAncG9zJ1xuXG5cdFx0XHRcdCMgQWRkIGRyYWcsIGlmIGl0IGV4aXN0c1xuXHRcdFx0XHRpZiBkcmFnLnBvc1xuXHRcdFx0XHRcdHZlbG9jaXR5LnggKz0gQGhvb2tzLmRlZnMuZ2V0RHJhZyh2ZWxvY2l0eS54LCBkcmFnLnBvcywgQGhvb2tzLmRlZnMuem9vbSlcblx0XHRcdFx0XHR2ZWxvY2l0eS55ICs9IEBob29rcy5kZWZzLmdldERyYWcodmVsb2NpdHkueSwgZHJhZy5wb3MsIEBob29rcy5kZWZzLnpvb20pXG5cdFx0XHRcdFx0XG5cdFx0XHRcdCMgQWRkIGFjY2VsZXJhdGlvbiB0byB2ZWxvY2l0eVxuXHRcdFx0XHR2ZWxvY2l0eS54ICs9IGFjY2VsZXJhdGlvbi5wb3MueFxuXHRcdFx0XHR2ZWxvY2l0eS55ICs9IGFjY2VsZXJhdGlvbi5wb3MueVxuXHRcdFx0XHRcblx0XHRcdFx0IyBBZGQgdmVsb2NpdHkgdG8gcG9zaXRpb25cblx0XHRcdFx0QHggKz0gdmVsb2NpdHkueCAqIGRlbHRhXG5cdFx0XHRcdEB5ICs9IHZlbG9jaXR5LnkgKiBkZWx0YVxuXHRcdFx0XG5cdFx0XHRlbHNlXG5cdFx0XHRcblx0XHRcdFx0IyBBZGQgZHJhZywgaWYgaXQgZXhpc3RzXG5cdFx0XHRcdGlmIGRyYWdbcHJvcF1cblx0XHRcdFx0XHRAaG9va3MudmVsb2NpdGllc1twcm9wXSArPSBAaG9va3MuZGVmcy5nZXREcmFnKEBob29rcy52ZWxvY2l0aWVzW3Byb3BdLCBkcmFnW3Byb3BdLCBAaG9va3MuZGVmcy56b29tKVxuXHRcdFx0XHRcblx0XHRcdFx0IyBBZGQgYWNjZWxlcmF0aW9uIHRvIHZlbG9jaXR5XG5cdFx0XHRcdEBob29rcy52ZWxvY2l0aWVzW3Byb3BdICs9IGFjY2VsZXJhdGlvbltwcm9wXVxuXHRcdFx0XHRcblx0XHRcdFx0IyBBZGQgdmVsb2NpdHkgdG8gcHJvcGVydHlcblx0XHRcdFx0QFtwcm9wXSArPSBAaG9va3MudmVsb2NpdGllc1twcm9wXSAqIGRlbHRhXG5cblx0XHRAb25Ib29rVXBkYXRlPyhkZWx0YSkiXX0=
346 |
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/images/cursor-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-spring.framer/framer/images/cursor-active.png
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/images/cursor-active@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-spring.framer/framer/images/cursor-active@2x.png
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/images/cursor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-spring.framer/framer/images/cursor.png
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/images/cursor@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-spring.framer/framer/images/cursor@2x.png
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/images/icon-120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-spring.framer/framer/images/icon-120.png
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/images/icon-152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-spring.framer/framer/images/icon-152.png
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/images/icon-180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-spring.framer/framer/images/icon-180.png
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/images/icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-spring.framer/framer/images/icon-192.png
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/images/icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-spring.framer/framer/images/icon-76.png
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/manifest.txt:
--------------------------------------------------------------------------------
1 | app.coffee
2 | framer/backups/backup-2016-09-09 14.23.29.coffee
3 | framer/backups/backup-2016-09-09 14.27.29.coffee
4 | framer/coffee-script.js
5 | framer/config.json
6 | framer/framer.generated.js
7 | framer/framer.init.js
8 | framer/framer.js
9 | framer/framer.js.map
10 | framer/framer.modules.js
11 | framer/images/cursor-active.png
12 | framer/images/cursor-active@2x.png
13 | framer/images/cursor.png
14 | framer/images/cursor@2x.png
15 | framer/images/icon-120.png
16 | framer/images/icon-152.png
17 | framer/images/icon-180.png
18 | framer/images/icon-192.png
19 | framer/images/icon-76.png
20 | framer/manifest.txt
21 | framer/metadata.json
22 | framer/preview.png
23 | framer/style.css
24 | framer/version
25 | index.html
26 | modules/Hook.coffee
27 | spring-example-720.gif
28 | spring-example.gif
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/metadata.json:
--------------------------------------------------------------------------------
1 | {"title":"hook-example-spring","date":"2016-09-09T12:30:12Z"}
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-spring.framer/framer/preview.png
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | border: none;
5 | -webkit-user-select: none;
6 | -webkit-tap-highlight-color: rgba(0,0,0,0);
7 | }
8 |
9 | body {
10 | background-color: #fff;
11 | font: 28px/1em "Helvetica";
12 | color: gray;
13 | overflow: hidden;
14 | }
15 |
16 | a {
17 | color: gray;
18 | }
19 |
20 | body {
21 | cursor: url('images/cursor.png') 32 32, auto;
22 | cursor: -webkit-image-set(
23 | url('images/cursor.png') 1x,
24 | url('images/cursor@2x.png') 2x
25 | ) 32 32, auto;
26 | }
27 |
28 | body:active {
29 | cursor: url('images/cursor-active.png') 32 32, auto;
30 | cursor: -webkit-image-set(
31 | url('images/cursor-active.png') 1x,
32 | url('images/cursor-active@2x.png') 2x
33 | ) 32 32, auto;
34 | }
35 |
36 | .framerAlertBackground {
37 | position: absolute; top:0px; left:0px; right:0px; bottom:0px;
38 | z-index: 1000;
39 | background-color: #fff;
40 | }
41 |
42 | .framerAlert {
43 | font:400 14px/1.4 "Helvetica Neue", Helvetica, Arial, sans-serif;
44 | -webkit-font-smoothing:antialiased;
45 | color:#616367; text-align:center;
46 | position: absolute; top:40%; left:50%; width:260px; margin-left:-130px;
47 | }
48 | .framerAlert strong { font-weight:500; color:#000; margin-bottom:8px; display:block; }
49 | .framerAlert a { color:#28AFFA; }
50 | .framerAlert .btn {
51 | font-weight:500; text-decoration:none; line-height:1;
52 | display:inline-block; padding:6px 12px 7px 12px;
53 | border-radius:3px; margin-top:12px;
54 | background:#28AFFA; color:#fff;
55 | }
56 |
57 | ::-webkit-scrollbar {
58 | display: none;
59 | }
--------------------------------------------------------------------------------
/hook-example-spring.framer/framer/version:
--------------------------------------------------------------------------------
1 | 4
--------------------------------------------------------------------------------
/hook-example-spring.framer/images/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-spring.framer/images/.gitkeep
--------------------------------------------------------------------------------
/hook-example-spring.framer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/hook-example-spring.framer/modules/Hook.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | --------------------------------------------------------------------------------
3 | Hook module for Framer
4 | --------------------------------------------------------------------------------
5 |
6 | by: Sigurd Mannsåker
7 | github: https://github.com/sigtm/framer-hook
8 |
9 | ················································································
10 |
11 |
12 | The Hook module simply expands the Layer prototype, and lets you make any
13 | numeric Layer property follow another property - either its own or another
14 | object's - via a spring or gravity attraction.
15 |
16 |
17 | --------------------------------------------------------------------------------
18 | Example: Layered animation (eased + spring)
19 | --------------------------------------------------------------------------------
20 |
21 | myLayer = new Layer
22 |
23 | # Make our own custom property for the x property to follow
24 | myLayer.easedX = 0
25 |
26 | # Hook x to easedX via a spring
27 | myLayer.hook
28 | property: "x"
29 | targetProperty: "easedX"
30 | type: "spring(150, 15)"
31 |
32 | # Animate easedX
33 | myLayer.animate
34 | properties:
35 | easedX: 200
36 | time: 0.15
37 | curve: "cubic-bezier(0.2, 0, 0.4, 1)"
38 |
39 | NOTE:
40 | To attach both the x and y position, use "pos", "midPos" or "maxPos" as the
41 | property/targetProperty.
42 |
43 |
44 | --------------------------------------------------------------------------------
45 | Example: Hooking property to another layer
46 | --------------------------------------------------------------------------------
47 |
48 | target = new Layer
49 | hooked = new Layer
50 |
51 | hooked.hook
52 | property: "scale"
53 | to: target
54 | type: "spring(150, 15)"
55 |
56 | The "hooked" layer's scale will now continuously follow the target layer's scale
57 | with a spring animation.
58 |
59 |
60 | --------------------------------------------------------------------------------
61 | layer.hook(options)
62 | --------------------------------------------------------------------------------
63 |
64 | Options are passed as a single object, like you would for a new Layer.
65 | The options object takes the following properties:
66 |
67 |
68 | property [String]
69 | -----------------
70 | The property you'd like to hook onto another object's property
71 |
72 |
73 | type [String]
74 | -------------
75 | Either "spring(strength, friction)" or "gravity(strength, drag)". Only the last
76 | specified drag value is used for each property, since it is only applied to
77 | each property once (and only if it has a gravity hook applied to it.)
78 |
79 |
80 | to [Object] (Optional)
81 | ----------------------
82 | The object to attach it to. Defaults to itself.
83 |
84 |
85 | targetProperty [String] (Optional)
86 | ----------------------------------
87 | Specify the target object's property to follow, if you don't want to follow
88 | the same property that the hook is applied to.
89 |
90 |
91 | modulator [Function] (Optional)
92 | -------------------------------
93 | The modulator function receives the target property's value, and lets you
94 | modify it before it is fed into the physics calculations. Useful for anything
95 | from standard Utils.modulate() type stuff to snapping and conditional values.
96 |
97 |
98 | zoom [Number] (Optional)
99 | ------------------------
100 | This factor defines the distance that 1px represents in regards to gravity and
101 | drag calculations. Only one value is stored per layer, so specifying it
102 | overwrites its existing value. Default is 100.
103 |
104 |
105 | --------------------------------------------------------------------------------
106 | layer.unHook(property, object)
107 | --------------------------------------------------------------------------------
108 |
109 | This removes all hooks for a given property and target object. Example:
110 |
111 | # Hook it
112 | layer.hook
113 | property: "x"
114 | to: "otherlayer"
115 | targetProperty: "y"
116 | type: "spring(200,20)"
117 |
118 | # Unhook it
119 | layer.unHook "x", otherlayer
120 |
121 |
122 | --------------------------------------------------------------------------------
123 | layer.onHookUpdate(delta)
124 | --------------------------------------------------------------------------------
125 |
126 | After a layer is done applying accelerations to its hooked properties, it calls
127 | onHookUpdate() at the end of each frame, if it is defined. This is an easy way
128 | to animate or trigger other stuff, perhaps based on your layer's updated
129 | properties or velocities.
130 |
131 | The delta value from the Framer loop is passed on to onHookUpdate() as well,
132 | which is the time in seconds since the last animation frame.
133 |
134 | Note that if you unhook all your hooks, onHookUpdate() will of course no longer
135 | be called for that layer.
136 |
137 | ###
138 |
139 |
140 | # Since older versions of Safari seem to be missing String.prototype.includes()
141 |
142 | unless String.prototype.includes
143 | String::includes = (search, start) ->
144 | 'use strict'
145 | start = 0 if typeof start is 'number'
146 |
147 | if start + search.length > this.length
148 | return false;
149 | else
150 | return @indexOf(search, start) isnt -1
151 |
152 | # Expand layer
153 |
154 | Layer::hook = (config) ->
155 |
156 | throw new Error 'layer.hook() needs a property, a hook type and either a target object or target property to work' unless config.property and config.type and (config.to or config.targetProperty)
157 |
158 | # Single array for all hooks, as opposed to nested arrays per property, because performance
159 | @hooks ?=
160 | hooks: []
161 | velocities: {}
162 | defs:
163 | zoom: 100
164 | getDrag: (velocity, drag, zoom) =>
165 | velocity /= zoom
166 | # Dividing by 10 is unscientific, but it means a value of 2 equals roughly a 100g ball with 15cm radius in air
167 | drag = -(drag / 10) * velocity * velocity * velocity / Math.abs(velocity)
168 | if _.isNaN(drag) then return 0 else return drag
169 | getGravity: (strength, distance, zoom) =>
170 | dist = Math.max(1, distance / zoom)
171 | return strength * zoom / (dist * dist)
172 |
173 | # Update the zoom value if given
174 | @hooks.zoom = config.zoom if config.zoom
175 |
176 | # Parse physics config string
177 | f = Utils.parseFunction config.type
178 | config.type = f.name
179 | config.strength = f.args[0]
180 | config.friction = f.args[1] or 0
181 |
182 | # Default to same targetProperty on same object (hopefully you've set at least one of these to something else)
183 | config.targetProperty ?= config.property
184 | config.to ?= @
185 |
186 | # All position accelerations are added to a single 'pos' velocity. Store actual properties so we don't have to do it again every frame
187 |
188 | if config.property.toLowerCase().includes 'pos'
189 | config.prop = 'pos'
190 |
191 | if config.property.toLowerCase().includes 'mid'
192 | config.thisX = 'midX'
193 | config.thisY = 'midY'
194 |
195 | else if config.property.toLowerCase().includes 'max'
196 | config.thisX = 'maxX'
197 | config.thisY = 'maxY'
198 |
199 | else
200 | config.thisX = 'x'
201 | config.thisY = 'y'
202 |
203 | if config.targetProperty.toLowerCase().includes 'mid'
204 | config.toX = 'midX'
205 | config.toY = 'midY'
206 |
207 | else if config.targetProperty.toLowerCase().includes 'max'
208 | config.toX = 'maxX'
209 | config.toY = 'maxY'
210 | else
211 | config.toX = 'x'
212 | config.toY = 'y'
213 |
214 | else
215 | config.prop = config.property
216 |
217 | # Save hook to @hooks array
218 | @hooks.hooks.push(config)
219 |
220 | # Create velocity property if necessary
221 | @hooks.velocities[config.prop] ?= if config.prop is 'pos' then { x: 0, y: 0 } else 0
222 |
223 | # Use Framer's animation loop, slightly more robust than requestAnimationFrame directly
224 | # Save the returned AnimationLoop reference to make sure @hookLoop isn't added multiple times per layer
225 | @hooks.emitter ?= Framer.Loop.on('render', @hookLoop, this)
226 |
227 | Layer::unHook = (property, object) ->
228 |
229 | return unless @hooks
230 |
231 | prop = if property.toLowerCase().includes 'pos' then 'pos' else property
232 |
233 | # Remove all matches
234 | @hooks.hooks = @hooks.hooks.filter (hook) ->
235 | hook.to isnt object or hook.property isnt property
236 |
237 | # If there are no hooks left, shut it down
238 | if @hooks.hooks.length is 0
239 | delete @hooks
240 | Framer.Loop.removeListener 'render', @hookLoop
241 | return
242 |
243 | # Still here? Check if there are any remaining hooks affecting same velocity
244 | remaining = @hooks.hooks.filter (hook) ->
245 | prop is hook.prop
246 |
247 | # If not, delete velocity (otherwise it won't be reset if you make new hook for same property)
248 | delete @hooks.velocities[prop] if remaining.length is 0
249 |
250 | Layer::hookLoop = (delta) ->
251 |
252 | if @hooks
253 |
254 | # Multiple hooks can affect the same property. Add accelerations to temporary object so the property's velocity is the same for all calculations within the same animation frame
255 | acceleration = {}
256 |
257 | # Save drag for each property to this object, since only most recently specified value is used for each property
258 | drag = {}
259 |
260 | # Add accelerations
261 | for hook in @hooks.hooks
262 |
263 | if hook.prop is 'pos'
264 |
265 | acceleration.pos ?= { x: 0, y: 0 }
266 |
267 | target = { x: hook.to[hook.toX], y: hook.to[hook.toY] }
268 |
269 | target = hook.modulator(target) if hook.modulator
270 |
271 | vector =
272 | x: target.x - @[hook.thisX]
273 | y: target.y - @[hook.thisY]
274 |
275 | vLength = Math.sqrt((vector.x * vector.x) + (vector.y * vector.y))
276 |
277 | if hook.type is 'spring'
278 |
279 | damper =
280 | x: -hook.friction * @hooks.velocities.pos.x
281 | y: -hook.friction * @hooks.velocities.pos.y
282 |
283 | vector.x *= hook.strength
284 | vector.y *= hook.strength
285 |
286 | acceleration.pos.x += (vector.x + damper.x) * delta
287 | acceleration.pos.y += (vector.y + damper.y) * delta
288 |
289 | else if hook.type is 'gravity'
290 |
291 | drag.pos = hook.friction
292 |
293 | gravity = @hooks.defs.getGravity(hook.strength, vLength, @hooks.defs.zoom)
294 |
295 | vector.x *= gravity / vLength
296 | vector.y *= gravity / vLength
297 |
298 | acceleration.pos.x += vector.x * delta
299 | acceleration.pos.y += vector.y * delta
300 |
301 | else
302 |
303 | acceleration[hook.prop] ?= 0
304 |
305 | target = hook.to[hook.targetProperty]
306 |
307 | target = hook.modulator(target) if hook.modulator
308 |
309 | vector = target - @[hook.prop]
310 |
311 | if hook.type is 'spring'
312 |
313 | force = vector * hook.strength
314 | damper = -hook.friction * @hooks.velocities[hook.prop]
315 |
316 | acceleration[hook.prop] += (force + damper) * delta
317 |
318 |
319 | else if hook.type is 'gravity'
320 |
321 | drag[hook.prop] = hook.friction
322 |
323 | force = @hooks.defs.getGravity(hook.strength, vector, @hooks.defs.zoom)
324 |
325 | acceleration[hook.prop] += force * delta
326 |
327 |
328 | # Add velocities to properties. Doing this at the end in case there are multiple hooks affecting the same velocity
329 | for prop, velocity of @hooks.velocities
330 |
331 | if prop is 'pos'
332 |
333 | # Add drag, if it exists
334 | if drag.pos
335 | velocity.x += @hooks.defs.getDrag(velocity.x, drag.pos, @hooks.defs.zoom)
336 | velocity.y += @hooks.defs.getDrag(velocity.y, drag.pos, @hooks.defs.zoom)
337 |
338 | # Add acceleration to velocity
339 | velocity.x += acceleration.pos.x
340 | velocity.y += acceleration.pos.y
341 |
342 | # Add velocity to position
343 | @x += velocity.x * delta
344 | @y += velocity.y * delta
345 |
346 | else
347 |
348 | # Add drag, if it exists
349 | if drag[prop]
350 | @hooks.velocities[prop] += @hooks.defs.getDrag(@hooks.velocities[prop], drag[prop], @hooks.defs.zoom)
351 |
352 | # Add acceleration to velocity
353 | @hooks.velocities[prop] += acceleration[prop]
354 |
355 | # Add velocity to property
356 | @[prop] += @hooks.velocities[prop] * delta
357 |
358 | @onHookUpdate?(delta)
--------------------------------------------------------------------------------
/hook-example-spring.framer/spring-example-720.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-spring.framer/spring-example-720.gif
--------------------------------------------------------------------------------
/hook-example-spring.framer/spring-example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sigtm/framer-hook/ca54ec1268092b5af195518aeb0b3dbd90bd533f/hook-example-spring.framer/spring-example.gif
--------------------------------------------------------------------------------