Merge pull request #63 from amarcu5/develop-1.0.0

v1.0.0
This commit is contained in:
amarcu5 2018-12-02 01:23:00 +00:00 committed by GitHub
commit 73299ed9ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
116 changed files with 6391 additions and 1548 deletions

157
README.md
View File

@ -1,11 +1,28 @@
# PiPer <p align="center">
Adds Picture in Picture functionality to Safari for YouTube, Netflix, Amazon Video, Twitch, and more! <img src="/promo/Icon-512.png" alt="PiPer logo" width="200" />
</p>
<img src="/promo/Promo-shot.png" alt="Screenshot of PiPer in action" width="512" /> <h1 align="center">
PiPer
</h1>
<p align="center">
PiPer is the browser extension to watch video Picture in Picture.
</p>
<p align="center">
<a href="#installation">Install</a> ·
<a href="https://paypal.me/adampmarcus">Donate</a> ·
<a href="https://github.com/amarcu5/PiPer/issues">Report an issue</a>
</p>
***
## Contents ## Contents
- [Features](#features) - [Features](#features)
- [Installation](#installation) - [Installation](#installation)
* [Safari](#safari)
* [Chrome](#chrome)
- [Supported sites](#supported-sites) - [Supported sites](#supported-sites)
- [Changelog](#changelog) - [Changelog](#changelog)
- [Development](#development) - [Development](#development)
@ -19,13 +36,18 @@ Adds Picture in Picture functionality to Safari for YouTube, Netflix, Amazon Vid
## Features ## Features
* Adds a dedicated Picture in Picture button to the video player of [supported sites](#supported-sites) * Adds a dedicated Picture in Picture button to the video player of [supported sites](#supported-sites)
* Button integrates seamlessly with the player including hover effects and tooltips * Button integrates seamlessly with the player including hover effects and tooltips
* Supports closed captions in Picture and Picture mode * Supports closed captions in Picture and Picture mode (Safari only)
* Supports Safari and Chrome
* Free and open source * Free and open source
## Installation ## Installation
Get the latest release from the [Safari Extension Gallery](https://safari-extensions.apple.com/details/?id=com.amarcus.safari.piper-BQ6Q24MF9X) ### Safari
Install from the [Mac App Store](https://itunes.apple.com/app/id1421915518?mt=12&ls=1) by clicking "Get"
<sub>...or live life on the edge with the latest [development build](https://rawgit.com/amarcu5/PiPer/develop/out/PiPer.safariextz) (IMPORTANT: these builds do not update automatically!)</sub> <sub>(The [Safari Extension Gallery](https://safari-extensions.apple.com/details/?id=com.amarcus.safari.piper-BQ6Q24MF9X) is now [deprecated](https://developer.apple.com/documentation/safariextensions))</sub>
### Chrome
Install from the [Chrome Web Store](https://chrome.google.com/webstore/detail/piper/jbjleapidaddpbncgofepljddfeoghkc) by clicking "Add to Chrome"
<sub>...or live life on the edge with the latest [development build](https://github.com/amarcu5/PiPer/tree/develop/out) (IMPORTANT: these builds do not update automatically!)</sub>
## Supported sites ## Supported sites
* [Amazon Video](http://www.amazon.com/PrimeVideo) * [Amazon Video](http://www.amazon.com/PrimeVideo)
@ -71,30 +93,38 @@ You can find information about releases [here](https://github.com/amarcu5/PiPer/
### Building ### Building
#### Prerequisites #### Prerequisites
* Mac running macOS 10.12 Sierra or later * Operating system
* macOS: 10.12 Sierra or newer (required to build Safari extension)
* Windows: Vista or newer using [Cygwin](https://cygwin.com/install.html)
* Linux: 64-bit Ubuntu 14.04+, Debian 8+, openSUSE 13.3+, or Fedora Linux 24+
* Software
* [Node.js](https://nodejs.org)
* [Java](https://www.java.com/en/download/) (Windows only)
#### Build tools #### Build tools
For convenience the following Node.js tools have been packaged with [nexe](https://github.com/nexe/nexe) and included in this repository: The following build tools are used to build the extension:
* [csso](https://github.com/css/csso) (3.1.1) for compressing CSS * [csso](https://github.com/css/csso) for compressing CSS
* [svgo](https://github.com/svg/svgo) (0.7.2) for compressing SVG images * [svgo](https://github.com/svg/svgo) for compressing SVG images
* [xarjs](https://github.com/robertknight/xar-js) (0.2.0) for packaging Safari extension * [xarjs](https://github.com/robertknight/xar-js) for packaging Safari legacy extension
* [google-closure-compiler-js](https://github.com/google/closure-compiler-js) (20170806.0.0) for compiling JavaScript * [google-closure-compiler](https://github.com/google/closure-compiler) for compiling JavaScript
However it is recommended to install the latest versions with [Node.js](https://nodejs.org): These can be installed by executing the following command:
```Shell ```Shell
npm install -g csso-cli npm install -g csso-cli
npm install -g svgo npm install -g svgo
npm install -g xar-js npm install -g xar-js
npm install -g google-closure-compiler-js npm install -g google-closure-compiler
``` ```
Additionally a reimplementation of the utility PlistBuddy used for automated build numbering is [provided](https://github.com/amarcu5/PiPer/tree/master/build-tools/) but it is advisable to download the original as part of [Xcode](https://itunes.apple.com/gb/app/xcode/id497799835?mt=12) or from [Apple's Command Line Tools](https://developer.apple.com/download/)
#### Steps #### Steps
1. Clone the repository 1. Clone the repository
2. Run `make.sh` 2. Run `make.sh`
1. By default this builds the unoptimized and unpackaged development version into the `./out/` directory that can then be installed using Safari's Extension Builder 1. By default this builds the unoptimized and unpackaged development version for all targets into the `./out/` directory
2. Alternatively run `./make.sh -p release` to build the optimized and packaged release version (note that packaging requires a private key) 2. Alternatively:
* `./make.sh -p release` to build the optimized release versions for all targets
* `./make.sh -p release -t chrome` to build the optimized release version for the Chrome browser
* `./make.sh -h` to see the full list of options
### Supporting a new site ### Supporting a new site
If we wanted to support `example.com` with the source: If we wanted to support `example.com` with the source:
@ -110,73 +140,46 @@ If we wanted to support `example.com` with the source:
</div> </div>
</div> </div>
``` ```
We would start by adding a new entry in the `resources` object in `main.js`: We would start by adding a new file `example.js` in the [resources directory](https://github.com/amarcu5/PiPer/tree/master/src/common/scripts/resources):
```JavaScript ```JavaScript
const resources = { export const domain = 'example';
...
'example' : { export const resource = {
buttonParent: function() { buttonParent: function() {
// Returns the element that will contain the button // Returns the element that will contain the button
return document.querySelector('.video-controls'); return document.querySelector('.video-controls');
}, },
videoElement: function() { videoElement: function() {
// Returns the video element // Returns the video element
return document.querySelector('.video-container video'); return document.querySelector('.video-container video');
}, },
// Optional // Optional
captionElement: function() { captionElement: function() {
// Returns the element that contains the video captions // Returns the element that contains the video captions
return document.querySelector('.video-captions'); return document.querySelector('.video-captions');
},
}, },
}; };
``` ```
We also need to update the extension permissions to support the new site by editing `./src/Info.plist`:
```XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Permissions</key>
<dict>
<key>Website Access</key>
<dict>
...
<key>Allowed Domains</key>
<array>
...
<string>example.com</string>
<string>*.example.com</string>
</array>
</dict>
</dict>
</dict>
</plist>
```
We might want to style the button so that it integrates with the page better: We might want to style the button so that it integrates with the page better:
```JavaScript ```JavaScript
const resources = { export const resource = {
... ...
'example' : { // Assign a CSS class
... buttonClassName: 'control',
// Assign a CSS class // Scale the button
buttonClassName: 'control', buttonScale: 0.5,
// Scale the button // Apply custom CSS styles
buttonScale: 0.5, buttonStyle: /** CSS */ (`
// Apply custom CSS styles /* Declaring CSS this way ensures it gets optimized when the extension is built */
buttonStyle: /** CSS */ (` cursor: pointer;
/* Declaring CSS this way ensures it gets optimized when the extension is built */ opacity: 0.5;
cursor: pointer; `),
opacity: 0.5; // Apply a custom CSS hover style
`), buttonHoverStyle: /** CSS */ (`opacity: 1 !important`),
// Apply a custom CSS hover style
buttonHoverStyle: /** CSS */ (`opacity: 1 !important`),
},
}; };
``` ```
For more examples, please see the [source](https://github.com/amarcu5/PiPer/tree/master/src/scripts) For more examples, please see the [source](https://github.com/amarcu5/PiPer/tree/master/src/)
## Acknowledgements ## Acknowledgements
* [Pied PíPer](https://github.com/JoeKuhns/PiedPiPer.safariextension) for the original inspiration and the Netflix icon * [Pied PíPer](https://github.com/JoeKuhns/PiedPiPer.safariextension) for the original inspiration and the Netflix icon
* [Google](https://github.com/google/material-design-icons) for the Picture in Picture icon

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

645
make.sh
View File

@ -4,12 +4,7 @@
EXTENSION_NAME="PiPer" EXTENSION_NAME="PiPer"
# Build tool paths; fallback to local build tools SOURCE_FILES=("main.js" "fix.js" "background.js" "install.js" "localization-bridge.js")
CSSO_PATH=$(type "csso" >/dev/null 2>&1 && echo "csso" || echo "./build-tools/csso")
CCJS_PATH=$(type "google-closure-compiler-js" >/dev/null 2>&1 && echo "google-closure-compiler-js" || echo "./build-tools/google-closure-compiler-js")
XARJS_PATH=$(type "xarjs" >/dev/null 2>&1 && echo "xarjs" || echo "./build-tools/xarjs")
SVGO_PATH=$(type "svgo" >/dev/null 2>&1 && echo "svgo" || echo "./build-tools/svgo")
PLISTBUDDY_PATH=$(type "/usr/libexec/PlistBuddy" >/dev/null 2>&1 && echo "/usr/libexec/PlistBuddy" || echo "./build-tools/plistbuddy")
# Certifcate paths # Certifcate paths
LEAF_CERT_PATH="../certs/cert.pem" LEAF_CERT_PATH="../certs/cert.pem"
@ -24,13 +19,20 @@ show_help() {
Usage: make.sh [options] Usage: make.sh [options]
Options: Options:
-h -? --help Show this screen -h -? --help Show this screen
-p --profile <release|debug> Set settings according to profile [default: debug] -t --target (all|safari|safari-legacy|chrome) Make extension for target browser [default: all]
-c --compress-css Compress CSS -p --profile (release|debug|distribute) Set settings according to profile [default: debug]
-j --compress-js Compress JavaScript -c --compress-css Compress CSS
-s --compress-svg Compress SVG -j --compress-js Compress JavaScript
-e --package-extension Package Safari extension (requires private key) -s --compress-svg Compress SVG
-v --no-version-increment Disable automatic version incrementing -l --logging-level <number> Set logging level (0=all 10=trace 20=info 30=warning 40=error)
-o --optimize-strings Remove unused localized strings by static program analysis
-i --development-team <id> Set development team ID
-a --archive-to-xcode Archive Safari extension to Xcode for Mac App Store distribution
-e --package-extension Package extension for distribution (safari-legacy requires private key)
-d --no-debug-js Remove JavaScript source maps to prevent debugging
-v --no-version-increment Disable automatic version incrementing
EOF EOF
exit 0 exit 0
} }
@ -43,6 +45,7 @@ while :; do
-h|-\?|--help) show_help ;; -h|-\?|--help) show_help ;;
-p|--profile) [[ "$2" ]] && profile=$2 ;; -p|--profile) [[ "$2" ]] && profile=$2 ;;
--profile=?*) profile=${1#*=} ;; --profile=?*) profile=${1#*=} ;;
-l|-t|-i|--logging-level|--target|--development-team) shift ;;
-?*) ;; -?*) ;;
*) break *) break
esac esac
@ -51,19 +54,39 @@ done
# Set default settings as per profile # Set default settings as per profile
case $profile in case $profile in
distribute)
compress_svg=1
compress_css=1
compress_js=1
debug_js=0
package_ext=1
logging_level=100
optimize_strings=1
;;
release) release)
compress_svg=1 compress_svg=1
compress_css=1 compress_css=1
compress_js=1 compress_js=1
package_ext=1 debug_js=1
package_ext=0
logging_level=40
optimize_strings=1
;; ;;
*) *)
compress_svg=0 compress_svg=0
compress_css=0 compress_css=0
compress_js=0 compress_js=0
debug_js=1
package_ext=0 package_ext=0
logging_level=0
optimize_strings=0
profile="debug"
;;
esac esac
update_version=1 update_version=1
archive_xcode=0
development_team=""
targets="all"
set -- "${arguments[@]}" set -- "${arguments[@]}"
@ -74,90 +97,588 @@ while :; do
-j|--compress-js) compress_js=1 ;; -j|--compress-js) compress_js=1 ;;
-s|--compress-svg) compress_svg=1 ;; -s|--compress-svg) compress_svg=1 ;;
-e|--package-extension) package_ext=1 ;; -e|--package-extension) package_ext=1 ;;
-o|--optimize-localizations) optimize_strings=1 ;;
-d|--no-debug-js) debug_js=0 ;;
-v|--no-version-increment) update_version=0 ;; -v|--no-version-increment) update_version=0 ;;
-t|--target) [[ "$2" ]] && targets=$2 && shift ;;
--target=?*) targets=${1#*=} ;;
-l|--logging-level) [[ "$2" ]] && logging_level=$2 && shift ;;
--logging-level=?*) logging_level=${1#*=} ;;
-i|--development-team) [[ "$2" ]] && development_team=$2 && shift ;;
--development-team=?*) development_team=${1#*=} ;;
-a|--archive-to-xcode) archive_xcode=1 ;;
-p|--profile) shift ;; -p|--profile) shift ;;
-?*) ;; -?*) ;;
*) break *) break ;;
esac esac
shift shift
done done
# Highlight selected build profile
echo "Setting '${profile}' profile"
# Validate targets
case $targets in
safari) targets=("safari") ;;
safari-legacy) targets=("safari-legacy") ;;
chrome) targets=("chrome") ;;
*) targets=("safari" "safari-legacy" "chrome")
esac
# Validate logging level
logging_level="${logging_level//[!0-9]/}"
[[ -z "$logging_level" ]] && logging_level=0
# Helper checks for build tool dependency and falls back to 'npx' if possible
function get_node_command() {
if type "$1" &>/dev/null; then
echo "$1"
elif type "npx" &>/dev/null; then
npx_package=$([[ -z "$2" ]] && echo "$1" || echo "$2")
echo "npx --quiet --package ${npx_package} $1"
echo "Info: '$1' command not found therefore falling back to 'npx'; performance may suffer (avoid this by installing package with 'npm install ${npx_package} -g')" >&2
else
echo "Error: '$1' command not found and neither fallback 'npx'" >&2
echo "Please install the latest version of Node.js (see https://nodejs.org/en/download/package-manager/)" >&2
return 1
fi
return 0
}
# Target specific build checks
for i in "${!targets[@]}"; do
if [[ "${targets[$i]}" = "safari" ]]; then
# Only build 'safari' extension target when running under macOS
if [[ "$(uname)" != "Darwin" ]]; then
echo "Warning: Building 'safari' extension skipped as requires macOS" >&2
unset "targets[$i]"
continue
fi
# Ensure with have Xcode command line tools installed
if [[ -z $(xcode-select --print-path) ]]; then
echo "Installing Xcode Command Line Tools (expect a GUI popup)"
xcode-select --install &>/dev/null
echo "Press any key after installation has completed"
read -rsn1
if [[ -z $(xcode-select --print-path) ]]; then
echo "Unable to find Xcode Command Line Tools"
exit 1
fi
fi
elif [[ "${targets[$i]}" = "safari-legacy" ]]; then
# Get 'safari-legacy' specific build tool path and exit if not found
[[ "${package_ext}" -eq 1 ]] && { XARJS_PATH=$(get_node_command "xarjs" "xar-js") || exit 1; }
fi
done
# Check for google closure compiler requirements and exit if not found
CCJS_PATH=$(get_node_command "google-closure-compiler") || exit 1;
if ${CCJS_PATH} --platform native --version &>/dev/null; then
CCJS_PATH="${CCJS_PATH} --platform native";
elif ${CCJS_PATH} --platform java --version &>/dev/null; then
CCJS_PATH="${CCJS_PATH} --platform java";
else
echo "Error: Java runtime required by 'google-closure-compiler' not found" >&2
echo "Please install the latest version of Java (see https://www.java.com/en/download/)" >&2
exit 1
fi
# Check for csso and exit if not found
[[ "${compress_css}" -eq 1 ]] && { CSSO_PATH=$(get_node_command "csso" "csso-cli") || exit 1; }
# Check for svgo and exit if not found
[[ "${compress_svg}" -eq 1 ]] && { SVGO_PATH=$(get_node_command "svgo") || exit 1; }
# Check for git and exit if not found
if [[ "${update_version}" -eq 1 ]] && { ! type "git" &>/dev/null; }; then
echo "Error: 'git' command not found" >&2
echo "Please install the latest version of git (see https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)" >&2
exit 1
else
GIT_PATH=$(sh /etc/profile; which git)
fi
# #
# Build script # Build script
# #
# Set working directory to project root # Set working directory to project root
project_root=$(cd -P -- "$(dirname -- "$0")" && pwd -P) cd "${BASH_SOURCE[0]%/*}"
cd "$project_root" || exit
# Remove output folder # Remove output folder
rm -rf out rm -rf out
# Make output folder # Make common output folder
mkdir -p "out/${EXTENSION_NAME}.safariextension" mkdir -p "out/${EXTENSION_NAME}"
# Copy items into output folder # Copy common items into common output folder
cp -r src/* "out/${EXTENSION_NAME}.safariextension/" cp -r "src/common"/* "out/${EXTENSION_NAME}/"
# Compress all supported images with SVGO # Compress all supported images with SVGO
if [[ "$compress_svg" -eq 1 ]]; then if [[ "${compress_svg}" -eq 1 ]]; then
${SVGO_PATH} -q -f "out/${EXTENSION_NAME}.safariextension/images" ${SVGO_PATH} -q -f "out/${EXTENSION_NAME}/images"
fi fi
# Compress all inline CSS with CSSO # Compress all inline CSS with CSSO
if [[ "$compress_css" -eq 1 ]]; then if [[ "${compress_css}" -eq 1 ]]; then
function minify_css() { function minify_css() {
echo "$@" | sed -e 's/\\"/"/g' -e 's/\\\$/$/g' | ${CSSO_PATH} --declaration-list echo "$@" | sed -e 's/\\"/"/g' -e 's/\\\$/$/g' | ${CSSO_PATH} --declaration-list
} }
export -f minify_css export -f minify_css
export CSSO_PATH export CSSO_PATH
for path in "out/${EXTENSION_NAME}.safariextension/scripts"/*.js; do for path in "out/${EXTENSION_NAME}/scripts"/{*,**/*}.js; do
[[ ! -f "${path}" ]] && continue
source=$(cat "${path}") source=$(cat "${path}")
echo "echo \"$(sed -e 's/\\/\\\\/g' -e 's/\$/\\$/g' -e 's/\`/\\`/g' -e 's/\"/\\\"/g' -e 's/\\n/\\\\n/g' <<< "$source" \ echo "echo \"$(sed -e 's/\\/\\\\/g' -e 's/\$/\\$/g' -e 's/`/\\`/g' -e 's/\"/\\\"/g' -e 's/\\n/\\\\n/g' <<< "$source" \
| perl -0pe 's/\/\*\*\s+CSS\s+\*\/\s*\(\s*\\`(.*?)\\`\s*\)/\\`\$(minify_css '\''$1'\'')\\`/gms')\"" \ | tr '\n' '\f' \
| sed -E 's/\/\*\*[[:space:]]+CSS[[:space:]]+\*\/[[:space:]]*\([[:space:]]*\\`([^`]*)\\`[[:space:]]*\)/\\`\$(minify_css '\''\1'\'')\\`/g' \
| tr '\f' '\n')\"" \
| sh > "${path}" | sh > "${path}"
done done
fi fi
# Use closure compiler to compress javascript # Get current version from git if automatic versioning enabled
if [[ "$compress_js" -eq 1 ]]; then if [[ "${update_version}" -eq 1 ]]; then
for path in "out/${EXTENSION_NAME}.safariextension/scripts"/*.js; do
[[ $(basename $path) == "externs.js" ]] && continue # Check we're inside a git work tree
path=${path%.*} inside_git_repo="$(git rev-parse --is-inside-work-tree 2>/dev/null)"
if [[ "${inside_git_repo}" ]]; then
# Get number of commits and release version from most recent tag
number_of_commits=$(($("${GIT_PATH}" rev-list HEAD --count) + 1))
git_release_version=$("${GIT_PATH}" describe --tags --always --abbrev=0)
git_release_version=${git_release_version%%-*};
git_release_version=${git_release_version#*v};
# Otherwise issue warning and set blank version
else
echo "Warning: Unable to set version automatically as cannot find 'git' repository (ensure repository has been cloned to fix this)" >&2
number_of_commits="0"
git_release_version="0.0.0"
fi
# Helper performs multiline sed regular expression
function multiline_sed_regex() {
mv "$1" "$1.bak"
echo -n "$(cat "$1.bak")" | tr "\n" "\f" | sed -E "$2" | tr "\f" "\n" > "$1"
rm -rf "$1.bak"
}
fi
# Make resources index file
import_list=""
resource_list=""
alias_list=""
resource_count=0
for path in "out/${EXTENSION_NAME}/scripts/resources"/*.js; do
path="${path##*/}"
[[ $path == "index.js" ]] && continue
resource_count=$((resource_count+1))
import_list="${import_list}"$'\n'"import * as r${resource_count} from \"./${path}\";"
resource=$(<"out/${EXTENSION_NAME}/scripts/resources/${path}")
regex_arr="(^|[ "$'\n'"])(const|let|var)[ "$'\n'"]+domain[ "$'\n'"]*=[ "$'\n'"]*\[([^]]+)\]"
regex_val="(^|[ "$'\n'"])(const|let|var)[ "$'\n'"]+domain[ "$'\n'"]*="
if [[ "$resource" =~ $regex_arr ]]; then
IFS=", " read -a arr <<< "${BASH_REMATCH[3]}"
resource_list="${resource_list}"$'\n'"resources[${arr[0]}] = r${resource_count}.resource;"
for ((i=1;i<${#arr[@]};i++)); do
alias_list="${alias_list}"$'\n'"resources[${arr[$i]}] = resources[${arr[0]}];"
done
elif [[ "$resource" =~ $regex_val ]]; then
resource_list="${resource_list}"$'\n'"resources[r${resource_count}.domain] = r${resource_count}.resource;"
else
echo "Warning: No domain's listed for resource '${path}'" >&2
fi
done
{
cat <<EOF
/** Auto-generated file **/
${import_list}
export const resources = {};
${resource_list}
${alias_list}
EOF
} >"out/${EXTENSION_NAME}/scripts/resources/index.js"
for target in "${targets[@]}"; do
echo "Building '${target}' extension"
# Set target specific flags
case $target in
safari)
browser=1
target_extension=""
common_file_path="/Extension/Resources"
;;
safari-legacy)
browser=1
target_extension=".safariextension"
common_file_path=""
;;
chrome)
browser=2
target_extension=""
common_file_path=""
;;
*) exit 1
esac
# Make target folder
mkdir -p "out/${EXTENSION_NAME}-${target}${target_extension}${common_file_path}"
# Copy items from common output folder to target folder
cp -r "out/${EXTENSION_NAME}"/* "out/${EXTENSION_NAME}-${target}${target_extension}${common_file_path}/"
# Copy target specific items to target output folder
cp -r "src/${target}"/* "out/${EXTENSION_NAME}-${target}${target_extension}/" 2>/dev/null
# Use closure compiler to compress javascript
function remove_element() {
for i in "${!files[@]}"; do
if [[ ${files[$i]} = "$1" ]]; then
unset "files[$i]"
fi
done
}
function add_element() {
remove_element "$1"
files+=("$1")
}
function get_absolute_path() {
local dirname="${1%/*}"
local basename="${1##*/}"
echo "$(cd "$dirname" 2>/dev/null; pwd)/$basename"
}
# Convert absolute paths to platform native path on Windows
function fix_absolute_path() {
case "$(uname -s)" in
CYGWIN*|MINGW32*|MSYS*)
echo "$(cygpath -wa ${1})"
;;
*) echo "$1"
esac
}
function process_file() {
local dirname="${1%/*}"
local imports=()
if [[ ! -f "$1" ]]; then
remove_element "$1"
return
fi
local source=$(<"$1")
regex="(^| |"$'\n'")(import|export)["$'\n'" ]+(([*a-zA-Z0-9_,{}"$'\n'" $]+)from["$'\n'" ]+)?['\"]([^'\"]+)['\"][ "$'\n'";]"
while true; do
if [[ "$source" =~ $regex ]]; then
source="${source##*${BASH_REMATCH[0]}}"
imports+=("${BASH_REMATCH[5]}")
else
break
fi
done
for i in "${!imports[@]}"; do
imports[$i]=$(cd "$dirname"; get_absolute_path "${imports[$i]}")
add_element "${imports[$i]}"
done
for i in "${!imports[@]}"; do
process_file "${imports[$i]}"
done
}
scripts_path=$(get_absolute_path "out/${EXTENSION_NAME}-${target}${target_extension}${common_file_path}/scripts")
defines_path="${scripts_path}/defines.js"
extern_path=$(fix_absolute_path "${scripts_path}/externs.js")
defines_processed_path=$(echo "${defines_path%.*}" | sed -E 's|[/@\]|$|g' | sed -E 's/[-. ]/_/g' | sed -e 's/\[/%5B/g' -e 's/]/%5D/g' -e 's/>/%3E/g' -e 's/</%3C/g')
browser_flag="BROWSER$\$module${defines_processed_path}=${browser}"
logging_flag="LOGGING_LEVEL$\$module${defines_processed_path}=${logging_level}"
if [[ "$optimize_strings" -eq 1 ]]; then
localization_path="${scripts_path}/localization.js"
localization_source=$(<"$localization_path")
fi
for entry in "${SOURCE_FILES[@]}"; do
files=()
absolute_entry="${scripts_path}/${entry}"
[[ ! -f "$absolute_entry" ]] && continue
add_element "$absolute_entry"
process_file "$absolute_entry"
# Statically analyze javascript and remove unused localized strings
if [[ "$optimize_strings" -eq 1 ]]; then
locale_keys=()
regex="(=[ \t"$'\n'"]*localizedString(WithReplacements)?[ \t"$'\n'";,])|(localizedString(WithReplacements)?\([ \t"$'\n'"]*(\"([^\"]+)\"|'([^']+)'|([^'\",)]+))[ \t"$'\n'"]*[,)])"
dynamic_access=0
for path in "${files[@]}"; do
[[ "$path" == "$localization_path" ]] && continue
source=$(<"$path")
while true; do
if [[ "$source" =~ $regex ]]; then
source="${source##*${BASH_REMATCH[0]}}"
locale_key="${BASH_REMATCH[6]:-${BASH_REMATCH[7]}}"
if [[ ! -z "$locale_key" ]]; then
locale_keys+=("$locale_key")
else
dynamic_access=1
break
fi
else
break
fi
done
done
source="$localization_source"
if [[ "$dynamic_access" -eq 0 ]]; then
processing="$localization_source"
regex="localizations\[[ \t"$'\n'"]*('([^']+)'|\"([^\"]+)\")[ \t"$'\n'"]*\][^=]+=[ "$'\n'"]*{([^}'\"]*('([^'\\]|\\.)*'|\"([^\"\\]|\\.)*\")?)*}[ \t"$'\n'"]*;?"
while true; do
if [[ "$processing" =~ $regex ]]; then
processing=${processing##*"${BASH_REMATCH[0]}"}
locale_key="${BASH_REMATCH[2]:-${BASH_REMATCH[3]}}"
found=0
for key in "${locale_keys[@]}"; do
if [[ "$key" == "$locale_key" ]]; then
found=1
break
fi
done
if [[ "$found" -eq 0 ]]; then
source=${source/"${BASH_REMATCH[0]}"/}
fi
else
break
fi
done
fi
echo "$source" > "${localization_path}"
fi
absolute_entry=$(fix_absolute_path "$absolute_entry")
defines=()
js_code=()
for path in "${files[@]}"; do
path=$(fix_absolute_path "$path")
js_code=("--js" "$path" "${js_code[@]}")
if [[ "$path" = "$defines_path" ]]; then
defines=(
"--define" "$logging_flag"
"--define" "$browser_flag"
)
fi
done
if [[ "$debug_js" -eq 0 ]]; then
source_map_options=()
else
source_map_options=(
--create_source_map "${absolute_entry}.map"
--source_map_location_mapping "$scripts_path|."
--source_map_include_content
)
fi
if [[ "$compress_js" -eq 0 ]]; then
compression_options=(
--compilation_level WHITESPACE_ONLY \
--js_module_root "$scripts_path" \
--formatting PRETTY_PRINT \
--formatting PRINT_INPUT_DELIMITER \
)
else
compression_options=(
--compilation_level ADVANCED \
--use_types_for_optimization \
--assume_function_wrapper \
--jscomp_error strictCheckTypes \
--jscomp_error strictMissingProperties \
--jscomp_error checkTypes \
--jscomp_error checkVars \
--jscomp_error reportUnknownTypes \
--externs "$extern_path" \
"${defines[@]}" \
)
fi
${CCJS_PATH} \ ${CCJS_PATH} \
--compilationLevel ADVANCED \ "${compression_options[@]}" \
--warningLevel VERBOSE \ --warning_level VERBOSE \
--newTypeInf \ "${source_map_options[@]}" \
--useTypesForOptimization \ "${js_code[@]}" \
--externs "out/${EXTENSION_NAME}.safariextension/scripts/externs.js" \ > "${absolute_entry%.*}.cjs"
"${path}.js" > "${path}.min.js"
mv "${path}.min.js" "${path}.js"
done done
fi
rm "out/${EXTENSION_NAME}.safariextension/scripts/externs.js"
# Update version info from git # Remove uncompiled JavaScript
if [[ "$update_version" -eq 1 ]]; then rm -f "${scripts_path}/"{*,**/*}.js
git=$(sh /etc/profile; which git)
number_of_commits=$(($("$git" rev-list HEAD --count) + 1)) # Remove any empty folders
git_release_version=$("$git" describe --tags --always --abbrev=0) for path in "${scripts_path}/"{*,**/*}; do
git_release_version=${git_release_version%%-*}; if [[ -d "$path" ]] && [[ ! -f "$path"/* ]]; then
git_release_version=${git_release_version#*v}; rm -rf "$path"
info_plist="out/${EXTENSION_NAME}.safariextension/Info.plist" fi
update_plist="update.plist" done
${PLISTBUDDY_PATH} -c "Set :CFBundleVersion $number_of_commits" "$info_plist"
${PLISTBUDDY_PATH} -c "Set :CFBundleShortVersionString ${git_release_version}" "$info_plist" # Restore '.js' extension for compiled JavaScript
${PLISTBUDDY_PATH} -c "Set \":Extension Updates:0:CFBundleVersion\" $number_of_commits" "$update_plist" for entry in "${SOURCE_FILES[@]}"; do
${PLISTBUDDY_PATH} -c "Set \":Extension Updates:0:CFBundleShortVersionString\" ${git_release_version#*v}" "$update_plist" entry="${scripts_path}/"${entry%.*}
fi [ ! -f "${entry}.cjs" ] && continue
mv "${entry}.cjs" "${entry}.js"
done
# Embed source maps and remove map files
if [[ "$debug_js" -eq 1 ]]; then
for entry in "${SOURCE_FILES[@]}"; do
entry="${scripts_path}/${entry}"
[[ ! -f "${entry}" ]] && continue
source_map=$(base64 "${entry}.map" | tr -d \\n)
echo "//# sourceMappingURL=data:application/json;base64,${source_map}" >> "${entry}"
rm -f "${entry}.map"
done
fi
# Package safari extension # Safari specific build steps
cd out || exit if [[ "${target}" == "safari" ]]; then
if [[ "$package_ext" -eq 1 ]] && [[ -f "${PRIVATE_KEY_PATH}" ]]; then
[[ ${XARJS_PATH} != /* ]] && ! type "${XARJS_PATH}" >/dev/null 2>&1 && XARJS_PATH="../${XARJS_PATH}" # Update version info from git
${XARJS_PATH} create "${EXTENSION_NAME}.safariextz" --cert "${LEAF_CERT_PATH}" --cert "${INTERMEDIATE_CERT_PATH}" --cert "${ROOT_CERT_PATH}" --private-key "${PRIVATE_KEY_PATH}" "${EXTENSION_NAME}.safariextension" if [[ "${update_version}" -eq 1 ]]; then
rm -rf "${EXTENSION_NAME}.safariextension" multiline_sed_regex "out/${EXTENSION_NAME}-${target}/Extension/Info.plist" "s|(> *CFBundleShortVersionString *</key>[^>]+>)[^<]+|\1${git_release_version}|g"
fi multiline_sed_regex "out/${EXTENSION_NAME}-${target}/Extension/Info.plist" "s|(> *CFBundleVersion *</key>[^>]+>)[^<]+|\1${number_of_commits}|g"
multiline_sed_regex "out/${EXTENSION_NAME}-${target}/App/Info.plist" "s|(> *CFBundleShortVersionString *</key>[^>]+>)[^<]+|\1${git_release_version}|g"
multiline_sed_regex "out/${EXTENSION_NAME}-${target}/App/Info.plist" "s|(> *CFBundleVersion *</key>[^>]+>)[^<]+|\1${number_of_commits}|g"
fi
# Get development team id automatically if needed
if [[ -z "${development_team}" ]]; then
# Helper maintains unique list of ids
team_ids=()
function add_unique_team_ids() {
for i in "${!team_ids[@]}"; do
[[ ${team_ids[$i]} = "$1" ]] && return
done
team_ids+=("$1")
}
# Search mobileprovision files for team identifiers
regex="TeamIdentifier<\/key>[^\/]+>([A-Z0-9]{10})<\/"
for path in "${HOME}/Library/MobileDevice/Provisioning Profiles"/*.mobileprovision; do
source=$(cat "${path}" | iconv -f "ISO-8859-1" -t "UTF-8")
if [[ "${source}" =~ $regex ]]; then
add_unique_team_ids "${BASH_REMATCH[1]}"
fi
done
# If multiple or no identifiers found then prompt the user
development_team_hint="(avoid this message in future by specifying --development-team)"
if (( ${#team_ids[@]} == 0 )); then
echo "Unable to find development team automatically, please enter below: ${development_team_hint}"
read development_team </dev/tty
elif (( ${#team_ids[@]} == 1 )); then
development_team="${team_ids[0]}"
else
echo "Multiple development team's found"
for (( i=0; i < ${#team_ids[@]}; i++ )); do
echo " [${i}]: ${team_ids[$i]}"
done
echo "Please select development team to use: ${development_team_hint}"
read selection </dev/tty
development_team="${team_ids[$selection]}"
fi
fi
# Build the xcode project
config_profile=$([[ "${profile}" == "debug" ]] && echo "Debug" || echo "Release")
xcodebuild -allowProvisioningUpdates -allowProvisioningDeviceRegistration -quiet -project "./out/${EXTENSION_NAME}-${target}/PiPer.xcodeproj" -scheme "PiPer" archive -archivePath "./out/${EXTENSION_NAME}-${target}.xcarchive" -configuration "${config_profile}" CODE_SIGN_STYLE="Automatic" CODE_SIGN_IDENTITY="Mac Developer" DEVELOPMENT_TEAM="${development_team}"
xcodebuild -allowProvisioningUpdates -allowProvisioningDeviceRegistration -quiet -exportArchive -archivePath "./out/${EXTENSION_NAME}-${target}.xcarchive" -exportPath "./out/" -exportOptionsPlist "./out/${EXTENSION_NAME}-${target}/exportOptions.plist" CODE_SIGN_STYLE="Automatic" CODE_SIGN_IDENTITY="Mac Developer" DEVELOPMENT_TEAM="${development_team}" &>/dev/null
# Copy archive to Xcode if needed
if [[ "${archive_xcode}" -eq 1 ]]; then
archive_time=$(date '+%d-%m-%Y, %H.%M')
mv "./out/${EXTENSION_NAME}-${target}.xcarchive" "${HOME}/Library/Developer/Xcode/Archives/${EXTENSION_NAME} ${archive_time}.xcarchive"
fi
# Package extension
if [[ "${package_ext}" -eq 1 ]]; then
productbuild --quiet --component "./out/PiPer.app" "/Applications" "./out/${EXTENSION_NAME}-${target}.pkg"
rm -rf "./out/PiPer.app"
else
mv "out/PiPer.app" "out/${EXTENSION_NAME}-${target}.app"
fi
# Remove everything else
rm -rf "out/${EXTENSION_NAME}-${target}.xcarchive"
rm -rf "out/${EXTENSION_NAME}-${target}"
elif [[ "${target}" == "safari-legacy" ]]; then
# Remove irrelevant target file
rm -f "out/${EXTENSION_NAME}-${target}${target_extension}/update.plist"
# Update version info from git
if [[ "${update_version}" -eq 1 ]]; then
info_plist="out/${EXTENSION_NAME}-${target}${target_extension}/Info.plist"
update_plist="src/${target}/update.plist"
multiline_sed_regex "${info_plist}" "s|(> *CFBundleShortVersionString *</key>[^>]+>)[^<]+|\1${git_release_version}|g"
multiline_sed_regex "${info_plist}" "s|(> *CFBundleVersion *</key>[^>]+>)[^<]+|\1${number_of_commits}|g"
multiline_sed_regex "${update_plist}" "s|(> *CFBundleShortVersionString *</key>[^>]+>)[^<]+|\1${git_release_version}|g"
multiline_sed_regex "${update_plist}" "s|(> *CFBundleVersion *</key>[^>]+>)[^<]+|\1${number_of_commits}|g"
fi
# Package safari extension
if [[ "${package_ext}" -eq 1 ]] && [[ -f "out/${PRIVATE_KEY_PATH}" ]]; then
(cd out && ${XARJS_PATH} create "${EXTENSION_NAME}-${target}.safariextz" --cert "${LEAF_CERT_PATH}" --cert "${INTERMEDIATE_CERT_PATH}" --cert "${ROOT_CERT_PATH}" --private-key "${PRIVATE_KEY_PATH}" "${EXTENSION_NAME}-${target}${target_extension}")
rm -rf "out/${EXTENSION_NAME}-${target}${target_extension}"
fi
elif [[ "${target}" == "chrome" ]]; then
# Update manifest version information
if [[ "${update_version}" -eq 1 ]]; then
sed -i.bak -E "s|\"version\": *\"[^\"]+\"|\"version\": \"${git_release_version}.${number_of_commits}\"|g" "out/${EXTENSION_NAME}-${target}/manifest.json"
sed -i.bak -E "s|\"version_name\": *\"[^\"]+\"|\"version_name\": \"${git_release_version}\"|g" "out/${EXTENSION_NAME}-${target}/manifest.json"
rm -rf "out/${EXTENSION_NAME}-${target}/manifest.json.bak"
fi
# Package chrome extension
if [[ "${package_ext}" -eq 1 ]]; then
(cd "out/${EXTENSION_NAME}-${target}${target_extension}" && zip -rq "${EXTENSION_NAME}-${target}${target_extension}.zip" *)
mv "out/${EXTENSION_NAME}-${target}${target_extension}/${EXTENSION_NAME}-${target}${target_extension}.zip" "out/"
rm -rf "out/${EXTENSION_NAME}-${target}${target_extension}"
fi
fi
done
# Clean common output folder
rm -rf "out/${EXTENSION_NAME}"
echo "Done." echo "Done."

BIN
out/PiPer-chrome.zip Normal file

Binary file not shown.

Binary file not shown.

BIN
out/PiPer-safari.pkg Normal file

Binary file not shown.

Binary file not shown.

BIN
promo/Chrome-small-tile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 512 B

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
promo/Icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

BIN
promo/Screenshot-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

BIN
promo/Screenshot-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
promo/Screenshot-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

BIN
promo/Screenshot-4a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
promo/Screenshot-4b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
promo/Screenshot-5a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 B

View File

@ -1,141 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Author</key>
<string>Adam Marcus</string>
<key>Builder Version</key>
<string>12602.4.8</string>
<key>CFBundleDisplayName</key>
<string>PiPer</string>
<key>CFBundleIdentifier</key>
<string>com.amarcus.safari.piper</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>0.0</string>
<key>CFBundleVersion</key>
<string>0</string>
<key>Content</key>
<dict>
<key>Scripts</key>
<dict>
<key>End</key>
<array>
<string>scripts/main.js</string>
</array>
</dict>
</dict>
<key>Description</key>
<string>Adds Picture in Picture functionality to Youtube, Netflix, Amazon Video, Twitch, and more!</string>
<key>DeveloperIdentifier</key>
<string>BQ6Q24MF9X</string>
<key>ExtensionInfoDictionaryVersion</key>
<string>1.0</string>
<key>Permissions</key>
<dict>
<key>Website Access</key>
<dict>
<key>Allowed Domains</key>
<array>
<string>aktualne.cz</string>
<string>*.aktualne.cz</string>
<string>amazon.co.uk</string>
<string>*.amazon.co.uk</string>
<string>amazon.com</string>
<string>*.amazon.com</string>
<string>amazon.fr</string>
<string>*.amazon.fr</string>
<string>amazon.de</string>
<string>*.amazon.de</string>
<string>amazon.ca</string>
<string>*.amazon.ca</string>
<string>collegehumor.com</string>
<string>*.collegehumor.com</string>
<string>curiositystream.com</string>
<string>*.curiositystream.com</string>
<string>eurosportplayer.com</string>
<string>*.eurosportplayer.com</string>
<string>giantbomb.com</string>
<string>*.giantbomb.com</string>
<string>hulu.com</string>
<string>*.hulu.com</string>
<string>littlethings.com</string>
<string>*.littlethings.com</string>
<string>mashable.com</string>
<string>*.mashable.com</string>
<string>metacafe.com</string>
<string>*.metacafe.com</string>
<string>mixer.com</string>
<string>*.mixer.com</string>
<string>mlb.com</string>
<string>*.mlb.com</string>
<string>mlb.tv</string>
<string>*.mlb.tv</string>
<string>netflix.com</string>
<string>*.netflix.com</string>
<string>ocs.fr</string>
<string>*.ocs.fr</string>
<string>openload.co</string>
<string>*.openload.co</string>
<string>oload.stream</string>
<string>*.oload.stream</string>
<string>oload.tv</string>
<string>*.oload.tv</string>
<string>periscope.tv</string>
<string>*.periscope.tv</string>
<string>plex.tv</string>
<string>*.plex.tv</string>
<string>primevideo.com</string>
<string>*.primevideo.com</string>
<string>pscp.tv</string>
<string>*.pscp.tv</string>
<string>seznam.cz</string>
<string>*.seznam.cz</string>
<string>stream.cz</string>
<string>*.stream.cz</string>
<string>streamable.com</string>
<string>*.streamable.com</string>
<string>ted.com</string>
<string>*.ted.com</string>
<string>theonion.com</string>
<string>*.theonion.com</string>
<string>twitch.tv</string>
<string>*.twitch.tv</string>
<string>udemy.com</string>
<string>*.udemy.com</string>
<string>vevo.com</string>
<string>*.vevo.com</string>
<string>vice.com</string>
<string>*.vice.com</string>
<string>vid.me</string>
<string>*.vid.me</string>
<string>vier.be</string>
<string>*.vier.be</string>
<string>vijf.be</string>
<string>*.vijf.be</string>
<string>vrt.be</string>
<string>*.vrt.be</string>
<string>vrv.co</string>
<string>*.vrv.co</string>
<string>yeloplay.be</string>
<string>*.yeloplay.be</string>
<string>youtu.be</string>
<string>*.youtu.be</string>
<string>youtube.com</string>
<string>*.youtube.com</string>
<string>zes.be</string>
<string>*.zes.be</string>
</array>
<key>Include Secure Pages</key>
<true/>
<key>Level</key>
<string>Some</string>
</dict>
</dict>
<key>Update Manifest URL</key>
<string>https://s3.amazonaws.com/piper-extension/update.plist</string>
<key>Website</key>
<string>https://github.com/amarcu5/PiPer/</string>
</dict>
</plist>

115
src/chrome/install.html Normal file

File diff suppressed because one or more lines are too long

31
src/chrome/manifest.json Executable file
View File

@ -0,0 +1,31 @@
{
"name": "PiPer",
"description": "Adds Picture in Picture functionality to YouTube, Netflix, Amazon Video, Twitch, and more!",
"version": "0.0.0.0",
"version_name": "0.0.0",
"icons": {
"128": "Icon-128.png"
},
"background": {
"scripts": ["scripts/background.js"],
"persistent": false
},
"content_scripts": [
{
"all_frames": true,
"matches": ["http://*/*", "https://*/*"],
"run_at": "document_idle",
"js": ["scripts/main.js"]
}
],
"permissions": [
"activeTab",
"storage"
],
"web_accessible_resources": [
"images/*.svg",
"scripts/*.js"
],
"minimum_chrome_version": "69.0.3483.0",
"manifest_version": 2
}

View File

@ -0,0 +1,5 @@
chrome.runtime.onInstalled.addListener(function(/** {reason: string} */ details) {
if (details.reason == "install") {
chrome.tabs.create({url: chrome.extension.getURL("install.html")});
}
});

View File

@ -0,0 +1,48 @@
import { info } from './logger.js'
import { localizedString, localizedStringWithReplacements } from './localization.js'
// Hide page during loading
const htmlTag = /** @type {HTMLElement} */ (document.getElementsByTagName("html")[0]);
htmlTag.style.display = 'none';
document.addEventListener('DOMContentLoaded', function() {
// Localize text elements
const localizedElements = document.getElementsByClassName('localized-string');
for (let index = 0, element; element = localizedElements[index]; index++) {
const key = element.textContent.trim();
let string;
if (key == 'chrome-flags-warning') {
string = localizedStringWithReplacements(key, [
['emphasis', '<span class="warning-emphasis">'],
['/emphasis', '</span>'],
]);
} else {
string = localizedString(key);
}
element.innerHTML = string;
}
// Make page visible
htmlTag.style.removeProperty('display');
// Open required Chrome flag if warning button clicked
document.getElementById('warning-button').addEventListener('click', function(event) {
chrome.tabs.create({url: 'chrome://flags/#enable-surfaces-for-videos'});
});
// Test for Picture in Picture support and display warning to activate Chrome flags if needed
const video = /** @type {HTMLVideoElement} */ (document.getElementById('test-video'));
video.addEventListener('loadeddata', function() {
video.requestPictureInPicture().catch(function(/** Error */ error) {
if (~error.message.indexOf('Picture-in-Picture is not available')) {
info('Picture-in-Picture NOT supported');
document.getElementById('warning').style.display = 'flex';
} else {
info('Picture-in-Picture IS supported');
}
});
});
});

BIN
src/common/Icon-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Symbol" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 36 36" style="enable-background:new 0 0 36 36;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<path class="st0" d="M3.5,25.5c-0.4,0-1-0.1-1.5-0.5C1.7,24.6,1.2,24,1.2,23V9.8c0-0.3,0-1,0.4-1.6c0.4-0.5,0.9-0.7,1.6-0.7h24.9
c1.3,0.1,2.5,0.8,2.5,2.2v8h-2.5V10c-0.1,0-0.1,0-0.1,0L3.8,10v13h10v2.5h-10C3.7,25.5,3.6,25.5,3.5,25.5z"/>
</g>
<path class="st0" d="M13.5,18.5l-5.6-5.6h2.7c0.6,0,1-0.4,1-1s-0.4-1-1-1H5.5c-0.1,0-0.3,0-0.4,0.1c-0.2,0.1-0.4,0.3-0.5,0.5
c-0.1,0.1-0.1,0.3-0.1,0.4v5.4c0,0.6,0.4,1,1,1s1-0.4,1-1v-3l5.6,5.6c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3
C13.9,19.5,13.9,18.9,13.5,18.5z"/>
<g>
<path class="st0" d="M31.9,31.2H17.6c-1.6,0-2.9-1.3-2.9-2.8v-6.3c0-1.6,1.3-2.8,2.9-2.8h14.3c1.6,0,2.8,1.3,2.8,2.8v6.3
C34.8,29.9,33.4,31.2,31.9,31.2z M17.6,21.8c-0.2,0-0.4,0.1-0.4,0.3v6.3c0,0.2,0.1,0.3,0.4,0.3h14.3c0.2,0,0.3-0.2,0.3-0.3v-6.3
c0-0.2-0.1-0.3-0.3-0.3H17.6z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Symbol" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 36 36" style="enable-background:new 0 0 36 36;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<path class="st0" d="M12.8,25.8H3.7c-1.2,0-2.5-0.9-2.5-2.5V9.6c0-2.1,1.7-2.2,2-2.2h24.9c2.4,0,2.5,1.7,2.5,2.2v8.5h-2.5V9.9l0,0
H3.8v13.4h9V25.8z"/>
</g>
<path class="st0" d="M13.8,14.2c0-0.6-0.4-1-1-1s-1,0.4-1,1v2.6l-5.6-5.6c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4l5.6,5.6H7.8
c-0.6,0-1,0.4-1,1s0.4,1,1,1h5c0.1,0,0.3,0,0.4-0.1c0.2-0.1,0.4-0.3,0.5-0.5c0.1-0.1,0.1-0.3,0.1-0.4V14.2z"/>
<g>
<path class="st0" d="M31.9,31.8H17.1c-1.6,0-2.9-1.3-2.9-2.8v-6.8c0-1.6,1.3-2.8,2.9-2.8h14.8c1.6,0,2.8,1.3,2.8,2.8v6.7
C34.8,30.4,33.5,31.8,31.9,31.8z M17.1,21.7c-0.2,0-0.4,0.2-0.4,0.4v6.8c0,0.2,0.1,0.3,0.4,0.3h14.8c0.2,0,0.3-0.2,0.3-0.5V22
c0-0.2-0.1-0.3-0.3-0.3H17.1L17.1,21.7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

848
src/common/images/logo.svg Normal file
View File

@ -0,0 +1,848 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#Backround_1_);}
.st1{opacity:0.25;}
.st2{opacity:0.2;}
.st3{fill:none;stroke:#231F20;}
.st4{fill:none;stroke:#231F20;stroke-width:2;}
.st5{opacity:0.18;}
.st6{fill:none;stroke:#231F20;stroke-width:3;}
.st7{fill:none;stroke:#231F20;stroke-width:4;}
.st8{opacity:0.16;}
.st9{fill:none;stroke:#231F20;stroke-width:5;}
.st10{fill:none;stroke:#231F20;stroke-width:6;}
.st11{opacity:0.14;}
.st12{fill:none;stroke:#231F20;stroke-width:7;}
.st13{fill:none;stroke:#231F20;stroke-width:8;}
.st14{opacity:0.12;}
.st15{fill:none;stroke:#231F20;stroke-width:9;}
.st16{fill:none;stroke:#231F20;stroke-width:10;}
.st17{opacity:0.1;}
.st18{fill:none;stroke:#231F20;stroke-width:11;}
.st19{fill:none;stroke:#231F20;stroke-width:12;}
.st20{opacity:9.000000e-02;}
.st21{fill:none;stroke:#231F20;stroke-width:13;}
.st22{fill:none;stroke:#231F20;stroke-width:14;}
.st23{opacity:8.000000e-02;}
.st24{fill:none;stroke:#231F20;stroke-width:15;}
.st25{fill:none;stroke:#231F20;stroke-width:16;}
.st26{opacity:7.000000e-02;}
.st27{fill:none;stroke:#231F20;stroke-width:17;}
.st28{fill:none;stroke:#231F20;stroke-width:18;}
.st29{opacity:6.000000e-02;}
.st30{fill:none;stroke:#231F20;stroke-width:19;}
.st31{fill:none;stroke:#231F20;stroke-width:20;}
.st32{opacity:5.000000e-02;}
.st33{fill:none;stroke:#231F20;stroke-width:21;}
.st34{fill:none;stroke:#231F20;stroke-width:22;}
.st35{opacity:4.000000e-02;}
.st36{fill:none;stroke:#231F20;stroke-width:23;}
.st37{fill:none;stroke:#231F20;stroke-width:24;}
.st38{opacity:3.000000e-02;}
.st39{fill:none;stroke:#231F20;stroke-width:25;}
.st40{fill:none;stroke:#231F20;stroke-width:26;}
.st41{opacity:2.000000e-02;}
.st42{fill:none;stroke:#231F20;stroke-width:27;}
.st43{fill:none;stroke:#231F20;stroke-width:28;}
.st44{opacity:1.000000e-02;}
.st45{fill:none;stroke:#231F20;stroke-width:29;}
.st46{fill:none;stroke:#231F20;stroke-width:30;}
.st47{fill:none;stroke:#FFFFFF;stroke-width:32;stroke-miterlimit:10;}
.st48{fill:none;stroke:#FFFFFF;stroke-width:32;stroke-linecap:round;stroke-miterlimit:10;}
</style>
<linearGradient id="Backround_1_" gradientUnits="userSpaceOnUse" x1="193.802" y1="830.1981" x2="830.1981" y2="193.802" gradientTransform="matrix(1 0 0 -1 0 1024)">
<stop offset="1.179610e-03" style="stop-color:#0671D9"/>
<stop offset="1" style="stop-color:#1B557C"/>
</linearGradient>
<circle id="Backround" class="st0" cx="512" cy="512" r="450"/>
<g id="Shadow" class="st1">
<g>
<g>
<path d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2l133.2,135.3
c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4S430.5,528,422.5,528z"/>
</g>
<g>
<path d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2c24.4,0,44.2,19.8,44.2,44.2
v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5c0,8.5,6.9,15.4,15.4,15.4h263.2
c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st2">
<g>
<path class="st3" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st3" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st3" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st3" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st3" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st2">
<g>
<path class="st4" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st4" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st4" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st4" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st4" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st5">
<g>
<path class="st6" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st6" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st6" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st6" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st6" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st5">
<g>
<path class="st7" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st7" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st7" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st7" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st7" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st8">
<g>
<path class="st9" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st9" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st9" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st9" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st9" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st8">
<g>
<path class="st10" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st10" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st10" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st10" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st10" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st11">
<g>
<path class="st12" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st12" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st12" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st12" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st12" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st11">
<g>
<path class="st13" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st13" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st13" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st13" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st13" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st14">
<g>
<path class="st15" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st15" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st15" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st15" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st15" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st14">
<g>
<path class="st16" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st16" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st16" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st16" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st16" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st17">
<g>
<path class="st18" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st18" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st18" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st18" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st18" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st17">
<g>
<path class="st19" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st19" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st19" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st19" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st19" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st20">
<g>
<path class="st21" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st21" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st21" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st21" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st21" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st20">
<g>
<path class="st22" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st22" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st22" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st22" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st22" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st23">
<g>
<path class="st24" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st24" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st24" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st24" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st24" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st23">
<g>
<path class="st25" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st25" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st25" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st25" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st25" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st26">
<g>
<path class="st27" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st27" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st27" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st27" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st27" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st26">
<g>
<path class="st28" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st28" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st28" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st28" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st28" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st29">
<g>
<path class="st30" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st30" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st30" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st30" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st30" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st29">
<g>
<path class="st31" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st31" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st31" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st31" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st31" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st32">
<g>
<path class="st33" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st33" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st33" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st33" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st33" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st32">
<g>
<path class="st34" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st34" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st34" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st34" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st34" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st35">
<g>
<path class="st36" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st36" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st36" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st36" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st36" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st35">
<g>
<path class="st37" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st37" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st37" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st37" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st37" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st38">
<g>
<path class="st39" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st39" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st39" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st39" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st39" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st38">
<g>
<path class="st40" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st40" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st40" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st40" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st40" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st41">
<g>
<path class="st42" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st42" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st42" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st42" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st42" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st41">
<g>
<path class="st43" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st43" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st43" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st43" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st43" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st44">
<g>
<path class="st45" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st45" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st45" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st45" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st45" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
<g class="st44">
<g>
<path class="st46" d="M253,619.9c-5.6,0-14.3-1.2-21.8-7.5c-5.5-4.6-12.1-13.5-12.1-29.7V339.9c-0.4-4.3-0.8-15.5,6.6-24.3
c3.7-4.3,10.5-9.5,22.1-9.5h458.3h0.3c1.4,0.1,8.8,0.5,16.6,3.8c13.1,5.4,20.7,16,20.7,29.1v146.5H715V338.9c0-0.5,0-1.3-2.9-2.4
c-2.9-1.2-6-1.5-6.8-1.6H247.9h-0.1c-0.1,0.7-0.1,1.5,0,1.7l0.2,1.2v245c0,4.5,0.9,6.8,1.7,7.6c0.8,0.7,2.9,0.9,4,0.8l1.4-0.4
l1,0.1h185.4v28.8H256.9C255.9,619.8,254.6,619.9,253,619.9z M253.4,591.1L253.4,591.1L253.4,591.1z"/>
</g>
<g>
<path class="st46" d="M422.5,528c-3.7,0-7.5-1.4-10.3-4.3L279,388.4c-5.6-5.7-5.5-14.8,0.2-20.3c5.7-5.6,14.8-5.5,20.3,0.2
l133.2,135.3c5.6,5.7,5.5,14.8-0.2,20.3C429.8,526.7,426.1,528,422.5,528z"/>
</g>
<g>
<path class="st46" d="M422.5,528h-94.2c-7.9,0-14.4-6.5-14.4-14.4s6.5-14.4,14.4-14.4h94.2c7.9,0,14.4,6.5,14.4,14.4
S430.5,528,422.5,528z"/>
</g>
<g>
<path class="st46" d="M422.5,528c-7.9,0-14.4-6.5-14.4-14.4V413.3c0-7.9,6.5-14.4,14.4-14.4c7.9,0,14.4,6.5,14.4,14.4v100.4
C436.9,521.5,430.5,528,422.5,528z"/>
</g>
<g>
<path class="st46" d="M773.8,725.6H510.6c-24.4,0-44.2-19.8-44.2-44.2V565.9c0-24.4,19.8-44.2,44.2-44.2h263.2
c24.4,0,44.2,19.8,44.2,44.2v115.5C818,705.7,798.2,725.6,773.8,725.6z M510.6,550.5c-8.5,0-15.4,6.9-15.4,15.4v115.5
c0,8.5,6.9,15.4,15.4,15.4h263.2c8.5,0,15.4-6.9,15.4-15.4V565.9c0-8.5-6.9-15.4-15.4-15.4H510.6L510.6,550.5z"/>
</g>
</g>
</g>
<g id="Symbol">
<g>
<path class="st47" d="M435.1,599H249.7c0,0-22.5,4.1-22.5-22.5c0-26.6,0-243.8,0-243.8s-3.1-18.5,14.3-18.5s457.9,0,457.9,0
s23.6,1,23.6,18.5c0,17.5,0,146.5,0,146.5"/>
<line class="st48" x1="416.1" y1="507.3" x2="283" y2="372"/>
<line class="st48" x1="321.9" y1="507.3" x2="416.1" y2="507.3"/>
<line class="st48" x1="416.1" y1="407" x2="416.1" y2="507.3"/>
<path class="st47" d="M767.5,704.9H504.4c-16.4,0-29.8-13.4-29.8-29.8V559.6c0-16.4,13.4-29.8,29.8-29.8h263.2
c16.4,0,29.8,13.4,29.8,29.8v115.5C797.3,691.5,783.9,704.9,767.5,704.9z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1,380 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1024 1024" enable-background="new 0 0 1024 1024" xml:space="preserve">
<g id="Triangle_Shadow" opacity="0.25">
<path fill="#323232" d="M512.1,116.8c-28.9,0-54.7,14.9-69.1,39.9L71.6,799.9c-14.4,25-14.4,54.8,0,79.8s40.3,39.9,70.7,36.9h742.6
c27.4,3,53.2-11.9,67.6-36.9c14.4-25,14.4-54.8,0-79.8L581.2,156.7C566.8,131.7,541,116.8,512.1,116.8L512.1,116.8z"/>
<g opacity="0.16">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,115.8c-29.2,0-55.4,15.1-70,40.4L70.8,799.4c-14.6,25.3-14.6,55.5,0,80.8
c13.9,24,37.1,37.8,63.8,37.8c2.6,0,5.2-0.1,7.8-0.4h742.5c2.3,0.3,4.7,0.4,7.1,0.4c12.2,0,24.4-3.5,35.2-10.2
c10.6-6.6,19.7-16.1,26.3-27.6c14.6-25.3,14.6-55.5,0-80.8L582.1,156.2C567.5,130.9,541.3,115.8,512.1,115.8L512.1,115.8z"/>
</g>
<g opacity="0.16">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,114.8c-29.6,0-56.1,15.3-70.9,40.9L69.9,798.9c-14.8,25.6-14.8,56.2,0,81.8
c6.7,11.7,16.1,21.3,27,27.9c11.4,6.9,24.1,10.4,37.7,10.4c2.6,0,5.2-0.1,7.8-0.4h742.4c2.4,0.3,4.7,0.4,7.1,0.4
c12.4,0,24.7-3.6,35.7-10.4c10.8-6.7,20-16.3,26.7-27.9c14.8-25.6,14.8-56.2,0-81.8L583,155.7
C568.2,130.1,541.7,114.8,512.1,114.8L512.1,114.8z"/>
</g>
<g opacity="0.14">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,113.8c-29.9,0-56.8,15.5-71.7,41.4L69,798.4c-15,25.9-15,56.9,0,82.8
c6.8,11.8,16.3,21.6,27.3,28.3c11.5,7,24.4,10.5,38.2,10.5c2.6,0,5.2-0.1,7.9-0.4h742.3c2.4,0.3,4.8,0.4,7.2,0.4
c12.6,0,25.1-3.6,36.2-10.5c10.9-6.8,20.2-16.5,27-28.3c15-25.9,15-56.9,0-82.8L583.8,155.2C568.9,129.3,542,113.8,512.1,113.8
L512.1,113.8z"/>
</g>
<g opacity="0.14">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,112.8c-30.3,0-57.5,15.7-72.6,41.9L68.2,797.9c-15.2,26.2-15.2,57.6,0,83.8
c6.9,12,16.5,21.9,27.7,28.6c11.7,7.1,24.7,10.6,38.7,10.6c2.6,0,5.3-0.1,7.9-0.4h742.2c2.4,0.3,4.8,0.4,7.2,0.4
c12.8,0,25.5-3.7,36.7-10.7c11-6.9,20.5-16.7,27.4-28.6c15.2-26.2,15.2-57.6,0-83.8L584.7,154.7
C569.5,128.5,542.4,112.8,512.1,112.8L512.1,112.8z"/>
</g>
<g opacity="0.12">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,111.8c-30.7,0-58.1,15.9-73.5,42.4L67.3,797.4C52,824,52,855.7,67.3,882.2
c7,12.1,16.7,22.1,28,29c11.9,7.2,25.1,10.8,39.2,10.8c2.6,0,5.3-0.1,8-0.4h742.1c2.4,0.3,4.9,0.4,7.3,0.4
c13,0,25.9-3.7,37.3-10.8c11.2-6.9,20.8-17,27.7-29c15.3-26.6,15.3-58.3,0-84.8L585.6,154.2C570.2,127.7,542.8,111.8,512.1,111.8
L512.1,111.8z"/>
</g>
<g opacity="0.12">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,110.8c-31,0-58.8,16-74.3,42.9L66.4,796.9c-15.5,26.9-15.5,59,0,85.8
c7.1,12.3,16.9,22.4,28.4,29.3c11.9,7.2,25.6,10.9,39.8,10.9c2.6,0,5.3-0.1,8-0.4h742c2.4,0.3,4.9,0.4,7.3,0.4
c13.2,0,26.2-3.8,37.8-11c11.3-7,21-17.2,28-29.3c15.5-26.9,15.5-59,0-85.8L586.4,153.7C570.9,126.9,543.1,110.8,512.1,110.8
L512.1,110.8z"/>
</g>
<g opacity="0.1">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,109.8c-31.4,0-59.5,16.2-75.2,43.4L65.6,796.4c-15.7,27.2-15.7,59.7,0,86.8
c7.2,12.4,17.1,22.7,28.7,29.7c12,7.2,25.9,11.1,40.3,11.1c2.7,0,5.4-0.1,8.1-0.4h741.9c2.4,0.3,4.9,0.4,7.4,0.4
c13.3,0,26.6-3.8,38.3-11.1c11.5-7.1,21.3-17.4,28.4-29.7c15.7-27.2,15.7-59.7,0-86.8L587.3,153.2
C571.6,126,543.5,109.8,512.1,109.8L512.1,109.8z"/>
</g>
<g opacity="0.1">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,108.8c-31.8,0-60.2,16.4-76.1,43.9L64.7,795.9c-15.9,27.5-15.9,60.3,0,87.8
c7.2,12.6,17.3,23,29.1,30.1c12.2,7.3,26.3,11.2,40.8,11.2c2.7,0,5.4-0.1,8.1-0.4h741.8c2.5,0.3,5,0.4,7.4,0.4
c13.5,0,27-3.9,38.9-11.3c11.6-7.2,21.5-17.6,28.7-30c15.9-27.5,15.9-60.3,0-87.8L588.2,152.7
C572.3,125.2,543.8,108.8,512.1,108.8L512.1,108.8z"/>
</g>
<g opacity="8.000000e-02">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,107.8c-32.1,0-60.9,16.6-76.9,44.4L63.8,795.4c-16.1,27.8-16.1,61,0,88.8
c7.3,12.7,17.5,23.2,29.4,30.4c12.3,7.4,26.6,11.4,41.3,11.4c2.7,0,5.4-0.1,8.1-0.4h741.7c2.5,0.3,5,0.4,7.5,0.4
c13.7,0,27.3-4,39.4-11.4c11.7-7.3,21.8-17.8,29-30.4c16.1-27.8,16.1-61,0-88.8L589,152.2C573,124.4,544.2,107.8,512.1,107.8
L512.1,107.8z"/>
</g>
<g opacity="8.000000e-02">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,106.8c-32.5,0-61.6,16.8-77.8,44.9L63,794.9c-16.2,28.1-16.2,61.7,0,89.8
c7.4,12.8,17.7,23.5,29.8,30.8c12.5,7.5,26.9,11.5,41.8,11.5c2.7,0,5.5-0.1,8.2-0.4h741.6c2.5,0.3,5,0.4,7.5,0.4
c13.9,0,27.7-4,39.9-11.6c11.9-7.4,22-18,29.4-30.7c16.2-28.1,16.2-61.7,0-89.8L589.9,151.7C573.7,123.6,544.6,106.8,512.1,106.8
L512.1,106.8z"/>
</g>
<g opacity="6.000000e-02">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,105.8c-32.8,0-62.2,17-78.7,45.4L62.1,794.4c-16.4,28.4-16.4,62.4,0,90.8
c7.5,13,17.9,23.8,30.1,31.1c12.6,7.6,27.3,11.7,42.3,11.7c2.7,0,5.5-0.1,8.2-0.4h741.5c2.5,0.3,5.1,0.4,7.6,0.4
c14.1,0,28.1-4.1,40.4-11.7c12-7.5,22.3-18.2,29.7-31.1c16.4-28.4,16.4-62.4,0-90.8L590.8,151.2
C574.3,122.8,544.9,105.8,512.1,105.8L512.1,105.8z"/>
</g>
<g opacity="6.000000e-02">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,104.8c-33.2,0-62.9,17.2-79.5,45.9L61.2,793.9c-16.6,28.8-16.6,63.1,0,91.8
c7.6,13.1,18.1,24,30.5,31.5c12.8,7.7,27.6,11.8,42.9,11.8c2.7,0,5.5-0.1,8.3-0.4h741.4c2.5,0.3,5.1,0.4,7.6,0.4
c14.3,0,28.4-4.1,41-11.9c12.2-7.5,22.6-18.4,30.1-31.4c16.6-28.8,16.6-63.1,0-91.8L591.6,150.7C575,122,545.3,104.8,512.1,104.8
L512.1,104.8z"/>
</g>
<g opacity="4.000000e-02">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,103.8c-33.6,0-63.6,17.4-80.4,46.4L60.4,793.4c-16.8,29.1-16.8,63.8,0,92.8
c7.7,13.3,18.3,24.3,30.8,31.8c12.9,7.8,27.9,11.9,43.4,11.9c2.7,0,5.5-0.1,8.3-0.4h741.3c2.6,0.3,5.1,0.4,7.7,0.4
c14.5,0,28.8-4.2,41.5-12c12.3-7.6,22.8-18.6,30.4-31.8c16.8-29.1,16.8-63.8,0-92.8L592.5,150.2
C575.7,121.2,545.7,103.8,512.1,103.8L512.1,103.8z"/>
</g>
<g opacity="4.000000e-02">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,102.8c-33.9,0-64.3,17.5-81.3,46.9L59.5,792.9c-17,29.4-17,64.5,0,93.8
c7.8,13.4,18.5,24.6,31.2,32.2c13.1,7.9,28.3,12.1,43.9,12.1c2.8,0,5.6-0.1,8.4-0.4h741.2c2.6,0.3,5.2,0.4,7.7,0.4
c14.6,0,29.2-4.2,42-12.2c12.4-7.7,23.1-18.8,30.7-32.1c17-29.4,17-64.5,0-93.8L593.4,149.7C576.4,120.4,546,102.8,512.1,102.8
L512.1,102.8z"/>
</g>
<g opacity="2.000000e-02">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,101.8c-34.3,0-65,17.7-82.1,47.4L58.6,792.4c-17.1,29.7-17.1,65.1,0,94.8
c7.8,13.6,18.7,24.8,31.5,32.6c13.3,8,28.6,12.2,44.4,12.2c2.8,0,5.6-0.1,8.4-0.4H884c2.6,0.3,5.2,0.4,7.8,0.4
c14.8,0,29.5-4.3,42.5-12.3c12.6-7.8,23.3-19,31.1-32.5c17.1-29.7,17.1-65.1,0-94.8L594.2,149.2
C577.1,119.5,546.4,101.8,512.1,101.8L512.1,101.8z"/>
</g>
<g opacity="2.000000e-02">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,100.8c-34.6,0-65.7,17.9-83,47.9L57.8,791.9c-17.3,30-17.3,65.8,0,95.8
c7.9,13.7,18.9,25.1,31.9,32.9c13.4,8.1,28.9,12.4,44.9,12.4c2.8,0,5.6-0.1,8.5-0.4h741c2.6,0.3,5.2,0.4,7.8,0.4
c15,0,29.9-4.3,43.1-12.5c12.7-7.9,23.6-19.2,31.4-32.8c17.3-30,17.3-65.8,0-95.8L595.1,148.7
C577.8,118.7,546.7,100.8,512.1,100.8L512.1,100.8z"/>
</g>
<g opacity="1.000000e-02">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,99.8c-35,0-66.4,18.1-83.9,48.4L56.9,791.4c-17.5,30.3-17.5,66.5,0,96.8
c8,13.9,19.2,25.4,32.2,33.3c13.6,8.2,29.3,12.5,45.4,12.5c2.8,0,5.7-0.1,8.5-0.4h741c2.6,0.3,5.3,0.4,7.9,0.4
c15.2,0,30.3-4.4,43.6-12.6c12.9-8,23.8-19.4,31.8-33.2c17.5-30.3,17.5-66.5,0-96.8L596,148.2C578.4,117.9,547.1,99.8,512.1,99.8
L512.1,99.8z"/>
</g>
<g opacity="1.000000e-02">
<path d="M512.1,116.8c28.9,0,54.7,14.9,69.1,39.9l371.3,643.1c14.4,25,14.4,54.8,0,79.8c-13.2,22.9-35.9,37.3-60.6,37.3
c-2.3,0-4.7-0.1-7-0.4H142.3c-2.6,0.3-5.2,0.4-7.7,0.4c-27,0-49.7-14.4-62.9-37.3c-14.4-25-14.4-54.8,0-79.8L443,156.7
C457.4,131.7,483.2,116.8,512.1,116.8 M512.1,98.8c-35.4,0-67,18.3-84.7,48.9L56,790.9c-17.7,30.6-17.7,67.2,0,97.8
c8.1,14,19.4,25.6,32.6,33.6c13.7,8.3,29.6,12.7,46,12.7c2.8,0,5.7-0.1,8.6-0.4H884c2.6,0.3,5.3,0.4,7.9,0.4
c15.4,0,30.6-4.4,44.1-12.8c13-8.1,24.1-19.7,32.1-33.5c17.7-30.6,17.7-67.2,0-97.8L596.8,147.7
C579.1,117.1,547.5,98.8,512.1,98.8L512.1,98.8z"/>
</g>
</g>
<g id="Triangle">
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="512.1" y1="886.825" x2="512.1" y2="144.3" gradientTransform="matrix(1 0 0 -1 0 1024)">
<stop offset="0" style="stop-color:#FAE462"/>
<stop offset="0.9989" style="stop-color:#DBAC3A"/>
</linearGradient>
<path fill="url(#SVGID_1_)" d="M469.1,162L97.8,805.2c-19.1,33.1,4.8,74.5,43,74.5h742.6c38.2,0,62.1-41.4,43-74.5L555.1,162
C536,128.9,488.2,128.9,469.1,162z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="512" y1="917" x2="512" y2="114.2" gradientTransform="matrix(1 0 0 -1 0 1024)">
<stop offset="0" style="stop-color:#FCFBF5"/>
<stop offset="1" style="stop-color:#E3E1DB"/>
</linearGradient>
<path fill="url(#SVGID_2_)" d="M512.1,137.2c16.7,0,33.4,8.3,43,24.8l371.3,643.1c19.1,33.1-4.8,74.5-43,74.5H140.8
c-38.2,0-62.1-41.4-43-74.5L469.1,162C478.7,145.5,495.4,137.2,512.1,137.2 M512.1,107c-28.9,0-54.7,14.9-69.1,39.9L71.6,790.1
c-14.4,25-14.4,54.8,0,79.8s40.3,39.9,69.1,39.9h742.6c28.9,0,54.7-14.9,69.1-39.9c14.4-25,14.4-54.8,0-79.8L581.2,146.9
C566.8,121.9,541,107,512.1,107L512.1,107z"/>
</g>
<g id="Point_Shadow" opacity="0.22">
<path fill="#323232" d="M511.8,714.5c8.6,0,16.4,2,23.1,6.1c6.8,4.1,12.1,9.4,16,16s5.9,14.3,5.9,22.9c0,8.7-2,16.3-6.1,22.9
c-4.1,6.6-9.4,12-16,16c-6.6,4.1-14.3,6.1-22.9,6.1c-10.5,0-20.6-4-30.2-12s-14.4-19-14.4-33.1c0-12.4,4.3-23,13-31.8
C488.8,718.9,499.3,714.5,511.8,714.5z"/>
<g opacity="0.14">
<path d="M511.8,714.5c8.6,0,16.4,2,23.1,6.1c6.8,4.1,12.1,9.4,16,16s5.9,14.3,5.9,22.9c0,8.7-2,16.3-6.1,22.9
c-4.1,6.6-9.4,12-16,16c-6.6,4.1-14.3,6.1-22.9,6.1c-10.5,0-20.6-4-30.2-12s-14.4-19-14.4-33.1c0-12.4,4.3-23,13-31.8
C488.8,718.9,499.3,714.5,511.8,714.5 M511.8,713.5c-12.7,0-23.5,4.5-32.3,13.5c-8.8,8.9-13.3,19.9-13.3,32.5
c0,14.3,5,25.7,14.8,33.8c9.7,8.1,20.1,12.2,30.9,12.2c8.8,0,16.7-2.1,23.4-6.2c6.7-4.1,12.2-9.6,16.4-16.3
c4.1-6.8,6.2-14.6,6.2-23.4c0-8.8-2-16.7-6-23.4s-9.5-12.2-16.4-16.4C528.5,715.6,520.5,713.5,511.8,713.5L511.8,713.5z"/>
</g>
<g opacity="0.14">
<path d="M511.8,714.5c8.6,0,16.4,2,23.1,6.1c6.8,4.1,12.1,9.4,16,16s5.9,14.3,5.9,22.9c0,8.7-2,16.3-6.1,22.9
c-4.1,6.6-9.4,12-16,16c-6.6,4.1-14.3,6.1-22.9,6.1c-10.5,0-20.6-4-30.2-12s-14.4-19-14.4-33.1c0-12.4,4.3-23,13-31.8
C488.8,718.9,499.3,714.5,511.8,714.5 M511.8,712.5c-12.9,0-24.1,4.6-33.1,13.8c-9,9.1-13.6,20.3-13.6,33.2
c0,14.6,5.1,26.3,15.1,34.6c9.9,8.2,20.5,12.4,31.5,12.4c9,0,17-2.1,24-6.4c6.9-4.2,12.5-9.8,16.7-16.7s6.4-15,6.4-24
s-2.1-17-6.2-23.9c-4.1-6.9-9.7-12.5-16.7-16.7C528.9,714.6,520.7,712.5,511.8,712.5L511.8,712.5z"/>
</g>
<g opacity="0.12">
<path d="M511.8,714.5c8.6,0,16.4,2,23.1,6.1c6.8,4.1,12.1,9.4,16,16s5.9,14.3,5.9,22.9c0,8.7-2,16.3-6.1,22.9
c-4.1,6.6-9.4,12-16,16c-6.6,4.1-14.3,6.1-22.9,6.1c-10.5,0-20.6-4-30.2-12s-14.4-19-14.4-33.1c0-12.4,4.3-23,13-31.8
C488.8,718.9,499.3,714.5,511.8,714.5 M511.8,711.5c-13.2,0-24.6,4.7-33.8,14.1c-9.2,9.3-13.8,20.7-13.8,33.9
c0,14.9,5.2,26.8,15.5,35.4c10.1,8.4,20.9,12.7,32.1,12.7c9.2,0,17.4-2.2,24.5-6.5c7-4.3,12.7-10,17-17c4.3-7.1,6.5-15.3,6.5-24.5
c0-9.1-2.1-17.4-6.3-24.4s-9.9-12.8-17.1-17.1C529.2,713.7,520.9,711.5,511.8,711.5L511.8,711.5z"/>
</g>
<g opacity="0.12">
<path d="M511.8,714.5c8.6,0,16.4,2,23.1,6.1c6.8,4.1,12.1,9.4,16,16s5.9,14.3,5.9,22.9c0,8.7-2,16.3-6.1,22.9
c-4.1,6.6-9.4,12-16,16c-6.6,4.1-14.3,6.1-22.9,6.1c-10.5,0-20.6-4-30.2-12s-14.4-19-14.4-33.1c0-12.4,4.3-23,13-31.8
C488.8,718.9,499.3,714.5,511.8,714.5 M511.8,710.5c-13.5,0-25.1,4.8-34.5,14.4c-9.4,9.5-14.1,21.2-14.1,34.6
c0,15.2,5.3,27.4,15.8,36.1c10.3,8.6,21.3,12.9,32.8,12.9c9.4,0,17.8-2.2,25-6.7c7.1-4.4,13-10.2,17.3-17.3
c4.4-7.2,6.7-15.6,6.7-25c0-9.3-2.2-17.7-6.4-24.9c-4.3-7.2-10.1-13-17.4-17.4C529.6,712.7,521.1,710.5,511.8,710.5L511.8,710.5z"
/>
</g>
<g opacity="0.1">
<path d="M511.8,714.5c8.6,0,16.4,2,23.1,6.1c6.8,4.1,12.1,9.4,16,16s5.9,14.3,5.9,22.9c0,8.7-2,16.3-6.1,22.9
c-4.1,6.6-9.4,12-16,16c-6.6,4.1-14.3,6.1-22.9,6.1c-10.5,0-20.6-4-30.2-12s-14.4-19-14.4-33.1c0-12.4,4.3-23,13-31.8
C488.8,718.9,499.3,714.5,511.8,714.5 M511.8,709.5c-13.8,0-25.6,4.9-35.2,14.7c-9.6,9.7-14.4,21.6-14.4,35.3
c0,15.6,5.5,28,16.2,36.9c10.5,8.7,21.7,13.1,33.4,13.1c9.5,0,18.1-2.3,25.5-6.8c7.3-4.5,13.2-10.4,17.7-17.7
c4.5-7.4,6.8-16,6.8-25.5s-2.2-18.1-6.6-25.5c-4.3-7.3-10.3-13.3-17.8-17.8C529.9,711.8,521.3,709.5,511.8,709.5L511.8,709.5z"/>
</g>
<g opacity="0.1">
<path d="M511.8,714.5c8.6,0,16.4,2,23.1,6.1c6.8,4.1,12.1,9.4,16,16s5.9,14.3,5.9,22.9c0,8.7-2,16.3-6.1,22.9
c-4.1,6.6-9.4,12-16,16c-6.6,4.1-14.3,6.1-22.9,6.1c-10.5,0-20.6-4-30.2-12s-14.4-19-14.4-33.1c0-12.4,4.3-23,13-31.8
C488.8,718.9,499.3,714.5,511.8,714.5 M511.8,708.5c-14,0-26.1,5-35.9,15c-9.8,9.9-14.7,22-14.7,36c0,15.9,5.6,28.5,16.6,37.7
c10.7,8.9,22.1,13.3,34,13.3c9.7,0,18.5-2.3,26-7c7.4-4.5,13.5-10.6,18-18c4.6-7.5,7-16.3,7-26s-2.3-18.4-6.7-26
c-4.4-7.5-10.5-13.6-18.1-18.1C530.3,710.8,521.5,708.5,511.8,708.5L511.8,708.5z"/>
</g>
<g opacity="8.000000e-02">
<path d="M511.8,714.5c8.6,0,16.4,2,23.1,6.1c6.8,4.1,12.1,9.4,16,16s5.9,14.3,5.9,22.9c0,8.7-2,16.3-6.1,22.9
c-4.1,6.6-9.4,12-16,16c-6.6,4.1-14.3,6.1-22.9,6.1c-10.5,0-20.6-4-30.2-12s-14.4-19-14.4-33.1c0-12.4,4.3-23,13-31.8
C488.8,718.9,499.3,714.5,511.8,714.5 M511.8,707.5c-14.3,0-26.6,5.1-36.6,15.3c-9.9,10.1-15,22.5-15,36.7
c0,16.2,5.7,29.1,16.9,38.4c10.8,9,22.5,13.6,34.7,13.6c9.9,0,18.9-2.4,26.6-7.1c7.6-4.6,13.7-10.8,18.3-18.3
c4.7-7.7,7.1-16.6,7.1-26.6c0-9.9-2.3-18.8-6.9-26.5c-4.5-7.6-10.7-13.8-18.4-18.5C530.6,709.9,521.6,707.5,511.8,707.5
L511.8,707.5z"/>
</g>
<g opacity="8.000000e-02">
<path d="M511.8,714.5c8.6,0,16.4,2,23.1,6.1c6.8,4.1,12.1,9.4,16,16s5.9,14.3,5.9,22.9c0,8.7-2,16.3-6.1,22.9
c-4.1,6.6-9.4,12-16,16c-6.6,4.1-14.3,6.1-22.9,6.1c-10.5,0-20.6-4-30.2-12s-14.4-19-14.4-33.1c0-12.4,4.3-23,13-31.8
C488.8,718.9,499.3,714.5,511.8,714.5 M511.8,706.5c-14.6,0-27.2,5.2-37.3,15.6c-10.1,10.3-15.3,22.9-15.3,37.4
c0,16.5,5.8,29.7,17.3,39.2c11,9.2,22.9,13.8,35.3,13.8c10.1,0,19.2-2.4,27.1-7.3c7.7-4.7,14-11,18.7-18.7
c4.8-7.9,7.3-17,7.3-27.1s-2.4-19.1-7-27c-4.6-7.8-10.9-14.1-18.8-18.8C531,708.9,521.8,706.5,511.8,706.5L511.8,706.5z"/>
</g>
<g opacity="6.000000e-02">
<path d="M511.8,714.5c8.6,0,16.4,2,23.1,6.1c6.8,4.1,12.1,9.4,16,16s5.9,14.3,5.9,22.9c0,8.7-2,16.3-6.1,22.9
c-4.1,6.6-9.4,12-16,16c-6.6,4.1-14.3,6.1-22.9,6.1c-10.5,0-20.6-4-30.2-12s-14.4-19-14.4-33.1c0-12.4,4.3-23,13-31.8
C488.8,718.9,499.3,714.5,511.8,714.5 M511.8,705.5c-14.9,0-27.7,5.3-38,15.9c-10.3,10.5-15.6,23.3-15.6,38.1
c0,16.8,5.9,30.3,17.6,40c11.2,9.3,23.3,14,36,14c10.3,0,19.6-2.5,27.6-7.4c7.8-4.8,14.2-11.2,19-19c4.9-8,7.4-17.3,7.4-27.6
c0-10.2-2.4-19.5-7.1-27.5c-4.7-7.9-11.1-14.3-19.1-19.2C531.3,708,522,705.5,511.8,705.5L511.8,705.5z"/>
</g>
<g opacity="6.000000e-02">
<path d="M511.8,714.5c8.6,0,16.4,2,23.1,6.1c6.8,4.1,12.1,9.4,16,16s5.9,14.3,5.9,22.9c0,8.7-2,16.3-6.1,22.9
c-4.1,6.6-9.4,12-16,16c-6.6,4.1-14.3,6.1-22.9,6.1c-10.5,0-20.6-4-30.2-12s-14.4-19-14.4-33.1c0-12.4,4.3-23,13-31.8
C488.8,718.9,499.3,714.5,511.8,714.5 M511.8,703.5c-15.4,0-28.7,5.5-39.5,16.5c-10.7,10.9-16.1,24.2-16.1,39.6
c0,17.4,6.2,31.4,18.4,41.5c11.6,9.6,24.1,14.5,37.2,14.5c10.7,0,20.3-2.6,28.7-7.7c8.1-5,14.7-11.6,19.7-19.7
c5.1-8.3,7.7-18,7.7-28.7c0-10.6-2.5-20.2-7.4-28.5c-4.9-8.2-11.5-14.9-19.8-19.9C532.1,706.1,522.4,703.5,511.8,703.5
L511.8,703.5z"/>
</g>
<g opacity="4.000000e-02">
<path d="M511.8,714.5c8.6,0,16.4,2,23.1,6.1c6.8,4.1,12.1,9.4,16,16s5.9,14.3,5.9,22.9c0,8.7-2,16.3-6.1,22.9
c-4.1,6.6-9.4,12-16,16c-6.6,4.1-14.3,6.1-22.9,6.1c-10.5,0-20.6-4-30.2-12s-14.4-19-14.4-33.1c0-12.4,4.3-23,13-31.8
C488.8,718.9,499.3,714.5,511.8,714.5 M511.8,702.5c-15.7,0-29.2,5.6-40.2,16.8c-10.9,11.1-16.4,24.6-16.4,40.3
c0,17.7,6.3,32,18.7,42.3c11.8,9.8,24.5,14.7,37.9,14.7c10.8,0,20.7-2.6,29.2-7.8c8.2-5,15-11.8,20-20c5.2-8.5,7.8-18.3,7.8-29.2
c0-10.8-2.5-20.5-7.5-29c-4.9-8.3-11.7-15.1-20.2-20.2C532.4,705.1,522.6,702.5,511.8,702.5L511.8,702.5z"/>
</g>
<g opacity="4.000000e-02">
<path d="M511.8,714.5c8.6,0,16.4,2,23.1,6.1c6.8,4.1,12.1,9.4,16,16s5.9,14.3,5.9,22.9c0,8.7-2,16.3-6.1,22.9
c-4.1,6.6-9.4,12-16,16c-6.6,4.1-14.3,6.1-22.9,6.1c-10.5,0-20.6-4-30.2-12s-14.4-19-14.4-33.1c0-12.4,4.3-23,13-31.8
C488.8,718.9,499.3,714.5,511.8,714.5 M511.8,701.5c-16,0-29.7,5.7-40.9,17.1c-11.1,11.3-16.7,25-16.7,41c0,18,6.4,32.5,19.1,43.1
c11.9,9.9,24.9,15,38.5,15c11,0,21-2.7,29.7-8c8.4-5.1,15.2-12,20.3-20.3c5.3-8.7,8-18.7,8-29.7s-2.6-20.9-7.7-29.5
c-5-8.5-11.9-15.4-20.5-20.6C532.8,704.2,522.7,701.5,511.8,701.5L511.8,701.5z"/>
</g>
<g opacity="2.000000e-02">
<path d="M511.8,714.5c8.6,0,16.4,2,23.1,6.1c6.8,4.1,12.1,9.4,16,16s5.9,14.3,5.9,22.9c0,8.7-2,16.3-6.1,22.9
c-4.1,6.6-9.4,12-16,16c-6.6,4.1-14.3,6.1-22.9,6.1c-10.5,0-20.6-4-30.2-12s-14.4-19-14.4-33.1c0-12.4,4.3-23,13-31.8
C488.8,718.9,499.3,714.5,511.8,714.5 M511.8,700.5c-16.3,0-30.3,5.8-41.6,17.4c-11.3,11.5-17,25.5-17,41.7
c0,18.4,6.5,33.1,19.5,43.8c12.1,10.1,25.3,15.2,39.2,15.2c11.2,0,21.4-2.7,30.2-8.1c8.5-5.2,15.5-12.2,20.7-20.7
c5.4-8.8,8.1-19,8.1-30.2c0-11.1-2.6-21.2-7.8-30c-5.1-8.6-12.1-15.7-20.9-20.9C533.1,703.2,522.9,700.5,511.8,700.5L511.8,700.5z
"/>
</g>
<g opacity="2.000000e-02">
<path d="M511.8,714.5c8.6,0,16.4,2,23.1,6.1c6.8,4.1,12.1,9.4,16,16s5.9,14.3,5.9,22.9c0,8.7-2,16.3-6.1,22.9
c-4.1,6.6-9.4,12-16,16c-6.6,4.1-14.3,6.1-22.9,6.1c-10.5,0-20.6-4-30.2-12s-14.4-19-14.4-33.1c0-12.4,4.3-23,13-31.8
C488.8,718.9,499.3,714.5,511.8,714.5 M511.8,699.5c-16.5,0-30.8,5.9-42.3,17.7c-11.5,11.6-17.3,25.9-17.3,42.4
c0,18.4,6.9,33.8,19.8,44.6c12.3,10.2,25.7,15.4,39.8,15.4c11.4,0,21.8-2.8,30.7-8.3c8.6-5.3,15.7-12.4,21-21
c5.5-9,8.3-19.3,8.3-30.7c0-11.3-2.7-21.6-8-30.5c-5.2-8.8-12.3-15.9-21.2-21.2C533.5,702.3,523.1,699.5,511.8,699.5L511.8,699.5z
"/>
</g>
</g>
<radialGradient id="Point_1_" cx="512" cy="272.1" r="44.9001" gradientTransform="matrix(1 0 0 -1 0 1024)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#F8F8F8"/>
<stop offset="1" style="stop-color:#E9E9E8"/>
</radialGradient>
<path id="Point" fill="url(#Point_1_)" d="M511.8,706.9c8.6,0,16.4,2,23.1,6.1c6.8,4.1,12.1,9.4,16,16s5.9,14.3,5.9,22.9
c0,8.7-2,16.3-6.1,22.9c-4.1,6.6-9.4,12-16,16c-6.6,4.1-14.3,6.1-22.9,6.1c-10.5,0-20.6-4-30.2-12s-14.4-19-14.4-33.1
c0-12.4,4.3-23,13-31.8C488.8,711.3,499.3,706.9,511.8,706.9z"/>
<g id="Exclamation_Shadow" opacity="0.22">
<path fill="#323232" d="M520.3,667.4h-15.8l-22.7-171.5c-9.2-66.5-13.8-105.2-13.8-116c0-15.9,4.1-28.9,12.4-38.9
c8.2-10,19.4-15,33.5-15c13.5,0,23.8,5.2,30.8,15.6s10.5,22.9,10.5,37.5c0,8.1-3.5,39.9-10.5,95.3L520.3,667.4z"/>
<g opacity="0.14">
<path d="M513.8,326c13.5,0,23.8,5.2,30.8,15.6s10.5,22.9,10.5,37.5c0,8.1-3.5,39.9-10.5,95.3l-24.3,193h-15.8l-22.7-171.5
c-9.2-66.5-13.8-105.2-13.8-116c0-15.9,4.1-28.9,12.4-38.9C488.6,331,499.7,326,513.8,326 M513.8,325c-14.3,0-25.8,5.2-34.2,15.4
c-8.4,10.1-12.6,23.5-12.6,39.6c0,10.8,4.6,49.9,13.8,116.1l22.7,171.5l0.1,0.9h0.9h15.8h0.9l0.1-0.9l24.3-193
c7.1-56,10.6-87.2,10.6-95.4c0-14.7-3.6-27.5-10.7-38.1C538.2,330.4,527.6,325,513.8,325L513.8,325z"/>
</g>
<g opacity="0.14">
<path d="M513.8,326c13.5,0,23.8,5.2,30.8,15.6s10.5,22.9,10.5,37.5c0,8.1-3.5,39.9-10.5,95.3l-24.3,193h-15.8l-22.7-171.5
c-9.2-66.5-13.8-105.2-13.8-116c0-15.9,4.1-28.9,12.4-38.9C488.6,331,499.7,326,513.8,326 M513.8,324c-14.6,0-26.4,5.3-35,15.7
c-8.5,10.3-12.8,23.8-12.8,40.2c0,10.9,4.6,50,13.8,116.3l22.7,171.5l0.2,1.7h1.8h15.8h1.8l0.2-1.7l24.3-193
c7.1-56,10.6-87.3,10.6-95.6c0-14.9-3.7-27.9-10.9-38.6C538.9,329.5,527.9,324,513.8,324L513.8,324z"/>
</g>
<g opacity="0.12">
<path d="M513.8,326c13.5,0,23.8,5.2,30.8,15.6s10.5,22.9,10.5,37.5c0,8.1-3.5,39.9-10.5,95.3l-24.3,193h-15.8l-22.7-171.5
c-9.2-66.5-13.8-105.2-13.8-116c0-15.9,4.1-28.9,12.4-38.9C488.6,331,499.7,326,513.8,326 M513.8,323c-14.9,0-27,5.4-35.8,16.1
c-8.7,10.5-13.1,24.2-13.1,40.8c0,11.1,4.5,49.1,13.8,116.4l22.7,171.5l0.3,2.6h2.6h15.8h2.6l0.3-2.6l24.3-193
c7.1-56.1,10.6-87.4,10.6-95.7c0-15.1-3.7-28.3-11.1-39.2C539.5,328.7,528.3,323,513.8,323L513.8,323z"/>
</g>
<g opacity="0.12">
<path d="M513.8,326c13.5,0,23.8,5.2,30.8,15.6s10.5,22.9,10.5,37.5c0,8.1-3.5,39.9-10.5,95.3l-24.3,193h-15.8l-22.7-171.5
c-9.2-66.5-13.8-105.2-13.8-116c0-15.9,4.1-28.9,12.4-38.9C488.6,331,499.7,326,513.8,326 M513.8,322c-15.2,0-27.5,5.5-36.5,16.5
c-8.8,10.7-13.3,24.6-13.3,41.5c0,11.1,4.5,49.2,13.8,116.5L500.5,668l0.5,3.5h3.5h15.8h3.5l0.4-3.5l24.3-193
c7.1-56.1,10.6-87.5,10.6-95.8c0-15.3-3.8-28.7-11.2-39.8C540.1,327.8,528.6,322,513.8,322L513.8,322z"/>
</g>
<g opacity="0.1">
<path d="M513.8,326c13.5,0,23.8,5.2,30.8,15.6s10.5,22.9,10.5,37.5c0,8.1-3.5,39.9-10.5,95.3l-24.3,193h-15.8l-22.7-171.5
c-9.2-66.5-13.8-105.2-13.8-116c0-15.9,4.1-28.9,12.4-38.9C488.6,331,499.7,326,513.8,326 M513.8,321c-15.6,0-28.1,5.7-37.3,16.8
c-9,10.9-13.5,25-13.5,42.1c0,11.2,4.5,49.3,13.8,116.7l22.7,171.5l0.6,4.3h4.4h15.8h4.4l0.6-4.4l24.3-193
c7.1-56.2,10.6-87.5,10.6-95.9c0-15.5-3.8-29.1-11.4-40.3C540.8,327,529,321,513.8,321L513.8,321z"/>
</g>
<g opacity="0.1">
<path d="M513.8,326c13.5,0,23.8,5.2,30.8,15.6s10.5,22.9,10.5,37.5c0,8.1-3.5,39.9-10.5,95.3l-24.3,193h-15.8l-22.7-171.5
c-9.2-66.5-13.8-105.2-13.8-116c0-15.9,4.1-28.9,12.4-38.9C488.6,331,499.7,326,513.8,326 M513.8,320c-15.9,0-28.7,5.8-38.1,17.2
c-9.1,11.1-13.7,25.4-13.7,42.7c0,11.2,4.5,49.4,13.8,116.8l22.7,171.5l0.7,5.2h5.3h15.8h5.3l0.7-5.2l24.3-193
c7.1-56.2,10.6-87.6,10.6-96.1c0-15.7-3.9-29.5-11.6-40.9C541.4,326.1,529.3,320,513.8,320L513.8,320z"/>
</g>
<g opacity="8.000000e-02">
<path d="M513.8,326c13.5,0,23.8,5.2,30.8,15.6s10.5,22.9,10.5,37.5c0,8.1-3.5,39.9-10.5,95.3l-24.3,193h-15.8l-22.7-171.5
c-9.2-66.5-13.8-105.2-13.8-116c0-15.9,4.1-28.9,12.4-38.9C488.6,331,499.7,326,513.8,326 M513.8,319c-16.2,0-29.3,5.9-38.9,17.6
c-9.3,11.2-14,25.8-14,43.4c0,11.3,4.5,49.5,13.9,116.9l22.7,171.5l0.8,6.1h6.1h15.8h6.2l0.8-6.1l24.3-193
c7.1-56.2,10.6-87.7,10.6-96.2c0-15.9-4-29.9-11.7-41.4C542,325.3,529.7,319,513.8,319L513.8,319z"/>
</g>
<g opacity="8.000000e-02">
<path d="M513.8,326c13.5,0,23.8,5.2,30.8,15.6s10.5,22.9,10.5,37.5c0,8.1-3.5,39.9-10.5,95.3l-24.3,193h-15.8l-22.7-171.5
c-9.2-66.5-13.8-105.2-13.8-116c0-15.9,4.1-28.9,12.4-38.9C488.6,331,499.7,326,513.8,326 M513.8,318c-16.5,0-29.8,6-39.6,17.9
c-9.4,11.4-14.2,26.2-14.2,44c0,11.3,4.5,49.6,13.9,117.1l22.7,171.5l0.9,7h7h15.8h7.1l0.9-7l24.3-193
c7.1-56.3,10.6-87.8,10.6-96.3c0-16.2-4-30.3-11.9-42C542.6,324.4,530,318,513.8,318L513.8,318z"/>
</g>
<g opacity="6.000000e-02">
<path d="M513.8,326c13.5,0,23.8,5.2,30.8,15.6s10.5,22.9,10.5,37.5c0,8.1-3.5,39.9-10.5,95.3l-24.3,193h-15.8l-22.7-171.5
c-9.2-66.5-13.8-105.2-13.8-116c0-15.9,4.1-28.9,12.4-38.9C488.6,331,499.7,326,513.8,326 M513.8,317c-16.8,0-30.4,6.2-40.4,18.3
c-9.6,11.6-14.4,26.6-14.4,44.7c0,11.3,4.5,49.7,13.9,117.2l22.7,171.5l1,7.8h7.9h15.8h7.9l1-7.9l24.3-193
c7.1-56.3,10.6-87.9,10.6-96.4c0-16.4-4.1-30.7-12.1-42.5C543.4,323.7,530.2,317,513.8,317L513.8,317z"/>
</g>
<g opacity="6.000000e-02">
<path d="M513.8,326c13.5,0,23.8,5.2,30.8,15.6s10.5,22.9,10.5,37.5c0,8.1-3.5,39.9-10.5,95.3l-24.3,193h-15.8l-22.7-171.5
c-9.2-66.5-13.8-105.2-13.8-116c0-15.9,4.1-28.9,12.4-38.9C488.6,331,499.7,326,513.8,326 M513.8,315c-17.4,0-31.5,6.4-41.9,19
c-9.9,12-14.9,27.4-14.9,45.9c0,11.4,4.5,49.9,13.9,117.5l22.7,171.5l1.3,9.6h9.6h15.8h9.7l1.2-9.6l24.3-193
c9.5-75.1,10.6-91.2,10.6-96.7c0-16.8-4.2-31.5-12.4-43.7C544.7,322,530.9,315,513.8,315L513.8,315z"/>
</g>
<g opacity="4.000000e-02">
<path d="M513.8,326c13.5,0,23.8,5.2,30.8,15.6s10.5,22.9,10.5,37.5c0,8.1-3.5,39.9-10.5,95.3l-24.3,193h-15.8l-22.7-171.5
c-9.2-66.5-13.8-105.2-13.8-116c0-15.9,4.1-28.9,12.4-38.9C488.6,331,499.7,326,513.8,326 M513.8,314c-17.7,0-32.1,6.5-42.7,19.4
c-10,12.2-15.1,27.8-15.1,46.6c0,11.5,4.5,50,13.9,117.6L492.6,669l1.4,10.4h10.5h15.8h10.6l1.3-10.5l24.3-193
c9.5-75.2,10.6-91.3,10.6-96.8c0-17-4.2-31.8-12.6-44.2C545.3,321.2,531.2,314,513.8,314L513.8,314z"/>
</g>
<g opacity="4.000000e-02">
<path d="M513.8,326c13.5,0,23.8,5.2,30.8,15.6s10.5,22.9,10.5,37.5c0,8.1-3.5,39.9-10.5,95.3l-24.3,193h-15.8l-22.7-171.5
c-9.2-66.5-13.8-105.2-13.8-116c0-15.9,4.1-28.9,12.4-38.9C488.6,331,499.7,326,513.8,326 M513.8,313c-18.1,0-32.7,6.6-43.5,19.7
C460.1,345,455,360.9,455,379.9c0,11.5,4.5,50,13.9,117.7l22.7,171.5l1.5,11.3h11.4h15.8h11.5l1.4-11.4l24.3-193
c9.5-75.3,10.6-91.4,10.6-96.9c0-17.2-4.3-32.2-12.8-44.8C545.9,320.4,531.6,313,513.8,313L513.8,313z"/>
</g>
<g opacity="2.000000e-02">
<path d="M513.8,326c13.5,0,23.8,5.2,30.8,15.6s10.5,22.9,10.5,37.5c0,8.1-3.5,39.9-10.5,95.3l-24.3,193h-15.8l-22.7-171.5
c-9.2-66.5-13.8-105.2-13.8-116c0-15.9,4.1-28.9,12.4-38.9C488.6,331,499.7,326,513.8,326 M513.8,312c-18.1,0-33.4,7-44.3,20.1
c-10.3,12.5-15.6,28.6-15.6,47.8c0,7.4,1.4,27.6,13.9,117.9l22.7,171.5l1.6,12.2h12.3h15.8h12.3l1.5-12.2l24.3-193
c9.5-75.3,10.7-91.5,10.7-97.1c0-17.4-4.4-32.6-12.9-45.3C546.6,319.5,531.9,312,513.8,312L513.8,312z"/>
</g>
<g opacity="2.000000e-02">
<path d="M513.8,326c13.5,0,23.8,5.2,30.8,15.6s10.5,22.9,10.5,37.5c0,8.1-3.5,39.9-10.5,95.3l-24.3,193h-15.8l-22.7-171.5
c-9.2-66.5-13.8-105.2-13.8-116c0-15.9,4.1-28.9,12.4-38.9C488.6,331,499.7,326,513.8,326 M513.8,311c-18.4,0-34,7.1-45,20.5
c-10.5,12.7-15.8,29-15.8,48.5c0,7.4,1.4,27.7,13.9,118l22.7,171.5l1.7,13h13.1h15.8h13.2l1.7-13.1l24.3-193
c9.6-75.4,10.7-91.6,10.7-97.2c0-17.6-4.4-33-13.1-45.9C547.2,318.7,532.2,311,513.8,311L513.8,311z"/>
</g>
</g>
<radialGradient id="Exclamation_1_" cx="511.6" cy="534.8233" r="124.5798" gradientTransform="matrix(1 0 0 -2.5 0 1826.2583)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#F8F8F8"/>
<stop offset="1" style="stop-color:#E9E9E8"/>
</radialGradient>
<path id="Exclamation" fill="url(#Exclamation_1_)" d="M520.3,659.9h-15.8l-22.7-171.5c-9.2-66.5-13.8-105.2-13.8-116
c0-15.9,4.1-28.9,12.4-38.9c8.2-10,19.4-15,33.5-15c13.5,0,23.8,5.2,30.8,15.6s10.5,22.9,10.5,37.5c0,8.1-3.5,39.9-10.5,95.3
L520.3,659.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,100 @@
import { info, error } from './logger.js'
import { getResource, getExtensionURL } from './common.js'
import { togglePictureInPicture, addPictureInPictureEventListener } from './video.js'
import { localizedString } from './localization.js'
const BUTTON_ID = 'PiPer_button';
let /** ?HTMLElement */ button = null;
/**
* Injects Picture in Picture button into webpage
*
* @param {Element} parent - Element button will be inserted into
*/
export const addButton = function(parent) {
// Create button if needed
if (!button) {
const buttonElementType = getResource().buttonElementType || 'button';
button = /** @type {HTMLElement} */ (document.createElement(buttonElementType));
// Set button properties
button.id = BUTTON_ID;
button.title = localizedString('button-title');
const buttonStyle = getResource().buttonStyle;
if (buttonStyle) button.style.cssText = buttonStyle;
const buttonClassName = getResource().buttonClassName;
if (buttonClassName) button.className = buttonClassName;
// Add scaled image to button
const image = /** @type {HTMLImageElement} */ (document.createElement('img'));
image.style.width = image.style.height = '100%';
const buttonScale = getResource().buttonScale;
if (buttonScale) image.style.transform = `scale(${buttonScale})`;
button.appendChild(image);
// Set image paths
let buttonImage = getResource().buttonImage;
let buttonExitImage = getResource().buttonExitImage;
if (!buttonImage) {
buttonImage = 'default';
buttonExitImage = 'default-exit';
}
const buttonImageURL = getExtensionURL(`images/${buttonImage}.svg`);
image.src = buttonImageURL;
if (buttonExitImage) {
const buttonExitImageURL = getExtensionURL(`images/${buttonExitImage}.svg`);
addPictureInPictureEventListener(function(video, isPlayingPictureInPicture) {
image.src = (isPlayingPictureInPicture) ? buttonExitImageURL : buttonImageURL;
});
}
// Add hover style to button (a nested stylesheet is used to avoid tracking another element)
const buttonHoverStyle = getResource().buttonHoverStyle;
if (buttonHoverStyle) {
const style = document.createElement('style');
const css = `#${BUTTON_ID}:hover{${buttonHoverStyle}}`;
style.appendChild(document.createTextNode(css));
button.appendChild(style);
}
// Toggle Picture in Picture mode when button is clicked
button.addEventListener('click', function(event) {
event.preventDefault();
// Get the video element and bypass caching to accomodate for the underlying video changing (e.g. pre-roll adverts)
const video = /** @type {?HTMLVideoElement} */ (getResource().videoElement(true));
if (!video) {
error('Unable to find video');
return;
}
togglePictureInPicture(video);
});
info('Picture in Picture button created');
}
// Inject button into correct place
const referenceNode = getResource().buttonInsertBefore ? getResource().buttonInsertBefore(parent) : null;
parent.insertBefore(button, referenceNode);
};
/**
* Returns the Picture in Picture button element
*
* @return {?HTMLElement}
*/
export const getButton = function() {
return button;
};
/**
* Checks if Picture in Picture button is injected into page
*
* @return {boolean}
*/
export const checkButton = function() {
return !!document.getElementById(BUTTON_ID);
};

