diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..c08c1377 --- /dev/null +++ b/.babelrc @@ -0,0 +1,7 @@ +{ + "presets": [ + ["@babel/env", { + "useBuiltIns": "entry" + }] + ] +} diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..d41ebe12 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,39 @@ +version: 2 + +jobs: + build: + docker: + - image: circleci/node:latest + steps: + - add_ssh_keys: + fingerprints: + - "cf:94:68:a5:ab:31:17:ab:24:6e:7b:7b:14:07:fe:79" + - checkout + - run: + name: Install dependencies + command: npm i + - run: + name: Install gh-pages + command: npm install gh-pages@2.0.1 + - run: + name: Build project + command: npm run build + - run: + name: Git config user.email + command: git config user.email "ivan@solovev.one" + - run: + name: Git config user.name + command: git config user.name "Ivan Solovev" + - run: + name: Deploy project to gh-pages branch + command: ./node_modules/.bin/gh-pages --dotfiles --message "[ci skip] Updates" --dist dist + +workflows: + version: 2 + build: + jobs: + - build: + filters: + branches: + only: master + diff --git a/.editorconfig b/.editorconfig new file mode 100755 index 00000000..6a064930 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# editorconfig.org +root = true + +[*] +indent_style = tab +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.yml] +indent_style = space +indent_size = 2 + +[node_modules/**.js] +codepaint = false diff --git a/.gitignore b/.gitignore index 13ca7f48..946df766 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ -.bundle/ -public/ +dist/ +.cache + +node_modules +yarn-error.log diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..849ddff3 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +dist/ diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 5e493a30..00000000 --- a/Gemfile +++ /dev/null @@ -1,14 +0,0 @@ -source 'https://rubygems.org' - -gem 'rake' -gem 'json' -gem 'sinatra' -gem 'mini_racer' - -gem 'uglifier' -gem 'evil-front' -gem 'jquery-cdn' -gem 'coffee-script' -gem 'autoprefixer-rails' - -gem 'r18n-core' diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 6a45e7ea..00000000 --- a/Gemfile.lock +++ /dev/null @@ -1,101 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - activesupport (5.2.1.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - autoprefixer-rails (9.3.1) - execjs - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.12.2) - concurrent-ruby (1.1.3) - dimensions (1.3.0) - evil-front (0.5.1) - i18n - nokogiri (>= 1) - rails-sass-images (>= 0.3) - rubypants-unicode - sass (>= 3.2.9) - slim (>= 1.3.9) - sprockets (>= 1) - standalone_typograf (>= 3.0.1) - unicode_utils (>= 1.4) - execjs (2.7.0) - ffi (1.9.25) - i18n (1.1.1) - concurrent-ruby (~> 1.0) - jquery-cdn (3.3.1) - sprockets (>= 2) - json (2.1.0) - libv8 (6.7.288.46.1) - mime-types (3.2.2) - mime-types-data (~> 3.2015) - mime-types-data (3.2018.0812) - mini_portile2 (2.3.0) - mini_racer (0.2.4) - libv8 (>= 6.3) - minitest (5.11.3) - mustermann (1.0.3) - nokogiri (1.8.5) - mini_portile2 (~> 2.3.0) - r18n-core (3.2.0) - rack (2.0.6) - rack-protection (2.0.4) - rack - rails-sass-images (1.0.3) - dimensions (> 0) - mime-types (> 0) - sass (> 0) - rake (12.3.1) - rb-fsevent (0.10.3) - rb-inotify (0.9.10) - ffi (>= 0.5.0, < 2) - rubypants-unicode (0.2.5) - sass (3.7.2) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - sinatra (2.0.4) - mustermann (~> 1.0) - rack (~> 2.0) - rack-protection (= 2.0.4) - tilt (~> 2.0) - slim (4.0.1) - temple (>= 0.7.6, < 0.9) - tilt (>= 2.0.6, < 2.1) - sprockets (3.7.2) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - standalone_typograf (3.0.2) - activesupport - temple (0.8.0) - thread_safe (0.3.6) - tilt (2.0.9) - tzinfo (1.2.5) - thread_safe (~> 0.1) - uglifier (4.1.20) - execjs (>= 0.3.0, < 3) - unicode_utils (1.4.0) - -PLATFORMS - ruby - -DEPENDENCIES - autoprefixer-rails - coffee-script - evil-front - jquery-cdn - json - mini_racer - r18n-core - rake - sinatra - uglifier - -BUNDLED WITH - 1.17.1 diff --git a/README.md b/README.md index b87d2da3..9e302b06 100644 --- a/README.md +++ b/README.md @@ -26,26 +26,16 @@ for example `fr-ca` for Canadian French) and translate all messages. ### Test -1. To build the site and test your fix/translation you’ll need to have Ruby and - Bundler installed. For example, in a Debian-based (e.g. Ubuntu) environment: +1. Install project dependencies: ```sh - sudo apt-get install ruby1.9.1 ruby1.9.1-dev - sudo gem1.9.1 install bundler --no-user-install --bindir /usr/bin + yarn install ``` -2. Install project dependencies: +2. That’s all. Run development server: ```sh - bundle install --path=.bundle + yarn run start ``` -3. That’s all. Run development server: - - ```sh - bundle exec rake server - ``` - -4. And open [localhost:3000] in browser. - -[localhost:3000]: http://localhost:3000/ +3. And open [localhost:3000] in browser. diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 45a88319..00000000 --- a/Rakefile +++ /dev/null @@ -1,297 +0,0 @@ -# encoding: utf-8 - -require 'pathname' -require 'uglifier' -require 'sprockets' -require 'r18n-core' -require 'evil-front' -require 'jquery-cdn' -require 'coffee_script' -require 'autoprefixer-rails' - -class Pathname - def glob(pattern, &block) - Pathname.glob(self.join(pattern), &block) - end -end - -ROOT = Pathname(__FILE__).dirname -VIEWS = ROOT.join('views/') -PUBLIC = ROOT.join('public/') -IMAGES = ROOT.join('images/') - -STANDALONE = %w( favicon.ico favicon.png apple-touch-icon.png ) - -JqueryCdn.local_url = proc { '/jquery.js' } - -R18n.default_places = ROOT.join('i18n') - -R18n::Filters.add('code') do |text, config| - text.gsub(/`([^`]+)`/, '\1') -end - -R18n::Filters.add('format') do |text, config| - '

