├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── docs ├── .gitignore ├── 404.html ├── CNAME ├── Gemfile ├── Gemfile.lock ├── _config.yml ├── _includes │ └── head.html ├── _sass │ ├── abstracts │ │ ├── _modularscale.scss │ │ ├── _normalize.scss │ │ ├── mixins.scss │ │ ├── modularscale │ │ │ ├── _function.scss │ │ │ ├── _pow.scss │ │ │ ├── _respond.scss │ │ │ ├── _round-px.scss │ │ │ ├── _settings.scss │ │ │ ├── _sort.scss │ │ │ ├── _strip-units.scss │ │ │ ├── _sugar.scss │ │ │ ├── _target.scss │ │ │ └── _vars.scss │ │ ├── normalize │ │ │ ├── _import-now.scss │ │ │ ├── _normalize-mixin.scss │ │ │ ├── _variables.scss │ │ │ └── _vertical-rhythm.scss │ │ ├── typi │ │ │ ├── .bowerrc │ │ │ ├── .gitignore │ │ │ ├── Gulpfile.js │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── bower.json │ │ │ ├── css │ │ │ │ ├── baseline.css │ │ │ │ ├── bp.css │ │ │ │ ├── create-class.css │ │ │ │ ├── ms.css │ │ │ │ ├── no-ms.css │ │ │ │ ├── test.css │ │ │ │ ├── typi-base.css │ │ │ │ ├── typi-ms-vr.css │ │ │ │ ├── typi-ms.css │ │ │ │ └── typi.css │ │ │ ├── js │ │ │ │ └── main.js │ │ │ ├── package.json │ │ │ ├── scss │ │ │ │ ├── _private.scss │ │ │ │ ├── _public.scss │ │ │ │ ├── _typi.scss │ │ │ │ ├── private │ │ │ │ │ ├── baseline │ │ │ │ │ │ └── _baseline.scss │ │ │ │ │ ├── breakpoints │ │ │ │ │ │ ├── _breakpoints.scss │ │ │ │ │ │ └── _utils.scss │ │ │ │ │ ├── calc │ │ │ │ │ │ ├── _calc-font-size.scss │ │ │ │ │ │ ├── _calc-ms-size.scss │ │ │ │ │ │ └── _calc.scss │ │ │ │ │ ├── typefaces │ │ │ │ │ │ ├── _multiplier.scss │ │ │ │ │ │ └── _typefaces.scss │ │ │ │ │ ├── utils │ │ │ │ │ │ ├── _bases.scss │ │ │ │ │ │ ├── _converters.scss │ │ │ │ │ │ ├── _extender.scss │ │ │ │ │ │ ├── _lists.scss │ │ │ │ │ │ ├── _maps.scss │ │ │ │ │ │ └── _utils.scss │ │ │ │ │ └── write │ │ │ │ │ │ └── _write.scss │ │ │ │ └── public │ │ │ │ │ ├── rhythm │ │ │ │ │ ├── _baseline.scss │ │ │ │ │ └── _rhythm.scss │ │ │ │ │ ├── typefaces │ │ │ │ │ └── _typefaces.scss │ │ │ │ │ └── typi │ │ │ │ │ ├── _create-class.scss │ │ │ │ │ ├── _init.scss │ │ │ │ │ └── _typi.scss │ │ │ ├── test │ │ │ │ ├── automated │ │ │ │ │ ├── _baseline.scss │ │ │ │ │ ├── _breakpoints.scss │ │ │ │ │ ├── _calc-font-sizes.scss │ │ │ │ │ ├── _rhythm-typi-ms.scss │ │ │ │ │ ├── _rhythm.scss │ │ │ │ │ ├── _typefaces.scss │ │ │ │ │ ├── _typi-base.scss │ │ │ │ │ ├── _typi-ms.scss │ │ │ │ │ ├── _write-props.scss │ │ │ │ │ ├── extender.scss │ │ │ │ │ └── test.scss │ │ │ │ ├── manual │ │ │ │ │ ├── baseline.scss │ │ │ │ │ ├── bp.scss │ │ │ │ │ ├── create-class.scss │ │ │ │ │ ├── typi-base.scss │ │ │ │ │ ├── typi-ms-vr.scss │ │ │ │ │ ├── typi-ms.scss │ │ │ │ │ └── typi.scss │ │ │ │ └── test_sass.js │ │ │ └── yarn.lock │ │ └── variables.scss │ ├── base │ │ └── typography.scss │ ├── layout │ │ ├── home.scss │ │ └── onboarding.scss │ └── master.scss ├── android-chrome-192x192.png ├── android-chrome-256x256.png ├── apple-touch-icon.png ├── assets │ ├── chrome.png │ ├── chrome@2x.png │ ├── curved_element.svg │ ├── firefox.png │ ├── firefox@2x.png │ ├── how_it_works.svg │ ├── illustrated_browser.svg │ ├── illustrated_icon_notification.svg │ ├── illustrated_notification.svg │ ├── main_art.svg │ └── scare_hackers.svg ├── browserconfig.xml ├── css │ └── style.scss ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── mstile-150x150.png ├── onboarding.html ├── safari-pinned-tab.svg └── site.webmanifest ├── package-lock.json ├── package.json ├── scripts └── data_pipeline.ts ├── src ├── extension │ ├── LICENSE.txt │ ├── browserAction │ │ ├── about.html │ │ ├── confirmation.html │ │ ├── doesNotSupport2FA.html │ │ ├── feedback.html │ │ ├── icons │ │ │ ├── About.svg │ │ │ ├── Back.svg │ │ │ ├── Expand.svg │ │ │ ├── Feedback.svg │ │ │ ├── Menu.svg │ │ │ ├── Settings.svg │ │ │ ├── Tutorial.svg │ │ │ ├── Twitter.svg │ │ │ └── checkmark.svg │ │ ├── images │ │ │ ├── 2fa_available.png │ │ │ ├── 2fa_available@2x.png │ │ │ ├── 2fa_not_available.png │ │ │ ├── 2fa_not_available@2x.png │ │ │ ├── scare_hackers.svg │ │ │ ├── well_done.png │ │ │ ├── well_done.svg │ │ │ └── well_done@2x.png │ │ ├── menu.html │ │ ├── normalize.css │ │ ├── popup.html │ │ ├── previouslyEnabled2FA.html │ │ ├── styles2FAN.css │ │ └── supports2FA.html │ ├── icons │ │ ├── app_icon_128.png │ │ ├── app_icon_48.png │ │ ├── no_2fa.png │ │ ├── no_2fa@2x.png │ │ ├── no_2fa@3x.png │ │ ├── notification_icon_80.png │ │ ├── notification_icon_80@2x.png │ │ ├── yes_2fa.png │ │ ├── yes_2fa@2x.png │ │ └── yes_2fa@3x.png │ ├── manifest.json │ └── options │ │ ├── options.css │ │ └── options.html └── typescript │ ├── background │ ├── backgroundScript.ts │ ├── eventHandlers.ts │ ├── logic.ts │ └── notifications.ts │ ├── browserAction │ ├── confirmation.ts │ ├── doesNotSupport2FA.ts │ ├── popup.ts │ ├── previouslyEnabled2FA.ts │ └── supports2FA.ts │ ├── options │ └── options.ts │ ├── utils.ts │ └── utils │ ├── dataService.ts │ └── storageService.ts ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist/ 3 | node_modules/ 4 | releases/ 5 | .vscode/ 6 | src/extension/generatedJS/* 7 | scripts/output.json 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.8.1] - 2019-02-16 10 | ### Changed 11 | - Removes jQuery as a dependency [[65]]. 12 | - Updates dependencies. 13 | 14 | ## [0.8.0] - 2018-09-24 15 | ### Added 16 | - Update browser action menu to show a custom 17 | page if the user has indicated that they have 18 | already enabled 2FA. 19 | 20 | ### Changed 21 | - Update confirmation page shown after a user 22 | initially indicates that they enabled 2FA 23 | to improve text and include illustration. 24 | - Included data updates from twofactorauth.org. 25 | - Updated and cleaned underlying data [[62]]. 26 | 27 | ## [0.7.2] 28 | ### Fixed 29 | - Eliminates duplicate notifications when visiting 30 | a site that support 2FA. 31 | 32 | ## [0.7.1] 33 | ### Fixed 34 | - Small fixes and cleanup to html pages 35 | - Fixes the capitalization on the generated file 36 | from supports2fa.html to supports2FA.html 37 | 38 | ## [0.7.0] 39 | ### Added 40 | - Built out Home page to include nifty illustrations 41 | - Added favicon 42 | - Allow user to indicate that they have already enabled 2FA 43 | on the current site so that they stop getting notifications 44 | on that site. 45 | - Reduce notification frequency to at most 1/site/24 hours. 46 | 47 | ### Fixed 48 | - Fixed aggressive hyphening and typographic orphans 49 | 50 | ## [0.6.0] 51 | ### Added 52 | - Added button to the 2FA Notifier add-on in the Firefox store. 53 | - On-boarding page designed and built 54 | - Automatically opens `/onboarding.html` in a new tab after initial extension install. 55 | 56 | ### Fixed 57 | - Fixed the link for the menu top level back arrow [[67]] 58 | - Homepage centering 59 | - Onboarding page logo link to homepage 60 | 61 | ## [0.5.0] 62 | ### Changed 63 | - Cleaned data for Facebook so that notifications work as expected. 64 | 65 | ### Added 66 | - Added Google Analytics to the homepage of the website. 67 | - Added data entry for www.google.com. 68 | 69 | ### Fixed 70 | - Typos on the website homepage. 71 | 72 | ## [0.4.0] 73 | ### Changed 74 | - Updated notification icon to have an 8px padding to look better on Windows 10 notifications 75 | 76 | ## Fixed 77 | - Updated browser action popup to handle the case where a site supports 78 | 2FA, but does not have any documentation. 79 | 80 | ## [0.3.2] 81 | ### Added 82 | - Icons included in manifest to show up on the Chrome extension page 83 | 84 | ## [0.3.1] 85 | ### Changed 86 | - Updated action bar icons to be have more clarity 87 | - Updated notification icon to have more clarity 88 | 89 | ## [0.3.0] 90 | ### Changed 91 | - Improved cleanliness of data 92 | - Reworded button text from "Enable 2FA" to "Show 2FA Guide" 93 | 94 | ## [0.2.2] 95 | ### Added 96 | - Website now uses Jekyll instead of relying on 97 | GitHub pages to render the single MD file. 98 | 99 | ### Changed 100 | - Updated website to improve copy and link to the 101 | Chrome Web Store entry. 102 | - Show the correct site name on the "No 2FA" page 103 | 104 | ## [0.2.1] - 2018-04-28 105 | ### Changed 106 | - Fix version in the manifest file 107 | 108 | ## [0.2.0] - 2018-04-28 109 | ### Added 110 | - Initial release of browser action popup window 111 | - Browser action button icon changes depending on 112 | whether the domain in the current tab supports 2FA 113 | 114 | ### Changed 115 | - Only show the notification for the current tab 116 | - Only show the notification once for any given domain 117 | 118 | ## [0.1.0] - 2018-04-09 119 | Initial release 120 | 121 | [Unreleased]: https://github.com/conorgil/2fa-notifier/compare/v0.8.0...HEAD 122 | [0.8.0]: https://github.com/conorgil/2fa-notifier/compare/v0.7.2...v0.8.0 123 | [0.7.2]: https://github.com/conorgil/2fa-notifier/compare/v0.7.1...v0.7.2 124 | [0.7.1]: https://github.com/conorgil/2fa-notifier/compare/v0.7.0...v0.7.1 125 | [0.7.0]: https://github.com/conorgil/2fa-notifier/compare/v0.6.0...v0.7.0 126 | [0.6.0]: https://github.com/conorgil/2fa-notifier/compare/v0.5.0...v0.6.0 127 | [0.5.0]: https://github.com/conorgil/2fa-notifier/compare/v0.4.0...v0.5.0 128 | [0.4.0]: https://github.com/conorgil/2fa-notifier/compare/v0.3.2...v0.4.0 129 | [0.3.2]: https://github.com/conorgil/2fa-notifier/compare/v0.3.1...v0.3.2 130 | [0.3.1]: https://github.com/conorgil/2fa-notifier/compare/v0.3.0...v0.3.1 131 | [0.3.0]: https://github.com/conorgil/2fa-notifier/compare/v0.2.2...v0.3.0 132 | [0.2.2]: https://github.com/conorgil/2fa-notifier/compare/v0.2.1...v0.2.2 133 | [0.2.1]: https://github.com/conorgil/2fa-notifier/compare/v0.2.0...v0.2.1 134 | [0.2.0]: https://github.com/conorgil/2fa-notifier/compare/v0.1.0...v0.2.0 135 | [0.1.0]: https://github.com/conorgil/2fa-notifier/releases/tag/v0.1.0 136 | 137 | [62]: https://github.com/conorgil/2fa-notifier/issues/62 138 | [65]: https://github.com/conorgil/2fa-notifier/issues/65 139 | [67]: https://github.com/conorgil/2fa-notifier/issues/67 -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | This project uses Typescript because I hate 2 | normal JS and the silly runtime bugs that 3 | are so easily avoided at compile time. 4 | 5 | # Install dependencies 6 | `npm install` 7 | 8 | # Build 9 | To run the build a single time: 10 | 11 | `npm run build` 12 | 13 | To run the build and leave it watching for 14 | changes in the Typescript files and auto-build 15 | them: 16 | 17 | `npm run watch` 18 | 19 | # Load extension into Chrome 20 | After running the build: 21 | 22 | 1. Go to `chrome://extensions` 23 | 2. Making sure that developer mode is enabled 24 | by checking the switch in the top right 25 | 3. Click `LOAD UNPACKED` 26 | 4. Select the `src/extension` directory 27 | 28 | # Load extension in Firefox 29 | This is quite a bit easier because Firefox 30 | has a handy tool to do this for us. 31 | 32 | `npm run ff` -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Conor Gilsenan and Ray Gonzales 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [2FA Notifier](https://2fanotifier.org/) 2 | 3 | 2FA Notifier is a web extension that notifies users 4 | whether or not the sites they visit support 5 | two factor authentication (2FA). 6 | 7 | It utilizes the data from the [twofactorauth.org](https://twofactorauth.org) 8 | project to determine which sites do and do not support 2FA. 9 | 10 | Contributions are gladly accepted! Create a new issue or comment 11 | on an existing issue to figure out how we can collaborate. 12 | 13 | ## Community 14 | 15 | Inspired by [Jordan Sissel](https://gist.github.com/jordansissel/3088552) and others: 16 | 17 | * If a new user has a bad time, it's a bug. 18 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .sass-cache 3 | .jekyll-metadata 4 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | 18 | 19 |
20 |

404

21 | 22 |

Page not found :(

23 |

The requested page could not be found.

24 |
25 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | 2fanotifier.org -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Hello! This is where you manage which Jekyll version is used to run. 4 | # When you want to use a different version, change it below, save the 5 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: 6 | # 7 | # bundle exec jekyll serve 8 | # 9 | # This will help ensure the proper Jekyll version is running. 10 | # Happy Jekylling! 11 | gem "jekyll", "~> 3.8.1" 12 | 13 | # This is the default theme for new Jekyll sites. You may change this to anything you like. 14 | gem "minima", "~> 2.0" 15 | 16 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and 17 | # uncomment the line below. To upgrade, run `bundle update github-pages`. 18 | # gem "github-pages", group: :jekyll_plugins 19 | 20 | # If you have any plugins, put them here! 21 | group :jekyll_plugins do 22 | gem "jekyll-feed", "~> 0.6" 23 | end 24 | 25 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 26 | gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby] 27 | 28 | # Performance-booster for watching directories on Windows 29 | gem "wdm", "~> 0.1.0" if Gem.win_platform? 30 | 31 | -------------------------------------------------------------------------------- /docs/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.5.2) 5 | public_suffix (>= 2.0.2, < 4.0) 6 | colorator (1.1.0) 7 | concurrent-ruby (1.0.5) 8 | em-websocket (0.5.1) 9 | eventmachine (>= 0.12.9) 10 | http_parser.rb (~> 0.6.0) 11 | eventmachine (1.2.6) 12 | ffi (1.9.23) 13 | forwardable-extended (2.6.0) 14 | http_parser.rb (0.6.0) 15 | i18n (0.9.5) 16 | concurrent-ruby (~> 1.0) 17 | jekyll (3.8.1) 18 | addressable (~> 2.4) 19 | colorator (~> 1.0) 20 | em-websocket (~> 0.5) 21 | i18n (~> 0.7) 22 | jekyll-sass-converter (~> 1.0) 23 | jekyll-watch (~> 2.0) 24 | kramdown (~> 1.14) 25 | liquid (~> 4.0) 26 | mercenary (~> 0.3.3) 27 | pathutil (~> 0.9) 28 | rouge (>= 1.7, < 4) 29 | safe_yaml (~> 1.0) 30 | jekyll-feed (0.9.3) 31 | jekyll (~> 3.3) 32 | jekyll-sass-converter (1.5.2) 33 | sass (~> 3.4) 34 | jekyll-seo-tag (2.4.0) 35 | jekyll (~> 3.3) 36 | jekyll-watch (2.0.0) 37 | listen (~> 3.0) 38 | kramdown (1.16.2) 39 | liquid (4.0.0) 40 | listen (3.1.5) 41 | rb-fsevent (~> 0.9, >= 0.9.4) 42 | rb-inotify (~> 0.9, >= 0.9.7) 43 | ruby_dep (~> 1.2) 44 | mercenary (0.3.6) 45 | minima (2.4.0) 46 | jekyll (~> 3.5) 47 | jekyll-feed (~> 0.9) 48 | jekyll-seo-tag (~> 2.1) 49 | pathutil (0.16.1) 50 | forwardable-extended (~> 2.6) 51 | public_suffix (3.0.2) 52 | rb-fsevent (0.10.3) 53 | rb-inotify (0.9.10) 54 | ffi (>= 0.5.0, < 2) 55 | rouge (3.1.1) 56 | ruby_dep (1.5.0) 57 | safe_yaml (1.0.4) 58 | sass (3.5.6) 59 | sass-listen (~> 4.0.0) 60 | sass-listen (4.0.0) 61 | rb-fsevent (~> 0.9, >= 0.9.4) 62 | rb-inotify (~> 0.9, >= 0.9.7) 63 | 64 | PLATFORMS 65 | ruby 66 | 67 | DEPENDENCIES 68 | jekyll (~> 3.8.1) 69 | jekyll-feed (~> 0.6) 70 | minima (~> 2.0) 71 | tzinfo-data 72 | 73 | BUNDLED WITH 74 | 1.16.1 75 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Jekyll! 2 | # 3 | # This config file is meant for settings that affect your whole blog, values 4 | # which you are expected to set up once and rarely edit after that. If you find 5 | # yourself editing this file very often, consider using Jekyll's data files 6 | # feature for the data you need to update frequently. 7 | # 8 | # For technical reasons, this file is *NOT* reloaded automatically when you use 9 | # 'bundle exec jekyll serve'. If you change this file, please restart the server process. 10 | 11 | # Site settings 12 | # These are used to personalize your new site. If you look in the HTML files, 13 | # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. 14 | # You can create any custom variable you would like, and they will be accessible 15 | # in the templates via {{ site.myvariable }}. 16 | title: 2FA Notifier 17 | email: feedback@2fanotifier.org 18 | description: >- # this means to ignore newlines until "baseurl:" 19 | 2FA Notifier is a web extension that notifies you when the sites 20 | you visit support two factor authentication (2FA) and tells you 21 | how to enable it to better protect your accounts from hackers. 22 | baseurl: "" # the subpath of your site, e.g. /blog 23 | url: "https://2fanotifier.org/" # the base hostname & protocol for your site, e.g. http://example.com 24 | twitter_username: 25 | github_username: conorgil 26 | 27 | conor_personal_url: https://twitter.com/conorgil 28 | ray_personal_url: https://ray-gonzales.com 29 | 30 | # Build settings 31 | markdown: kramdown 32 | theme: minima 33 | plugins: 34 | - jekyll-feed 35 | 36 | sass: 37 | style: compressed 38 | 39 | # Exclude from processing. 40 | # The following items will not be processed, by default. Create a custom list 41 | # to override the default setting. 42 | # exclude: 43 | # - Gemfile 44 | # - Gemfile.lock 45 | # - node_modules 46 | # - vendor/bundle/ 47 | # - vendor/cache/ 48 | # - vendor/gems/ 49 | # - vendor/ruby/ 50 | -------------------------------------------------------------------------------- /docs/_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/_modularscale.scss: -------------------------------------------------------------------------------- 1 | // Defaults and variables 2 | @import 'modularscale/vars'; 3 | 4 | // Core functions 5 | @import 'modularscale/settings'; 6 | @import 'modularscale/pow'; 7 | @import 'modularscale/strip-units'; 8 | @import 'modularscale/sort'; 9 | @import 'modularscale/round-px'; 10 | @import 'modularscale/target'; 11 | @import 'modularscale/function'; 12 | 13 | // Mixins 14 | @import 'modularscale/respond'; 15 | 16 | // Syntax sugar 17 | @import 'modularscale/sugar'; -------------------------------------------------------------------------------- /docs/_sass/abstracts/_normalize.scss: -------------------------------------------------------------------------------- 1 | @import 'normalize/variables'; 2 | @import 'normalize/vertical-rhythm'; 3 | @import 'normalize/normalize-mixin'; 4 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/mixins.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | // breakpoints mixin 4 | @mixin respond-to($breakpoint) { 5 | @if map-has-key($breakpoints, $breakpoint) { 6 | $value: map-get($breakpoints, $breakpoint); 7 | 8 | @media screen and (min-width: $value) { 9 | @content; 10 | } 11 | } 12 | 13 | //@warn "Unknown `#{$breakpoint}` in $breakpoints"; 14 | } 15 | 16 | @mixin max-respond-to($breakpoint) { 17 | @if map-has-key($breakpoints, $breakpoint) { 18 | $value: map-get($breakpoints, $breakpoint); 19 | 20 | @media screen and (max-width: $value) { 21 | @content; 22 | } 23 | } 24 | 25 | //@warn "Unknown `#{$breakpoint}` in $breakpoints"; 26 | } 27 | 28 | @mixin primary-grid-layout(){ 29 | display: grid; 30 | grid-template-columns: repeat(2, [col-start] 1fr); 31 | grid-column-gap: 2rem; 32 | grid-row-gap: 1rem; 33 | justify-items: center; 34 | align-items: center; 35 | 36 | @include max-respond-to(m){ 37 | grid-template-columns: 1fr; 38 | grid-auto-rows: auto auto; 39 | } 40 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/modularscale/_function.scss: -------------------------------------------------------------------------------- 1 | @function ms-function($v: 0, $base: false, $ratio: false, $thread: false, $settings: $modularscale) { 2 | 3 | // Parse settings 4 | $ms-settings: ms-settings($base,$ratio,$thread,$settings); 5 | $base: nth($ms-settings, 1); 6 | $ratio: nth($ms-settings, 2); 7 | 8 | // Render target values from settings. 9 | @if unit($ratio) != '' { 10 | $ratio: ms-target($ratio,$base) 11 | } 12 | 13 | // Fast calc if not multi stranded 14 | @if(length($base) == 1) { 15 | @return ms-round-px(ms-pow($ratio, $v) * $base); 16 | } 17 | 18 | // Create new base array 19 | $ms-bases: nth($base,1); 20 | 21 | // Normalize base values 22 | @for $i from 2 through length($base) { 23 | // initial base value 24 | $ms-base: nth($base,$i); 25 | // If the base is bigger than the main base 26 | @if($ms-base > nth($base,1)) { 27 | // divide the value until it aligns with main base. 28 | @while($ms-base > nth($base,1)) { 29 | $ms-base: $ms-base / $ratio; 30 | } 31 | $ms-base: $ms-base * $ratio; 32 | } 33 | // If the base is smaller than the main base. 34 | @elseif ($ms-base < nth($base,1)) { 35 | // pump up the value until it aligns with main base. 36 | @while $ms-base < nth($base,1) { 37 | $ms-base: $ms-base * $ratio; 38 | } 39 | } 40 | // Push into new array 41 | $ms-bases: append($ms-bases,$ms-base); 42 | } 43 | 44 | // Sort array from smallest to largest. 45 | $ms-bases: ms-sort($ms-bases); 46 | 47 | // Find step to use in calculation 48 | $vtep: floor($v / length($ms-bases)); 49 | // Find base to use in calculation 50 | $ms-base: round(($v / length($ms-bases) - $vtep) * length($ms-bases)) + 1; 51 | 52 | @return ms-round-px(ms-pow($ratio, $vtep) * nth($ms-bases,$ms-base)); 53 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/modularscale/_pow.scss: -------------------------------------------------------------------------------- 1 | // Sass does not have native pow() support so this needs to be added. 2 | // Compass and other libs implement this more extensively. 3 | // In order to keep this simple, use those when they are avalible. 4 | // Issue for pow() support in Sass: https://github.com/sass/sass/issues/684 5 | 6 | @function ms-pow($b,$e) { 7 | 8 | // Return 1 if exponent is 0 9 | @if $e == 0 { 10 | @return 1; 11 | } 12 | 13 | // If pow() exists (compass or mathsass) use that. 14 | @if function-exists('pow') { 15 | @return pow($b,$e); 16 | } 17 | 18 | // This does not support non-integer exponents, 19 | // Check and return an error if a non-integer exponent is passed. 20 | @if (floor($e) != $e) { 21 | @error 'Non-integer values are not supported in modularscale by default. Try using mathsass in your project to add non-integer scale support. https://github.com/terkel/mathsass' 22 | } 23 | 24 | // Seed the return. 25 | $ms-return: $b; 26 | 27 | // Multiply or divide by the specified number of times. 28 | @if $e > 0 { 29 | @for $i from 1 to $e { 30 | $ms-return: $ms-return * $b; 31 | } 32 | } 33 | @if $e < 0 { 34 | @for $i from $e through 0 { 35 | $ms-return: $ms-return / $b; 36 | } 37 | } 38 | @return $ms-return; 39 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/modularscale/_respond.scss: -------------------------------------------------------------------------------- 1 | // Generate calc() function 2 | // based on Mike Riethmuller's Precise control over responsive typography 3 | // http://madebymike.com.au/writing/precise-control-responsive-typography/ 4 | @function ms-fluid($val1: 1em, $val2: 1em, $break1: 0, $break2: 0) { 5 | $diff: ms-unitless($val2) - ms-unitless($val1); 6 | 7 | // v1 + (v2 - v1) * ( (100vw - b1) / b2 - b1 ) 8 | @return calc( #{$val1} + #{ms-unitless($val2) - ms-unitless($val1)} * ( ( 100vw - #{$break1}) / #{ms-unitless($break2) - ms-unitless($break1)} ) ); 9 | } 10 | 11 | // Main responsive mixin 12 | @mixin ms-respond($prop, $val, $map: $modularscale) { 13 | $base: $ms-base; 14 | $ratio: $ms-ratio; 15 | 16 | $first-write: true; 17 | $last-break: null; 18 | 19 | // loop through all settings with a breakpoint type value 20 | @each $v, $s in $map { 21 | @if type-of($v) == number { 22 | @if unit($v) != '' { 23 | 24 | // Write out the first value without a media query. 25 | @if $first-write { 26 | #{$prop}: ms-function($val, $thread: $v, $settings: $map); 27 | 28 | // Not the first write anymore, reset to false to move on. 29 | $first-write: false; 30 | $last-break: $v; 31 | } 32 | 33 | // Write intermediate breakpoints. 34 | @else { 35 | @media (min-width: $last-break) and (max-width: $v) { 36 | $val1: ms-function($val, $thread: $last-break, $settings: $map); 37 | $val2: ms-function($val, $thread: $v, $settings: $map); 38 | #{$prop}: ms-fluid($val1,$val2,$last-break,$v); 39 | } 40 | $last-break: $v; 41 | } 42 | } 43 | } 44 | } 45 | 46 | // Write the last breakpoint. 47 | @if $last-break { 48 | @media (min-width: $last-break) { 49 | #{$prop}: ms-function($val, $thread: $last-break, $settings: $map); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/modularscale/_round-px.scss: -------------------------------------------------------------------------------- 1 | // No reason to have decimal pixel values, 2 | // normalize them to whole numbers. 3 | 4 | @function ms-round-px($r) { 5 | @if unit($r) == 'px' { 6 | @return round($r); 7 | } 8 | @return $r; 9 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/modularscale/_settings.scss: -------------------------------------------------------------------------------- 1 | // Parse settings starting with defaults. 2 | // Settings should cascade down like you would expect in CSS. 3 | // More specific overrides previous settings. 4 | 5 | @function ms-settings($b: false, $r: false, $t: false, $m: $modularscale) { 6 | $base: $ms-base; 7 | $ratio: $ms-ratio; 8 | $thread: map-get($m, $t); 9 | 10 | // Override with user settings 11 | @if map-get($m, base) { 12 | $base: map-get($m, base); 13 | } 14 | @if map-get($m, ratio) { 15 | $ratio: map-get($m, ratio); 16 | } 17 | 18 | // Override with thread settings 19 | @if $thread { 20 | @if map-get($thread, base) { 21 | $base: map-get($thread, base); 22 | } 23 | @if map-get($thread, ratio) { 24 | $ratio: map-get($thread, ratio); 25 | } 26 | } 27 | 28 | // Override with inline settings 29 | @if $b { 30 | $base: $b; 31 | } 32 | @if $r { 33 | $ratio: $r; 34 | } 35 | 36 | @return $base $ratio; 37 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/modularscale/_sort.scss: -------------------------------------------------------------------------------- 1 | // Basic list sorting 2 | // Would like to replace with http://sassmeister.com/gist/30e4863bd03ce0e1617c 3 | // Unfortunately libsass has a bug with passing arguments into the min() funciton. 4 | 5 | @function ms-sort($l) { 6 | 7 | // loop until the list is confirmed to be sorted 8 | $sorted: false; 9 | @while $sorted == false { 10 | 11 | // Start with the assumption that the lists are sorted. 12 | $sorted: true; 13 | 14 | // Loop through the list, checking each value with the one next to it. 15 | // Swap the values if they need to be swapped. 16 | // Not super fast but simple and modular scale doesn't lean hard on sorting. 17 | @for $i from 2 through length($l) { 18 | $n1: nth($l,$i - 1); 19 | $n2: nth($l,$i); 20 | 21 | // If the first value is greater than the 2nd, swap them. 22 | @if $n1 > $n2 { 23 | $l: set-nth($l, $i, $n1); 24 | $l: set-nth($l, $i - 1, $n2); 25 | 26 | // The list isn't sorted and needs to be looped through again. 27 | $sorted: false; 28 | } 29 | } 30 | } 31 | 32 | // Return the sorted list. 33 | @return $l; 34 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/modularscale/_strip-units.scss: -------------------------------------------------------------------------------- 1 | // Stripping units is not a best practice 2 | // This function should not be used elsewhere 3 | // It is used here because calc() doesn't do unit logic 4 | // AND target ratios use units as a hack to get a number. 5 | @function ms-unitless($val) { 6 | @return ($val / ($val - $val + 1)); 7 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/modularscale/_sugar.scss: -------------------------------------------------------------------------------- 1 | // To attempt to avoid conflicts with other libraries 2 | // all funcitons are namespaced with `ms-`. 3 | // However, to increase usability, a shorthand function is included here. 4 | 5 | @function ms($v: 0, $base: false, $ratio: false, $thread: false, $settings: $modularscale) { 6 | @return ms-function($v, $base, $ratio, $thread, $settings); 7 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/modularscale/_target.scss: -------------------------------------------------------------------------------- 1 | // Convert number string to number 2 | @function ms-to-num($n) { 3 | $l: str-length($n); 4 | $r: 0; 5 | $m: str-index($n,'.'); 6 | @if $m == null { 7 | $m: $l + 1; 8 | } 9 | // Loop through digits and convert to numbers 10 | @for $i from 1 through $l { 11 | $v: str-slice($n,$i,$i); 12 | @if $v == '1' { $v: 1; } 13 | @elseif $v == '2' { $v: 2; } 14 | @elseif $v == '3' { $v: 3; } 15 | @elseif $v == '4' { $v: 4; } 16 | @elseif $v == '5' { $v: 5; } 17 | @elseif $v == '6' { $v: 6; } 18 | @elseif $v == '7' { $v: 7; } 19 | @elseif $v == '8' { $v: 8; } 20 | @elseif $v == '9' { $v: 9; } 21 | @elseif $v == '0' { $v: 0; } 22 | @else { $v: null; } 23 | @if $v != null { 24 | $m: $m - 1; 25 | $r: $r + ms-pow(10,$m - 1) * $v; 26 | } @else { 27 | $l: $l - 1; 28 | } 29 | } 30 | @return $r; 31 | } 32 | 33 | // Find a ratio based on a target value 34 | @function ms-target($t,$b) { 35 | // Convert to string 36 | $t: $t + ''; 37 | // Remove base units to calulate ratio 38 | $b: ms-unitless(nth($b,1)); 39 | // Find where 'at' is in the string 40 | $at: str-index($t,'at'); 41 | 42 | // Slice the value and target out 43 | // and convert strings to numbers 44 | $v: ms-to-num(str-slice($t,0,$at - 1)); 45 | $t: ms-to-num(str-slice($t,$at + 2)); 46 | 47 | // Solve the modular scale function for the ratio. 48 | @return ms-pow(($v/$b),(1/$t)); 49 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/modularscale/_vars.scss: -------------------------------------------------------------------------------- 1 | // Ratios 2 | $double-octave : 4 ; 3 | $pi : 3.14159265359 ; 4 | $major-twelfth : 3 ; 5 | $major-eleventh : 2.666666667 ; 6 | $major-tenth : 2.5 ; 7 | $octave : 2 ; 8 | $major-seventh : 1.875 ; 9 | $minor-seventh : 1.777777778 ; 10 | $major-sixth : 1.666666667 ; 11 | $phi : 1.618034 ; 12 | $golden : $phi ; 13 | $minor-sixth : 1.6 ; 14 | $fifth : 1.5 ; 15 | $augmented-fourth : 1.41421 ; 16 | $fourth : 1.333333333 ; 17 | $major-third : 1.25 ; 18 | $minor-third : 1.2 ; 19 | $major-second : 1.125 ; 20 | $minor-second : 1.066666667 ; 21 | 22 | // Base config 23 | $ms-base : 1em !default; 24 | $ms-ratio : $fifth !default; 25 | $modularscale : () !default; -------------------------------------------------------------------------------- /docs/_sass/abstracts/normalize/_import-now.scss: -------------------------------------------------------------------------------- 1 | // Import Now 2 | // 3 | // If you import this module directly, it will immediately output all the CSS 4 | // needed to normalize default HTML elements across all browsers. 5 | // 6 | // ``` 7 | // @import "normalize/import-now"; 8 | // ``` 9 | 10 | @import '../normalize'; 11 | @include normalize(); 12 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/normalize/_variables.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Variables 3 | // 4 | // You can override the default values by setting the variables in your Sass 5 | // before importing the normalize-scss library. 6 | 7 | // The font size set on the root html element. 8 | $base-font-size: 16px !default; 9 | 10 | // The base line height determines the basic unit of vertical rhythm. 11 | $base-line-height: 24px !default; 12 | 13 | // The length unit in which to output vertical rhythm values. 14 | // Supported values: px, em, rem. 15 | $base-unit: 'em' !default; 16 | 17 | // The default font family. 18 | $base-font-family: null !default; 19 | 20 | // The font sizes for h1-h6. 21 | $h1-font-size: 2 * $base-font-size !default; 22 | $h2-font-size: 1.5 * $base-font-size !default; 23 | $h3-font-size: 1.17 * $base-font-size !default; 24 | $h4-font-size: 1 * $base-font-size !default; 25 | $h5-font-size: 0.83 * $base-font-size !default; 26 | $h6-font-size: 0.67 * $base-font-size !default; 27 | 28 | // The amount lists and blockquotes are indented. 29 | $indent-amount: 40px !default; 30 | 31 | // The following variable controls whether normalize-scss will output 32 | // font-sizes, line-heights and block-level top/bottom margins that form a basic 33 | // vertical rhythm on the page, which differs from the original Normalize.css. 34 | // However, changing any of the variables above will cause 35 | // $normalize-vertical-rhythm to be automatically set to true. 36 | $normalize-vertical-rhythm: false !default; 37 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/normalize/_vertical-rhythm.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Vertical Rhythm 3 | // 4 | // This is the minimal amount of code needed to create vertical rhythm in our 5 | // CSS. If you are looking for a robust solution, look at the excellent Typey 6 | // library. @see https://github.com/jptaranto/typey 7 | 8 | @function normalize-rhythm($value, $relative-to: $base-font-size, $unit: $base-unit) { 9 | @if unit($value) != px { 10 | @error "The normalize vertical-rhythm module only supports px inputs. The typey library is better."; 11 | } 12 | @if $unit == rem { 13 | @return ($value / $base-font-size) * 1rem; 14 | } 15 | @else if $unit == em { 16 | @return ($value / $relative-to) * 1em; 17 | } 18 | @else { // $unit == px 19 | @return $value; 20 | } 21 | } 22 | 23 | @mixin normalize-font-size($value, $relative-to: $base-font-size) { 24 | @if unit($value) != 'px' { 25 | @error "normalize-font-size() only supports px inputs. The typey library is better."; 26 | } 27 | font-size: normalize-rhythm($value, $relative-to); 28 | } 29 | 30 | @mixin normalize-rhythm($property, $values, $relative-to: $base-font-size) { 31 | $value-list: $values; 32 | $sep: space; 33 | @if type-of($values) == 'list' { 34 | $sep: list-separator($values); 35 | } 36 | @else { 37 | $value-list: append((), $values); 38 | } 39 | 40 | $normalized-values: (); 41 | @each $value in $value-list { 42 | @if unitless($value) and $value != 0 { 43 | $value: $value * normalize-rhythm($base-line-height, $relative-to); 44 | } 45 | $normalized-values: append($normalized-values, $value, $sep); 46 | } 47 | #{$property}: $normalized-values; 48 | } 49 | 50 | @mixin normalize-margin($values, $relative-to: $base-font-size) { 51 | @include normalize-rhythm(margin, $values, $relative-to); 52 | } 53 | 54 | @mixin normalize-line-height($font-size, $min-line-padding: 2px) { 55 | $lines: ceil($font-size / $base-line-height); 56 | // If lines are cramped include some extra leading. 57 | @if ($lines * $base-line-height - $font-size) < ($min-line-padding * 2) { 58 | $lines: $lines + 1; 59 | } 60 | @include normalize-rhythm(line-height, $lines, $font-size); 61 | } 62 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/.gitignore: -------------------------------------------------------------------------------- 1 | # osx noise 2 | .DS_Store 3 | profile 4 | 5 | #ignore sass caches 6 | .sass-cache 7 | 8 | #ignore sublime projects 9 | .sublime-project 10 | .sublime-workspace 11 | 12 | #ignore packages 13 | node_modules 14 | bower_components 15 | *.log 16 | 17 | sassdoc -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/Gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var sass = require('gulp-sass'); 3 | var mocha = require('gulp-mocha'); 4 | var plumber = require('gulp-plumber'); 5 | var notify = require('gulp-notify'); 6 | var sassdoc = require('sassdoc'); 7 | function customPlumber(errTitle) { 8 | return plumber({ 9 | errorHandler: notify.onError({ 10 | // Customizing error title 11 | title: errTitle || 'Error running Sass', 12 | message: 'Error: <%= error %>', 13 | }) 14 | }); 15 | }; 16 | 17 | gulp.task('sass', function() { 18 | gulp.src([ 19 | 'scss/**/*.scss', 20 | 'test/manual/**/*.scss', 21 | ]) 22 | .pipe(customPlumber()) 23 | .pipe(sass({ 24 | includePaths: [ 25 | './bower_components/', 26 | './node_modules/', 27 | './' 28 | ], 29 | })) 30 | .pipe(gulp.dest('css')); 31 | }); 32 | 33 | gulp.task('sassdoc', function () { 34 | return gulp.src('./scss/**/*.scss') 35 | .pipe(sassdoc({ 36 | groups: { 37 | config: 'Config', 38 | core: 'Core', 39 | 'helpers-typefaces': "Helpers - typefaces", 40 | utils: 'Utilities' 41 | } 42 | })); 43 | }); 44 | 45 | gulp.task('mocha', function() { 46 | gulp.src('test/**/*.js') 47 | .pipe(customPlumber()) 48 | .pipe(mocha({reporter: 'nyan'})) 49 | }); 50 | 51 | gulp.task('watch', ['mocha'], function() { 52 | gulp.watch('test/automated/**/*.scss', ['mocha']); 53 | gulp.watch('scss/**/*.scss', ['sass', 'mocha']); 54 | gulp.watch('test/manual/**/*.scss', ['sass', 'mocha']); 55 | }); 56 | 57 | gulp.task('default', ['watch', 'sassdoc', 'sass']); 58 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Zell Liew 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typi", 3 | "version": "3.0.0-alpha", 4 | "authors": [ 5 | "Zell Liew " 6 | ], 7 | "description": "Making responsive typography easy", 8 | "main": "scss/_typi.scss", 9 | "moduleType": [], 10 | "keywords": [ 11 | "responsive", 12 | "typography", 13 | "type" 14 | ], 15 | "license": "MIT", 16 | "homepage": "", 17 | "ignore": [ 18 | "**/.*", 19 | "node_modules", 20 | "bower_components", 21 | "app/bower_components", 22 | "test", 23 | "tests" 24 | ], 25 | "devDependencies": { 26 | "mappy-breakpoints": "^0.2.3", 27 | "compass-breakpoint": "breakpoint-sass#~2.6.1", 28 | "modular-scale": "~2.1.1", 29 | "sass-mq": "^4.0.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/css/baseline.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 100%; 3 | line-height: 1.5; } 4 | @media all and (min-width: 400px) { 5 | html { 6 | font-size: 112.5%; } } 7 | @media all and (min-width: 1000px) { 8 | html { 9 | font-size: 125%; } } 10 | 11 | h1 { 12 | margin: 0; 13 | margin-top: 3rem; 14 | font-size: 1.5rem; 15 | line-height: 1.3; 16 | padding-top: 0.48em; 17 | margin-bottom: -0.48em; } 18 | @media all and (min-width: 400px) { 19 | h1 { 20 | font-size: 1.625rem; 21 | padding-top: 0.52em; 22 | margin-bottom: -0.52em; } } 23 | @media all and (min-width: 1000px) { 24 | h1 { 25 | font-size: 1.875rem; 26 | padding-top: 0.6em; 27 | margin-bottom: -0.6em; } } 28 | 29 | p { 30 | margin: 0; 31 | margin-top: 1.5rem; 32 | padding-top: 0.42em; 33 | margin-bottom: -0.42em; } 34 | @media all and (min-width: ) { 35 | p { 36 | padding-top: 0.4725em; 37 | margin-bottom: -0.4725em; } } 38 | @media all and (min-width: ) { 39 | p { 40 | padding-top: 0.525em; 41 | margin-bottom: -0.525em; } } 42 | 43 | body { 44 | font-family: Helvetica; 45 | max-width: 600px; 46 | margin: 0 auto; 47 | background-image: linear-gradient(to bottom, rgba(0, 0, 255, 0.25) 1px, transparent 0, transparent 24px); 48 | background-size: 100% 24px; 49 | background-position: 0 0; } 50 | @media all and (min-width: 400px) { 51 | body { 52 | background-image: linear-gradient(to bottom, rgba(0, 0, 255, 0.25) 1px, transparent 0, transparent 27px); 53 | background-size: 100% 27px; } } 54 | @media all and (min-width: 1000px) { 55 | body { 56 | background-image: linear-gradient(to bottom, rgba(0, 0, 255, 0.25) 1px, transparent 0, transparent 30px); 57 | background-size: 100% 30px; } } 58 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/css/bp.css: -------------------------------------------------------------------------------- 1 | /* Test with normal breakpoints */ 2 | .h1 { 3 | font-size: 1.33333rem; 4 | line-height: 1.3; } 5 | @media all and (min-width: 600px) { 6 | .h1 { 7 | font-size: 1.66667rem; } } 8 | @media all and (min-width: 800px) { 9 | .h1 { 10 | font-size: 3rem; } } 11 | 12 | /* Test with mappy bp */ 13 | .h1 { 14 | font-size: 1.33333rem; 15 | line-height: 1.3; } 16 | @media all and (min-width: 37.5em) { 17 | .h1 { 18 | font-size: 1.66667rem; } } 19 | @media all and (min-width: 50em) { 20 | .h1 { 21 | font-size: 3rem; } } 22 | 23 | /* Test with breakpoint-sass */ 24 | .h1 { 25 | font-size: 1.33333rem; 26 | line-height: 1.3; } 27 | @media (min-width: 37.5em) { 28 | .h1 { 29 | font-size: 1.66667rem; } } 30 | @media (min-width: 50em) { 31 | .h1 { 32 | font-size: 3rem; } } 33 | 34 | /* Test with sass-mq */ 35 | .h1 { 36 | font-size: 1.33333rem; 37 | line-height: 1.3; } 38 | @media (min-width: 37.5em) { 39 | .h1 { 40 | font-size: 1.66667rem; } } 41 | @media (min-width: 50em) { 42 | .h1 { 43 | font-size: 3rem; } } 44 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/css/create-class.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Should create two classes, .base and .h1 3 | */ 4 | .base { 5 | font-size: 100%; 6 | line-height: 1.5; } 7 | 8 | .h1 { 9 | font-size: 6.8541rem; 10 | line-height: 1.3; } 11 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/css/ms.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Iterates through both $typi-ms and $font-map 3 | * ------------------- 4 | * null should produce: 1.2 * 1.2 = 1.44 5 | * small should produce 1.618 * 1.618 * 1.618 = 4.2358 6 | * med should produce 1.2 * 1.5 * 1.5 * 1.5 = 4.05 7 | * large should produce 1.5 8 | * huge should produce 1.5 9 | * =================== 10 | */ 11 | /** 12 | * Iterates through both $typi-ms and $font-map 13 | * ------------------- 14 | * null should produce: 1.2 * 1.2 = 1.44 15 | * small should produce 1.618 * 1.618 = 2.617924 16 | * med should produce 1.618 * 1.618 * 1.618 = 4.2358 17 | * =================== 18 | */ 19 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/css/no-ms.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/docs/_sass/abstracts/typi/css/no-ms.css -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/css/test.css: -------------------------------------------------------------------------------- 1 | /* Test with normal breakpoints */ 2 | .h1 { 3 | font-size: 1.33333rem; 4 | line-height: 1.3; } 5 | @media all and (min-width: 600px) { 6 | .h1 { 7 | font-size: 2rem; } } 8 | @media all and (min-width: 800px) { 9 | .h1 { 10 | font-size: 3rem; } } 11 | 12 | /* Test with mappy bp */ 13 | .h1 { 14 | font-size: 1.33333rem; 15 | line-height: 1.3; } 16 | @media all and (min-width: 37.5em) { 17 | .h1 { 18 | font-size: 2rem; } } 19 | @media all and (min-width: 50em) { 20 | .h1 { 21 | font-size: 3rem; } } 22 | 23 | /* Test with breakpoint-sass */ 24 | .h1 { 25 | font-size: 1.33333rem; 26 | line-height: 1.3; } 27 | @media (min-width: 37.5em) { 28 | .h1 { 29 | font-size: 2rem; } } 30 | @media (min-width: 50em) { 31 | .h1 { 32 | font-size: 3rem; } } 33 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/css/typi-base.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests Output of Typi-init 3 | * ------------------------- 4 | * Should output: 5 | * - no breakpoint: font-size: 112.5%; 6 | * line-height: 1.5; 7 | * - small breakpoint: font-size: 125%; 8 | * - med breakpoint: font-size: 137.5%; 9 | * - large breakpoint: font-size: 150%; 10 | * line-height: 1.6; 11 | */ 12 | html { 13 | font-size: 112.5%; 14 | line-height: 1.5; } 15 | @media all and (min-width: small) { 16 | html { 17 | font-size: 125%; } } 18 | @media all and (min-width: med) { 19 | html { 20 | font-size: 137.5%; } } 21 | @media all and (min-width: large) { 22 | html { 23 | font-size: 150%; 24 | line-height: 1.6; } } 25 | 26 | /** 27 | * Tests selector of typi-base 28 | * --------------------------- 29 | * Selector should match $selector 30 | */ 31 | body { 32 | font-size: 112.5%; 33 | line-height: 1.5; } 34 | @media all and (min-width: small) { 35 | body { 36 | font-size: 125%; } } 37 | @media all and (min-width: med) { 38 | body { 39 | font-size: 137.5%; } } 40 | @media all and (min-width: large) { 41 | body { 42 | font-size: 150%; 43 | line-height: 1.6; } } 44 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/css/typi-ms-vr.css: -------------------------------------------------------------------------------- 1 | .testing { 2 | margin-top: 2.5em; } 3 | @media all and (min-width: small) { 4 | .testing { 5 | margin-top: 2.30769em; } } 6 | @media all and (min-width: med) { 7 | .testing { 8 | margin-top: 1.09329em; } } 9 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/css/typi-ms.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Inits. 3 | * ------------------- 4 | * Should call typi-base 5 | * Should only produce base and small breakpoints 6 | * Should check $typi and $typi-ms maps for breakpoints. (comment any breakpoint to see warning) 7 | */ 8 | html { 9 | font-size: 112.5%; 10 | line-height: 1.5; } 11 | @media all and (min-width: small) { 12 | html { 13 | font-size: 125%; } } 14 | 15 | /** 16 | * Creates sizes in rem 17 | * ------------------- 18 | * null should produce: 1rem 19 | * small should produce: 1.2rem 20 | * med should produce: 1.44rem http://www.modularscale.com/?1&em&1.2,1.5&web&text 21 | * large should produce: 1.38889rem http://www.modularscale.com/?1,2&em&1.2&web&text 22 | * huge should produce: 2.592rem http://www.modularscale.com/?1.5&em&1.2,1.5&web&text 23 | * =================== 24 | */ 25 | h2 { 26 | font-size: 1rem; 27 | line-height: 1.3; } 28 | @media all and (min-width: small) { 29 | h2 { 30 | font-size: 1.2rem; } } 31 | @media all and (min-width: med) { 32 | h2 { 33 | font-size: 1.44rem; } } 34 | @media all and (min-width: large) { 35 | h2 { 36 | font-size: 1.38889rem; } } 37 | @media all and (min-width: huge) { 38 | h2 { 39 | font-size: 2.592rem; } } 40 | 41 | /** 42 | * Creates sizes in em 43 | * ------------------- 44 | * same as above, but in em 45 | * =================== 46 | */ 47 | h2 { 48 | font-size: 1em; 49 | line-height: 1.3; } 50 | @media all and (min-width: small) { 51 | h2 { 52 | font-size: 1.2em; } } 53 | @media all and (min-width: med) { 54 | h2 { 55 | font-size: 1.44em; } } 56 | @media all and (min-width: large) { 57 | h2 { 58 | font-size: 1.38889em; } } 59 | @media all and (min-width: huge) { 60 | h2 { 61 | font-size: 2.592em; } } 62 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/css/typi.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests Output of Typi mixin 3 | * ------------------------- 4 | * Should output: 5 | * - no breakpoint: font-size: 1.33333rem; (24/18) 6 | * line-height: 1.3; 7 | * - med breakpoint: font-size: 1.666666667rem; (30/18) 8 | * - large breakpoint: font-size: 3rem; 9 | */ 10 | .rem { 11 | font-size: 1.33333rem; 12 | line-height: 1.3; } 13 | @media all and (min-width: med) { 14 | .rem { 15 | font-size: 1.66667rem; } } 16 | @media all and (min-width: large) { 17 | .rem { 18 | font-size: 3rem; } } 19 | 20 | /** 21 | * Tests Output of Typi mixin 22 | * ------------------------- 23 | * Should output same values as above, but in em instead 24 | */ 25 | .em { 26 | font-size: 1.33333em; 27 | line-height: 1.3; } 28 | @media all and (min-width: med) { 29 | .em { 30 | font-size: 1.66667em; } } 31 | @media all and (min-width: large) { 32 | .em { 33 | font-size: 3em; } } 34 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/js/main.js: -------------------------------------------------------------------------------- 1 | WebFont.load({ 2 | google: { 3 | families: ["Source Sans Pro:100,300,400,500,700"] 4 | }, 5 | loading: function() { 6 | capHeight.setContainer(document.body); 7 | }, 8 | 9 | fontactive: capHeight.fontActive(function(properties) { 10 | console.log(properties); 11 | }) 12 | }); 13 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typi", 3 | "version": "3.1.2", 4 | "description": "A sass mixin to make responsive typography easy", 5 | "main": "scss/_typi.scss", 6 | "scripts": { 7 | "start": "gulp", 8 | "test": "mocha test/test_sass.js", 9 | "release": "np" 10 | }, 11 | "directories": { 12 | "test": "test" 13 | }, 14 | "devDependencies": { 15 | "cap-height": "^1.1.2", 16 | "gulp": "^3.9.1", 17 | "gulp-mocha": "^2.2.0", 18 | "gulp-notify": "^2.2.0", 19 | "gulp-plumber": "^1.1.0", 20 | "gulp-sass": "^2.3.1", 21 | "mocha": "^2.3.4", 22 | "np": "^2.13.3", 23 | "sass-true": "^2.0.3", 24 | "sassdoc": "^2.1.20" 25 | }, 26 | "keywords": [ 27 | "eyeglass-module" 28 | ], 29 | "eyeglass": { 30 | "sassDir": "scss", 31 | "exports": false, 32 | "name": "typi", 33 | "needs": "*" 34 | }, 35 | "homepage": "https://github.com/zellwk/typi", 36 | "repository": { 37 | "type": "git", 38 | "url": "https://github.com/zellwk/typi" 39 | }, 40 | "author": "Zell Liew ", 41 | "license": "MIT" 42 | } 43 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/_private.scss: -------------------------------------------------------------------------------- 1 | @import 'private/baseline/baseline'; 2 | @import 'private/breakpoints/breakpoints'; 3 | @import 'private/calc/calc'; 4 | @import 'private/typefaces/typefaces'; 5 | @import 'private/utils/utils'; 6 | @import 'private/write/write'; 7 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/_public.scss: -------------------------------------------------------------------------------- 1 | @import 'public/rhythm/rhythm'; 2 | @import 'public/typi/typi'; 3 | @import 'public/typefaces/typefaces'; -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/_typi.scss: -------------------------------------------------------------------------------- 1 | 2 | @import 'private'; 3 | @import 'public'; 4 | 5 | //// 6 | /// @author Zell Liew 7 | /// @access public 8 | /// @group config 9 | //// 10 | 11 | // Breakpoints Map 12 | // ---------- 13 | /// Contains all breakpoints Typi uses whenever writes media queries. 14 | /// Possible to output media queries in em (even when you write in pixels) 15 | /// @example 16 | /// $breakpoints: ( 17 | /// small: 400px, 18 | /// med: 600px, 19 | /// large: 800px 20 | /// ); 21 | /// @see typi-breakpoint 22 | $breakpoints: () !default; 23 | 24 | // Typefaces Map 25 | // ---------- 26 | /// Contains typefaces that Typi uses. 27 | /// @prop {Number} font-size-ratio [1] - ratio of font-size to primary font-size 28 | /// @prop {Map} stack - font stack 29 | /// @prop {Map} weights - font weights 30 | /// @example 31 | /// $typefaces: ( 32 | /// primary: ( 33 | /// font-size-ratio: 1, 34 | /// stack: (Helvetica, Arial, sans-serif), 35 | /// weights: ( 36 | /// light: 300, 37 | /// normal: 400, 38 | /// bold: 700 39 | /// ), 40 | /// ) 41 | /// ); 42 | $typefaces: false !default; 43 | 44 | // Typi 45 | // ---------- 46 | /// Contains font-map, which in turn contains font-size and 47 | /// line-height properties used at all breakpoints. 48 | /// 49 | /// - **First value** - font-size (see below) 50 | /// - **second value** - line-height (must be unitless) 51 | /// 52 | /// **Font-size** 53 | /// 54 | /// Font-size can either be either of these values. They will 55 | /// automatically be converted into `rem` or `em` 56 | /// 57 | /// - `px` - base font-sizes must be written in pixels 58 | /// - `em` 59 | /// - unitless (requires typi-ms) 60 | /// 61 | /// @prop {Map} $font-map - Font map that holds breakpoint key 62 | /// @prop {string} $breakpoint - holds font-size and line-height list 63 | /// 64 | /// @example 65 | /// $typi: ( 66 | /// base: ( 67 | /// null: (18px, 1.5), 68 | /// small: 20px, 69 | /// med: 22px, 70 | /// large: (24px, 1.6) 71 | /// ), 72 | /// h1: ( 73 | /// null: (24px, 1.3), 74 | /// med: 2em, 75 | /// large: 3em 76 | /// ) 77 | /// ); 78 | /// @see typi-ms 79 | /// @requires $breakpoints 80 | $typi: ( 81 | base: ( 82 | null: (16px, 1.5) 83 | ) 84 | ) !default; 85 | 86 | // Typi-ms 87 | // ---------- 88 | /// Contains modular scale base and ratios for 89 | /// you to change ratios and bases at different 90 | /// breakpoints. 91 | /// 92 | /// **Make sure** $typi-ms, $typi and $breakpoints **contain 93 | /// all breakpoints** (except null) if you use this functionality 94 | /// 95 | /// @requires $typi 96 | /// @requires $breakpoints 97 | /// 98 | /// @example 99 | /// $typi-ms: ( 100 | /// null: 1.2, // one ratio: 1.2 101 | /// small: 1.2, 102 | /// med: (1.2 1.5), // two ratios: 1.2, 1.5 103 | /// large: (1em 2em 1.2), // two bases: 1em, 2em | one ratio: 1.2 104 | /// huge: (1.5em 1.2 1.5) // one base: 1.5em | two ratios: 1.2, 1.5 105 | /// ); 106 | $typi-ms: false !default; 107 | 108 | // Typi-breakpoint 109 | // ---------- 110 | /// Integrates Typi with Mappy Breakpoints or Breakpoint-sass 111 | /// @example 112 | /// $typi-breakpoint: 'mappy-bp'; // uses mappy-breakpoint to create media queries 113 | /// $typi-brekapoint: 'breakpoint'; // uses breakpoint-sass to create media queries 114 | /// @link https://github.com/zellwk/mappy-breakpoints Mappy-breakpoints 115 | /// @link https://github.com/at-import/breakpoint Breakpoint Sass 116 | $typi-breakpoint: null !default; 117 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/private/baseline/_baseline.scss: -------------------------------------------------------------------------------- 1 | // Requires 2 | // typeface 3 | // typefaces 4 | // target map 5 | // base map 6 | @mixin _ty-write-baseline-push ( 7 | $font-size: $font-size, 8 | $line-height: $line-height, 9 | $breakpoint: $breakpoint, 10 | $_map: $_map 11 | ) { 12 | $typeface: map-get($_map, typeface); 13 | $typefaces: map-get($_map, typefaces); 14 | 15 | $line-height: _ty-get-line-height-for-baseline-push( 16 | $line-height, 17 | $breakpoint, 18 | $_map 19 | ); 20 | 21 | $push-amt: _ty-get-baseline-push-amt( 22 | $font-size: $font-size, 23 | $line-height: $line-height, 24 | $typeface: $typeface, 25 | $typefaces: $typefaces 26 | ); 27 | 28 | // Write output 29 | padding-top: $push-amt; 30 | margin-bottom: $push-amt * -1; 31 | } 32 | 33 | // _ty-get-baseline-push-amt 34 | // ---------- 35 | // Gets amount to push baseline by 36 | // ========== 37 | @function _ty-get-baseline-push-amt( 38 | $font-size: $font-size, 39 | $line-height: $line-height, 40 | $typeface: null, 41 | $typefaces: $typefaces 42 | ) { 43 | $cap-height: 1; 44 | @if unit($font-size) != 'em' { 45 | @error '$font-size must be in em'; 46 | } 47 | @if not unitless($line-height) { 48 | @error '$line-height must be unitless'; 49 | } 50 | @if $typeface { 51 | $cap-height: _ty-parse-cap-height($typeface, $typefaces); 52 | } 53 | @return $font-size * ($line-height - $cap-height) * 0.5; 54 | } 55 | 56 | // _ty-get-line-height-for-baseline-push 57 | // ---------- 58 | // Gets line-height value used to calc baseline-push amt 59 | // ========== 60 | @function _ty-get-line-height-for-baseline-push( 61 | $line-height, 62 | $breakpoint: null, 63 | $_map: null 64 | ) { 65 | // should use own line-height if present 66 | @if $line-height { 67 | @return $line-height; 68 | } 69 | 70 | @if not $_map { @error "$_map not found"; } 71 | 72 | // Falls back to own line-height from previous breakpoint 73 | $target-map: map-get($_map, target-map); 74 | $_line-height: _ty-get-closest-line-height($target-map, $breakpoint); 75 | 76 | @if $_line-height { 77 | @return $_line-height; 78 | } 79 | 80 | // Falls back to line-height from base map, same breakpoint 81 | $_base-map: map-get($_map, basemap); 82 | $_target-breakpoint-map: map-get($_base-map, $breakpoint); 83 | $_line-height: _ty-get-line-height($_target-breakpoint-map); 84 | 85 | @if $_line-height { 86 | @return $_line-height; 87 | } 88 | 89 | // Falls back to line-height from base map, previous breakpoint 90 | $_line-height: _ty-get-closest-line-height($_base-map, $breakpoint); 91 | 92 | @if $_line-height { 93 | @return $_line-height; 94 | } 95 | 96 | @return 1; 97 | } 98 | 99 | // _ty-get-closest-line-height 100 | // ---------- 101 | // Gets closest line-height value for baseline push calc 102 | // ========== 103 | @function _ty-get-closest-line-height( 104 | $_target-map, 105 | $breakpoint 106 | ) { 107 | $_map-keys: map-keys($_target-map); 108 | $_current-bp-index: index($_map-keys, $breakpoint); 109 | $_closest-lh: null; 110 | 111 | @while $_current-bp-index > 1 { 112 | // @while not $_closest-lh { 113 | $_closest-index: $_current-bp-index - 1; 114 | $_prev-breakpoint-key: nth($_map-keys, $_closest-index); 115 | $_prev-font-map-bp: map-get($_target-map, $_prev-breakpoint-key); 116 | $_line-height: _ty-get-line-height($_prev-font-map-bp); 117 | 118 | // Breaks loop 119 | @if $_line-height { 120 | $_closest-lh: $_line-height; 121 | $_current-bp-index: 1; 122 | @return $_closest-lh; 123 | } 124 | 125 | @else { 126 | $_current-bp-index: $_closest-index; 127 | 128 | // Completed own map, but couldn't find index. 129 | // Breaks loop. Fall back on next. 130 | @if ($_closest-index == 1) { 131 | $_closest-lh: true; 132 | @return false; 133 | } 134 | } 135 | } 136 | 137 | @return false; 138 | } 139 | 140 | // _ty-parse-cap-height 141 | // ---------- 142 | // Gets and ensures cap-height is present 143 | // ========== 144 | @function _ty-parse-cap-height( 145 | $typeface, 146 | $typefaces: $typefaces 147 | ) { 148 | $typeface-map: _ty-get-typeface-map($typefaces, $typeface); 149 | @if map-has-key($typeface-map, cap-height) { 150 | @return map-get($typeface-map, cap-height); 151 | } @else { 152 | @error 'cap-height not found in #{$typeface-map}'; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/private/breakpoints/_breakpoints.scss: -------------------------------------------------------------------------------- 1 | @import 'utils'; 2 | 3 | // _ty-write-breakpoints 4 | // ---------- 5 | // - writes props for every valid breakpoint 6 | // ========== 7 | @mixin _ty-write-breakpoints ( 8 | $_map 9 | ) { 10 | $target: map-get($_map, target); 11 | $breakpoints: map-get($_map, breakpoints); 12 | $typi: map-get($_map, typi); 13 | $rem: map-get($_map, rem); 14 | $breakpoint-lib: map-get($_map, breakpoint-lib); 15 | 16 | $basemap: null; 17 | $target-map: null; 18 | 19 | // Ensures target map is present 20 | @if not map-has-key($typi, $target) { 21 | @error "#{$target} not found in $typi map"; 22 | } 23 | 24 | $target-map: map-get($typi, $target); 25 | 26 | // Ensures base map is present 27 | @if not map-has-key($typi, base) { 28 | @error "base key is missing from $typi map"; 29 | } 30 | 31 | $basemap: map-get($typi, base); 32 | 33 | $_map: map-merge($_map, ( 34 | basemap: $basemap, 35 | target-map: $target-map 36 | )); 37 | 38 | @each $breakpoint, $target-value in $target-map { 39 | @if $breakpoint == null { 40 | @include _ty-output-props( 41 | $_map: $_map, 42 | $breakpoint: $breakpoint 43 | ); 44 | } 45 | 46 | @else { 47 | @include _ty-output-with-breakpoint-library( 48 | $breakpoint-lib, 49 | $breakpoint: $breakpoint, 50 | $breakpoints: $breakpoints 51 | ) { 52 | @include _ty-output-props( 53 | $_map: $_map, 54 | $breakpoint: $breakpoint 55 | ); 56 | } 57 | } 58 | } 59 | } 60 | 61 | // _ty-output-with-breakpoint-library 62 | // ---------- 63 | // Outputs with breakpoint library 64 | // ========== 65 | @mixin _ty-output-with-breakpoint-library ( 66 | $library: false, 67 | $breakpoint: null, 68 | $breakpoints: $breakpoints 69 | ) { 70 | $lib: null; 71 | 72 | // Parses library 73 | @if $library { 74 | $lib: _ty-parse-breakpoint-lib($library); 75 | @if not $lib { 76 | @error "#{$lib} mixin not found"; 77 | } 78 | } 79 | 80 | 81 | @if $lib == 'breakpoint' { 82 | @include breakpoint-set('to ems', true); 83 | @include breakpoint(map-get($breakpoints, $breakpoint)) { 84 | @content; 85 | } 86 | } @else if $lib == 'mappy-bp' { 87 | @include mappy-bp(map-get($breakpoints, $breakpoint)) { 88 | @content; 89 | } 90 | } @else if $lib == 'mq' { 91 | @include mq(map-get($mq-breakpoints, $breakpoint)) { 92 | @content; 93 | } 94 | } @else { 95 | @media all and (min-width: #{map-get($breakpoints, $breakpoint)}) { 96 | @content; 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/private/breakpoints/_utils.scss: -------------------------------------------------------------------------------- 1 | // _ty-has-breakpoint 2 | // ---------- 3 | // - Ensures $breakpoints and $typi has breakpoint 4 | // ========== 5 | @function _ty-has-breakpoint ( 6 | $target: null, 7 | $breakpoint: $breakpoint, 8 | $breakpoints: $breakpoints, 9 | $basemap: $basemap 10 | ) { 11 | 12 | @if $target == 'base' { 13 | @return true; 14 | } 15 | 16 | @if not map-has-key($breakpoints, $breakpoint) { 17 | @error "$breakpoints map is missing #{$breakpoint} key"; 18 | } 19 | 20 | // @if not map-has-key($basemap, $breakpoint) { 21 | // @error "$typi base key is missing #{$breakpoint} key"; 22 | // } 23 | 24 | @return true; 25 | } 26 | 27 | // _has-breakpoint-library 28 | // - Checks if breakpoint library is installed 29 | // ========== 30 | @function _ty-has-breakpoint-library($lib) { 31 | // Activates if $typi-breakpoint = mentioned breakpoint 32 | @if $typi-breakpoint == $lib { 33 | @if mixin-exists($lib) { @return true; } 34 | @else { @error "#{$lib} not found"; } 35 | } 36 | // Else use default breakpoint built in typi 37 | @else { 38 | @return false; 39 | } 40 | } 41 | 42 | // _ty-parse-breakpoint-lib 43 | // ---------- 44 | // Checks if breakpoint library is supported 45 | // ========== 46 | @function _ty-parse-breakpoint-lib($lib) { 47 | @if mixin-exists($lib) { 48 | @return $lib; 49 | } @else { 50 | @return false; 51 | } 52 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/private/calc/_calc-font-size.scss: -------------------------------------------------------------------------------- 1 | // Calculates font size to return to typi 2 | // ---------- 3 | // - return font-size in rem or em 4 | // ========== 5 | @function _ty-calc-font-size ( 6 | $font-size: $font-size, 7 | $basemap: $basemap, 8 | $rem: $rem, 9 | $breakpoint: null, 10 | $typeface-multiplier: 1 11 | ) { 12 | // Using typi-ms if font-size is unitless 13 | @if unitless($font-size) { 14 | $step: $font-size; 15 | @if $rem { 16 | @return _ty-ms-to-rem($step, $breakpoint) * $typeface-multiplier; 17 | } @else { 18 | @return _ty-ms-to-em($step, $breakpoint) * $typeface-multiplier; 19 | } 20 | } 21 | 22 | $base-null: null; 23 | $base-font-size: null; 24 | 25 | // Calculation uses base-null becase everything 26 | // is relative to the base font-size. 27 | @if map-has-key($basemap, null) { 28 | $base-null: map-get($basemap, null); 29 | $base-font-size: nth($base-null, 1); 30 | } @else { 31 | @error 'Base map must have null key' 32 | } 33 | 34 | // Converts to rem 35 | @if $rem { 36 | @if unit($font-size) == 'px' { 37 | @return _ty-to-rem($font-size, $base-font-size) * $typeface-multiplier; 38 | } @else { 39 | @return _ty-to-rem($font-size) * $typeface-multiplier; 40 | } 41 | } 42 | 43 | // Converts to em 44 | @else { 45 | @if unit($font-size) == 'px' { 46 | @return _ty-to-em($font-size, $base-font-size) * $typeface-multiplier; 47 | } @else { 48 | @return _ty-to-em($font-size) * $typeface-multiplier; 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/private/calc/_calc-ms-size.scss: -------------------------------------------------------------------------------- 1 | // _ty-calc-ms 2 | // ========== 3 | @function _ty-calc-ms( 4 | $step, 5 | $breakpoint: null, 6 | $typi-ms: $typi-ms 7 | ) { 8 | $ms-setting: null; 9 | $ty-ms-base: (); 10 | $ty-ms-ratio: (); 11 | 12 | @if type-of($typi-ms) != 'map' { 13 | @error "$typi-ms map not found"; 14 | } 15 | 16 | @if map-has-key($typi-ms, $breakpoint) { 17 | $ms-setting: map-get($typi-ms, $breakpoint); 18 | } @else { 19 | @error "$typi-ms does not contain #{$breakpoint}"; 20 | } 21 | 22 | @if length($ms-setting) == 0 { 23 | @error "$typi-ms at #{$breakpoint} must not be empty"; 24 | } 25 | 26 | // Only ratio if length == 1 27 | @if length($ms-setting) == 1 { 28 | $_is-ratio: _ty-is-ratio($ms-setting); 29 | 30 | @if $_is-ratio { 31 | @return ms($step, 1em, $ms-setting); 32 | } @else { 33 | @error "$typi-ms ratio at #{$breakpoint} must be unitless"; 34 | } 35 | } 36 | 37 | // Two or more values. 38 | // All values with em units are font-sizes. 39 | // All unitless values are ratios. 40 | @else { 41 | @for $i from 1 through length($ms-setting) { 42 | $_current: nth($ms-setting, $i); 43 | $_is-base: _ty-is-base($_current); 44 | $_is-ratio: _ty-is-ratio($_current); 45 | 46 | @if $_is-base { 47 | $ty-ms-base: _ty-append($ty-ms-base, $_current) 48 | } 49 | 50 | @if $_is-ratio { 51 | $ty-ms-ratio: _ty-append($ty-ms-ratio, $_current) 52 | } 53 | } 54 | 55 | // Sets base to 1em if there's no base 56 | @if length($ty-ms-base) == 0 { 57 | $ty-ms-base: 1em; 58 | } 59 | 60 | @if length($ty-ms-ratio) == 0 { 61 | @error "$typi-ms at #{$breakpoint} must contain at least one ratio"; 62 | } 63 | 64 | @return ms($step, $ty-ms-base, $ty-ms-ratio); 65 | } 66 | } 67 | 68 | // Checks if $num is base for ms calc 69 | // ========== 70 | @function _ty-is-base($num) { 71 | @if unit($num) == 'em' { 72 | @return true; 73 | } @else if unitless($num) { 74 | @return false; 75 | } @else { 76 | @error '$typi-ms only accepts em base values'; 77 | } 78 | } 79 | 80 | // Checks if $num is ratio for ms calc 81 | // ========== 82 | @function _ty-is-ratio($num) { 83 | @if unitless($num) { 84 | @return true; 85 | } @else { 86 | @return false; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/private/calc/_calc.scss: -------------------------------------------------------------------------------- 1 | @import 'calc-font-size'; 2 | @import 'calc-ms-size'; 3 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/private/typefaces/_multiplier.scss: -------------------------------------------------------------------------------- 1 | // -ty_get-typeface-multiplier 2 | // ---------- 3 | // Gets typeface multiplier 4 | // ========== 5 | @function _ty-get-typeface-multiplier( 6 | $typeface, 7 | $typefaces 8 | ) { 9 | $_font-size-ratio: null; 10 | $_typeface-map: _ty-get-typeface-map($typefaces, $typeface); 11 | 12 | @if $_typeface-map { 13 | $_font-size-ratio: map-get($_typeface-map, font-size-ratio); 14 | } 15 | 16 | @if $_font-size-ratio { 17 | @return 1 / $_font-size-ratio; 18 | } @else { 19 | @return 1; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/private/typefaces/_typefaces.scss: -------------------------------------------------------------------------------- 1 | // _ty-parse-typeface 2 | // ---------- 3 | // Ensures typeface key present in $typefaces. 4 | // ========== 5 | @function _ty-parse-typeface( 6 | $typeface, 7 | $typefaces 8 | ) { 9 | @if map-has-key($typefaces, $typeface) { 10 | @return map-get($typefaces, $typeface); 11 | } @else { 12 | @error "$typefaces does not have #{$typeface} typeface"; 13 | } 14 | } 15 | 16 | @function _ty-get-typeface-map( 17 | $typefaces, 18 | $typeface 19 | ) { 20 | // Returns false if typeface map is not used, 21 | // so caller can handle their own. 22 | @if type-of($typefaces) != 'map' { 23 | @return false; 24 | } 25 | @if not map-has-key($typefaces, $typeface) { 26 | @error '#{$typeface}' not found in $typefaces 27 | } 28 | @return map-get($typefaces, $typeface); 29 | } 30 | 31 | @import 'multiplier'; -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/private/utils/_bases.scss: -------------------------------------------------------------------------------- 1 | // _ty-get-font-size 2 | // ---------- 3 | // Gets font-size from font-map-breakpoint 4 | // ========== 5 | @function _ty-get-font-size ($font-map-breakpoint) { 6 | @if type-of($font-map-breakpoint) == 'number' { 7 | @return $font-map-breakpoint; 8 | } @else { 9 | @return nth($font-map-breakpoint, 1); 10 | } 11 | } 12 | 13 | // _ty-get-line-height 14 | // ---------- 15 | // Gets line-height from font-map-breakpoint 16 | // ========== 17 | @function _ty-get-line-height ($font-map-breakpoint) { 18 | @if type-of($font-map-breakpoint) == 'list' { 19 | @return nth($font-map-breakpoint, 2); 20 | } @else { 21 | @return false; 22 | } 23 | } 24 | 25 | // _ty-get-base-font-size 26 | // ========== 27 | @function _ty-get-base-font-size($basemap) { 28 | $base-null: map-get($basemap, null); 29 | @return _ty-get-font-size($base-null); 30 | } 31 | 32 | // _ty-get-base-line-height 33 | // ========== 34 | @function _ty-get-base-line-height($basemap) { 35 | $base-null: map-get($basemap, null); 36 | $base-line-height: _ty-get-line-height($base-null); 37 | 38 | @if not $base-line-height { 39 | @error "Typi base map requires line-height multiple in null key" 40 | } 41 | 42 | @if not unitless($base-line-height) { 43 | @error "Line-height in $typi base map should be unitless" 44 | } 45 | 46 | @return $base-line-height; 47 | } 48 | 49 | // _ty-get-base-map 50 | // ---------------- 51 | @function _ty-get-base-map($typi: $typi) { 52 | @if map-has-key($typi, base) { 53 | @return map-get($typi, base); 54 | } @else { 55 | @error "Base key not found in $typi"; 56 | } 57 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/private/utils/_converters.scss: -------------------------------------------------------------------------------- 1 | // _ty-strip-unit 2 | // ============== 3 | @function _ty-strip-unit($num) { 4 | @return $num / ($num * 0 + 1); 5 | } 6 | 7 | 8 | // _ty-to-percentage 9 | // ======== 10 | @function _ty-to-percentage($font-size) { 11 | @if unit($font-size) != 'px' { 12 | @error "_ty-to-percentage() only accepts pixel sizes" 13 | } @else { 14 | @return $font-size / 16px * 100%; 15 | } 16 | } 17 | 18 | // _ty-to-em 19 | // ======== 20 | @function _ty-to-em($font-size, $ref-size: null) { 21 | @if unit($font-size) == "px" { 22 | @if not $ref-size { 23 | @error "$ref-size must be present for px -> em conversion"; 24 | } @else { 25 | @return _ty-strip-unit($font-size) / _ty-strip-unit($ref-size) * 1em; 26 | } 27 | } 28 | 29 | @else if unit($font-size) == 'em' { 30 | @if $ref-size { 31 | @return _ty-strip-unit($font-size) / _ty-strip-unit($ref-size) * 1em; 32 | } @else { 33 | @return _ty-strip-unit($font-size) * 1em; 34 | } 35 | } 36 | 37 | @else if unit($font-size) == "rem" { 38 | @return _ty-strip-unit($font-size) * 1em; 39 | } 40 | 41 | @else { 42 | @error "_ty-to-em() only supports px -> em, em -> em and rem -> em conversion"; 43 | } 44 | } 45 | 46 | // _ty-to-rem 47 | // ========= 48 | @function _ty-to-rem($font-size, $ref-size: null) { 49 | @if unit($font-size) == "px" { 50 | @if not $ref-size { 51 | @error "$ref-size must be present for px -> rem conversion"; 52 | } @else { 53 | @return _ty-strip-unit($font-size) / _ty-strip-unit($ref-size) * 1rem; 54 | } 55 | } 56 | 57 | @else if unit($font-size) == "em" { 58 | @return _ty-strip-unit($font-size) * 1rem; 59 | } 60 | 61 | @else { 62 | @error "_ty-to-rem() only supports px -> rem and em -> rem conversion"; 63 | } 64 | } 65 | 66 | // _ty-ms-to-rem 67 | // ========== 68 | @function _ty-ms-to-rem($step, $breakpoint) { 69 | @if not unitless($step) { 70 | @error "steps must be unitless when you're using Typi-ms"; 71 | } 72 | 73 | $font-size: _ty-calc-ms($step, $breakpoint); 74 | @return _ty-to-rem($font-size); 75 | } 76 | 77 | @function _ty-ms-to-em($step, $breakpoint) { 78 | @if not unitless($step) { 79 | @error "steps must be unitless when you're using Typi-ms"; 80 | } 81 | 82 | @return _ty-calc-ms($step, $breakpoint); 83 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/private/utils/_extender.scss: -------------------------------------------------------------------------------- 1 | // _ty-map-extender 2 | // ---------- 3 | // - Extends $typi-ms and all target maps 4 | // to include breakpoints from breakpoints map 5 | // TODO: Update when libsass v3.4 6 | // https://github.com/zellwk/typi/issues/17 7 | // ========== 8 | 9 | @function _ty-map-extender( 10 | $target: null, 11 | $typi: $typi, 12 | $typi-ms: $typi-ms, 13 | $breakpoints: $breakpoints 14 | ) { 15 | $_o: null; 16 | 17 | // Extends $typi map 18 | @if $target { 19 | $_o: _ty-extend-typi($target); 20 | } 21 | 22 | // Extends $typi-ms 23 | @else { 24 | $_o: _ty-extend-typi-ms() 25 | } 26 | 27 | @return $_o; 28 | } 29 | 30 | // _ty-extend-typi 31 | // ========== 32 | @function _ty-extend-typi($target) { 33 | $_return: $typi; 34 | $_target-map: null; 35 | $_prev-breakpoint: null; 36 | $_overwrite-value: null; 37 | 38 | // Does not extend typi base 39 | @if $target == 'base' { 40 | @return $_return; 41 | } 42 | 43 | @if map-has-key($typi, $target) { 44 | $_target-map: map-get($typi, $target); 45 | } @else { 46 | @error '$typi should contain #{$target}'; 47 | } 48 | 49 | // Initializes $_overwrite-value 50 | @if map-has-key($_target-map, null) { 51 | $_overwrite-value: map-get($_target-map, null); 52 | } @else { 53 | @error "#{$_target-map} must contain a null key"; 54 | } 55 | 56 | @each $breakpoint, $value in $breakpoints { 57 | // Updates overwrite value if has target map 58 | @if map-has-key($_target-map, $breakpoint) { 59 | $_overwrite-value: map-get($_target-map, $breakpoint); 60 | } 61 | 62 | // Overwrites relevant map otherwise 63 | // TODO: Needs to get map and override in the correct order. Needs Libsass v3.4 64 | // See manual/typi-ms.scss for details 65 | @else { 66 | $_return: map-deep-set( 67 | $_return, 68 | $target $breakpoint, 69 | $_overwrite-value 70 | ); 71 | $typi: map-merge($typi, $_return) !global; 72 | } 73 | 74 | // Sets values for next loop 75 | $_prev-breakpoint: $breakpoint; 76 | } 77 | 78 | @return $_return; 79 | } 80 | 81 | // _ty-extend-typi-ms 82 | // ========== 83 | @function _ty-extend-typi-ms() { 84 | $_return: $typi-ms; 85 | $_prev-breakpoint: 'null'; 86 | $_overwrite-value: null; 87 | 88 | // Target map must always have a null key 89 | @if not map-has-key($_return, null) { 90 | @error "$typi-ms must contain a null key"; 91 | } 92 | 93 | // Initializes $_overwrite-value 94 | $_overwrite-value: map-get($_return, null); 95 | 96 | @each $breakpoint, $value in $breakpoints { 97 | // Updates overwrite value if already in $typi-ms 98 | @if map-has-key($_return, $breakpoint) { 99 | $_overwrite-value: map-get($_return, $breakpoint); 100 | } 101 | 102 | // Overwrites relevant map otherwise 103 | @else { 104 | $_return: map-deep-set( 105 | $_return, 106 | $breakpoint, 107 | $_overwrite-value 108 | ); 109 | $typi-ms: map-merge($typi-ms, $_return) !global; 110 | } 111 | 112 | // Sets values for next loop 113 | $_prev-breakpoint: $breakpoint 114 | } 115 | 116 | @return $_return; 117 | } 118 | 119 | // _ty-check-extended-map 120 | // ========== 121 | @mixin _ty-check-extended-map( 122 | $map: 'typi', 123 | $typi: $typi, 124 | $typi-ms: $typi-ms, 125 | $breakpoints: $breakpoints 126 | ) { 127 | $breakpoint-list: map-keys($breakpoints); 128 | $font-map-list: map-keys($typi); 129 | $font-map-list: _ty-remove($font-map-list, 'base'); 130 | 131 | // Loops through every font-map to ensure that 132 | // each map contains all breakpoint keys 133 | @each $map-key in $font-map-list { 134 | $font-map: map-get($typi, $map-key); 135 | @each $breakpoint in $breakpoint-list { 136 | @if not map-has-key($font-map, $breakpoint) { 137 | @warn "#{$breakpoint} breakpoint not found in $typi's #{$map-key} key. Make sure every breakpoint is present in every font-map when using $typi-ms!"; 138 | } 139 | } 140 | } 141 | 142 | // checks $typi-ms 143 | @each $breakpoint in $breakpoint-list { 144 | @if not map-has-key($typi-ms, $breakpoint) { 145 | @warn "#{$breakpoint} breakpoint not found in $typi-ms. Make sure every breakpoint is present in every font-map when using $typi-ms!"; 146 | } 147 | } 148 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/private/utils/_lists.scss: -------------------------------------------------------------------------------- 1 | // _ty-append 2 | // ---------- 3 | // Appending to empty list is wonky. This function fixes 4 | // append by setting first item to be appended as a list item 5 | // ========== 6 | @function _ty-append($list, $value) { 7 | @if length($list) == 0 { 8 | @return ($value); 9 | } @else { 10 | @return append($list, $value); 11 | } 12 | } 13 | 14 | // _ty-insert-nth 15 | // ---------- 16 | // Inserting value at index n 17 | // http://hugogiraudel.com/2013/08/08/advanced-sass-list-functions/#adding-values-to-a-list 18 | // ========== 19 | @function _ty-insert-nth($list, $index, $value) { 20 | $result: null; 21 | 22 | @if type-of($index) != number { 23 | @warn "$index: #{quote($index)} is not a number for `insert-nth`."; 24 | } 25 | 26 | @else if $index < 1 { 27 | @warn "List index 0 must be a non-zero integer for `insert-nth`"; 28 | } 29 | 30 | @else if $index > length($list) { 31 | @warn "List index is #{$index} but list is only #{length($list)} item long for `insert-nth'."; 32 | } 33 | 34 | @else { 35 | $result: (); 36 | 37 | @for $i from 1 through length($list) { 38 | @if $i == $index { 39 | $result: append($result, $value); 40 | } 41 | 42 | $result: append($result, nth($list, $i)); 43 | } 44 | } 45 | 46 | @return $result; 47 | } 48 | 49 | // _ty-remove 50 | // ---------- 51 | // - Removes item from list 52 | // http://hugogiraudel.com/2013/08/08/advanced-sass-list-functions/#removing-values-from-list 53 | // ========== 54 | @function _ty-remove($list, $value, $recursive: false) { 55 | $result: (); 56 | 57 | @for $i from 1 through length($list) { 58 | @if type-of(nth($list, $i)) == list and $recursive { 59 | $result: append($result, remove(nth($list, $i), $value, $recursive)); 60 | } 61 | 62 | @else if nth($list, $i) != $value { 63 | $result: append($result, nth($list, $i)); 64 | } 65 | } 66 | 67 | @return $result; 68 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/private/utils/_maps.scss: -------------------------------------------------------------------------------- 1 | // Map-fetch 2 | // ---------- 3 | /// An easy way to fetch a deep value in a multi-level map. 4 | /// Works much like map-get() except that you pass multiple 5 | /// keys as the second parameter to go down multiple levels 6 | /// in the nested map. 7 | /// @access public 8 | /// @param {Map} $map - Map 9 | /// @param {List} $keys - Key chain. 10 | /// @return {*} - Desired value 11 | /// @link https://gist.github.com/jlong/8760275 12 | /// @group utils 13 | @function map-fetch($map, $keys) { 14 | $key: nth($keys, 1); 15 | $length: length($keys); 16 | $value: map-get($map, $key); 17 | @if $length > 1 { 18 | $rest: (); 19 | @for $i from 2 through $length { 20 | $rest: append($rest, nth($keys, $i)); 21 | } 22 | @return map-fetch($value, $rest); 23 | } @else { 24 | @return $value; 25 | } 26 | } 27 | 28 | /// Map deep get 29 | /// @author Hugo Giraudel 30 | /// @access public 31 | /// @param {Map} $map - Map 32 | /// @param {Arglist} $keys - Key chain 33 | /// @return {*} - Desired value 34 | /// @group utils 35 | @function map-deep-get($map, $keys...) { 36 | @each $key in $keys { 37 | $map: map-get($map, $key); 38 | } 39 | @return $map; 40 | } 41 | 42 | 43 | /// Deep set function to set a value in nested maps 44 | /// @author Hugo Giraudel 45 | /// @access public 46 | /// @param {Map} $map - Map 47 | /// @param {List} $keys - Key chaine 48 | /// @param {*} $value - Value to assign 49 | /// @return {Map} 50 | /// @group utils 51 | @function map-deep-set($map, $keys, $value) { 52 | $maps: ($map,); 53 | $result: null; 54 | 55 | // If the last key is a map already 56 | // Warn the user we will be overriding it with $value 57 | @if type-of(nth($keys, -1)) == "map" { 58 | @warn "The last key you specified is a map; it will be overrided with `#{$value}`."; 59 | } 60 | 61 | // If $keys is a single key 62 | // Just merge and return 63 | @if length($keys) == 1 { 64 | @return map-merge($map, ($keys: $value)); 65 | } 66 | 67 | // Loop from the first to the second to last key from $keys 68 | // Store the associated map to this key in the $maps list 69 | // If the key doesn't exist, throw an error 70 | @for $i from 1 through length($keys) - 1 { 71 | $current-key: nth($keys, $i); 72 | $current-map: nth($maps, -1); 73 | $current-get: map-get($current-map, $current-key); 74 | @if $current-get == null { 75 | @error "Key `#{$key}` doesn't exist at current level in map."; 76 | } 77 | $maps: append($maps, $current-get); 78 | } 79 | 80 | // Loop from the last map to the first one 81 | // Merge it with the previous one 82 | @for $i from length($maps) through 1 { 83 | $current-map: nth($maps, $i); 84 | $current-key: nth($keys, $i); 85 | $current-val: if($i == length($maps), $value, $result); 86 | $result: map-merge($current-map, ($current-key: $current-val)); 87 | } 88 | 89 | // Return result 90 | @return $result; 91 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/private/utils/_utils.scss: -------------------------------------------------------------------------------- 1 | @import 'converters'; 2 | @import 'bases'; 3 | @import 'maps'; 4 | @import 'lists'; 5 | @import 'extender'; -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/private/write/_write.scss: -------------------------------------------------------------------------------- 1 | // _ty-output-props 2 | // ---------- 3 | // Decides mixins used to write properties 4 | // ========== 5 | @mixin _ty-output-props ( 6 | $_map: $_map, 7 | $breakpoint: $breakpoint 8 | ) { 9 | $target: map-get($_map, target); 10 | $target-map: map-get($_map, target-map); 11 | $output: map-get($_map, output); 12 | $baseline-push: map-get($_map, baseline-push); 13 | 14 | // Ensures breakpoint is present in target 15 | @if not map-has-key($target-map, $breakpoint) { 16 | @error "#{$target} does not have #{$breakpoint} breakpoint" 17 | } 18 | 19 | @if $target == 'base' { 20 | @include _ty-write-base($_map, $breakpoint); 21 | @if $baseline-push { 22 | @error "$baseline option not allowed on base map"; 23 | } 24 | } @else if $output == 'props' { 25 | @include _ty-write-props($_map, $breakpoint); 26 | } @else if $output == 'vr' { 27 | @include _ty-write-vr($_map, $breakpoint); 28 | } 29 | } 30 | 31 | // _ty-write-base 32 | // ---------- 33 | // Creates font-size and line-height for base map 34 | // ========== 35 | @mixin _ty-write-base ($_map, $breakpoint) { 36 | $target-map: map-get($_map, target-map); 37 | $font-map-breakpoint: map-get($target-map, $breakpoint); 38 | $font-size: _ty-get-font-size($font-map-breakpoint); 39 | $line-height: _ty-get-line-height($font-map-breakpoint); 40 | 41 | font-size: _ty-to-percentage($font-size); 42 | @if $line-height { 43 | line-height: $line-height; 44 | } 45 | } 46 | 47 | // _ty-write-props 48 | // ---------- 49 | // Creates font-size and line-height properties 50 | // ========== 51 | @mixin _ty-write-props($_map, $breakpoint) { 52 | $target-map: map-get($_map, target-map); 53 | $font-map-breakpoint: map-get($target-map, $breakpoint); 54 | $basemap: map-get($_map, basemap); 55 | $rem: map-get($_map, rem); 56 | $typefaces: map-get($_map, typefaces); 57 | $typeface: map-get($_map, typeface); 58 | $baseline-push: map-get($_map, baseline-push); 59 | 60 | $typeface-multiplier: _ty-get-typeface-multiplier($typeface, $typefaces); 61 | $orig-font-size: _ty-get-font-size($font-map-breakpoint); 62 | $_line-height: _ty-get-line-height($font-map-breakpoint); 63 | $_font-size: _ty-calc-font-size( 64 | $font-size: $orig-font-size, 65 | $basemap: $basemap, 66 | $rem: $rem, 67 | $breakpoint: $breakpoint, 68 | $typeface-multiplier: $typeface-multiplier 69 | ); 70 | 71 | font-size: $_font-size; 72 | @if $_line-height { 73 | line-height: $_line-height; 74 | } 75 | 76 | @if $baseline-push { 77 | $_font-size: _ty-calc-font-size( 78 | $font-size: $orig-font-size, 79 | $basemap: $basemap, 80 | $rem: false, 81 | $breakpoint: $breakpoint, 82 | $typeface-multiplier: $typeface-multiplier 83 | ); 84 | @include _ty-write-baseline-push( 85 | $font-size: $_font-size, 86 | $line-height: $_line-height, 87 | $breakpoint: $breakpoint, 88 | $_map: $_map 89 | ); 90 | } 91 | } 92 | 93 | // _ty-write-vr 94 | // ---------- 95 | // Creates values for ms-vr mixin. 96 | // ========== 97 | @mixin _ty-write-vr($_map, $breakpoint) { 98 | $vr: map-get($_map, vr); 99 | $properties: map-get($_map, properties); 100 | $target-map: map-get($_map, target-map); 101 | $basemap: map-get($_map, basemap); 102 | $typeface: map-get($_map, typeface); 103 | $typefaces: map-get($_map, typefaces); 104 | 105 | $font-map-breakpoint: map-get($target-map, $breakpoint); 106 | $step: _ty-get-font-size($font-map-breakpoint); 107 | 108 | @if not unitless($step) { 109 | $target: map-get($_map, target); 110 | @error 'Step #{$step} in #{$target}, #{$breakpoint} must be unitless'; 111 | } 112 | 113 | $font-size: _ty-ms-to-em($step, $breakpoint); 114 | $rhythm: vr($vr, $font-size, $typeface, $basemap, $typefaces: $typefaces); 115 | 116 | @each $property in $properties { 117 | #{$property}: $rhythm; 118 | } 119 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/public/rhythm/_baseline.scss: -------------------------------------------------------------------------------- 1 | // Baseline grid 2 | // ---------- 3 | /// Creates baseline grid 4 | /// @access public 5 | /// @param {Number} $offset [0] - Offsets baseline grid. 6 | /// @param {Number} $baseline [null] - Generates a baseline of your choice. 7 | /// @param {Hex} $color [rgba(blue, 0.15)] - Color of baseline 8 | /// @param {Map} $typi [$typi] - $typi map. 9 | @mixin baseline-grid( 10 | $offset: 0, 11 | $baseline: null, 12 | $color: rgba(blue, 0.25), 13 | $typi: $typi 14 | ) { 15 | // Creates user requested baseline 16 | @if $baseline { 17 | background-image: linear-gradient(to bottom, $color 1px, transparent 0, transparent $baseline); 18 | background-size: 100% $baseline; 19 | background-position: 0 $offset; 20 | } 21 | 22 | // Automatically generate baseline from $typi base 23 | @else { 24 | $base-map: _ty-get-base-map($typi); 25 | $_base-font-size: _ty-get-base-font-size($base-map); 26 | $_base-line-height: _ty-get-base-line-height($base-map); 27 | $_prev-bp-line-height: $_base-line-height; 28 | $baseline: $_base_font-size * $_base-line-height; 29 | 30 | background-image: linear-gradient(to bottom, $color 1px, transparent 0, transparent $baseline); 31 | background-size: 100% $baseline; 32 | background-position: 0 $offset; 33 | 34 | @each $breakpoint, $breakpoint-value in $breakpoints { 35 | @if map-has-key($base-map, $breakpoint) { 36 | $font-map-breakpoint: map-fetch($typi, base $breakpoint); 37 | $_bp-base-font-size: _ty-get-font-size($font-map-breakpoint); 38 | $_bp-line-height: _ty-get-line-height($font-map-breakpoint); 39 | 40 | @if not $_bp-line-height { 41 | $_bp-line-height: $_prev-bp-line-height; 42 | } 43 | 44 | $_bp-baseline: $_bp-base-font-size * $_bp-line-height; 45 | @media all and (min-width: #{$breakpoint-value}) { 46 | background-image: linear-gradient(to bottom, $color 1px, transparent 0, transparent $_bp-baseline); 47 | background-size: 100% $_bp-baseline; 48 | } 49 | 50 | $_prev-bp-line-height: $_bp-line-height; 51 | } 52 | } 53 | } 54 | } 55 | 56 | @mixin baseline-push ( 57 | $typeface: 'primary', 58 | $breakpoints: $breakpoints, 59 | $typi: $typi, 60 | $typi-ms: $typi-ms, 61 | $typi-breakpoint: $typi-breakpoint, 62 | $typefaces: $typefaces 63 | ) { 64 | $base-map: map-get($typi, base); 65 | @if not $base-map { 66 | @error "$typi needs to have a base map"; 67 | } 68 | 69 | $base-font-size: _ty-get-base-font-size($base-map); 70 | 71 | @each $breakpoint, $breakpoint-value in $base-map { 72 | $font-size: _ty-to-em(_ty-get-font-size($breakpoint-value), $base-font-size); 73 | $line-height: _ty-get-line-height($breakpoint-value); 74 | 75 | // Output without breakpoints 76 | @if $breakpoint == null { 77 | @include _ty-write-baseline-push( 78 | $font-size: $font-size, 79 | $line-height: $line-height, 80 | $breakpoint: $breakpoint, 81 | $_map: ( 82 | target-map: $base-map, 83 | basemap: $base-map, 84 | breakpoints: $breakpoints, 85 | typeface: $typeface, 86 | typefaces: $typefaces 87 | ) 88 | ) 89 | } 90 | 91 | // Output with breakpoints 92 | @else { 93 | @include _ty-output-with-breakpoint-library { 94 | @include _ty-write-baseline-push( 95 | $font-size: $font-size, 96 | $line-height: $line-height, 97 | $breakpoint: $breakpoint, 98 | $_map: ( 99 | target-map: $base-map, 100 | basemap: $base-map, 101 | breakpoints: $breakpoints, 102 | typeface: $typeface, 103 | typefaces: $typefaces 104 | ) 105 | ) 106 | } 107 | } 108 | } 109 | } 110 | 111 | 112 | 113 | // @if $baseline-push { 114 | // $basline-push-prop: map-get($_map, baseline-push-prop); 115 | // $_line-height: _ty-get-line-height-for-baseline-push( 116 | // $_line-height, 117 | // $breakpoint, 118 | // $_map 119 | // ); 120 | // $push-amt: _ty-get-baseline-push-amt( 121 | // $font-size: $_font-size, 122 | // $line-height: $_line-height, 123 | // $typeface: $typeface, 124 | // $typefaces: $typefaces 125 | // ); 126 | 127 | // padding-top: $push-amt; 128 | // margin-bottom: $push-amt * -1; 129 | // } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/public/rhythm/_rhythm.scss: -------------------------------------------------------------------------------- 1 | @import 'baseline'; 2 | //// 3 | /// @param {Map} $typefaces [$typefaces] 4 | /// 5 | //// 6 | 7 | // vr 8 | // ---------- 9 | /// Calculates Vertical Rhythm. Can output units either in rem or em 10 | /// @access public 11 | /// @param {Number} $vr - Vertical Rhythm multiple 12 | /// @param {Number} $current-font-size [null] - Current font-size value. Required to output em. 13 | /// @param {String} $typeface [null] - Typeface key in $typefaces. Required to adjust em output according to typeface size 14 | /// @see $typefaces 15 | /// @group core 16 | /// @example 17 | /// .selector { 18 | /// margin-top: vr(3); 19 | /// } 20 | /// @return {Number} - Value to output, either in `em` or `rem` 21 | @function vr( 22 | $vr, 23 | $current-font-size: null, 24 | $typeface: null, 25 | $basemap: map-get($typi, base), 26 | $typefaces: $typefaces 27 | ) { 28 | $base-font-size: _ty-get-base-font-size($basemap); 29 | $base-line-height: _ty-get-base-line-height($basemap); 30 | $rhythm: $vr * $base-line-height; 31 | 32 | // Returns rem values 33 | @if not $current-font-size { 34 | @return _ty-to-rem($rhythm * 1em); 35 | } 36 | 37 | // return em values 38 | @else { 39 | $_rhythm-multiplier: 1; 40 | $_return: 1; 41 | @if $typeface { 42 | $_rhythm-multiplier: 1 / _ty-get-typeface-multiplier($typeface, $typefaces); 43 | } 44 | 45 | @if unit($current-font-size) == 'px' { 46 | $rhythm-px: $rhythm * $base-font-size; 47 | $_return: _ty-to-em($rhythm-px, $current-font-size); 48 | } 49 | @else if unit($current-font-size) == 'em' { 50 | $_return: _ty-to-em($rhythm * 1em, $current-font-size); 51 | } 52 | 53 | 54 | @return $_return * $_rhythm-multiplier; 55 | } 56 | } 57 | 58 | // vr-ms 59 | // ---------- 60 | /// This mixin comes in when you need to write **rhythms** in `em` 61 | /// and you're **changing Modular Scale ratio** at different breakpoints. 62 | /// 63 | /// If you change Modular Scale ratio at different breakpoints, 64 | /// font-sizes will be recalculated, which means there's no way 65 | /// to tell what font-size is present at which breakpoint. This 66 | /// mixin helps to calculate the current font-size, and creates 67 | /// the corresponding rhythm value. 68 | /// 69 | /// @access public 70 | /// 71 | /// @param {List} $properties - Properties to output 72 | /// @param {Number} $vr - Vertical Rhythm multiple 73 | /// @param {String} $target - Font map used 74 | /// @param {String} $typeface [false] - Typeface used 75 | /// @see $typefaces 76 | /// @requires $typi-ms 77 | /// @group core 78 | /// @example 79 | /// .selector { 80 | /// @include ms-vr(margin-top, 2, 'h1'); 81 | /// } 82 | /// @output 83 | /// .selector { 84 | /// margin-top: 3em; 85 | /// } 86 | @mixin vr-ms( 87 | $properties, 88 | $vr, 89 | $target, 90 | $typeface: false, 91 | $typi: $typi, 92 | $typi-ms: $typi-ms, 93 | $typefaces: $typefaces, 94 | $breakpoints: $breakpoints 95 | ) { 96 | @include _ty-write-breakpoints(( 97 | breakpoints: $breakpoints, 98 | output: 'vr', 99 | properties: $properties, 100 | rem: true, 101 | target: $target, 102 | typeface: $typeface, 103 | typefaces: $typefaces, 104 | typi-ms: $typi-ms, 105 | typi: $typi, 106 | vr: $vr 107 | )) 108 | } 109 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/public/typefaces/_typefaces.scss: -------------------------------------------------------------------------------- 1 | //// 2 | /// @author Zell Liew 3 | /// @access public 4 | /// @group helpers-typefaces 5 | /// @requires $typefaces 6 | /// @param {Map} $typefaces [$typefaces] - $typefaces map 7 | //// 8 | 9 | // fw 10 | // ---------- 11 | /// Creates font weight property from $typefaces map. 12 | /// @param {String} $typeface - typeface key 13 | /// @param {String} $weight - key of weight 14 | /// @output font-weight: 300; 15 | @mixin fw( 16 | $typeface, 17 | $weight, 18 | $typefaces: $typefaces 19 | ) { 20 | @if type-of($typefaces) != 'map' { 21 | @error "Cannot find #{$typefaces} map found"; 22 | } 23 | 24 | font-weight: map-fetch($typefaces, $typeface weights $weight); 25 | } 26 | 27 | // ff 28 | // ---------- 29 | /// Creates font family property from $typefaces map. 30 | /// @param {String} $typeface - typeface key 31 | /// @output font-family: 'font-stack' 32 | @mixin ff( 33 | $typeface, 34 | $typefaces: $typefaces 35 | ) { 36 | @if type-of($typefaces) != 'map' { 37 | @error "Cannot find #{$typefaces} map found"; 38 | } 39 | 40 | font-family:map-fetch($typefaces, $typeface stack); 41 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/public/typi/_create-class.scss: -------------------------------------------------------------------------------- 1 | // typi-creates-classes 2 | // ---------- 3 | /// Typi helps create classes according to font-map keys 4 | /// @author Zell Liew 5 | /// @access public 6 | /// @param {Map} $typi [$typi] - $typi map 7 | @mixin typi-create-classes($typi: $typi) { 8 | @each $map, $values in $typi { 9 | .#{$map} { 10 | @include typi($map); 11 | } 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/public/typi/_init.scss: -------------------------------------------------------------------------------- 1 | // Typi Initializer 2 | // ---------- 3 | // - Creates base map 4 | // - Calls extender (if typi-ms is used) 5 | // ========== 6 | 7 | @mixin typi-init( 8 | $typi: $typi, 9 | $breakpoints: $breakpoints, 10 | $typi-ms: $typi-ms, 11 | $typefaces: $typefaces 12 | ) { 13 | $_updated-typi: $typi; 14 | $_updated-typi-ms: $typi-ms; 15 | 16 | // Uncomment when extender is completed (Requires libsass#3.4) 17 | // Only invokve extender if $typi-ms is present 18 | // $typi-ms and $typi map must be their original names 19 | // @if type-of($typi-ms) == 'map' { 20 | // @each $key, $value in $typi-ms { 21 | // $_updated-typi-ms: _ty-map-extender($typi-ms: $typi-ms); 22 | // } 23 | // @each $key, $value in $typi { 24 | // $_updated-typi: _ty-map-extender($key); 25 | // } 26 | // } 27 | 28 | @if type-of($typi-ms) == 'map' { 29 | @include _ty-check-extended-map; 30 | } 31 | 32 | // Is there a need for primary typeface? Commented until there really is. 33 | // @if type-of($typefaces) == 'map' { 34 | // $_has-primary-typeface: _ty-has-typeface('primary', $typefaces); 35 | // } 36 | 37 | @include typi-base( 38 | $typi: $_updated-typi, 39 | $typi-ms: $_updated-typi-ms, 40 | $breakpoints: $breakpoints 41 | ) 42 | } 43 | 44 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/scss/public/typi/_typi.scss: -------------------------------------------------------------------------------- 1 | @import 'init'; 2 | @import 'create-class'; 3 | 4 | //// 5 | /// @author Zell Liew 6 | /// @access public 7 | /// @param {Map} $typi [$typi] - $typi map 8 | /// @param {Map} $typi-ms [$typi-ms] - $typi-ms map 9 | /// @param {Map} $breakpoints [$breakpoints] - $breakpoints map 10 | /// @group Core 11 | //// 12 | 13 | // typi 14 | // ---------- 15 | /// Creates font-size and line-height properties 16 | /// @author Zell Liew 17 | /// @access public 18 | /// @param {String} $target - font-map key 19 | /// @param {String} $typeface ['primary'] - typeface key 20 | /// @param {Bool} $baseline [false] - Pushes to baseline with padding-top and margin-bottom props 21 | /// @param {Bool} $rem [true] - Outputs rem or em 22 | /// @param {Map} $typi-breakpoint [$typi-breakpoint] - Breakpoint library 23 | /// @param {Map} $typefaces [$typefaces] - $typefaces map 24 | @mixin typi ( 25 | $target, 26 | $typeface: 'primary', 27 | $baseline: false, 28 | $rem: true, 29 | $typi: $typi, 30 | $typi-ms: $typi-ms, 31 | $typi-breakpoint: $typi-breakpoint, 32 | $typefaces: $typefaces, 33 | $breakpoints: $breakpoints 34 | ) { 35 | @include _ty-write-breakpoints(( 36 | baseline-push: $baseline, 37 | breakpoints: $breakpoints, 38 | breakpoint-lib: $typi-breakpoint, 39 | output: 'props', 40 | rem: $rem, 41 | target: $target, 42 | typeface: $typeface, 43 | typefaces: $typefaces, 44 | typi-ms: $typi-ms, 45 | typi: $typi 46 | )) 47 | } 48 | 49 | // Typi-base 50 | // ---------- 51 | /// Creates styles for root selector (defaults to HTML) 52 | /// @author Zell Liew 53 | /// @access public 54 | /// @param {String} $selector [html] - Default selector for root font-size and line-height 55 | @mixin typi-base( 56 | $selector: 'html', 57 | $typi: $typi, 58 | $typi-ms: $typi-ms, 59 | $breakpoints: $breakpoints 60 | ) { 61 | #{$selector} { 62 | @include typi( 63 | $target: base, 64 | $typi: $typi, 65 | $typi-ms: $typi-ms, 66 | $breakpoints: $breakpoints, 67 | $rem: false 68 | ) 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/automated/_baseline.scss: -------------------------------------------------------------------------------- 1 | // Ensures has $typefaces map 2 | // Ensures has cap-height property 3 | // $baseline-push option 4 | // - Needs to output posrelative, top x 5 | // - Needs to calculate how much top to push 6 | // - Top to push is determined by cap-height, font-size and line-height. 7 | // - Need to calculate amount to push 8 | // 9 | // $adjust-to mixin 10 | // - Used specifically if there's a need change baseline by an amount that 11 | // you need to calculate weirdly. Every 5th line will hit sit on the baseline. 12 | // Basically, every 5th line sits on the same line as target text. 13 | // - Needs to calculate amount to adjust 14 | // - Needs to incorp $baseline option 15 | 16 | $breakpoints: ( 17 | small: 'small', 18 | med: 'med', 19 | large: 'large', 20 | ); 21 | 22 | $typi: ( 23 | base: ( 24 | null: (16px, 1.4), 25 | small: 18px, 26 | med: 20px, // TODO: remove this and will break. Must fix. 27 | large: 24px 1.5 28 | ), 29 | 30 | h1: ( 31 | null: 20px, 32 | med: 25px, 33 | large: 30px 34 | ), 35 | 36 | test2: ( 37 | null: (30px, 1.2), 38 | small: 40px, 39 | med: (40px, 1.3), 40 | large: (40px) 41 | ), 42 | ); 43 | 44 | $typefaces: ( 45 | primary: ( 46 | cap-height: 0.85 47 | ) 48 | ); 49 | 50 | $basemap: map-get($typi, base); 51 | 52 | // Text needs to move down by half the difference between `line-height` and `cap-height`. 53 | // Line height will change if font-size change... Based on the font-size, I can find the line-height. 54 | // Based on font-size, I can also find the cap height. 55 | // So, I only need to change line-height when line-height changes 56 | @include test-module('Baseline Push') { 57 | @include test('Push amount should be equal to (computed line-height - computed cap-height) / 2') { 58 | $font-size: _ty-calc-font-size( 59 | $font-size: map-fetch($typi, h1 null), 60 | $basemap: $basemap, 61 | $rem: false 62 | ); 63 | $line-height: nth(map-fetch($typi, base null), 2); 64 | $cap-height: map-fetch($typefaces, primary cap-height); 65 | $test: _ty-get-baseline-push-amt($font-size, $line-height, 'primary'); 66 | $result: $font-size * ($line-height - $cap-height) * 0.5; 67 | @include assert-equal($test, $result); 68 | } 69 | 70 | @include test('should obtain the right line-height for calculation') { 71 | $line-height: nth(map-fetch($typi, test2 med), 2); 72 | $test: _ty-get-line-height-for-baseline-push($line-height); 73 | $result: $line-height; 74 | @include assert-equal($line-height, $result, 'should use own line-height if present'); 75 | 76 | $test: _ty-get-line-height-for-baseline-push( 77 | null, 78 | $breakpoint: med, 79 | $_map: ( 80 | target-map: map-get($typi, test2) 81 | ) 82 | ); 83 | $line-height: nth(map-fetch($typi, test2 null), 2); 84 | $result: $line-height; 85 | @include assert-equal($test, $result, 'fallback to own line-height from previous breakpoint'); 86 | 87 | $test: _ty-get-line-height-for-baseline-push( 88 | null, 89 | $breakpoint: large, 90 | $_map: ( 91 | target-map: map-get($typi, h1), 92 | basemap: map-get($typi, base) 93 | ) 94 | ); 95 | $line-height: nth(map-fetch($typi, base large), 2); 96 | $result: $line-height; 97 | @include assert-equal($test, $result, 'fallback to line-height from base map at same breakpoint'); 98 | 99 | 100 | $test: _ty-get-line-height-for-baseline-push( 101 | null, 102 | $breakpoint: med, 103 | $_map: ( 104 | target-map: map-get($typi, h1), 105 | basemap: map-get($typi, base) 106 | ) 107 | ); 108 | $line-height: nth(map-fetch($typi, base null), 2); 109 | $result: $line-height; 110 | @include assert-equal($test, $result, 'fallback to line-height from base map at prev breakpoint'); 111 | } 112 | } 113 | 114 | // Test output 115 | // Unable to test cos of how Sass true is structured 116 | // @include test-module('Baseline Push') { 117 | // @include test('should output padding-top and margin-bottom properties') { 118 | // @include assert('for h1 map') { 119 | // @include input { 120 | // @include typi('h1', $baseline: true); 121 | // } 122 | // @include expect { 123 | // // Lazy to calc fz. 124 | // font-size: 1.25rem; 125 | // $lh: nth(map-fetch($typi, base null),2); 126 | // $ch: map-fetch($typefaces, primary cap-height); 127 | // $val: 1.25em * ($lh - $ch) * 0.5; 128 | // padding-top: $val; 129 | // margin-bottom: -$val; 130 | // } 131 | // } 132 | // } 133 | // } 134 | 135 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/automated/_breakpoints.scss: -------------------------------------------------------------------------------- 1 | // Test breakpoints 2 | // ---------- 3 | // - Ensure to loop through each map in target 4 | // - Ensure each breakpoint in target is present in $brekapoints 5 | // - Ensure each breakpoint in target is present in $typi 6 | // ========= 7 | $breakpoints: ( 8 | small: 400px, 9 | med: 600px, 10 | large: 800px, 11 | ); 12 | 13 | $typi: ( 14 | base: ( 15 | null: (18px, 1.5), 16 | small: 20px, 17 | med: 22px, 18 | large: (24px, 1.6) 19 | ), 20 | h1: ( 21 | null: (24px, 1.3), 22 | med: 2em, 23 | large: (3em), 24 | ) 25 | ); 26 | 27 | $basemap: map-get($typi, base); 28 | 29 | @include test-module('Has breakpoint') { 30 | @include test('ensures basemap has target breakpoint') { 31 | // If you create a huge key, this will break 32 | $test: _ty-has-breakpoint( 33 | $breakpoint: large, 34 | $breakpoints: $breakpoints, 35 | $basemap: $basemap 36 | ); 37 | @include assert-equal($test, true); 38 | } 39 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/automated/_calc-font-sizes.scss: -------------------------------------------------------------------------------- 1 | // Test Calculation of font-sizes 2 | // ---------- 3 | // - px -> rem 4 | // - em -> rem 5 | // - px -> em 6 | // - rem -> em 7 | // - px -> % 8 | // - unitless -> em (For typi-ms) 9 | // - unitless -> rem (For typi-ms) 10 | // ========== 11 | 12 | $breakpoints: ( 13 | small: 800px, 14 | med: 1000px, 15 | large: 1200px, 16 | ); 17 | 18 | $typi: ( 19 | base: ( 20 | null: (18px, 1.5), 21 | small: 20px, 22 | med: 22px, 23 | large: (24px, 1.6) 24 | ) 25 | ); 26 | 27 | $typi-ms: ( 28 | small: 1.2, 29 | med: 1.5, 30 | large: 1.8 31 | ); 32 | 33 | $basemap: map-get($typi, base); 34 | 35 | @include test-module('Calculate Typi font-size in Rem') { 36 | @include test('em -> rem') { 37 | $test: _ty-calc-font-size( 38 | $font-size: 2em, 39 | $basemap: $basemap, 40 | $rem: true 41 | ); 42 | 43 | @include assert-equal($test, 2rem, 'em should be converted into rem'); 44 | } 45 | 46 | @include test('px -> rem') { 47 | $test: _ty-calc-font-size( 48 | $font-size: 20px, 49 | $basemap: $basemap, 50 | $rem: true 51 | ); 52 | $base-size: nth(map-fetch($typi, base null), 1); 53 | $result: 20px / $base-size * 1rem; 54 | @include assert-equal($test, $result, 'px should be converted into rem'); 55 | } 56 | } 57 | 58 | @include test-module('Calculate Typi font-size in em') { 59 | @include test('rem -> em') { 60 | $test: _ty-calc-font-size( 61 | $font-size: 2rem, 62 | $basemap: $basemap, 63 | $rem: false 64 | ); 65 | $result: 2em; 66 | @include assert-equal($test, $result, 'rem should be converted into em'); 67 | } 68 | 69 | @include test('px -> em') { 70 | $test: _ty-calc-font-size( 71 | $font-size: 20px, 72 | $basemap: $basemap, 73 | $rem: false 74 | ); 75 | $base-size: nth(map-fetch($typi, base null), 1); 76 | $result: 20px / $base-size * 1em; 77 | @include assert-equal($test, $result); 78 | } 79 | } 80 | 81 | @include test-module('Calculate font-size in %') { 82 | @include test('px -> %') { 83 | $test: _ty-to-percentage(20px); 84 | $result: 125%; 85 | @include assert-equal($test, $result, 'px should be converted into % with 16px as base'); 86 | } 87 | } 88 | 89 | @include test-module('Calculate unitless Typi font-size') { 90 | @include test('unitless -> rem') { 91 | $test: _ty-ms-to-rem(2, small); 92 | $ratio: map-get($typi-ms, small); 93 | $result: _ty-strip-unit(ms(2, 1em, $ratio)) * 1rem; 94 | @include assert-equal($test, $result); 95 | } 96 | 97 | @include test('unitless -> em') { 98 | $test: _ty-ms-to-em(2, med); 99 | $ratio: map-get($typi-ms, med); 100 | $result: ms(2, 1em, $ratio); 101 | @include assert-equal($test, $result); 102 | } 103 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/automated/_rhythm-typi-ms.scss: -------------------------------------------------------------------------------- 1 | $typi: ( 2 | base: ( 3 | null: (16px, 1.5), 4 | small: 20px, 5 | ), 6 | h1: ( 7 | null: (1, 1.3), 8 | small: 1, 9 | med: 2, 10 | ) 11 | ); 12 | 13 | $breakpoints: ( 14 | small: 'small', 15 | med: 'med', 16 | ); 17 | 18 | $typi-ms: ( 19 | null: 1.2, 20 | small: 1.3, 21 | med: 1.4, 22 | ); 23 | 24 | 25 | // unable to test due to new way Sass True is structured 26 | // @include test-module('VR with Typi-ms') { 27 | // @include test('With _ty-write-vr()') { 28 | // $step: nth(map-fetch($typi, h1 small), 1); 29 | // $base: 1em; 30 | // $ratio: map-get($typi-ms, small); 31 | // $current-font-size: ms($step, $base, $ratio); 32 | // $base-line-height: nth(map-fetch($typi, base null), 2); 33 | // $vr: 2 * $base-line-height / _ty-strip-unit($current-font-size) * 1em; 34 | 35 | // @include assert('write one property') { 36 | // @include output { 37 | // .testing { 38 | // @include _ty-write-vr(( 39 | // vr: 2, 40 | // properties: margin-top, 41 | // target-map: map-get($typi, 'h1'), 42 | // basemap: map-get($typi, 'base') 43 | // ), small); 44 | // } 45 | // } 46 | 47 | // @include expect { 48 | // margin-top: 2.5em; 49 | // @media all and (min-width: small) { 50 | // .tests-output { 51 | // margin-top: 2.30769em; 52 | // } 53 | // } 54 | // } 55 | // @media all and (min-width: med) { 56 | // .testing { 57 | // margin-top: 1.09329em; } } 58 | 59 | // } 60 | 61 | // @include assert('write multiple properties') { 62 | // @include output { 63 | // @include _ty-write-vr(( 64 | // vr: 2, 65 | // properties: margin-top margin-bottom, 66 | // target-map: map-get($typi, 'h1'), 67 | // basemap: map-get($typi, 'base') 68 | // ), small); 69 | // } 70 | 71 | // @include expect { 72 | // margin-top: $vr; 73 | // margin-bottom: $vr; 74 | // } 75 | // } 76 | // } 77 | 78 | // @include test('With ms-vr()') { 79 | // $step: nth(map-fetch($typi, h1 null), 1); 80 | // $base: 1em; 81 | // $ratio: map-get($typi-ms, null); 82 | // $current-font-size: ms($step, $base, $ratio); 83 | // $base-line-height: nth(map-fetch($typi, base null), 2); 84 | // $vr: 2 * $base-line-height / _ty-strip-unit($current-font-size) * 1em; 85 | 86 | // @include assert('write one property') { 87 | // @include output { 88 | // @include vr-ms(margin-top, 2, $target: 'h1'); 89 | // } 90 | 91 | // @include expect { 92 | // margin-top: $vr; 93 | // } 94 | // } 95 | 96 | // @include assert('write multiple properties') { 97 | // @include output { 98 | // @include vr-ms(margin-top margin-bottom, 2, $target: 'h1'); 99 | // } 100 | 101 | // @include expect { 102 | // margin-top: $vr; 103 | // margin-bottom: $vr; 104 | // } 105 | // } 106 | // } 107 | // // manual test for all breakpoints 108 | // } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/automated/_rhythm.scss: -------------------------------------------------------------------------------- 1 | // Vertical Rhythms 2 | // ---------------- 3 | // Test Vertical Rhythm calculations 4 | // - Normal VR 5 | // ================ 6 | $typi: ( 7 | base: ( 8 | null: (18px, 1.5), 9 | small: 20px, 10 | ), 11 | h1: ( 12 | null: (0, 1.3), 13 | small: 1, 14 | med: 2, 15 | large: 3, 16 | huge: 4 17 | ) 18 | ); 19 | 20 | $basemap: map-get($typi, base); 21 | $base-font-size: nth(map-fetch($typi, base null), 1); 22 | $base-line-height: nth(map-fetch($typi, base null), 2); 23 | 24 | @include test-module('Normal VR') { 25 | @include test('-> rem') { 26 | $test: vr(3); 27 | $result: $base-line-height * 3 * 1rem; 28 | @include assert-equal($test, $result); 29 | } 30 | 31 | @include test('px -> em') { 32 | $test: vr(3, 18px); 33 | $result: 3 * $base-font-size * $base-line-height / 18px * 1em; 34 | @include assert-equal($test, $result); 35 | 36 | $test2: vr(2, 30px); 37 | $result2: 2 * $base-font-size * $base-line-height / 30px * 1em; 38 | @include assert-equal($test2, $result2) 39 | } 40 | 41 | @include test('em -> em') { 42 | $test: vr(2, 1.25em); 43 | $result: 3em / 1.25em * 1em; 44 | @include assert-equal($test, $result); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/automated/_typi-base.scss: -------------------------------------------------------------------------------- 1 | // Tests output of typi-base for one media query 2 | // ========== 3 | 4 | $typi: ( 5 | base: ( 6 | null: (18px, 1.5), 7 | small: 20px, 8 | med: 22px, 9 | large: (24px, 1.6) 10 | ) 11 | ); 12 | 13 | // unable to test due to new way Sass True is structured 14 | // @include test-module('Typi-Base') { 15 | // @include test('should output font-size and line-height') { 16 | // @include assert('font-size must be in %') { 17 | // @include output { 18 | // @include typi-base(); 19 | // } 20 | 21 | // @include expect { 22 | // $base-null: map-fetch($typi, base null); 23 | // $base-font-size: nth($base-null, 1); 24 | // $base-line-height: nth($base-null, 2); 25 | // font-size: $base-font-size / 16px * 100%; 26 | // line-height: $base-line-height; 27 | // } 28 | // } 29 | // } 30 | // } 31 | 32 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/automated/_typi-ms.scss: -------------------------------------------------------------------------------- 1 | $breakpoints: ( 2 | single-ratio: 400px, 3 | double-ratio: 600px, 4 | double-base: 800px, 5 | double-base-2: 800px, 6 | single-both: 1000px, 7 | double-both: 1200px, 8 | large: 400px 9 | ); 10 | 11 | $typi: ( 12 | base: ( 13 | null: (18px, 1.5), 14 | large: (24px, 1.6) 15 | ), 16 | h1: ( 17 | null: (2, 1.3), 18 | large: (3, 1.3) 19 | ) 20 | ); 21 | 22 | $typi-ms: ( 23 | single-ratio: (1.2), 24 | double-ratio: (1.2 1.5), 25 | double-base: (2em 3em 1.2), 26 | double-base-2: (1em 2em 1.2), 27 | single-both: (2em 1.2), 28 | double-both: (2em 3em 1.2 1.5), 29 | large: 1.2 30 | ); 31 | 32 | @include test-module('Typi MS Calculations') { 33 | @include test('for 1 ratio') { 34 | $test: _ty-calc-ms(2, single-ratio); 35 | $base: 1em; 36 | $ratio: map-get($typi-ms, single-ratio); 37 | $result: ms(2, $base, $ratio); 38 | @include assert-equal($test, $result); 39 | } 40 | 41 | @include test('for 2 ratios') { 42 | $test: _ty-calc-ms(2, double-ratio); 43 | $base: 1em; 44 | $ratio: map-get($typi-ms, double-ratio); 45 | $result: ms(2, $base, $ratio); 46 | @include assert-equal($test, $result); 47 | } 48 | 49 | // Hardcoded test 50 | @include test('for 2 bases') { 51 | $test: _ty-calc-ms(2, double-base); 52 | $result: ms(2, 2em 3em, 1.2); 53 | @include assert-equal($test, $result); 54 | 55 | $test2: _ty-calc-ms(1, double-base-2); 56 | $result2: ms(1, 1em 2em, 1.2); 57 | @include assert-equal($test2, $result2); 58 | } 59 | 60 | // Hardcoded test 61 | @include test('for 1 ratio 1 base') { 62 | $test: _ty-calc-ms(2, single-both); 63 | $result: ms(2, 2em, 1.2); 64 | @include assert-equal($test, $result); 65 | } 66 | 67 | // Hardcoded test 68 | @include test('for 2 base 2 ratio') { 69 | $test: _ty-calc-ms(2, double-both); 70 | $result: ms(2, 2em 3em, 1.2 1.5); 71 | @include assert-equal($test, $result); 72 | } 73 | } 74 | 75 | @include test-module('Writing Typi MS props') { 76 | @include test('should output font-size and line-height') { 77 | @include assert('') { 78 | @include input { 79 | @include _ty-output-props(( 80 | target: 'h1', 81 | target-map: map-get($typi, 'h1'), 82 | basemap: map-get($typi, 'base'), 83 | rem: false, 84 | output: props 85 | ), $breakpoint: large); 86 | } 87 | 88 | @include expect() { 89 | $ratio: map-get($typi-ms, large); 90 | $step: nth(map-fetch($typi, 'h1' 'large'), 1); 91 | $line-height: nth(map-fetch($typi, 'h1' 'large'), 2); 92 | font-size: ms($step, 1em, $ratio); 93 | line-height: $line-height; 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/automated/_write-props.scss: -------------------------------------------------------------------------------- 1 | // Test writing of properties 2 | // ========== 3 | $typi: ( 4 | base: ( 5 | null: (18px, 1.5), 6 | small: 20px, 7 | med: 22px, 8 | large: (24px, 1.6) 9 | ), 10 | h1: ( 11 | null: (24px, 1.3), 12 | med: 2em, 13 | large: (3em) 14 | ) 15 | ); 16 | 17 | $basemap: map-get($typi, base); 18 | 19 | @include test-module('Write properties') { 20 | @include test('font-size only') { 21 | @include assert('h1 null breakpoint') { 22 | @include input { 23 | @include _ty-output-props(( 24 | target: 'h1', 25 | target-map: map-get($typi, 'h1'), 26 | basemap: $basemap, 27 | rem: true, 28 | output: props 29 | ), $breakpoint: null 30 | ); 31 | } 32 | 33 | @include expect { 34 | $base: map-fetch($typi, base null); 35 | $base-font-size: nth($base, 1); 36 | $h1: map-fetch($typi, h1 null); 37 | $font-size: nth($h1, 1); 38 | $line-height: nth($h1, 2); 39 | font-size: _ty-to-rem($font-size, $base-font-size); 40 | line-height: $line-height; 41 | } 42 | } 43 | } 44 | 45 | @include test('font-size and line-height') { 46 | @include assert('h1 med breakpoint') { 47 | @include input { 48 | @include _ty-output-props(( 49 | target: 'h1', 50 | target-map: map-get($typi, 'h1'), 51 | basemap: $basemap, 52 | rem: true, 53 | output: props 54 | ), $breakpoint: med 55 | ); 56 | } 57 | 58 | @include expect { 59 | $base-size: map-fetch($typi, h1 med); 60 | font-size: _ty-to-rem($base-size); 61 | } 62 | } 63 | } 64 | 65 | @include test('list type for font-size in map') { 66 | @include assert('h1 large breakpoint') { 67 | @include input { 68 | @include _ty-output-props(( 69 | target: 'h1', 70 | target-map: map-get($typi, 'h1'), 71 | basemap: $basemap, 72 | rem: true, 73 | output: props 74 | ), $breakpoint: large 75 | ) 76 | } 77 | 78 | @include expect { 79 | $base-size: map-fetch($typi, h1 large); 80 | font-size: _ty-to-rem($base-size); 81 | } 82 | } 83 | } 84 | 85 | @include test('write base font-size') { 86 | @include assert('base') { 87 | @include input() { 88 | @include _ty-output-props(( 89 | target: 'base', 90 | target-map: map-get($typi, 'base'), 91 | basemap: $basemap, 92 | rem: false 93 | ), $breakpoint: null 94 | ) 95 | } 96 | 97 | @include expect() { 98 | font-size: 112.5%; 99 | line-height: 1.5; 100 | } 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/automated/extender.scss: -------------------------------------------------------------------------------- 1 | @import 'true'; 2 | @import 'modular-scale/stylesheets/_modular-scale'; 3 | @import 'scss/typi'; 4 | 5 | // Test individual extension of maps 6 | // ========== 7 | $breakpoints: ( 8 | small: 400px, 9 | med: 600px, 10 | large: 800px 11 | ); 12 | 13 | $typi: ( 14 | base: ( 15 | null: (18px, 1.5), 16 | large: (24px, 1.6) 17 | ), 18 | h1: ( 19 | null: (24px, 1.3), 20 | ) 21 | ); 22 | 23 | $typi-ms: ( 24 | null: 1.2, 25 | large: 1.3 26 | ); 27 | 28 | @include test-module('Extend $typi-ms map') { 29 | @include test('') { 30 | $_overwrite: _ty-map-extender(); 31 | $test: $typi-ms; 32 | $result: ( 33 | null: 1.2, 34 | small: 1.2, 35 | med: 1.2, 36 | large: 1.3 37 | ); 38 | @include assert-equal($test, $result); 39 | } 40 | } 41 | 42 | @include test-module('Extend $typi map') { 43 | @include test('overwrites $typi map with missing breakpoints') { 44 | $overwriting: _ty-map-extender('base'); 45 | $test: $typi; 46 | $result: ( 47 | base: ( 48 | null: (18px, 1.5), 49 | large: (24px, 1.6), 50 | ), 51 | h1: ( 52 | null: (24px, 1.3) 53 | ) 54 | ); 55 | @include assert-equal($test, $result, 'base should not extend'); 56 | 57 | $overwriting: _ty-map-extender('h1'); 58 | $test2: $typi; 59 | $result2: ( 60 | base: ( 61 | null: (18px, 1.5), 62 | large: (24px, 1.6), 63 | ), 64 | h1: ( 65 | null: (24px, 1.3), 66 | small: (24px, 1.3), 67 | med: (24px, 1.3), 68 | large: (24px, 1.3) 69 | ) 70 | ); 71 | @include assert-equal($test2, $result2, 'for h1') 72 | } 73 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/automated/test.scss: -------------------------------------------------------------------------------- 1 | @import 'true'; 2 | @import 'bower_components/modular-scale/stylesheets/_modular-scale'; 3 | @import 'scss/typi'; 4 | 5 | @import 'breakpoints'; 6 | @import 'calc-font-sizes'; 7 | @import 'rhythm'; 8 | @import 'rhythm-typi-ms'; 9 | @import 'typefaces'; 10 | @import 'typi-base'; 11 | @import 'typi-ms'; 12 | @import 'write-props'; 13 | @import 'baseline'; 14 | 15 | // @import 'rvr'; 16 | // @include report; 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/manual/baseline.scss: -------------------------------------------------------------------------------- 1 | @import 'scss/typi'; 2 | @import 'mappy-breakpoints/mappy-breakpoints'; 3 | @import 'compass-breakpoint/stylesheets/breakpoint'; 4 | 5 | $breakpoints: ( 6 | small: 400px, 7 | med: 1000px 8 | ); 9 | 10 | $typi: ( 11 | base: ( 12 | null: (16px, 1.5), 13 | small: 18px, 14 | med: (20px) 15 | ), 16 | h1: ( 17 | null: (24px, 1.3), 18 | small: 26px, 19 | med: 30px 20 | ) 21 | ); 22 | 23 | $typefaces: ( 24 | primary: ( 25 | cap-height: 0.66 26 | ) 27 | ); 28 | 29 | @include typi-init; 30 | 31 | // TODO: Baseline calculation error problem! Ugh. 32 | // Note: Baseline padding-top should be twice the current amount, but margin-bottom should remain the same. 33 | // Probably need to use difference between font-size and line-height as margin-bottom? Not sure. Need to explore, but definitely after the workshop. 34 | h1 { 35 | margin: 0; 36 | margin-top: vr(2); 37 | @include typi('h1', $baseline: true); 38 | } 39 | 40 | p { 41 | margin: 0; 42 | margin-top: vr(1); 43 | @include baseline-push(); 44 | } 45 | 46 | body { 47 | font-family: Helvetica; 48 | max-width: 600px; 49 | margin: 0 auto; 50 | @include baseline-grid(); 51 | } 52 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/manual/bp.scss: -------------------------------------------------------------------------------- 1 | @import 'scss/typi'; 2 | @import 'mappy-breakpoints/mappy-breakpoints'; 3 | @import 'compass-breakpoint/stylesheets/breakpoint'; 4 | @import 'sass-mq/mq'; 5 | 6 | $breakpoints: ( 7 | small: 400px, 8 | med: 600px, 9 | large: 800px, 10 | ); 11 | 12 | $mq-breakpoints: ( 13 | small: 400px, 14 | med: 600px, 15 | large: 800px, 16 | ); 17 | 18 | $typi: ( 19 | base: ( 20 | null: (18px, 1.5), 21 | small: 20px, 22 | med: 22px, 23 | large: (24px, 1.6) 24 | ), 25 | h1: ( 26 | null: (24px, 1.3), 27 | med: 30px, 28 | large: (3em) 29 | ) 30 | ); 31 | 32 | /* Test with normal breakpoints */ 33 | .h1 { 34 | @include typi(h1); 35 | } 36 | 37 | /* Test with mappy bp */ 38 | $typi-breakpoint: mappy-bp; 39 | .h1 { 40 | @include typi('h1') 41 | } 42 | 43 | /* Test with breakpoint-sass */ 44 | $typi-breakpoint: breakpoint; 45 | .h1 { 46 | @include typi(h1); 47 | } 48 | 49 | /* Test with sass-mq */ 50 | $typi-breakpoint: mq; 51 | .h1 { 52 | @include typi(h1); 53 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/manual/create-class.scss: -------------------------------------------------------------------------------- 1 | @import 'scss/typi'; 2 | @import 'modular-scale/stylesheets/_modular-scale'; 3 | 4 | $typi: ( 5 | base: ( 6 | null: (16px, 1.5) 7 | ), 8 | h1: ( 9 | null: (ms(4), 1.3) 10 | ) 11 | ); 12 | 13 | /** 14 | * Should create two classes, .base and .h1 15 | */ 16 | @include typi-create-classes; 17 | 18 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/manual/typi-base.scss: -------------------------------------------------------------------------------- 1 | @import 'scss/typi'; 2 | 3 | $breakpoints: ( 4 | small: 'small', 5 | med: 'med', 6 | large: 'large', 7 | ); 8 | 9 | $typi: ( 10 | base: ( 11 | null: (18px, 1.5), 12 | small: 20px, 13 | med: 22px, 14 | large: (24px, 1.6) 15 | ), 16 | h1: ( 17 | null: (24px, 1.3), 18 | med: 30px, 19 | large: (3em) 20 | ) 21 | ); 22 | 23 | /** 24 | * Tests Output of Typi-init 25 | * ------------------------- 26 | * Should output: 27 | * - no breakpoint: font-size: 112.5%; 28 | * line-height: 1.5; 29 | * - small breakpoint: font-size: 125%; 30 | * - med breakpoint: font-size: 137.5%; 31 | * - large breakpoint: font-size: 150%; 32 | * line-height: 1.6; 33 | */ 34 | @include typi-init; 35 | 36 | /** 37 | * Tests selector of typi-base 38 | * --------------------------- 39 | * Selector should match $selector 40 | */ 41 | @include typi-base($selector: 'body'); -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/manual/typi-ms-vr.scss: -------------------------------------------------------------------------------- 1 | @import 'modular-scale/stylesheets/_modular-scale'; 2 | @import 'scss/typi'; 3 | 4 | $typi: ( 5 | base: ( 6 | null: (16px, 1.5), 7 | small: 20px, 8 | ), 9 | h1: ( 10 | null: (1, 1.3), 11 | small: 1, 12 | med: 3, 13 | ) 14 | ); 15 | 16 | $breakpoints: ( 17 | small: 'small', 18 | med: 'med', 19 | ); 20 | 21 | $typi-ms: ( 22 | null: 1.2, 23 | small: 1.3, 24 | med: 1.4, 25 | ); 26 | 27 | .testing { 28 | @include vr-ms(margin-top, 2, $target: 'h1'); 29 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/manual/typi-ms.scss: -------------------------------------------------------------------------------- 1 | @import 'bower_components/modular-scale/stylesheets/_modular-scale'; 2 | @import 'scss/typi'; 3 | 4 | $breakpoints: ( 5 | small: 'small', 6 | med: 'med', 7 | large: 'large', 8 | huge: 'huge', 9 | ); 10 | 11 | $typi-ms: ( 12 | null: 1.2, 13 | small: 1.2, 14 | med: (1.2 1.5), 15 | large: (1em 2em 1.2), 16 | huge: (1.5em 1.2 1.5) 17 | ); 18 | 19 | $typi: ( 20 | base: ( 21 | null: (18px, 1.5), 22 | small: 20px, 23 | ), 24 | h2: ( 25 | null: (0, 1.3), 26 | small: 1, 27 | med: 2, 28 | large: 3, 29 | huge: 4 30 | ) 31 | ); 32 | 33 | /** 34 | * Inits. 35 | * ------------------- 36 | * Should call typi-base 37 | * Should only produce base and small breakpoints 38 | * Should check $typi and $typi-ms maps for breakpoints. (comment any breakpoint to see warning) 39 | */ 40 | @include typi-init; 41 | 42 | /** 43 | * Creates sizes in rem 44 | * ------------------- 45 | * null should produce: 1rem 46 | * small should produce: 1.2rem 47 | * med should produce: 1.44rem http://www.modularscale.com/?1&em&1.2,1.5&web&text 48 | * large should produce: 1.38889rem http://www.modularscale.com/?1,2&em&1.2&web&text 49 | * huge should produce: 2.592rem http://www.modularscale.com/?1.5&em&1.2,1.5&web&text 50 | * =================== 51 | */ 52 | 53 | h2 { 54 | @include typi('h2'); 55 | } 56 | 57 | /** 58 | * Creates sizes in em 59 | * ------------------- 60 | * same as above, but in em 61 | * =================== 62 | */ 63 | 64 | h2 { 65 | @include typi('h2', $rem: false); 66 | } -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/manual/typi.scss: -------------------------------------------------------------------------------- 1 | @import 'scss/typi'; 2 | 3 | $breakpoints: ( 4 | small: 'small', 5 | med: 'med', 6 | large: 'large', 7 | ); 8 | 9 | $typi: ( 10 | base: ( 11 | null: (18px, 1.5), 12 | small: 20px, 13 | med: 22px, 14 | large: (24px, 1.6) 15 | ), 16 | h1: ( 17 | null: (24px, 1.3), 18 | med: 30px, 19 | large: (3em) 20 | ) 21 | ); 22 | 23 | /** 24 | * Tests Output of Typi mixin 25 | * ------------------------- 26 | * Should output: 27 | * - no breakpoint: font-size: 1.33333rem; (24/18) 28 | * line-height: 1.3; 29 | * - med breakpoint: font-size: 1.666666667rem; (30/18) 30 | * - large breakpoint: font-size: 3rem; 31 | */ 32 | .rem{ 33 | @include typi('h1'); 34 | } 35 | 36 | /** 37 | * Tests Output of Typi mixin 38 | * ------------------------- 39 | * Should output same values as above, but in em instead 40 | */ 41 | .em { 42 | @include typi('h1', $rem: false); 43 | } 44 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/typi/test/test_sass.js: -------------------------------------------------------------------------------- 1 | var mocha = require('mocha') 2 | var path = require('path') 3 | var sassTrue = require('sass-true') 4 | 5 | const testFiles = [ 6 | 'test', 7 | 'extender' 8 | ] 9 | 10 | testFiles.forEach(basename => { 11 | var testFile = path.join(__dirname, 'automated', `${basename}.scss`) 12 | sassTrue.runSass({ 13 | file: testFile, 14 | includePaths: [ 15 | './bower_components', 16 | './node_modules' 17 | ] 18 | }, describe, it) 19 | }) 20 | -------------------------------------------------------------------------------- /docs/_sass/abstracts/variables.scss: -------------------------------------------------------------------------------- 1 | // Breakpoints 2 | $breakpoints: ( 3 | s: 320px, 4 | m: 720px, 5 | l: 900px, 6 | xl: 1200px 7 | ); 8 | 9 | //colors 10 | $branded-yellow: #F3CD50; 11 | $branded-brown: #433E36; 12 | $branded-brown-light: #69655E; 13 | $branded-beige-dark: #E0DFDA; 14 | $branded-beige: #F5F2EB; 15 | $branded-beige-lighter: #F9F7F2; 16 | $highlight-color: #4c70f3; 17 | $muted-grey-color: #e4e3ed; 18 | $muted-grey-blue-tinge-color: #9caeb4; 19 | 20 | //$large-breakpoint: 64em !default; 21 | //$medium-breakpoint: 42em !default; 22 | 23 | // Headers 24 | $header-heading-color: #333 !default; 25 | $header-bg-color: #159957 !default; 26 | $header-bg-color-secondary: #155799 !default; 27 | 28 | // Footers 29 | $footer-bg-color: $muted-grey-color !default; 30 | 31 | // Text 32 | $section-headings-color: $branded-brown !default; 33 | $body-text-color: $branded-brown !default; 34 | $body-link-color: $highlight-color !default; 35 | $blockquote-text-color: $branded-brown !default; 36 | $caption-text-color: $branded-brown !default; 37 | $project-info-display-text-color: $branded-brown !default; 38 | $muted-display-text-color: $muted-grey-blue-tinge-color; 39 | 40 | // Code 41 | $code-bg-color: #f3f6fa !default; 42 | $code-text-color: #567482 !default; 43 | 44 | // Borders 45 | $border-color: #dce6f0 !default; 46 | $table-border-color: #e9ebec !default; 47 | $hr-border-color: #eff0f1 !default; 48 | 49 | // Divider line styles 50 | //$1px-line: solid 1px $hr-border-color; 51 | 52 | // Fonts 53 | $display-font-stack: "Rubik", Helvetica, Arial, sans-serif; 54 | $body-font-stack: "Rubik", Helvetica, Arial, sans-serif; 55 | $code-font-stack: Consolas, "Liberation Mono", Menlo, Courier, monospace; 56 | -------------------------------------------------------------------------------- /docs/_sass/base/typography.scss: -------------------------------------------------------------------------------- 1 | $typi-ms: ( 2 | null: 1.25, 3 | s: 1.2, 4 | m: 1.25, 5 | l: 1.25, 6 | xl: 1.25 7 | ); 8 | 9 | $typi: ( 10 | base: ( 11 | null: (16px, 1.5), 12 | s: (16px), 13 | m: (16px), 14 | l: (17px), 15 | xl: (18px) 16 | ), 17 | h1: ( 18 | null: (4, 1.2), 19 | s: (4, 1.2), 20 | m: (4, 1.2), 21 | l: (4, 1.2), 22 | xl: (4, 1.2) 23 | ), 24 | h2: ( 25 | null: (3, 1.2), 26 | s: (3, 1.2), 27 | m: (3, 1.2), 28 | l: (3, 1.2), 29 | xl: (3, 1.2) 30 | ), 31 | h3: ( 32 | null: (2, 1.2), 33 | s: (2, 1.2), 34 | m: (2, 1.2), 35 | l: (2, 1.2), 36 | xl: (2, 1.2) 37 | ), 38 | h4: ( 39 | null: (1, 1.25), 40 | s: (1, 1.25), 41 | m: (1, 1.25), 42 | l: (1, 1.25), 43 | xl: (1, 1.25), 44 | ), 45 | h5: ( 46 | null: (1, 1.25), 47 | s: (1, 1.25), 48 | m: (1, 1.25), 49 | l: (1, 1.25), 50 | xl: (1, 1.25) 51 | ), 52 | h6: ( 53 | null: 0, 54 | s: 0, 55 | m: 0, 56 | l: 0, 57 | xl: 0 58 | ), 59 | figcaption: ( // why isn't this working? 60 | null: -1, 61 | s: -1, 62 | m: -1, 63 | l: -1, 64 | xl: -1 65 | ) 66 | ); 67 | 68 | $typography-weight: ( 69 | h1: 400, 70 | h2: 400, 71 | h3: 400, 72 | h4: 400, 73 | h5: 900, 74 | h6: 900 75 | ); 76 | 77 | @include typi-init; 78 | 79 | 80 | html, body { 81 | //-ms-word-break: break-all; 82 | //word-break: break-all; 83 | 84 | /* Non standard for WebKit */ 85 | //word-break: break-word; 86 | 87 | text-rendering: optimizeLegibility; 88 | -webkit-font-smoothing: antialiased; 89 | -moz-osx-font-smoothing: grayscale; 90 | 91 | -webkit-text-size-adjust: 100%; 92 | -ms-text-size-adjust: 100%; 93 | font-family: $body-font-stack; 94 | } 95 | 96 | // Header Styling 97 | @for $i from 1 through 6 { 98 | h#{$i} { 99 | @include typi('h#{$i}'); 100 | font-weight: map-get($typography-weight , h#{$i} ); 101 | font-family: $display-font-stack; 102 | margin-top: 2rem; 103 | margin-bottom: 1rem; 104 | color: $branded-brown; 105 | } 106 | } 107 | 108 | figcaption{ 109 | @include typi('figcaption'); 110 | } 111 | 112 | p { 113 | margin-bottom: 1rem; 114 | } 115 | 116 | code { 117 | padding: 2px 4px; 118 | font-family: $code-font-stack; 119 | font-size: 0.9rem; 120 | color: $code-text-color; 121 | background-color: $code-bg-color; 122 | border-radius: 0.3rem; 123 | } 124 | 125 | pre { 126 | padding: 0.8rem; 127 | margin-top: 0; 128 | margin-bottom: 1rem; 129 | font: 1rem $code-font-stack; 130 | color: $code-text-color; 131 | word-wrap: normal; 132 | background-color: $code-bg-color; 133 | border: solid 1px $border-color; 134 | border-radius: 0.3rem; 135 | 136 | > code { 137 | padding: 0; 138 | margin: 0; 139 | font-size: 0.9rem; 140 | color: $code-text-color; 141 | word-break: normal; 142 | white-space: pre; 143 | background: transparent; 144 | border: 0; 145 | } 146 | } 147 | 148 | .highlight { 149 | margin-bottom: 1rem; 150 | 151 | pre { 152 | margin-bottom: 0; 153 | word-break: normal; 154 | } 155 | } 156 | 157 | .highlight pre, 158 | pre { 159 | padding: 0.8rem; 160 | overflow: auto; 161 | font-size: 0.9rem; 162 | line-height: 1.45; 163 | border-radius: 0.3rem; 164 | -webkit-overflow-scrolling: touch; 165 | } 166 | 167 | pre code, 168 | pre tt { 169 | display: inline; 170 | max-width: initial; 171 | padding: 0; 172 | margin: 0; 173 | overflow: initial; 174 | line-height: inherit; 175 | word-wrap: normal; 176 | background-color: transparent; 177 | border: 0; 178 | 179 | &:before, 180 | &:after { 181 | content: normal; 182 | } 183 | } 184 | 185 | ul, 186 | ol { 187 | margin-top: 0; 188 | } 189 | 190 | blockquote { 191 | padding: 0 1rem; 192 | margin-left: 0; 193 | color: $blockquote-text-color; 194 | border-left: 0.3rem solid $border-color; 195 | 196 | > :first-child { 197 | margin-top: 0; 198 | } 199 | 200 | > :last-child { 201 | margin-bottom: 0; 202 | } 203 | } 204 | 205 | figcaption { 206 | color: $caption-text-color; 207 | text-align: center; 208 | font-style: italic; 209 | } -------------------------------------------------------------------------------- /docs/_sass/layout/onboarding.scss: -------------------------------------------------------------------------------- 1 | .onboarding-logo{ 2 | color: inherit; 3 | 4 | a { 5 | text-decoration: none; 6 | opacity: 0.4; 7 | transition: 300ms; 8 | } 9 | a:visited { 10 | color: inherit; 11 | } 12 | a:hover { 13 | opacity: 0.6; 14 | transition: 300ms; 15 | } 16 | } 17 | 18 | .onboarding-tutorial__installed-text{ 19 | font-size: ms(1); 20 | opacity: 0.8; 21 | } 22 | 23 | .onboarding-tutorial { 24 | padding-top: 3rem; 25 | padding-bottom: 3rem; 26 | position: relative; 27 | 28 | @include primary-grid-layout(); 29 | 30 | grid-template-areas: 31 | "artwork description "; 32 | 33 | @include max-respond-to(m){ 34 | grid-template-areas: 35 | "artwork" 36 | "description"; 37 | } 38 | 39 | } 40 | 41 | .onboarding-tutorial__text { 42 | //background-color: #ffffff; 43 | grid-area: description; 44 | //padding-left: 2rem; 45 | 46 | } 47 | 48 | .onboarding-tutorial__image { 49 | //background-color: #ffffff; 50 | grid-area: artwork; 51 | display: flex; 52 | position: relative; 53 | overflow: hidden; 54 | display: block; 55 | 56 | @include max-respond-to(l){ 57 | position: absolute; 58 | right: 0; 59 | width: 500px; 60 | } 61 | 62 | @include max-respond-to(m){ 63 | position: relative; 64 | right: unset; 65 | width: unset; 66 | } 67 | 68 | img { 69 | height: unset; 70 | } 71 | } 72 | 73 | .onboarding-tutorial__image--notification { 74 | position: absolute; 75 | top: 1rem; 76 | right: 1rem; 77 | //transform: translateX(25rem); 78 | 79 | animation-name: notify; 80 | animation-duration: 3.5s; 81 | animation-timing-function: cubic-bezier(0.2,1,0.3,1); 82 | animation-direction: alternate; 83 | animation-iteration-count: infinite; 84 | animation-play-state: running; 85 | } 86 | 87 | @keyframes notify { 88 | 0%, 90% { 89 | transform: translateX(0rem); 90 | } 91 | 100% { 92 | transform: translateX(25rem); 93 | } 94 | }/**/ 95 | 96 | .onboarding-tutorial__image--explanation { 97 | position: absolute; 98 | top: 10%; 99 | right: -4%; 100 | opacity: 0; 101 | width: 65%; 102 | 103 | animation-name: showicon; 104 | animation-duration: 4s; 105 | animation-timing-function: cubic-bezier(0.2,1,0.3,1); 106 | animation-direction: alternate; 107 | animation-iteration-count: infinite; 108 | animation-play-state: running; 109 | animation-delay: 2s; 110 | } 111 | 112 | @keyframes showicon { 113 | 0% { 114 | opacity: 0; 115 | 116 | } 117 | 30% { 118 | opacity: 1; 119 | transform: scale(1rem); 120 | } 121 | 40% { 122 | opacity: 1; 123 | transform: scale(0rem); 124 | } 125 | 100% { 126 | 127 | opacity: 1; 128 | transform: scale(0rem); 129 | } 130 | } 131 | 132 | -------------------------------------------------------------------------------- /docs/_sass/master.scss: -------------------------------------------------------------------------------- 1 | // abstracts 2 | @import "abstracts/normalize"; 3 | @import "abstracts/modularscale"; 4 | @import "abstracts/typi/scss/typi"; 5 | @import "abstracts/variables"; 6 | @import "abstracts/mixins"; 7 | 8 | // base 9 | @import "base/typography"; 10 | 11 | // components 12 | 13 | // layout 14 | @import "layout/home"; 15 | @import "layout/onboarding"; 16 | 17 | * { 18 | box-sizing: border-box; 19 | } -------------------------------------------------------------------------------- /docs/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/docs/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/android-chrome-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/docs/android-chrome-256x256.png -------------------------------------------------------------------------------- /docs/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/docs/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/assets/chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/docs/assets/chrome.png -------------------------------------------------------------------------------- /docs/assets/chrome@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/docs/assets/chrome@2x.png -------------------------------------------------------------------------------- /docs/assets/curved_element.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/assets/firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/docs/assets/firefox.png -------------------------------------------------------------------------------- /docs/assets/firefox@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/docs/assets/firefox@2x.png -------------------------------------------------------------------------------- /docs/assets/illustrated_notification.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #9f00a7 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/css/style.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | // imports 5 | @import 'master'; -------------------------------------------------------------------------------- /docs/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/docs/favicon-16x16.png -------------------------------------------------------------------------------- /docs/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/docs/favicon-32x32.png -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/docs/favicon.ico -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | # Empty yaml front matter section. 3 | --- 4 | 5 | 6 | 7 | {% include head.html %} 8 | 9 | 10 | 11 |
12 |
13 |