View File

@ -0,0 +1,49 @@
import { getResource } from './common.js'
/**
* Initialises caching for button, video, and caption elements
*/
export const initialiseCaches = function() {
// Return a unique id
let uniqueIdCounter = 0;
const /** function():string */ uniqueId = function() {
return 'PiPer_' + uniqueIdCounter++;
};
/**
* Wraps a function that returns an element to provide faster lookups by id
*
* @param {function(boolean=):?Element} elementFunction
* @return {function(boolean=):?Element}
*/
const cacheElementWrapper = function(elementFunction) {
let /** ?string */ cachedElementId = null;
return /** function():?Element */ function(/** boolean= */ bypassCache) {
// Return element by id if possible
const cachedElement = cachedElementId ?
document.getElementById(cachedElementId) : null;
if (cachedElement && !bypassCache) return cachedElement;
// Call the underlying function to get the element
const uncachedElement = elementFunction();
if (uncachedElement) {
// Save the native id otherwise assign a unique id
if (!uncachedElement.id) uncachedElement.id = uniqueId();
cachedElementId = uncachedElement.id;
}
return uncachedElement;
};
};
// Wrap the button, video, and caption elements
const currentResource = getResource();
currentResource.buttonParent = cacheElementWrapper(currentResource.buttonParent);
currentResource.videoElement = cacheElementWrapper(currentResource.videoElement);
if (currentResource.captionElement) {
currentResource.captionElement = cacheElementWrapper(currentResource.captionElement);
}
};

