├── .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 --------------------------------------------------------------------------------