' + - text.gsub(/~([^~]+)~/, '\1').gsub("\n", '

') + - '

' -end - -class R18n::TranslatedString - def link(href, args = { }) - args[:href] = href - args = args.map { |k, v| "#{k}=\"#{v}\"" }.join(' ') - self.sub(/\^([^\^]+)\^/, "\\1").to_s - end -end - -class Easing - attr_reader :name - attr_reader :css - - def initialize(attrs) - @name = attrs['name'] - @css = attrs['css'] - end - - def linear? - @name == 'linear' - end - - def in_out? - @name =~ /InOut/ - end - - def x(t) - if linear? - t - else - jquery_easings.eval("jQuery.easing.#{@name}(null, #{t}, 0, 1, 1)") - end - end - - def dots(count, x, y) - dots = count.times.to_a.map { |i| (x.to_f / count) * (i + 1) } - dots.map do |i| - iy = y - (y * self.x(i / x.to_f)) - [i.round(1), iy.round(1)] - end - end - - def bezier - css.match(/cubic-bezier\(([^\)]+)\)/)[1].gsub(' ', '').gsub('0.', '.') - end - - def jquery_easings - @@jquery_easings ||= begin - require 'execjs' - js = 'jQuery = { easing: {},' + - ' extend: function (a, b) { jQuery.easing = b } };' - js += ROOT.join('vendor/jquery.easing.js').read - ExecJS.compile(js) - end - end -end - -class Sprockets::Context - include R18n::Helpers -end - -class Builder - include R18n::Helpers - include JqueryCdn::Helpers - - attr_accessor :env - - def self.instance(env = :development) - @@instance ||= self.new - @@instance.env = env - @@instance - end - - def assets - @sprockets ||= begin - Sprockets::Environment.new(ROOT) do |env| - env.append_path(IMAGES) - env.append_path(ROOT.join('scripts/')) - env.append_path(ROOT.join('styles/')) - env.append_path(ROOT.join('vendor')) - - AutoprefixerRails.install(env) - JqueryCdn.install(env) - EvilFront.install(env) - EvilFront.set_slim_options! - - if @env == :production - env.js_compressor = Uglifier.new(output: { comments: :none }) - env.css_compressor = :sass - else - env.css_compressor = nil - end - end - end - end - - def all_easings - @all_easings ||= begin - YAML.load_file(ROOT.join('easings.yml')).map { |i| Easing.new(i) } - end - end - - def css_easings - all_easings.reject(&:linear?).reject { |i| !i.css } - end - - def js_easings - all_easings.reject(&:linear?).reject { |i| i.css } - end - - def blocked(easings) - blocks = [] - block = [] - easings.each do |easing| - block << easing - if easing.in_out? - blocks << block - block = [] - end - end - blocks - end - - def linear_easing - @linear_easings ||= all_easings.find(&:linear?) - end - - def render(file, &block) - options = { format: :html, disable_escape: true, pretty: false } - Slim::Template.new(file.to_s, options).render(self, &block) - end - - def to_path(dots) - dots.map { |i| i.join(',') }.join(' ') - end - - def fonts_list(fonts) - fonts.map! { |i| i =~ /\s/ ? "\"#{i}\"" : i } - fonts.uniq.join(', ') - end - - def production? - @env == :production - end - - def each_locale(&block) - r18n.available_locales.sort { |a, b| a.code <=> b.code }.each do |locale| - yield(locale.code, locale) - end - end - - def include_statistics - VIEWS.join('statistics.html').read - end - - def partial(name) - render(VIEWS.join("_#{name}.slim")) - end -end - -def copy_with_extra_js(from, to, js) - to.open('w') do |io| - io << from.read.gsub(/<\/title>/, "<\/title>") - end -end - -def build_index(production = false) - index = VIEWS.join('index.html.slim') - locale = R18n.get.locale.code.downcase - builder = Builder.instance(production ? :production : :development) - - PUBLIC.mkpath - - file = PUBLIC.join("#{locale}.html") - file.open('w') { |io| io << builder.render(index) } - - if locale == 'en' - redirect = builder.assets['language-redirect.js'] - copy_with_extra_js(file, PUBLIC.join("index.html"), redirect) - end -end - -desc 'Build site files' -task :build do - PUBLIC.mkpath - PUBLIC.glob('*') { |i| i.rmtree } - - R18n.available_locales.each do |locale| - R18n.set(locale.code) - build_index(true) - print '.' - end - - STANDALONE.each { |i| FileUtils.cp IMAGES.join(i), PUBLIC.join(i) } - PUBLIC.join('jquery.js').open('w') do |io| - io << Builder.instance(:production).assets['jquery.js'] - end - - print "\n" -end - -desc 'Run server for development' -task :server do - require 'sinatra/base' - - class EasingsNet < Sinatra::Base - set :public_folder, nil - set :port, 3000 - set :lock, true - - get "/" do - build_page('en') - send_file PUBLIC.join('index.html') - end - - STANDALONE.each do |image| - get "/#{image}" do - send_file IMAGES.join(image) - end - end - - get '/jquery.js' do - content_type 'text/javascript' - Builder.instance.assets['jquery.js'].to_s - end - - get '/:locale' do - file = params[:locale] - file += '.html' unless file =~ /\.html/ - - locale = params[:locale] - locale = locale.match(/index\.(\w+)\.html/)[1] if locale =~ /index\./ - - build_page(locale) - send_file PUBLIC.join(file) - end - - def build_page(locale_code) - R18n.clear_cache! - R18n.set(locale_code).reload! - build_index - end - end - - EasingsNet.run! -end - -desc 'Prepare commit to GitHub Pages' -task :deploy => :build do - sh ['git checkout gh-pages', - 'git rm *.ico', - 'git rm *.png', - 'git rm *.html', - 'git rm *.js', - 'cp public/* ./', - 'git add *.js', - 'git add *.html', - 'git add *.png', - 'git add *.ico'].join(' && ') -end diff --git a/build.js b/build.js new file mode 100644 index 00000000..f6898493 --- /dev/null +++ b/build.js @@ -0,0 +1,298 @@ +const fs = require("fs"); +const path = require("path"); +const { promisify } = require("util"); +const yamlParse = require("js-yaml"); +const Mustache = require("mustache"); + +const readFile = promisify(fs.readFile); +const writeFile = promisify(fs.writeFile); +const unlink = promisify(fs.unlink); + +const Parcel = require("parcel-bundler"); + +const PostHTML = require("posthtml"); +const PostHTMLNano = require("htmlnano"); +const MQPacker = require("css-mqpacker"); +const postcssCustomProperties = require("postcss-custom-properties"); +const PostCSS = require("postcss"); +const Terser = require("terser"); + +const format = require("./helpers/format"); +const i18nDir = path.join(__dirname, "i18n"); + +const langList = fs + .readdirSync(i18nDir) + .filter(filename => !/^_/.test(filename) && /\.ya?ml$/i.test(filename)) + .map(filename => fs.readFileSync(path.join(i18nDir, filename))) + .map(file => yamlParse.load(file)) + .filter(dic => dic.version && dic.version > 1 && dic.lang_name); + +const linksElements = [ + "js-info-name", + "js-info-func", + "js-info-simple", + "js-info-complex", + "js-cubic-bezier" +]; + +const htmlMinifyOptions = { + minifySvg: false +}; + +const shortCssClassName = generateCssClassName(); + +const bundler = new Parcel("./src/index.pug", { + sourceMaps: false, + scopeHoist: true, + publicUrl: "./" +}); + +let bundleAssets = []; + +async function build() { + const bundleHome = await bundler.bundle(); + bundleAssets = findAssets(bundleHome); + + const cssFile = bundleAssets.find( + item => item.type === "css" && item.name.includes("/src.") + ); + const jsFile = bundleAssets.find( + item => item.type === "js" && item.name.includes("/src.") + ); + + let cssData = (await readFile(cssFile.name)).toString(); + let jsData = (await readFile(jsFile.name)).toString(); + + await Promise.all([unlink(cssFile.name), unlink(jsFile.name)]); + + const classesList = {}; + + linksElements.forEach(item => { + classesList[item] = shortCssClassName.next().value; + }); + + function cssPlugin(root) { + root.walkRules(rule => { + rule.selector = rule.selector.replace(/\.[\w_-]+/g, str => { + const kls = str.substr(1); + + if (!classesList[kls]) { + classesList[kls] = shortCssClassName.next().value; + } + + return "." + classesList[kls]; + }); + }); + } + + const styles = await PostCSS([ + postcssCustomProperties({ + preserve: false + }), + cssPlugin, + MQPacker + ]).process(cssData, { from: cssFile.name }); + + Object.keys(classesList).forEach(origin => { + const startSelector = `["'.]`; + const endSelector = `["'\\s\\[):,+~>]`; + + jsData = jsData.replace( + new RegExp(`(${startSelector})${origin}(${endSelector})`, "g"), + `$1${classesList[origin]}$2` + ); + }); + + jsData = jsData + .replace( + /parcelRequire=function.*\(function \(require\)\s?{/i, + "(function(window,document){" + ) + .replace(/}\);$/, "})(window,document);"); + + const minifyJS = Terser.minify(jsData, { + toplevel: true + }); + + await writeFile(jsFile.name, minifyJS.code); + + function htmlPlugin(lang = "en") { + return tree => { + tree.match({ attrs: { class: true } }, i => ({ + tag: i.tag, + content: i.content, + attrs: { + ...i.attrs, + class: i.attrs.class + .split(" ") + .map(origin => { + if (!(origin in classesList)) { + console.error(`Class "${origin}" don't use`); + return ""; + } + + return classesList[origin]; + }) + .join(" ") + } + })); + + tree.match({ tag: "link", attrs: { rel: "stylesheet" } }, file => { + if (file.attrs.href.includes("src.")) { + return { + tag: "style", + content: styles.css + }; + } + + return file; + }); + + tree.match({ tag: "link", attrs: { rel: "manifest" } }, file => { + return { + tag: "link", + attrs: { + ...file.attrs, + href: `manifest.${lang}.json` + } + }; + }); + + tree.match({ tag: "meta", attrs: { property: "og:image" } }, file => ({ + tag: "meta", + attrs: { + ...file.attrs, + content: `https://easings.net/${file.attrs.content}` + } + })); + }; + } + + const manifest = bundleAssets.find(asset => asset.type === "webmanifest"); + const manifestFile = await readFile(manifest.name, "utf8"); + + bundleAssets + .filter(i => i.type === "html") + .forEach(async item => { + const file = await readFile(item.name); + const html = PostHTML().process(file, { sync: true }).html; + + if (/\/index\.html$/i.test(item.name)) { + const distDirName = path.dirname(item.name); + + langList.forEach(async lang => { + const viewData = format( + lang, + lang.lang_code, + langList.map(dic => ({ + code: dic.lang_code, + name: dic.lang_name + })) + ); + + const htmlFragment = Mustache.render(html, viewData); + const manifestLang = Mustache.render(manifestFile, viewData); + + await writeFile( + path.join(distDirName, `manifest.${lang.lang_code}.json`), + manifestLang + ); + + const htmlMinFragment = await PostHTML([ + PostHTMLNano(htmlMinifyOptions) + ]) + .use(htmlPlugin(lang.lang_code)) + .process(htmlFragment); + + await writeFile( + path.join(distDirName, `${lang.lang_code}.html`), + htmlMinFragment.html.replace(/\n/g, "").replace(/>\s<") + ); + }); + + const engLang = langList.find(lang => lang.lang_code === "en"); + const htmlFragment = Mustache.render( + html, + format( + engLang, + "", + langList.map(dic => ({ + code: dic.lang_code, + name: dic.lang_name + })) + ) + ); + + const htmlMinFragment = await PostHTML([ + PostHTMLNano(htmlMinifyOptions) + ]) + .use(htmlPlugin()) + .process(htmlFragment); + + await writeFile( + item.name, + htmlMinFragment.html.replace(/\n/g, "").replace(/>\s<") + ); + } else { + await writeFile( + item.name, + PostHTML() + .use(htmlPlugin()) + .process(file, { sync: true }).html + ); + } + }); +} + +build().catch(error => { + process.stderr.write(error.stack + "\n"); + process.exit(1); +}); + +function findAssets(bundle) { + return Array.from(bundle.childBundles).reduce( + (all, item) => all.concat(findAssets(item)), + [ + { + name: bundle.name, + type: bundle.type + } + ] + ); +} + +function* generateCssClassName() { + const options = { + alphabet: "abcefghijklmnopqrstuvwxyz0123456789-_", + length: 1, + index: 0 + }; + + const getClassName = () => { + let result = ""; + + for (let i = options.length - 1; i >= 0; i--) { + const x = Math.pow(options.alphabet.length, i); + const n = Math.floor(options.index / x); + result += options.alphabet[n % options.alphabet.length]; + } + + options.index++; + if (options.index > Math.pow(options.alphabet.length, options.length) - 1) { + options.length++; + options.index = 0; + } + + return result; + }; + + while (true) { + let result = getClassName(); + + while (/^[0-9-].*$/.test(result)) { + result = getClassName(); + } + + yield result; + } +} diff --git a/helpers/format.js b/helpers/format.js new file mode 100644 index 00000000..24e6c0f3 --- /dev/null +++ b/helpers/format.js @@ -0,0 +1,112 @@ +function format(dictionary, lang, langList) { + const currentLang = lang ? lang : "en"; + + const defaultDictionary = { + lang: currentLang, + authors: { + sitnik: "Andrey Sitnik", + solovev: "Ivan Solovev" + }, + covenant: /(ru|uk)/i.test(currentLang) ? "и" : "and", + short_name: "Easings.net" + }; + + const helpers = { + link: renderLink, + langList: langList.map(item => { + return [ + `` + ].join(""); + }), + redirect_script: !lang + ? renderRedirectScript(langList.map(item => item.code)) + : "" + }; + + const newDictionary = Object.assign(defaultDictionary, dictionary); + return Object.assign(formatObject(newDictionary), helpers); +} + +function formatObject(dictionary) { + const newDictionary = Object.assign({}, dictionary); + + for (let field in newDictionary) { + if (newDictionary.hasOwnProperty(field)) { + if (typeof newDictionary[field] === "string") { + newDictionary[field] = formatString(newDictionary[field]); + } else { + newDictionary[field] = formatObject(newDictionary[field]); + } + } + } + + return newDictionary; +} + +function formatString(string) { + if (/^__format/i.test(string)) { + const newText = string + .replace(/^__format?\s/i, "") + .replace(/~([^~]+)~/g, "$1") + .replace(/\n/g, "

"); + + return `

${newText}

`; + } else if (/^__code/i.test(string)) { + return string + .replace(/^__code?\s/i, "") + .replace(/`([^`]+)`/g, "$1"); + } + + return string; +} + +function renderLink() { + return (fragment, render) => { + const [text, linkAttr] = fragment.match(/(\([^)]+\)|\[[^\]]+\])/g); + const renderText = render(text.replace(/\(([^)]+)\)/, "{{$1}}")); + + return renderText + .replace(/\((.*)\)/, "$1") + .replace( + /\^([^\^]+)\^/, + `$1` + ); + }; +} + +function renderRedirectScript(langList) { + return ` + + `; +} + +module.exports = format; diff --git a/i18n/af.yml b/i18n/_af.yml similarity index 100% rename from i18n/af.yml rename to i18n/_af.yml diff --git a/i18n/az.yml b/i18n/_az.yml similarity index 100% rename from i18n/az.yml rename to i18n/_az.yml diff --git a/i18n/ca.yml b/i18n/_ca.yml similarity index 100% rename from i18n/ca.yml rename to i18n/_ca.yml diff --git a/i18n/cs.yml b/i18n/_cs.yml similarity index 100% rename from i18n/cs.yml rename to i18n/_cs.yml diff --git a/i18n/cy.yml b/i18n/_cy.yml similarity index 100% rename from i18n/cy.yml rename to i18n/_cy.yml diff --git a/i18n/da.yml b/i18n/_da.yml similarity index 100% rename from i18n/da.yml rename to i18n/_da.yml diff --git a/i18n/de.yml b/i18n/_de.yml similarity index 100% rename from i18n/de.yml rename to i18n/_de.yml diff --git a/i18n/es.yml b/i18n/_es.yml similarity index 97% rename from i18n/es.yml rename to i18n/_es.yml index a5c417a7..d422346b 100644 --- a/i18n/es.yml +++ b/i18n/_es.yml @@ -1,52 +1,52 @@ -title: Funciones Easing Guía de Referencia -description: - Cree animaciones más realistas escogiendo la función `easing` correcta. -share: - La función `easing` especifica la velocidad del progreso de la animación - para hacerla más realista. El objeto real no comienza su movimiento - instantáneamente y de manera constante. Esta página le ayudará a escoger - la función deseada. - -about: !!format - La ~función easing~ especifica la velocidad de la animación para hacerla - más realista. - - El objeto real no comienza su movimiento instantáneamente y de manera constante. - Cuando abrimos el cajón, primero le damos aceleración, y después lo frenamos. Cuando - algo se cae, primero baja rápidamente y después de alcanzar el suelo rebota. - - Esta página le ayudará a escoger la función deseada. - -easings: - css: Disponible en todo - js: Solo en JavaScript - -howtos: - name: nombre easing - curve: curva Bézier del easing - js: !!code - jQuery con ^jQuery Easing Plugin^ es la manera más sencilla de definir una animación - con `easing`. Únicamente necesita ajustar el nombre `easing` al método `.animate` como - tercer argumento o la key `easing`. - scss: !!code - Sass/SCSS le ayudan a definir la animación. Compass elimina los prefijos previos a - las propiedades `transition` y `animation` y el plugin ^Compass Ceaser^ permite fijar - el `easing` por su nombre (sin curvas Bézier). - css: !!code - Las propiedades CSS `transition` y `animation` le permiten fijar la función `easing`. - css_bad: - Desgraciadamente, no están todos soportados y deberá fijar la función - con Bezier curve. - css_help: - Seleccione `easing` para mostrar su descripción en la curva Bézier. - -easing: - all_easings: Todos los easings - no_css: !!code - Desgraciadamente, CSS no soporta este `easing`. Pero puede utilizarlo con - JavaScript o con la propiedad CSS Animation `@keyframes`. - edit: ^Editar^ en cubic-bezier.com. - -opensource: - title: open source - translate: ^Ayuda a traducir^ esta web a tu idioma +title: Funciones Easing Guía de Referencia +description: + Cree animaciones más realistas escogiendo la función `easing` correcta. +share: + La función `easing` especifica la velocidad del progreso de la animación + para hacerla más realista. El objeto real no comienza su movimiento + instantáneamente y de manera constante. Esta página le ayudará a escoger + la función deseada. + +about: !!format + La ~función easing~ especifica la velocidad de la animación para hacerla + más realista. + + El objeto real no comienza su movimiento instantáneamente y de manera constante. + Cuando abrimos el cajón, primero le damos aceleración, y después lo frenamos. Cuando + algo se cae, primero baja rápidamente y después de alcanzar el suelo rebota. + + Esta página le ayudará a escoger la función deseada. + +easings: + css: Disponible en todo + js: Solo en JavaScript + +howtos: + name: nombre easing + curve: curva Bézier del easing + js: !!code + jQuery con ^jQuery Easing Plugin^ es la manera más sencilla de definir una animación + con `easing`. Únicamente necesita ajustar el nombre `easing` al método `.animate` como + tercer argumento o la key `easing`. + scss: !!code + Sass/SCSS le ayudan a definir la animación. Compass elimina los prefijos previos a + las propiedades `transition` y `animation` y el plugin ^Compass Ceaser^ permite fijar + el `easing` por su nombre (sin curvas Bézier). + css: !!code + Las propiedades CSS `transition` y `animation` le permiten fijar la función `easing`. + css_bad: + Desgraciadamente, no están todos soportados y deberá fijar la función + con Bezier curve. + css_help: + Seleccione `easing` para mostrar su descripción en la curva Bézier. + +easing: + all_easings: Todos los easings + no_css: !!code + Desgraciadamente, CSS no soporta este `easing`. Pero puede utilizarlo con + JavaScript o con la propiedad CSS Animation `@keyframes`. + edit: ^Editar^ en cubic-bezier.com. + +opensource: + title: open source + translate: ^Ayuda a traducir^ esta web a tu idioma diff --git a/i18n/fa.yml b/i18n/_fa.yml similarity index 100% rename from i18n/fa.yml rename to i18n/_fa.yml diff --git a/i18n/fr.yml b/i18n/_fr.yml similarity index 100% rename from i18n/fr.yml rename to i18n/_fr.yml diff --git a/i18n/id.yml b/i18n/_id.yml similarity index 100% rename from i18n/id.yml rename to i18n/_id.yml diff --git a/i18n/it.yml b/i18n/_it.yml similarity index 100% rename from i18n/it.yml rename to i18n/_it.yml diff --git a/i18n/ja.yml b/i18n/_ja.yml similarity index 100% rename from i18n/ja.yml rename to i18n/_ja.yml diff --git a/i18n/ko.yml b/i18n/_ko.yml similarity index 100% rename from i18n/ko.yml rename to i18n/_ko.yml diff --git a/i18n/nb.yml b/i18n/_nb.yml similarity index 100% rename from i18n/nb.yml rename to i18n/_nb.yml diff --git a/i18n/nl.yml b/i18n/_nl.yml similarity index 100% rename from i18n/nl.yml rename to i18n/_nl.yml diff --git a/i18n/pl.yml b/i18n/_pl.yml similarity index 100% rename from i18n/pl.yml rename to i18n/_pl.yml diff --git a/i18n/pt-br.yml b/i18n/_pt-br.yml similarity index 100% rename from i18n/pt-br.yml rename to i18n/_pt-br.yml diff --git a/i18n/pt.yml b/i18n/_pt.yml similarity index 100% rename from i18n/pt.yml rename to i18n/_pt.yml diff --git a/i18n/sk.yml b/i18n/_sk.yml similarity index 100% rename from i18n/sk.yml rename to i18n/_sk.yml diff --git a/i18n/sr-latn.yml b/i18n/_sr-latn.yml similarity index 100% rename from i18n/sr-latn.yml rename to i18n/_sr-latn.yml diff --git a/i18n/sv-se.yml b/i18n/_sv-se.yml similarity index 100% rename from i18n/sv-se.yml rename to i18n/_sv-se.yml diff --git a/i18n/tr.yml b/i18n/_tr.yml similarity index 100% rename from i18n/tr.yml rename to i18n/_tr.yml diff --git a/i18n/uk.yml b/i18n/_uk.yml similarity index 100% rename from i18n/uk.yml rename to i18n/_uk.yml diff --git a/i18n/vi.yml b/i18n/_vi.yml similarity index 100% rename from i18n/vi.yml rename to i18n/_vi.yml diff --git a/i18n/zh-cn.yml b/i18n/_zh-cn.yml similarity index 100% rename from i18n/zh-cn.yml rename to i18n/_zh-cn.yml diff --git a/i18n/zh-tw.yml b/i18n/_zh-tw.yml similarity index 100% rename from i18n/zh-tw.yml rename to i18n/_zh-tw.yml diff --git a/i18n/en.yml b/i18n/en.yml index a368cfb3..78a740f8 100644 --- a/i18n/en.yml +++ b/i18n/en.yml @@ -1,3 +1,7 @@ +version: 2 +lang_code: en +lang_name: English + title: Easing Functions Cheat Sheet description: Make animations more realistic by picking the right easing function. @@ -7,7 +11,7 @@ share: at a constant speed, and do not start and stop in an instant. This page helps you choose the right easing function. -about: !!format +about: __format ~Easing functions~ specify the rate of change of a parameter over time. @@ -18,39 +22,38 @@ about: !!format This page helps you choose the right easing function. -easings: - css: Available everywhere - js: Only in JavaScript - howtos: - name: easing name - curve: easing’s Bezier curve - js: !!code - jQuery with the ^jQuery Easing Plugin^ is the easiest way to describe - animation with easing. Pass the easing name (like `easeInCirc`) - to the `.animate()` function as the third argument, or use it as the value - of the `easing` option. - scss: !!code - Sass/SCSS can describe animations too! Compass removes prefixes before - the `transition` and `animation` properties, and the - ^Compass Ceaser^ plugin lets you pass the easing function by name - (without specifying the exact Bezier curves). - css: !!code - CSS properties `transition` and `animation` allow you to pick the easing - function. - css_bad: - Unfortunately, they don’t support all easings and you must - specify the desired easing function yourself (as a Bezier curve). - css_help: - Select an easing function to show its Bezier curve notation. + css: + text: In CSS, the transition and animation properties allow you to specify an easing function. + edit: Edit on ^cubic-bezier.com^. + with_animation: __code In CSS, this function can be implemented using `@keyframes` + example_size: Size + example_position: Position + example_opacity: Transparency + + postcss: + text: + In PostCSS, the easing function is much easier to describe. + There is a plugin ^postcss-easings^ that takes the transition information from that site. + explanation: That declaration is converted to the one described above. + disabled: __code + Unfortunately, the easing function cannot be set with any PostCSS plugin. + Can be done with `@keyframes`, see above. + + gradient: + name: Gradient + text: It is possible to draw a gradient using ^postcss-easing-gradients^. easing: all_easings: All easings - no_css: !!code - Not supported in CSS. But you can use in JS - or write it out with the CSS Animation `@keyframes`. - edit: ^Edit^ on cubic-bezier.com. + check: Check easing for changes + check_size: Size's + check_position: Positions + check_opacity: Transparencies + current_func: That function + linear_func: Linear function + load: Loading... opensource: - title: open source - translate: ^Help translate^ site to your language + title: Open Source + translate: Help translate site to your language diff --git a/i18n/ru.yml b/i18n/ru.yml index b20f609f..a37d1c70 100644 --- a/i18n/ru.yml +++ b/i18n/ru.yml @@ -1,3 +1,7 @@ +version: 2 +lang_code: ru +lang_name: Русский + title: Шпаргалка функций плавности (easing) description: Сделай анимацию более реалистичной, подобрав нужную смягчающую функцию @@ -7,9 +11,8 @@ share: более реалистичной, так как реальные вещи не начинают двигаться мгновенно и с постоянной скоростью. Этот сайт поможет каждый раз подобрать нужную смягчающую функцию. -font: 400,700&subset=latin,cyrillic -about: !!format +about: __format ~Функция плавности (easing)~ определяет скорость течения анимации, делая её более реалистичной. @@ -20,41 +23,41 @@ about: !!format Этот сайт поможет каждый раз подобрать нужную функцию плавности. -easings: - css: Доступные везде - js: Есть только в JavaScript - howtos: - name: имя функции - curve: кривая Безье функции - js: !!code - Проще всего рисовать анимации по функции плавности через jQuery, добавив - ^jQuery Easing Plugin^. После этого надо лишь указать имя функции - в методе `.animate` третьим аргументом или через ключ `easing`. - scss: !!code - В Sass и SCSS анимации описывать гораздо проще — Compass избавляет - от префиксов перед свойствами `transition` и `animation`, - а расширение ^Compass Ceaser^ позволит указывать функцию плавности просто - по имени, без кривых Безье. - css: !!code - В CSS свойства `transition` и `animation` позволяют указывать - функцию плавности. - css_bad: - К сожалению, доступны не все функции и чаще всего указывать их надо через - кривую Безье. - css_help: - Выберите функцию плавности, чтобы увидеть, как её описать через - кривую Безье. + css: + text: В CSS свойства transition и animation позволяют указывать функцию плавности. + edit: Редактировать на ^cubic-bezier.com^. + with_animation: __code В CSS эту функцию можно реализовать с помощью `@keyframes` + example_size: Размер + example_position: Положение + example_opacity: Прозрачность + + postcss: + text: + В PostCSS функцию перехода описывать сильно проще. + Есть плагин ^postcss-easings^, который берёт информацию о переходе с этого сайта. + explanation: Эта декларация преобразуется в подобную, которая описана выше. + disabled: __code + К сожалению, такую функцию плавности нельзя задать с помощью какого-либо плагина PostCSS. + Это можно сделать с помощью `@keyframes`, смотри выше. + + gradient: + name: Градиент + text: Возможно нарисовать градиент с помощью ^postcss-easing-gradients^. easing: all_easings: Все функции - no_css: !!code - К сожалению, такую функцию плавности нельзя задать с помощью CSS. - Используйте JavaScript или специальные `@keyframes` в CSS Animation. - edit: ^Редактировать^ на cubic-bezier.com. + check: Проверить изинг на изменение + check_size: Размера + check_position: Положения + check_opacity: Прозрачности + current_func: Текущая функция + linear_func: Линейная функция + load: Загрузка... opensource: - title: open source - translate: ^Помоги перевести^ сайт на твой язык + title: Опенсорс -author: Андрей Ситник +authors: + sitnik: Андрей Ситник + solovev: Иван Соловьев diff --git a/images/apple-touch-icon.png b/images/apple-touch-icon.png deleted file mode 100644 index 9c5fe05c..00000000 Binary files a/images/apple-touch-icon.png and /dev/null differ diff --git a/images/favicon.ico b/images/favicon.ico deleted file mode 100644 index 14168c19..00000000 Binary files a/images/favicon.ico and /dev/null differ diff --git a/images/favicon.png b/images/favicon.png deleted file mode 100644 index ea1d72f0..00000000 Binary files a/images/favicon.png and /dev/null differ diff --git a/images/mask.png b/images/mask.png deleted file mode 100644 index 6e11b67a..00000000 Binary files a/images/mask.png and /dev/null differ diff --git a/images/octocat.png b/images/octocat.png deleted file mode 100644 index 984fc572..00000000 Binary files a/images/octocat.png and /dev/null differ diff --git a/package.json b/package.json new file mode 100644 index 00000000..eab80df1 --- /dev/null +++ b/package.json @@ -0,0 +1,69 @@ +{ + "name": "easings.net", + "scripts": { + "start": "node ./start.js", + "build": "NODE_ENV=production node ./build.js", + "format:javascript": "prettier --write **/*.js *.js", + "lint:typescript": "tsc && tslint -p ./tsconfig.json -c ./tslint.json" + }, + "browserslist": [ + "defaults", + "not ie 11", + "not ie_mob 11" + ], + "postcss": { + "plugins": { + "postcss-pxtorem": { + "rootValue": 16, + "mediaQuery": true, + "propList": [ + "font", + "font-size", + "line-height", + "letter-spacing", + "margin", + "padding" + ] + }, + "postcss-flexbugs-fixes": {}, + "postcss-font-family-system-ui": {}, + "autoprefixer": {} + } + }, + "devDependencies": { + "@babel/core": "^7.3.4", + "autoprefixer": "^9.5.0", + "css-mqpacker": "^7.0.0", + "express": "^4.16.4", + "htmlnano": "^0.2.3", + "husky": "^1.3.1", + "js-yaml": "^3.12.2", + "lint-staged": "^8.1.5", + "mustache": "^3.0.1", + "parcel-bundler": "^1.12.2", + "postcss": "^7.0.14", + "postcss-custom-properties": "^8.0.9", + "postcss-flexbugs-fixes": "^4.1.0", + "postcss-font-family-system-ui": "^4.1.0", + "postcss-preset-env": "^6.6.0", + "postcss-pxtorem": "^4.0.1", + "posthtml": "^0.11.3", + "prettier": "^1.16.4", + "pug": "^2.0.3", + "terser": "^3.17.0", + "tslint": "^5.14.0", + "typescript": "^3.3.3333" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "*.ts": [ + "tsc --noEmit", + "tslint -p ./tsconfig.json -c ./tslint.json" + ], + "*.js": "npm run format:javascript" + } +} diff --git a/scripts/action.coffee b/scripts/action.coffee deleted file mode 100644 index 8d35d2ad..00000000 --- a/scripts/action.coffee +++ /dev/null @@ -1,203 +0,0 @@ -#= require jquery.easing -#= require evil-front/shortcuts -#= require evil-front/after -#= require evil-front/detect-3d - -evil.doc.ready ($) -> - easings = $('.easings li') - descriptions = $('.easing-description') - - # Scroll - - allScroll = null - - hash = (hash) -> - scroll = evil.win.scrollTop() - document.location.hash = hash - evil.win.scrollTop(scroll) - - scrollTo = (top, fn) -> $('html, body').animate(scrollTop: top, 600, fn) - - # Don’t scroll on hash change - - $('a[href^="#"]').click -> - hash($(@).attr('href')) - false - - # Block :active on title click - - easings.find('.easing-title').mousedown -> - $(@).closest('.easing').removeClass('is-clickable') - easings.find('.easing-title').mouseup -> - $(@).closest('.easing').addClass('is-clickable') - - # Easing example - - easings.on 'touchstart', -> $(@).addClass('is-tapped') - easings.mouseenter -> - div = $(@) - - return if div.hasClass('is-tapped') - easings.removeClass('is-tapped') - - easing = div.find('.easing-title').text() - div.find('.example').stop().css(marginTop: 0).delay(400). - animate(marginTop: -60, 1000, easing) - div.find('.dot').stop().css(marginTop: 0, marginLeft: 0).delay(400). - animate { marginTop: -60, marginLeft: 119 }, - duration: 1000 - specialEasing: marginTop: easing, marginLeft: 'linear' - - # Highlight easings part - - section = $('.easings') - titles = section.find('.part-title') - titles.mouseenter -> section.addClass('hightlight-part') - titles.mouseleave -> section.removeClass('hightlight-part') - - # Easing example in easing page - - cleanExample = (page) -> - return if page.hasClass('exampled') - page.find('.example').stop().css(marginTop: 0) - page.find('.dot').stop().css(marginTop: 0, marginLeft: 0) - - showExample = (page) -> - return if page.hasClass('exampled') - easing = page.find('h2').text() - - page.addClass('exampled') - after 400, -> - page.find('.example').animate(marginTop: -78, 1000, easing) - page.find('.dot').animate { marginTop: -78, marginLeft: 154 }, - duration: 1000 - specialEasing: marginTop: easing, marginLeft: 'linear' - complete: -> - after 400, -> page.removeClass('exampled') - - descriptions.on 'open', -> showExample($(@)) - descriptions.on 'close', -> cleanExample($(@)) - - descriptions.find('.graph').on 'mouseenter click', -> - page = $(@).closest('.easing-description') - cleanExample(page) - showExample(page) - - # Show easing description - - easingPages = $('.easing-description') - slider = $('.slide-slider') - lastPage = null - pageAnimated = false - - showSubpage = -> - if location.hash == '#' or location.hash == '' - return unless pageAnimated - - evil.body.removeClass('easing-page') - after 600, -> - lastPage?.trigger('close') - scrollTo(allScroll) if allScroll - else - name = location.hash[1..-1] - easingPages.hide() - lastPage = easingPages.filter(".#{name}").show() - evil.body.addClass('easing-page') - - if pageAnimated - allScroll = evil.win.scrollTop() - pageTop = lastPage.offset().top + 10 - after 600, -> - if allScroll > pageTop - scrollTo pageTop, -> lastPage.trigger('open') - else - lastPage.trigger('open') - else - after 800, -> lastPage.trigger('open') - - showSubpage() - evil.win.on('hashchange', showSubpage) - - after 1, -> - pageAnimated = true - evil.body.addClass('page-animation') - - evil.win.on 'keyup', (e) -> - location.hash = '' if e.keyCode == 27 # Esc - - # Open source badge text - - corner = $('.open-source') - mainLang = (code) -> code.replace(/-[^-]+$/, '') - - pageLang = mainLang($('html').attr('lang')) - systemLang = mainLang(navigator.language || navigator.userLanguage) - corner.addClass('user-can-translate') if pageLang != systemLang - - # Open source corner animation - - cornerActivated = false - cornerAnimation = -> - return if evil.win.width() < 600 - - cornerActivated = true - evil.win.off('resize.corner') - - if evil.body.hasClass('transform-3d') - shadow = corner.find('.shadow') - rotator = corner.find('.rotator') - duration = rotator.css('transition-duration') - duration = parseFloat(duration) * 1000 - - shadowing = -> - if shadow.is(':animated') - shadow.stop(true).animate(opacity: 0, (duration / 2), 'easeOutQuart') - else - shadow.animate(opacity: 1, (duration / 2), 'easeInQuart'). - animate(opacity: 0, (duration / 2), 'easeOutQuart') - - showCorner = -> - corner.addClass('show') - shadowing() - hideCorner = -> - corner.removeClass('show tappable') - shadowing() - else - showCorner = -> corner.addClass('show') - hideCorner = -> corner.removeClass('show tappable') - - corner.mouseenter(showCorner) - corner.mouseleave(hideCorner) - - corner.find('.crop').on 'touchend', -> - if corner.hasClass('show') - hideCorner() - else - showCorner() - corner.addClass('tappable') - false - - cornerAnimation() - evil.win.on('resize.corner', cornerAnimation) unless cornerActivated - - # Enable GitHub star button - - star = $('.star') - if evil.body.width() > 430 - document.body.onload = -> - star.html( star.text() ).addClass('is-enable') - star.find('iframe').on 'load', -> star.addClass('is-show') - after 5000, -> star.addClass('is-show') - - # Change languages link to select - - changer = $('