View File

@ -0,0 +1,185 @@
import { info } from './logger.js'
import { Browser, getBrowser, getResource } from './common.js'
import { videoPlayingPictureInPicture, addPictureInPictureEventListener, removePictureInPictureEventListener } from './video.js'
const TRACK_ID = 'PiPer_track';
let /** ?TextTrack */ track = null;
let /** boolean */ captionsEnabled = false;
let /** boolean */ showingCaptions = false;
let /** boolean */ showingEmptyCaption = false;
let /** string */ lastUnprocessedCaption = '';
/**
* Disable closed caption support in Picture in Picture mode
*/
export const disableCaptions = function() {
captionsEnabled = false;
showingCaptions = false;
processCaptions();
removePictureInPictureEventListener(pictureInPictureEventListener);
info('Closed caption support disabled');
};
/**
* Enable closed caption support in Picture in Picture mode
*
* @param {boolean=} ignoreNowPlayingCheck - assumes video isn't already playing Picture in Picture
*/
export const enableCaptions = function(ignoreNowPlayingCheck) {
if (!getResource().captionElement) return;
captionsEnabled = true;
addPictureInPictureEventListener(pictureInPictureEventListener);
info('Closed caption support enabled');
if (ignoreNowPlayingCheck) return;
const video = /** @type {?HTMLVideoElement} */ (getResource().videoElement(true));
if (!video) return;
showingCaptions = videoPlayingPictureInPicture(video);
prepareCaptions(video);
processCaptions();
};
/**
* Checks whether processing closed captions is required
*
* @return {boolean}
*/
export const shouldProcessCaptions = function() {
return captionsEnabled && showingCaptions;
};
/**
* Prepares video for captions
*
* @param {HTMLVideoElement} video - video element that will display captions
*/
const prepareCaptions = function(video) {
// Find existing caption track
track = null;
const allTracks = video.textTracks;
for (let trackId = allTracks.length; trackId--;) {
if (allTracks[trackId].label === TRACK_ID) {
track = allTracks[trackId];
info('Existing caption track found');
break;
}
}
if (track) return;
// Otherwise create new caption track
info('Caption track created');
track = video.addTextTrack('captions', TRACK_ID, 'en');
track.mode = 'showing';
};
/**
* Toggles captions when video enters or exits Picture in Picture mode
*
* @param {HTMLVideoElement} video - target video element
* @param {boolean} isPlayingPictureInPicture - true if video playing Picture in Picture
*/
const pictureInPictureEventListener = function(video, isPlayingPictureInPicture) {
// Toggle display of the captions and prepare video if needed
showingCaptions = isPlayingPictureInPicture;
if (showingCaptions) prepareCaptions(video);
lastUnprocessedCaption = '';
processCaptions();
info(`Video presentation mode changed (showingCaptions: ${showingCaptions})`);
};
/**
* Removes visible Picture in Picture mode captions
*
* @param {HTMLVideoElement} video - video element showing captions
* @param {boolean=} workaround - apply Safari bug workaround
*/
const removeCaptions = function(video, workaround = true) {
while (track.activeCues.length) {
track.removeCue(track.activeCues[0]);
}
// Workaround Safari bug; 'removeCue' doesn't immediately remove captions shown in Picture in Picture mode
if (getBrowser() == Browser.SAFARI && workaround && video && !showingEmptyCaption) {
track.addCue(new VTTCue(video.currentTime, video.currentTime + 60, ''));
showingEmptyCaption = true;
}
};
/**
* Displays Picture in Picture mode caption
*
* @param {HTMLVideoElement} video - video element showing captions
* @param {string} caption - a caption to display
*/
const addCaption = function(video, caption) {
info(`Showing caption '${caption}'`);
track.addCue(new VTTCue(video.currentTime, video.currentTime + 60, caption));
if (getBrowser() == Browser.SAFARI) {
showingEmptyCaption = false;
}
};
/**
* Updates visible captions
*/
export const processCaptions = function() {
// Get handles to caption and video elements
const captionElement = getResource().captionElement();
const video = /** @type {?HTMLVideoElement} */ (getResource().videoElement());
// Remove Picture in Picture mode captions and show native captions if no longer showing captions or encountered an error
if (!showingCaptions || !captionElement) {
removeCaptions(video);
if (captionElement) captionElement.style.visibility = '';
return;
}
// Otherwise ensure native captions remain hidden
captionElement.style.visibility = 'hidden';
// Check if a new native caption needs to be processed
const unprocessedCaption = captionElement.textContent;
if (unprocessedCaption == lastUnprocessedCaption) return;
lastUnprocessedCaption = unprocessedCaption;
// Remove old captions and apply Safari bug fix if caption has no content as otherwise causes flicker
removeCaptions(video, !unprocessedCaption);
// Performance optimisation - early exit if caption has no content
if (!unprocessedCaption) return;
// Show correctly spaced and formatted Picture in Picture mode caption
let caption = '';
const walk = document.createTreeWalker(captionElement, NodeFilter.SHOW_TEXT, null, false);
while (walk.nextNode()) {
const segment = walk.currentNode.nodeValue.trim();
if (segment) {
const style = window.getComputedStyle(walk.currentNode.parentElement);
if (style.fontStyle == 'italic') {
caption += `<i>${segment}</i>`;
} else if (style.textDecoration == 'underline') {
caption += `<u>${segment}</u>`;
} else {
caption += segment;
}
caption += ' ';
} else if (caption.charAt(caption.length - 1) != '\n') {
caption += '\n';
}
}
caption = caption.trim();
addCaption(video, caption);
};

