├── .DS_Store
├── LICENSE
├── example.coffee
├── example.framer
├── .gitignore
├── app.coffee
├── framer
│ ├── .bookmark
│ ├── coffee-script.js
│ ├── config.json
│ ├── design.vekter
│ ├── framer.generated.js
│ ├── framer.init.js
│ ├── framer.js
│ ├── framer.js.map
│ ├── framer.modules (Stephen Ruiz's conflicted copy 2017-11-17).js
│ ├── framer.modules.js
│ ├── framer.vekter.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 (Stephen Ruiz's conflicted copy 2017-11-17).txt
│ ├── preview (Stephen Ruiz's conflicted copy 2017-11-17 (1)).png
│ ├── preview (Stephen Ruiz's conflicted copy 2017-11-17).png
│ ├── style.css
│ └── version
├── images
│ ├── .gitkeep
│ └── design
│ │ ├── jGRzkD2R7YrxRiRi9FkYz6kxJNgft3BT8iJ0bKuSD99x6bCz9iP5RZLkmnoV9TMAcxh4cxTViNynzzqhiIQ.png
│ │ ├── jGRzkD2R7YrxRiRi9FkYz6kxJNgft3BT8iJ0bKuSD99x6bCz9iP5RZLkmnoV9TMAcxh4cxTViNynzzqhiIQ.png-1024
│ │ ├── jGRzkD2R7YrxRiRi9FkYz6kxJNgft3BT8iJ0bKuSD99x6bCz9iP5RZLkmnoV9TMAcxh4cxTViNynzzqhiIQ.png-512
│ │ └── jGRzkD2R7YrxRiRi9FkYz6kxJNgft3BT8iJ0bKuSD99x6bCz9iP5RZLkmnoV9TMAcxh4cxTViNynzzqhiIQ.png-preview
├── index.html
└── modules
│ └── gotcha.coffee
├── gotcha.coffee
├── icon.png
├── icon@1x.png
├── icon_small.png
├── module.json
├── readme.md
└── splash.png
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/.DS_Store
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/example.coffee:
--------------------------------------------------------------------------------
1 | plugin.run = (contents, options) ->
2 | """
3 | #{contents}
4 |
5 | require "gotcha"
6 | """
--------------------------------------------------------------------------------
/example.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 | # Thumbnails
13 | ._*
14 |
15 | # Files that might appear in the root of a volume
16 | .DocumentRevisions-V100
17 | .fseventsd
18 | .Spotlight-V100
19 | .TemporaryItems
20 | .Trashes
21 | .VolumeIcon.icns
22 |
23 | # Directories potentially created on remote AFP share
24 | .AppleDB
25 | .AppleDesktop
26 | Network Trash Folder
27 | Temporary Items
28 | .apdisk
29 |
30 | # Framer Specific
31 | .*.html
32 | .app.js
33 | framer/*.old*
34 | framer/.*.hash
35 | framer/backup.coffee
36 | framer/backups/*
37 | framer/manifest.txt
38 | framer/preview.png
39 | framer/social-*x*.png
40 |
--------------------------------------------------------------------------------
/example.framer/app.coffee:
--------------------------------------------------------------------------------
1 | # Gotcha Demo
2 | # @steveruizok
3 |
4 | { gotcha } = require "gotcha"
5 | gotcha.onlyVisible = true
6 |
7 | Framer.Extras.Preloader.enable()
8 |
9 | Screen.backgroundColor = '#000'
10 |
11 |
12 | # BulletItem
13 | class BulletItem extends Layer
14 | constructor: (options = {}) ->
15 |
16 | _.defaults options,
17 | name: 'Bullet Item'
18 | backgroundColor: null
19 | height: 15
20 |
21 | super options
22 |
23 | @bullet = new Layer
24 | parent: @
25 | name: 'Bullet'
26 | y: Align.center(4)
27 | height: 12
28 | width: 12
29 | borderRadius: 16
30 | backgroundColor: '#FFF'
31 |
32 | @textLayer = new TextLayer
33 | name: 'Label'
34 | parent: @
35 | x: @bullet.width + 16
36 | y: 0
37 | fontFamily: 'Helvetica'
38 | text: 'Gotcha'
39 | color: '#fff'
40 | fontWeight: 600
41 | fontSize: 18
42 | width: 300
43 | lineHeight: 1.2
44 | text: options.text ? 'Bullet point'
45 |
46 | @height = @textLayer.height
47 | @width = 320
48 |
49 | # CTA
50 | class CTA extends Layer
51 | constructor: (options = {}) ->
52 |
53 | _.defaults options,
54 | backgroundColor: null
55 | borderRadius: 4
56 | height: 44
57 | width: 128
58 | text: 'Click Me'
59 | gradient:
60 | start: '#0070ff'
61 | end: '#00aaff'
62 | angle: 15
63 | borderWidth: 1
64 | borderColor: '#00aaff'
65 |
66 | @action = -> null
67 |
68 | super options
69 |
70 | @textLayer = new TextLayer
71 | parent: @
72 | name: 'CTA Label'
73 | y: Align.center
74 | textAlign: 'center'
75 | width: @width
76 | fontFamily: 'Helvetica'
77 | fontWeight: 600
78 | fontSize: 16
79 | shadowY: 1
80 | shadowBlur: 3
81 | shadowColor: 'rgba(0,0,0,.26)'
82 | color: 'rgba(255, 255, 255, 1.000)'
83 | text: 'Get Started'
84 |
85 | @onTap => @action()
86 |
87 | spin = new Animation @,
88 | saturate: 150
89 | gradient:
90 | start: '#0050fa'
91 | end: '#00aaff'
92 | angle: 50
93 | options:
94 | time: 3
95 | curve: 'linear'
96 |
97 | spinBack = spin.reverse()
98 |
99 | spin.onAnimationEnd -> spinBack.start()
100 | spinBack.onAnimationEnd -> spin.start()
101 |
102 | spin.start()
103 |
104 | # Step
105 |
106 | class Step extends Layer
107 | constructor: (options = {}) ->
108 |
109 | _.defaults options,
110 | name: 'Step'
111 | icons: []
112 | text: 'Example Text'
113 | height: 44
114 | backgroundColor: null
115 | opacity: 0
116 |
117 | super options
118 |
119 | last = 0
120 |
121 | for icon, i in options.icons
122 | _.assign icon,
123 | parent: @
124 | x: last
125 | y: 0
126 |
127 | if i is 0 and options.icons.length > 1
128 | textLayer = new TextLayer
129 | name: 'or'
130 | parent: @
131 | x: icon.maxX + 16
132 | y: Align.center()
133 | text: 'or'
134 | fontSize: 18
135 | fontWeight: 600
136 | color: '#FFF'
137 |
138 | last = textLayer.maxX + 16
139 |
140 | else
141 | last = icon.maxX + 16
142 |
143 | textLayer = new TextLayer
144 | parent: @
145 | name: 'body'
146 | x: last
147 | y: Align.center()
148 | width: (Screen.width - last) - 48
149 | text: options.text
150 | fontSize: 22
151 | fontWeight: 600
152 | color: '#FFF'
153 |
154 | show: =>
155 | y = @y
156 |
157 | _.assign @,
158 | opacity: 0
159 | y: y - 16
160 |
161 | @animate
162 | opacity: 1
163 | y: y
164 | options:
165 | time: .35
166 |
167 | # --
168 |
169 | # layers
170 |
171 |
172 | title = new TextLayer
173 | name: 'Title'
174 | x: 32
175 | y: 64
176 | color: '#FFF'
177 | fontSize: 56
178 | fontWeight: 600
179 | fontFamily: 'Helvetica'
180 | text: 'Gotcha'
181 | letterSpacing: 1.5
182 |
183 | ok_hand.props =
184 | x: title.maxX + 16
185 | midY: title.midY - 12
186 | scale: 2.5
187 | rotation: -10
188 | padding: {top: 8}
189 | opacity: 0
190 |
191 | subtitle = new TextLayer
192 | name: 'Subtitle'
193 | x: title.x
194 | y: title.maxY + 8
195 | width: 320
196 | fontSize: 24
197 | color: '#FFF'
198 | fontWeight: 600
199 | fontFamily: 'Helvetica'
200 | text: 'A developer handoff tool for Framer'
201 |
202 |
203 | # Step 1
204 |
205 | step1 = new Step
206 | x: 32
207 | y: subtitle.maxY + 32
208 | icons: [open_key, open_key_alt]
209 | text: 'to enable or disable'
210 |
211 | # Step 2
212 |
213 | step2 = new Step
214 | x: 32
215 | y: step1.maxY + 32
216 | icons: [select_key_alt, select_key]
217 | text: 'to select a hovered layer'
218 |
219 | # Step 3
220 |
221 | step3 = new Step
222 | x: 32
223 | y: step2.maxY + 32
224 | icons: [tap_key]
225 | text: 'to send a tap'
226 |
227 | # Step 4
228 |
229 | step4 = new Step
230 | x: 32
231 | y: step3.maxY + 32
232 | icons: [pause_key]
233 | text: 'to pause or unpause'
234 |
235 | # Step 5
236 |
237 | step5 = new Step
238 | x: 32
239 | y: step4.maxY + 32
240 | text: 'Works in Framer Cloud, too.'
241 |
242 | # CTA
243 |
244 | getStarted = new CTA
245 | name: 'Get Started CTA'
246 | x: 16
247 | y: subtitle.maxY + 16
248 | opacity: 0
249 | text: 'Get Started'
250 |
251 | slides.brightness = 0
252 |
253 | lowscrim.gradient =
254 | start: 'rgba(0,0,0,.7)'
255 | end: 'rgba(0,0,0,0)'
256 |
257 | # animation toy
258 |
259 | loader = new Layer
260 | width: 64
261 | height: 64
262 | x: Align.right(-32)
263 | y: Align.bottom(-32)
264 | backgroundColor: null
265 | borderWidth: 16
266 | borderRadius: 64
267 | borderColor: '0070ff'
268 | blending: Blending.colorDodge
269 | opacity: 0
270 | scale: .7
271 |
272 | loaderSplit = new Layer
273 | parent: loader
274 | height: 18, width: 18
275 | x: Align.center
276 | y: -2
277 | backgroundColor: '#0070ff'
278 | borderRadius: 8
279 |
280 | loading = new Animation loader,
281 | rotation: 365
282 | borderColor: '#0070f0'
283 | options:
284 | time: 12
285 | looping: true
286 | curve: 'linear'
287 |
288 | loading.start()
289 | loader.draggable.enabled = true
290 | loader.draggable.constraints =
291 | width: Screen.width
292 | height: Screen.height
293 |
294 |
295 | # Functions
296 |
297 | # start
298 | start = ->
299 | title.opacity = 0
300 | subtitle.opacity = 0
301 |
302 | title.animate
303 | opacity: 1
304 | options:
305 | time: 2
306 | delay: 1.5
307 |
308 | ok_hand.animate
309 | opacity: 1
310 | rotation: 10
311 | options:
312 | curve: Spring
313 | delay: 2.2
314 |
315 | subtitle.animate
316 | opacity: 1
317 | options:
318 | time: .8
319 | delay: 3.2
320 |
321 |
322 | slides.animate
323 | brightness: 60
324 | options:
325 | time: 4
326 | delay: .4
327 | curve: 'ease-out'
328 |
329 | slides.animate
330 | y: -80
331 | options:
332 | time: 4
333 | curve: 'ease-out'
334 |
335 |
336 | getStarted.once Events.AnimationEnd, ->
337 | getStarted.action = showSteps1
338 |
339 | getStarted.animate
340 | x: 32
341 | opacity: 1
342 | options:
343 | time: .75
344 | delay: 4.5
345 |
346 |
347 |
348 | # Show Step 1
349 |
350 | showSteps1 = ->
351 | slides.animate
352 | blur: 6
353 | scale: 1.03
354 | brightness: 35
355 | options:
356 | time: .35
357 |
358 | getStarted.textLayer.animate
359 | opacity: 0
360 | options:
361 | time: .25
362 |
363 | getStarted.once Events.AnimationEnd,->
364 | getStarted.textLayer.text = 'Next'
365 | getStarted.textLayer.animate
366 | opacity: 1
367 | options:
368 | time: .25
369 |
370 | getStarted.animate
371 | y: step1.maxY + 32
372 | options:
373 | time: .35
374 |
375 | step1.show()
376 |
377 | getStarted.action = showSteps2
378 |
379 | # Show Step 2
380 |
381 | showSteps2 = ->
382 |
383 | getStarted.animate
384 | y: step2.maxY + 32
385 | options:
386 | time: .35
387 |
388 | step2.show()
389 |
390 | getStarted.action = showSteps3
391 |
392 | # Show Step 3
393 |
394 | showSteps3 = ->
395 |
396 | getStarted.animate
397 | y: step3.maxY + 32
398 | options:
399 | time: .35
400 |
401 | step3.show()
402 |
403 | getStarted.action = showSteps3
404 |
405 | getStarted.action = showSteps4
406 |
407 | # Show Step 4
408 |
409 | showSteps4 = ->
410 |
411 | getStarted.animate
412 | y: step4.maxY + 32
413 | options:
414 | time: .35
415 |
416 | step4.show()
417 |
418 | loader.animate
419 | scale: 1
420 | opacity: 1
421 | options:
422 | time: .6
423 |
424 | getStarted.action = showSteps5
425 |
426 | # Show Step 5
427 |
428 | showSteps5 = ->
429 |
430 | getStarted.once Events.AnimationEnd, ->
431 | @visible = false
432 |
433 | getStarted.animate
434 | y: step5.maxY + 32
435 | opacity: 0
436 | options:
437 | time: .35
438 |
439 | step5.show()
440 |
441 | loader.animate
442 | scale: .5
443 | options:
444 | time: .6
445 |
446 |
447 | # Kickoff
448 |
449 | start()
--------------------------------------------------------------------------------
/example.framer/framer/.bookmark:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/framer/.bookmark
--------------------------------------------------------------------------------
/example.framer/framer/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "orientation" : 0,
3 | "updateDelay" : 1,
4 | "designModeSelected" : 0,
5 | "cachedDeviceHeight" : 667,
6 | "contentScale" : 2,
7 | "fullScreen" : false,
8 | "cachedDeviceWidth" : 375,
9 | "deviceType" : "apple-iphone-8-space-gray",
10 | "sharedPrototype" : true,
11 | "propertyPanelToggleStates" : {
12 | "Filters" : true
13 | },
14 | "projectId" : "5141C669-A2EF-481F-9670-73469F2A2175",
15 | "deviceOrientation" : 0,
16 | "selectedHand" : "",
17 | "showBezel" : true,
18 | "foldedCodeRanges" : [
19 | "{155, 647}",
20 | "{803, 967}",
21 | "{1771, 954}",
22 | "{2732, 1241}",
23 | "{3974, 623}",
24 | "{4612, 616}",
25 | "{5229, 447}",
26 | "{5677, 148}",
27 | "{5826, 182}",
28 | "{6009, 211}",
29 | "{6221, 242}"
30 | ],
31 | "deviceScale" : "fit"
32 | }
--------------------------------------------------------------------------------
/example.framer/framer/design.vekter:
--------------------------------------------------------------------------------
1 | {
2 | "root" : {
3 | "__class" : "CanvasNode",
4 | "children" : [
5 | {
6 | "__class" : "FrameNode",
7 | "aspectRatioLocked" : false,
8 | "blendingEnabled" : 0,
9 | "blendingMode" : "normal",
10 | "blur" : 12,
11 | "blurEnabled" : 0,
12 | "blurType" : "layer",
13 | "borderBottom" : 1,
14 | "borderColor" : "#222",
15 | "borderEnabled" : 0,
16 | "borderLeft" : 1,
17 | "borderPerSide" : false,
18 | "borderRight" : 1,
19 | "borderStyle" : "solid",
20 | "borderTop" : 1,
21 | "borderWidth" : 1,
22 | "bottom" : null,
23 | "boxShadows" : [
24 |
25 | ],
26 | "brightness" : 100,
27 | "brightnessEnabled" : 0,
28 | "centerAnchorX" : 0,
29 | "centerAnchorY" : 0,
30 | "children" : [
31 | {
32 | "__class" : "ImageNode",
33 | "aspectRatioLocked" : true,
34 | "blendingEnabled" : 0,
35 | "blendingMode" : "normal",
36 | "blur" : 12,
37 | "blurEnabled" : 0,
38 | "blurType" : "layer",
39 | "borderBottom" : 1,
40 | "borderColor" : "#222",
41 | "borderEnabled" : 0,
42 | "borderLeft" : 1,
43 | "borderPerSide" : false,
44 | "borderRight" : 1,
45 | "borderStyle" : "solid",
46 | "borderTop" : 1,
47 | "borderWidth" : 1,
48 | "bottom" : -105,
49 | "boxShadows" : [
50 |
51 | ],
52 | "brightness" : 100,
53 | "brightnessEnabled" : 0,
54 | "centerAnchorX" : 0.5,
55 | "centerAnchorY" : 0.56465517241379315,
56 | "children" : [
57 |
58 | ],
59 | "clip" : false,
60 | "constraintsLocked" : false,
61 | "contrast" : 100,
62 | "contrastEnabled" : 0,
63 | "exportOptions" : [
64 |
65 | ],
66 | "fillColor" : "transparent",
67 | "fillEnabled" : false,
68 | "grayscale" : 0,
69 | "grayScaleEnabled" : 0,
70 | "height" : 917,
71 | "heightFactor" : null,
72 | "hueRotate" : 0,
73 | "hueRotateEnabled" : 0,
74 | "id" : "Ql|Z#=r2",
75 | "image" : "jGRzkD2R7YrxRiRi9FkYz6kxJNgft3BT8iJ0bKuSD99x6bCz9iP5RZLkmnoV9TMAcxh4cxTViNynzzqhiIQ.png",
76 | "imageBackgroundSize" : "Cover",
77 | "intrinsicHeight" : 917,
78 | "intrinsicWidth" : 375,
79 | "invert" : 0,
80 | "invertEnabled" : 0,
81 | "left" : 0,
82 | "locked" : false,
83 | "name" : "slides",
84 | "opacity" : 1,
85 | "originalFilename" : "",
86 | "parentid" : "VCU'zKmu",
87 | "radius" : 0,
88 | "radiusBottomLeft" : 0,
89 | "radiusBottomRight" : 0,
90 | "radiusPerCorner" : false,
91 | "radiusTopLeft" : 0,
92 | "radiusTopRight" : 0,
93 | "right" : 0,
94 | "rotation" : 0,
95 | "saturate" : 100,
96 | "saturateEnabled" : 0,
97 | "sepia" : 0,
98 | "sepiaEnabled" : 0,
99 | "targetName" : "slides",
100 | "top" : null,
101 | "visible" : true,
102 | "width" : 375,
103 | "widthFactor" : null
104 | },
105 | {
106 | "__class" : "FrameNode",
107 | "aspectRatioLocked" : false,
108 | "blendingEnabled" : 0,
109 | "blendingMode" : "normal",
110 | "blur" : 12,
111 | "blurEnabled" : 0,
112 | "blurType" : "layer",
113 | "borderBottom" : 1,
114 | "borderColor" : "#222",
115 | "borderEnabled" : 0,
116 | "borderLeft" : 1,
117 | "borderPerSide" : false,
118 | "borderRight" : 1,
119 | "borderStyle" : "solid",
120 | "borderTop" : 1,
121 | "borderWidth" : 1,
122 | "bottom" : 0,
123 | "boxShadows" : [
124 |
125 | ],
126 | "brightness" : 100,
127 | "brightnessEnabled" : 0,
128 | "centerAnchorX" : 0.5,
129 | "centerAnchorY" : 0.91379310344827591,
130 | "children" : [
131 |
132 | ],
133 | "clip" : false,
134 | "constraintsLocked" : false,
135 | "contrast" : 100,
136 | "contrastEnabled" : 0,
137 | "deviceType" : null,
138 | "exportIncludesBackground" : true,
139 | "exportOptions" : [
140 |
141 | ],
142 | "fillColor" : "hsla(0, 0%, 0%, 0)",
143 | "fillEnabled" : true,
144 | "fillGradient" : {
145 | "__class" : "LinearGradient",
146 | "alpha" : 1,
147 | "angle" : 0,
148 | "end" : "rgba(0,0,0,0)",
149 | "start" : "black"
150 | },
151 | "fillType" : "color",
152 | "grayscale" : 0,
153 | "grayScaleEnabled" : 0,
154 | "height" : 140,
155 | "heightFactor" : null,
156 | "hueRotate" : 0,
157 | "hueRotateEnabled" : 0,
158 | "id" : "r>4L1L@I",
159 | "invert" : 0,
160 | "invertEnabled" : 0,
161 | "left" : 0,
162 | "locked" : false,
163 | "name" : "lowscrim",
164 | "opacity" : 1,
165 | "parentid" : "VCU'zKmu",
166 | "radius" : 0,
167 | "radiusBottomLeft" : 0,
168 | "radiusBottomRight" : 0,
169 | "radiusIsRelative" : false,
170 | "radiusPerCorner" : false,
171 | "radiusTopLeft" : 0,
172 | "radiusTopRight" : 0,
173 | "right" : 0,
174 | "rotation" : 0,
175 | "saturate" : 100,
176 | "saturateEnabled" : 0,
177 | "sepia" : 0,
178 | "sepiaEnabled" : 0,
179 | "targetName" : "lowscrim",
180 | "top" : null,
181 | "visible" : true,
182 | "width" : 375,
183 | "widthFactor" : null
184 | }
185 | ],
186 | "clip" : true,
187 | "constraintsLocked" : false,
188 | "contrast" : 100,
189 | "contrastEnabled" : 0,
190 | "deviceType" : "apple-iphone-x-space-gray",
191 | "exportIncludesBackground" : true,
192 | "exportOptions" : [
193 |
194 | ],
195 | "fillColor" : "#ffffff",
196 | "fillEnabled" : true,
197 | "fillGradient" : {
198 | "__class" : "LinearGradient",
199 | "alpha" : 1,
200 | "angle" : 0,
201 | "end" : "rgba(0,0,0,0)",
202 | "start" : "black"
203 | },
204 | "fillType" : "color",
205 | "grayscale" : 0,
206 | "grayScaleEnabled" : 0,
207 | "height" : 812,
208 | "heightFactor" : null,
209 | "hueRotate" : 0,
210 | "hueRotateEnabled" : 0,
211 | "id" : "VCU'zKmu",
212 | "invert" : 0,
213 | "invertEnabled" : 0,
214 | "left" : -85,
215 | "locked" : false,
216 | "name" : null,
217 | "opacity" : 1,
218 | "parentid" : "p3N%MBBR",
219 | "radius" : 0,
220 | "radiusBottomLeft" : 0,
221 | "radiusBottomRight" : 0,
222 | "radiusIsRelative" : false,
223 | "radiusPerCorner" : false,
224 | "radiusTopLeft" : 0,
225 | "radiusTopRight" : 0,
226 | "right" : null,
227 | "rotation" : 0,
228 | "saturate" : 100,
229 | "saturateEnabled" : 0,
230 | "sepia" : 0,
231 | "sepiaEnabled" : 0,
232 | "targetName" : null,
233 | "top" : 1024,
234 | "visible" : true,
235 | "width" : 375,
236 | "widthFactor" : null
237 | },
238 | {
239 | "__class" : "TextNode",
240 | "autoSize" : true,
241 | "blendingEnabled" : 0,
242 | "blendingMode" : "normal",
243 | "blur" : 12,
244 | "blurEnabled" : 0,
245 | "blurType" : "layer",
246 | "bottom" : null,
247 | "brightness" : 100,
248 | "brightnessEnabled" : 0,
249 | "centerAnchorX" : 0,
250 | "centerAnchorY" : 0,
251 | "constraintsLocked" : false,
252 | "contrast" : 100,
253 | "contrastEnabled" : 0,
254 | "editable" : false,
255 | "exportOptions" : [
256 |
257 | ],
258 | "grayscale" : 0,
259 | "grayScaleEnabled" : 0,
260 | "height" : 100,
261 | "heightFactor" : null,
262 | "hueRotate" : 0,
263 | "hueRotateEnabled" : 0,
264 | "id" : "H8F;`Z:>",
265 | "invert" : 0,
266 | "invertEnabled" : 0,
267 | "left" : 101,
268 | "locked" : false,
269 | "name" : "ok_hand",
270 | "opacity" : 1,
271 | "parentid" : "p3N%MBBR",
272 | "right" : null,
273 | "rotation" : 0,
274 | "saturate" : 100,
275 | "saturateEnabled" : 0,
276 | "sepia" : 0,
277 | "sepiaEnabled" : 0,
278 | "shadows" : [
279 |
280 | ],
281 | "styledText" : {
282 | "__class" : "StyledTextDraft",
283 | "blocks" : [
284 | {
285 | "data" : {
286 | "emptyStyle" : [
287 |
288 | ]
289 | },
290 | "depth" : 0,
291 | "entityRanges" : [
292 |
293 | ],
294 | "inlineStyleRanges" : [
295 |
296 | ],
297 | "key" : "7le2p",
298 | "text" : "👌",
299 | "type" : "unstyled"
300 | }
301 | ],
302 | "entityMap" : {
303 |
304 | }
305 | },
306 | "targetName" : "ok_hand",
307 | "top" : 918,
308 | "visible" : true,
309 | "width" : 100,
310 | "widthFactor" : null
311 | },
312 | {
313 | "__class" : "FrameNode",
314 | "aspectRatioLocked" : true,
315 | "blendingEnabled" : 0,
316 | "blendingMode" : "normal",
317 | "blur" : 12,
318 | "blurEnabled" : 0,
319 | "blurType" : "layer",
320 | "borderBottom" : 1,
321 | "borderColor" : "#222",
322 | "borderEnabled" : 1,
323 | "borderLeft" : 1,
324 | "borderPerSide" : false,
325 | "borderRight" : 1,
326 | "borderStyle" : "solid",
327 | "borderTop" : 1,
328 | "borderWidth" : 1,
329 | "bottom" : null,
330 | "boxShadows" : [
331 | {
332 | "__class" : "BoxShadow",
333 | "blur" : 0,
334 | "color" : "hsl(0, 0%, 0%)",
335 | "enabled" : true,
336 | "inset" : false,
337 | "spread" : 0,
338 | "x" : 2,
339 | "y" : 2
340 | }
341 | ],
342 | "brightness" : 100,
343 | "brightnessEnabled" : 0,
344 | "centerAnchorX" : 0.35199999999999998,
345 | "centerAnchorY" : 0.1499250374812594,
346 | "children" : [
347 | {
348 | "__class" : "TextNode",
349 | "autoSize" : false,
350 | "blendingEnabled" : 0,
351 | "blendingMode" : "normal",
352 | "blur" : 12,
353 | "blurEnabled" : 0,
354 | "blurType" : "layer",
355 | "bottom" : null,
356 | "brightness" : 100,
357 | "brightnessEnabled" : 0,
358 | "centerAnchorX" : 0.5,
359 | "centerAnchorY" : 0.5,
360 | "constraintsLocked" : false,
361 | "contrast" : 100,
362 | "contrastEnabled" : 0,
363 | "editable" : false,
364 | "exportOptions" : [
365 |
366 | ],
367 | "grayscale" : 0,
368 | "grayScaleEnabled" : 0,
369 | "height" : 26,
370 | "heightFactor" : null,
371 | "hueRotate" : 0,
372 | "hueRotateEnabled" : 0,
373 | "id" : "X6>jf:$Z",
374 | "invert" : 0,
375 | "invertEnabled" : 0,
376 | "left" : 0,
377 | "locked" : false,
378 | "name" : null,
379 | "opacity" : 1,
380 | "parentid" : "Dys.w-Ea",
381 | "right" : 0,
382 | "rotation" : 0,
383 | "saturate" : 100,
384 | "saturateEnabled" : 0,
385 | "sepia" : 0,
386 | "sepiaEnabled" : 0,
387 | "shadows" : [
388 |
389 | ],
390 | "styledText" : {
391 | "__class" : "StyledTextDraft",
392 | "blocks" : [
393 | {
394 | "data" : {
395 | "emptyStyle" : [
396 | "COLOR:rgb(0, 0, 0)",
397 | "LETTERSPACING:0",
398 | "LINEHEIGHT:1.2",
399 | "FONT:__SF-UI-Text-Semibold__",
400 | "SIZE:22",
401 | "ALIGN:center"
402 | ]
403 | },
404 | "depth" : 0,
405 | "entityRanges" : [
406 |
407 | ],
408 | "inlineStyleRanges" : [
409 | {
410 | "length" : 1,
411 | "offset" : 0,
412 | "style" : "COLOR:rgb(0, 0, 0)"
413 | },
414 | {
415 | "length" : 1,
416 | "offset" : 0,
417 | "style" : "LETTERSPACING:0"
418 | },
419 | {
420 | "length" : 1,
421 | "offset" : 0,
422 | "style" : "LINEHEIGHT:1.2"
423 | },
424 | {
425 | "length" : 1,
426 | "offset" : 0,
427 | "style" : "FONT:__SF-UI-Text-Semibold__"
428 | },
429 | {
430 | "length" : 1,
431 | "offset" : 0,
432 | "style" : "SIZE:22"
433 | },
434 | {
435 | "length" : 1,
436 | "offset" : 0,
437 | "style" : "ALIGN:center"
438 | }
439 | ],
440 | "key" : "7a7l6",
441 | "text" : "<",
442 | "type" : "unstyled"
443 | }
444 | ],
445 | "entityMap" : {
446 |
447 | }
448 | },
449 | "targetName" : null,
450 | "top" : null,
451 | "visible" : true,
452 | "width" : 44,
453 | "widthFactor" : null
454 | }
455 | ],
456 | "clip" : false,
457 | "constraintsLocked" : false,
458 | "contrast" : 100,
459 | "contrastEnabled" : 0,
460 | "deviceType" : null,
461 | "exportIncludesBackground" : true,
462 | "exportOptions" : [
463 |
464 | ],
465 | "fillColor" : "hsl(0, 0%, 96%)",
466 | "fillEnabled" : true,
467 | "fillGradient" : {
468 | "__class" : "LinearGradient",
469 | "alpha" : 1,
470 | "angle" : 0,
471 | "end" : "rgba(0,0,0,0)",
472 | "start" : "black"
473 | },
474 | "fillType" : "color",
475 | "grayscale" : 0,
476 | "grayScaleEnabled" : 0,
477 | "height" : 44,
478 | "heightFactor" : null,
479 | "hueRotate" : 0,
480 | "hueRotateEnabled" : 0,
481 | "id" : "Dys.w-Ea",
482 | "invert" : 0,
483 | "invertEnabled" : 0,
484 | "left" : 446,
485 | "locked" : false,
486 | "name" : "open_key_alt",
487 | "opacity" : 1,
488 | "parentid" : "p3N%MBBR",
489 | "radius" : 4,
490 | "radiusBottomLeft" : 4,
491 | "radiusBottomRight" : 4,
492 | "radiusIsRelative" : false,
493 | "radiusPerCorner" : false,
494 | "radiusTopLeft" : 4,
495 | "radiusTopRight" : 4,
496 | "right" : null,
497 | "rotation" : 0,
498 | "saturate" : 100,
499 | "saturateEnabled" : 0,
500 | "sepia" : 0,
501 | "sepiaEnabled" : 0,
502 | "targetName" : "open_key_alt",
503 | "top" : 1024,
504 | "visible" : true,
505 | "width" : 44,
506 | "widthFactor" : null
507 | },
508 | {
509 | "__class" : "FrameNode",
510 | "aspectRatioLocked" : true,
511 | "blendingEnabled" : 0,
512 | "blendingMode" : "normal",
513 | "blur" : 12,
514 | "blurEnabled" : 0,
515 | "blurType" : "layer",
516 | "borderBottom" : 1,
517 | "borderColor" : "#222",
518 | "borderEnabled" : 1,
519 | "borderLeft" : 1,
520 | "borderPerSide" : false,
521 | "borderRight" : 1,
522 | "borderStyle" : "solid",
523 | "borderTop" : 1,
524 | "borderWidth" : 1,
525 | "bottom" : null,
526 | "boxShadows" : [
527 | {
528 | "__class" : "BoxShadow",
529 | "blur" : 0,
530 | "color" : "hsl(0, 0%, 0%)",
531 | "enabled" : true,
532 | "inset" : false,
533 | "spread" : 0,
534 | "x" : 2,
535 | "y" : 2
536 | }
537 | ],
538 | "brightness" : 100,
539 | "brightnessEnabled" : 0,
540 | "centerAnchorX" : 0.17333333333333331,
541 | "centerAnchorY" : 0.1499250374812594,
542 | "children" : [
543 | {
544 | "__class" : "TextNode",
545 | "autoSize" : false,
546 | "blendingEnabled" : 0,
547 | "blendingMode" : "normal",
548 | "blur" : 12,
549 | "blurEnabled" : 0,
550 | "blurType" : "layer",
551 | "bottom" : null,
552 | "brightness" : 100,
553 | "brightnessEnabled" : 0,
554 | "centerAnchorX" : 0.5,
555 | "centerAnchorY" : 0.5,
556 | "constraintsLocked" : false,
557 | "contrast" : 100,
558 | "contrastEnabled" : 0,
559 | "editable" : false,
560 | "exportOptions" : [
561 |
562 | ],
563 | "grayscale" : 0,
564 | "grayScaleEnabled" : 0,
565 | "height" : 26,
566 | "heightFactor" : null,
567 | "hueRotate" : 0,
568 | "hueRotateEnabled" : 0,
569 | "id" : "&(b0H`{s",
570 | "invert" : 0,
571 | "invertEnabled" : 0,
572 | "left" : 0,
573 | "locked" : false,
574 | "name" : null,
575 | "opacity" : 1,
576 | "parentid" : "a>M}5w7,",
577 | "right" : 0,
578 | "rotation" : 0,
579 | "saturate" : 100,
580 | "saturateEnabled" : 0,
581 | "sepia" : 0,
582 | "sepiaEnabled" : 0,
583 | "shadows" : [
584 |
585 | ],
586 | "styledText" : {
587 | "__class" : "StyledTextDraft",
588 | "blocks" : [
589 | {
590 | "data" : {
591 | "emptyStyle" : [
592 | "COLOR:rgb(0, 0, 0)",
593 | "LETTERSPACING:0",
594 | "LINEHEIGHT:1.2",
595 | "FONT:__SF-UI-Text-Semibold__",
596 | "SIZE:22",
597 | "ALIGN:center"
598 | ]
599 | },
600 | "depth" : 0,
601 | "entityRanges" : [
602 |
603 | ],
604 | "inlineStyleRanges" : [
605 | {
606 | "length" : 1,
607 | "offset" : 0,
608 | "style" : "COLOR:rgb(0, 0, 0)"
609 | },
610 | {
611 | "length" : 1,
612 | "offset" : 0,
613 | "style" : "LETTERSPACING:0"
614 | },
615 | {
616 | "length" : 1,
617 | "offset" : 0,
618 | "style" : "LINEHEIGHT:1.2"
619 | },
620 | {
621 | "length" : 1,
622 | "offset" : 0,
623 | "style" : "FONT:__SF-UI-Text-Semibold__"
624 | },
625 | {
626 | "length" : 1,
627 | "offset" : 0,
628 | "style" : "SIZE:22"
629 | },
630 | {
631 | "length" : 1,
632 | "offset" : 0,
633 | "style" : "ALIGN:center"
634 | }
635 | ],
636 | "key" : "7a7l6",
637 | "text" : "`",
638 | "type" : "unstyled"
639 | }
640 | ],
641 | "entityMap" : {
642 |
643 | }
644 | },
645 | "targetName" : null,
646 | "top" : null,
647 | "visible" : true,
648 | "width" : 44,
649 | "widthFactor" : null
650 | }
651 | ],
652 | "clip" : false,
653 | "constraintsLocked" : false,
654 | "contrast" : 100,
655 | "contrastEnabled" : 0,
656 | "deviceType" : null,
657 | "exportIncludesBackground" : true,
658 | "exportOptions" : [
659 |
660 | ],
661 | "fillColor" : "hsl(0, 0%, 96%)",
662 | "fillEnabled" : true,
663 | "fillGradient" : {
664 | "__class" : "LinearGradient",
665 | "alpha" : 1,
666 | "angle" : 0,
667 | "end" : "rgba(0,0,0,0)",
668 | "start" : "black"
669 | },
670 | "fillType" : "color",
671 | "grayscale" : 0,
672 | "grayScaleEnabled" : 0,
673 | "height" : 44,
674 | "heightFactor" : null,
675 | "hueRotate" : 0,
676 | "hueRotateEnabled" : 0,
677 | "id" : "a>M}5w7,",
678 | "invert" : 0,
679 | "invertEnabled" : 0,
680 | "left" : 379,
681 | "locked" : false,
682 | "name" : "open_key",
683 | "opacity" : 1,
684 | "parentid" : "p3N%MBBR",
685 | "radius" : 4,
686 | "radiusBottomLeft" : 4,
687 | "radiusBottomRight" : 4,
688 | "radiusIsRelative" : false,
689 | "radiusPerCorner" : false,
690 | "radiusTopLeft" : 4,
691 | "radiusTopRight" : 4,
692 | "right" : null,
693 | "rotation" : 0,
694 | "saturate" : 100,
695 | "saturateEnabled" : 0,
696 | "sepia" : 0,
697 | "sepiaEnabled" : 0,
698 | "targetName" : "open_key",
699 | "top" : 1024,
700 | "visible" : true,
701 | "width" : 44,
702 | "widthFactor" : null
703 | },
704 | {
705 | "__class" : "FrameNode",
706 | "aspectRatioLocked" : true,
707 | "blendingEnabled" : 0,
708 | "blendingMode" : "normal",
709 | "blur" : 12,
710 | "blurEnabled" : 0,
711 | "blurType" : "layer",
712 | "borderBottom" : 1,
713 | "borderColor" : "#222",
714 | "borderEnabled" : 1,
715 | "borderLeft" : 1,
716 | "borderPerSide" : false,
717 | "borderRight" : 1,
718 | "borderStyle" : "solid",
719 | "borderTop" : 1,
720 | "borderWidth" : 1,
721 | "bottom" : null,
722 | "boxShadows" : [
723 | {
724 | "__class" : "BoxShadow",
725 | "blur" : 0,
726 | "color" : "hsl(0, 0%, 0%)",
727 | "enabled" : true,
728 | "inset" : false,
729 | "spread" : 0,
730 | "x" : 2,
731 | "y" : 2
732 | }
733 | ],
734 | "brightness" : 100,
735 | "brightnessEnabled" : 0,
736 | "centerAnchorX" : 0.17333333333333331,
737 | "centerAnchorY" : 0.26236881559220387,
738 | "children" : [
739 | {
740 | "__class" : "TextNode",
741 | "autoSize" : false,
742 | "blendingEnabled" : 0,
743 | "blendingMode" : "normal",
744 | "blur" : 12,
745 | "blurEnabled" : 0,
746 | "blurType" : "layer",
747 | "bottom" : null,
748 | "brightness" : 100,
749 | "brightnessEnabled" : 0,
750 | "centerAnchorX" : 0.5,
751 | "centerAnchorY" : 0.5,
752 | "constraintsLocked" : false,
753 | "contrast" : 100,
754 | "contrastEnabled" : 0,
755 | "editable" : false,
756 | "exportOptions" : [
757 |
758 | ],
759 | "grayscale" : 0,
760 | "grayScaleEnabled" : 0,
761 | "height" : 26,
762 | "heightFactor" : null,
763 | "hueRotate" : 0,
764 | "hueRotateEnabled" : 0,
765 | "id" : "G5v,!1uu",
766 | "invert" : 0,
767 | "invertEnabled" : 0,
768 | "left" : 0,
769 | "locked" : false,
770 | "name" : null,
771 | "opacity" : 1,
772 | "parentid" : "3+)-&.7!",
773 | "right" : 0,
774 | "rotation" : 0,
775 | "saturate" : 100,
776 | "saturateEnabled" : 0,
777 | "sepia" : 0,
778 | "sepiaEnabled" : 0,
779 | "shadows" : [
780 |
781 | ],
782 | "styledText" : {
783 | "__class" : "StyledTextDraft",
784 | "blocks" : [
785 | {
786 | "data" : {
787 | "emptyStyle" : [
788 | "COLOR:rgb(0, 0, 0)",
789 | "LETTERSPACING:0",
790 | "LINEHEIGHT:1.2",
791 | "FONT:__SF-UI-Text-Semibold__",
792 | "SIZE:22",
793 | "ALIGN:center"
794 | ]
795 | },
796 | "depth" : 0,
797 | "entityRanges" : [
798 |
799 | ],
800 | "inlineStyleRanges" : [
801 | {
802 | "length" : 1,
803 | "offset" : 0,
804 | "style" : "COLOR:rgb(0, 0, 0)"
805 | },
806 | {
807 | "length" : 1,
808 | "offset" : 0,
809 | "style" : "LETTERSPACING:0"
810 | },
811 | {
812 | "length" : 1,
813 | "offset" : 0,
814 | "style" : "LINEHEIGHT:1.2"
815 | },
816 | {
817 | "length" : 1,
818 | "offset" : 0,
819 | "style" : "FONT:__SF-UI-Text-Semibold__"
820 | },
821 | {
822 | "length" : 1,
823 | "offset" : 0,
824 | "style" : "SIZE:22"
825 | },
826 | {
827 | "length" : 1,
828 | "offset" : 0,
829 | "style" : "ALIGN:center"
830 | }
831 | ],
832 | "key" : "7a7l6",
833 | "text" : "\/",
834 | "type" : "unstyled"
835 | }
836 | ],
837 | "entityMap" : {
838 |
839 | }
840 | },
841 | "targetName" : null,
842 | "top" : null,
843 | "visible" : true,
844 | "width" : 44,
845 | "widthFactor" : null
846 | }
847 | ],
848 | "clip" : false,
849 | "constraintsLocked" : false,
850 | "contrast" : 100,
851 | "contrastEnabled" : 0,
852 | "deviceType" : null,
853 | "exportIncludesBackground" : true,
854 | "exportOptions" : [
855 |
856 | ],
857 | "fillColor" : "hsl(0, 0%, 96%)",
858 | "fillEnabled" : true,
859 | "fillGradient" : {
860 | "__class" : "LinearGradient",
861 | "alpha" : 1,
862 | "angle" : 0,
863 | "end" : "rgba(0,0,0,0)",
864 | "start" : "black"
865 | },
866 | "fillType" : "color",
867 | "grayscale" : 0,
868 | "grayScaleEnabled" : 0,
869 | "height" : 44,
870 | "heightFactor" : null,
871 | "hueRotate" : 0,
872 | "hueRotateEnabled" : 0,
873 | "id" : "3+)-&.7!",
874 | "invert" : 0,
875 | "invertEnabled" : 0,
876 | "left" : 379,
877 | "locked" : false,
878 | "name" : "select_key_alt",
879 | "opacity" : 1,
880 | "parentid" : "p3N%MBBR",
881 | "radius" : 4,
882 | "radiusBottomLeft" : 4,
883 | "radiusBottomRight" : 4,
884 | "radiusIsRelative" : false,
885 | "radiusPerCorner" : false,
886 | "radiusTopLeft" : 4,
887 | "radiusTopRight" : 4,
888 | "right" : null,
889 | "rotation" : 0,
890 | "saturate" : 100,
891 | "saturateEnabled" : 0,
892 | "sepia" : 0,
893 | "sepiaEnabled" : 0,
894 | "targetName" : "select_key_alt",
895 | "top" : 1099,
896 | "visible" : true,
897 | "width" : 44,
898 | "widthFactor" : null
899 | },
900 | {
901 | "__class" : "FrameNode",
902 | "aspectRatioLocked" : true,
903 | "blendingEnabled" : 0,
904 | "blendingMode" : "normal",
905 | "blur" : 12,
906 | "blurEnabled" : 0,
907 | "blurType" : "layer",
908 | "borderBottom" : 1,
909 | "borderColor" : "#222",
910 | "borderEnabled" : 1,
911 | "borderLeft" : 1,
912 | "borderPerSide" : false,
913 | "borderRight" : 1,
914 | "borderStyle" : "solid",
915 | "borderTop" : 1,
916 | "borderWidth" : 1,
917 | "bottom" : null,
918 | "boxShadows" : [
919 | {
920 | "__class" : "BoxShadow",
921 | "blur" : 0,
922 | "color" : "hsl(0, 0%, 0%)",
923 | "enabled" : true,
924 | "inset" : false,
925 | "spread" : 0,
926 | "x" : 2,
927 | "y" : 2
928 | }
929 | ],
930 | "brightness" : 100,
931 | "brightnessEnabled" : 0,
932 | "centerAnchorX" : 0.17333333333333331,
933 | "centerAnchorY" : 0.3748125937031484,
934 | "children" : [
935 | {
936 | "__class" : "TextNode",
937 | "autoSize" : false,
938 | "blendingEnabled" : 0,
939 | "blendingMode" : "normal",
940 | "blur" : 12,
941 | "blurEnabled" : 0,
942 | "blurType" : "layer",
943 | "bottom" : null,
944 | "brightness" : 100,
945 | "brightnessEnabled" : 0,
946 | "centerAnchorX" : 0.5,
947 | "centerAnchorY" : 0.5,
948 | "constraintsLocked" : false,
949 | "contrast" : 100,
950 | "contrastEnabled" : 0,
951 | "editable" : false,
952 | "exportOptions" : [
953 |
954 | ],
955 | "grayscale" : 0,
956 | "grayScaleEnabled" : 0,
957 | "height" : 26,
958 | "heightFactor" : null,
959 | "hueRotate" : 0,
960 | "hueRotateEnabled" : 0,
961 | "id" : "G,|`zHUg",
962 | "invert" : 0,
963 | "invertEnabled" : 0,
964 | "left" : 0,
965 | "locked" : false,
966 | "name" : null,
967 | "opacity" : 1,
968 | "parentid" : "7hQBJ!lT",
969 | "right" : 0,
970 | "rotation" : 0,
971 | "saturate" : 100,
972 | "saturateEnabled" : 0,
973 | "sepia" : 0,
974 | "sepiaEnabled" : 0,
975 | "shadows" : [
976 |
977 | ],
978 | "styledText" : {
979 | "__class" : "StyledTextDraft",
980 | "blocks" : [
981 | {
982 | "data" : {
983 | "emptyStyle" : [
984 | "COLOR:rgb(0, 0, 0)",
985 | "LETTERSPACING:0",
986 | "LINEHEIGHT:1.2",
987 | "FONT:__SF-UI-Text-Semibold__",
988 | "SIZE:22",
989 | "ALIGN:center"
990 | ]
991 | },
992 | "depth" : 0,
993 | "entityRanges" : [
994 |
995 | ],
996 | "inlineStyleRanges" : [
997 | {
998 | "length" : 1,
999 | "offset" : 0,
1000 | "style" : "COLOR:rgb(0, 0, 0)"
1001 | },
1002 | {
1003 | "length" : 1,
1004 | "offset" : 0,
1005 | "style" : "LETTERSPACING:0"
1006 | },
1007 | {
1008 | "length" : 1,
1009 | "offset" : 0,
1010 | "style" : "LINEHEIGHT:1.2"
1011 | },
1012 | {
1013 | "length" : 1,
1014 | "offset" : 0,
1015 | "style" : "FONT:__SF-UI-Text-Semibold__"
1016 | },
1017 | {
1018 | "length" : 1,
1019 | "offset" : 0,
1020 | "style" : "SIZE:22"
1021 | },
1022 | {
1023 | "length" : 1,
1024 | "offset" : 0,
1025 | "style" : "ALIGN:center"
1026 | }
1027 | ],
1028 | "key" : "7a7l6",
1029 | "text" : "\\",
1030 | "type" : "unstyled"
1031 | }
1032 | ],
1033 | "entityMap" : {
1034 |
1035 | }
1036 | },
1037 | "targetName" : null,
1038 | "top" : null,
1039 | "visible" : true,
1040 | "width" : 44,
1041 | "widthFactor" : null
1042 | }
1043 | ],
1044 | "clip" : false,
1045 | "constraintsLocked" : false,
1046 | "contrast" : 100,
1047 | "contrastEnabled" : 0,
1048 | "deviceType" : null,
1049 | "exportIncludesBackground" : true,
1050 | "exportOptions" : [
1051 |
1052 | ],
1053 | "fillColor" : "hsl(0, 0%, 96%)",
1054 | "fillEnabled" : true,
1055 | "fillGradient" : {
1056 | "__class" : "LinearGradient",
1057 | "alpha" : 1,
1058 | "angle" : 0,
1059 | "end" : "rgba(0,0,0,0)",
1060 | "start" : "black"
1061 | },
1062 | "fillType" : "color",
1063 | "grayscale" : 0,
1064 | "grayScaleEnabled" : 0,
1065 | "height" : 44,
1066 | "heightFactor" : null,
1067 | "hueRotate" : 0,
1068 | "hueRotateEnabled" : 0,
1069 | "id" : "7hQBJ!lT",
1070 | "invert" : 0,
1071 | "invertEnabled" : 0,
1072 | "left" : 379,
1073 | "locked" : false,
1074 | "name" : "pause_key",
1075 | "opacity" : 1,
1076 | "parentid" : "p3N%MBBR",
1077 | "radius" : 4,
1078 | "radiusBottomLeft" : 4,
1079 | "radiusBottomRight" : 4,
1080 | "radiusIsRelative" : false,
1081 | "radiusPerCorner" : false,
1082 | "radiusTopLeft" : 4,
1083 | "radiusTopRight" : 4,
1084 | "right" : null,
1085 | "rotation" : 0,
1086 | "saturate" : 100,
1087 | "saturateEnabled" : 0,
1088 | "sepia" : 0,
1089 | "sepiaEnabled" : 0,
1090 | "targetName" : "pause_key",
1091 | "top" : 1174,
1092 | "visible" : true,
1093 | "width" : 44,
1094 | "widthFactor" : null
1095 | },
1096 | {
1097 | "__class" : "FrameNode",
1098 | "aspectRatioLocked" : true,
1099 | "blendingEnabled" : 0,
1100 | "blendingMode" : "normal",
1101 | "blur" : 12,
1102 | "blurEnabled" : 0,
1103 | "blurType" : "layer",
1104 | "borderBottom" : 1,
1105 | "borderColor" : "#222",
1106 | "borderEnabled" : 1,
1107 | "borderLeft" : 1,
1108 | "borderPerSide" : false,
1109 | "borderRight" : 1,
1110 | "borderStyle" : "solid",
1111 | "borderTop" : 1,
1112 | "borderWidth" : 1,
1113 | "bottom" : null,
1114 | "boxShadows" : [
1115 | {
1116 | "__class" : "BoxShadow",
1117 | "blur" : 0,
1118 | "color" : "hsl(0, 0%, 0%)",
1119 | "enabled" : true,
1120 | "inset" : false,
1121 | "spread" : 0,
1122 | "x" : 2,
1123 | "y" : 2
1124 | }
1125 | ],
1126 | "brightness" : 100,
1127 | "brightnessEnabled" : 0,
1128 | "centerAnchorX" : 0.35199999999999998,
1129 | "centerAnchorY" : 0.26236881559220387,
1130 | "children" : [
1131 | {
1132 | "__class" : "TextNode",
1133 | "autoSize" : false,
1134 | "blendingEnabled" : 0,
1135 | "blendingMode" : "normal",
1136 | "blur" : 12,
1137 | "blurEnabled" : 0,
1138 | "blurType" : "layer",
1139 | "bottom" : null,
1140 | "brightness" : 100,
1141 | "brightnessEnabled" : 0,
1142 | "centerAnchorX" : 0.5,
1143 | "centerAnchorY" : 0.5,
1144 | "constraintsLocked" : false,
1145 | "contrast" : 100,
1146 | "contrastEnabled" : 0,
1147 | "editable" : false,
1148 | "exportOptions" : [
1149 |
1150 | ],
1151 | "grayscale" : 0,
1152 | "grayScaleEnabled" : 0,
1153 | "height" : 26,
1154 | "heightFactor" : null,
1155 | "hueRotate" : 0,
1156 | "hueRotateEnabled" : 0,
1157 | "id" : "F]T\/k>0:",
1158 | "invert" : 0,
1159 | "invertEnabled" : 0,
1160 | "left" : 0,
1161 | "locked" : false,
1162 | "name" : null,
1163 | "opacity" : 1,
1164 | "parentid" : "q&]oPsZ-",
1165 | "right" : 0,
1166 | "rotation" : 0,
1167 | "saturate" : 100,
1168 | "saturateEnabled" : 0,
1169 | "sepia" : 0,
1170 | "sepiaEnabled" : 0,
1171 | "shadows" : [
1172 |
1173 | ],
1174 | "styledText" : {
1175 | "__class" : "StyledTextDraft",
1176 | "blocks" : [
1177 | {
1178 | "data" : {
1179 | "emptyStyle" : [
1180 | "COLOR:rgb(0, 0, 0)",
1181 | "LETTERSPACING:0",
1182 | "LINEHEIGHT:1.2",
1183 | "FONT:__SF-UI-Text-Semibold__",
1184 | "ALIGN:center",
1185 | "SIZE:22"
1186 | ]
1187 | },
1188 | "depth" : 0,
1189 | "entityRanges" : [
1190 |
1191 | ],
1192 | "inlineStyleRanges" : [
1193 | {
1194 | "length" : 1,
1195 | "offset" : 0,
1196 | "style" : "COLOR:rgb(0, 0, 0)"
1197 | },
1198 | {
1199 | "length" : 1,
1200 | "offset" : 0,
1201 | "style" : "LETTERSPACING:0"
1202 | },
1203 | {
1204 | "length" : 1,
1205 | "offset" : 0,
1206 | "style" : "LINEHEIGHT:1.2"
1207 | },
1208 | {
1209 | "length" : 1,
1210 | "offset" : 0,
1211 | "style" : "FONT:__SF-UI-Text-Semibold__"
1212 | },
1213 | {
1214 | "length" : 1,
1215 | "offset" : 0,
1216 | "style" : "ALIGN:center"
1217 | },
1218 | {
1219 | "length" : 1,
1220 | "offset" : 0,
1221 | "style" : "SIZE:22"
1222 | }
1223 | ],
1224 | "key" : "7a7l6",
1225 | "text" : ">",
1226 | "type" : "unstyled"
1227 | }
1228 | ],
1229 | "entityMap" : {
1230 |
1231 | }
1232 | },
1233 | "targetName" : null,
1234 | "top" : null,
1235 | "visible" : true,
1236 | "width" : 44,
1237 | "widthFactor" : null
1238 | }
1239 | ],
1240 | "clip" : false,
1241 | "constraintsLocked" : false,
1242 | "contrast" : 100,
1243 | "contrastEnabled" : 0,
1244 | "deviceType" : null,
1245 | "exportIncludesBackground" : true,
1246 | "exportOptions" : [
1247 |
1248 | ],
1249 | "fillColor" : "hsl(0, 0%, 96%)",
1250 | "fillEnabled" : true,
1251 | "fillGradient" : {
1252 | "__class" : "LinearGradient",
1253 | "alpha" : 1,
1254 | "angle" : 0,
1255 | "end" : "rgba(0,0,0,0)",
1256 | "start" : "black"
1257 | },
1258 | "fillType" : "color",
1259 | "grayscale" : 0,
1260 | "grayScaleEnabled" : 0,
1261 | "height" : 44,
1262 | "heightFactor" : null,
1263 | "hueRotate" : 0,
1264 | "hueRotateEnabled" : 0,
1265 | "id" : "q&]oPsZ-",
1266 | "invert" : 0,
1267 | "invertEnabled" : 0,
1268 | "left" : 446,
1269 | "locked" : false,
1270 | "name" : "select_key",
1271 | "opacity" : 1,
1272 | "parentid" : "p3N%MBBR",
1273 | "radius" : 4,
1274 | "radiusBottomLeft" : 4,
1275 | "radiusBottomRight" : 4,
1276 | "radiusIsRelative" : false,
1277 | "radiusPerCorner" : false,
1278 | "radiusTopLeft" : 4,
1279 | "radiusTopRight" : 4,
1280 | "right" : null,
1281 | "rotation" : 0,
1282 | "saturate" : 100,
1283 | "saturateEnabled" : 0,
1284 | "sepia" : 0,
1285 | "sepiaEnabled" : 0,
1286 | "targetName" : "select_key",
1287 | "top" : 1099,
1288 | "visible" : true,
1289 | "width" : 44,
1290 | "widthFactor" : null
1291 | },
1292 | {
1293 | "__class" : "FrameNode",
1294 | "aspectRatioLocked" : true,
1295 | "blendingEnabled" : 0,
1296 | "blendingMode" : "normal",
1297 | "blur" : 12,
1298 | "blurEnabled" : 0,
1299 | "blurType" : "layer",
1300 | "borderBottom" : 1,
1301 | "borderColor" : "#222",
1302 | "borderEnabled" : 1,
1303 | "borderLeft" : 1,
1304 | "borderPerSide" : false,
1305 | "borderRight" : 1,
1306 | "borderStyle" : "solid",
1307 | "borderTop" : 1,
1308 | "borderWidth" : 1,
1309 | "bottom" : null,
1310 | "boxShadows" : [
1311 | {
1312 | "__class" : "BoxShadow",
1313 | "blur" : 0,
1314 | "color" : "hsl(0, 0%, 0%)",
1315 | "enabled" : true,
1316 | "inset" : false,
1317 | "spread" : 0,
1318 | "x" : 2,
1319 | "y" : 2
1320 | }
1321 | ],
1322 | "brightness" : 100,
1323 | "brightnessEnabled" : 0,
1324 | "centerAnchorX" : 0.69866666666666666,
1325 | "centerAnchorY" : 0.02508178844056707,
1326 | "children" : [
1327 | {
1328 | "__class" : "TextNode",
1329 | "autoSize" : false,
1330 | "blendingEnabled" : 0,
1331 | "blendingMode" : "normal",
1332 | "blur" : 12,
1333 | "blurEnabled" : 0,
1334 | "blurType" : "layer",
1335 | "bottom" : null,
1336 | "brightness" : 100,
1337 | "brightnessEnabled" : 0,
1338 | "centerAnchorX" : 0.5,
1339 | "centerAnchorY" : 0.5,
1340 | "constraintsLocked" : false,
1341 | "contrast" : 100,
1342 | "contrastEnabled" : 0,
1343 | "editable" : false,
1344 | "exportOptions" : [
1345 |
1346 | ],
1347 | "grayscale" : 0,
1348 | "grayScaleEnabled" : 0,
1349 | "height" : 26,
1350 | "heightFactor" : null,
1351 | "hueRotate" : 0,
1352 | "hueRotateEnabled" : 0,
1353 | "id" : "2Z~{Ct?N",
1354 | "invert" : 0,
1355 | "invertEnabled" : 0,
1356 | "left" : 0,
1357 | "locked" : false,
1358 | "name" : null,
1359 | "opacity" : 1,
1360 | "parentid" : "}<*sZ#lp",
1361 | "right" : 0,
1362 | "rotation" : 0,
1363 | "saturate" : 100,
1364 | "saturateEnabled" : 0,
1365 | "sepia" : 0,
1366 | "sepiaEnabled" : 0,
1367 | "shadows" : [
1368 |
1369 | ],
1370 | "styledText" : {
1371 | "__class" : "StyledTextDraft",
1372 | "blocks" : [
1373 | {
1374 | "data" : {
1375 | "emptyStyle" : [
1376 | "COLOR:rgb(0, 0, 0)",
1377 | "LETTERSPACING:0",
1378 | "LINEHEIGHT:1.2",
1379 | "FONT:__SF-UI-Text-Semibold__",
1380 | "ALIGN:center",
1381 | "SIZE:22"
1382 | ]
1383 | },
1384 | "depth" : 0,
1385 | "entityRanges" : [
1386 |
1387 | ],
1388 | "inlineStyleRanges" : [
1389 | {
1390 | "length" : 1,
1391 | "offset" : 0,
1392 | "style" : "COLOR:rgb(0, 0, 0)"
1393 | },
1394 | {
1395 | "length" : 1,
1396 | "offset" : 0,
1397 | "style" : "LETTERSPACING:0"
1398 | },
1399 | {
1400 | "length" : 1,
1401 | "offset" : 0,
1402 | "style" : "LINEHEIGHT:1.2"
1403 | },
1404 | {
1405 | "length" : 1,
1406 | "offset" : 0,
1407 | "style" : "FONT:__SF-UI-Text-Semibold__"
1408 | },
1409 | {
1410 | "length" : 1,
1411 | "offset" : 0,
1412 | "style" : "ALIGN:center"
1413 | },
1414 | {
1415 | "length" : 1,
1416 | "offset" : 0,
1417 | "style" : "SIZE:22"
1418 | }
1419 | ],
1420 | "key" : "7a7l6",
1421 | "text" : ".",
1422 | "type" : "unstyled"
1423 | }
1424 | ],
1425 | "entityMap" : {
1426 |
1427 | }
1428 | },
1429 | "targetName" : null,
1430 | "top" : null,
1431 | "visible" : true,
1432 | "width" : 44,
1433 | "widthFactor" : null
1434 | }
1435 | ],
1436 | "clip" : false,
1437 | "constraintsLocked" : false,
1438 | "contrast" : 100,
1439 | "contrastEnabled" : 0,
1440 | "deviceType" : null,
1441 | "exportIncludesBackground" : true,
1442 | "exportOptions" : [
1443 |
1444 | ],
1445 | "fillColor" : "hsl(0, 0%, 96%)",
1446 | "fillEnabled" : true,
1447 | "fillGradient" : {
1448 | "__class" : "LinearGradient",
1449 | "alpha" : 1,
1450 | "angle" : 0,
1451 | "end" : "rgba(0,0,0,0)",
1452 | "start" : "black"
1453 | },
1454 | "fillType" : "color",
1455 | "grayscale" : 0,
1456 | "grayScaleEnabled" : 0,
1457 | "height" : 44,
1458 | "heightFactor" : null,
1459 | "hueRotate" : 0,
1460 | "hueRotateEnabled" : 0,
1461 | "id" : "}<*sZ#lp",
1462 | "invert" : 0,
1463 | "invertEnabled" : 0,
1464 | "left" : 379,
1465 | "locked" : false,
1466 | "name" : "tap_key",
1467 | "opacity" : 1,
1468 | "parentid" : "p3N%MBBR",
1469 | "radius" : 4,
1470 | "radiusBottomLeft" : 4,
1471 | "radiusBottomRight" : 4,
1472 | "radiusIsRelative" : false,
1473 | "radiusPerCorner" : false,
1474 | "radiusTopLeft" : 4,
1475 | "radiusTopRight" : 4,
1476 | "right" : null,
1477 | "rotation" : 0,
1478 | "saturate" : 100,
1479 | "saturateEnabled" : 0,
1480 | "sepia" : 0,
1481 | "sepiaEnabled" : 0,
1482 | "targetName" : "tap_key",
1483 | "top" : 1249,
1484 | "visible" : true,
1485 | "width" : 44,
1486 | "widthFactor" : null
1487 | }
1488 | ],
1489 | "id" : "p3N%MBBR",
1490 | "parentid" : null
1491 | },
1492 | "version" : 19
1493 | }
--------------------------------------------------------------------------------
/example.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-8-space-gray","contentScale":2,"hideBezel":false,"orientation":0};
7 | }
8 | if (window.Framer) {window.Framer.Defaults.DeviceComponent = {"deviceScale":"fit","selectedHand":"","deviceType":"apple-iphone-8-space-gray","contentScale":2,"hideBezel":false,"orientation":0};
9 | }
10 | window.FramerStudioInfo = {"deviceImagesUrl":"\/_server\/resources\/DeviceImages","documentTitle":"example.framer"};
11 |
12 | Framer.Device = new Framer.DeviceView();
13 | Framer.Device.setupContext();
--------------------------------------------------------------------------------
/example.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(callback) {
60 | CoffeeScript.load("app.coffee", callback)
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(function(){
94 | // CoffeeScript: Framer?.CurrentContext?.emit?("loaded:project")
95 | var context;
96 | if (typeof Framer !== "undefined" && Framer !== null) {
97 | if ((context = Framer.CurrentContext) != null) {
98 | if (typeof context.emit === "function") {
99 | context.emit("loaded:project");
100 | }
101 | }
102 | }
103 | })
104 | }
105 |
106 | init()
107 |
108 | })()
109 |
--------------------------------------------------------------------------------
/example.framer/framer/framer.vekter.js:
--------------------------------------------------------------------------------
1 | (function(scope) {var __layer_0__ = new Layer({"backgroundColor":"#ffffff","width":375,"height":812,"constraintValues":{"height":812,"heightFactor":1,"width":375,"widthFactor":1},"blending":"normal","clip":true,"borderStyle":"solid"});var slides = new Layer({"parent":__layer_0__,"name":"slides","backgroundColor":null,"width":375,"height":917,"constraintValues":{"aspectRatioLocked":true,"height":917,"centerAnchorX":0.5,"width":375,"bottom":-105,"right":0,"top":null,"centerAnchorY":0.56465517241379315},"blending":"normal","image":"images\/design\/jGRzkD2R7YrxRiRi9FkYz6kxJNgft3BT8iJ0bKuSD99x6bCz9iP5RZLkmnoV9TMAcxh4cxTViNynzzqhiIQ.png","clip":false,"borderStyle":"solid"});var lowscrim = new Layer({"parent":__layer_0__,"name":"lowscrim","backgroundColor":"hsla(0, 0%, 0%, 0)","width":375,"height":140,"constraintValues":{"height":140,"centerAnchorX":0.5,"width":375,"bottom":0,"right":0,"top":null,"centerAnchorY":0.91379310344827591},"blending":"normal","clip":false,"borderStyle":"solid","y":672});var ok_hand = new TextLayer({"name":"ok_hand","backgroundColor":null,"width":22,"x":186,"styledText":{"blocks":[{"inlineStyles":[{"startIndex":0,"endIndex":2,"css":{"fontSize":"16px","WebkitTextFillColor":"rgb(0, 0, 0)","whiteSpace":"pre","fontWeight":400,"letterSpacing":"0px","tabSize":4,"fontFamily":"\"HelveticaNeue\", \"Helvetica Neue\", sans-serif","lineHeight":"1.2"}}],"text":"👌"}]},"height":19,"constraintValues":null,"blending":"normal","autoSize":true,"y":-106});var open_key_alt = new Layer({"name":"open_key_alt","shadows":[{"spread":0,"x":2,"type":"box","y":2,"blur":0,"color":"hsl(0, 0%, 0%)"}],"borderWidth":1,"backgroundColor":"hsl(0, 0%, 96%)","width":44,"x":531,"borderColor":"#222","height":44,"constraintValues":null,"blending":"normal","borderRadius":4,"clip":false,"borderStyle":"solid"});var __layer_1__ = new TextLayer({"parent":open_key_alt,"backgroundColor":null,"width":44,"styledText":{"blocks":[{"inlineStyles":[{"startIndex":0,"endIndex":1,"css":{"fontSize":"22px","letterSpacing":"0px","lineHeight":"1.2","tabSize":4,"fontFamily":"\".SFNSText-Semibold\", \"SFProText-Semibold\", \"SFUIText-Semibold\", \".SFUIText-Semibold\", sans-serif","WebkitTextFillColor":"rgb(0, 0, 0)"}}],"text":"<"}],"alignment":"center"},"height":26,"constraintValues":{"height":26,"centerAnchorX":0.5,"width":44,"right":0,"top":null,"centerAnchorY":0.5},"blending":"normal","autoSize":false,"y":9});var open_key = new Layer({"name":"open_key","shadows":[{"spread":0,"x":2,"type":"box","y":2,"blur":0,"color":"hsl(0, 0%, 0%)"}],"borderWidth":1,"backgroundColor":"hsl(0, 0%, 96%)","width":44,"x":464,"borderColor":"#222","height":44,"constraintValues":null,"blending":"normal","borderRadius":4,"clip":false,"borderStyle":"solid"});var __layer_2__ = new TextLayer({"parent":open_key,"backgroundColor":null,"width":44,"styledText":{"blocks":[{"inlineStyles":[{"startIndex":0,"endIndex":1,"css":{"fontSize":"22px","letterSpacing":"0px","lineHeight":"1.2","tabSize":4,"fontFamily":"\".SFNSText-Semibold\", \"SFProText-Semibold\", \"SFUIText-Semibold\", \".SFUIText-Semibold\", sans-serif","WebkitTextFillColor":"rgb(0, 0, 0)"}}],"text":"`"}],"alignment":"center"},"height":26,"constraintValues":{"height":26,"centerAnchorX":0.5,"width":44,"right":0,"top":null,"centerAnchorY":0.5},"blending":"normal","autoSize":false,"y":9});var select_key_alt = new Layer({"name":"select_key_alt","shadows":[{"spread":0,"x":2,"type":"box","y":2,"blur":0,"color":"hsl(0, 0%, 0%)"}],"borderWidth":1,"backgroundColor":"hsl(0, 0%, 96%)","width":44,"x":464,"borderColor":"#222","height":44,"constraintValues":null,"blending":"normal","borderRadius":4,"clip":false,"borderStyle":"solid","y":75});var __layer_3__ = new TextLayer({"parent":select_key_alt,"backgroundColor":null,"width":44,"styledText":{"blocks":[{"inlineStyles":[{"startIndex":0,"endIndex":1,"css":{"fontSize":"22px","letterSpacing":"0px","lineHeight":"1.2","tabSize":4,"fontFamily":"\".SFNSText-Semibold\", \"SFProText-Semibold\", \"SFUIText-Semibold\", \".SFUIText-Semibold\", sans-serif","WebkitTextFillColor":"rgb(0, 0, 0)"}}],"text":"\/"}],"alignment":"center"},"height":26,"constraintValues":{"height":26,"centerAnchorX":0.5,"width":44,"right":0,"top":null,"centerAnchorY":0.5},"blending":"normal","autoSize":false,"y":9});var pause_key = new Layer({"name":"pause_key","shadows":[{"spread":0,"x":2,"type":"box","y":2,"blur":0,"color":"hsl(0, 0%, 0%)"}],"borderWidth":1,"backgroundColor":"hsl(0, 0%, 96%)","width":44,"x":464,"borderColor":"#222","height":44,"constraintValues":null,"blending":"normal","borderRadius":4,"clip":false,"borderStyle":"solid","y":150});var __layer_4__ = new TextLayer({"parent":pause_key,"backgroundColor":null,"width":44,"styledText":{"blocks":[{"inlineStyles":[{"startIndex":0,"endIndex":1,"css":{"fontSize":"22px","letterSpacing":"0px","lineHeight":"1.2","tabSize":4,"fontFamily":"\".SFNSText-Semibold\", \"SFProText-Semibold\", \"SFUIText-Semibold\", \".SFUIText-Semibold\", sans-serif","WebkitTextFillColor":"rgb(0, 0, 0)"}}],"text":"\\"}],"alignment":"center"},"height":26,"constraintValues":{"height":26,"centerAnchorX":0.5,"width":44,"right":0,"top":null,"centerAnchorY":0.5},"blending":"normal","autoSize":false,"y":9});var select_key = new Layer({"name":"select_key","shadows":[{"spread":0,"x":2,"type":"box","y":2,"blur":0,"color":"hsl(0, 0%, 0%)"}],"borderWidth":1,"backgroundColor":"hsl(0, 0%, 96%)","width":44,"x":531,"borderColor":"#222","height":44,"constraintValues":null,"blending":"normal","borderRadius":4,"clip":false,"borderStyle":"solid","y":75});var __layer_5__ = new TextLayer({"parent":select_key,"backgroundColor":null,"width":44,"styledText":{"blocks":[{"inlineStyles":[{"startIndex":0,"endIndex":1,"css":{"fontSize":"22px","letterSpacing":"0px","lineHeight":"1.2","tabSize":4,"fontFamily":"\".SFNSText-Semibold\", \"SFProText-Semibold\", \"SFUIText-Semibold\", \".SFUIText-Semibold\", sans-serif","WebkitTextFillColor":"rgb(0, 0, 0)"}}],"text":">"}],"alignment":"center"},"height":26,"constraintValues":{"height":26,"centerAnchorX":0.5,"width":44,"right":0,"top":null,"centerAnchorY":0.5},"blending":"normal","autoSize":false,"y":9});var tap_key = new Layer({"name":"tap_key","shadows":[{"spread":0,"x":2,"type":"box","y":2,"blur":0,"color":"hsl(0, 0%, 0%)"}],"borderWidth":1,"backgroundColor":"hsl(0, 0%, 96%)","width":44,"x":464,"borderColor":"#222","height":44,"constraintValues":null,"blending":"normal","borderRadius":4,"clip":false,"borderStyle":"solid","y":225});var __layer_6__ = new TextLayer({"parent":tap_key,"backgroundColor":null,"width":44,"styledText":{"blocks":[{"inlineStyles":[{"startIndex":0,"endIndex":1,"css":{"fontSize":"22px","letterSpacing":"0px","lineHeight":"1.2","tabSize":4,"fontFamily":"\".SFNSText-Semibold\", \"SFProText-Semibold\", \"SFUIText-Semibold\", \".SFUIText-Semibold\", sans-serif","WebkitTextFillColor":"rgb(0, 0, 0)"}}],"text":"."}],"alignment":"center"},"height":26,"constraintValues":{"height":26,"centerAnchorX":0.5,"width":44,"right":0,"top":null,"centerAnchorY":0.5},"blending":"normal","autoSize":false,"y":9});lowscrim.__framerInstanceInfo = {"framerClass":"Layer","hash":"#vekter|lowscrim","targetName":"lowscrim","vekterClass":"FrameNode"};select_key_alt.__framerInstanceInfo = {"framerClass":"Layer","hash":"#vekter|select_key_alt","targetName":"select_key_alt","vekterClass":"FrameNode"};__layer_2__.__framerInstanceInfo = {"framerClass":"TextLayer","hash":"#vekter|__layer_2__","vekterClass":"TextNode","text":"`"};pause_key.__framerInstanceInfo = {"framerClass":"Layer","hash":"#vekter|pause_key","targetName":"pause_key","vekterClass":"FrameNode"};open_key_alt.__framerInstanceInfo = {"framerClass":"Layer","hash":"#vekter|open_key_alt","targetName":"open_key_alt","vekterClass":"FrameNode"};open_key.__framerInstanceInfo = {"framerClass":"Layer","hash":"#vekter|open_key","targetName":"open_key","vekterClass":"FrameNode"};__layer_0__.__framerInstanceInfo = {"framerClass":"Layer","hash":"#vekter|__layer_0__","vekterClass":"FrameNode","deviceType":"apple-iphone-x-space-gray","deviceName":"Apple iPhone X"};tap_key.__framerInstanceInfo = {"framerClass":"Layer","hash":"#vekter|tap_key","targetName":"tap_key","vekterClass":"FrameNode"};slides.__framerInstanceInfo = {"originalFilename":"","framerClass":"Layer","hash":"#vekter|slides","targetName":"slides","vekterClass":"ImageNode"};ok_hand.__framerInstanceInfo = {"framerClass":"TextLayer","hash":"#vekter|ok_hand","targetName":"ok_hand","vekterClass":"TextNode","text":"👌"};__layer_4__.__framerInstanceInfo = {"framerClass":"TextLayer","hash":"#vekter|__layer_4__","vekterClass":"TextNode","text":"\\"};__layer_1__.__framerInstanceInfo = {"framerClass":"TextLayer","hash":"#vekter|__layer_1__","vekterClass":"TextNode","text":"<"};select_key.__framerInstanceInfo = {"framerClass":"Layer","hash":"#vekter|select_key","targetName":"select_key","vekterClass":"FrameNode"};__layer_6__.__framerInstanceInfo = {"framerClass":"TextLayer","hash":"#vekter|__layer_6__","vekterClass":"TextNode","text":"."};__layer_3__.__framerInstanceInfo = {"framerClass":"TextLayer","hash":"#vekter|__layer_3__","vekterClass":"TextNode","text":"\/"};__layer_5__.__framerInstanceInfo = {"framerClass":"TextLayer","hash":"#vekter|__layer_5__","vekterClass":"TextNode","text":">"};if (scope["__vekterVariables"]) { scope["__vekterVariables"].map(function(variable) { delete scope[variable] } ) };Object.assign(scope, {slides, lowscrim, ok_hand, open_key_alt, open_key, select_key_alt, pause_key, select_key, tap_key});scope["__vekterVariables"] = ["slides", "lowscrim", "ok_hand", "open_key_alt", "open_key", "select_key_alt", "pause_key", "select_key", "tap_key"];if (typeof Framer.CurrentContext.layout === 'function') {Framer.CurrentContext.layout()};})(window);
--------------------------------------------------------------------------------
/example.framer/framer/images/cursor-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/framer/images/cursor-active.png
--------------------------------------------------------------------------------
/example.framer/framer/images/cursor-active@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/framer/images/cursor-active@2x.png
--------------------------------------------------------------------------------
/example.framer/framer/images/cursor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/framer/images/cursor.png
--------------------------------------------------------------------------------
/example.framer/framer/images/cursor@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/framer/images/cursor@2x.png
--------------------------------------------------------------------------------
/example.framer/framer/images/icon-120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/framer/images/icon-120.png
--------------------------------------------------------------------------------
/example.framer/framer/images/icon-152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/framer/images/icon-152.png
--------------------------------------------------------------------------------
/example.framer/framer/images/icon-180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/framer/images/icon-180.png
--------------------------------------------------------------------------------
/example.framer/framer/images/icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/framer/images/icon-192.png
--------------------------------------------------------------------------------
/example.framer/framer/images/icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/framer/images/icon-76.png
--------------------------------------------------------------------------------
/example.framer/framer/manifest (Stephen Ruiz's conflicted copy 2017-11-17).txt:
--------------------------------------------------------------------------------
1 | app.coffee
2 | framer/coffee-script.js
3 | framer/framer.generated.js
4 | framer/framer.init.js
5 | framer/framer.js
6 | framer/framer.modules.js
7 | framer/images/cursor-active.png
8 | framer/images/cursor-active@2x.png
9 | framer/images/cursor.png
10 | framer/images/cursor@2x.png
11 | framer/images/icon-120.png
12 | framer/images/icon-152.png
13 | framer/images/icon-180.png
14 | framer/images/icon-192.png
15 | framer/images/icon-76.png
16 | framer/manifest.txt
17 | framer/preview.png
18 | framer/style.css
19 | framer/version
20 | index.html
21 | modules/fraplin.coffee
--------------------------------------------------------------------------------
/example.framer/framer/preview (Stephen Ruiz's conflicted copy 2017-11-17 (1)).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/framer/preview (Stephen Ruiz's conflicted copy 2017-11-17 (1)).png
--------------------------------------------------------------------------------
/example.framer/framer/preview (Stephen Ruiz's conflicted copy 2017-11-17).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/framer/preview (Stephen Ruiz's conflicted copy 2017-11-17).png
--------------------------------------------------------------------------------
/example.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 | }
--------------------------------------------------------------------------------
/example.framer/framer/version:
--------------------------------------------------------------------------------
1 | 11
--------------------------------------------------------------------------------
/example.framer/images/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/images/.gitkeep
--------------------------------------------------------------------------------
/example.framer/images/design/jGRzkD2R7YrxRiRi9FkYz6kxJNgft3BT8iJ0bKuSD99x6bCz9iP5RZLkmnoV9TMAcxh4cxTViNynzzqhiIQ.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/images/design/jGRzkD2R7YrxRiRi9FkYz6kxJNgft3BT8iJ0bKuSD99x6bCz9iP5RZLkmnoV9TMAcxh4cxTViNynzzqhiIQ.png
--------------------------------------------------------------------------------
/example.framer/images/design/jGRzkD2R7YrxRiRi9FkYz6kxJNgft3BT8iJ0bKuSD99x6bCz9iP5RZLkmnoV9TMAcxh4cxTViNynzzqhiIQ.png-1024:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/images/design/jGRzkD2R7YrxRiRi9FkYz6kxJNgft3BT8iJ0bKuSD99x6bCz9iP5RZLkmnoV9TMAcxh4cxTViNynzzqhiIQ.png-1024
--------------------------------------------------------------------------------
/example.framer/images/design/jGRzkD2R7YrxRiRi9FkYz6kxJNgft3BT8iJ0bKuSD99x6bCz9iP5RZLkmnoV9TMAcxh4cxTViNynzzqhiIQ.png-512:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/images/design/jGRzkD2R7YrxRiRi9FkYz6kxJNgft3BT8iJ0bKuSD99x6bCz9iP5RZLkmnoV9TMAcxh4cxTViNynzzqhiIQ.png-512
--------------------------------------------------------------------------------
/example.framer/images/design/jGRzkD2R7YrxRiRi9FkYz6kxJNgft3BT8iJ0bKuSD99x6bCz9iP5RZLkmnoV9TMAcxh4cxTViNynzzqhiIQ.png-preview:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/example.framer/images/design/jGRzkD2R7YrxRiRi9FkYz6kxJNgft3BT8iJ0bKuSD99x6bCz9iP5RZLkmnoV9TMAcxh4cxTViNynzzqhiIQ.png-preview
--------------------------------------------------------------------------------
/example.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 |
41 |
--------------------------------------------------------------------------------
/gotcha.coffee:
--------------------------------------------------------------------------------
1 | # .88888. dP dP
2 | # d8' `88 88 88
3 | # 88 .d8888b. d8888P .d8888b. 88d888b. .d8888b.
4 | # 88 YP88 88' `88 88 88' `"" 88' `88 88' `88
5 | # Y8. .88 88. .88 88 88. ... 88 88 88. .88
6 | # `88888' `88888P' dP `88888P' dP dP `88888P8
7 | #
8 | #
9 | # by @steveruizok
10 | #
11 | # A Framer module for handoff. It works kind of like that other tool.
12 |
13 | deviceType = window.localStorage.deviceType
14 |
15 | if deviceType?
16 | device = Framer.DeviceComponent.Devices[deviceType]
17 | Framer.Device._context.devicePixelRatio = device.devicePixelRatio
18 |
19 | Framer.Device.deviceType = deviceType
20 | window.localStorage.device = undefined
21 |
22 | Framer.Extras.Hints.disable()
23 |
24 | svgContext = undefined
25 | startOpen = false
26 | accordionsOpen = false
27 |
28 | # debugging
29 |
30 | document.getElementsByClassName('DevicePhone')[0]?.classList.add('IgnorePointerEvents')
31 |
32 |
33 | ### -------------------------------------------
34 |
35 | .d88888b dP dP .88888. a88888b. dP
36 | 88. "' 88 88 d8' `88 d8' `88 88
37 | `Y88888b. 88 .8P 88 88 .d8888b. 88d8b.d8b. 88d888b. .d8888b. 88d888b. .d8888b. 88d888b. d8888P .d8888b.
38 | `8b 88 d8' 88 YP88 88 88' `88 88'`88'`88 88' `88 88' `88 88' `88 88ooood8 88' `88 88 Y8ooooo.
39 | d8' .8P 88 .d8P Y8. .88 Y8. .88 88. .88 88 88 88 88. .88 88. .88 88 88 88. ... 88 88 88 88
40 | Y88888P 888888' `88888' Y88888P' `88888P' dP dP dP 88Y888P' `88888P' dP dP `88888P' dP dP dP `88888P'
41 | 88
42 | dP
43 | ###
44 |
45 |
46 | # ---------------------
47 | # SVG Context
48 |
49 | class SVGContext
50 | constructor: (options = {}) ->
51 | @__constructor = true
52 |
53 | @shapes = []
54 |
55 | svgContext = @
56 |
57 | # namespace
58 | svgNS = "http://www.w3.org/2000/svg"
59 |
60 | # set attributes
61 | setAttributes = (element, attributes = {}) ->
62 | for key, value of attributes
63 | element.setAttribute(key, value)
64 |
65 |
66 | # Create SVG element
67 |
68 | @svg = document.createElementNS(svgNS, 'svg')
69 | document.body.appendChild(@svg)
70 | @svg.style['z-index'] = '999'
71 |
72 | @frameElement = Framer.Device.screenBackground._element
73 |
74 | @setContext()
75 |
76 | # defs
77 |
78 | @svgDefs = document.createElementNS(svgNS, 'defs')
79 | @svg.appendChild @svgDefs
80 |
81 | delete @__constructor
82 |
83 | setAttributes: (element, attributes = {}) ->
84 | for key, value of attributes
85 | element.setAttribute(key, value)
86 |
87 | setContext: =>
88 |
89 | @lFrame = @frameElement.getBoundingClientRect()
90 |
91 | _.assign @,
92 | width: @lFrame.width.toFixed()
93 | height: @lFrame.height.toFixed()
94 | x: @lFrame.left.toFixed()
95 | y: @lFrame.top.toFixed()
96 |
97 | @screenElement = document.getElementsByClassName('framerContext')[0]
98 | sFrame = @screenElement.getBoundingClientRect()
99 |
100 | @setAttributes @svg,
101 | x: 0
102 | y: 0
103 | width: sFrame.width
104 | height: sFrame.height
105 | viewBox: "0 0 #{sFrame.width} #{sFrame.height}"
106 |
107 | _.assign @svg.style,
108 | position: "absolute"
109 | left: 0
110 | top: 0
111 | width: '100%'
112 | height: '100%'
113 | 'pointer-events': 'none'
114 |
115 | addShape: (shape) ->
116 | @shapes.push(shape)
117 | @showShape(shape)
118 |
119 | removeShape: (shape) ->
120 | @hideShape(shape)
121 | _.pull(@shapes, shape)
122 |
123 | hideShape: (shape) ->
124 | @svg.removeChild(shape.element)
125 |
126 | showShape: (shape) ->
127 | @svg.appendChild(shape.element)
128 |
129 | addDef: (def) ->
130 | @svgDefs.appendChild(def)
131 |
132 | removeAll: =>
133 | for shape in @shapes
134 | @svg.removeChild(shape.element)
135 | @shapes = []
136 |
137 | # ---------------------
138 | # SVG Shape
139 |
140 | class SVGShape
141 | constructor: (options = {type: 'circle'}) ->
142 | @__constructor = true
143 |
144 | @parent = svgContext
145 |
146 | @element = document.createElementNS(
147 | "http://www.w3.org/2000/svg",
148 | options.type
149 | )
150 |
151 | @setCustomProperty('text', 'textContent', 'textContent', options.text)
152 |
153 | # assign attributes set by options
154 | for key, value of options
155 | @setAttribute(key, value)
156 |
157 | @parent.addShape(@)
158 |
159 | @show()
160 |
161 | setAttribute: (key, value) =>
162 | return if key is 'text'
163 | if not @[key]?
164 | Object.defineProperty @,
165 | key,
166 | get: =>
167 | return @element.getAttribute(key)
168 | set: (value) =>
169 | @element.setAttribute(key, value)
170 |
171 | @[key] = value
172 |
173 | setCustomProperty: (variableName, returnValue, setValue, startValue) ->
174 | Object.defineProperty @,
175 | variableName,
176 | get: ->
177 | return returnValue
178 | set: (value) ->
179 | @element[setValue] = value
180 |
181 | @[variableName] = startValue
182 |
183 | hide: ->
184 | @parent.hideShape(@)
185 |
186 | show: ->
187 | @parent.showShape(@)
188 |
189 | remove: ->
190 | @parent.removeShape(@)
191 |
192 | # ---------------------
193 | # Dashed Line
194 |
195 | class DashedLine extends SVGShape
196 | constructor: (pointA, pointB, color = '#000', offset = 0, options = {}) ->
197 |
198 | _.assign options,
199 | type: 'path'
200 | d: "M #{pointA.x} #{pointA.y} L #{pointB.x} #{pointB.y}"
201 | stroke: color
202 | 'stroke-width': '1px'
203 | 'stroke-dasharray': "5, 5"
204 | 'stroke-dashoffset': offset
205 |
206 | super options
207 |
208 |
209 | # ----------------------------------------
210 | # Panel Components
211 |
212 | Utils.insertCSS """
213 |
214 | .logo {
215 | opacity: .4;
216 | }
217 |
218 | .logo:hover {
219 | opacity: 1;
220 | }
221 |
222 | #linkedin_logo {
223 | position: absolute;
224 | bottom: 8px;
225 | right: 68px;
226 | }
227 |
228 |
229 | #twitter_logo {
230 | position: absolute;
231 | bottom: 4px;
232 | right: 4px;
233 | }
234 |
235 | #github_logo {
236 | position: absolute;
237 | bottom: 8px;
238 | right: 36px;
239 | }
240 |
241 | .framerLayer {
242 | pointer-events: all !important;
243 | }
244 |
245 | .IgnorePointerEvents {
246 | pointer-events: none !important;
247 | }
248 |
249 | .dropdown {
250 | opacity: 0;
251 | }
252 |
253 | #pContainer {
254 | position: fixed;
255 | right: 0;
256 | width: 224px;
257 | height: 100%;
258 | font-family: 'Helvetica Neue';
259 | font-size: 11px;
260 | background-color: rgba(20, 20, 20, 1.000);
261 | border-left: 1px solid rgba(45, 45, 45, 1.000);
262 | pointer-events: all;
263 | white-space: nowrap;
264 | cursor: default;
265 | overflow: scroll;
266 | padding-top: 8px;
267 | }
268 |
269 | .pDiv {
270 | display: block;
271 | width: 100%;
272 | }
273 |
274 | .hidden {
275 | display: none;
276 | }
277 |
278 | .pRow {
279 | width: 100%;
280 | height: 32px;
281 | }
282 |
283 | .pSpan {
284 | position: absolute;
285 | color: #888888;
286 | font-weight: 400;
287 | letter-spacing: .5px;
288 | padding-left: 8px;
289 | margin-top: 2px;
290 | }
291 |
292 | .pLabel {
293 | position: absolute;
294 | text-align: right;
295 | font-size: 10px;
296 | width: none;
297 | margin-top: 2px;
298 | margin-right: 8px;
299 | z-index: 10;
300 | pointer-events: none;
301 | }
302 |
303 | .pRange {
304 | position: absolute;
305 | border-radius: 4px;
306 | margin-top: 15px;
307 | margin-right: 4px;
308 | border: 1px solid #000;
309 | -webkit-appearance: none; /* Override default CSS styles */
310 | appearance: none;
311 | width: 100%;
312 | height: 4px;
313 | background: #323232;
314 | outline: none;
315 | opacity: 1;
316 | }
317 |
318 |
319 | .pRange::-webkit-slider-thumb {
320 | border-radius: 8px;
321 | -webkit-appearance: none;
322 | appearance: none;
323 | width: 16px;
324 | height: 16px;
325 | background: #888888;
326 | border: 1px solid #000;
327 | cursor: pointer;
328 | }
329 |
330 | .pRange::-moz-range-thumb {
331 | border-radius: 8px;
332 | width: 16px;
333 | height: 16px;
334 | background: #888888;
335 | border: 1px solid #000;
336 | cursor: pointer;
337 | }
338 |
339 | .pInput {
340 | background-color: #292929;
341 | border: 1px solid #000;
342 | color: #555555;
343 | padding: 4px;
344 | position: absolute;
345 | border-radius: 4px;
346 | outline: none;
347 | margin-top: 4px;
348 | }
349 |
350 | .pInput:hover {
351 | border: 1px solid #48cfff;
352 | color: #48cfff;
353 | }
354 |
355 | .right {
356 | right: 8px;
357 | width: 48px;
358 | }
359 |
360 | .left {
361 | right: 72px;
362 | width: 48px;
363 | }
364 |
365 | .alignLeft {
366 | text-align: left;
367 | }
368 |
369 | .full {
370 | right: 8px;
371 | width: 112px;
372 | }
373 |
374 | .pImage {
375 | display: block;
376 | margin-left: 8px;
377 | height: auto;
378 | width: 196px;
379 | overflow: hidden;
380 | background-color: #292929;
381 | border: 1px solid #000;
382 | border-radius: 4px;
383 | outline: 4px solid #292929;
384 | outline-offset: -4px;
385 | padding: 4px;
386 | }
387 |
388 | .pImage:hover {
389 | border: 1px solid #48cfff;
390 | color: #48cfff;
391 | outline: 2px solid #292929;
392 | }
393 |
394 | .pColor {
395 | outline: 4px solid #292929;
396 | outline-offset: -4px;
397 | }
398 |
399 | .pColor:hover {
400 | outline: 2px solid #292929;
401 | color: #48cfff;
402 | }
403 |
404 | .pSelect {
405 | position: absolute;
406 | right: 8px;
407 | width: 122px;
408 | color: #555555;
409 | background-color: #292929;
410 | -webkit-appearance: none;
411 | border: 1px solid #000;
412 | padding: 4px;
413 | border-radius: 4px;
414 | outline: none;
415 | }
416 |
417 | .pDivider {
418 | height: 1px;
419 | background-color: #000;
420 | margin: 16px 8px 16px 8px;
421 | }
422 |
423 | .pAccordian {
424 | border-top: 1px solid #141414;
425 | border-bottom: 1px solid #141414;
426 | height: auto;
427 | min-height: 32px;
428 | background-color: #1D1D1D;
429 | margin-top: 16px;
430 | }
431 |
432 | .pAccordianBody {
433 | display: none;
434 | height: auto;
435 | margin-top: 32px;
436 | padding-top: 4px;
437 | background-color: #141414;
438 | }
439 |
440 | .active {
441 | display: block;
442 | height: auto;
443 | }
444 |
445 | .hasValue {
446 | color: #FFF;
447 | }
448 |
449 | .socialLinks {
450 | background-color: #141414;
451 | position: fixed;
452 | bottom: 0px;
453 | right: 0px;
454 | padding-top: 4px;
455 | z-index: 100;
456 | }
457 |
458 | .strong {
459 | font-weight: 600;
460 | }
461 |
462 | """
463 |
464 | # ---------------------
465 | # Div
466 |
467 | class pDiv
468 | constructor: (options = {}) ->
469 |
470 | _.defaults options,
471 | parent: undefined
472 |
473 | @properties = []
474 |
475 | @element = document.createElement('div')
476 | @element.classList.add("pDiv")
477 | parent = options.parent?.element ? panel
478 | parent.appendChild(@element)
479 |
480 |
481 | Object.defineProperty @,
482 | "visible",
483 | get: -> return @_visible
484 | set: (bool) ->
485 | return if bool is @_visible
486 |
487 | @_visible = bool
488 |
489 | if bool
490 | @element.classList.remove('hidden')
491 | return
492 |
493 | @element.classList.add('hidden')
494 |
495 |
496 | # ---------------------
497 | # Row
498 |
499 | class pRow extends pDiv
500 | constructor: (options = {}) ->
501 |
502 | _.defaults options,
503 | text: 'Label'
504 | bold: false
505 |
506 | _.assign @,
507 | children: []
508 |
509 | super options
510 |
511 | @element.classList.remove("pDiv")
512 | @element.classList.add("pRow")
513 |
514 | @label = new pSpan
515 | parent: @
516 | text: options.text
517 | bold: options.bold
518 |
519 | Object.defineProperty @, 'color',
520 | get: -> return @label.style.color
521 | set: (value) ->
522 | @label.element.style.color = value
523 |
524 | # ---------------------
525 | # Divider
526 |
527 | class pDivider
528 | constructor: (options = {}) ->
529 |
530 | _.defaults options,
531 | parent: undefined
532 |
533 | @element = document.createElement('div')
534 | @element.classList.add("pDivider")
535 |
536 | parent = options.parent?.element ? panel
537 | parent.appendChild(@element)
538 |
539 | # ---------------------
540 | # Span
541 |
542 | class pSpan
543 | constructor: (options = {}) ->
544 |
545 | _.defaults options,
546 | parent: undefined
547 | text: 'hello world'
548 | bold: false
549 |
550 | @element = document.createElement('span')
551 | @element.classList.add("pSpan")
552 | @element.textContent = options.text
553 |
554 | if options.bold
555 | @element.classList.add("strong")
556 |
557 | parent = options.parent?.element ? panel
558 | parent.appendChild(@element)
559 |
560 | Object.defineProperty @,
561 | 'text',
562 | get: -> return @element.textContent
563 | set: (value) -> @element.textContent = value
564 |
565 |
566 | # ---------------------
567 | # Range
568 |
569 | class pRange
570 | constructor: (options = {}) ->
571 |
572 | _.defaults options,
573 | parent: null
574 | className: 'full'
575 | value: ''
576 | min: '0'
577 | max: '100'
578 | value: '100'
579 | action: (value) => null
580 |
581 | @element = document.createElement('input')
582 | _.assign @element,
583 | type: 'range'
584 | min: options.min
585 | max: options.max
586 | value: options.value
587 | action: options.action
588 |
589 | @element.classList.add("pRange")
590 | @element.classList.add(options.className)
591 |
592 | @element.oninput = => @action(@value)
593 |
594 | parent = options.parent?.element ? panel
595 | parent.appendChild(@element)
596 |
597 | propLayers.push(@)
598 |
599 | Object.defineProperty @,
600 | 'value',
601 | get: -> return @element.value
602 |
603 | _.assign @,
604 | action: options.action
605 |
606 | # ---------------------
607 | # Label
608 |
609 | class pLabel
610 | constructor: (options = {}) ->
611 |
612 | _.defaults options,
613 | parent: undefined
614 | className: null
615 | text: 'x'
616 | for: undefined
617 |
618 | @element = document.createElement('label')
619 | @element.classList.add("pLabel")
620 | @element.classList.add(options.className)
621 |
622 | _.assign @element,
623 | textContent: options.text
624 | for: options.for
625 |
626 | parent = options.parent?.element ? panel
627 | parent.appendChild(@element)
628 |
629 | # ---------------------
630 | # Input
631 |
632 | class pInput
633 | constructor: (options = {}) ->
634 |
635 | _.defaults options,
636 | parent: null
637 | className: 'left'
638 | value: ''
639 | unit: 'x'
640 | default: ''
641 | isDefault: true
642 | section: undefined
643 |
644 | @element = document.createElement('input')
645 | @element.classList.add("pInput")
646 | @element.classList.add(options.className)
647 |
648 | parent = options.parent?.element ? panel
649 | parent.appendChild(@element)
650 |
651 | options.section?.properties.push(@)
652 |
653 | @unit = new pLabel
654 | parent: options.parent
655 | className: options.className
656 | text: options.unit
657 | for: @element
658 |
659 | propLayers.push(@)
660 |
661 | Object.defineProperty @,
662 | 'default',
663 | get: -> return @_default
664 | set: (value) ->
665 | @_default = value
666 |
667 | @default = options.default ? ''
668 |
669 | Object.defineProperty @,
670 | 'value',
671 | get: -> return @_value
672 | set: (value) ->
673 | @_value = value
674 | if not value? or value is "" or value is "undefined"
675 | value = String(@default)
676 |
677 | @element.value = value ? ""
678 |
679 | if value? and not @isDefault and value isnt ""
680 | # @section?.color = '#FFF'
681 | @section?.visible = true
682 |
683 | Object.defineProperty @,
684 | 'isDefault',
685 | get: -> return @_isDefault
686 | set: (bool) ->
687 | @_isDefault = bool
688 |
689 | if bool
690 | @element.classList.remove('hasValue')
691 | return
692 |
693 | @.element.classList.add('hasValue')
694 |
695 |
696 | @element.addEventListener 'click', =>
697 | if not secretBox
698 | return
699 |
700 | secretBox.value = @value
701 | secretBox.select()
702 | document.execCommand('copy')
703 | secretBox.blur()
704 |
705 | _.assign @,
706 | value: options.value
707 | default: options.default
708 | section: options.section
709 | isDefault: options.isDefault
710 |
711 | # ---------------------
712 | # Image
713 |
714 | class pImage
715 | constructor: (options = {}) ->
716 |
717 | _.defaults options,
718 | parent: null
719 | value: ''
720 | unit: ''
721 | section: undefined
722 |
723 | @element = document.createElement('img')
724 | @element.classList.add("pImage")
725 |
726 | parent = options.parent?.element ? panel
727 | parent.appendChild(@element)
728 |
729 | options.section?.properties.push(@)
730 |
731 | propLayers.push(@)
732 |
733 | Object.defineProperty @,
734 | 'value',
735 | get: -> return @_value
736 | set: (value = '') ->
737 | @_value = value
738 | @element.src = value
739 | @section?.visible = value isnt ''
740 |
741 |
742 | @element.addEventListener 'click', =>
743 | if not secretBox
744 | return
745 |
746 | secretBox.value = @value
747 | secretBox.select()
748 | document.execCommand('copy')
749 | secretBox.blur()
750 |
751 | _.assign @,
752 | value: options.value
753 | section: options.section
754 |
755 | # ---------------------
756 | # Color Box
757 |
758 | class pColor
759 | constructor: (options = {}) ->
760 |
761 | _.defaults options,
762 | parent: null
763 | value: '#292929'
764 |
765 | @element = document.createElement('input')
766 | @element.classList.add("pInput")
767 | @element.classList.add('pColor')
768 | @element.classList.add(options.className)
769 |
770 | parent = options.parent?.element ? panel
771 | parent.appendChild(@element)
772 |
773 | options.section?.properties.push(@)
774 |
775 | propLayers.push(@)
776 |
777 | Object.defineProperty @,
778 | 'value',
779 | get: -> return @_value
780 | set: (value) ->
781 |
782 | if value?.color is 'transparent'
783 | value = null
784 |
785 | if value? and value isnt ''
786 | @section?.visible = true
787 |
788 | @_value = value ? ''
789 | @element.style['background-color'] = value ? 'none'
790 |
791 | @element.addEventListener 'click', =>
792 | if not secretBox
793 | return
794 |
795 | secretBox.value = @value
796 | secretBox.select()
797 | document.execCommand('copy')
798 | secretBox.blur()
799 |
800 | _.assign @,
801 | value: options.value
802 | section: options.section
803 |
804 | # ---------------------
805 | # Select
806 |
807 | class pSelect
808 | constructor: (options = {}) ->
809 |
810 | _.defaults options,
811 | parent: undefined
812 | selected: 0
813 | options: ['red', 'white', 'blue']
814 | callback: (value) -> null
815 |
816 | @element = document.createElement('select')
817 | @element.classList.add("pSelect")
818 | @element.classList.add('hasValue')
819 |
820 | @unit = new pLabel
821 | parent: options.parent
822 | className: 'right'
823 | text: '▾'
824 | for: @element
825 |
826 | parent = options.parent?.element ? panel
827 | parent.appendChild(@element)
828 |
829 | Object.defineProperty @,
830 | 'options',
831 | get: -> return @_options
832 | set: (array) ->
833 | @_options = array
834 | @makeOptions()
835 |
836 | Object.defineProperty @,
837 | 'selected',
838 | get: -> return @_selected
839 | set: (num) ->
840 | @_selected = num
841 |
842 | _.assign @,
843 | _options: []
844 | _optionElements: []
845 | options: options.options
846 | callback: options.callback
847 | selected: options.selected
848 |
849 | @element.selectedIndex = options.selected
850 |
851 | @element.onchange = =>
852 | @selected = @element.selectedIndex
853 | @callback(@element.selectedIndex)
854 |
855 |
856 | makeOptions: =>
857 | for option, i in @_optionElements
858 | @element.removeChild(option)
859 |
860 | @_optionElements = []
861 |
862 | for option, i in @options
863 | o = document.createElement('option')
864 | o.value = option
865 | o.label = option
866 | o.innerHTML = option
867 | @element.appendChild(o)
868 |
869 | @_optionElements.push(o)
870 |
871 | if i is @selected
872 | @value = @element.options[@element.selectedIndex].label
873 |
874 | # ---------------------
875 | # Accordian
876 |
877 | class pAccordian extends pRow
878 | constructor: (options = {}) ->
879 |
880 | super options
881 | @element.classList.add('pAccordian')
882 | @element.addEventListener "click", @toggle
883 |
884 | _.assign @,
885 | toggled: false
886 |
887 | @unit = new pLabel
888 | parent: @
889 | className: 'right'
890 | text: '▿'
891 | for: @element
892 |
893 | @body = new pRow
894 | parent: @
895 | text: ''
896 | @body.element.removeChild(@body.label.element)
897 |
898 | @element.appendChild(@body.element)
899 | @body.element.classList.add('pAccordianBody')
900 |
901 | @body.element.addEventListener 'click', (event) ->
902 | event.stopPropagation()
903 |
904 | if accordionsOpen then @toggle() # start open
905 |
906 | toggle: =>
907 | @toggled = !@toggled
908 |
909 | if @toggled
910 | @body.element.classList.add('active')
911 | @unit.element.textContent = '▾'
912 | return
913 |
914 | if @body.element.classList.contains('active')
915 | @body.element.classList.remove('active')
916 | @unit.element.textContent = '▿'
917 |
918 |
919 | ### -------------------------------------------
920 |
921 | .d88888b 888888ba dP
922 | 88. "' 88 `8b 88
923 | `Y88888b. 88d888b. .d8888b. .d8888b. a88aaaa8P' .d8888b. 88d888b. .d8888b. 88
924 | `8b 88' `88 88ooood8 88' `"" 88 88' `88 88' `88 88ooood8 88
925 | d8' .8P 88. .88 88. ... 88. ... 88 88. .88 88 88 88. ... 88
926 | Y88888P 88Y888P' `88888P' `88888P' dP `88888P8 dP dP `88888P' dP
927 | 88
928 | dP
929 |
930 | ###
931 |
932 | class SpecPanel
933 | constructor: ->
934 |
935 | @element = panel
936 | @propLayers = []
937 | @_props = {}
938 | @frame = @element.getBoundingClientRect()
939 | @defaults = Framer.Device.screen._propertyList()
940 |
941 | Object.defineProperty @,
942 | 'props',
943 | get: ->
944 | return @_props
945 | set: (obj) ->
946 | for key, value of obj
947 | if _.has(@props, key)
948 | @props[key] = value
949 |
950 | @element.style.opacity = if startOpen then '1' else '0'
951 | @canvas = document.createElement('canvas')
952 |
953 |
954 | # ------------------
955 | # device
956 |
957 | # Set Device Options
958 |
959 | deviceOptions = ['fullscreen']
960 | currentSelected = undefined
961 |
962 | for key, value of Framer.DeviceComponent.Devices
963 | if _.endsWith(key, 'hand')
964 | continue
965 |
966 | if not value.minStudioVersion?
967 | continue
968 |
969 | if Utils.framerStudioVersion() > value.maxStudioVersion
970 | continue
971 |
972 | if Utils.framerStudioVersion() < value.minStudioVersion
973 | continue
974 |
975 | deviceOptions.push (key)
976 |
977 | if key is Framer.Device.deviceType
978 | currentSelected = deviceOptions.length - 1
979 |
980 | # ------------------------------------
981 | # framer settings
982 |
983 | # ------------------
984 | # device
985 |
986 | row = new pRow
987 | text: 'Device'
988 |
989 | @deviceBox = new pSelect
990 | parent: row
991 | unit: ''
992 | options: deviceOptions
993 | selected: currentSelected
994 | callback: (index) =>
995 | deviceType = deviceOptions[index]
996 | device = Framer.DeviceComponent.Devices[deviceType]
997 |
998 | _.assign window.localStorage,
999 | deviceType: deviceType
1000 | device: device
1001 | bg: Screen.backgroundColor
1002 |
1003 | window.location.reload()
1004 |
1005 | # ------------------
1006 | # animation speed
1007 |
1008 | @speedRow = new pRow
1009 | text: 'Speed 100%'
1010 |
1011 | minp = parseInt(0, 10)
1012 | maxp = parseInt(100, 10)
1013 |
1014 | minv = Math.log(0.00001)
1015 | maxv = Math.log(0.01666666667)
1016 |
1017 | vScale = (maxv-minv) / (maxp-minp)
1018 |
1019 | @speedBox = new pRange
1020 | parent: @speedRow
1021 | className: 'full'
1022 | unit: ''
1023 | action: (value) =>
1024 |
1025 | delta = Math.exp(minv + vScale*(value-minp))
1026 | rate = (delta/(1/60))*100
1027 | spaces = if rate < 1 then 2 else if rate < 10 then 1 else 0
1028 |
1029 | @speedRow.label.text = 'Speed ' + rate.toFixed(spaces) + '%'
1030 |
1031 | Framer.Loop.delta = delta
1032 |
1033 | # ------------------------------------
1034 | # layer details
1035 |
1036 | new pDivider
1037 |
1038 | row = new pRow
1039 | text: 'Name'
1040 |
1041 | @nameBox = new pInput
1042 | parent: row
1043 | className: 'full'
1044 | unit: ''
1045 |
1046 | row = new pRow
1047 | text: 'Component'
1048 |
1049 | @componentNameBox = new pInput
1050 | parent: row
1051 | className: 'full'
1052 | unit: ''
1053 |
1054 | @componentNamesRow = new pRow
1055 | text: 'Part of'
1056 |
1057 | @componentNamesBox = new pInput
1058 | parent: @componentNamesRow
1059 | className: 'full'
1060 | unit: ''
1061 |
1062 | # ------------------------------------
1063 | # position details
1064 |
1065 | new pDivider
1066 |
1067 | # ------------------
1068 | # position
1069 |
1070 | row = new pRow
1071 | text: 'Position'
1072 |
1073 | @xBox = new pInput
1074 | parent: row,
1075 | className: 'left'
1076 | unit: 'x'
1077 |
1078 | @yBox = new pInput
1079 | parent: row,
1080 | className: 'right'
1081 | unit: 'y'
1082 |
1083 | # ------------------
1084 | # size
1085 |
1086 | row = new pRow
1087 | text: 'Size'
1088 |
1089 | @widthBox = new pInput
1090 | parent: row,
1091 | className: 'left'
1092 | unit: 'w'
1093 |
1094 | @heightBox = new pInput
1095 | parent: row,
1096 | className: 'right'
1097 | unit: 'h'
1098 |
1099 | # ------------------
1100 | # background color
1101 |
1102 | row = new pRow
1103 | text: 'Background'
1104 |
1105 | @backgroundColorBox = new pColor
1106 | parent: row,
1107 | className: 'left'
1108 |
1109 | # ----------------------------------------
1110 | # gradient
1111 |
1112 | @gradientPropertiesDiv = new pDiv
1113 |
1114 | row = new pRow
1115 | parent: @gradientPropertiesDiv
1116 | text: 'Gradient'
1117 |
1118 | @gradientStartBox = new pColor
1119 | parent: row
1120 | className: 'left'
1121 | section: @gradientPropertiesDiv
1122 | default: null
1123 |
1124 | @gradientEndBox = new pColor
1125 | parent: row
1126 | className: 'right'
1127 | section: @gradientPropertiesDiv
1128 | default: null
1129 |
1130 | # ------------------
1131 | # gradient angle
1132 |
1133 | row = new pRow
1134 | parent: @gradientPropertiesDiv
1135 | text: ''
1136 |
1137 | @gradientAngleBox = new pInput
1138 | parent: row
1139 | className: 'left'
1140 | unit: 'a'
1141 | section: @gradientPropertiesDiv
1142 | default: null
1143 |
1144 | # ------------------
1145 | # opacity
1146 |
1147 | row = new pRow
1148 | text: 'Opacity'
1149 |
1150 | @opacityBox = new pInput
1151 | parent: row
1152 | className: 'left'
1153 | unit: ''
1154 |
1155 |
1156 | new pDivider
1157 | parent: @filtersDiv
1158 |
1159 | # ------------------------------------
1160 | # border properties
1161 |
1162 | @borderPropertiesDiv = new pDiv
1163 |
1164 | # ------------------
1165 | # border
1166 |
1167 | row = new pRow
1168 | text: 'Border'
1169 | parent: @borderPropertiesDiv
1170 |
1171 | @borderColorBox = new pColor
1172 | parent: row
1173 | className: 'left'
1174 |
1175 | @borderWidthBox = new pInput
1176 | parent: row
1177 | className: 'right'
1178 | unit: 'w'
1179 | section: @borderPropertiesDiv
1180 |
1181 | # ------------------
1182 | # radius
1183 |
1184 | row = new pRow
1185 | text: 'Radius'
1186 | parent: @borderPropertiesDiv
1187 |
1188 | @borderRadiusBox = new pInput
1189 | parent: row
1190 | className: 'left'
1191 | unit: ''
1192 | section: @borderPropertiesDiv
1193 |
1194 | # ------------------
1195 | # shadow
1196 |
1197 |
1198 | @shadowPropertiesDiv = new pDiv
1199 |
1200 | row = new pRow
1201 | parent: @shadowPropertiesDiv
1202 | text: 'Shadow'
1203 |
1204 | @shadowColorBox = new pColor
1205 | parent: row
1206 | section: @shadowPropertiesDiv
1207 | className: 'left'
1208 |
1209 | @shadowSpreadBox = new pInput
1210 | parent: row
1211 | section: @shadowPropertiesDiv
1212 | className: 'right'
1213 | unit: 's'
1214 | default: '0'
1215 |
1216 | row = new pRow
1217 | parent: @shadowPropertiesDiv
1218 | text: ''
1219 |
1220 | @shadowXBox = new pInput
1221 | parent: row
1222 | section: @shadowPropertiesDiv
1223 | className: 'left'
1224 | unit: 'x'
1225 | default: '0'
1226 |
1227 | @shadowYBox = new pInput
1228 | parent: row
1229 | section: @shadowPropertiesDiv
1230 | className: 'right'
1231 | unit: 'y'
1232 | default: '0'
1233 |
1234 | row = new pRow
1235 | parent: @shadowPropertiesDiv
1236 | text: ''
1237 |
1238 | @shadowBlurBox = new pInput
1239 | parent: row
1240 | section: @shadowPropertiesDiv
1241 | className: 'left'
1242 | unit: 'b'
1243 | default: '0'
1244 |
1245 | # ------------------------------------
1246 | # text styles
1247 |
1248 | @textPropertiesDiv = new pDiv
1249 |
1250 | # ------------------
1251 | # font family
1252 |
1253 | row = new pRow
1254 | parent: @textPropertiesDiv
1255 | text: 'Font'
1256 |
1257 | @fontFamilyBox = new pInput
1258 | parent: row
1259 | section: @textPropertiesDiv
1260 | className: 'full'
1261 | unit: ''
1262 |
1263 | # ------------------
1264 | # color
1265 |
1266 | row = new pRow
1267 | parent: @textPropertiesDiv
1268 | text: 'Color'
1269 |
1270 | @colorBox = new pColor
1271 | parent: row
1272 | className: 'left'
1273 |
1274 | @fontSizeBox = new pInput
1275 | parent: row
1276 | section: @textPropertiesDiv
1277 | className: 'right'
1278 | unit: ''
1279 |
1280 | # ------------------
1281 | # weight
1282 |
1283 | row = new pRow
1284 | parent: @textPropertiesDiv
1285 | text: 'Style'
1286 |
1287 | @fontStyleBox = new pInput
1288 | parent: row
1289 | section: @textPropertiesDiv
1290 | className: 'left'
1291 | unit: ''
1292 |
1293 | @fontWeightBox = new pInput
1294 | parent: row
1295 | section: @textPropertiesDiv
1296 | className: 'right'
1297 | unit: 'w'
1298 |
1299 | # ------------------
1300 | # align
1301 |
1302 | row = new pRow
1303 | parent: @textPropertiesDiv
1304 | text: 'Align'
1305 |
1306 | @textAlignBox = new pInput
1307 | parent: row
1308 | section: @textPropertiesDiv
1309 | className: 'full'
1310 | unit: ''
1311 | default: 'left'
1312 |
1313 | # ------------------
1314 | # spacing
1315 |
1316 | row = new pRow
1317 | parent: @textPropertiesDiv
1318 | text: 'Spacing'
1319 |
1320 | @letterSpacingBox = new pInput
1321 | parent: row
1322 | section: @textPropertiesDiv
1323 | className: 'left'
1324 | unit: 'lt'
1325 | default: '1'
1326 |
1327 | @lineHeightBox = new pInput
1328 | parent: row
1329 | section: @textPropertiesDiv
1330 | className: 'right'
1331 | unit: 'ln'
1332 |
1333 | # ------------------
1334 | # text
1335 |
1336 | row = new pRow
1337 | parent: @textPropertiesDiv
1338 | text: 'Text'
1339 |
1340 | @textBox = new pInput
1341 | parent: row
1342 | section: @textPropertiesDiv
1343 | className: 'full'
1344 | unit: ''
1345 |
1346 |
1347 | # ------------------------------------
1348 | # transform properties
1349 |
1350 |
1351 | @transformsDiv = new pDiv
1352 |
1353 | @transformsAcco = new pAccordian
1354 | text: 'Transforms'
1355 | parent: @transformsDiv
1356 |
1357 |
1358 | # ------------------
1359 | # scale
1360 |
1361 | row = new pRow
1362 | parent: @transformsAcco.body
1363 | text: 'Scale'
1364 |
1365 | @scaleBox = new pInput
1366 | parent: row
1367 | section: @transformsDiv
1368 | className: 'left'
1369 | unit: ''
1370 | default: '1'
1371 |
1372 | row = new pRow
1373 | parent: @transformsAcco.body
1374 | text: ''
1375 |
1376 | @scaleXBox = new pInput
1377 | parent: row
1378 | section: @transformsDiv
1379 | className: 'left'
1380 | unit: 'x'
1381 | default: '1'
1382 |
1383 | @scaleYBox = new pInput
1384 | parent: row
1385 | section: @transformsDiv
1386 | className: 'right'
1387 | unit: 'y'
1388 | default: '1'
1389 |
1390 | # ------------------
1391 | # rotation
1392 |
1393 | row = new pRow
1394 | parent: @transformsAcco.body
1395 | text: 'Rotate'
1396 |
1397 | @rotationBox = new pInput
1398 | parent: row
1399 | section: @transformsDiv
1400 | className: 'left'
1401 | unit: ''
1402 | default: '0'
1403 |
1404 | row = new pRow
1405 | parent: @transformsAcco.body
1406 | text: ''
1407 |
1408 | @rotationXBox = new pInput
1409 | parent: row
1410 | section: @transformsDiv
1411 | className: 'left'
1412 | unit: 'x'
1413 | default: '0'
1414 |
1415 | @rotationYBox = new pInput
1416 | parent: row
1417 | section: @transformsDiv
1418 | className: 'right'
1419 | unit: 'y'
1420 | default: '0'
1421 |
1422 |
1423 | # ------------------
1424 | # origin
1425 |
1426 | row = new pRow
1427 | parent: @transformsAcco.body
1428 | text: 'Origin'
1429 |
1430 | @originXBox = new pInput
1431 | parent: row
1432 | section: @transformsDiv
1433 | className: 'left'
1434 | unit: 'x'
1435 | default: '0.50'
1436 |
1437 | @originYBox = new pInput
1438 | parent: row
1439 | section: @transformsDiv
1440 | className: 'right'
1441 | unit: 'y'
1442 | default: '0.50'
1443 |
1444 | # ------------------
1445 | # skew
1446 |
1447 | row = new pRow
1448 | parent: @transformsAcco.body
1449 | text: 'Skew'
1450 |
1451 | @skewBox = new pInput
1452 | parent: row
1453 | section: @transformsDiv
1454 | className: 'left'
1455 | unit: ''
1456 | default: '0'
1457 |
1458 | row = new pRow
1459 | parent: @transformsAcco.body
1460 | text: ''
1461 |
1462 | @skewXBox = new pInput
1463 | parent: row
1464 | section: @transformsDiv
1465 | className: 'left'
1466 | unit: 'x'
1467 | default: '0'
1468 |
1469 | @skewYBox = new pInput
1470 | parent: row
1471 | section: @transformsDiv
1472 | className: 'right'
1473 | unit: 'y'
1474 | default: '0'
1475 |
1476 | # ------------------
1477 | # perspective
1478 |
1479 | row = new pRow
1480 | parent: @transformsAcco.body
1481 | text: 'Perspective'
1482 |
1483 | @perspectiveBox = new pInput
1484 | parent: row
1485 | section: @transformsDiv
1486 | className: 'left'
1487 | unit: ''
1488 | default: '0'
1489 |
1490 |
1491 | # ------------------------------------
1492 | # filters properties
1493 |
1494 | @filtersDiv = new pDiv
1495 |
1496 | @filtersAcco = new pAccordian
1497 | parent: @filtersDiv
1498 | text: 'Filters'
1499 |
1500 | # ------------------
1501 | # blur
1502 |
1503 | row = new pRow
1504 | parent: @filtersAcco.body
1505 | text: 'Blur'
1506 |
1507 | @blurBox = new pInput
1508 | parent: row
1509 | section: @filtersDiv
1510 | className: 'left'
1511 | unit: ''
1512 | default: '0'
1513 |
1514 | # ------------------
1515 | # brightness
1516 |
1517 | row = new pRow
1518 | parent: @filtersAcco.body
1519 | text: 'Brightness'
1520 |
1521 | @brightnessBox = new pInput
1522 | parent: row
1523 | section: @filtersDiv
1524 | className: 'left'
1525 | unit: ''
1526 | default: '100'
1527 |
1528 | # ------------------
1529 | # contrast
1530 |
1531 | row = new pRow
1532 | parent: @filtersAcco.body
1533 | text: 'Contrast'
1534 |
1535 | @contrastBox = new pInput
1536 | parent: row
1537 | section: @filtersDiv
1538 | className: 'left'
1539 | unit: ''
1540 | default: '100'
1541 |
1542 | # ------------------
1543 | # grayscale
1544 |
1545 | row = new pRow
1546 | parent: @filtersAcco.body
1547 | text: 'Grayscale'
1548 |
1549 | @grayscaleBox = new pInput
1550 | parent: row
1551 | section: @filtersDiv
1552 | className: 'left'
1553 | unit: ''
1554 | default: '0'
1555 |
1556 | # ------------------
1557 | # huerotate
1558 |
1559 | row = new pRow
1560 | parent: @filtersAcco.body
1561 | text: 'hueRotate'
1562 |
1563 | @hueRotateBox = new pInput
1564 | parent: row
1565 | section: @filtersDiv
1566 | className: 'left'
1567 | unit: ''
1568 | default: '0'
1569 |
1570 | # ------------------
1571 | # invert
1572 |
1573 | row = new pRow
1574 | parent: @filtersAcco.body
1575 | text: 'Invert'
1576 |
1577 | @invertBox = new pInput
1578 | parent: row
1579 | section: @filtersDiv
1580 | className: 'left'
1581 | unit: ''
1582 | default: '0'
1583 |
1584 | # ------------------
1585 | # saturate
1586 |
1587 | row = new pRow
1588 | parent: @filtersAcco.body
1589 | text: 'Saturate'
1590 |
1591 | @saturateBox = new pInput
1592 | parent: row
1593 | section: @filtersDiv
1594 | className: 'left'
1595 | unit: ''
1596 | default: '100'
1597 |
1598 | # ------------------
1599 | # sepia
1600 |
1601 | row = new pRow
1602 | parent: @filtersAcco.body
1603 | text: 'Sepia'
1604 |
1605 | @sepiaBox = new pInput
1606 | parent: row
1607 | section: @filtersDiv
1608 | className: 'left'
1609 | unit: ''
1610 | default: '0'
1611 |
1612 | # -------------------------- end filters
1613 |
1614 |
1615 | # ------------------------------------
1616 | # effects properties
1617 |
1618 |
1619 | @effectsDiv = new pDiv
1620 |
1621 | @effectsAcco = new pAccordian
1622 | text: 'Effects'
1623 | parent: @effectsDiv
1624 |
1625 |
1626 | # ------------------
1627 | # background filters
1628 |
1629 | row = new pRow
1630 | parent: @effectsAcco.body
1631 | text: 'Blending'
1632 |
1633 | @blendingBox = new pInput
1634 | parent: row
1635 | section: @effectsDiv
1636 | className: 'full'
1637 | unit: ''
1638 | default: 'normal'
1639 |
1640 | row = new pRow
1641 | parent: @effectsAcco.body
1642 | text: 'Blur'
1643 |
1644 | @backgroundBlurBox = new pInput
1645 | parent: row
1646 | section: @effectsDiv
1647 | className: 'left'
1648 | unit: ''
1649 | default: '0'
1650 |
1651 |
1652 | row = new pRow
1653 | parent: @effectsAcco.body
1654 | text: 'Brightness'
1655 |
1656 | @backgroundBrightnessBox = new pInput
1657 | parent: row
1658 | section: @effectsDiv
1659 | className: 'left'
1660 | unit: ''
1661 | default: '100'
1662 |
1663 |
1664 | row = new pRow
1665 | parent: @effectsAcco.body
1666 | text: 'Saturate'
1667 |
1668 | @backgroundSaturateBox = new pInput
1669 | parent: row
1670 | section: @effectsDiv
1671 | className: 'left'
1672 | unit: ''
1673 | default: '100'
1674 |
1675 |
1676 | row = new pRow
1677 | parent: @effectsAcco.body
1678 | text: 'hueRotate'
1679 |
1680 | @backgroundHueRotateBox = new pInput
1681 | parent: row
1682 | section: @effectsDiv
1683 | className: 'left'
1684 | unit: ''
1685 | default: '0'
1686 |
1687 |
1688 | row = new pRow
1689 | parent: @effectsAcco.body
1690 | text: 'Contrast'
1691 |
1692 | @backgroundContrastBox = new pInput
1693 | parent: row
1694 | section: @effectsDiv
1695 | className: 'left'
1696 | unit: ''
1697 | default: '100'
1698 |
1699 |
1700 | row = new pRow
1701 | parent: @effectsAcco.body
1702 | text: 'Invert'
1703 |
1704 | @backgroundInvertBox = new pInput
1705 | parent: row
1706 | section: @effectsDiv
1707 | className: 'left'
1708 | unit: ''
1709 | default: '0'
1710 |
1711 |
1712 | row = new pRow
1713 | parent: @effectsAcco.body
1714 | text: 'Grayscale'
1715 |
1716 | @backgroundGrayscaleBox = new pInput
1717 | parent: row
1718 | section: @effectsDiv
1719 | className: 'left'
1720 | unit: ''
1721 | default: '0'
1722 |
1723 |
1724 | row = new pRow
1725 | parent: @effectsAcco.body
1726 | text: 'Sepia'
1727 |
1728 | @backgroundSepiaBox = new pInput
1729 | parent: row
1730 | section: @effectsDiv
1731 | className: 'left'
1732 | unit: ''
1733 | default: '0'
1734 |
1735 |
1736 |
1737 | # ------------------------------------
1738 | # animation properties
1739 |
1740 |
1741 | @animsDiv = new pDiv
1742 |
1743 | @animsAcco = new pAccordian
1744 | text: 'Animations'
1745 | parent: @animsDiv
1746 |
1747 |
1748 |
1749 | # ------------------------------------
1750 | # event listener properties
1751 |
1752 |
1753 | @eventListenersDiv = new pDiv
1754 |
1755 | @eventListenersAcco = new pAccordian
1756 | text: 'Event Listeners'
1757 | parent: @eventListenersDiv
1758 |
1759 |
1760 |
1761 | # image --------------------------------
1762 |
1763 | @imagePropertiesDiv = new pDiv
1764 |
1765 | new pDivider
1766 | parent: @imagePropertiesDiv
1767 |
1768 | # ------------------
1769 | # image
1770 |
1771 | @imageBox = new pImage
1772 | parent: @imagePropertiesDiv
1773 | section: @imagePropertiesDiv
1774 |
1775 |
1776 | # screenshot ---------------------------
1777 |
1778 | @screenshotDiv = new pDiv
1779 |
1780 | # ------------------
1781 | # screenshot
1782 |
1783 | @screenshotBox = new pImage
1784 | parent: @screenshotDiv
1785 | section: @screenshotDiv
1786 |
1787 |
1788 | # ------------------
1789 | # placeholders
1790 |
1791 | row = new pRow
1792 | text: ''
1793 | row.element.style.height = '64px'
1794 |
1795 | # ------------------
1796 | # social media links
1797 |
1798 | @socialMediaRow = new pRow
1799 | parent: @textPropertiesDiv.body
1800 | text: ''
1801 |
1802 | @linkedinIcon = document.createElement('a')
1803 | _.assign @linkedinIcon,
1804 | href: "http://www.linkedin.com/in/steveruizok"
1805 | innerHTML: ' '
1806 |
1807 | @githubIcon = document.createElement('a')
1808 | _.assign @githubIcon,
1809 | href: "http://github.com/steveruizok/gotcha"
1810 | innerHTML: ' '
1811 |
1812 | @twitterIcon = document.createElement('a')
1813 | _.assign @twitterIcon,
1814 | href: "http://twitter.com/steveruizok"
1815 | innerHTML: ''
1816 |
1817 | for element in [@linkedinIcon, @githubIcon, @twitterIcon]
1818 | @socialMediaRow.element.appendChild(element)
1819 | @socialMediaRow.element.classList.add('socialLinks')
1820 |
1821 | @hideDivs()
1822 |
1823 | clearChildrenThenShowAnimations: (animations) =>
1824 | child = @animsAcco.body.element.childNodes[0]
1825 |
1826 | if not child?
1827 | @showAnimations(animations)
1828 | return
1829 |
1830 | @animsAcco.body.element.removeChild(child)
1831 | @clearChildrenThenShowAnimations(animations)
1832 |
1833 | clearChildrenThenShowEventListeners: (eventListeners) =>
1834 |
1835 | child = @eventListenersAcco.body.element.childNodes[0]
1836 |
1837 | if not child?
1838 | @showEventListeners(eventListeners)
1839 | return
1840 |
1841 | @eventListenersAcco.body.element.removeChild(child)
1842 | @clearChildrenThenShowEventListeners(eventListeners)
1843 |
1844 | showEventListeners: (eventListeners = []) =>
1845 |
1846 | defaults = [
1847 | "function (){return fn.apply(me,arguments)}",
1848 | "function (){return fn.apply(me, arguments)}",
1849 | "function (event){return event.preventDefault()}",
1850 | "function (){ return fn.apply(me, arguments); }",
1851 | 'function debounced(){var time=now(),isInvoking=shouldInvoke(time);if(lastArgs=arguments,lastThis=this,lastCallTime=time,isInvoking){if(timerId===undefined)return leadingEdge(lastCallTime);if(maxing)return timerId=setTimeout(timerExpired,wait),invokeFunc(lastCallTime)}return timerId===undefined&&(timerId=setTimeout(timerExpired,wait)),result}',
1852 | 'function (value){if(null!==value)return"fontSize"!==property&&"font"!==property&&_this._styledText.resetStyle(property),_this.renderText()}',
1853 | ]
1854 |
1855 | realListeners = 0
1856 |
1857 | for listener, i in eventListeners
1858 |
1859 | continue if _.every(listener.events, (e) -> _.includes(defaults, e.function))
1860 |
1861 | # --------------------------------
1862 | # listener
1863 |
1864 | row = new pRow
1865 | parent: @eventListenersAcco.body
1866 | text: '"' + listener.listener + '"'
1867 | bold: true
1868 |
1869 | # --------------------------------
1870 | # events
1871 |
1872 | for event, e in listener.events
1873 |
1874 | continue if _.includes(defaults, event.function)
1875 |
1876 | realListeners++
1877 |
1878 | # name
1879 |
1880 | row = new pRow
1881 | parent: @eventListenersAcco.body
1882 | text: 'Name'
1883 |
1884 | box = new pInput
1885 | parent: row
1886 | className: 'full'
1887 | unit: ''
1888 | value: event.name ? ''
1889 | isDefault: event.name isnt 'undefined'
1890 |
1891 | # function
1892 |
1893 | row = new pRow
1894 | parent: @eventListenersAcco.body
1895 | text: 'Function'
1896 |
1897 | box = new pInput
1898 | parent: row
1899 | className: 'full'
1900 | unit: ''
1901 | value: event.function
1902 | isDefault: false
1903 |
1904 | # Once
1905 |
1906 | row = new pRow
1907 | parent: @eventListenersAcco.body
1908 | text: 'Once'
1909 |
1910 | box = new pInput
1911 | parent: row
1912 | className: 'left'
1913 | unit: ''
1914 | value: event.once
1915 | isDefault: event.name isnt 'false'
1916 |
1917 | unless e is listener.events.length - 1
1918 | new pDivider
1919 | parent: @eventListenersAcco.body
1920 |
1921 | unless i is eventListeners.length - 1
1922 | new pDivider
1923 | parent: @eventListenersAcco.body
1924 |
1925 |
1926 | # set color
1927 |
1928 | if realListeners is 0
1929 | @eventListenersAcco.color = '#888888'
1930 | return
1931 |
1932 | @eventListenersAcco.color = '#FFFFFF'
1933 |
1934 | showAnimations: (animations) =>
1935 |
1936 | @animsAcco.color = if animations.length > 0 then '#FFF' else '#888888'
1937 |
1938 | for anim, i in animations
1939 |
1940 | properties = anim.properties
1941 | options = anim.options
1942 | stateA = anim._stateA
1943 | stateB = anim._stateB
1944 |
1945 | # --------------------------------
1946 |
1947 | # animation
1948 |
1949 | row = new pRow
1950 | parent: @animsAcco.body
1951 | text: 'Animation ' + (i + 1)
1952 | bold: true
1953 |
1954 | fromUnit = new pLabel
1955 | parent: row
1956 | className: 'left'
1957 | text: 'from'
1958 |
1959 | toUnit = new pLabel
1960 | parent: row
1961 | className: 'right'
1962 | text: 'to'
1963 |
1964 | for element in [fromUnit.element, toUnit.element]
1965 | element.classList.add('alignLeft')
1966 |
1967 | # ---------------
1968 | # properties
1969 |
1970 | for key, value of properties
1971 |
1972 | if Color.isColorObject(value) or Color.isColor(value)
1973 |
1974 | row = new pRow
1975 | parent: @animsAcco.body
1976 | text: _.startCase(key)
1977 |
1978 | # from
1979 | box = new pColor
1980 | parent: row
1981 | className: 'left'
1982 | unit: ''
1983 | value: stateA?[key]
1984 | isDefault: false
1985 |
1986 | # to
1987 | box = new pColor
1988 | parent: row
1989 | className: 'right'
1990 | unit: ''
1991 | value: stateB?[key]
1992 | isDefault: false
1993 |
1994 | else if key is 'gradient'
1995 |
1996 | # start
1997 | row = new pRow
1998 | parent: @animsAcco.body
1999 | text: 'Grad Start'
2000 |
2001 | # from
2002 | box = new pColor
2003 | parent: row
2004 | className: 'left'
2005 | unit: ''
2006 | value: stateA?[key]?.start
2007 | isDefault: false
2008 |
2009 | # to
2010 | box = new pColor
2011 | parent: row
2012 | className: 'right'
2013 | unit: ''
2014 | value: stateB?[key]?.start
2015 | isDefault: false
2016 |
2017 | # end
2018 | row = new pRow
2019 | parent: @animsAcco.body
2020 | text: 'Grad End'
2021 |
2022 | # from
2023 | box = new pColor
2024 | parent: row
2025 | className: 'left'
2026 | unit: ''
2027 | value: stateA?[key]?.end
2028 | isDefault: false
2029 |
2030 | # to
2031 | box = new pColor
2032 | parent: row
2033 | className: 'right'
2034 | unit: ''
2035 | value: stateB?[key]?.end
2036 | isDefault: false
2037 |
2038 | # angle
2039 | row = new pRow
2040 | parent: @animsAcco.body
2041 | text: 'Grad Angle'
2042 |
2043 | # from
2044 | box = new pInput
2045 | parent: row
2046 | className: 'left'
2047 | unit: ''
2048 | value: stateA?[key]?.angle
2049 | isDefault: false
2050 |
2051 | # to
2052 | box = new pInput
2053 | parent: row
2054 | className: 'right'
2055 | unit: ''
2056 | value: stateB?[key]?.angle
2057 | isDefault: false
2058 |
2059 | else
2060 |
2061 | row = new pRow
2062 | parent: @animsAcco.body
2063 | text: _.startCase(key)
2064 |
2065 | # from
2066 | box = new pInput
2067 | parent: row
2068 | className: 'left'
2069 | unit: ''
2070 | value: stateA?[key]
2071 | isDefault: false
2072 |
2073 | # to
2074 | box = new pInput
2075 | parent: row
2076 | className: 'right'
2077 | unit: ''
2078 | value: stateB?[key]
2079 | isDefault: false
2080 |
2081 | # ---------------
2082 | # options
2083 |
2084 | row = new pRow
2085 | parent: @animsAcco.body
2086 | text: 'Options'
2087 |
2088 | # time
2089 | box = new pInput
2090 | parent: row
2091 | className: 'left'
2092 | unit: 's'
2093 | value: options.time
2094 | isDefault: false
2095 |
2096 | # time
2097 | box = new pInput
2098 | parent: row
2099 | className: 'right'
2100 | unit: 'd'
2101 | value: options.delay
2102 | isDefault: false
2103 |
2104 | row = new pRow
2105 | parent: @animsAcco.body
2106 | text: ''
2107 |
2108 | # repeat
2109 | box = new pInput
2110 | parent: row
2111 | className: 'left'
2112 | unit: 'r'
2113 | value: options.repeat
2114 | isDefault: false
2115 |
2116 | # time
2117 | box = new pInput
2118 | parent: row
2119 | className: 'right'
2120 | unit: 'l'
2121 | value: options.looping
2122 | isDefault: false
2123 |
2124 | unless i is animations.length - 1
2125 | new pDivider
2126 | parent: @animsAcco.body
2127 |
2128 |
2129 | showProperties: (layer, customProps) =>
2130 |
2131 | @scrollTop = @element.scrollTop
2132 |
2133 | props = layer.props
2134 | _.assign props, customProps
2135 |
2136 | defaults = layer._propertyList()
2137 |
2138 | _.assign defaults,
2139 | rotation: defaults.rotationZ
2140 | blending: {default: 'normal'}
2141 |
2142 | @hideDivs()
2143 |
2144 | for key, value of _.merge(layer.props, customProps)
2145 |
2146 | propLayer = @[key + 'Box']
2147 |
2148 | if not propLayer
2149 | continue
2150 |
2151 | def = defaults[key]?.default
2152 |
2153 | @showProperty(key, value, propLayer, def)
2154 |
2155 | @showOverrideInAcco(@effectsDiv, @effectsAcco)
2156 | @showOverrideInAcco(@filtersDiv, @filtersAcco)
2157 | @showOverrideInAcco(@transformsDiv, @transformsAcco)
2158 |
2159 | @element.scrollTop = @scrollTop
2160 |
2161 | showOverrideInAcco: (div, acco) ->
2162 | acco.color = '#888888'
2163 | for propLayer in div.properties
2164 | if propLayer.value? and propLayer.value isnt propLayer.default
2165 | acco.color = '#FFF'
2166 |
2167 | showProperty: (key, value, propLayer, def) =>
2168 |
2169 | return if value is propLayer.value
2170 |
2171 | propLayer.isDefault = false
2172 |
2173 | if not value? or _.isNaN(value) or value is def
2174 | value = def ? ''
2175 | propLayer.isDefault = true
2176 |
2177 | # color
2178 | if Color.isColorObject(value)
2179 | value = value.toHslString()
2180 |
2181 | # gradient
2182 | if value?.constructor?.name is 'Gradient'
2183 | propLayer.value = ''
2184 | return
2185 |
2186 | # string
2187 | if typeof value is 'string'
2188 | propLayer.value = value
2189 | return
2190 |
2191 | value = value.toString()
2192 |
2193 | # float
2194 | if value.indexOf('.') isnt -1
2195 | propLayer.value = parseFloat(value).toFixed(2)
2196 | return
2197 |
2198 | # numer
2199 | propLayer.value = parseInt(value, 10).toFixed()
2200 |
2201 | hideDivs: =>
2202 | for div in [
2203 | @gradientPropertiesDiv,
2204 | @textPropertiesDiv,
2205 | @shadowPropertiesDiv,
2206 | @borderPropertiesDiv,
2207 | @imagePropertiesDiv,
2208 | @screenshotDiv
2209 | ]
2210 | div.visible = false
2211 |
2212 |
2213 |
2214 |
2215 |
2216 |
2217 |
2218 |
2219 | propLayers = []
2220 |
2221 | ### -------------------------------------------
2222 |
2223 | .88888. dP dP
2224 | d8' `88 88 88
2225 | 88 .d8888b. d8888P .d8888b. 88d888b. .d8888b.
2226 | 88 YP88 88' `88 88 88' `"" 88' `88 88' `88
2227 | Y8. .88 88. .88 88 88. ... 88 88 88. .88
2228 | `88888' `88888P' dP `88888P' dP dP `8888888
2229 |
2230 | ###
2231 |
2232 |
2233 | class Gotcha
2234 | constructor: (options = {}) ->
2235 |
2236 | @specPanel = new SpecPanel
2237 |
2238 | _.defaults options,
2239 | color: 'rgba(72, 207, 255, 1.000)'
2240 | selectedColor: 'rgba(255, 1, 255, 1.000)'
2241 | secondaryColor: '#FFFFFF'
2242 | fontFamily: 'Menlo'
2243 | fontSize: '10'
2244 | fontWeight: '500'
2245 | borderRadius: 4
2246 | padding: {top: 1, bottom: 1, left: 3, right: 3}
2247 |
2248 | _.assign @,
2249 | color: options.color
2250 | selectedColor: options.selectedColor
2251 | secondaryColor: options.secondaryColor
2252 | fontFamily: options.fontFamily
2253 | fontSize: options.fontSize
2254 | fontWeight: options.fontWeight
2255 | shapes: []
2256 | borderRadius: options.borderRadius
2257 | padding: options.padding
2258 | focusedElement: undefined
2259 | enabled: false
2260 | screenElement: document.getElementsByClassName('DeviceComponentPort')[0]
2261 | layers: []
2262 | containers: []
2263 | timer: undefined
2264 | _onlyVisible: true
2265 |
2266 | document.addEventListener('keyup', @toggle)
2267 | Framer.CurrentContext.domEventManager.wrap(window).addEventListener("resize", @update)
2268 |
2269 | @context = document.getElementsByClassName('framerLayer DeviceScreen')[0]
2270 | @context.classList.add('hoverContext')
2271 | @context.childNodes[2].classList.add('IgnorePointerEvents')
2272 |
2273 | Object.defineProperty @,
2274 | "onlyVisible",
2275 | get: -> return @_onlyVisible
2276 | set: (bool) ->
2277 | return if typeof bool isnt 'boolean'
2278 | @_onlyVisible = bool
2279 |
2280 | Framer.Device.on "change:deviceType", =>
2281 | Utils.delay 0, @update
2282 |
2283 | toggle: (event, open) =>
2284 | # return if Framer.Device.hands.isAnimating
2285 |
2286 | if event.key is "`" or event.key is "<" or open is true
2287 | if @opened then @disable() else @enable()
2288 | @opened = !@opened
2289 | return
2290 |
2291 | return if not @enabled
2292 |
2293 | if event.key is "/" or event.key is ">"
2294 | @setSelectedLayer()
2295 | return
2296 |
2297 | if event.key is "."
2298 | @hoveredLayer?.emit Events.Tap
2299 | return
2300 |
2301 | if event.key is "\\"
2302 | @_lastSpeed ?= 1
2303 | thisSpeed = @specPanel.speedBox.element.value
2304 |
2305 | if thisSpeed is "0"
2306 | @specPanel.speedBox.element.value = @_lastSpeed
2307 | @specPanel.speedBox.action(@_lastSpeed)
2308 | else
2309 | @specPanel.speedBox.element.value = 0
2310 | Framer.Loop.delta = .000000000000000000001
2311 | @_lastSpeed = thisSpeed
2312 |
2313 | # open the panel, start listening for events
2314 | enable: =>
2315 | @_canvasColor = Canvas.backgroundColor
2316 | svgContext.setContext()
2317 |
2318 | @transition(true)
2319 |
2320 | if @timer? then clearInterval @timer
2321 | @timer = Utils.interval 1/15, @focus
2322 |
2323 | disable: =>
2324 | @unfocus()
2325 | @enabled = false
2326 |
2327 | @transition(false)
2328 |
2329 | if @timer? then clearInterval @timer
2330 |
2331 | transition: (open = true, seconds = .5) =>
2332 | hands = Framer.Device.hands
2333 |
2334 | hands.on "change:x", @showTransition
2335 |
2336 | hands.once Events.AnimationEnd, =>
2337 | hands.off "change:x", @showTransition
2338 | @enabled = @opened = open
2339 |
2340 | if open
2341 | Framer.Device.screen.on Events.MouseOver, @setHoveredLayer
2342 | Framer.Device.screen.on Events.MouseOut, @unsetHoveredLayer
2343 | Framer.Device.background.on Events.MouseOver, @unsetHoveredLayer
2344 | Framer.Device.screen.on Events.Click, @setSelectedLayer
2345 |
2346 | else
2347 | Framer.Device.screen.off Events.MouseOver, @setHoveredLayer
2348 | Framer.Device.screen.off Events.MouseOut, @unsetHoveredLayer
2349 | Framer.Device.background.off Events.MouseOver, @unsetHoveredLayer
2350 | Framer.Device.screen.off Events.Click, @setSelectedLayer
2351 |
2352 | @focus()
2353 |
2354 | @_startPosition = Framer.Device.hands.x
2355 |
2356 | midX = hands._context.innerWidth / 2
2357 |
2358 | Framer.Device.hands.animate
2359 | midX: if open then midX - 112 else midX
2360 | options:
2361 | time: seconds
2362 | curve: Spring(damping: 10)
2363 |
2364 | showTransition: =>
2365 | hands = Framer.Device.hands
2366 | midX = hands._context.innerWidth / 2
2367 |
2368 | opacity = Utils.modulate(
2369 | hands.midX,
2370 | [midX - 56, midX - 112],
2371 | [0, 1],
2372 | true
2373 | )
2374 |
2375 | factor = Utils.modulate(
2376 | hands.midX,
2377 | [midX, midX - 112],
2378 | [0, 1],
2379 | true
2380 | )
2381 |
2382 | @specPanel.element.style.opacity = opacity
2383 | Canvas.backgroundColor = Color.mix @_canvasColor,'rgba(30, 30, 30, 1.000)', factor
2384 |
2385 | # update when screen size changes
2386 | update: =>
2387 | return if not @opened
2388 |
2389 | Framer.Device.hands.midX -= 122
2390 |
2391 | svgContext.setContext()
2392 | @focus()
2393 |
2394 | # get the dimensions of an element
2395 | getDimensions: (element) =>
2396 | return if not element
2397 | d = element.getBoundingClientRect()
2398 |
2399 | dimensions = {
2400 | x: d.left
2401 | y: d.top
2402 | width: d.width
2403 | height: d.height
2404 | midX: d.left + (d.width / 2)
2405 | midY: d.top + (d.height / 2)
2406 | maxX: d.left + d.width
2407 | maxY: d.top + d.height
2408 | frame: d
2409 | }
2410 |
2411 | return dimensions
2412 |
2413 | # make a relative distance line
2414 | makeLine: (pointA, pointB, label = true) =>
2415 |
2416 | color = if @selectedLayer? then @selectedColor else @color
2417 |
2418 | line = new SVGShape
2419 | type: 'path'
2420 | d: "M #{pointA[0]} #{pointA[1]} L #{pointB[0]} #{pointB[1]}"
2421 | stroke: color
2422 | 'stroke-width': '1px'
2423 |
2424 | if pointA[0] is pointB[0]
2425 |
2426 | capA = new SVGShape
2427 | type: 'path'
2428 | d: "M #{pointA[0] - 5} #{pointA[1]} L #{pointA[0] + 5} #{pointA[1]}"
2429 | stroke: color
2430 | 'stroke-width': '1px'
2431 |
2432 | capB = new SVGShape
2433 | type: 'path'
2434 | d: "M #{pointB[0] - 5} #{pointB[1]} L #{pointB[0] + 5} #{pointB[1]}"
2435 | stroke: color
2436 | 'stroke-width': '1px'
2437 |
2438 | else if pointA[1] is pointB[1]
2439 |
2440 | capA = new SVGShape
2441 | type: 'path'
2442 | d: "M #{pointA[0]} #{pointA[1] - 5} L #{pointA[0]} #{pointA[1] + 5}"
2443 | stroke: color
2444 | 'stroke-width': '1px'
2445 |
2446 | capB = new SVGShape
2447 | type: 'path'
2448 | d: "M #{pointB[0]} #{pointB[1] - 5} L #{pointB[0]} #{pointB[1] + 5}"
2449 | stroke: color
2450 | 'stroke-width': '1px'
2451 |
2452 | # make the label box for distance lines
2453 | makeLabel: (x, y, text) =>
2454 |
2455 | color = if @selectedLayer? then @selectedColor else @color
2456 |
2457 | label = new SVGShape
2458 | type: 'text'
2459 | parent: svgContext
2460 | x: x
2461 | y: y + 4
2462 | 'font-family': @fontFamily
2463 | 'font-size': @fontSize
2464 | 'font-weight': @fontWeight
2465 | 'text-anchor': "middle"
2466 | fill: @secondaryColor
2467 | text: Math.floor(text / @ratio)
2468 |
2469 | w = label.element.textLength.baseVal.value
2470 |
2471 | box = new SVGShape
2472 | type: 'rect'
2473 | parent: svgContext
2474 | x: x - (w / 2) - @padding.left
2475 | y: y - 7
2476 | width: w + @padding.left + @padding.right
2477 | height: 15
2478 | rx: @borderRadius
2479 | ry: @borderRadius
2480 | fill: new Color(color).darken(40)
2481 | stroke: color
2482 | 'stroke-width': '1px'
2483 |
2484 | label.show()
2485 |
2486 | # make the bounding rectangle for selected / hovered elements
2487 | makeRectOverlays: (selectedLayer, s, hoveredLayer, h) =>
2488 | if not s or not h
2489 | return
2490 |
2491 | if hoveredLayer is selectedLayer
2492 | hoveredLayer = Framer.Device.screen
2493 |
2494 | hoverFill = new Color(@color).alpha(.2)
2495 |
2496 | if hoveredLayer is Framer.Device.screen
2497 | hoverFill = new Color(@color).alpha(0)
2498 |
2499 | hoveredRect = new SVGShape
2500 | type: 'rect'
2501 | parent: svgContext
2502 | x: h.x
2503 | y: h.y
2504 | width: h.width
2505 | height: h.height
2506 | stroke: @color
2507 | fill: hoverFill
2508 | 'stroke-width': '1px'
2509 |
2510 | selectFill = new Color(@selectedColor).alpha(.2)
2511 |
2512 | if selectedLayer is Framer.Device.screen
2513 | selectFill = new Color(@selectedColor).alpha(0)
2514 |
2515 | selectedRect = new SVGShape
2516 | type: 'rect'
2517 | parent: svgContext
2518 | x: s.x
2519 | y: s.y
2520 | width: s.width
2521 | height: s.height
2522 | stroke: @selectedColor
2523 | fill: selectFill
2524 | 'stroke-width': '1px'
2525 |
2526 | # make dashed lines from bounding rect to screen edge
2527 | makeDashedLines: (e, f, color, offset) =>
2528 | return if not e
2529 | return if e is f
2530 |
2531 | color = new Color(color).alpha(.8)
2532 |
2533 | new DashedLine(
2534 | {x: e.x, y: f.y},
2535 | {x: e.x, y: f.maxY}
2536 | color,
2537 | offset
2538 | )
2539 |
2540 | new DashedLine(
2541 | {x: e.maxX, y: f.y},
2542 | {x: e.maxX, y: f.maxY},
2543 | color,
2544 | offset
2545 | )
2546 |
2547 | new DashedLine(
2548 | {x: f.x, y: e.y},
2549 | {x: f.maxX, y: e.y},
2550 | color,
2551 | offset
2552 | )
2553 |
2554 | new DashedLine(
2555 | {x: f.x, y: e.maxY},
2556 | {x: f.maxX, y: e.maxY},
2557 | color,
2558 | offset
2559 | )
2560 |
2561 | showDistances: (selectedLayer, hoveredLayer) =>
2562 |
2563 | return if not selectedLayer or not hoveredLayer
2564 |
2565 | s = @getDimensions(selectedLayer._element)
2566 | h = @getDimensions(hoveredLayer._element)
2567 | f = @getDimensions(Framer.Device.screen._element)
2568 |
2569 | @makeDashedLines(s, f, @selectedColor, 5)
2570 |
2571 | @makeRectOverlays(selectedLayer, s, hoveredLayer, h)
2572 |
2573 | # When selected element contains hovered element
2574 |
2575 | @ratio = Framer.Device.screen._element.getBoundingClientRect().width / Screen.width
2576 |
2577 | if s.x < h.x and s.maxX > h.maxX and s.y < h.y and s.maxY > h.maxY
2578 |
2579 | # top
2580 |
2581 | d = Math.abs(s.y - h.y)
2582 | m = s.y + d / 2
2583 |
2584 | @makeLine([h.midX, s.y + 5], [h.midX, h.y - 4])
2585 | @makeLabel(h.midX, m, d)
2586 |
2587 | # right
2588 |
2589 | d = Math.abs(s.maxX - h.maxX)
2590 | m = h.maxX + (d / 2)
2591 |
2592 | @makeLine([h.maxX + 5, h.midY], [s.maxX - 4, h.midY])
2593 | @makeLabel(m, h.midY, d)
2594 |
2595 | # bottom
2596 |
2597 | d = Math.abs(s.maxY - h.maxY)
2598 | m = h.maxY + (d / 2)
2599 |
2600 | @makeLine([h.midX, h.maxY + 5], [h.midX, s.maxY - 4])
2601 | @makeLabel(h.midX, m, d)
2602 |
2603 | # left
2604 |
2605 | d = Math.abs(s.x - h.x)
2606 | m = s.x + d / 2
2607 |
2608 | @makeLine([s.x + 5, h.midY], [h.x - 4, h.midY])
2609 | @makeLabel(m, h.midY, d)
2610 |
2611 | return
2612 |
2613 | # When hovered element contains selected element
2614 |
2615 | if s.x > h.x and s.maxX < h.maxX and s.y > h.y and s.maxY < h.maxY
2616 |
2617 | # top
2618 |
2619 | d = Math.abs(h.y - s.y)
2620 | m = h.y + d / 2
2621 |
2622 | @makeLine([s.midX, h.y + 5], [s.midX, s.y - 4])
2623 | @makeLabel(s.midX, m, d)
2624 |
2625 | # right
2626 |
2627 | d = Math.abs(h.maxX - s.maxX)
2628 | m = s.maxX + (d / 2)
2629 |
2630 | @makeLine([s.maxX + 5, s.midY], [h.maxX - 4, s.midY])
2631 | @makeLabel(m, s.midY, d)
2632 |
2633 | # bottom
2634 |
2635 | d = Math.abs(h.maxY - s.maxY)
2636 | m = s.maxY + (d / 2)
2637 |
2638 | @makeLine([s.midX, s.maxY + 5], [s.midX, h.maxY - 4])
2639 | @makeLabel(s.midX, m, d)
2640 |
2641 | # left
2642 |
2643 | d = Math.abs(h.x - s.x)
2644 | m = h.x + d / 2
2645 |
2646 | @makeLine([h.x + 5, s.midY], [s.x - 4, s.midY])
2647 | @makeLabel(m, s.midY, d)
2648 |
2649 |
2650 | return
2651 |
2652 | # When selected element doesn't contain hovered element
2653 |
2654 | # top
2655 |
2656 | if s.y > h.maxY
2657 |
2658 | d = Math.abs(s.y - h.maxY)
2659 | m = s.y - (d / 2)
2660 |
2661 | @makeLine([h.midX, h.maxY + 5], [h.midX, s.y - 4])
2662 | @makeLabel(h.midX, m, d)
2663 |
2664 | else if s.y > h.y
2665 |
2666 | d = Math.abs(s.y - h.y)
2667 | m = s.y - (d / 2)
2668 |
2669 | @makeLine([h.midX, h.y + 5], [h.midX, s.y - 4])
2670 | @makeLabel(h.midX, m, d)
2671 |
2672 | # left
2673 |
2674 | if h.maxX < s.x
2675 |
2676 | d = Math.abs(s.x - h.maxX)
2677 | m = s.x - (d / 2)
2678 |
2679 | @makeLine([h.maxX + 5, h.midY], [s.x - 4, h.midY])
2680 | @makeLabel(m, h.midY, d)
2681 |
2682 | else if h.x < s.x
2683 |
2684 | d = Math.abs(s.x - h.x)
2685 | m = s.x - (d / 2)
2686 |
2687 | @makeLine([h.x + 5, h.midY], [s.x - 4, h.midY])
2688 | @makeLabel(m, h.midY, d)
2689 |
2690 | # right
2691 |
2692 | if s.maxX < h.x
2693 |
2694 | d = Math.abs(h.x - s.maxX)
2695 | m = s.maxX + (d / 2)
2696 |
2697 | @makeLine([s.maxX + 5, h.midY], [h.x - 4, h.midY])
2698 | @makeLabel(m, h.midY, d)
2699 |
2700 | else if s.x < h.x
2701 |
2702 | d = Math.abs(h.x - s.x)
2703 | m = s.x + (d / 2)
2704 |
2705 | @makeLine([s.x + 5, h.midY], [h.x - 4, h.midY])
2706 | @makeLabel(m, h.midY, d)
2707 |
2708 | # bottom
2709 |
2710 | if s.maxY < h.y
2711 |
2712 | d = Math.abs(h.y - s.maxY)
2713 | m = s.maxY + (d / 2)
2714 |
2715 | @makeLine([h.midX, s.maxY + 5], [h.midX, h.y - 4])
2716 | @makeLabel(h.midX, m, d)
2717 |
2718 | else if s.y < h.y
2719 |
2720 | d = Math.abs(h.y - s.y)
2721 | m = s.y + (d / 2)
2722 |
2723 | @makeLine([h.midX, s.y + 5], [h.midX, h.y - 4])
2724 | @makeLabel(h.midX, m, d)
2725 |
2726 | # set the panel with current properties
2727 | setPanelProperties: () =>
2728 |
2729 | layer = @selectedLayer ? @hoveredLayer
2730 |
2731 | if layer is @lastLayer and layer.isAnimating is false
2732 | return
2733 |
2734 | @lastLayer = layer
2735 | @lastProps = layer.props
2736 |
2737 | # properties to assigned to layer.props
2738 | customProps =
2739 | x: layer.screenFrame.x
2740 | y: layer.screenFrame.y
2741 | componentName: layer.constructor.name
2742 | componentNames: @getComponentFromLayer(layer.parent)
2743 | parentName: layer.parent?.name
2744 | rotation: layer.rotationZ
2745 | # textAlign: layer.props.styledTextOptions?.alignment
2746 | blending: layer.blending
2747 | # screenshot: @getScreenshot(layer._element)
2748 |
2749 | if layer.gradient?
2750 | _.assign customProps,
2751 | gradientStart: layer.gradient.start
2752 | gradientEnd: layer.gradient.end
2753 | gradientAngle: layer.gradient.angle
2754 |
2755 | if layer.shadows?
2756 | _.assign customProps,
2757 | shadowX: layer.shadows[0]?.x
2758 | shadowY: layer.shadows[0]?.y
2759 | shadowSpread: layer.shadows[0]?.spread
2760 | shadowColor: layer.shadows[0]?.color
2761 | shadowType: layer.shadows[0]?.type
2762 | shadowBlur: layer.shadows[0]?.blur
2763 |
2764 | @specPanel.showProperties(layer, customProps)
2765 |
2766 | eventListeners = @getLayerEventListeners(layer)
2767 | @specPanel.clearChildrenThenShowEventListeners(eventListeners)
2768 |
2769 | animations = layer.animations()
2770 | @specPanel.clearChildrenThenShowAnimations(animations)
2771 |
2772 |
2773 | setHoveredLayer: (event) =>
2774 | return if not @enabled
2775 |
2776 | layer = @getLayerFromElement(event?.target)
2777 | return if not @getLayerIsVisible(layer)
2778 |
2779 | @hoveredLayer = layer
2780 |
2781 | @tryFocus(event)
2782 |
2783 | return false
2784 |
2785 | unsetHoveredLayer: (event) =>
2786 | @hoveredLayer = undefined
2787 | Utils.delay .05, =>
2788 | if not @hoveredLayer then @focus()
2789 |
2790 | setSelectedLayer: =>
2791 | return if not @hoveredLayer
2792 |
2793 | if @selectedLayer is @hoveredLayer
2794 | @unsetSelectedLayer()
2795 | return
2796 |
2797 | @selectedLayer = @hoveredLayer
2798 | @focus()
2799 |
2800 | unsetSelectedLayer: =>
2801 | @selectedLayer = undefined
2802 | @focus()
2803 |
2804 |
2805 | # Find an element that belongs to a Framer Layer
2806 | findLayerElement: (element) ->
2807 | return if not element
2808 | return if not element.classList
2809 |
2810 | if element.classList.contains('framerLayer')
2811 | return element
2812 |
2813 | @findLayerElement(element.parentNode)
2814 |
2815 | # Find a Framer Layer that matches a Framer Layer element
2816 | getLayerFromElement: (element) =>
2817 | return if not element
2818 |
2819 | element = @findLayerElement(element)
2820 |
2821 | layers = _.filter(Framer.CurrentContext._layers, (l) ->
2822 | if l.gotchaIgnore
2823 | return false
2824 |
2825 | return true
2826 | )
2827 |
2828 | layer = _.find(layers, (l) -> l._element is element)
2829 |
2830 | return layer
2831 |
2832 |
2833 | getLayerIsVisible: (layer) =>
2834 | if not layer
2835 | return true
2836 |
2837 | if @onlyVisible
2838 | if layer.opacity is 0
2839 | return false
2840 |
2841 | if layer.visible is false
2842 | return false
2843 |
2844 | @getLayerIsVisible(layer.parent)
2845 |
2846 | getLayerEventListeners: (layer) =>
2847 |
2848 | listeners = _.map(layer._events, (evs, listener, c) ->
2849 | if not _.isArray(evs) then evs = [evs]
2850 |
2851 | {
2852 | listener: listener
2853 | events: _.map evs, (ev) ->
2854 | {
2855 | name: ev.fn.name
2856 | function: ev.fn.toString()
2857 | context: ev.context
2858 | once: ev.once
2859 | }
2860 | }
2861 | )
2862 |
2863 | return listeners
2864 |
2865 | getScreenshot: (element) =>
2866 |
2867 | foreignObject = new SVGShape
2868 | type: 'foreignObject'
2869 |
2870 | # element = document.getElementsByClassName('framerLayer DeviceComponentPort')[0]
2871 |
2872 | rect = element.getBoundingClientRect()
2873 | ctx = @specPanel.canvas.getContext('2d');
2874 |
2875 | data = "" +
2876 | '' +
2877 | '' +
2878 | element.innerHTML +
2879 | '
' +
2880 | ' ' +
2881 | ' '
2882 |
2883 | DOMURL = window.URL or window.webkitURL or window
2884 |
2885 | svg = new Blob([data], {type: 'image/svg+xml'})
2886 | url = DOMURL.createObjectURL(svg)
2887 | @specPanel.screenshotBox.value = url
2888 |
2889 |
2890 | # Find a non-standard Component that includes a Layer
2891 | getComponentFromLayer: (layer, names = []) =>
2892 | if not layer
2893 | return names.join(', ')
2894 |
2895 | if not _.includes(["Layer", "TextLayer", "ScrollComponent"], layer.constructor.name)
2896 | names.push(layer.constructor.name)
2897 |
2898 | @getComponentFromLayer(layer.parent, names)
2899 |
2900 |
2901 | # Delay focus by a small amount to prevent flashing
2902 | tryFocus: (event) =>
2903 | return if not @enabled
2904 |
2905 | @focusElement = event.target
2906 | do (event) =>
2907 | Utils.delay .05, =>
2908 | if @focusElement isnt event.target
2909 | return
2910 |
2911 | @focus()
2912 |
2913 | # Change focus to a new hovered or selected element
2914 | focus: =>
2915 | return if not @enabled
2916 |
2917 | @unfocus()
2918 |
2919 | # @selectedLayer ?= Framer.Device.screen
2920 | @hoveredLayer ?= Framer.Device.screen
2921 |
2922 | hoveredLayer = @hoveredLayer ? Framer.Device.screen
2923 | selectedLayer = @selectedLayer ? Framer.Device.screen
2924 |
2925 | if selectedLayer is hoveredLayer
2926 | hoveredLayer = Framer.Device.screen
2927 |
2928 | if hoveredLayer is selectedLayer
2929 | return
2930 |
2931 | @showDistances(selectedLayer, hoveredLayer)
2932 | @setPanelProperties(selectedLayer, hoveredLayer)
2933 |
2934 | unfocus: (event) =>
2935 | svgContext.removeAll()
2936 |
2937 |
2938 | # kickoff
2939 |
2940 | panel = document.createElement('div')
2941 | panel.id = 'pContainer'
2942 | viewC = document.getElementById('FramerContextRoot-Default')
2943 | Utils.delay 0, => viewC.appendChild(panel)
2944 |
2945 | secretBox = document.createElement('input')
2946 | document.body.appendChild(secretBox)
2947 |
2948 |
2949 | svgContext = new SVGContext
2950 |
2951 | exports.gotcha = gotcha = new Gotcha
2952 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/icon.png
--------------------------------------------------------------------------------
/icon@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/icon@1x.png
--------------------------------------------------------------------------------
/icon_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/icon_small.png
--------------------------------------------------------------------------------
/module.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Gotcha 👌",
3 | "description": "Press a key and your prototype becomes its own hand-off spec.",
4 | "author": "Steve Ruiz",
5 |
6 | "require": "require 'gotcha'",
7 | "install": "gotcha.coffee",
8 | "example": "example.coffee",
9 |
10 | "thumb": "icon_small.png"
11 | }
12 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Gotcha 👌
4 |
5 |
6 |
7 |
8 |
9 |
10 |
INTRODUCTION
11 |
12 | Press a key and your Framer prototype becomes its own live, self-documenting, developer-ready spec. When enabled, Gotcha provides precise information about your project's elements: from positions and colors to font styling and even component names and relationships. Best of all, Gotcha works seamlessly with Framer Cloud, so the links you send can work as specs, too.Check out the demo.
13 |
14 |
15 |
16 |
17 | ## Installation
18 |
19 | Download the **gotcha.coffee** file and drag it into your project's code editor.
20 |
21 | ## Usage
22 |
23 | In the Preview window...
24 | - Hover a Layer to get information about it
25 | - Tap a Layer to select it
26 |
27 | On your keyboard...
28 | - Press *`* or *<* to enable or disable Gotcha
29 | - Press */* or *>* to select or deselect a Layer
30 | - Press *.* to simulate a tap on a hovered Layer
31 | - Press \ to pause all animations
32 |
33 | In the spec panel...
34 | - click any field to copy the field's value
35 | - Slow down the global animation speed using the Speed slider
36 | - Change devices using the Devices dropdown
37 |
38 |
39 | ## Tips
40 |
41 | - Gotcha works in the Device Preview window. If your preview window is zoomed in, or if you can't see a device, you'll need to select the Toggle Device from the window's hamburger menu, or press Command + Shift + D.
42 | - Be sure that your Device Preview window is active before pressing any keys, lest you find your code full of random tick marks.
43 |
44 |
45 | ## Planned Features
46 |
47 | - Copy formatted CSS from the spec panel.
48 | - Give your components custom properties to display.
49 |
50 | ## Contact
51 |
52 | - Follow me at @steveruizok
53 |
--------------------------------------------------------------------------------
/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steveruizok/gotcha/d6735c69b79c483906bc997ca7a32431774a4760/splash.png
--------------------------------------------------------------------------------