2FA Notifier

14 |
15 |
16 | 17 |
18 |
19 |
20 | Primary illustration of extension dropdown 21 |
22 |
23 |

Get notified when the websites you visit offer two-factor authentication (2FA)

24 | 25 |
26 | Chrome browser icon 27 | Install
for Chrome 28 |
29 |
30 | 31 |
32 | Firefox browser icon 33 | Install
for Firefox 34 |
35 |
36 |
37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 | 45 | 46 |
47 | Illustration of a personified browser and smartphone scaring a cartoonized hacker. 48 |
49 |
50 |

2FA helps keep hackers at bay

51 |

Passwords can be digitally stolen. But to steal a phone requires physical exertion. This is what gives you that extra layer of protection. While a hacker can comfortably sit at home to steal your password, getting your phone requires a lot more effort.

52 | 53 | Do your part! Piss off a hacker and add some extra security to all your accounts - enable 2FA!

54 |
55 |
56 | 57 |
58 |
59 | Illustration of a personified browser and smartphone checking a browser URL against their list of 2FA supported websites. 60 |
61 |
62 |

How does it work?

63 |

2FA Notifier simply compares the website you are visiting to a list of known 2FA capable websites provided by twofactorauth.org. If the website is in our list, we notify you to enable 2FA.

64 |
65 |
66 | 67 |
68 | 69 |
70 | 81 |
82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /docs/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/docs/mstile-150x150.png -------------------------------------------------------------------------------- /docs/onboarding.html: -------------------------------------------------------------------------------- 1 | --- 2 | # Empty yaml front matter section. 3 | --- 4 | 5 | 6 | 7 | 8 | {% include head.html %} 9 | 10 | 11 | 12 |
13 |
14 |