View File

@ -0,0 +1,104 @@
import { BROWSER } from './defines.js'
import { warn } from './logger.js'
/** @enum {number} - Enum for browser */
export const Browser = {
UNKNOWN: 0,
SAFARI: 1,
CHROME: 2,
};
/**
* Returns current web browser
*
* @return {Browser}
*/
export const getBrowser = function() {
if (BROWSER != Browser.UNKNOWN) {
return /** @type {Browser} */ (BROWSER);
}
if (/Safari/.test(navigator.userAgent) && /Apple/.test(navigator.vendor)) {
return Browser.SAFARI;
}
if (/Chrome/.test(navigator.userAgent) && /Google/.test(navigator.vendor)) {
return Browser.CHROME;
}
return Browser.UNKNOWN;
};
/**
* @typedef {{
* buttonClassName: (string|undefined),
* buttonDidAppear: (function():undefined|undefined),
* buttonElementType: (string|undefined),
* buttonExitImage: (string|undefined),
* buttonHoverStyle: (string|undefined),
* buttonImage: (string|undefined),
* buttonInsertBefore: (function(Element):?Node|undefined),
* buttonParent: function(boolean=):?Element,
* buttonScale: (number|undefined),
* buttonStyle: (string|undefined),
* captionElement: (function(boolean=):?Element|undefined),
* videoElement: function(boolean=):?Element,
* }}
*/
let PiperResource;
let /** ?PiperResource */ currentResource = null;
/**
* Returns the current resource
*
* @return {?PiperResource}
*/
export const getResource = function() {
return currentResource;
};
/**
* Sets the current resource
*
* @param {?PiperResource} resource - a resource to set as current resource
*/
export const setResource = function(resource) {
currentResource = resource;
};
/**
* Converts a relative path within an extension to a fully-qualified URL
*
* @param {string} path - a path to a resource
* @return {string}
*/
export const getExtensionURL = function(path) {
switch (getBrowser()) {
case Browser.SAFARI:
return safari.extension.baseURI + path;
case Browser.CHROME:
return chrome.runtime.getURL(path);
case Browser.UNKNOWN:
default:
return path;
}
};
/**
* Applies fix to bypass background DOM timer throttling
*/
export const bypassBackgroundTimerThrottling = function() {
// Issue warning for unnecessary use of background timer throttling
if (!currentResource.captionElement) {
warn('Unnecessary bypassing of background timer throttling on page without caption support');
}
const request = new XMLHttpRequest();
request.open('GET', getExtensionURL('scripts/fix.js'));
request.onload = function() {
const script = document.createElement('script');
script.setAttribute('type', 'module');
script.appendChild(document.createTextNode(request.responseText));
document.head.appendChild(script);
};
request.send();
};

View File

@ -0,0 +1,5 @@
/** @define {number} - Flag used by closure compiler to set logging level */
export const LOGGING_LEVEL = 0;
/** @define {number} - Flag used by closure compiler to target specific browser */
export const BROWSER = 0;

View File

@ -0,0 +1,109 @@
/**
* @fileoverview Externs file for PiPer used by Closure Compiler
* @externs
*/
/* Safari Extension */
/** @const */
const safari = {};
/** @const */
safari.extension = {};
/** @type {string} */
safari.extension.baseURI;
/** @type {string} */
HTMLVideoElement.prototype.webkitPresentationMode;
/** @return {undefined} */
HTMLVideoElement.prototype.webkitSetPresentationMode = function(mode) {};
/** @type {string} */
TextTrack.prototype.label;
/** @return {undefined} */
safari.extension.dispatchMessage = function(message, userInfo) {};
/** @const */
safari.self = {};
/** @return {undefined} */
safari.self.addEventListener = function(type, listener) {};
/** @constructor */
const SafariExtensionMessageEvent = function() {};
/** @type {*} */
SafariExtensionMessageEvent.prototype.message;
/** @type {string} */
SafariExtensionMessageEvent.prototype.name;
/* Legacy Safari Extension */
/** @const */
safari.extension.settings = {};
/** @return {undefined} */
safari.extension.settings.clear = function() {};
/* Chrome Extension */
/** @const */
const chrome = {};
/** @const */
chrome.runtime = {};
/** @return {string} */
chrome.runtime.getURL = function(path) {};
/** @const */
chrome.runtime.onInstalled = {};
/** @return {undefined} */
chrome.runtime.onInstalled.addListener = function(callback) {};
/** @constructor */
chrome.runtime.Manifest = function() {};
/** @type {string} */
chrome.runtime.Manifest.prototype.version;
/** @return {!chrome.runtime.Manifest} */
chrome.runtime.getManifest = function() {};
/** @const */
chrome.tabs = {};
/** @return {undefined} */
chrome.tabs.create = function(properties) {};
/** @return {Promise<*>} */
HTMLVideoElement.prototype.requestPictureInPicture = function() {};
/** @const */
chrome.extension = {};
/** @return {string} */
chrome.extension.getURL = function(path) {};
/** @const */
chrome.storage = {};
/** @const */
chrome.storage.sync = {};
/** @return {undefined} */
chrome.storage.sync.get = function(keys, callback) {};
/** @return {undefined} */
chrome.storage.sync.set = function(items, callback) {};
/** @return {undefined} */
chrome.storage.sync.clear = function(callback) {};

152
src/common/scripts/fix.js Normal file
View File

@ -0,0 +1,152 @@
import { info } from './logger.js'
let activeVideo = null;
let timeoutId = 0;
let /** !Object<string, Array> */ timeouts = {};
const /** !Array<number> */ requests = [];
const /** !Array<function(number): undefined> */ callbacks = [];
const originalSetTimeout = window.setTimeout;
const originalClearTimeout = window.clearTimeout;
const originalRequestAnimationFrame = window.requestAnimationFrame;
/**
* Tracks animation frame requests and forwards requests when page visible
*
* @param {function(number): undefined} callback - a requestAnimationFrame callback
*/
const trackAnimationFrameRequest = function(callback) {
let request = 0;
if (!activeVideo) {
request = originalRequestAnimationFrame(callback);
requests.push(request);
}
callbacks.push(callback);
return request;
};
window.requestAnimationFrame = trackAnimationFrameRequest;
/**
* Clears tracked animation frame requests on new frame
*/
const clearAnimationFrameRequests = function() {
requests.length = 0;
callbacks.length = 0;
originalRequestAnimationFrame(clearAnimationFrameRequests);
};
clearAnimationFrameRequests();
/**
* Calls tracked animation frame requests and timeouts
*/
const callAnimationFrameRequestsAndTimeouts = function() {
// Copy animation frame callbacks before calling to prevent endless looping
const callbacksCopy = callbacks.slice();
callbacks.length = 0;
// Call animation frame requests
const timestamp = window.performance.now();
for (let callback; callback = callbacksCopy.pop();) {
callback(timestamp);
}
// Copy timeouts to prevent endless looping
const timeoutsCopy = timeouts;
timeouts = {};
// Call elapsed timeouts
for (let id in timeoutsCopy) {
let timeout = timeoutsCopy[id];
if (timeout[0] <= timestamp) {
if (typeof timeout[1] == "function") {
timeout[1]();
} else {
eval(/** @type {string} */ (timeout[1]));
}
} else {
timeouts[id] = timeout;
}
}
};
/**
* Avoids background throttling by invoking timeouts with media 'timeupdate' events
*
* @param {Function|string} callback - a setTimeout callback
* @param {number=} timeout - a delay in ms
* @return {number}
*/
const unthrottledSetTimeout = function(callback, timeout) {
const id = timeoutId++;
timeouts[id.toString()] = [window.performance.now() + (timeout || 0), callback];
return id;
};
/**
* Clears queued timeouts to be invoked with media 'timeupdate' events
*
* @param {?number|undefined} id - an id returned by unthrottledSetTimeout
*/
const unthrottledClearTimeout = function(id) {
if (id) delete timeouts[id.toString()];
};
/**
* Bypasses background timer throttling when video playing picture in picture
*/
const bypassBackgroundTimerThrottling = function() {
if (document.hidden) {
const allVideos = document.querySelectorAll('video');
for (let videoId = allVideos.length; videoId--;) {
const video = /** @type {?HTMLVideoElement} */ (allVideos[videoId]);
if (video.webkitPresentationMode == 'picture-in-picture') {
activeVideo = video;
break;
}
}
if (!activeVideo) return;
for (let request; request = requests.pop();) {
window.cancelAnimationFrame(request);
}
window.setTimeout = unthrottledSetTimeout;
window.clearTimeout = unthrottledClearTimeout;
activeVideo.addEventListener('timeupdate', callAnimationFrameRequestsAndTimeouts);
info('Bypassing background timer throttling');
} else if (activeVideo) {
info('Finished bypassing background timer throttling');
window.setTimeout = originalSetTimeout;
window.clearTimeout = originalClearTimeout;
activeVideo.removeEventListener('timeupdate', callAnimationFrameRequestsAndTimeouts);
activeVideo = null;
for (let callbackId = callbacks.length; callbackId--;) {
let request = originalRequestAnimationFrame(callbacks[callbackId]);
requests.push(request);
}
const timestamp = window.performance.now();
for (let id in timeouts) {
let timeout = timeouts[id];
originalSetTimeout(timeout[1], timeout[0] - timestamp);
}
timeouts = {};
}
};
document.addEventListener('visibilitychange', bypassBackgroundTimerThrottling);

View File

@ -0,0 +1,117 @@
import { error } from './logger.js'
const localizations = {};
localizations['button-title'] = {
'en': 'Open Picture in Picture mode',
'de': 'Bild-in-Bild starten',
'nl': 'Beeld in beeld starten',
'fr': 'Démarrer Image dans limage',
};
localizations['donate'] = {
'en': 'Donate',
'de': 'Spenden',
};
localizations['donate-small'] = {
'en': 'Small donation',
};
localizations['donate-medium'] = {
'en': 'Medium donation',
};
localizations['donate-large'] = {
'en': 'Grand donation',
};
localizations['total-donations'] = {
'en': 'Total donations:',
};
localizations['donate-error'] = {
'en': 'In-app purchase unavailable',
};
localizations['report-bug'] = {
'en': 'Report a bug',
'de': 'Einen Fehler melden',
};
localizations['options'] = {
'en': 'Options',
};
localizations['install-thanks'] = {
'en': 'Thanks for adding PiPer!',
};
localizations['enable'] = {
'en': 'Enable',
};
localizations['safari-disabled-warning'] = {
'en': 'Extension is currently disabled, enable in Safari preferences',
};
localizations['chrome-flags-open'] = {
'en': 'Open Chrome Flags',
};
localizations['chrome-flags-warning'] = {
'en': 'Before you get started you need to enable the chrome flag [emphasis]"SurfaceLayer objects for videos"[/emphasis]',
};
// Set English as the default fallback language
const defaultLanguage = 'en';
/**
* Returns a localized version of the string designated by the specified key
*
* @param {string} key - the key for a string
* @param {string=} language - two-letter ISO 639-1 language code
* @return {string}
*/
export const localizedString = function(key, language = navigator.language.substring(0, 2)) {
// Get all localizations for key
const /** Object<string,string> */ localizationsForKey = localizations[key];
if (localizationsForKey) {
// Get the users specific localization or fallback to default language
let string = localizationsForKey[language] || localizationsForKey[defaultLanguage];
if (string) return string;
}
error(`No localized string found for key '${key}'`);
return '';
};
/**
* Returns a localized version of the string designated by the specified key with tags replaced
*
* @param {string} key - the key for a string
* @param {Array<Array<string>>} replacements - an array of arrays containing pairs of tags and their replacement
* @param {string=} language - two-letter ISO 639-1 language code
* @return {string}
*/
export const localizedStringWithReplacements = function(key, replacements, language) {
let string = localizedString(key, language);
// Replace tags of the form [XXX] with directed replacements if needed
for (let index = replacements.length; index--; ) {
let replacement = replacements[index];
// Ensure tags do not contain special characters (this gets optimised away as opposed to escaping the tags with the associated performance cost)
if (/[^-_0-9a-zA-Z\/]/.test(replacement[0])) {
error(`Invalid characters used in localized string tag '${replacement[0]}'`);
}
const regex = new RegExp(`\\\[${replacement[0]}\\\]`, 'g');
string = string.replace(regex, replacement[1]);
}
return string;
};

View File

@ -0,0 +1,36 @@
import { LOGGING_LEVEL } from './defines.js'
const loggingPrefix = '[PiPer] ';
/** @enum {number} - Enum for logging level */
export const LoggingLevel = {
ALL: 0,
TRACE: 10,
INFO: 20,
WARNING: 30,
ERROR: 40,
};
/**
* Logs stack trace to console
*/
export const trace = (LoggingLevel.TRACE >= LOGGING_LEVEL) ?
console.trace.bind(console) : function(){};
/**
* Logs informational message to console
*/
export const info = (LoggingLevel.INFO >= LOGGING_LEVEL) ?
console.info.bind(console, loggingPrefix) : function(){};
/**
* Logs warning message to console
*/
export const warn = (LoggingLevel.WARNING >= LOGGING_LEVEL) ?
console.warn.bind(console, loggingPrefix) : function(){};
/**
* Logs error message to console
*/
export const error = (LoggingLevel.ERROR >= LOGGING_LEVEL) ?
console.error.bind(console, loggingPrefix) : function(){};

View File

@ -0,0 +1,50 @@
import { info } from './logger.js'
import { Browser, getBrowser, getResource, setResource } from './common.js'
import { addVideoElementListeners } from './video.js'
import { resources } from './resources/index.js';
import { checkButton, addButton } from './button.js'
import { shouldProcessCaptions, enableCaptions, processCaptions } from './captions.js'
import { initialiseCaches } from './cache.js'
/**
* Tracks injected button and captions
*/
const mutationObserver = function() {
// Process video captions if needed
if (shouldProcessCaptions()) processCaptions();
// Workaround Chrome's lack of an entering Picture in Picture mode event by monitoring all video elements
if (getBrowser() == Browser.CHROME) addVideoElementListeners();
// Try adding the button to the page if needed
if (checkButton()) return;
const currentResource = getResource();
const buttonParent = currentResource.buttonParent();
if (buttonParent) {
addButton(buttonParent);
if (currentResource.buttonDidAppear) currentResource.buttonDidAppear();
info('Picture in Picture button added to webpage');
}
};
// Remove subdomain and public suffix (far from comprehensive as only removes .X and .co.Y)
const domainName = location.hostname && location.hostname.match(/([^.]+)\.(?:co\.)?[^.]+$/)[1];
if (domainName in resources) {
info(`Matched site ${domainName} (${location})`);
setResource(resources[domainName]);
initialiseCaches();
if (getBrowser() == Browser.SAFARI) {
enableCaptions(true);
}
const observer = new MutationObserver(mutationObserver);
observer.observe(document, {
childList: true,
subtree: true,
});
mutationObserver();
}

View File

@ -0,0 +1,22 @@
export const domain = 'aktualne';
export const resource = {
buttonClassName: 'jw-icon jw-icon-inline jw-button-color jw-reset jw-icon-logo',
buttonElementType: 'div',
buttonHoverStyle: /** CSS */ (`
filter: brightness(50%) sepia(1) hue-rotate(311deg) saturate(550%) brightness(49%) !important;
`),
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
return document.querySelector('.jw-controlbar-right-group');
},
buttonStyle: /** CSS */ (`
width: 38px;
filter: brightness(80%);
`),
videoElement: function() {
return document.querySelector('video.jw-video');
},
};

View File