2FA Notifier

15 |
Installed! Here’s a quick tutorial.
16 |
17 |
18 | 19 |
20 |
21 |
22 | abstract image of browser 23 | abstract image of system notification popping in and out 24 |
25 |
26 |

Notifications appear when you visit sites that support 2FA

27 |

Clicking on the notification will bring you to a 2FA guide for that website. Otherwise, you can ignore or close the notification.

28 |
29 |
30 | 31 |
32 |
33 | abstract image of browser 34 | a grey 2FA notifier icon signifies no 2FA support. A Green 2FA notifier icon signifies 2FA support available. 35 |
36 |
37 |

2FA Notifier icon changes to indicate 2FA support

38 |

Clicking on the 2FA Notifier icon reveals additional information about the current website. If 2FA is supported, you'll get a direct link to the 2FA guide.

39 |
40 |
41 |
42 | 43 |
44 | 45 |
46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 17 | 24 | 26 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "2FA Notifier", 3 | "short_name": "2FA Notifier", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-256x256.png", 12 | "sizes": "256x256", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "2FA_Notifier", 3 | "version": "v0.8.1", 4 | "scripts": { 5 | "clean": "rm -rf src/extension/generatedJS/*", 6 | "prebuild": "npm run clean", 7 | "build": "npm run webpack", 8 | "watch": "npm run webpack -- -w", 9 | "webpack": "webpack --display-error-details --progress --colors", 10 | "ff": "web-ext run -s src/extension", 11 | "typescript": "tsc", 12 | "prerelease": "npm run build", 13 | "release": "cd src/extension && zip -r ../../releases/2FAN_NEXT.zip *", 14 | "test": "npm run test-ts && npm run test-js", 15 | "test-ts": "mocha --require ts-node/register src/**/*.test.ts", 16 | "test-js": "mocha src/**/*.test.js", 17 | "ts-node": "ts-node", 18 | "scripts/generate": "ts-node scripts/data_pipeline.ts" 19 | }, 20 | "devDependencies": { 21 | "@types/chai": "^4.1.7", 22 | "@types/mocha": "^5.2.6", 23 | "awesome-typescript-loader": "4.0.1", 24 | "chai": "^4.2.0", 25 | "mocha": "^5.2.0", 26 | "ts-node": "^5.0.1", 27 | "typescript": "^3.0.0", 28 | "web-ext": "^2.9.3", 29 | "webextension-polyfill-ts": "^0.8.9", 30 | "webpack": "3.10.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/extension/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Conor Gilsenan and Ray Gonzales 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/extension/browserAction/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