@ -0,0 +1,32 @@
export const domain = ['amazon', 'primevideo'];
export const resource = {
buttonHoverStyle: /** CSS */ (`opacity: 1 !important`),
buttonInsertBefore: function(/** Element */ parent) {
return parent.querySelector('.fullscreenButtonWrapper');
},
buttonParent: function() {
const e = document.getElementById('dv-web-player');
return e && e.querySelector('.hideableTopButtons');
},
buttonStyle: /** CSS */ (`
position: relative;
left: 8px;
width: 3vw;
height: 2vw;
min-width: 35px;
min-height: 24px;
border: 0px;
padding: 0px;
background-color: transparent;
opacity: 0.8;
`),
captionElement: function() {
const e = document.getElementById('dv-web-player');
return e && e.querySelector('.captions');
},
videoElement: function() {
const e = document.querySelector('.rendererContainer');
return e && e.querySelector('video[width="100%"]');
},
};

View File

@ -0,0 +1,13 @@
export const domain = 'bbc';
export const resource = {
buttonParent: function() {
return null;
},
captionElement: function() {
return document.querySelector('.p_subtitlesContainer');
},
videoElement: function() {
return document.querySelector('#mediaContainer video[src]');
},
};

View File

@ -0,0 +1,16 @@
export const domain = 'collegehumor';
export const resource = {
buttonClassName: 'vjs-control vjs-button',
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
return document.querySelector('.vjs-control-bar');
},
buttonScale: 0.6,
buttonStyle: /** CSS */ (`cursor: pointer`),
videoElement: function() {
return document.getElementById('vjs_video_3_html5_api');
},
};

View File

@ -0,0 +1,38 @@
import { Browser, getBrowser, getResource } from './../common.js'
export const domain = 'curiositystream';
export const resource = {
buttonClassName: 'vjs-control vjs-button',
buttonDidAppear: function() {
if (getBrowser() != Browser.SAFARI) return;
const video = /** @type {?HTMLVideoElement} */ (getResource().videoElement());
const videoContainer = video.parentElement;
video.addEventListener('webkitbeginfullscreen', function() {
const height = Math.floor(100 * video.videoHeight / video.videoWidth) + 'vw';
const maxHeight = video.videoHeight + 'px';
videoContainer.style.setProperty('height', height, 'important');
videoContainer.style.setProperty('max-height', maxHeight);
});
video.addEventListener('webkitendfullscreen', function() {
videoContainer.style.removeProperty('height');
videoContainer.style.removeProperty('max-height');
});
},
buttonHoverStyle: /** CSS */ (`opacity: 1 !important`),
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
const e = document.getElementById('main-player');
return e && e.querySelector('.vjs-control-bar');
},
buttonScale: 0.7,
buttonStyle: /** CSS */ (`
opacity: 0.8;
cursor: pointer;
`),
videoElement: function() {
return document.getElementById('main-player_html5_api');
},
};

View File

@ -0,0 +1,19 @@
export const domain = 'eurosportplayer';
export const resource = {
buttonElementType: 'div',
buttonHoverStyle: /** CSS */ (`opacity: 1 !important`),
buttonParent: function() {
return document.querySelector('.controls-bar-right-section');
},
buttonScale: 0.9,
buttonStyle: /** CSS */ (`
height: 100%;
margin-right: 15px;
opacity: 0.8;
cursor: pointer;
`),
videoElement: function() {
return document.querySelector('.video-player__screen');
},
};

View File

@ -0,0 +1,21 @@
export const domain = 'giantbomb';
export const resource = {
buttonElementType: 'div',
buttonInsertBefore: function(/** Element */ parent) {
return parent.querySelector('.js-vid-pin-wrap').nextSibling;
},
buttonParent: function() {
return document.querySelector('.av-controls--right');
},
buttonScale: 0.7,
buttonStyle: /** CSS */ (`
margin-left: 16px;
height: 100%;
opacity: 1.0;
cursor: pointer;
`),
videoElement: function() {
return document.querySelector('video[id^="video_js-vid-player"]');
}
};

View File

@ -0,0 +1,31 @@
import { getResource } from './../common.js'
export const domain = 'hulu';
export const resource = {
buttonClassName: 'simple-button',
buttonDidAppear: function() {
const buttonParent = getResource().buttonParent();
buttonParent.querySelector('.progress-bar-tracker').style.width = 'calc(100% - 380px)';
buttonParent.querySelector('.progress-time-container').style.marginRight = '45px';
},
buttonElementType: 'div',
buttonHoverStyle: /** CSS */ (`
filter: brightness(50%) sepia(1) hue-rotate(58deg) saturate(160%) brightness(110%) !important;
`),
buttonParent: function() {
return document.querySelector('#site-player .main-bar');
},
buttonScale: 0.7,
buttonStyle: /** CSS */ (`
top: -45px;
left: -50px;
filter: brightness(80%);
`),
captionElement: function() {
return document.querySelector('.closed-caption-container');
},
videoElement: function() {
return document.getElementById('content-video-player');
},
};

View File

@ -0,0 +1,16 @@
export const domain = 'littlethings';
export const resource = {
buttonClassName: 'jw-icon jw-icon-inline jw-button-color jw-reset jw-icon-logo',
buttonElementType: 'div',
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
return document.querySelector('.jw-controlbar-right-group');
},
buttonStyle: /** CSS */ (`width: 38px`),
videoElement: function() {
return document.querySelector('video.jw-video');
},
};

View File

@ -0,0 +1,19 @@
export const domain = 'mashable';
export const resource = {
buttonClassName: 'jw-icon jw-icon-inline jw-button-color jw-reset jw-icon-logo',
buttonElementType: 'div',
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
return document.querySelector('.jw-controlbar-right-group');
},
buttonStyle: /** CSS */ (`
top: -2px;
width: 38px;
`),
videoElement: function() {
return document.querySelector('video.jw-video');
},
};

View File

@ -0,0 +1,15 @@
export const domain = 'metacafe';
export const resource = {
buttonElementType: 'div',
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
return document.querySelector('#player_place .tray');
},
buttonScale: 0.85,
videoElement: function() {
return document.querySelector('#player_place video');
},
};

View File

@ -0,0 +1,23 @@
export const domain = 'mixer';
export const resource = {
buttonClassName: 'control',
buttonElementType: 'div',
buttonHoverStyle: /** CSS */ (`background: rgba(255, 255, 255, 0.08)`),
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild.previousSibling;
},
buttonParent: function() {
return document.querySelector('.control-container .toolbar .right');
},
buttonScale: 0.65,
buttonStyle: /** CSS */ (`
margin-top: 3px;
height: 36px;
border-radius: 50%;
cursor: pointer;
`),
videoElement: function() {
return document.querySelector('.control-container + video');
},
};

View File

@ -0,0 +1,20 @@
export const domain = 'mlb';
export const resource = {
buttonScale: 0.7,
buttonStyle: /** CSS */ (`
border: 0px;
background: transparent;
filter: brightness(80%);
`),
buttonHoverStyle: /** CSS */ (`filter: brightness(120%) !important`),
buttonParent: function() {
return document.querySelector('.bottom-controls-right');
},
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
videoElement: function() {
return document.querySelector('.mlbtv-media-player video');
},
};

View File

@ -0,0 +1,22 @@
import { getResource } from './../common.js'
export const domain = 'netflix';
export const resource = {
buttonClassName: 'touchable PlayerControls--control-element nfp-button-control default-control-button',
buttonHoverStyle: /** CSS */ (`transform: scale(1.2);`),
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
return document.querySelector('.PlayerControlsNeo__button-control-row');
},
buttonScale: 0.7,
captionElement: function() {
const e = getResource().videoElement();
return e && e.parentElement.querySelector('.player-timedtext');
},
videoElement: function() {
return document.querySelector('.VideoContainer video');
},
};

View File

@ -0,0 +1,25 @@
export const domain = 'ocs';
export const resource = {
buttonClassName: 'footer-elt fltr',
buttonInsertBefore: function(/** Element */ parent) {
return parent.querySelector('#togglePlay');
},
buttonParent: function() {
return document.querySelector('.footer-block:last-child');
},
buttonScale: 1.2,
buttonStyle: /** CSS */ (`
display: block;
width: 25px;
height: 18px;
margin-right: 10px;
margin-bottom: -10px;
padding: 0px;
border: 0px;
background-color: transparent;
`),
videoElement: function() {
return document.getElementById('LgyVideoPlayer');
},
};

View File

@ -0,0 +1,19 @@
export const domain = ['openload', 'oload'];
export const resource = {
buttonClassName: 'vjs-control vjs-button',
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
return document.querySelector('.vjs-control-bar');
},
buttonScale: 0.6,
buttonStyle: /** CSS */ (`
left: 5px;
cursor: pointer;
`),
videoElement: function() {
return document.getElementById('olvideo_html5_api');
},
};

View File

@ -0,0 +1,24 @@
export const domain = ['periscope', 'pscp'];
export const resource = {
buttonClassName: 'Pill Pill--withIcon',
buttonElementType: 'span',
buttonHoverStyle: /** CSS */ (`
opacity: 0.8 !important;
filter: brightness(125%) !important;
`),
buttonInsertBefore: function(/** Element */ parent) {
return parent.querySelector('.ShareBroadcast').nextSibling;
},
buttonParent: function() {
return document.querySelector('.VideoOverlayRedesign-BottomBar-Right');
},
buttonScale: 0.6,
buttonStyle: /** CSS */ (`
opacity: 0.5;
filter: brightness(200%);
`),
videoElement: function() {
return document.querySelector('.vjs-tech video[src]');
},
};

View File

@ -0,0 +1,33 @@
import { bypassBackgroundTimerThrottling } from './../common.js'
export const domain = 'plex';
export const resource = {
buttonDidAppear: function() {
bypassBackgroundTimerThrottling();
},
buttonHoverStyle: /** CSS */ (`opacity: 1 !important`),
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
const e = document.querySelector('div[class^="FullPlayerTopControls-topControls"]');
return /** @type {?Element} */ (e && e.lastChild);
},
buttonScale: 0.6,
buttonStyle: /** CSS */ (`
position: relative;
top: -3px;
padding: 10px;
border: 0px;
background: transparent;
opacity: 0.7;
text-shadow: 0px 0px 4px rgba(0, 0, 0, 0.45);
`),
captionElement: function() {
return document.querySelector('.libjass-subs');
},
videoElement: function() {
return document.querySelector('video[class^="HTMLMedia-mediaElement"]');
},
};

View File

@ -0,0 +1,18 @@
export const domain = ['seznam', 'stream'];
export const resource = {
buttonClassName: 'sznp-ui-widget-box',
buttonElementType: 'div',
buttonHoverStyle: /** CSS */ (`transform: scale(1.05)`),
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
return document.querySelector('.sznp-ui-ctrl-panel-layout-wrapper');
},
buttonScale: 0.75,
buttonStyle: /** CSS */ (`cursor: pointer`),
videoElement: function() {
return document.querySelector('.sznp-ui-tech-video-wrapper video');
},
};

View File

@ -0,0 +1,29 @@
import { getButton } from './../button.js'
export const domain = 'streamable';
export const resource = {
buttonDidAppear: function() {
const progressBar = document.getElementById('player-progress');
const progressBarStyle = window.getComputedStyle(progressBar);
getButton().style.right = progressBarStyle.right;
progressBar.style.right = (parseInt(progressBarStyle.right, 10) + 40) + 'px';
},
buttonElementType: 'div',
buttonHoverStyle: /** CSS */ (`opacity: 1 !important`),
buttonParent: function() {
return document.querySelector('.player-controls-right');
},
buttonStyle: /** CSS */ (`
position: absolute;
bottom: 10px;
height: 26px;
width: 26px;
cursor: pointer;
opacity: 0.9;
filter: drop-shadow(rgba(0, 0, 0, 0.5) 0px 0px 2px);
`),
videoElement: function() {
return document.getElementById('video-player-tag');
},
};

View File

@ -0,0 +1,23 @@
import { getButton } from './../button.js'
export const domain = 'ted';
export const resource = {
buttonClassName: 'z-i:0 pos:r bottom:0 hover/bg:white.7 b-r:.1 p:1 cur:p',
buttonElementType: 'div',
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
const playButton = document.querySelector('[aria-controls="video1"]');
return playButton.parentElement.parentElement;
},
buttonDidAppear: function() {
const img = getButton().querySelector('img');
img.classList.add('w:2');
img.classList.add('h:2');
},
videoElement: function() {
return document.querySelector('video[id^="ted-player-"]');
}
};

View File

@ -0,0 +1,20 @@
export const domain = 'theonion';
export const resource = {
buttonClassName: 'jw-icon jw-icon-inline jw-button-color jw-reset jw-icon-logo',
buttonElementType: 'div',
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
return document.querySelector('.jw-controlbar-right-group');
},
buttonStyle: /** CSS */ (`
top: -2px;
left: 10px;
width: 38px;
`),
videoElement: function() {
return document.querySelector('video.jw-video');
},
};

View File

@ -0,0 +1,44 @@
import { getResource } from './../common.js'
import { getButton } from './../button.js'
import { videoPlayingPictureInPicture, togglePictureInPicture } from './../video.js'
export const domain = 'twitch';
export const resource = {
buttonClassName: 'player-button',
buttonDidAppear: function() {
const neighbourButton = document.querySelector('.qa-fullscreen-button');
const neighbourTooltip = /** @type {HTMLElement} */ (neighbourButton.querySelector('.player-tip'));
const button = getButton();
const title = button.title;
const /** string */ neighbourTitle = neighbourTooltip.dataset['tip'];
button.title = '';
button.addEventListener('mouseover', function() {
neighbourTooltip.dataset['tip'] = title;
neighbourTooltip.style.display = 'block';
});
button.addEventListener('mouseout', function() {
neighbourTooltip.style.display = '';
neighbourTooltip.dataset['tip'] = neighbourTitle;
});
neighbourButton.addEventListener('click', function() {
const video = /** @type {?HTMLVideoElement} */ (getResource().videoElement());
if (videoPlayingPictureInPicture(video)) togglePictureInPicture(video);
});
neighbourButton.style.order = 2;
},
buttonHoverStyle: /** CSS */ (`
filter: brightness(50%) sepia(1) hue-rotate(219deg) saturate(117%) brightness(112%);
`),
buttonParent: function() {
return document.querySelector('.player-buttons-right');
},
buttonScale: 0.8,
buttonStyle: /** CSS */ (`order: 1`),
captionElement: function() {
return document.querySelector('.player-captions-container');
},
videoElement: function() {
return document.querySelector('.player-video video');
},
};

View File

@ -0,0 +1,26 @@
import { getResource } from './../common.js'
import { videoPlayingPictureInPicture, togglePictureInPicture } from './../video.js'
export const domain = 'udemy';
export const resource = {
buttonClassName: 'vjs-control vjs-button',
buttonDidAppear: function() {
document.querySelector('.vjs-fullscreen-control').addEventListener('click', function() {
const video = /** @type {?HTMLVideoElement} */ (getResource().videoElement());
if (videoPlayingPictureInPicture(video)) togglePictureInPicture(video);
});
},
buttonParent: function() {
return document.querySelector('.vjs-control-bar');
},
buttonScale: 0.7,
buttonStyle: /** CSS */ (`order: 7`),
captionElement: function() {
const e = getResource().videoElement();
return e && e.parentElement.querySelector('.vjs-text-track-display');
},
videoElement: function() {
return document.querySelector('video.vjs-tech');
},
};

View File

@ -0,0 +1,23 @@
export const domain = 'ustream';
export const resource = {
buttonClassName: 'component shown',
buttonElementType: 'div',
buttonHoverStyle: /** CSS */ (`
opacity: 1 !important;
filter: drop-shadow(0px 0px 5px rgba(255, 255, 255, 0.5));
`),
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonScale: 0.8,
buttonStyle: /** CSS */ (`
opacity: 0.7;
`),
buttonParent: function() {
return document.getElementById('controlPanelRight');
},
videoElement: function() {
return document.querySelector('#ViewerContainer video');
},
};

View File

@ -0,0 +1,19 @@
export const domain = 'vevo';
export const resource = {
buttonClassName: 'player-control',
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
return document.querySelector('#control-bar .right-controls');
},
buttonScale: 0.7,
buttonStyle: /** CSS */ (`
border: 0px;
background: transparent;
`),
videoElement: function() {
return document.getElementById('html5-player');
},
};

View File

@ -0,0 +1,17 @@
export const domain = 'vice';
export const resource = {
buttonClassName: 'vp__controls__icon__popup__container',
buttonElementType: 'div',
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
return document.querySelector('.vp__controls__icons');
},
buttonScale: 0.6,
buttonStyle: /** CSS */ (`top: -11px`),
videoElement: function() {
return document.querySelector('video.jw-video');
},
};

View File

@ -0,0 +1,21 @@
export const domain = 'vid';
export const resource = {
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
return document.querySelector('.vjs-control-bar');
},
buttonScale: 0.7,
buttonStyle: /** CSS */ (`
position: relative;
top: -2px;
left: 9px;
padding: 0px;
margin: 0px;
`),
videoElement: function() {
return document.getElementById('video_player_html5_api');
},
};

View File

@ -0,0 +1,23 @@
import { getButton } from './../button.js'
export const domain = ['vijf', 'vier', 'zes'];
export const resource = {
buttonClassName: 'vjs-control vjs-button',
buttonDidAppear: function() {
// Move fullscreen button to the right so the pip button appears left of it
const fullScreenButton = document.getElementsByClassName('vjs-fullscreen-control')[0];
fullScreenButton.style.order = 10;
},
buttonParent: function() {
return document.getElementsByClassName('vjs-control-bar')[0];
},
buttonStyle: /** CSS */ (`
text-indent: 0! important;
margin-left: 10px;
order: 9;
`),
videoElement: function() {
return document.querySelector('video[preload="metadata"]');
},
};

View File

@ -0,0 +1,25 @@
export const domain = 'vrt';
export const resource = {
buttonClassName: 'vuplay-control',
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
return document.getElementsByClassName('vuplay-control-right')[0];
},
captionElement: function() {
return document.querySelector('.theoplayer-texttracks');
},
buttonStyle: /** CSS */ (`
width: 30px;
height: 47px;
padding: 0;
position: relative;
top: -9px;
right: 8px;
`),
videoElement: function() {
return document.querySelector('video[preload="metadata"]');
},
};

View File

@ -0,0 +1,38 @@
import { getResource, bypassBackgroundTimerThrottling } from './../common.js'
import { getButton } from './../button.js'
import { videoPlayingPictureInPicture, togglePictureInPicture } from './../video.js'
export const domain = 'vrv';
export const resource = {
buttonClassName: 'vjs-control vjs-button',
buttonDidAppear: function() {
const neighbourButton = getButton().nextSibling;
neighbourButton.addEventListener('click', function() {
const video = /** @type {?HTMLVideoElement} */ (getResource().videoElement());
if (videoPlayingPictureInPicture(video)) togglePictureInPicture(video);
});
bypassBackgroundTimerThrottling();
},
buttonHoverStyle: /** CSS */ (`opacity: 1 !important`),
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
return document.querySelector('.vjs-control-bar');
},
buttonScale: 0.6,
buttonStyle: /** CSS */ (`
position: absolute;
right: calc(50px + 2.5rem);
width: 50px;
cursor: pointer;
opacity: 0.6;
`),
captionElement: function() {
return document.querySelector('.libjass-subs');
},
videoElement: function() {
return document.getElementById('player_html5_api');
},
};

View File

@ -0,0 +1,31 @@
import { getResource } from './../common.js'
export const domain = 'yeloplay';
export const resource = {
buttonClassName: 'button',
buttonDidAppear: function() {
const parent = getResource().buttonParent();
parent.style.width = '210px';
},
buttonHoverStyle: /** CSS */ (`opacity: 1 !important`),
buttonInsertBefore: function(/** Element */ parent) {
return document.getElementsByTagName('player-fullscreen-button')[0];
},
buttonParent: function() {
return document.getElementsByClassName('buttons')[0];
},
buttonScale: 0.8,
buttonStyle: /** CSS */ (`
margin-bottom: -10px;
margin-left: 10px;
width: 50px;
cursor: pointer;
opacity: 0.8;
height: 40px !important;
margin-bottom: 0px !important;
`),
videoElement: function() {
return document.querySelector('video[src]');
},
};

View File

@ -0,0 +1,55 @@
import { Browser, getBrowser, getResource, bypassBackgroundTimerThrottling } from './../common.js'
import { getButton } from './../button.js'
import { enableCaptions, disableCaptions, shouldProcessCaptions } from './../captions.js'
export const domain = ['youtube', 'youtu'];
export const resource = {
buttonClassName: 'ytp-button',
buttonDidAppear: function() {
const button = getButton();
const neighbourButton = /** @type {?HTMLElement} */ (button.nextSibling);
const /** string */ title = button.title;
const /** string */ neighbourTitle = neighbourButton.title;
button.title = '';
button.addEventListener('mouseover', function() {
neighbourButton.title = title;
neighbourButton.dispatchEvent(new Event('mouseover'));
});
button.addEventListener('mouseout', function() {
neighbourButton.dispatchEvent(new Event('mouseout'));
neighbourButton.title = neighbourTitle;
});
bypassBackgroundTimerThrottling();
// Workaround Safari bug; old captions persist in Picture in Picture mode when MediaSource buffers change
if (getBrowser() == Browser.SAFARI) {
const video = /** @type {?HTMLVideoElement} */ (getResource().videoElement());
let captionsVisible = false;
const navigateStart = function() {
captionsVisible = shouldProcessCaptions();
if (captionsVisible) disableCaptions();
};
const navigateFinish = function() {
if (captionsVisible) enableCaptions();
};
window.addEventListener('spfrequest', navigateStart);
window.addEventListener('spfdone', navigateFinish);
window.addEventListener('yt-navigate-start', navigateStart);
window.addEventListener('yt-navigate-finish', navigateFinish);
}
},
buttonInsertBefore: function(/** Element */ parent) {
return parent.lastChild;
},
buttonParent: function() {
return document.querySelector('.ytp-right-controls');
},
buttonScale: 0.68,
captionElement: function() {
return document.querySelector('.caption-window');
},
videoElement: function() {
return document.querySelector('video.html5-main-video');
},
};

153
src/common/scripts/video.js Normal file
View File

@ -0,0 +1,153 @@
import { info } from './logger.js'
import { Browser, getBrowser, getResource } from './common.js'
const CHROME_PLAYING_PIP_ATTRIBUTE = 'data-playing-picture-in-picture';
const /** !Array<function(HTMLVideoElement, boolean)> */ eventListeners = [];
/**
* Toggles video Picture in Picture
*
* @param {HTMLVideoElement} video - video element to toggle Picture in Picture mode
*/
export const togglePictureInPicture = function(video) {
const playingPictureInPicture = videoPlayingPictureInPicture(video);
switch (getBrowser()) {
case Browser.SAFARI:
if (playingPictureInPicture) {
video.webkitSetPresentationMode('inline');
} else {
video.webkitSetPresentationMode('picture-in-picture');
}
break;
case Browser.CHROME:
if (playingPictureInPicture) {
// Workaround Chrome content scripts being unable to call 'exitPictureInPicture' directly
const script = document.createElement('script');
script.textContent = 'document.exitPictureInPicture()';
document.head.appendChild(script);
script.remove();
} else {
video.requestPictureInPicture();
}
break;
case Browser.UNKNOWN:
default:
break;
}
};
/**
* Adds a Picture in Picture event listener
*
* @param {function(HTMLVideoElement, boolean)} listener - an event listener to add
*/
export const addPictureInPictureEventListener = function(listener) {
if (!eventListeners.includes(listener)) {
eventListeners.push(listener);
}
if (getBrowser() == Browser.SAFARI) {
document.addEventListener('webkitpresentationmodechanged', videoPresentationModeChanged, {
capture: true,
});
}
};
/**
* Removes a Picture in Picture event listener
*
* @param {function(HTMLVideoElement,boolean)} listener - an event listener to remove
*/
export const removePictureInPictureEventListener = function(listener) {
const index = eventListeners.indexOf(listener);
if (index > -1) {
eventListeners.splice(index, 1);
}
if (getBrowser() == Browser.SAFARI && eventListeners.length == 0) {
document.removeEventListener('webkitpresentationmodechanged', videoPresentationModeChanged);
}
};
/**
* Dispatches a Picture in Picture event
*
* @param {HTMLVideoElement} video - target video element
*/
const dispatchPictureInPictureEvent = function(video) {
// Ignore events from other video elements e.g. adverts
const expectedVideo = getResource().videoElement(true);
if (video != expectedVideo) return;
const isPlayingPictureInPicture = videoPlayingPictureInPicture(video);
if (isPlayingPictureInPicture) {
info('Video entering Picture in Picture mode');
} else {
info('Video leaving Picture in Picture mode');
}
// Call event listeners using a copy to prevent possiblity of endless looping
const eventListenersCopy = eventListeners.slice();
for (let listener; listener = eventListenersCopy.pop();) {
listener(video, isPlayingPictureInPicture);
}
}
/**
* Dispatches a Picture in Picture event for every 'webkitpresentationmodechanged' event
*
* @param {!Event} event - a webkitpresentationmodechanged event
*/
const videoPresentationModeChanged = function(event) {
const video = /** @type {HTMLVideoElement} */ (event.target);
dispatchPictureInPictureEvent(video);
};
/**
* Returns true if video is playing Picture in Picture
*
* @param {HTMLVideoElement} video - video element to test
* @return {boolean}
*/
export const videoPlayingPictureInPicture = function(video) {
switch (getBrowser()) {
case Browser.SAFARI:
return video.webkitPresentationMode == 'picture-in-picture';
case Browser.CHROME:
return video.hasAttribute(CHROME_PLAYING_PIP_ATTRIBUTE);
case Browser.UNKNOWN:
default:
return false;
}
};
/**
* Sets Picture in Picture attribute and toggles captions on entering Picture in Picture mode
*
* @param {!Event} event - an enterpictureinpicture event
*/
const videoDidEnterPictureInPicture = function(event) {
const video = /** @type {HTMLVideoElement} */ (event.target);
// Set playing in Picture in Picture mode attribute and dispatch event
video.setAttribute(CHROME_PLAYING_PIP_ATTRIBUTE, true);
dispatchPictureInPictureEvent(video);
// Remove Picture in Picture attribute and dispatch event on leaving Picture in Picture mode
video.addEventListener('leavepictureinpicture', function(event) {
video.removeAttribute(CHROME_PLAYING_PIP_ATTRIBUTE);
dispatchPictureInPictureEvent(video);
}, { once: true });
};
/**
* Adds Picture in Picture event listeners to all video elements
*/
export const addVideoElementListeners = function() {
const elements = document.getElementsByTagName('video');
for (let index = 0, element; element = elements[index]; index++) {
element.addEventListener('enterpictureinpicture', videoDidEnterPictureInPicture);
}
};

View File

@ -1,4 +0,0 @@
<svg fill="#FFFFFF" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg">
<path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 1.98 2 1.98h18c1.1 0 2-.88 2-1.98V5c0-1.1-.9-2-2-2zm0 16.01H3V4.98h18v14.03z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 289 B

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 36 36" style="enable-background:new 0 0 36 36;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:url(#SVGID_2_);}
</style>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="18" y1="0" x2="18" y2="36">
<stop offset="0" style="stop-color:#C9C9C9"/>
<stop offset="1" style="stop-color:#6D6D6D"/>
</linearGradient>
<path class="st0" d="M36,18c0,9.9-8.1,18-18,18S0,27.9,0,18S8.1,0,18,0S36,8.1,36,18z M18,3.1C9.8,3.1,3.1,9.8,3.1,18
S9.8,32.9,18,32.9S32.9,26.2,32.9,18S26.2,3.1,18,3.1z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="18" y1="9.8" x2="18" y2="26.3">
<stop offset="0" style="stop-color:#C9C9C9"/>
<stop offset="1" style="stop-color:#6D6D6D"/>
</linearGradient>
<path class="st1" d="M27,26.3H9c-1.1,0-2-0.9-2-2V11.8c0-1.1,0.9-2,2-2h18c1.1,0,2,0.9,2,2v12.5C29,25.4,28.1,26.3,27,26.3z
M18.4,15v-1.9c0-1.1-0.9-2-2-2h-6.3c-1.1,0-2,0.9-2,2V15c0,1.1,0.9,2,2,2h6.3C17.5,17,18.4,16.1,18.4,15z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Author</key>
<string>Adam Marcus</string>
<key>Builder Version</key>
<string>12602.4.8</string>
<key>CFBundleDisplayName</key>
<string>PiPer</string>
<key>CFBundleIdentifier</key>
<string>com.amarcus.safari.piper</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>0.0</string>
<key>CFBundleVersion</key>
<string>0</string>
<key>Content</key>
<dict>
<key>Scripts</key>
<dict>
<key>End</key>
<array>
<string>scripts/main.js</string>
</array>
</dict>
</dict>
<key>Description</key>
<string>Adds Picture in Picture functionality to Youtube, Netflix, Amazon Video, Twitch, and more!</string>
<key>DeveloperIdentifier</key>
<string>BQ6Q24MF9X</string>
<key>ExtensionInfoDictionaryVersion</key>
<string>1.0</string>
<key>Permissions</key>
<dict>
<key>Website Access</key>
<dict>
<key>Include Secure Pages</key>
<true/>
<key>Level</key>
<string>All</string>
</dict>
</dict>
<key>Update Manifest URL</key>
<string>https://s3.amazonaws.com/piper-extension/update.plist</string>
<key>Website</key>
<string>https://github.com/amarcu5/PiPer/</string>
</dict>
</plist>

View File

@ -8,9 +8,9 @@
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.amarcus.safari.piper</string> <string>com.amarcus.safari.piper</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.2.5</string> <string></string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>151</string> <string>186</string>
<key>Developer Identifier</key> <key>Developer Identifier</key>
<string>BQ6Q24MF9X</string> <string>BQ6Q24MF9X</string>
<key>URL</key> <key>URL</key>

View File

@ -0,0 +1,27 @@
//
// AppDelegate.swift
// PiPer App
//
// Created by Adam Marcus on 19/07/2018.
// Copyright © 2018 Adam Marcus. All rights reserved.
//
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Preload donation in-app purchases
DonationManager.shared.getDonationProducts()
}
func applicationWillTerminate(_ aNotification: Notification) {
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true;
}
}

View File

@ -0,0 +1,117 @@
//
// ConfettiView.swift
// PiPer
//
// Created by Adam Marcus on 22/11/2018.
// Copyright © 2018 Adam Marcus. All rights reserved.
//
import Cocoa
class ConfettiView: NSView {
private static var colors = [
NSColor(red:0.95, green:0.40, blue:0.27, alpha:1.0),
NSColor(red:1.00, green:0.78, blue:0.36, alpha:1.0),
NSColor(red:0.48, green:0.78, blue:0.64, alpha:1.0),
NSColor(red:0.30, green:0.76, blue:0.85, alpha:1.0),
NSColor(red:0.58, green:0.39, blue:0.55, alpha:1.0)
]
private var emitter: CAEmitterLayer!
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
public override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
private func setup() {
// Set up confetti emitter layer
emitter = CAEmitterLayer()
emitter.emitterShape = CAEmitterLayerEmitterShape.line
emitter.birthRate = 0
// Set confetti emitter layer position/size and respond to view frame changes
self.postsFrameChangedNotifications = true
NotificationCenter.default.addObserver(
self,
selector: #selector(setEmitterFrame),
name: NSView.frameDidChangeNotification,
object: self)
setEmitterFrame()
// Add emitter cells for each confetti color
var cells = [CAEmitterCell]()
for color in ConfettiView.colors {
cells.append(confettiWithColor(color: color))
}
emitter.emitterCells = cells
// Add confetti emitter layer
self.wantsLayer = true
self.layer!.addSublayer(emitter)
}
@objc private func setEmitterFrame() {
// Position confetti emitter offscreen and size to fit view
emitter.emitterPosition = CGPoint(x: frame.size.width * 0.5,
y: frame.size.height + 32)
emitter.emitterSize = CGSize(width: frame.size.width,
height: 1)
}
public func dropConfetti() {
// Set confetti emitter to show particles start spawning from emitter position
emitter.beginTime = CACurrentMediaTime()
// Animate confetti emitter birth rate to spawn a burst of confetti
let birthRateDecayAnimation = CABasicAnimation()
birthRateDecayAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
birthRateDecayAnimation.duration = CFTimeInterval(1.0)
birthRateDecayAnimation.fromValue = NSNumber(value: 1.0)
birthRateDecayAnimation.toValue = NSNumber(value: 0.0)
emitter.add(birthRateDecayAnimation, forKey: "birthRate")
}
// Generate a diamond CGImage representing confetti
private func getDiamondImage() -> CGImage? {
let image = NSImage(size: CGSize(width: 24, height: 32), flipped: false) { _ in
let path = NSBezierPath()
path.move(to: NSPoint(x: 12, y: 0))
path.line(to: NSPoint(x: 24, y: 16))
path.line(to: NSPoint(x: 12, y: 32))
path.line(to: NSPoint(x: 0, y: 16))
path.line(to: NSPoint(x: 12, y: 0))
NSColor.white.setFill()
path.fill()
return true
}
return image.cgImage(forProposedRect: nil, context: nil, hints: nil)
}
// Setup an confetti emitter cell with a specific color
private func confettiWithColor(color: NSColor) -> CAEmitterCell {
let confetti = CAEmitterCell()
confetti.birthRate = 400.0
confetti.lifetime = 10.0
confetti.alphaSpeed = -1.0 / confetti.lifetime
confetti.color = color.cgColor
confetti.velocity = 200.0
confetti.velocityRange = 200.0
confetti.emissionRange = CGFloat(Double.pi * 0.5)
confetti.spin = 3.0
confetti.spinRange = 3.0
confetti.scale = 0.15
confetti.scaleRange = 0.15
confetti.contents = getDiamondImage()
return confetti
}
}

View File

@ -0,0 +1,53 @@
//
// DonateContainerViewController.swift
// PiPer
//
// Created by Adam Marcus on 19/11/2018.
// Copyright © 2018 Adam Marcus. All rights reserved.
//
import Cocoa
class DonateContainerViewController: NSTabViewController {
@IBOutlet var donateProgressViewTabItem: NSTabViewItem!
@IBOutlet var donateViewTabItem: NSTabViewItem!
override func viewDidLoad() {
super.viewDidLoad()
let donateViewController = donateViewTabItem.viewController as! DonateViewController
let donateProgressViewController = donateProgressViewTabItem.viewController as! DonateProgressViewController
let productsAvaliable = DonationManager.shared.donationProductsAvaliable()
donateViewController.loadProducts(completionHandler: {
success in
if !success {
donateProgressViewController.showError()
} else if !productsAvaliable {
self.tabView.selectTabViewItem(self.donateViewTabItem)
}
})
if productsAvaliable {
self.tabView.selectTabViewItem(self.donateViewTabItem)
}
}
override func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) {
super.tabView(tabView, didSelect: tabViewItem)
if let window = self.view.window, let contentSize = tabViewItem?.view?.fittingSize {
let newWindowSize = window.frameRect(forContentRect: NSRect(origin: CGPoint.zero, size: contentSize)).size
var frame = window.frame
frame.origin.x = frame.origin.x + (frame.size.width - newWindowSize.width) * 0.5
frame.origin.y = frame.origin.y + (frame.size.height - newWindowSize.height)
frame.size = newWindowSize
window.animator().setFrame(frame, display: false)
}
}
}

View File

@ -0,0 +1,29 @@
//
// DonateProgressViewController.swift
// PiPer
//
// Created by Adam Marcus on 19/11/2018.
// Copyright © 2018 Adam Marcus. All rights reserved.
//
import Cocoa
class DonateProgressViewController: NSViewController {
@IBOutlet var progressIndicator: NSProgressIndicator!
@IBOutlet var errorMessage: LocalizedTextField!
override func viewDidLoad() {
super.viewDidLoad()
progressIndicator.startAnimation(self)
}
func showError() {
progressIndicator.stopAnimation(self)
errorMessage.isHidden = false
}
@IBAction func dismissClicked(sender: NSButton) {
self.parent?.dismiss(self.parent)
}
}

View File

@ -0,0 +1,88 @@
//
// DonateViewController.swift
// PiPer
//
// Created by Adam Marcus on 17/11/2018.
// Copyright © 2018 Adam Marcus. All rights reserved.
//
import Cocoa
class DonateViewController: NSViewController {
@IBOutlet var totalDonations: NSTextField!
@objc dynamic var donationProducts = [DonationProduct]()
@IBOutlet var donationTableView: NSTableView!
@IBOutlet var tableViewHeightConstraint: NSLayoutConstraint!
@IBOutlet var tableViewWidthConstraint: NSLayoutConstraint!
@IBOutlet var confettiView: ConfettiView!
func loadProducts(completionHandler: @escaping (_ success: Bool) -> ()) {
DonationManager.shared.getDonationProducts(completionHandler: {
productsResponse, error in
if let products = productsResponse {
self.donationProducts = products
completionHandler(true)
} else {
completionHandler(false)
}
})
}
override func viewDidLoad() {
super.viewDidLoad()
self.donationTableView.postsFrameChangedNotifications = true
NotificationCenter.default.addObserver(
self,
selector: #selector(sizeDonationTableViewToFitContents),
name: NSView.frameDidChangeNotification,
object: self.donationTableView)
sizeDonationTableViewToFitContents()
updateTotalDonations()
}
@objc private func sizeDonationTableViewToFitContents() {
var computedWidth: CGFloat = 0
for row in 0..<self.donationTableView.numberOfRows {
if let tableCellView = self.donationTableView.view(atColumn: 0, row:row, makeIfNecessary: true) {
computedWidth = max(computedWidth, tableCellView.fittingSize.width)
}
}
self.tableViewHeightConstraint.constant = self.donationTableView.frame.size.height
self.tableViewWidthConstraint.constant = computedWidth
self.donationTableView.tableColumns.first?.width = computedWidth
self.donationTableView.needsUpdateConstraints = true
}
func updateTotalDonations() {
let totalDonations = DonationManager.shared.totalDonations
if let priceString = DonationManager.shared.localizedStringForPrice(totalDonations),
let emoticon = DonationManager.shared.emoticonForPrice(totalDonations) {
self.totalDonations.stringValue = priceString + " " + emoticon
}
}
@IBAction func buyClicked(sender: NSButton) {
let row = self.donationTableView.row(for: sender)
let donationProduct = self.donationProducts[row]
DonationManager.shared.buyDonationProduct(donationProduct, completionHandler: {
transaction in
let state = transaction.transactionState
if state == .purchased || state == .restored {
self.confettiView.dropConfetti()
self.updateTotalDonations()
}
})
}
@IBAction func dismissClicked(sender: NSButton) {
self.parent?.dismiss(self.parent)
}
}

View File

@ -0,0 +1,176 @@
//
// DonationManager.swift
// PiPer
//
// Created by Adam Marcus on 21/11/2018.
// Copyright © 2018 Adam Marcus. All rights reserved.
//
import Cocoa
import StoreKit
class DonationProduct : NSObject {
@objc let name: String
@objc let price: String
@objc let emoticon: String
fileprivate let product: SKProduct
fileprivate init(name: String, price: String, emoticon: String, product: SKProduct) {
self.name = name
self.price = price
self.emoticon = emoticon
self.product = product
super.init()
}
}
class DonationManager {
public typealias GetDonationProductsCompletionHandler = (_ products: [DonationProduct]?, _ error: Error?) -> ()
public typealias BuyDonationProductCompletionHandler = (_ transaction: SKPaymentTransaction) -> ()
static let shared = DonationManager()
static private let identifiers = Set([
"com.amarcus.PiPer.donation.1",
"com.amarcus.PiPer.donation.3",
"com.amarcus.PiPer.donation.10"
])
static private let totalDonationsKey = "com.amarcus.PiPer.totalDonations"
private var donationProducts:[DonationProduct]?
private var smallestDonation: NSDecimalNumber?
private var priceFormatter: NumberFormatter?
private init() {}
func getDonationProducts(completionHandler: @escaping GetDonationProductsCompletionHandler = {_,_ in}) {
if let products = self.donationProducts {
completionHandler(products, .none)
return
}
guard InAppPurchaseHelper.shared.canMakePayments() else {
let error = NSError(domain: "com.amarcus.PiPer.DonationManager",
code: 0,
userInfo: [NSLocalizedDescriptionKey: "Payments are unavailable"])
completionHandler(nil, error)
return
}
InAppPurchaseHelper.shared.requestProducts(identifiers:DonationManager.identifiers, completionHandler: {
productResponse, error in
DispatchQueue.main.async {
guard error == nil else {
completionHandler(nil, error)
return
}
if let response = productResponse {
let sortedProducts = response.products.sorted { (product1, product2) -> Bool in
return product1.price.compare(product2.price) == .orderedAscending
}
if let cheapestProduct = sortedProducts.first {
let priceFormatter = NumberFormatter()
priceFormatter.numberStyle = .currency
priceFormatter.locale = cheapestProduct.priceLocale
self.priceFormatter = priceFormatter
self.smallestDonation = cheapestProduct.price
}
self.donationProducts = sortedProducts.map {
DonationProduct(name: $0.localizedTitle,
price: self.localizedStringForPrice($0.price)!,
emoticon: self.emoticonForPrice($0.price)!,
product: $0)
}
completionHandler(self.donationProducts, .none)
}
}
})
}
var totalDonations: NSDecimalNumber {
set {
guard let priceFormatter = self.priceFormatter else {
return
}
var totalDonationsDictionary = NSUbiquitousKeyValueStore.default.dictionary(forKey: DonationManager.totalDonationsKey) ?? [String:String]()
let numberString = newValue.description(withLocale: [NSLocale.Key.decimalSeparator: "."])
totalDonationsDictionary[priceFormatter.currencyCode] = numberString
NSUbiquitousKeyValueStore.default.set(totalDonationsDictionary, forKey: DonationManager.totalDonationsKey)
NSUbiquitousKeyValueStore.default.synchronize()
}
get {
guard let priceFormatter = self.priceFormatter,
let totalDonationsDictionary = NSUbiquitousKeyValueStore.default.dictionary(forKey: DonationManager.totalDonationsKey),
let numberString = totalDonationsDictionary[priceFormatter.currencyCode] as? String else {
return NSDecimalNumber.zero
}
return NSDecimalNumber(string: numberString)
}
}
func buyDonationProduct(_ donationProduct: DonationProduct, completionHandler: @escaping BuyDonationProductCompletionHandler = {_ in}) {
InAppPurchaseHelper.shared.buyProduct(donationProduct.product, completionHandler: {
transaction in
DispatchQueue.main.async {
self.totalDonations = self.totalDonations.adding(donationProduct.product.price)
completionHandler(transaction)
}
})
}
func donationProductsAvaliable() -> Bool {
return self.donationProducts != nil
}
func localizedStringForPrice(_ price: NSDecimalNumber) -> String? {
guard let priceFormatter = self.priceFormatter else {
return nil
}
return priceFormatter.string(from:price) ?? "\(price)"
}
func emoticonForPrice(_ decimalPrice: NSDecimalNumber) -> String? {
guard let baseUnit = self.smallestDonation?.doubleValue else {
return nil
}
// Get seasonal emoticons
let emoticons = Array({
() -> String in
switch Calendar.current.dateComponents([.month, .weekdayOrdinal, .weekday, .day], from: Date()) {
case let date where date.month == 10 && date.day == 31: // Halloween
return "💀👻🧙‍♀️🎃"
case let date where date.month == 11 && date.weekdayOrdinal == 4 && date.weekday == 5: // Thanksgiving
return "🍂🍴🍗🇺🇸"
case let date where date.month == 12 && date.day == 25: // Christmas
return "🥶🎄🎁🎅"
default:
return "😢🙂😃😍"
}
}()).map { String($0) }
// Select emoticon based on price
let price = decimalPrice.doubleValue
if price < baseUnit {
return emoticons[0]
} else if price < 3 * baseUnit {
return emoticons[1]
} else if price < 10 * baseUnit {
return emoticons[2]
} else {
return emoticons[3]
}
}
}

BIN
src/safari/App/Icon.icns Normal file

Binary file not shown.

View File

@ -0,0 +1,87 @@
//
// InAppPurchaseHelper.swift
// PiPer
//
// Created by Adam Marcus on 18/11/2018.
// Copyright © 2018 Adam Marcus. All rights reserved.
//
import Foundation
import StoreKit
class InAppPurchaseHelper: NSObject {
static let shared = InAppPurchaseHelper()
public typealias RequestProductsCompletionHandler = (_ response: SKProductsResponse?, _ error: Error?) -> ()
public typealias BuyProductCompletionHandler = (_ transaction: SKPaymentTransaction) -> ()
private var productsRequestsInProgress = [SKRequest:RequestProductsCompletionHandler]()
private var purchasesInProgress = [SKPayment:BuyProductCompletionHandler]()
private let paymentQueue = SKPaymentQueue.default()
private override init() {
super.init()
self.paymentQueue.add(self)
}
deinit {
self.paymentQueue.remove(self)
}
func requestProducts(identifiers: Set<String>, completionHandler: @escaping RequestProductsCompletionHandler) {
let request = SKProductsRequest(productIdentifiers: identifiers)
self.productsRequestsInProgress[request] = completionHandler
request.delegate = self
request.start()
}
func buyProduct(_ product: SKProduct, completionHandler: @escaping BuyProductCompletionHandler) {
let payment = SKPayment(product: product)
self.purchasesInProgress[payment] = completionHandler
self.paymentQueue.add(payment)
}
func canMakePayments() -> Bool {
return SKPaymentQueue.canMakePayments()
}
}
extension InAppPurchaseHelper: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
if let completionHandler = self.productsRequestsInProgress[request] {
completionHandler(response, .none)
}
self.productsRequestsInProgress.removeValue(forKey: request)
}
func request(_ request: SKRequest, didFailWithError error: Error) {
if let completionHandler = self.productsRequestsInProgress[request] {
completionHandler(.none, error)
}
self.productsRequestsInProgress.removeValue(forKey: request)
}
}
extension InAppPurchaseHelper: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue,
updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchased, .failed, .restored:
if let completionHandler = self.purchasesInProgress[transaction.payment] {
completionHandler(transaction)
self.purchasesInProgress.removeValue(forKey: transaction.payment)
}
queue.finishTransaction(transaction)
break
case .purchasing, .deferred:
break
}
}
}
}

36
src/safari/App/Info.plist Normal file
View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>PiPer</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string>Icon.icns</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.0.0</string>
<key>CFBundleVersion</key>
<string>0</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 Adam Marcus. All rights reserved.</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@ -0,0 +1,49 @@
//
// LocalizationManager.swift
// PiPer
//
// Created by Adam Marcus on 12/10/2018.
// Copyright © 2018 Adam Marcus. All rights reserved.
//
import Foundation
import JavaScriptCore
class LocalizationManager {
let context: JSContext = JSContext()
let languageCode: String
static let `default` = LocalizationManager()
init(withLanguageCode languageCode: String? = Locale.current.languageCode) {
self.languageCode = languageCode ?? ""
#if DEBUG
context.exceptionHandler = { _, value in
print("Localization JavaScriptCore error: \(value!)")
}
#endif
context.evaluateScript("const window = {};")
if let extensionBundleURL = ResourceHelper.extensionBundleURL {
let localizationFile = extensionBundleURL.appendingPathComponent("scripts/localization-bridge.js").path
let localizationFileContents = try? String(contentsOfFile: localizationFile,
encoding: String.Encoding.utf8)
if let localizationScript = localizationFileContents {
context.evaluateScript(localizationScript)
}
}
}
func localizedString(forKey key: String) -> String {
let string = context.evaluateScript("window.localizedString('\(key)', '\(languageCode)');").toString()
return string ?? ""
}
}

View File

@ -0,0 +1,33 @@
//
// LocalizedButton.swift
// PiPer
//
// Created by Adam Marcus on 13/10/2018.
// Copyright © 2018 Adam Marcus. All rights reserved.
//
import Cocoa
@IBDesignable
class LocalizedButton: NSButton {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
localizeTitle()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
localizeTitle()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
localizeTitle()
}
func localizeTitle() {
self.title = LocalizationManager.default.localizedString(forKey:self.title)
}
}

View File

@ -0,0 +1,33 @@
//
// LocalizedTextField.swift
// PiPer
//
// Created by Adam Marcus on 13/10/2018.
// Copyright © 2018 Adam Marcus. All rights reserved.
//
import Cocoa
@IBDesignable
class LocalizedTextField: NSTextField {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
localizeValue()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
localizeValue()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
localizeValue()
}
func localizeValue() {
self.stringValue = LocalizationManager.default.localizedString(forKey:self.stringValue)
}
}

View File