19 | Created by Conor 20 | and Ray, 2FAN 21 | (Two Factor Authentication Notifier) strives to help all netizens be more 22 | secure on the web. 23 |

24 | 25 |

26 | Thanks to the folks at twofactorauth.org 27 | for opensourcing their project, which allowed us to build this tool. 28 |

29 | 30 |

31 | Also, thanks to Blair Adams, Perametade Games, anbileru adaleru for 32 | allowing us to use their icons through 33 | thenounproject.com 34 | as well as the Google Design team 35 | for open sourcing their icons. 36 |

37 | 38 |

39 | If you are interested in jumping into the code for 2FAN, we've open sourced 40 | everything on our Github repo here. 41 |

42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /src/extension/browserAction/confirmation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Well Done Art 21 |

Nice!

22 |
23 |

24 | Just like that, you've pissed off a hacker. Well done! 25 |

26 |

27 | We'll stop bugging you with reminders for
28 | website.com. 29 |

30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /src/extension/browserAction/doesNotSupport2FA.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Main Art 21 |

No 2FA here :(

22 |
23 |

24 | Here are some ways to better protect your account on
25 | website.com: 26 |

27 |
    28 |
  • Create a unique password
  • 29 |
  • Use a password manager
  • 30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /src/extension/browserAction/feedback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

19 | Do you find this extension helpful? Annoying? 20 | Is there something you'd like to see changed or added? 21 |

22 | 23 |

24 | We'd love to hear from you! Drop us a line on our 25 | Github Issues page! 26 |

27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /src/extension/browserAction/icons/About.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/extension/browserAction/icons/Back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/extension/browserAction/icons/Expand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/extension/browserAction/icons/Feedback.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/extension/browserAction/icons/Menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/extension/browserAction/icons/Tutorial.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/extension/browserAction/icons/checkmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/extension/browserAction/images/2fa_available.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/src/extension/browserAction/images/2fa_available.png -------------------------------------------------------------------------------- /src/extension/browserAction/images/2fa_available@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/src/extension/browserAction/images/2fa_available@2x.png -------------------------------------------------------------------------------- /src/extension/browserAction/images/2fa_not_available.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/src/extension/browserAction/images/2fa_not_available.png -------------------------------------------------------------------------------- /src/extension/browserAction/images/2fa_not_available@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/src/extension/browserAction/images/2fa_not_available@2x.png -------------------------------------------------------------------------------- /src/extension/browserAction/images/well_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/src/extension/browserAction/images/well_done.png -------------------------------------------------------------------------------- /src/extension/browserAction/images/well_done@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/src/extension/browserAction/images/well_done@2x.png -------------------------------------------------------------------------------- /src/extension/browserAction/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/extension/browserAction/previouslyEnabled2FA.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Well Done Art 20 |

All good here

21 |
22 |

23 | You've enabled 2FA for
24 | website.com. 25 |

26 |

27 | But, in case you need it, here is 28 | the 2FA guide. 29 |

30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /src/extension/browserAction/styles2FAN.css: -------------------------------------------------------------------------------- 1 | *{ 2 | box-sizing: border-box; 3 | } 4 | 5 | html, body { 6 | font-family: 'Rubik', sans-serif; 7 | font-size: 16px; 8 | text-rendering: optimizeLegibility; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | body { 14 | width: 15rem; 15 | padding: 1.5rem; 16 | color: #433E36; 17 | 18 | display: flex; 19 | flex-direction: column; 20 | align-items: flex-start; 21 | } 22 | 23 | svg { 24 | display: block; 25 | } 26 | 27 | .icon { 28 | fill: #9B8333; 29 | width: 24px; 30 | height: 24px; 31 | } 32 | 33 | 34 | .icon__checkmark { 35 | fill: #219653; 36 | } 37 | 38 | 39 | 40 | 41 | .primary-nav { 42 | transition: transform 400ms; 43 | transition-timing-function: cubic-bezier(0.2,1,0.3,1); 44 | width: 20%; 45 | } 46 | 47 | .primary-nav:hover { 48 | transform: translate3d(-0.25rem, 0, 0); 49 | } 50 | 51 | 52 | .hidden { 53 | display: none; 54 | } 55 | 56 | 57 | .methods { 58 | font-weight: 400; 59 | width: 100%; 60 | } 61 | 62 | .methods__info-btn { 63 | align-self: center; 64 | margin-bottom: 1rem; 65 | transition: transform 400ms; 66 | transition-timing-function: cubic-bezier(0.2,1,0.3,1); 67 | } 68 | 69 | .methods__info-btn:hover { 70 | transform: translate3d(0, 0.25rem, 0); 71 | } 72 | 73 | 74 | .methods__title { 75 | font-size: 0.8125rem; 76 | padding: 0.5rem 0; 77 | color: #87847E; 78 | opacity: 0.7; 79 | } 80 | 81 | .methods__list { 82 | list-style-type: none; 83 | padding:0; 84 | margin: 0.5rem 0 0 0; 85 | width: 100%; 86 | } 87 | 88 | .methods__list li { 89 | display: flex; 90 | flex-direction: row; 91 | align-items: center; 92 | justify-content: space-between; 93 | width: 100%; 94 | padding: 0.5rem 1rem 0 1rem; 95 | } 96 | 97 | .methods__list li:first-child { 98 | border-top: solid 1px #F3F0E9; 99 | } 100 | .methods__list li:last-child { 101 | padding-bottom: 0.5rem; 102 | border-bottom: solid 1px #F3F0E9; 103 | } 104 | 105 | .methods__list p { 106 | margin: 0; 107 | font-size: 0.875rem; 108 | } 109 | 110 | .methods--checkmark-right-align { 111 | align-self: flex-end; 112 | } 113 | 114 | 115 | 116 | 117 | .main-art { 118 | width: 192px; 119 | height: 159px; 120 | } 121 | 122 | .extension-headline { 123 | font-size: 1.5rem; 124 | font-weight: 300; 125 | color: #69655E; 126 | text-align: center; 127 | margin:0; 128 | align-self: center; 129 | } 130 | 131 | .extension__body-text { 132 | font-size: 0.8125rem; 133 | text-align: center; 134 | } 135 | 136 | .extension__body-text--bold { 137 | font-weight: 600; 138 | } 139 | 140 | .extension__list { 141 | font-size: 0.8125rem; 142 | padding-left: 1rem; 143 | } 144 | 145 | .extension__list li { 146 | padding-top: 0.5rem; 147 | } 148 | 149 | .cta-btn { 150 | margin: 1rem 0; 151 | width: 100%; 152 | padding: 0.75rem 0; 153 | text-align: center; 154 | text-decoration: none; 155 | background-color: #F3CD50; 156 | box-shadow: 0px 3px 12px rgba(135, 205, 220, 0.34); 157 | border-radius: 200px; 158 | align-self: center; 159 | transition: transform 400ms; 160 | transition-timing-function: cubic-bezier(0.2,1,0.3,1); 161 | } 162 | 163 | .cta-btn::after { 164 | content: ''; 165 | position: absolute; 166 | top: 0; 167 | left: 0; 168 | z-index: -1; 169 | width: 100%; 170 | height: 100%; 171 | border-radius: 200px; 172 | align-self: center; 173 | box-shadow: 0px 8px 24px rgba(135, 205, 220, 1); 174 | opacity: 0; 175 | transition: opacity 400ms; 176 | transition-timing-function: cubic-bezier(0.2,1,0.3,1); 177 | } 178 | 179 | .cta-btn:hover { 180 | transform: translate3d(0, -0.25rem, 0); 181 | } 182 | 183 | .cta-btn:hover::after { 184 | opacity: 0.5; 185 | } 186 | 187 | .cta-btn__headline { 188 | font-weight: 700; 189 | font-size: 1.30rem; 190 | display: block; 191 | color: #433E36; 192 | } 193 | 194 | .cta-btn__site-name { 195 | font-weight: 700; 196 | font-size: 0.75rem; 197 | color: #433E36; 198 | } 199 | 200 | 201 | 202 | 203 | 204 | .menu-page-list { 205 | list-style-type: none; 206 | padding:0; 207 | width: 100%; 208 | } 209 | 210 | .menu-page-list li { 211 | display: flex; 212 | flex-direction: row; 213 | border-top: solid 1px #F5F2EB; 214 | width: 100%; 215 | } 216 | 217 | .menu-page-list li:last-child { 218 | border-bottom: solid 1px #F5F2EB; 219 | } 220 | 221 | .menu-page-list a { 222 | text-decoration: none; 223 | display: inline-block; 224 | display: flex; 225 | flex-direction: row; 226 | align-items: center; 227 | transition: transform 400ms; 228 | transition-timing-function: cubic-bezier(0.2,1,0.3,1); 229 | } 230 | 231 | .menu-page-list a:hover{ 232 | opacity: 0.7; 233 | transform: translate3d(0.25rem, 0, 0); 234 | } 235 | 236 | .menu-page-list p { 237 | margin-left: 1rem; 238 | font-weight: 400; 239 | color: #433E36; 240 | } 241 | 242 | .extension-body-text p{ 243 | font-size: 0.8125rem; 244 | } 245 | 246 | .temporary-hide { 247 | display: none !important; 248 | } 249 | 250 | .missing-docs { 251 | margin: 0.25rem 0; 252 | padding: 0.5rem 0; 253 | width: 100%; 254 | text-align: center; 255 | text-decoration: none; 256 | 257 | align-self: center; 258 | } 259 | 260 | .alreadyEnabled2fa { 261 | margin: 0.25rem 0; 262 | padding: 0.5rem 0; 263 | width: 100%; 264 | text-align: center; 265 | text-decoration: underline; 266 | 267 | align-self: center; 268 | } 269 | 270 | .alreadyEnabled2fa:visited { 271 | color: inherit; 272 | } -------------------------------------------------------------------------------- /src/extension/icons/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/src/extension/icons/app_icon_128.png -------------------------------------------------------------------------------- /src/extension/icons/app_icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/src/extension/icons/app_icon_48.png -------------------------------------------------------------------------------- /src/extension/icons/no_2fa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/src/extension/icons/no_2fa.png -------------------------------------------------------------------------------- /src/extension/icons/no_2fa@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/src/extension/icons/no_2fa@2x.png -------------------------------------------------------------------------------- /src/extension/icons/no_2fa@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/src/extension/icons/no_2fa@3x.png -------------------------------------------------------------------------------- /src/extension/icons/notification_icon_80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/src/extension/icons/notification_icon_80.png -------------------------------------------------------------------------------- /src/extension/icons/notification_icon_80@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/src/extension/icons/notification_icon_80@2x.png -------------------------------------------------------------------------------- /src/extension/icons/yes_2fa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/src/extension/icons/yes_2fa.png -------------------------------------------------------------------------------- /src/extension/icons/yes_2fa@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/src/extension/icons/yes_2fa@2x.png -------------------------------------------------------------------------------- /src/extension/icons/yes_2fa@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conorgil/2fa-notifier/d3ff7ff50a9673045f02258526a145fe6d54cbc9/src/extension/icons/yes_2fa@3x.png -------------------------------------------------------------------------------- /src/extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "2FA Notifier", 3 | "manifest_version": 2, 4 | "version": "0.8.1", 5 | "author": "Conor Gilsenan", 6 | "description": "Notifies users whether the current browser page supports 2FA or not.", 7 | 8 | "background": { 9 | "scripts": [ 10 | "generatedJS/background.bundle.js" 11 | ], 12 | "persistent": true 13 | }, 14 | 15 | "browser_action": { 16 | "default_popup": "browserAction/popup.html", 17 | "default_title": "2FA Notifier", 18 | "default_icon": { 19 | "16": "icons/no_2fa.png", 20 | "32": "icons/no_2fa@2x.png", 21 | "48": "icons/no_2fa@3x.png" 22 | } 23 | }, 24 | 25 | "icons": { 26 | "48": "icons/app_icon_48.png", 27 | "128": "icons/app_icon_128.png" 28 | }, 29 | 30 | "permissions": [ 31 | "storage", 32 | "tabs", 33 | "notifications" 34 | ], 35 | 36 | "web_accessible_resources": [ 37 | "**/*.html", 38 | "**/*.css", 39 | "**/*.js", 40 | "**/*.png" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /src/extension/options/options.css: -------------------------------------------------------------------------------- 1 | input { 2 | width: 225px; 3 | text-overflow: ellipsis; 4 | } 5 | 6 | hidden { 7 | 8 | } -------------------------------------------------------------------------------- /src/extension/options/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 2FAN Options 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/typescript/background/backgroundScript.ts: -------------------------------------------------------------------------------- 1 | import { browser, Tabs } from 'webextension-polyfill-ts'; 2 | import { handleRuntimeInstalledEvent } from './eventHandlers'; 3 | import { processUrl } from './logic'; 4 | import { getCurrentTab } from '../utils'; 5 | 6 | const STATUS_COMPLETE = 'complete'; 7 | 8 | browser.tabs.onUpdated.addListener(async function (tabId: number, changeInfo: Tabs.OnUpdatedChangeInfoType, tab: Tabs.Tab) { 9 | let currentTab = await getCurrentTab(); 10 | if (currentTab && tabId === currentTab.id && changeInfo.status === STATUS_COMPLETE) { 11 | console.log('[background_script:tabs.onUpdated] tab %d status complete', tabId); 12 | processTab(currentTab); 13 | } 14 | }); 15 | 16 | browser.tabs.onActivated.addListener(async function(activeInfo: Tabs.OnActivatedActiveInfoType) { 17 | let currentTab = await getCurrentTab(); 18 | console.log('[background_script:tabs.onActivated] tab %d activated', currentTab.id); 19 | processTab(currentTab); 20 | }); 21 | 22 | function processTab(tab: Tabs.Tab) { 23 | let url = new URL(tab.url); 24 | processUrl(url); 25 | } 26 | 27 | browser.runtime.onInstalled.addListener(handleRuntimeInstalledEvent); -------------------------------------------------------------------------------- /src/typescript/background/eventHandlers.ts: -------------------------------------------------------------------------------- 1 | import { browser } from 'webextension-polyfill-ts'; 2 | 3 | /** 4 | * Handle runtime.onInstalled event 5 | * 6 | * @param details 7 | * TODO: update param to type Runtime.OnInstalledDetailsType 8 | */ 9 | export function handleRuntimeInstalledEvent(details: any) { 10 | console.log(details.reason); 11 | 12 | switch (details.reason) { 13 | case 'install': 14 | return handleExtensionInstalledEvent(details); 15 | case 'update': 16 | return handleExtensionUpdatedEvent(details); 17 | case 'browser_update': 18 | case 'chrome_update': 19 | return handleBrowserUpdatedEvent(details); 20 | case 'shared_module_update': 21 | return handleSharedModuleUpdatedEvent(details); 22 | default: 23 | console.log('Unknown runtime.OnInstalledReason: %s', details.reason); 24 | } 25 | } 26 | 27 | function handleExtensionInstalledEvent(details: any) { 28 | console.log('[background_script:handleExtensionInstalled] Details = %O', details); 29 | 30 | // TODO move hard coded domain into a config file so 31 | // that local testing can replace the value with localhost. 32 | browser.tabs.create({ 33 | url: "https://2fanotifier.org/onboarding.html" 34 | }); 35 | } 36 | 37 | function handleExtensionUpdatedEvent(details: any) { 38 | console.log('[background_script:handleExtensionUpdated] Details = %O', details); 39 | } 40 | 41 | function handleBrowserUpdatedEvent(details: any) { 42 | console.log('[background_script:handleBrowserUpdated] Details = %O', details); 43 | } 44 | 45 | function handleSharedModuleUpdatedEvent(details: any) { 46 | console.log('[background_script:handleSharedModuleUpdated] Details = %O', details); 47 | } -------------------------------------------------------------------------------- /src/typescript/background/logic.ts: -------------------------------------------------------------------------------- 1 | import { browser } from 'webextension-polyfill-ts'; 2 | import { popNotification } from './notifications'; 3 | import { loadOriginConfig, OriginConfig } from '../utils/dataService'; 4 | import { StorageService } from '../utils/storageService'; 5 | import { userAlreadyEnabled2faOnSite } from '../utils'; 6 | 7 | const DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24; 8 | 9 | export async function processUrl(url: URL) { 10 | // TODO: extract into function 11 | let config = await loadOriginConfig(url); 12 | 13 | if (config === undefined) { 14 | await noData(url); 15 | return; 16 | } 17 | 18 | let has2FA = config.tfa; 19 | if (!has2FA) { 20 | await tfaNotSupported(url); 21 | return; 22 | } 23 | 24 | await tfaSupported(url, config); 25 | } 26 | 27 | async function noData(url: URL) { 28 | console.log('No data found for this site: %s', url.hostname); 29 | await showBrowserActionIconForNo2faSupport(); 30 | } 31 | 32 | async function tfaNotSupported(url: URL) { 33 | console.log('%s DOES NOT support 2FA!', url.hostname); 34 | await showBrowserActionIconForNo2faSupport(); 35 | } 36 | 37 | async function tfaSupported(url: URL, config: OriginConfig) { 38 | showBrowserActionIconFor2faSupport(); 39 | 40 | let hostname = url.hostname; 41 | let docsUrl = config.doc; 42 | let message = ''; 43 | 44 | if (docsUrl) { 45 | console.log('[tfaSupported] %s DOES support 2FA and DOES have docs! Woo :)', hostname); 46 | message = 'Click here for info on how to enable it!'; 47 | } else { 48 | console.log('[tfaSupported] %s DOES support 2FA, but DOES NOT have docs! Boo :(', hostname); 49 | message = 'Check their website for info on how to enable it.'; 50 | } 51 | 52 | let options = { 53 | title: hostname + ' supports 2FA!', 54 | message: message 55 | }; 56 | 57 | if (await shouldShowNotification(url)) { 58 | popNotification(url, options, config); 59 | } else { 60 | console.log('[tfaSupported] Skipping notification for %s', hostname); 61 | } 62 | } 63 | 64 | async function showBrowserActionIconFor2faSupport() { 65 | await browser.browserAction.setIcon({ 66 | path: { 67 | '16': 'icons/yes_2fa.png', 68 | '32': 'icons/yes_2fa@2x.png', 69 | '48': 'icons/yes_2fa@3x.png' 70 | } 71 | }); 72 | return; 73 | } 74 | 75 | async function showBrowserActionIconForNo2faSupport() { 76 | await browser.browserAction.setIcon({ 77 | path: { 78 | '16': 'icons/no_2fa.png', 79 | '32': 'icons/no_2fa@2x.png', 80 | '48': 'icons/no_2fa@3x.png' 81 | } 82 | }); 83 | return; 84 | } 85 | 86 | /** 87 | * Return true if we should pop a notification for the 88 | * current site; otherwise, false. 89 | * 90 | * @param url The URL of the site the user is currently visiting. 91 | */ 92 | export async function shouldShowNotification(url: URL) { 93 | if (await userAlreadyEnabled2faOnSite(url)) { 94 | return false; 95 | } 96 | 97 | if (await notificationWasShownRecently(url)) { 98 | return false; 99 | } 100 | 101 | return true; 102 | } 103 | 104 | /** 105 | * Return true if a notification for the current origin 106 | * was already shown within the last 24 hours. 107 | */ 108 | async function notificationWasShownRecently(url: URL) { 109 | let siteSettings = await StorageService.getOrCreateSiteSettings(url); 110 | if (!siteSettings.previousNotificationTimestamp) { 111 | return false; 112 | } 113 | 114 | let lastShown = new Date(siteSettings.previousNotificationTimestamp).getTime(); 115 | if (Date.now() - lastShown > DAY_IN_MILLISECONDS) { 116 | return false; 117 | } 118 | 119 | console.log('[shouldShowNotification] Notification was already shown for %s at %s', url.hostname, siteSettings.previousNotificationTimestamp); 120 | return true; 121 | } -------------------------------------------------------------------------------- /src/typescript/background/notifications.ts: -------------------------------------------------------------------------------- 1 | import { browser } from 'webextension-polyfill-ts'; 2 | import { OriginConfig } from '../utils/dataService'; 3 | import { StorageService } from '../utils/storageService'; 4 | 5 | type NOTIFICATION_CONTEXT = OriginConfig; 6 | 7 | const NOTIFICATION_ID_TO_CONTEXT: { [notificationId: string]: NOTIFICATION_CONTEXT } = {}; 8 | 9 | export async function popNotification(url: URL, options: { title: string; message: string; }, config: NOTIFICATION_CONTEXT) { 10 | let notificationId = await browser.notifications.create( 11 | { 12 | type: 'basic', 13 | // TODO: update to use the icon for this service from twofactorauth.org? 14 | iconUrl: browser.extension.getURL('icons/notification_icon_80.png'), 15 | title: options.title, 16 | message: options.message 17 | } 18 | ); 19 | 20 | NOTIFICATION_ID_TO_CONTEXT[notificationId] = config; 21 | console.log('notification id = %s', notificationId); 22 | 23 | let settings = await StorageService.getOrCreateSiteSettings(url) || {}; 24 | settings.previousNotificationTimestamp = new Date().toISOString(); 25 | await StorageService.setSiteSettings(url, settings); 26 | } 27 | 28 | browser.notifications.onClicked.addListener(function onNotificationClicked(notificationId) { 29 | console.log('Notification clicked: %s', notificationId); 30 | let config = getAndClearContext(notificationId); 31 | 32 | if (config && config.doc) { 33 | let docsUrl = config.doc; 34 | console.log('Load docs from: %s', docsUrl); 35 | browser.tabs.create({ 36 | url: docsUrl 37 | }); 38 | } else { 39 | // no config OR no docs 40 | console.log('No config OR no docs!'); 41 | } 42 | }); 43 | 44 | browser.notifications.onClosed.addListener(function onNotificationClosed(notificationId, byUser) { 45 | console.log('Notification with id %s closed byUser? %s', notificationId, byUser); 46 | getAndClearContext(notificationId); 47 | }); 48 | 49 | function getAndClearContext(notificationId: string) : NOTIFICATION_CONTEXT { 50 | let config = NOTIFICATION_ID_TO_CONTEXT[notificationId]; 51 | delete NOTIFICATION_ID_TO_CONTEXT[notificationId]; 52 | return config; 53 | } -------------------------------------------------------------------------------- /src/typescript/browserAction/confirmation.ts: -------------------------------------------------------------------------------- 1 | import { showCorrectSiteName } from '../utils'; 2 | 3 | async function main() { 4 | await showCorrectSiteName('siteName'); 5 | } 6 | 7 | main(); -------------------------------------------------------------------------------- /src/typescript/browserAction/doesNotSupport2FA.ts: -------------------------------------------------------------------------------- 1 | import { showCorrectSiteName } from '../utils'; 2 | 3 | async function main() { 4 | await showCorrectSiteName('siteName'); 5 | } 6 | 7 | main(); -------------------------------------------------------------------------------- /src/typescript/browserAction/popup.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getConfigForCurrentTab, 3 | userAlreadyEnabled2faOnSite, 4 | getCurrentTab 5 | } from '../utils'; 6 | 7 | /** 8 | * Redirects the current page 9 | */ 10 | async function redirectPage() { 11 | let tab = await getCurrentTab(); 12 | let currentUrl = new URL(tab.url); 13 | 14 | let is2faAlreadyEnabled = await userAlreadyEnabled2faOnSite(currentUrl); 15 | if (is2faAlreadyEnabled) { 16 | window.location.href = '../browserAction/previouslyEnabled2FA.html'; 17 | return; 18 | } 19 | 20 | let config = await getConfigForCurrentTab(); 21 | if (config && config.tfa) { 22 | window.location.href = '../browserAction/supports2FA.html'; 23 | return; 24 | } 25 | 26 | window.location.href = '../browserAction/doesNotSupport2FA.html'; 27 | } 28 | 29 | redirectPage(); -------------------------------------------------------------------------------- /src/typescript/browserAction/previouslyEnabled2FA.ts: -------------------------------------------------------------------------------- 1 | import { showCorrectSiteName, getConfigForCurrentTab } from '../utils'; 2 | 3 | /** 4 | * Update the anchor tag with the given id 5 | * to link to the doc URL for the current 6 | * tab, if it exists. 7 | * 8 | * @param id The id of the anchor tag whose href should 9 | * be updated to the doc URL. 10 | */ 11 | // TODO: this should be in utils.ts 12 | async function setupDocLink(id: string) { 13 | let config = await getConfigForCurrentTab(); 14 | if (!config) { 15 | return; 16 | } 17 | 18 | if (config.doc) { 19 | let a = document.getElementById(id) as HTMLAnchorElement; 20 | if (a) { 21 | a.href = config.doc; 22 | } 23 | return; 24 | } 25 | } 26 | 27 | async function main() { 28 | await showCorrectSiteName('siteName'); 29 | await setupDocLink('docLink'); 30 | } 31 | 32 | main(); -------------------------------------------------------------------------------- /src/typescript/browserAction/supports2FA.ts: -------------------------------------------------------------------------------- 1 | import { getConfigForCurrentTab, getCurrentTab, showCorrectSiteName } from '../utils'; 2 | import { StorageService } from '../utils/storageService'; 3 | 4 | const CLASS_HIDDEN = 'hidden'; 5 | 6 | document.addEventListener("DOMContentLoaded", onDomContentLoaded); 7 | async function onDomContentLoaded() { 8 | let more2faInfoButton = document.getElementById('more2faInfoButton'); 9 | more2faInfoButton.addEventListener('click', async function onClickedMore2faInfoButton() { 10 | more2faInfoButton.classList.toggle(CLASS_HIDDEN); 11 | 12 | let methodsInfo = document.querySelector('.methods'); 13 | methodsInfo.classList.toggle(CLASS_HIDDEN); 14 | }); 15 | 16 | let alreadyEnabledLink = document.getElementById('alreadyEnabled2fa'); 17 | alreadyEnabledLink.addEventListener('click', async function onClickedAlreadyEnabled2fa() { 18 | let tab = await getCurrentTab(); 19 | let url = new URL(tab.url); 20 | console.log('User indicated that they already enabled 2FA on %s', url.hostname); 21 | let settings = await StorageService.getOrCreateSiteSettings(url); 22 | settings.is2faEnabled = true; 23 | await StorageService.setSiteSettings(url, settings); 24 | 25 | window.location.href = '../browserAction/confirmation.html'; 26 | return; 27 | }); 28 | 29 | await showCorrect2faMethods(); 30 | await showCorrectSiteName('siteName'); 31 | await setupCorrectDocLink(); 32 | } 33 | 34 | async function showCorrect2faMethods() { 35 | let config = await getConfigForCurrentTab(); 36 | 37 | if (!config.sms) { 38 | hideElementById('sms'); 39 | } 40 | 41 | if (!config.phone) { 42 | hideElementById('phone'); 43 | } 44 | 45 | if (!config.email) { 46 | hideElementById('email'); 47 | } 48 | 49 | if (!config.software) { 50 | hideElementById('software'); 51 | } 52 | 53 | if (!config.hardware) { 54 | hideElementById('hardware'); 55 | } 56 | } 57 | 58 | /** 59 | * Hide an element by adding the 'hide' class to its classList. 60 | * 61 | * @param methodId The id of the element to hide. 62 | */ 63 | function hideElementById(methodId: string) { 64 | let elToHide = document.getElementById(methodId); 65 | elToHide.classList.add(CLASS_HIDDEN); 66 | } 67 | 68 | /** 69 | * Show an element by removing the 'hide' class from its classList. 70 | * 71 | * @param methodId The id of the element to show. 72 | */ 73 | function showElementById(methodId: string) { 74 | let elToShow = document.getElementById(methodId); 75 | elToShow.classList.remove(CLASS_HIDDEN); 76 | } 77 | 78 | async function setupCorrectDocLink() { 79 | let config = await getConfigForCurrentTab(); 80 | if (!config) { 81 | return; 82 | } 83 | 84 | if (config.doc) { 85 | let el = document.getElementById('cta-btn'); 86 | el.setAttribute('href', config.doc); 87 | return; 88 | } 89 | 90 | await showCorrectSiteName('nameOfSiteWith2faAndNoDocs'); 91 | await hideElementById('cta-btn'); 92 | await hideElementById('more2faInfoButton'); 93 | await showElementById('missingDocs'); 94 | } -------------------------------------------------------------------------------- /src/typescript/options/options.ts: -------------------------------------------------------------------------------- 1 | import { browser } from 'webextension-polyfill-ts'; 2 | -------------------------------------------------------------------------------- /src/typescript/utils.ts: -------------------------------------------------------------------------------- 1 | import { browser, Tabs } from "webextension-polyfill-ts"; 2 | import { OriginConfig, loadOriginConfig } from "./utils/dataService"; 3 | import { StorageService } from "./utils/storageService"; 4 | 5 | /** 6 | * Return the active tab in the current window, or null 7 | * if it is undefined. 8 | */ 9 | export async function getCurrentTab(): Promise { 10 | let tabs = await browser.tabs.query({ 11 | active: true, 12 | currentWindow: true 13 | }); 14 | 15 | if (tabs && tabs.length) { 16 | return tabs[0]; 17 | } 18 | return null; 19 | } 20 | 21 | /** 22 | * Return the 2FA configuration data for the origin loaded 23 | * in the current tab, or null if it is undefined. 24 | */ 25 | export async function getConfigForCurrentTab(): Promise { 26 | let currentTab = await getCurrentTab(); 27 | if (!currentTab) { 28 | return null; 29 | } 30 | 31 | let currentUrl = currentTab.url; 32 | let config = await loadOriginConfig(new URL(currentUrl)); 33 | if (!config) { 34 | return null; 35 | } 36 | 37 | return config; 38 | } 39 | 40 | /** 41 | * Look up the current tab and inject the current 42 | * hostname into the page on the element with the 43 | * given ID. 44 | * 45 | * @param id The ID of the span where the current site 46 | * name should be written. 47 | */ 48 | export async function showCorrectSiteName(id: string) { 49 | let tab = await getCurrentTab(); 50 | if (!tab) { return; } 51 | 52 | let url = new URL(tab.url); 53 | let site = url.hostname; 54 | document.getElementById(id).textContent = site; 55 | } 56 | 57 | /** 58 | * Returns true if the user has previously indicated that they 59 | * have already enabled 2FA on the given site. 60 | * 61 | * @param url The URL of the current site 62 | */ 63 | export async function userAlreadyEnabled2faOnSite(url: URL): Promise { 64 | let originSettings = await StorageService.getOrCreateSiteSettings(url); 65 | if (originSettings.is2faEnabled) { 66 | console.log('[shouldShowNotification] User already enabled 2FA for %s', url.hostname); 67 | return true; 68 | } 69 | return false; 70 | } 71 | -------------------------------------------------------------------------------- /src/typescript/utils/storageService.ts: -------------------------------------------------------------------------------- 1 | import { browser } from "webextension-polyfill-ts"; 2 | 3 | const SITE_SETTINGS = 'SITE_SETTINGS'; 4 | 5 | export interface SiteSettingsType { 6 | is2faEnabled?: boolean; 7 | previousNotificationTimestamp?: string; 8 | } 9 | 10 | interface SiteSettingsMapType { 11 | [hostname: string]: SiteSettingsType; 12 | } 13 | 14 | export class StorageService { 15 | private static async getOrCreateAllSiteSettings(): Promise { 16 | let results = await browser.storage.local.get(SITE_SETTINGS); 17 | // console.log('[getOrCreateAllSiteSettings] %O ', results); 18 | if (!results[SITE_SETTINGS]) { 19 | return {}; 20 | } 21 | return results[SITE_SETTINGS]; 22 | } 23 | 24 | private static async setAllSiteSettings(settings: SiteSettingsMapType) { 25 | // console.log('[setAllSiteSettings] %O ', settings); 26 | await browser.storage.local.set({ [SITE_SETTINGS]: settings }); 27 | } 28 | 29 | static async getOrCreateSiteSettings(url: URL): Promise { 30 | let hostname = url.hostname; 31 | let allSiteSettings = await this.getOrCreateAllSiteSettings(); 32 | if (!allSiteSettings[hostname]) { 33 | return {}; 34 | } 35 | 36 | return allSiteSettings[hostname]; 37 | } 38 | 39 | static async setSiteSettings(url: URL, settings: SiteSettingsType) { 40 | let hostname = url.hostname; 41 | let allSiteSettings = await this.getOrCreateAllSiteSettings(); 42 | if (!allSiteSettings) { 43 | return; 44 | } 45 | 46 | allSiteSettings[hostname] = settings; 47 | await this.setAllSiteSettings(allSiteSettings); 48 | } 49 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "allowJs": false, 5 | "target": "ES2017", 6 | "lib": [ 7 | "es2017", 8 | "dom", 9 | 10 | // https://github.com/Microsoft/TypeScript/issues/4947#issuecomment-295966266 11 | "dom.iterable" 12 | ], 13 | "alwaysStrict": true, 14 | "module": "commonjs", 15 | "noImplicitAny": true, 16 | "noImplicitReturns": true, 17 | "noImplicitThis": true 18 | }, 19 | "include": [ 20 | "src/typescript/**/*" 21 | ] 22 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | let path = require('path'); 2 | 3 | module.exports = { 4 | entry: { 5 | // Files that need to be transpiled from Typescript 6 | // to Javascript and included in the extension source. 7 | background: './src/typescript/background/backgroundScript.ts', 8 | popup: './src/typescript/browserAction/popup.ts', 9 | confirmation: './src/typescript/browserAction/confirmation.ts', 10 | options: './src/typescript/options/options.ts', 11 | supports2FA: './src/typescript/browserAction/supports2FA.ts', 12 | doesNotSupport2FA: './src/typescript/browserAction/doesNotSupport2FA.ts', 13 | previouslyEnabled2FA: './src/typescript/browserAction/previouslyEnabled2FA.ts' 14 | }, 15 | output: { 16 | // This copies each source entry into the folder 17 | // extension/generatedJS and names it after its 18 | // entry config key. 19 | path: path.join(__dirname, 'src', 'extension', 'generatedJS'), 20 | filename: '[name].bundle.js', 21 | }, 22 | module: { 23 | rules: [ 24 | // All files with a '.ts' or '.tsx' extension will be 25 | // handled by 'awesome-typescript-loader'. 26 | { 27 | test: /\.tsx?$/, 28 | use: ['awesome-typescript-loader'] 29 | } 30 | ] 31 | }, 32 | resolve: { 33 | // Tell webpack which paths to use when we import modules 34 | // in our typescript code 35 | extensions: ['.js', '.jsx', '.ts', '.tsx'], 36 | modules: [ 37 | 'src', 38 | 'node_modules', 39 | ] 40 | } 41 | } --------------------------------------------------------------------------------