@ -0,0 +1,494 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Application-->
<scene sceneID="JPo-4y-FX3">
<objects>
<application id="hnw-xV-0zn" sceneMemberID="viewController">
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="PiPer" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="PiPer" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="Quit PiPer" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
</connections>
</application>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="PiPer" customModuleProvider="target"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="360" y="-44"/>
</scene>
<!--Window Controller-->
<scene sceneID="R2V-B0-nI4">
<objects>
<windowController id="B8D-0N-5wS" sceneMemberID="viewController">
<window key="window" title="PiPer" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" titleVisibility="hidden" id="IQv-IB-iLA">
<windowStyleMask key="styleMask" titled="YES" closable="YES" fullSizeContentView="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="356" height="220"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
<value key="minSize" type="size" width="356" height="220"/>
<value key="maxSize" type="size" width="356" height="220"/>
<connections>
<outlet property="delegate" destination="B8D-0N-5wS" id="98r-iN-zZc"/>
</connections>
</window>
<connections>
<segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
</connections>
</windowController>
<customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="55"/>
</scene>
<!--View Controller-->
<scene sceneID="hIz-AP-VOD">
<objects>
<viewController id="XfG-lQ-9wD" customClass="ViewController" customModule="PiPer" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="m2S-Jp-Qdl">
<rect key="frame" x="0.0" y="0.0" width="356" height="220"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<visualEffectView appearanceType="inheritedVibrantLight" blendingMode="behindWindow" material="underWindowBackground" state="followsWindowActiveState" translatesAutoresizingMaskIntoConstraints="NO" id="fhh-nv-kqK">
<rect key="frame" x="0.0" y="0.0" width="356" height="220"/>
</visualEffectView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="fhh-nv-kqK" secondAttribute="trailing" id="R1a-1n-kYy"/>
<constraint firstItem="fhh-nv-kqK" firstAttribute="top" secondItem="m2S-Jp-Qdl" secondAttribute="top" id="Vgc-sd-EOP"/>
<constraint firstItem="fhh-nv-kqK" firstAttribute="leading" secondItem="m2S-Jp-Qdl" secondAttribute="leading" id="ZHb-JW-3Uc"/>
<constraint firstAttribute="bottom" secondItem="fhh-nv-kqK" secondAttribute="bottom" id="f4x-N6-X6o"/>
</constraints>
</view>
<connections>
<outlet property="extensionDisabledView" destination="Wtu-CL-ZAO" id="yuV-8q-YtZ"/>
<outlet property="mainView" destination="3Hb-XS-UyM" id="yUU-7Q-ncx"/>
<outlet property="viewContainer" destination="fhh-nv-kqK" id="c0U-Yq-xxu"/>
</connections>
</viewController>
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<customView id="Wtu-CL-ZAO">
<rect key="frame" x="0.0" y="0.0" width="356" height="220"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="SlA-kL-hiD">
<rect key="frame" x="16" y="94" width="32" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="32" id="ZWA-Q4-Gnv"/>
<constraint firstAttribute="width" constant="32" id="sOE-7r-348"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSCaution" id="dJM-ez-eQ2"/>
</imageView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5oh-0M-Zn6" customClass="LocalizedButton" customModule="PiPer" customModuleProvider="target">
<rect key="frame" x="265" y="93" width="81" height="32"/>
<buttonCell key="cell" type="push" title="enable" bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="HcB-QW-aQq">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="clickedEnableExtensionWithSender:" target="XfG-lQ-9wD" id="jgv-vd-Hd3"/>
</connections>
</button>
<textField horizontalHuggingPriority="200" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="500" translatesAutoresizingMaskIntoConstraints="NO" id="Gzc-Eb-L1r" customClass="LocalizedTextField" customModule="PiPer" customModuleProvider="target">
<rect key="frame" x="56" y="102" width="207" height="17"/>
<textFieldCell key="cell" alignment="left" title="safari-disabled-warning" drawsBackground="YES" id="LEL-vU-9sz">
<font key="font" metaFont="systemMedium" size="13"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="5oh-0M-Zn6" firstAttribute="leading" secondItem="Gzc-Eb-L1r" secondAttribute="trailing" constant="8" id="Hcd-kf-9uY"/>
<constraint firstItem="Gzc-Eb-L1r" firstAttribute="leading" secondItem="SlA-kL-hiD" secondAttribute="trailing" constant="8" id="IAC-H1-5u2"/>
<constraint firstItem="SlA-kL-hiD" firstAttribute="centerY" secondItem="Wtu-CL-ZAO" secondAttribute="centerY" id="P98-CO-DBI"/>
<constraint firstItem="5oh-0M-Zn6" firstAttribute="centerY" secondItem="Wtu-CL-ZAO" secondAttribute="centerY" id="Ttv-0y-TAb"/>
<constraint firstItem="Gzc-Eb-L1r" firstAttribute="centerY" secondItem="Wtu-CL-ZAO" secondAttribute="centerY" id="cWx-Ul-bxK"/>
<constraint firstItem="SlA-kL-hiD" firstAttribute="leading" secondItem="Wtu-CL-ZAO" secondAttribute="leading" constant="16" id="gUm-TC-3DN"/>
<constraint firstAttribute="trailing" secondItem="5oh-0M-Zn6" secondAttribute="trailing" constant="16" id="rAw-aH-ZXO"/>
</constraints>
</customView>
<customView id="3Hb-XS-UyM">
<rect key="frame" x="0.0" y="0.0" width="356" height="220"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="l9e-rg-eUq">
<rect key="frame" x="58" y="88" width="80" height="80"/>
<constraints>
<constraint firstAttribute="width" constant="80" id="52v-J9-pFo"/>
<constraint firstAttribute="height" constant="80" id="9ic-ED-CML"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="Icon" id="655-Gm-l0p"/>
</imageView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aXl-MI-TCo">
<rect key="frame" x="61" y="45" width="73" height="39"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="PiPer" drawsBackground="YES" id="pyu-MO-9NT">
<font key="font" metaFont="system" size="32"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="h9M-Bg-2Qi" customClass="LocalizedButton" customModule="PiPer" customModuleProvider="target">
<rect key="frame" x="196" y="104" width="107" height="32"/>
<buttonCell key="cell" type="push" title="report-bug" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="HPq-wV-jsz">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="clickedReportBugWithSender:" target="XfG-lQ-9wD" id="GA1-uc-3Zx"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="l7Q-NM-lYD" customClass="LocalizedButton" customModule="PiPer" customModuleProvider="target">
<rect key="frame" x="196" y="70" width="107" height="32"/>
<buttonCell key="cell" type="push" title="donate" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="jbL-wE-6Bb">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<segue destination="1sh-YF-bwE" kind="sheet" id="qCv-wd-VJy"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="l9e-rg-eUq" firstAttribute="centerX" secondItem="3Hb-XS-UyM" secondAttribute="centerX" multiplier="0.55" id="0rd-47-5ke"/>
<constraint firstItem="l7Q-NM-lYD" firstAttribute="centerY" secondItem="3Hb-XS-UyM" secondAttribute="centerY" multiplier="1.2" id="1yu-Ya-ffe"/>
<constraint firstItem="aXl-MI-TCo" firstAttribute="top" secondItem="l9e-rg-eUq" secondAttribute="bottom" constant="4" id="9A4-i3-Pj0"/>
<constraint firstItem="l9e-rg-eUq" firstAttribute="centerY" secondItem="3Hb-XS-UyM" secondAttribute="centerY" constant="-18" id="NqP-Fs-Cka"/>
<constraint firstItem="aXl-MI-TCo" firstAttribute="centerX" secondItem="l9e-rg-eUq" secondAttribute="centerX" id="SqJ-aZ-Yox"/>
<constraint firstItem="h9M-Bg-2Qi" firstAttribute="centerY" secondItem="3Hb-XS-UyM" secondAttribute="centerY" multiplier="0.9" id="XJe-gR-gCd"/>
<constraint firstItem="l7Q-NM-lYD" firstAttribute="centerX" secondItem="3Hb-XS-UyM" secondAttribute="centerX" multiplier="1.4" id="b0H-tS-xdf"/>
<constraint firstItem="h9M-Bg-2Qi" firstAttribute="centerX" secondItem="3Hb-XS-UyM" secondAttribute="centerX" multiplier="1.4" id="bh5-aa-dpw"/>
<constraint firstItem="l7Q-NM-lYD" firstAttribute="width" secondItem="h9M-Bg-2Qi" secondAttribute="width" id="orO-L8-WbF"/>
</constraints>
</customView>
</objects>
<point key="canvasLocation" x="75" y="688"/>
</scene>
<!--Donate Progress View Controller-->
<scene sceneID="exg-Cy-V2E">
<objects>
<viewController id="xq6-KE-CKE" customClass="DonateProgressViewController" customModule="PiPer" customModuleProvider="target" sceneMemberID="viewController">
<customView key="view" id="Z3j-yD-7xa" userLabel="Progress View">
<rect key="frame" x="0.0" y="0.0" width="232" height="164"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<progressIndicator wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="E2A-bR-Me7">
<rect key="frame" x="100" y="50" width="32" height="32"/>
</progressIndicator>
<textField horizontalHuggingPriority="700" verticalHuggingPriority="700" translatesAutoresizingMaskIntoConstraints="NO" id="bXO-nx-K3f" customClass="LocalizedTextField" customModule="PiPer" customModuleProvider="target">
<rect key="frame" x="14" y="130" width="61" height="22"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="left" title="donate" id="rKt-S9-lad">
<font key="font" metaFont="system" size="18"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="bch-TQ-lJc">
<rect key="frame" x="200" y="132" width="16" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="5Dk-nE-VK5"/>
<constraint firstAttribute="width" constant="16" id="FKA-tU-4uc"/>
</constraints>
<buttonCell key="cell" type="bevel" bezelStyle="rounded" image="NSStopProgressTemplate" imagePosition="only" alignment="center" imageScaling="proportionallyUpOrDown" inset="2" id="fAn-Nq-nO9">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="dismissClickedWithSender:" target="xq6-KE-CKE" id="G6E-PK-2VE"/>
</connections>
</button>
<textField hidden="YES" horizontalHuggingPriority="200" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="500" translatesAutoresizingMaskIntoConstraints="NO" id="3uK-eO-TcT" customClass="LocalizedTextField" customModule="PiPer" customModuleProvider="target">
<rect key="frame" x="74" y="58" width="84" height="17"/>
<textFieldCell key="cell" alignment="left" title="donate-error" drawsBackground="YES" id="Hxk-5m-6NQ">
<font key="font" metaFont="systemMedium" size="13"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="E2A-bR-Me7" firstAttribute="centerX" secondItem="3uK-eO-TcT" secondAttribute="centerX" id="2vw-3v-xBM"/>
<constraint firstAttribute="trailing" secondItem="E2A-bR-Me7" secondAttribute="trailing" constant="100" id="BlZ-i8-jaF"/>
<constraint firstAttribute="bottom" secondItem="E2A-bR-Me7" secondAttribute="bottom" constant="50" id="M9l-9k-0VE"/>
<constraint firstItem="E2A-bR-Me7" firstAttribute="centerY" secondItem="3uK-eO-TcT" secondAttribute="centerY" id="SJ6-ae-jQf"/>
<constraint firstItem="bXO-nx-K3f" firstAttribute="leading" secondItem="Z3j-yD-7xa" secondAttribute="leading" constant="16" id="VcV-Nc-ter"/>
<constraint firstItem="E2A-bR-Me7" firstAttribute="leading" secondItem="Z3j-yD-7xa" secondAttribute="leading" constant="100" id="fGX-Sq-opK"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="3uK-eO-TcT" secondAttribute="trailing" constant="16" id="hSv-y4-EXk"/>
<constraint firstItem="3uK-eO-TcT" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Z3j-yD-7xa" secondAttribute="leading" constant="16" id="hTs-8t-V5s"/>
<constraint firstItem="bch-TQ-lJc" firstAttribute="top" secondItem="Z3j-yD-7xa" secondAttribute="top" constant="16" id="hdL-dp-n0p"/>
<constraint firstItem="bXO-nx-K3f" firstAttribute="top" secondItem="Z3j-yD-7xa" secondAttribute="top" constant="12" id="j5I-CN-UJD"/>
<constraint firstAttribute="trailing" secondItem="bch-TQ-lJc" secondAttribute="trailing" constant="16" id="rRf-qq-P7G"/>
<constraint firstItem="bch-TQ-lJc" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="bXO-nx-K3f" secondAttribute="trailing" constant="32" id="uO5-sb-fQB"/>
<constraint firstItem="E2A-bR-Me7" firstAttribute="top" secondItem="Z3j-yD-7xa" secondAttribute="top" constant="82" id="xHG-uf-eaf"/>
</constraints>
</customView>
<connections>
<outlet property="errorMessage" destination="3uK-eO-TcT" id="8PG-6n-cfA"/>
<outlet property="progressIndicator" destination="E2A-bR-Me7" id="6tU-D7-Ded"/>
</connections>
</viewController>
<customObject id="vHi-dq-1X0" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="883" y="1114"/>
</scene>
<!--Donate Container View Controller-->
<scene sceneID="4f0-FV-KFt">
<objects>
<tabViewController selectedTabViewItemIndex="0" tabStyle="unspecified" canPropagateSelectedChildViewControllerTitle="NO" id="1sh-YF-bwE" customClass="DonateContainerViewController" customModule="PiPer" customModuleProvider="target" sceneMemberID="viewController">
<tabViewItems>
<tabViewItem id="FNZ-vA-ksg">
<nil key="label"/>
</tabViewItem>
<tabViewItem id="yts-dh-y6U">
<nil key="label"/>
</tabViewItem>
</tabViewItems>
<viewControllerTransitionOptions key="transitionOptions"/>
<tabView key="tabView" type="noTabsNoBorder" id="wNE-Ok-s1w">
<rect key="frame" x="0.0" y="0.0" width="440" height="300"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<font key="font" metaFont="message"/>
<connections>
<outlet property="delegate" destination="1sh-YF-bwE" id="AYb-DG-aSi"/>
</connections>
</tabView>
<connections>
<outlet property="donateProgressViewTabItem" destination="FNZ-vA-ksg" id="6Rv-TM-Xbs"/>
<outlet property="donateViewTabItem" destination="yts-dh-y6U" id="ssm-6E-EOf"/>
<outlet property="tabView" destination="wNE-Ok-s1w" id="4Hw-fF-nhC"/>
<segue destination="xq6-KE-CKE" kind="relationship" relationship="tabItems" id="ows-ED-wFf"/>
<segue destination="GR1-9j-nxR" kind="relationship" relationship="tabItems" id="W0l-7m-cHu"/>
</connections>
</tabViewController>
<customObject id="4GA-r3-EqO" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="708" y="688"/>
</scene>
<!--Donate View Controller-->
<scene sceneID="SK0-Zh-jDO">
<objects>
<userDefaultsController representsSharedInstance="YES" id="w8N-nO-ksB"/>
<customObject id="x5G-oc-Cl6" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<viewController id="GR1-9j-nxR" customClass="DonateViewController" customModule="PiPer" customModuleProvider="target" sceneMemberID="viewController">
<customView key="view" id="jce-U3-f7Q" userLabel="Donate View">
<rect key="frame" x="0.0" y="0.0" width="290" height="211"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="700" verticalHuggingPriority="700" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="Qla-Ap-0TG" customClass="LocalizedTextField" customModule="PiPer" customModuleProvider="target">
<rect key="frame" x="14" y="177" width="214" height="22"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="left" title="donate" id="5EO-9z-Dxr">
<font key="font" metaFont="system" size="18"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="fkf-qb-CLL">
<rect key="frame" x="258" y="179" width="16" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="Ib1-Px-Ar5"/>
<constraint firstAttribute="width" constant="16" id="ZLd-K6-ZMG"/>
</constraints>
<buttonCell key="cell" type="bevel" bezelStyle="rounded" image="NSStopProgressTemplate" imagePosition="only" alignment="center" imageScaling="proportionallyUpOrDown" inset="2" id="gLf-Cj-g0U">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="dismissClickedWithSender:" target="GR1-9j-nxR" id="Old-11-Q0X"/>
</connections>
</button>
<scrollView borderType="none" horizontalLineScroll="41" horizontalPageScroll="10" verticalLineScroll="41" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="3xY-5n-Vbh">
<rect key="frame" x="20" y="57" width="250" height="100"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="gfE-sr-B6p">
<rect key="frame" x="0.0" y="0.0" width="250" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="none" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="41" rowSizeStyle="automatic" usesAutomaticRowHeights="YES" viewBased="YES" floatsGroupRows="NO" id="olo-kf-cLh">
<rect key="frame" x="0.0" y="0.0" width="250" height="100"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<color key="gridColor" red="1" green="1" blue="1" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<tableColumns>
<tableColumn identifier="DonationView" editable="NO" width="250" minWidth="40" maxWidth="1000" id="JyQ-k0-fgZ" userLabel="Table Column">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="Mzk-zH-z2o">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES"/>
<prototypeCellViews>
<tableCellView identifier="DonationView" id="ZU1-3P-6SN">
<rect key="frame" x="0.0" y="0.0" width="250" height="41"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button horizontalHuggingPriority="600" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sog-KQ-GEk">
<rect key="frame" x="155" y="3" width="81" height="32"/>
<buttonCell key="cell" type="push" title="[price]" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="8UV-Mq-9Dr">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="buyClickedWithSender:" target="GR1-9j-nxR" id="IIx-8J-Ntq"/>
<binding destination="ZU1-3P-6SN" name="title" keyPath="objectValue.price" id="iFQ-vj-MH6"/>
</connections>
</button>
<textField horizontalHuggingPriority="600" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Hr1-xK-qgu">
<rect key="frame" x="41" y="12" width="83" height="17"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="[donate title]" id="udR-9h-GKY">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="ZU1-3P-6SN" name="value" keyPath="objectValue.name" id="47R-DM-6Jh"/>
</connections>
</textField>
<textField horizontalHuggingPriority="600" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="b0V-JY-XrR">
<rect key="frame" x="18" y="12" width="23" height="17"/>
<constraints>
<constraint firstAttribute="height" constant="17" id="QdU-dF-gV5"/>
<constraint firstAttribute="width" constant="19" id="mmi-Q8-YXX"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="🙂" id="43s-1k-v0h">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="ZU1-3P-6SN" name="value" keyPath="objectValue.emoticon" id="hk1-FH-AEy"/>
</connections>
</textField>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="sog-KQ-GEk" secondAttribute="trailing" constant="20" id="3dr-fi-PSr"/>
<constraint firstItem="Hr1-xK-qgu" firstAttribute="leading" secondItem="b0V-JY-XrR" secondAttribute="trailing" constant="4" id="NwT-BL-sll"/>
<constraint firstItem="sog-KQ-GEk" firstAttribute="top" secondItem="ZU1-3P-6SN" secondAttribute="top" constant="10" id="Ugx-5j-kJ5"/>
<constraint firstAttribute="bottom" secondItem="sog-KQ-GEk" secondAttribute="bottom" constant="10" id="X4t-gu-0b6"/>
<constraint firstItem="b0V-JY-XrR" firstAttribute="leading" secondItem="ZU1-3P-6SN" secondAttribute="leading" constant="20" id="bch-fC-0rz"/>
<constraint firstItem="Hr1-xK-qgu" firstAttribute="centerY" secondItem="sog-KQ-GEk" secondAttribute="centerY" id="ozX-67-q6l"/>
<constraint firstItem="b0V-JY-XrR" firstAttribute="centerY" secondItem="sog-KQ-GEk" secondAttribute="centerY" id="xyO-Jl-lyi"/>
<constraint firstItem="sog-KQ-GEk" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Hr1-xK-qgu" secondAttribute="trailing" constant="20" id="yI5-rB-nOq"/>
</constraints>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
<connections>
<binding destination="GR1-9j-nxR" name="content" keyPath="self.donationProducts" id="tGM-Nk-kgc"/>
</connections>
</tableView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</clipView>
<constraints>
<constraint firstAttribute="height" constant="100" id="0zv-4R-5jc"/>
<constraint firstAttribute="width" constant="250" id="cl8-we-9Se"/>
</constraints>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="fcd-Ce-3EI">
<rect key="frame" x="-100" y="-100" width="228" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="gac-6B-vof">
<rect key="frame" x="-100" y="-100" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="h03-80-d3t">
<rect key="frame" x="59" y="20" width="172" height="17"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pBV-ld-iOi" customClass="LocalizedTextField" customModule="PiPer" customModuleProvider="target">
<rect key="frame" x="-2" y="0.0" width="97" height="17"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="justified" title="total-donations" id="x3s-bb-ehg">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="iQm-Zi-kbg">
<rect key="frame" x="99" y="0.0" width="75" height="17"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="[total price]" id="rsy-PF-Hcd">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="iQm-Zi-kbg" secondAttribute="bottom" id="1EG-C8-Id8"/>
<constraint firstItem="pBV-ld-iOi" firstAttribute="top" secondItem="h03-80-d3t" secondAttribute="top" id="DiX-q8-tQ0"/>
<constraint firstItem="iQm-Zi-kbg" firstAttribute="top" secondItem="h03-80-d3t" secondAttribute="top" id="Grc-VA-qu3"/>
<constraint firstItem="iQm-Zi-kbg" firstAttribute="leading" secondItem="pBV-ld-iOi" secondAttribute="trailing" constant="8" id="Jjf-dV-WJ0"/>
<constraint firstAttribute="bottom" secondItem="pBV-ld-iOi" secondAttribute="bottom" id="OyK-Oz-Bdw"/>
<constraint firstItem="pBV-ld-iOi" firstAttribute="leading" secondItem="h03-80-d3t" secondAttribute="leading" id="SI9-EH-ZqN"/>
<constraint firstAttribute="trailing" secondItem="iQm-Zi-kbg" secondAttribute="trailing" id="xYO-fk-ic9"/>
</constraints>
</customView>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="LkE-AY-8Be" customClass="ConfettiView" customModule="PiPer" customModuleProvider="target">
<rect key="frame" x="0.0" y="211" width="0.0" height="0.0"/>
<constraints>
<constraint firstAttribute="width" placeholder="YES" id="odk-ek-PXI"/>
<constraint firstAttribute="height" placeholder="YES" id="rAx-rk-ghc"/>
</constraints>
</customView>
</subviews>
<constraints>
<constraint firstItem="fkf-qb-CLL" firstAttribute="top" secondItem="jce-U3-f7Q" secondAttribute="top" constant="16" id="1vq-gO-4zV"/>
<constraint firstItem="fkf-qb-CLL" firstAttribute="leading" secondItem="Qla-Ap-0TG" secondAttribute="trailing" constant="32" id="5xK-PD-qqj"/>
<constraint firstItem="3xY-5n-Vbh" firstAttribute="centerX" secondItem="jce-U3-f7Q" secondAttribute="centerX" id="6IE-e6-52M"/>
<constraint firstAttribute="trailing" secondItem="fkf-qb-CLL" secondAttribute="trailing" constant="16" id="6ef-vV-JKx"/>
<constraint firstItem="h03-80-d3t" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="jce-U3-f7Q" secondAttribute="leading" constant="20" id="8Jn-IB-ZqE"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="3xY-5n-Vbh" secondAttribute="trailing" constant="20" id="BUz-au-06b"/>
<constraint firstAttribute="bottom" secondItem="h03-80-d3t" secondAttribute="bottom" constant="20" id="EKh-7R-Bb6"/>
<constraint firstAttribute="trailing" secondItem="LkE-AY-8Be" secondAttribute="trailing" priority="10" id="GR9-El-pSz"/>
<constraint firstItem="Qla-Ap-0TG" firstAttribute="top" secondItem="jce-U3-f7Q" secondAttribute="top" constant="12" id="Mxa-mZ-LYM"/>
<constraint firstItem="h03-80-d3t" firstAttribute="top" secondItem="3xY-5n-Vbh" secondAttribute="bottom" constant="20" id="Nu0-Dh-Q4Z"/>
<constraint firstItem="3xY-5n-Vbh" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="jce-U3-f7Q" secondAttribute="leading" constant="20" id="RRH-PB-gLG"/>
<constraint firstAttribute="bottom" secondItem="LkE-AY-8Be" secondAttribute="bottom" priority="10" id="dec-YN-wsX"/>
<constraint firstItem="LkE-AY-8Be" firstAttribute="top" secondItem="jce-U3-f7Q" secondAttribute="top" priority="100" id="gVA-aT-I3V"/>
<constraint firstItem="3xY-5n-Vbh" firstAttribute="top" secondItem="Qla-Ap-0TG" secondAttribute="bottom" constant="20" id="h9U-lb-nmw"/>
<constraint firstItem="Qla-Ap-0TG" firstAttribute="leading" secondItem="jce-U3-f7Q" secondAttribute="leading" constant="16" id="oSs-lE-Sua"/>
<constraint firstItem="LkE-AY-8Be" firstAttribute="leading" secondItem="jce-U3-f7Q" secondAttribute="leading" priority="20" id="sLZ-FG-Ydl"/>
<constraint firstItem="h03-80-d3t" firstAttribute="centerX" secondItem="jce-U3-f7Q" secondAttribute="centerX" id="tc6-rX-HfD"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="h03-80-d3t" secondAttribute="trailing" constant="20" id="wzf-a8-HBd"/>
</constraints>
</customView>
<connections>
<outlet property="confettiView" destination="LkE-AY-8Be" id="z2W-cQ-VLV"/>
<outlet property="donationTableView" destination="olo-kf-cLh" id="fLx-Vx-APx"/>
<outlet property="tableViewHeightConstraint" destination="0zv-4R-5jc" id="m53-Ot-Emw"/>
<outlet property="tableViewWidthConstraint" destination="cl8-we-9Se" id="Y1e-W1-pqX"/>
<outlet property="totalDonations" destination="iQm-Zi-kbg" id="cQ3-32-YCb"/>
</connections>
</viewController>
</objects>
<point key="canvasLocation" x="514" y="1138"/>
</scene>
</scenes>
<resources>
<image name="Icon" width="512" height="512"/>
<image name="NSCaution" width="128" height="128"/>
<image name="NSStopProgressTemplate" width="11" height="11"/>
</resources>
</document>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.icloud-container-identifiers</key>
<array/>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(APP_GROUP_ID)</string>
</array>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,23 @@
//
// ResourceHelper.swift
// PiPer
//
// Created by Adam Marcus on 13/10/2018.
// Copyright © 2018 Adam Marcus. All rights reserved.
//
import Foundation
class ResourceHelper {
static let extensionBundleURL: URL? = {
guard let pluginURL = Bundle.main.builtInPlugInsURL else {
return nil
}
guard let extensionBundle = Bundle(url: pluginURL.appendingPathComponent("PiPerExt.appex")) else {
return nil
}
return extensionBundle.resourceURL
}()
}

View File

@ -0,0 +1,63 @@
//
// ViewController.swift
// PiPer App
//
// Created by Adam Marcus on 19/07/2018.
// Copyright © 2018 Adam Marcus. All rights reserved.
//
import Cocoa
import SafariServices
class ViewController: NSViewController {
let extensionId = String(cString:EXTENSION_BUNDLE_ID)
@IBOutlet var viewContainer: NSVisualEffectView!
@IBOutlet var mainView: NSView!
@IBOutlet var extensionDisabledView: NSView!
override func viewDidLoad() {
super.viewDidLoad()
// Display the main view by default
viewContainer.addSubview(mainView)
// Poll every second for Safari extension state changes
Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.checkExtensionState), userInfo: nil, repeats: true).fire()
}
// Display the requested view
func showView(_ newView: NSView) {
let oldView = viewContainer.subviews.first
if oldView == newView {
return
}
// Avoid animating the transition due to NSButton vibrancy rendering glitch in dark mode
// viewContainer.animator().replaceSubview(oldView!, with:newView)
viewContainer.replaceSubview(oldView!, with:newView)
}
// Check extension state and show extension disabled view if necessary
@objc func checkExtensionState() {
SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionId) { state, error in
DispatchQueue.main.async {
if let status = state?.isEnabled {
self.showView(status ? self.mainView : self.extensionDisabledView)
}
}
}
}
@IBAction func clickedEnableExtension(sender: NSButton) {
SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionId)
}
@IBAction func clickedReportBug(sender: NSButton) {
if let url = URL(string: "https://github.com/amarcu5/PiPer/issues") {
NSWorkspace.shared.open(url)
}
}
}

View File

@ -0,0 +1,15 @@
//
// Defines.c
// PiPer
//
// Created by Adam Marcus on 10/08/2018.
// Copyright © 2018 Adam Marcus. All rights reserved.
//
#define QUOTE(str) #str
#define EXPAND_AND_QUOTE(str) QUOTE(str)
const char * APP_GROUP_ID = EXPAND_AND_QUOTE(APP_GROUP_ID_CONST);
const char * APP_BUNDLE_ID = EXPAND_AND_QUOTE(APP_BUNDLE_ID_CONST);
const char * EXTENSION_BUNDLE_ID = EXPAND_AND_QUOTE(EXTENSION_BUNDLE_ID_CONST);

View File

@ -0,0 +1,12 @@
//
// Defines.h
// PiPer
//
// Created by Adam Marcus on 10/08/2018.
// Copyright © 2018 Adam Marcus. All rights reserved.
//
extern const char * APP_GROUP_ID;
extern const char * APP_BUNDLE_ID;
extern const char * EXTENSION_BUNDLE_ID;

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>PiPer</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>0.0.0</string>
<key>CFBundleVersion</key>
<string>0</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.Safari.extension</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).SafariExtensionHandler</string>
<key>SFSafariContentScript</key>
<array>
<dict>
<key>Script</key>
<string>scripts/main.js</string>
</dict>
</array>
<key>SFSafariExtensionBundleIdentifiersToUninstall</key>
<array>
<string>com.amarcus.safari.piper</string>
</array>
<key>SFSafariWebsiteAccess</key>
<dict>
<key>Level</key>
<string>All</string>
</dict>
</dict>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 Adam Marcus. All rights reserved.</string>
<key>NSHumanReadableDescription</key>
<string>Adds Picture in Picture functionality to Youtube, Netflix, Amazon Video, Twitch, and more!</string>
</dict>
</plist>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(APP_GROUP_ID)</string>
</array>
</dict>
</plist>

Some files were not shown because too many files have changed in this diff Show More