@charset "UTF-8";
/***
 *                                 /$$$$$$  /$$
 *                                /$$__  $$|__/
 *      ______ _       _     _   | $$  \__/ /$$ /$$$$$$$$  /$$$$$$
 *     |  ____| |     (_)   | |  |  $$$$$$ | $$|____ /$$/ /$$__  $$
 *     | |___ | |_   _ _  __| |  \____  $$| $$   /$$$$/ | $$$$$$$$
 *     |  ___|| | | | | |/ _` |   /$$  \ $$| $$  /$$__/  | $$_____/
 *     | |    | | |_| | | (_| |  |  $$$$$$/| $$ /$$$$$$$$|  $$$$$$$
 *     |_|    |_|\__,_|_|\__,_|  \______/ |__/|________/ \_______/
 *
 **/
// 

// ** Break Points *************************************************************
$small-phone-width:  320px !default;
$small-phone-height: 586px !default;
$phone-width:        360px !default;
$phone-height:       640px !default;
$high-phone-width:   414px !default;
$high-phone-height:  896px !default;

$tablet-width:        768px !default;
$tablet-height:      1024px !default;
$high-tablet-width:  1024px !default;
$high-tablet-height: 1366px !default;

$laptop-width:      1280px !default;
$laptop-height:      720px !default;
$high-laptop-width: 1600px !default;
$high-laptop-height: 900px !default;

$desktop-width:       1920px !default;
$desktop-height:      1080px !default;
$high-desktop-width:  3840px !default;
$high-desktop-height: 2160px !default;

$breakpoints: (        // px
  small-phone:  $small-phone-width,
  phone:        $phone-width,
  high-phone:   $high-phone-width,
  tablet:       $tablet-width,
  high-tablet:  $high-tablet-width,
  laptop:       $laptop-width,
  high-laptop:  $high-laptop-width,
  desktop:      $desktop-width,
  high-desktop: $high-desktop-width
) !default;

$breakpoints-height: ( // px
  small-phone:  $small-phone-height,
  phone:        $phone-height,
  high-phone:   $high-phone-height,
  tablet:       $tablet-height,
  high-tablet:  $high-tablet-height,
  laptop:       $laptop-height,
  high-laptop:  $high-laptop-height,
  desktop:      $desktop-height,
  high-desktop: $high-desktop-height
) !default;

$screen-sizes: (       // inch
  small-phone:  4,
  phone:        4.7,
  high-phone:   5.8,
  tablet:       9.7,
  high-tablet:  12.9,
  laptop:       13,
  high-laptop:  14,
  desktop:      24,
  high-desktop: 32
) !default;

$screen-distances: (   // 10cm
  small-phone:  3,
  phone:        3,
  high-phone:   3,
  tablet:       4,
  high-tablet:  4,
  laptop:       5,
  high-laptop:  5,
  desktop:      6,
  high-desktop: 6
) !default;

// Alias
$small-phone:  $small-phone-width   !default;
$phone:        $phone-width        !default;
$high-phone:   $high-phone-width   !default;
$tablet:       $tablet-width       !default;
$high-tablet:  $high-tablet-width  !default;
$laptop:       $laptop-width       !default;
$high-laptop:  $high-laptop-width  !default;
$desktop:      $desktop-width      !default;
$high-desktop: $high-desktop-width !default;

// == Validation ===============================================================
$CACHED-BREAKPOINTS:        $breakpoints        !default;
$CACHED-BREAKPOINTS-HEIGHT: $breakpoints-height !default;
$CACHED-SCREEN-SIZES:       $screen-sizes       !default;
$CACHED-SCREEN-DISTANCES:   $screen-distances   !default;

@function check-breakpoints($criteria-name, $others-name, $criteria-keys, $others-keys) {
  $validate-breakpoints: true;

  $criteria-length: length($criteria-keys);
  $others-length:   length($others-keys);
  @each $i in range($others-length) {
    $other-name:   nth($others-name, $i);
    $other-keys:   nth($others-keys, $i);
    $other-length: length($other-keys);

    // Length Check
    $validate-breakpoints: $validate-breakpoints and $criteria-length == $other-length;
    @if not $validate-breakpoints {
      @error "#{$criteria-name}'s length doesn't same #{$other-name}'s length";
    }

    // Key Check
    $validate-breakpoints: $validate-breakpoints and $criteria-keys == $other-keys;
    @if not $validate-breakpoints {
      @error "#{$criteria-name}'s keys doesn't same #{$other-name}'s keys";
    }
  }
  @return $validate-breakpoints;
}

// -- Interface --
@function validate-breakpoints() {
  $validate-breakpoints: true;

  // Check Cache
  @if not ($CACHED-BREAKPOINTS  == $breakpoints  and $CACHED-BREAKPOINTS-HEIGHT == $breakpoints-height and
           $CACHED-SCREEN-SIZES == $screen-sizes and $CACHED-SCREEN-DISTANCES   == $screen-distances) {
    $break-keys: map($breakpoints $breakpoints-height $screen-sizes $screen-distances,
                     map-keys list-sort);
    $breakpoints-keys:        nth($break-keys, 1);
    $breakpoints-height-keys: nth($break-keys, 2);
    $screen-sizes-keys:       nth($break-keys, 3);
    $screen-distances-keys:   nth($break-keys, 4);

    // Check Validate
    $others-name: "$breakpoints-height"    "$screen-sizes"    "$screen-distances";
    $others-keys: $breakpoints-height-keys $screen-sizes-keys $screen-distances-keys;
    $validate-breakpoints: check-breakpoints("$breakpoints", $others-name, $breakpoints-keys, $others-keys);

    // Cache Update
    $CACHED-BREAKPOINTS:        $breakpoints        !global;
    $CACHED-BREAKPOINTS-HEIGHT: $breakpoints-height !global;
    $CACHED-SCREEN-SIZES:       $screen-sizes       !global;
    $CACHED-SCREEN-DISTANCES:   $screen-distances   !global;
  }

  @return $validate-breakpoints;
}

@charset "UTF-8";

//     _            _           _                           _ _
//    (_)          | |         | |                         | (_)
//     _ _ __   ___| |_   _  __| | ___   _ __ ___   ___  __| |_  __ _
//    | | '_ \ / __| | | | |/ _` |/ _ \ | '_ ` _ \ / _ \/ _` | |/ _` |
//    | | | | | (__| | |_| | (_| |  __/ | | | | | |  __/ (_| | | (_| |
//    |_|_| |_|\___|_|\__,_|\__,_|\___| |_| |_| |_|\___|\__,_|_|\__,_|
//
//      Simple, elegant and maintainable media queries in Sass
//                        v1.4.9
//
//                http://include-media.com
//
//         Authors: Eduardo Boucas (@eduardoboucas)
//                  Hugo Giraudel (@hugogiraudel)
//
//      This project is licensed under the terms of the MIT license


////
/// include-media library public configuration
/// @author Eduardo Boucas
/// @access public
////


///
/// Creates a list of global breakpoints
///
/// @example scss - Creates a single breakpoint with the label `phone`
///  $breakpoints: ('phone': 320px);
///
$breakpoints: (
  'phone': 320px,
  'tablet': 768px,
  'desktop': 1024px
) !default;


///
/// Creates a list of static expressions or media types
///
/// @example scss - Creates a single media type (screen)
///  $media-expressions: ('screen': 'screen');
///
/// @example scss - Creates a static expression with logical disjunction (OR operator)
///  $media-expressions: (
///    'retina2x': '(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)'
///  );
///
$media-expressions: (
  'screen': 'screen',
  'print': 'print',
  'handheld': 'handheld',
  'landscape': '(orientation: landscape)',
  'portrait': '(orientation: portrait)',
  'retina2x': '(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi), (min-resolution: 2dppx)',
  'retina3x': '(-webkit-min-device-pixel-ratio: 3), (min-resolution: 350dpi), (min-resolution: 3dppx)'
) !default;


///
/// Defines a number to be added or subtracted from each unit when declaring breakpoints with exclusive intervals
///
/// @example scss - Interval for pixels is defined as `1` by default
///  @include media('>128px') {}
///
///  /* Generates: */
///  @media (min-width: 129px) {}
///
/// @example scss - Interval for ems is defined as `0.01` by default
///  @include media('>20em') {}
///
///  /* Generates: */
///  @media (min-width: 20.01em) {}
///
/// @example scss - Interval for rems is defined as `0.1` by default, to be used with `font-size: 62.5%;`
///  @include media('>2.0rem') {}
///
///  /* Generates: */
///  @media (min-width: 2.1rem) {}
///
$unit-intervals: (
  'px': 1,
  'em': 0.01,
  'rem': 0.1,
  '': 0
) !default;

///
/// Defines whether support for media queries is available, useful for creating separate stylesheets
/// for browsers that don't support media queries.
///
/// @example scss - Disables support for media queries
///  $im-media-support: false;
///  @include media('>=tablet') {
///    .foo {
///      color: tomato;
///    }
///  }
///
///  /* Generates: */
///  .foo {
///    color: tomato;
///  }
///
$im-media-support: true !default;

///
/// Selects which breakpoint to emulate when support for media queries is disabled. Media queries that start at or
/// intercept the breakpoint will be displayed, any others will be ignored.
///
/// @example scss - This media query will show because it intercepts the static breakpoint
///  $im-media-support: false;
///  $im-no-media-breakpoint: 'desktop';
///  @include media('>=tablet') {
///    .foo {
///      color: tomato;
///    }
///  }
///
///  /* Generates: */
///  .foo {
///    color: tomato;
///  }
///
/// @example scss - This media query will NOT show because it does not intercept the desktop breakpoint
///  $im-media-support: false;
///  $im-no-media-breakpoint: 'tablet';
///  @include media('>=desktop') {
///    .foo {
///      color: tomato;
///    }
///  }
///
///  /* No output */
///
$im-no-media-breakpoint: 'desktop' !default;

///
/// Selects which media expressions are allowed in an expression for it to be used when media queries
/// are not supported.
///
/// @example scss - This media query will show because it intercepts the static breakpoint and contains only accepted media expressions
///  $im-media-support: false;
///  $im-no-media-breakpoint: 'desktop';
///  $im-no-media-expressions: ('screen');
///  @include media('>=tablet', 'screen') {
///    .foo {
///      color: tomato;
///    }
///  }
///
///   /* Generates: */
///   .foo {
///     color: tomato;
///   }
///
/// @example scss - This media query will NOT show because it intercepts the static breakpoint but contains a media expression that is not accepted
///  $im-media-support: false;
///  $im-no-media-breakpoint: 'desktop';
///  $im-no-media-expressions: ('screen');
///  @include media('>=tablet', 'retina2x') {
///    .foo {
///      color: tomato;
///    }
///  }
///
///  /* No output */
///
$im-no-media-expressions: ('screen', 'portrait', 'landscape') !default;

////
/// Cross-engine logging engine
/// @author Hugo Giraudel
/// @access private
////


///
/// Log a message either with `@error` if supported
/// else with `@warn`, using `feature-exists('at-error')`
/// to detect support.
///
/// @param {String} $message - Message to log
///
@function im-log($message) {
  @if feature-exists('at-error') {
    @error $message;
  } @else {
    @warn $message;
    $_: noop();
  }

  @return $message;
}


///
/// Wrapper mixin for the log function so it can be used with a more friendly
/// API than `@if im-log('..') {}` or `$_: im-log('..')`. Basically, use the function
/// within functions because it is not possible to include a mixin in a function
/// and use the mixin everywhere else because it's much more elegant.
///
/// @param {String} $message - Message to log
///
@mixin log($message) {
  @if im-log($message) {}
}


///
/// Function with no `@return` called next to `@warn` in Sass 3.3
/// to trigger a compiling error and stop the process.
///
@function noop() {}

///
/// Determines whether a list of conditions is intercepted by the static breakpoint.
///
/// @param {Arglist}   $conditions  - Media query conditions
///
/// @return {Boolean} - Returns true if the conditions are intercepted by the static breakpoint
///
@function im-intercepts-static-breakpoint($conditions...) {
  $no-media-breakpoint-value: map-get($breakpoints, $im-no-media-breakpoint);

  @if not $no-media-breakpoint-value {
    @if im-log('`#{$im-no-media-breakpoint}` is not a valid breakpoint.') {}
  }

  @each $condition in $conditions {
    @if not map-has-key($media-expressions, $condition) {
      $operator: get-expression-operator($condition);
      $prefix: get-expression-prefix($operator);
      $value: get-expression-value($condition, $operator);

      // scss-lint:disable SpaceAroundOperator
      @if ($prefix == 'max' and $value <= $no-media-breakpoint-value) or
          ($prefix == 'min' and $value > $no-media-breakpoint-value) {
        @return false;
      }
    } @else if not index($im-no-media-expressions, $condition) {
      @return false;
    }
  }

  @return true;
}

////
/// Parsing engine
/// @author Hugo Giraudel
/// @access private
////


///
/// Get operator of an expression
///
/// @param {String} $expression - Expression to extract operator from
///
/// @return {String} - Any of `>=`, `>`, `<=`, `<`, `≥`, `≤`
///
@function get-expression-operator($expression) {
  @each $operator in ('>=', '>', '<=', '<', '≥', '≤') {
    @if str-index($expression, $operator) {
      @return $operator;
    }
  }

  // It is not possible to include a mixin inside a function, so we have to
  // rely on the `im-log(..)` function rather than the `log(..)` mixin. Because
  // functions cannot be called anywhere in Sass, we need to hack the call in
  // a dummy variable, such as `$_`. If anybody ever raise a scoping issue with
  // Sass 3.3, change this line in `@if im-log(..) {}` instead.
  $_: im-log('No operator found in `#{$expression}`.');
}


///
/// Get dimension of an expression, based on a found operator
///
/// @param {String} $expression - Expression to extract dimension from
/// @param {String} $operator - Operator from `$expression`
///
/// @return {String} - `width` or `height` (or potentially anything else)
///
@function get-expression-dimension($expression, $operator) {
  $operator-index: str-index($expression, $operator);
  $parsed-dimension: str-slice($expression, 0, $operator-index - 1);
  $dimension: 'width';

  @if str-length($parsed-dimension) > 0 {
    $dimension: $parsed-dimension;
  }

  @return $dimension;
}


///
/// Get dimension prefix based on an operator
///
/// @param {String} $operator - Operator
///
/// @return {String} - `min` or `max`
///
@function get-expression-prefix($operator) {
  @return if(index(('<', '<=', '≤'), $operator), 'max', 'min');
}


///
/// Get value of an expression, based on a found operator
///
/// @param {String} $expression - Expression to extract value from
/// @param {String} $operator - Operator from `$expression`
///
/// @return {Number} - A numeric value
///
@function get-expression-value($expression, $operator) {
  $operator-index: str-index($expression, $operator);
  $value: str-slice($expression, $operator-index + str-length($operator));

  @if map-has-key($breakpoints, $value) {
    $value: map-get($breakpoints, $value);
  } @else {
    $value: to-number($value);
  }

  $interval: map-get($unit-intervals, unit($value));

  @if not $interval {
    // It is not possible to include a mixin inside a function, so we have to
    // rely on the `im-log(..)` function rather than the `log(..)` mixin. Because
    // functions cannot be called anywhere in Sass, we need to hack the call in
    // a dummy variable, such as `$_`. If anybody ever raise a scoping issue with
    // Sass 3.3, change this line in `@if im-log(..) {}` instead.
    $_: im-log('Unknown unit `#{unit($value)}`.');
  }

  @if $operator == '>' {
    $value: $value + $interval;
  } @else if $operator == '<' {
    $value: $value - $interval;
  }

  @return $value;
}


///
/// Parse an expression to return a valid media-query expression
///
/// @param {String} $expression - Expression to parse
///
/// @return {String} - Valid media query
///
@function parse-expression($expression) {
  // If it is part of $media-expressions, it has no operator
  // then there is no need to go any further, just return the value
  @if map-has-key($media-expressions, $expression) {
    @return map-get($media-expressions, $expression);
  }

  $operator: get-expression-operator($expression);
  $dimension: get-expression-dimension($expression, $operator);
  $prefix: get-expression-prefix($operator);
  $value: get-expression-value($expression, $operator);

  @return '(#{$prefix}-#{$dimension}: #{$value})';
}

///
/// Slice `$list` between `$start` and `$end` indexes
///
/// @access private
///
/// @param {List} $list - List to slice
/// @param {Number} $start [1] - Start index
/// @param {Number} $end [length($list)] - End index
///
/// @return {List} Sliced list
///
@function slice($list, $start: 1, $end: length($list)) {
  @if length($list) < 1 or $start > $end {
    @return ();
  }

  $result: ();

  @for $i from $start through $end {
    $result: append($result, nth($list, $i));
  }

  @return $result;
}

////
/// String to number converter
/// @author Hugo Giraudel
/// @access private
////


///
/// Casts a string into a number
///
/// @param {String | Number} $value - Value to be parsed
///
/// @return {Number}
///
@function to-number($value) {
  @if type-of($value) == 'number' {
    @return $value;
  } @else if type-of($value) != 'string' {
    $_: im-log('Value for `to-number` should be a number or a string.');
  }

  $first-character: str-slice($value, 1, 1);
  $result: 0;
  $digits: 0;
  $minus: ($first-character == '-');
  $numbers: ('0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9);

  // Remove +/- sign if present at first character
  @if ($first-character == '+' or $first-character == '-') {
    $value: str-slice($value, 2);
  }

  @for $i from 1 through str-length($value) {
    $character: str-slice($value, $i, $i);

    @if not (index(map-keys($numbers), $character) or $character == '.') {
      @return to-length(if($minus, -$result, $result), str-slice($value, $i))
    }

    @if $character == '.' {
      $digits: 1;
    } @else if $digits == 0 {
      $result: $result * 10 + map-get($numbers, $character);
    } @else {
      $digits: $digits * 10;
      $result: $result + map-get($numbers, $character) / $digits;
    }
  }

  @return if($minus, -$result, $result);
}


///
/// Add `$unit` to `$value`
///
/// @param {Number} $value - Value to add unit to
/// @param {String} $unit - String representation of the unit
///
/// @return {Number} - `$value` expressed in `$unit`
///
@function to-length($value, $unit) {
  $units: ('px': 1px, 'cm': 1cm, 'mm': 1mm, '%': 1%, 'ch': 1ch, 'pc': 1pc, 'in': 1in, 'em': 1em, 'rem': 1rem, 'pt': 1pt, 'ex': 1ex, 'vw': 1vw, 'vh': 1vh, 'vmin': 1vmin, 'vmax': 1vmax);

  @if not index(map-keys($units), $unit) {
    $_: im-log('Invalid unit `#{$unit}`.');
  }

  @return $value * map-get($units, $unit);
}

///
/// This mixin aims at redefining the configuration just for the scope of
/// the call. It is helpful when having a component needing an extended
/// configuration such as custom breakpoints (referred to as tweakpoints)
/// for instance.
///
/// @author Hugo Giraudel
///
/// @param {Map} $tweakpoints [()] - Map of tweakpoints to be merged with `$breakpoints`
/// @param {Map} $tweak-media-expressions [()] - Map of tweaked media expressions to be merged with `$media-expression`
///
/// @example scss - Extend the global breakpoints with a tweakpoint
///  @include media-context(('custom': 678px)) {
///    .foo {
///      @include media('>phone', '<=custom') {
///       // ...
///      }
///    }
///  }
///
/// @example scss - Extend the global media expressions with a custom one
///  @include media-context($tweak-media-expressions: ('all': 'all')) {
///    .foo {
///      @include media('all', '>phone') {
///       // ...
///      }
///    }
///  }
///
/// @example scss - Extend both configuration maps
///  @include media-context(('custom': 678px), ('all': 'all')) {
///    .foo {
///      @include media('all', '>phone', '<=custom') {
///       // ...
///      }
///    }
///  }
///
@mixin media-context($tweakpoints: (), $tweak-media-expressions: ()) {
  // Save global configuration
  $global-breakpoints: $breakpoints;
  $global-media-expressions: $media-expressions;

  // Update global configuration
  $breakpoints: map-merge($breakpoints, $tweakpoints) !global;
  $media-expressions: map-merge($media-expressions, $tweak-media-expressions) !global;

  @content;

  // Restore global configuration
  $breakpoints: $global-breakpoints !global;
  $media-expressions: $global-media-expressions !global;
}

////
/// include-media public exposed API
/// @author Eduardo Boucas
/// @access public
////


///
/// Generates a media query based on a list of conditions
///
/// @param {Arglist}   $conditions  - Media query conditions
///
/// @example scss - With a single set breakpoint
///  @include media('>phone') { }
///
/// @example scss - With two set breakpoints
///  @include media('>phone', '<=tablet') { }
///
/// @example scss - With custom values
///  @include media('>=358px', '<850px') { }
///
/// @example scss - With set breakpoints with custom values
///  @include media('>desktop', '<=1350px') { }
///
/// @example scss - With a static expression
///  @include media('retina2x') { }
///
/// @example scss - Mixing everything
///  @include media('>=350px', '<tablet', 'retina3x') { }
///
@mixin media($conditions...) {
  // scss-lint:disable SpaceAroundOperator
  @if ($im-media-support and length($conditions) == 0) or
      (not $im-media-support and im-intercepts-static-breakpoint($conditions...)) {
    @content;
  } @else if ($im-media-support and length($conditions) > 0) {
    @media #{unquote(parse-expression(nth($conditions, 1)))} {
      // Recursive call
      @include media(slice($conditions, 2)...) {
        @content;
      }
    }
  }
}

// MathSass v0.11.0
// https://github.com/terkel/mathsass
//
// Copyright (c) 2014 Takeru Suzuki - http://terkel.jp/
// Licensed under the MIT license - http://opensource.org/licenses/MIT

// Constants
// Constants
$E:     2.718281828459045;
$PI:    3.141592653589793;
$LN2:   0.6931471805599453;
$SQRT2: 1.4142135623730951;


// Exponentiation
// Returns base to the exponent power.
// @param {Number} $base The base number
// @param {Number} $exp The exponent to which to raise base
// @return {Number}
// @example
//     pow(4, 2)   // 16
//     pow(4, -2)  // 0.0625
//     pow(4, 0.2) // 1.31951
@function pow ($base, $exp) {
    @if $exp == floor($exp) {
        $r: 1;
        $s: 0;
        @if $exp < 0 {
            $exp: $exp * -1;
            $s: 1;
        }
        @while $exp > 0 {
            @if $exp % 2 == 1 {
                $r: $r * $base;
            }
            $exp: floor($exp * 0.5);
            $base: $base * $base;
        }
        @return if($s != 0, 1 / $r, $r);
    } @else if $base == 0 and $exp > 0 {
        @return 0;
    } @else {
        $expint: floor($exp);
        $r1: pow($base, $expint);
        $r2: _exp(log($base) * ($exp - $expint));
        @return $r1 * $r2;
    }
}

// A good approximation for $x close to 0.
@function _exp ($x) {
    $ret: 0;
    $i: 1;
    @for $n from 0 to 24 {
        $ret: $ret + $i;
        $i: $i * $x / ($n + 1);
    }
    @return $ret;
}


// Factorial
// Returns the factorial of a non-negative integer.
// @param {Number} $x A non-negative integer.
// @return {Number}
// @example
//     fact(0) // 1
//     fact(8) // 40320
@function fact ($x) {
    @if $x < 0 or  $x != floor($x) {
        @warn "Argument for `fact()` must be a positive integer.";
        @return null;
    }
    $ret: 1;
    @while $x > 0 {
        $ret: $ret * $x;
        $x: $x - 1;
    }
    @return $ret;
}


// Square root
// Returns the square root of a number.
// @param {Number} $x
// @example
//     sqrt(2) // 1.41421
//     sqrt(5) // 2.23607
@function sqrt ($x) {
    @if $x < 0 {
        @warn "Argument for `sqrt()` must be a positive number.";
        @return null;
    }
    $ret: 1;
    @for $i from 1 through 24 {
        $ret: $ret - ($ret * $ret - $x) / (2 * $ret);
    }
    @return $ret;
}


// Exponential function
// Returns E^x, where x is the argument, and E is Euler's constant, the base of the natural logarithms.
// @param {Number} $x
// @example
//     exp(1)  // 2.71828
//     exp(-1) // 0.36788
@function exp ($x) {
    @return pow($E, $x);
}

// Returns a two-element list containing the normalized fraction and exponent of number.
// @param {Number} $x
// @return {List} fraction, exponent
@function frexp ($x) {
    $exp: 0;
    @if $x < 0 {
        $x: $x * -1;
    }
    @if $x < 0.5 {
        @while $x < 0.5 {
            $x: $x * 2;
            $exp: $exp - 1;
        }
    } @else if $x >= 1 {
        @while $x >= 1 {
            $x: $x / 2;
            $exp: $exp + 1;
        }
    }
    @return $x, $exp;
}

// Returns $x * 2^$exp
// @param {Number} $x
// @param {Number} $exp
@function ldexp ($x, $exp) {
    $b: if($exp >= 0, 2, 1 / 2);
    @if $exp < 0 {
        $exp: $exp * -1;
    }
    @while $exp > 0 {
        @if $exp % 2 == 1 {
            $x: $x * $b;
        }
        $b: $b * $b;
        $exp: floor($exp * 0.5);
    }
    @return $x;
}


// Logarithms
// Returns the natural logarithm of a number.
// @param {Number} $x
// @param {Number} $b The base number
// @example
//     log(2)     // 0.69315
//     log(10)    // 2.30259
//     log(2, 10) // 0.30103
@function log ($x, $b: null) {
    @if $b != null {
        @return log($x) / log($b);
    }

    @if $x <= 0 {
        @return 0 / 0;
    }
    $k: nth(frexp($x / $SQRT2), 2);
    $x: $x / ldexp(1, $k);

    @return $LN2 * $k + _log($x);
}

// a good aproximation for $x close to 1
@function _log ($x) {
    $x: ($x - 1) / ($x + 1);
    $x2: $x * $x;
    $i: 1;
    $s: $x;
    $sp: null;
    @while $sp != $s {
        $x: $x * $x2;
        $i: $i + 2;
        $sp: $s;
        $s: $s + $x / $i;
    }
    @return 2 * $s;
}


// Trigonometric functions
// Returns the sine of a number.
// @param {Number} $x A number in rad or deg. Assuming unitless number to be in rad.
// @example
//     sin(1.0472) // 0.86603
//     sin(60deg)  // 0.86603
@function sin ($x) {
    $x: unitless-rad($x);
    @return cos($x - $PI / 2);
}

// Returns the cosine of a number.
// @param {Number} $x A number in rad or deg. Assuming unitless number to be in rad.
// @example
//     cos(0.7854) // 0.70711
//     cos(45deg)  // 0.70711
@function cos ($x) {
    $x: unitless-rad($x) % ($PI * 2);
    $ret: 1;
    $i: 1;
    @for $n from 1 to 24 {
        $i: $i * -1 * $x * $x / (2 * $n) / (2 * $n - 1);
        $ret: $ret + $i;
    }
    @return $ret;
}

// Returns the tangent of a number.
// @param {Number} $x A number in rad or deg. Assuming unitless number to be in rad.
// @example
//     tan(0.5236) // 0.57735
//     tan(30deg)  // 0.57735
@function tan ($x) {
    $x: unitless-rad($x);
    @return sin($x) / cos($x);
}

// Returns the cosecant of a number.
// @param {Number} $x A number in rad or deg. Assuming unitless number to be in rad.
// @example
//     csc(1.0472) // 1.1547
//     csc(60deg)  // 1.1547
@function csc ($x) {
    $x: unitless-rad($x);
    @return 1 / sin($x);
}

// Returns the secant of a number.
// @param {Number} $x A number in rad or deg. Assuming unitless number to be in rad.
// @example
//     sec(0.7854) // 1.41422
//     sec(45deg)  // 1.41422
@function sec ($x) {
    $x: unitless-rad($x);
    @return 1 / cos($x);
}

// Returns the cotangent of a number.
// @param {Number} $x A number in rad or deg. Assuming unitless number to be in rad.
// @example
//     cot(0.5236) // 1.73205
//     cot(30deg)  // 1.73205
@function cot ($x) {
    $x: unitless-rad($x);
    @return 1 / tan($x);
}


// Inverse trigonometric functions
// Returns the arcsine of a number.
// @param {Number} $x A number between -1 and 1.
// @example
//     asin(0.1) // 0.10017
//     asin(-1)  // -1.5708
@function asin ($x) {
    @if $x > 1 or $x < -1 {
        @warn "Argument for `asin()` must be a number between -1 and 1";
        @return null;
    }
    @return atan($x / sqrt(1 - $x * $x));
}

// Returns the arccosine of a number.
// @param {Number} $x A number between -1 and 1.
// @example
//     acos(0.1) // 1.47063
//     acos(-1)  // 3.14159
@function acos ($x) {
    @if $x > 1 or $x < -1 {
        @warn "Argument for `acos()` must be a number between -1 and 1";
        @return null;
    }
    @return $PI / 2 - asin($x);
}

// Returns the arctangent of a number.
// @param {Number} $x
// @example
//     atan(0.1) // 0.09967
//     atan(-1)  // -0.7854
@function atan ($x) {
    $i:  24;
    $sgn: 0;
    $a:   0;
    @if $x > 1 {
        $sgn:  1;
        $x:    1 / $x;
    } @else if $x < -1 {
        $sgn: -1;
        $x:    1 / $x;
    }
    @while $i > 0 {
        $a: ($i * $i * $x * $x) / (2 * $i + 1 + $a);
        $i: $i - 1;
    }
    @if $sgn > 0 {
        @return  $PI / 2 - $x / (1 + $a);
    } @else if $sgn < 0 {
        @return -$PI / 2 - $x / (1 + $a);
    } @else {
        @return            $x / (1 + $a);
    }
}

// Returns the arctangent of the quotient of its arguments.
// @param {Number} $y
// @param {Number} $x
// @example
//     atan2(0, 0)       // 0
//     atan2(0, -0.0)    // 3.14159
//     atan2(-0.0, 0)    // 0
//     atan2(-0.0, -0.0) // -3.14159
//     atan2(0, 1)       // 0
//     atan2(0, -1)      // 3.14159
@function atan2 ($y, $x) {
    @if $x > 0 {
        @return atan($y / $x);
    } @else if $x < 0 {
        @if $y < 0 {
            @return atan($y / $x) - $PI;
        } @else {
            @return atan($y / $x) + $PI;
        }
    } @else {
        @if $y < 0 {
            @return - $PI / 2;
        } @else if $y > 0 {
            @return $PI / 2;
        } @else {
            @if 1 / $x == 1 / 0 {
                @return 0;
            } @else {
                @if 1 / $y == 1 / 0 {
                    @return $PI;
                } @else {
                    @return -$PI;
                }
            }
        }
    }
}


// Greatest common divisor and least common multiple
// Returns the greatest common divisor
// @param {Number} $a
// @param {Number} $b
// @example
//     gcd(2, 3)   // 1
//     gcd(54, 24) // 6
@function gcd ($a, $b) {
    @return if($b == 0, $a, gcd($b, $a % $b));
}

// Returns the least common multiple
// @param {Number} $a
// @param {Number} $b
// @example
//     lcm(4, 6)   // 12
//     lcm(30, 42) // 210
@function lcm ($a, $b) {
    @return $a * $b / gcd($a, $b);
}


// Helpers
// Strip unit from a number
@function strip-unit ($number) {
    @if unitless($number) {
        @return $number;
    } @else {
        @return $number / ($number * 0 + 1);
    }
}

// Convert deg to rad
@function deg-to-rad ($deg, $unit: true) {
    @return strip-unit($deg) * $PI / 180 * if($unit, 1rad, 1);
}

// Convert rad to deg
@function rad-to-deg ($rad, $unit: true) {
    @return strip-unit($rad) * 180 / $PI * if($unit, 1deg, 1);
}

// Convert to unitless rad
@function unitless-rad ($angle) {
    @return (0rad + $angle) / 1rad;
}


// ____________________________________________________________________________
//
//    Sass Unit Converter v.2.5.0
//    npm install sass-unitconverter
//    Based on https://github.com/jakob-e/unitconversion
// ____________________________________________________________________________
//
//   Function               Input units
//
//   Absolute length
//   px(input, options);        px, pt, pc, mm, cm, in, em, rem, vw, vh, vmin, vmax, number
//   pt(input, options);        px, pt, pc, mm, cm, in, em, rem, vw, vh, vmin, vmax, number
//   pc(input, options);        px, pt, pc, mm, cm, in, em, rem, vw, vh, vmin, vmax, number
//   mm(input, options);        px, pt, pc, mm, cm, in, em, rem, vw, vh, vmin, vmax, number
//   cm(input, options);        px, pt, pc, mm, cm, in, em, rem, vw, vh, vmin, vmax, number
//   in(input, options);        px, pt, pc, mm, cm, in, em, rem, vw, vh, vmin, vmax, number
//
//   Relative length
//   em(input,  options);       px, pt, pc, mm, cm, in, em, rem, vw, vh, vmin, vmax, number
//   rem(input, options);       px, pt, pc, mm, cm, in, em, rem, vw, vh, vmin, vmax, number
//   ex(input, options);        ex, number
//   ch(input, options);        ch, number
//   vw(input, options);        px, pt, pc, mm, cm, in, em, rem, vw, vh, vmin, vmax, number
//   vh(input, options);        px, pt, pc, mm, cm, in, em, rem, vw, vh, vmin, vmax, number
//   vmin(input, options);      px, pt, pc, mm, cm, in, em, rem, vw, vh, vmin, vmax, number
//   vmax(input, options);      px, pt, pc, mm, cm, in, em, rem, vw, vh, vmin, vmax, number
//
//   Angle
//   deg(input, options);       deg, rad, grad, turn, number
//   rad(input, options);       deg, rad, grad, turn, number
//   grad(input, options);      deg, rad, grad, turn, number
//   turn(input, options);      deg, rad, grad, turn, number
//
//   Time
//   s(input, options);         s, ms, number
//   ms(input, options);        s, ms, number
//
//   Frequency
//   hz(input, options);        hz, khz, number
//   khz(input, options);       hz, khz, number
//
//   Resolution
//   dpi(input, options);       dpi, dpcm, dppx, number
//   dpcm(input, options);      dpi, dpcm, dppx, number
//   dppx(input, options);      dpi, dpcm, dppx, number
//
//   String
//   str(input);                anything not null
//
//   Number, int and uint
//   num(input);                px, pt, pc, mm, cm, in, em, rem, ex, ch,
//                              vw, vh, vmin, vmax, deg, rad, grad, turn,
//                              dpi, dpcm, dppx, s, ms, hz, khz, number, string
//   int(input);                as number
//   uint(input);               as number
//
//   ratio                      number to fraction
//
//   ETC Convert
//   one-unit(input);           px, pt, pc, mm, cm, in, em, rem, ex, ch,
//                              vw, vh, vmin, vmax, deg, rad, grad, turn,
//                              dpi, dpcm, dppx, s, ms, hz, khz, number
//   to-unit(input, unit);      same
//   to-unit-list(list, unit);  same as list
//   to-unit-map(map, unit);    same as map
//
//   is-[unit](input)           each unit
//   is-num(input, strict)      strict: true(default) => pure number
//                                      false         => type-is number
//   is-null(input, strict)     strict: true(default) => pure null
//                                      false         => null, length is 0(list, map, string)
//   is-exist(input, strict)    strict: true(default) => pure exist
//                                      false         => exist, not false
//   is-equal(inputA, inputB)   anything
//
//   Aliases
//   string(input);
//   number(input);
//   is-percent(input);
//   is-string(input);
//   is-length(input);
//   is-number(input, strict);
//
//   Options
//   root-size:                 px, pt, pc, mm, cm, in, em, rem
//   base-size:                 px, pt, pc, mm, cm, in, em, rem
//   unit-width:                px, pt, pc, mm, cm, in, em, rem
//   unit-height:               px, pt, pc, mm, cm, in, em, rem
//   check-callback:            Function Name of
//                              (input, unit, Options) => Boolean
//
// ____________________________________________________________________________

// == Base =====================================================================
// Base font size in pixel for converting em and rem to absolute lengths.
$root-font-size: 16px            !default;
$base-font-size: $root-font-size !default;
$unit-width:     1200px          !default;
$unit-height:    800px           !default;
$check-callback: null            !default;

@function n-units($number) {
  @return (
    // absolute length
    px: $number * 1px, pt: $number * 1pt, pc: $number * 1pc,
    mm: $number * 1mm, cm: $number * 1cm, in: $number * 1in,

    // relative length - font based
    em: $number * 1em, rem: $number * 1rem,
    ch: $number * 1ch,  ex: $number * 1ex,

    // relative length - viewport based
    vw:   $number * 1vw,     vh: $number * 1vh,
    vmin: $number * 1vmin, vmax: $number * 1vmax,

    // angle
    deg:  $number * 1deg,   rad: $number * 1rad,
    grad: $number * 1grad, turn: $number * 1turn,

    // time
    s: $number * 1s, ms: $number * 1ms,

    // frequency
    hz: $number * 1Hz, khz: $number * 1kHz,
    Hz: $number * 1Hz, kHz: $number * 1kHz,

    // resolution
    dppx: $number * 1dppx, dpcm: $number * 1dpcm, dpi: $number * 1dpi,

    // percent or number
    "%": $number * 1%, pct:    $number * 1%, percent: $number * 1%,
    num: $number * 1,  number: $number * 1
  );
}

// == Option ===================================================================
@function unit-global-options() {
  $global-options:(
//  Global Name Global Variable
    root-size:  $root-font-size,
    base-size:  $base-font-size,
    width:      $unit-width,
    height:     $unit-height,
    callback:   $check-callback,
  );
  @return $global-options;
}

// Mapping with Scoped Options
$unit-options-map: (
//Scoped Name  Global Name.
  root:        root-size,
  root-size:   root-size,
  size-root:   root-size,
  base:        base-size,
  base-size:   base-size,
  size-base:   base-size,
  width:       width,
  width-size:  width,
  size-width:  width,
  height:      height,
  height-size: height,
  size-height: height,
  callback:    callback,
) !default;

// Get Global Named Scoped Option
@function unit-scoped-convert($scoped-options) {
  @if length($scoped-options) == 0 {
    @return ();
  }

  $result: ();
  @each $key, $value in $scoped-options {
    $global-key: map-get($unit-options-map, $key);
    $result:     map-merge($result, ($global-key: $value));
  }
  @return $result;
}

// Get Global Name
@function unit-name-convert($name) {
  $result: ();
  @if is-list($name) {
    @each $key in $name {
      $global-key: map-get($unit-options-map, $key);
      $result:     append($result, $global-key);
    }
  }
  @else {
    $result: map-get($unit-options-map, $name);
  }
  @return $result;
}

// Get Option Single Value
@function unit-get-option-value($scoped, $name) {
  @return if(map-has-key($scoped, $name),
  map-get($scoped, $name),
  map-get(unit-global-options(), $name));
}

// Get Option Value
@function unit-get-option($options: (), $name: all) {
  $scoped: unit-scoped-convert($options);
  $result: ();

  @if $name == all {
    $result: map-merge(unit-global-options(), $scoped);
  }
  @else {
    $name: unit-name-convert($name);

    @if is-list($name) {
      @each $option in $name {
        $value:  unit-get-option-value($scoped, $option);
        $result: append($result, $value);
      }
    }
    @else {
      $result: unit-get-option-value($scoped, $name);
    }
  }
  @return $result;
}

// == Conversion ===============================================================
// Get unit
@function get-unit($input) {
  $type: type-of($input);
  @if $type == 'number' {
    $unit: unit($input);
    @return if($unit == '', 'num', $unit);
  }
  @return $type;
}

// Get 1size of value
@function one-unit($input) {
  $type: type-of($input);
  @if $type == 'number' {
    @return ($input / num($input));
  }
  @if $type != 'string' {
    @error 'Value for `unit` should be a number or string.';
  }

  $units: n-units(1);
  $parsed-unit: false;

  @each $unit in $units {
    // str-index - find substring in a string
    // 'px' in '10px' for example

    // $unit is a pair of ['px': 1px] (item in $units)
    // nth(['px': 1px], 1) returns 'px'
    // nth(['px': 1px], 2) returns 1px

    @if (str-index($input, nth($unit, 1))) {
      $parsed-unit: nth($unit, 2);
    }
  }

  @if (not $parsed-unit) {
    @error 'Invalid unit `#{$input}` – must be a valid CSS unit';
  }

  @return $parsed-unit;
}

// Get break
@function get-break($unit, $width, $height, $options) {
  $width:  if(is-null($width),  null, px($width, $options));
  $height: if(is-null($height), null, px($height, $options));

  @if $unit == vw {
    @return $width;
  }
  @if $unit == vh {
    @return $height;
  }
  @if $unit == vmin {
    @return min($width, $height);
  }
  @if $unit == vmax {
    @return max($width, $height);
  }
}

// Conversion function
@function to-unit($input, $unit, $options: ()) {
  // Get Options
  $root-size: unit-get-option($options, root-size);
  $base-size: unit-get-option($options, base-size);
  $width:     unit-get-option($options, width);
  $height:    unit-get-option($options, height);
  $callback:  unit-get-option($options, callback);

  // Check if convertable
  $callback-args: ($input $unit $options);
  @if (not is-null($callback)) and (not call($callback, $callback-args...)) {
    @return $input;
  }

  // Test against valid CSS units
  $units: n-units(0);
  $to-unit: map-get($units, $unit);

  // Error handling – wrong $unit
  // Incomparable units are caught in convertion
  @if not $to-unit {
    @error 'Could not convert to `#{$unit}` – must be a valid CSS unit';
    @return null;
  }

  // Number/incomparable conversion
  @if index(num number ex ch, $unit) {
    $value: num($input);
  }

  // $input is VW, VH, VMIN, VMAX using px as base
  @if index(vw vh vmin vmax, unit($input)) {
    $break: get-break(unit($input), $width, $height, $options);
    $input: ($break * num($input)) / 100;
  }

  // $unit is VW, VH, VMIN, VNAX using px as base
  @if index(vw vh vmin vmax, $unit) and not index(num number, unit($input)) {
    $break: num(get-break($unit, $width, $height, $options));
    $input: num(px($input, $options));
    $input: if($break == 0, 0,
    ($input / $break) * 100);
  }

  // EM convertion using px as base
  @if index(em, unit($input)) {
    $input: 0px + num($input) * $base-size/1px;
  }
  @if index(em, $unit) and not unitless($input) {
    $input: 0px + px($input);
    $input: num($input) * 1px/$base-size;
  }

  // REM convertion using px as base
  @if index(rem, unit($input)) {
    $input: 0px + num($input) * $root-size/1px;
  }
  @if index(rem, $unit) and not unitless($input) {
    $input: 0px + $input;
    $input: num($input) * 1px/$root-size;
  }

  // Bug fix – resolution units seems to be flipped
  @if index(dpi dpcm dppx, $unit){
    $units: (dppx: 0dppx, dpcm: 0dpcm, dpi: 0dpi);
    $input-unit: map-get($units, unit($input));
    $input: if(1dppx < 95dpi,num($input-unit + (num($input) + $to-unit)),$input);
  }

  // Convert
  @return $to-unit + $input;
}

// Convesion List
@function to-unit-list($list, $unit, $options: ()) {
  $new-list: ();

  @each $value in $list {
    $converted: to-unit($value, $unit, $options);
    $new-list: append($new-list, $converted);
  }
  @return $new-list;
}

// Conversion Map
@function to-unit-map($map, $unit, $options: ()) {
  $new-map: ();

  @each $key, $value in $map {
    $converted: to-unit($value, $unit, $options: ());
    $new-map: map-merge($new-map, ($key: $converted));
  }
  @return $new-map;
}

// Trampoline Function
@function to-unit-converted($value, $unit, $options: ()) {
  @return if(is-num($value, false),
             to-unit($value, $unit, $options),
             to-unit-data($value, $unit, $options));
}

// Conversion most of the data.
@function to-unit-data($data, $unit, $options: ()) {
  $new-data: ();
  @if      is-list($data) {
    @each $value in $data {
      $converted: to-unit-converted($value, $unit, $options);
      $new-data: append($new-data, $converted);
    }
  }
  @else if is-map($data) {
    @each $key, $value in $data {
      $converted: to-unit-converted($value, $unit, $options);
      $new-data: map-merge($new-data, ($key: $converted));
    }
  }
  @else {
    @return if(is-num($data, false), to-unit($data, $unit, $options), $data);
  }
  @return $new-data;
}

//  Convert number to ratio (fraction)
//  ratio(1.7777778) =>   16/9
@function ratio($x, $y: null){
  @if not $y {
    $n: $x; $y: 1;
    @while $y < 10 {
      $x:  $n * 10 - ((10 - $y) * $n);
      @if $x == round($x){ @return #{$x}/#{$y}; }
      @else { $y: $y + 1; }
    }
    $x: round($n * 1000000); $y: 1000000;
    @while $x % 10 == 0 { $x: $x/10; $y: $y/10; }
    @while $x % 5 == 0 { $x: $x/5; $y: $y/5; }
    @while $x % 2 == 0 { $x: $x/2; $y: $y/2; }
    @return #{$x}/#{$y};
  }
  @else if $x == round($x) and $y == round($y){ @return #{$x}/#{$y}; }
  @warn 'X and Y must be integers'; @return false;
}


// == Interface - Convert ======================================================
// Absolute lengths
@function px($input, $options: ()) { @return to-unit($input, px, $options); }
@function pt($input, $options: ()) { @return to-unit($input, pt, $options); }
@function pc($input, $options: ()) { @return to-unit($input, pc, $options); }
@function mm($input, $options: ()) { @return to-unit($input, mm, $options); }
@function cm($input, $options: ()) { @return to-unit($input, cm, $options); }
@function in($input, $options: ()) { @return to-unit($input, in, $options); }

// Relative lengths
@function em($input, $options: ()) {
  $em: to-unit(nth($input, 1), em, $options);
  // Adjust for compounds (visual size)
  @if length($input) > 1 {
    @for $i from 2 through length($input) {
      $em: $em / num(em(nth($input, $i), $options));
    }
  }
  @return $em;
}
@function rem($input, $options: ()) { @return to-unit($input, rem, $options); }

// Inconvertible relative lengths – depends on font
@function ex($input, $options: ()) { @return to-unit($input, ex, $options); }
@function ch($input, $options: ()) { @return to-unit($input, ch, $options); }

// Relative lengths - Viewport
@function   vw($input, $options: ()) { @return to-unit($input,   vw, $options); }
@function   vh($input, $options: ()) { @return to-unit($input,   vh, $options); }
@function vmin($input, $options: ()) { @return to-unit($input, vmin, $options); }
@function vmax($input, $options: ()) { @return to-unit($input, vmax, $options); }

// Angles
@function  deg($input, $options: ()) { @return to-unit($input,  deg, $options); }
@function  rad($input, $options: ()) { @return to-unit($input,  rad, $options); }
@function grad($input, $options: ()) { @return to-unit($input, grad, $options); }
@function turn($input, $options: ()) { @return to-unit($input, turn, $options); }

// Time
@function ms($input, $options: ()) { @return to-unit($input, ms, $options); }
@function  s($input, $options: ()) { @return to-unit($input,  s, $options); }

// Frequencies
@function  hz($input, $options: ()) { @return to-unit($input,  hz, $options); }
@function khz($input, $options: ()) { @return to-unit($input, khz, $options); }

// Resolution
@function  dpi($input, $options: ()) { @return to-unit($input,  dpi, $options); }
@function dpcm($input, $options: ()) { @return to-unit($input, dpcm, $options); }
@function dppx($input, $options: ()) { @return to-unit($input, dppx, $options); }

// Strings and numbers
// https://css-tricks.com/snippets/sass/strip-unit-function/
// https://stackoverflow.com/questions/47630616/scss-arithmetic-operation-with-string
@function str($input) { @return #{$input};  }
@function num($input) {
  @if is-num($input) { @return $input; }
  @if type-of($input) == 'number' and not unitless($input) {
    @return $input / ($input * 0 + 1);
  } @else if type-of($input) != 'string' {
    @error 'Could not convert `#{$input}` - must be `type-of number or a string.`';
    @return null;
  }

  $result: 0;
  $digits: 0;
  $minus: str-slice($input, 1, 1) == '-';
  $numbers: ('0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9);

  @for $i from if($minus, 2, 1) through str-length($input) {
    $character: str-slice($input, $i, $i);

    @if (index(map-keys($numbers), $character) or $character == '.') {
      @if $character == '.' {
        $digits: 1;
      } @else if $digits == 0 {
        $result: $result * 10 + map-get($numbers, $character);
      } @else {
        $digits: $digits * 10;
        $result: $result + map-get($numbers, $character) / $digits;
      }
    }
  }

  @return if($minus, -$result, $result);;
}
@function int($input) {
  $num: num($input);
  @return if($num<0, ceil($num), floor($num));
}
@function uint($input) { @return abs(int($input)); }

// Aliases
@function string($input) { @return str($input); }
@function number($input) { @return num($input); }


// == Interface - Check ========================================================
// Absolute lengths
@function is-px($input) { @return get-unit($input) == 'px'; }
@function is-pt($input) { @return get-unit($input) == 'pt'; }
@function is-pc($input) { @return get-unit($input) == 'pc'; }
@function is-mm($input) { @return get-unit($input) == 'mm'; }
@function is-cm($input) { @return get-unit($input) == 'cm'; }
@function is-in($input) { @return get-unit($input) == 'in'; }

// Relative lengths - font based
@function  is-em($input) { @return get-unit($input) ==  'em'; }
@function is-rem($input) { @return get-unit($input) == 'rem'; }
@function  is-ex($input) { @return get-unit($input) ==  'ex'; }
@function  is-ch($input) { @return get-unit($input) ==  'ch'; }

// Relative lengths - viewport based
@function   is-vw($input) { @return get-unit($input) ==   'vw'; }
@function   is-vh($input) { @return get-unit($input) ==   'vh'; }
@function is-vmin($input) { @return get-unit($input) == 'vmin'; }
@function is-vmax($input) { @return get-unit($input) == 'vmax'; }

// Angles
@function  is-deg($input) { @return get-unit($input) ==  'deg'; }
@function  is-rad($input) { @return get-unit($input) ==  'rad'; }
@function is-grad($input) { @return get-unit($input) == 'grad'; }
@function is-turn($input) { @return get-unit($input) == 'turn'; }

// Time
@function is-ms($input) { @return get-unit($input) == 'ms'; }
@function  is-s($input) { @return get-unit($input) ==  's'; }

// Frequencies
@function  is-hz($input) { @return get-unit($input) ==  'hz'; }
@function is-khz($input) { @return get-unit($input) == 'khz'; }

// Resolution
@function  is-dpi($input) { @return get-unit($input) ==  'dpi'; }
@function is-dpcm($input) { @return get-unit($input) == 'dpcm'; }
@function is-dppx($input) { @return get-unit($input) == 'dppx'; }

// Percent, Number, Length, String
@function  is-pct($input) { @return get-unit($input) ==      '%'; }
@function  is-num($input, $strict: true) {
  @return if($strict, get-unit($input) == 'num', type-of($input) == 'number');
}
@function  is-len($input) {
  @return is-num($input) != is-num($input, false);
}
@function  is-str($input) { @return get-unit($input) == 'string'; }

// ETC: SASS Types
@function  is-null($input, $strict: true) {
  $null-check: get-unit($input) == 'null';
  @return if($strict, $null-check, $null-check or length($input) == 0 or $input == '');
}
@function  is-bool($input) { @return get-unit($input) ==  'bool'; }
@function  is-list($input) { @return get-unit($input) ==  'list'; }
@function   is-map($input) { @return get-unit($input) ==   'map'; }
@function is-color($input) { @return get-unit($input) == 'color'; }

@function is-equal($inputA, $inputB) { @return $inputA == $inputB; }
@function is-exist($input, $strict: true) {
  $variable-exists: variable-exists($input);
  @return if($strict, $variable-exists, $variable-exists and $input);
}
@function is-calc($input) {
  @if not is-str($input) {
    @return false;
  }
  @else {
    $str-front:  str-slice($input, 1, 5) == "calc(";
    $str-back:   str-slice($input, str-length($input)) == ")";
    @return $str-front and $str-back;
  }
}

// Aliases
@function is-percent($input) { @return is-pct($input); }
@function  is-number($input, $strict: true) { @return is-num($input, $strict); }
@function  is-length($input) { @return is-len($input); }
@function  is-string($input) { @return is-str($input); }

// 
// 

// ** Utils ********************************************************************
// == Data Structure ===========================================================
// -- List ---------------------------------------------------------------------
// -- To List --
@function range($values) {
  $iter-num: 0;
  $range-list: ();

  // Get Length
  @if is-num($values) {
    $iter-num: $values;
  }
  @else {
    $iter-num: length($values);
  }

  // Append of count
  @for $i from 1 through $iter-num {
    $range-list: append($range-list, $i);
  }
  @return $range-list;
}

@function repeat($value, $size) {
  $repeat-list:();
  @each $i in range($size) {
    $repeat-list: append($repeat-list, $value);
  }
  @return $repeat-list;
}

@function list($args...) {
  $list: ();
  @each $arg in $args {
    $list: append($list, $arg);
  }
  @return $list;
}

@function flatten($list...) {
  $newList: ();
  @each $value in $list {
    @if is-list($value) {
      // One Depth Flatten
      @each $deepV in $value {
        $newList: append($newList, $deepV);
      }
    }
    @else {
      $newList: append($newList, $value);
    }
  }
  @return $newList;
}

// -- At List --
@function contain($values, $item, $type: 'key') {
  @if      is-list($values) {
    @return index($values, $item) != null;
  }
  @else if is-map($values) {
    @if      $type == 'key' {
      @return map-has-key($values, $item);
    }
    @else if $type == 'value' {
      @return contain(map-values($values), $item);
    }
    @else if $type == 'map' {
      @return not is-null(map-index($values, $item, 'map'));
    }
  }
}

// https://hugogiraudel.com/2013/08/08/advanced-sass-list-functions/
@function replace-nth($list, $index, $value) {
  $result: null;

  @if not is-num($index) {
    @warn "$index: #{quote($index)} is not a number for `replace-nth`.";
  } @else if $index == 0 {
    @warn "List index 0 must be a non-zero integer for `replace-nth`.";
  } @else if abs($index) > length($list) {
    @warn "List index is #{$index} but list is only #{length($list)} item long for `replace-nth`.";
  } @else {
    $result: ();
    $index: if($index < 0, length($list) + $index + 1, $index);

    @for $i from 1 through length($list) {
      @if $i == $index {
        $result: append($result, $value);
      } @else {
        $result: append($result, nth($list, $i));
      }
    }
  }

  @return $result;
}

// https://gist.github.com/Jakobud/ec056b52f3673cc369dc97f2c2428424
@function list-remove($list, $index) {
  $newList: ();
  @for $i from 1 through length($list) {
    @if $i != $index {
      $newList: append($newList, nth($list,$i), 'space');
    }
  }
  @return $newList;
}

// -- Map ----------------------------------------------------------------------
@function map-index($map, $key-value, $type: 'key') {
  @if      $type == 'key'   {
    @return index(map-keys($map),   $key-value);
  }
  @else if $type == 'value' {
    @return index(map-values($map), $key-value);
  }
  @else if $type == 'map'   {
    @return index($map, to-list($key-value));
  }
}

@function map-nth($map, $n, $type: 'key') {
  @if      $type == 'key'   {
    @return nth(nth($map, $n), 1);
  }
  @else if $type == 'value' {
    @return nth(nth($map, $n), 2);
  }
}

// -- Convert ------------------------------------------------------------------
// https://hugogiraudel.com/2014/04/28/casting-map-into-list/
@function to-list($value, $keep: 'both') {
  $keep: if(index('keys' 'values', $keep), $keep, 'both');

  @if is-map($value) {
    $keys: ();
    $values: ();

    // Each Sets
    @each $key, $val in $value {
      $keys: append($keys, $key);
      $values: append($values, $val);
    }

    // Types of list
    @if $keep == 'keys' {
      @return $keys;
    } @else if $keep == 'values' {
      @return $values;
    } @else {
      @return zip($keys, $values);
    }
  }

  @return if(not is-list($value), ($value,), $value);
}

@function to-map($keys, $values) {
  $map: ();

  @each $i in range($keys) {
    $map-value: (nth($keys, $i): nth($values, $i));
    $map: map-merge($map, $map-value);
  }
  @return $map;
}

@function empty-map($x: x) {
  @return map-remove(($x:$x), $x);
}

// -- Sort ---------------------------------------------------------------------
// https://gist.github.com/Jakobud/744b98b629abe018766f6d506a2e92ae
// https://css-tricks.com/snippets/sass/sorting-function/
@function sort-compare($a, $b) {
  @if is-num($a, false) and is-num($b, false) {
    @return $a < $b;
  }

  // If string
  $order: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"
          "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"
          "k" "l" "m" "n" "o" "p" "q" "r" "s" "t"
          "u" "v" "w" "x" "y" "z";

  $a: to-lower-case($a + unquote(""));
  $b: to-lower-case($b + unquote(""));

  @for $i from 1 through min(str-length($a), str-length($b)) {
    $char-a: str-slice($a, $i, $i);
    $char-b: str-slice($b, $i, $i);

    // Check
    @if $char-a and $char-b and index($order, $char-a) != index($order, $char-b) {
      @return index($order, $char-a) < index($order, $char-b);
    }
  }

  @return str-length($a) < str-length($b);
}

// -- List --
@function list-sort($list) {
  $sortedlist: ();
  @while length($list) > 0 {
    $value: nth($list,1);
    @each $item in $list {
      @if sort-compare($item, $value) {
        $value: $item;
      }
    }
    $sortedlist: append($sortedlist, $value, 'space');
    $list: list-remove($list, index($list, $value));
  }
  @return $sortedlist;
}

@function list-sort-index($list) {
  $sortedMap: ();
  @each $i in range($list) {
    $value: nth($list, $i);
    @if map-has-key($sortedMap, $value) {
      $other-i: map-get($sortedMap, $value);
      $i: if(is-list($other-i), append($other-i, $i), ($other-i, $i));
    }
    $sortedMap: map-merge($sortedMap, ($value: $i));
  }

  $sortedMap: map-sort($sortedMap);
  @return flatten(map-values($sortedMap)...);
}

// -- Map --
// https://gist.github.com/Jakobud/a0ac11e80a1de453cd86f0d3fc0a1410
@function map-sort($map) {
  $keys: list-sort(map-keys($map));
  $sortedMap: ();
  @each $key in $keys {
    $sortedMap: map-merge($sortedMap, ($key: map-get($map, $key)));
  }
  @return $sortedMap;
}

@function map-sort-values($map) {
  // Transform map to zipped list
  $keys: ();
  $values: ();

  @each $key, $val in $map {
    $keys: append($keys, $key);
    $values: append($values, $val);
  }

  $list: zip($keys, $values);

  $sortedMap: ();
  @while length($list) > 0 {

    // Find smallest pair
    $smallestPair: nth($list, 1);
    @each $pair in $list {
      $value: nth($pair, 2);
      $smallestValue: nth($smallestPair, 2);
      @if $value < $smallestValue {
        $smallestPair: $pair;
      }
    }

    // Add smallest pair to sorted map
    $key: nth($smallestPair, 1);
    $value: nth($smallestPair, 2);
    $sortedMap: map-merge($sortedMap, ($key: $value));

    // Remove from list smallest pair
    $smallestPairIndex: index($list, $smallestPair);
    $newList: ();
    @for $i from 1 through length($list) {
      @if $i != $smallestPairIndex {
        $newList: append($newList, nth($list, $i), "space");
      }
    }
    $list: $newList;
  }

  @return $sortedMap;
}

// == Math =====================================================================
@function count-round($number, $count: 1) {
  $digit: pow(10, $count - 1);

  $upper: $number * $digit;
  $round: floor($upper + 0.5);
  @return $round / $digit;
}

// == Other ====================================================================
// -- High Order ---------------------------------------------------------------
@function do($value, $functions...) {
  $result: $value;
  $functions: flatten($functions...);
  // Call each functions
  @each $function in $functions {
    $result: call($function, $result);
  }
  @return $result;
}

@function do-list($value, $functions...) {
  $result: $value;
  $functions: flatten($functions...);
  // Call each functions with full args
  @each $function in $functions {
    $result: call($function, $result...);
  }
  @return $result;
}

@function map($values, $function, $type: 'value', $separator: auto) {
  @if      is-list($values) {
    $newList: ();
    @if      $type == 'list' {
      $newList: do-list($values, $function);
    }
    @else if $type == 'value' {
      @each $value in $values {
        $newList: append($newList, do($value, $function), $separator);
      }
    }
    @return $newList;
  }
  @else if is-map($values)  {
    $newMap: ();
    @each $key, $value in $values {
      $newMap: map-merge($newMap, if($type == 'key',
                                     (do($key, $function): $value),
                                     ($key: do($value, $function))
      ));
    }
    @return $newMap;
  }
  @else {
    @return do($values, $function);
  }
}

// -- Calcable -----------------------------------------------------------------
@function is-calcable($size) {
  // Pass Value
  // - String
  //   font-size: 16px !important
  //   text-indent: 5em hanging each-line
  // - Num
  // - Percent
  @return (is-len($size) and not is-pct($size));
}

@function calcable-callback($input, $unit, $options) {
  @return is-calcable($input);
}

$check-callback: calcable-callback;

// -- Size ---------------------------------------------------------------------
@function limit-size($size, $min-size, $max-size) {
  @if not is-null($min-size) {
    $size: if($size < $min-size, $min-size, $size);
  }
  @if not is-null($max-size) {
    $size: if($size > $max-size, $max-size, $size);
  }

  @return $size;
}
@function limit-sizes($sizes, $min-size, $max-size) {
  $sizes:    to-unit-data($sizes,    px);
  $min-size: to-unit-data($min-size, px);
  $max-size: to-unit-data($max-size, px);

  $new-sizes: ();
  @each $size in $sizes {
    $new-size: if(is-calcable($size), limit-size($size, $min-size, $max-size), $size);
    $new-sizes: append($new-sizes, $new-size);
  }
  @return $new-sizes;
}

@function replace-size($sizes, $index, $subtitute-size) {
  $size: nth($sizes, $index);
  @return if(is-calcable($size) or is-calc($size),
             replace-nth($sizes, $index, $subtitute-size), $sizes);
}

@function check-sizes($sizesA, $sizesB) {
  $sizes: zip($sizesA, $sizesB);

  @each $sizeA, $sizeB in $sizes {
    @if is-calcable($sizeA) and is-calcable($sizeB) {
      @return $sizeA <= $sizeB;
    }
  }
  @return false;
}

// ** Others *******************************************************************
// -- Body Width Fix --
// Account for difference between 100vw and 100% when scroll bars are included on page.
// Center and trims the offset caused in most browsers by scroll bars when true
// https://github.com/jdillick/fluid-typography#vw-quirks--fixes
@mixin body-width-fix() {
  @media screen {
    width: 100vw;
    margin-left: calc((100% - 100vw) / 2);
    overflow-x: hidden;
  }
}

$CACHED-BODY-WIDTH-FIX: false !default; // Don't change it!!
@mixin fluid-width-fix($is-width-fix) {
  @if $is-width-fix and not $CACHED-BODY-WIDTH-FIX {
    $CACHED-BODY-WIDTH-FIX: true !global; // Only 1 apply

    html {
      @include body-width-fix();
    }
  }
}

// -- Body Width Fix --
// Safari doesn't resize its values in an iframe if the iframe is resized.
// https://github.com/twbs/rfs#class-boolean
@mixin safari-iframe-resize-fix() {
  min-width: 0vw;
}

// 
// 
// 

// ** Options ******************************************************************
// == Global Options ===========================================================
$fls-mode:   fluid !default; // fluid or fit, vw-only
$fls-device:   min !default; // min   or max, devices's key(Ex. phone, tablet, ...)
$fls-unit:    each !default; // each  or units(Ex. px, em, rem, ...)
$fls-breakunit: px !default; // units(Ex. px, em, rem, ...)
$fls-min:     null !default; // null  or length(Ex. 20px, 20pt, ...)
$fls-max:     null !default; // null  or length(Ex. 20px, 20pt, ...)
$fls-limit:   size !default; // size  or break
$fls-class:  false !default; // false or true
$fls-safari-iframe-fix: false !default; // false or true

// == Mapping ==================================================================
// -- Mapping with API Name --
@function global-options() {
  $global-options: (
//  Global Name. Global Variable.
    mode:        $fls-mode,
    device:      $fls-device,
    unit:        $fls-unit,
    breakunit:   $fls-breakunit,
    min:         $fls-min,
    max:         $fls-max,
    limit:       $fls-limit,
    class:       $fls-class,
    iframe-fix:  $fls-safari-iframe-fix,

    // Unit Converter
    root-size:   $root-font-size,
    base-size:   $base-font-size,
  );
  @return $global-options;
}

// -- Mapping with Scoped Options --
$fluid-options-map: (
// Scoped Name. Global Name.
   mode:        mode,
   device:      device,
   unit:        unit,
   breakunit:   breakunit,
   min:         min,
   min-size:    min,
   max:         max,
   max-size:    max,
   limit:       limit,
   class:       class,
   iframe:      iframe-fix,
   iframe-fix:  iframe-fix,
   safari-iframe-fix: iframe-fix,

   // Unit Converter
   root:        root-size,
   root-size:   root-size,
   size-root:   root-size,
   base:        base-size,
   base-size:   base-size,
   size-base:   base-size,
);

// -- Mapping with Types --
$unit-types: map-keys(n-units(0)) !default;
$bool-types: true false;
@function fluid-types() {
  $fluid-types: (
//  Global Name. Types.
    mode:        fluid fit vw-only,
    device:      join(min max, map-keys($breakpoints)),
    unit:        join(each, $unit-types),
    breakunit:   $unit-types,
    min:         join(null, $unit-types),
    max:         join(null, $unit-types),
    limit:       size break,
    class:       $bool-types,
    iframe-fix:  $bool-types,

    // Unit Converter
    root-size:   $unit-types,
    base-size:   $unit-types,
  );
  @return $fluid-types;
}

// == Get ======================================================================
@function scoped-convert($scoped-options) {
  // Option Empty
  @if length($scoped-options) == 0 {
    @return ();
  }

  // Replace key to Global Name
  $result: ();
  @each $key, $value in $scoped-options {
    $global-key: map-get($fluid-options-map, $key);
    $result:     map-merge($result, ($global-key: $value));
  }
  @return $result;
}

@function name-convert($name) {
  $result: ();
  @if is-list($name) {
    @each $key in $name {
      $global-key: map-get($fluid-options-map, $key);
      $result: append($result, $global-key);
    }
  }
  @else {
    $result: map-get($fluid-options-map, $name);
  }
  @return $result;
}

@function get-option-value($scoped, $name) {
  @return if(map-has-key($scoped, $name),
             map-get($scoped, $name),
             map-get(global-options(), $name));
}

// -- Interface --
@function get-option($options: (), $name: all) {
  $scoped: scoped-convert($options);
  $result: ();

  @if $name == all {
    $result: map-merge(global-options(), $scoped);
  }
  @else {
    $name: name-convert($name);

    @if is-list($name) {
      @each $option in $name {
        $value:  get-option-value($scoped, $option);
        $result: append($result, $value);
      }
    }
    @else {
      $result: get-option-value($scoped, $name);
    }
  }
  @return $result;
}

// == Validation ===============================================================
$CACHED-GLOBAL-OPTIONS: global-options()   !default;
$CACHED-FLUID-OPTIONS:  $fluid-options-map !default;
$CACHED-OPTIONS-KEYS:   ()                 !default;
$CACHED-ALL-OPTIONS:    global-options()   !default;
$CACHED-FLUID-TYPES:    fluid-types()      !default;

@function check-option($criteria-name, $target-name, $target-type, $option-criteria, $options) {
  @each $option in $options {
    @if not contain($option-criteria, $option) {
      @error "#{$target-name}'s #{$target-type}, #{$option} doesn't exist at #{$criteria-name}";
      @return false;
    }
  }
  @return true;
}

// -- Interface --
@function validate-options($options: ()) {
  $validate-mapping: false;
  $validate-keys:    false;
  $validate-types:   true;

  // - Check api name -
  // Mapping check
  $global-options:       global-options();
  $mapping-names: map-values($fluid-options-map);
  @if not ($CACHED-GLOBAL-OPTIONS == $global-options and
           $CACHED-FLUID-OPTIONS  == $fluid-options-map) {
    $validate-mapping: check-option("global-options", "$fluid-options-map", "global name",
                                    $global-options, $mapping-names);

    $CACHED-GLOBAL-OPTIONS: $global-options    !global;
    $CACHED-FLUID-OPTIONS:  $fluid-options-map !global;
  }

  // Exist key check
  $options-keys: map-keys($options);
  @if not contain($CACHED-OPTIONS-KEYS, $options-keys) {
    $validate-keys: check-option("$fluid-options-map key", "$options", "key",
                                 $fluid-options-map, $options-keys);

    $CACHED-OPTIONS-KEYS: map-merge($CACHED-OPTIONS-KEYS, ($options-keys: true)) !global;
  }

  // - Check each type -
  $all-options: get-option($options);
  $all-options-keys: map-keys($all-options);

  $fluid-types: fluid-types();
  @if not ($CACHED-ALL-OPTIONS == $all-options and $CACHED-FLUID-TYPES == $fluid-types) {
    @each $all-options-key in $all-options-keys {
      $fluid-type:   map-get($fluid-types, $all-options-key);
      $option-value: map-get($all-options, $all-options-key);

      // Case min or max
      @if contain('min' 'max', $all-options-key) {
        $option-value: if(is-null($option-value), $option-value, get-unit($option-value));
      }

      // Case root-size or base-size
      @if contain('root-size' 'base-size', $all-options-key) {
        $option-value: get-unit($option-value);
      }

      // Case not contain in types
      @if not contain($fluid-type, $option-value) {
        @error "$options's value #{$option-value} doesn't exist at fluid-types";
        $validate-types: false;
      }
    }

    $CACHED-ALL-OPTIONS: $all-options !global;
    $CACHED-FLUID-TYPES: $fluid-types !global;
  }

  @return $validate-mapping and $validate-keys and $validate-types;
}


// ** Fluid Size ***************************************************************
$FIRST-KEY:   default !default;
$FIRST-BREAK: 0px     !default;

@function zip-responsive() {
  @return zip(
    map-values($screen-distances), map-values($screen-sizes      ),
    map-values($breakpoints     ), map-values($breakpoints-height)
  );
}

// == Fit Size =================================================================
// Based on Theory
// https://github.com/black7375/Fluid-Size/wiki/The-theory-of-font-size-and-readability
@function calc-ppi($screen-width, $screen-height, $screen-size) {
  @return sqrt(pow($screen-width, 2) + pow($screen-height, 2)) / $screen-size;
}

// -- Measure Angle ------------------------------------------------------------
@function calc-dependency($value) {
  $screen-distance: nth($value, 1) * 100;
  $screen-size:     nth($value, 2);
  $screen-width:    num(nth($value, 3));
  $screen-height:   num(nth($value, 4));

  $ppi: calc-ppi($screen-width, $screen-height, $screen-size);
  $dependent-value: $screen-distance * $ppi;
  @return $dependent-value;
}

@function calc-angle($size, $options) {
  // String, Color value passing(Ex. !important, black, white, #111111 ...ETC)
  @if not is-calcable($size) {
    @return if(is-num($size), #{$size}num, $size);
  }

  $size:   num(px($size));
  $device: get-option($options, device);
  $target: null;

  @if contain('min' 'max', $device) { // Will look min/max
    $values:   zip-responsive();
    @each $value in $values {
      $dependent-value: calc-dependency($value);

      @if $device == 'min' {
        @if (($target == null) or ($target > $dependent-value)) {
          $target: $dependent-value;
        }
      }
      @if $device == 'max' {
        @if (($target == null) or ($target < $dependent-value)) {
          $target: $dependent-value;
        }
      }
    }
  }
  @else { // based on Device's Key
    $value: list(map-get($screen-distances, $device), map-get($screen-sizes,       $device),
                 map-get($breakpoints,      $device), map-get($breakpoints-height, $device));
    $target: calc-dependency($value);
  }

  $angle: $size * 54 / $target;
  $visual-angle: atan($angle) * (10800 / $PI);
  @return count-round($visual-angle, 2);
}
@function calc-angles($sizes, $options) {
  $angles: ();
  @each $size in $sizes {
    $angles: append($angles, calc-angle($size, $options));
  }
  @return $angles;
}

// -- Generate Fit Size --------------------------------------------------------
@function convert-visual($visual-angle) {
  // if origin $size type is num
  // => ${$size}num
  // Return it to its original type.
  @if is-str($visual-angle) {
    $str-start: str-length($visual-angle) - 2;
    $is-number: str-slice($visual-angle, $str-start) == "num";
    @return if($is-number, num($visual-angle), $visual-angle);
  }
  @else {
    @return $visual-angle;
  }
}

// Calc Fit Size
@function calc-size($visual-angle) {
  $check:       is-num($visual-angle);
  $values:      zip-responsive();
  $break-sizes: ();

  @each $value in $values {
    @if $check { // If Calc able
      $screen-distance: nth($value, 1) * 100;
      $screen-size:     nth($value, 2);
      $screen-width:    num(nth($value, 3));
      $screen-height:   num(nth($value, 4));

      $ppi: calc-ppi($screen-width, $screen-height, $screen-size);
      $angle: tan($PI * $visual-angle / 10800);
      $size: $screen-distance * $angle * $ppi / 54;

      $size: px($size, (callback: null));
      $break-sizes: append($break-sizes, ($size));
    }
    @else { // Can't Calc: passed
      $visual-angle: convert-visual($visual-angle);
      $break-sizes: append($break-sizes, ($visual-angle));
    }
  }

  @return $break-sizes;
}

@function fit-size($sizes, $options: empty-map()) {
  // Each Fit Size
  $visual-angle: calc-angles($sizes, $options);
  $scaled-sizes: map($visual-angle, calc-size, $separator: comma);

  @return if(is-list($sizes), call(zip, $scaled-sizes...), $scaled-sizes);
}

// == Fluid ====================================================================
@function size-break($function, $fluid-unit, $now-sizes, $next-sizes,
                     $now-break, $next-break, $max-size: null) {
  // unit convert
  $now-sizes:  to-unit-data($now-sizes,  $fluid-unit);
  $next-sizes: to-unit-data($next-sizes, $fluid-unit);
  $now-break:  to-unit-data($now-break,  $fluid-unit);
  $next-break: to-unit-data($next-break, $fluid-unit);
  $max-size:   to-unit-data($max-size,   $fluid-unit);

  // Each function args
  $check-function: $function == 'fluid-size';
  $args: if($check-function,
            list($now-sizes, $next-sizes, $now-break, $next-break),
            list($now-sizes, $next-sizes, $max-size,  $now-break, $next-break));

  // Mapping
  $results: ();
  $each-sizes: zip($now-sizes, $next-sizes);
  @each $start-size, $end-size in $each-sizes {
    $result: null;
    @if is-len($start-size) and is-len($end-size) {
      $args: replace-nth($args, 1, $start-size);
      $args: replace-nth($args, 2, $end-size  );

      $result: map($args, $function, 'list');
    }
    @else {
      $result: if($check-function, $start-size, $next-break);
    }
    $results: append($results, $result);
  }
  @return $results;
}

// -- Option::Mode -------------------------------------------------------------
// -- Mode: Fluid --
// https://www.madebymike.com.au/writing/fluid-type-calc-examples/
@function fluid-rate($start-size, $end-size, $min-screen, $max-screen) {
  @return ($end-size - $start-size) / ($max-screen - $min-screen);
}
@function fluid-basic-size($start-size, $min-screen, $rate) {
  @return $start-size - $rate * $min-screen;
}

@function fluid-size($start-size, $end-size, $min-screen, $max-screen) {
  @if $start-size == $end-size {
    @return $start-size;
  }
  // Get based values
  $rate: fluid-rate($start-size, $end-size, $min-screen, $max-screen);
  $basic-size: fluid-basic-size($start-size, $min-screen, $rate);

  // Decide Sign
  $sign: "+";
  @if ($basic-size < 0) {
    $sign: "-";
    $basic-size: abs($basic-size);
  }

  // Conbine
  @return calc(#{$rate*100}vw #{$sign} #{$basic-size});
}
@function fluid-sizes($now-sizes, $next-sizes, $now-break, $next-break, $fluid-unit: px) {
  $fluid-sizes: size-break(fluid-size, $fluid-unit,
                           $now-sizes, $next-sizes, $now-break, $next-break);
  @return $fluid-sizes;
}

// -- Mode: VW-Only --
@function vw-sizes($now-sizes, $now-break) {
  @return to-unit-data($now-sizes, vw, (width: $now-break));
}

// -- Option::Limit ------------------------------------------------------------
// -- Limit with Mode --
@function fluid-limit-break($start-size, $end-size, $max-size,
                            $min-screen, $max-screen) {
  @if($start-size == $end-size) {
    @return $min-screen;
  }
  // Simulate Fluid Size
  $rate: fluid-rate($start-size, $end-size, $min-screen, $max-screen);
  $basic-size: fluid-basic-size($start-size, $min-screen, $rate);

  // Calc Limit Point
  $limit-break: ($max-size - $basic-size) / $rate;
  $limit-break: count-round($limit-break, 1);
  @return if($limit-break <= 0px, 0px, $limit-break);
}
@function fluid-limit-breaks($now-sizes, $next-sizes, $max-size,
                             $now-break, $next-break, $fluid-unit: px) {
  $limit-breaks: size-break(fluid-limit-break, $fluid-unit,
                            $now-sizes, $next-sizes, $now-break, $next-break, $max-size);
  @return $limit-breaks;
}

@function vw-limit-break($now-vw, $max-size) {
  $now-vw:   num($now-vw);
  $max-size: px($max-size);

  // Calc Limit Point
  $limit-break: ($max-size / $now-vw) * 100;
  $limit-break: count-round($limit-break, 1);
  @return if($limit-break <= 0px, 0px, $limit-break);
}
@function vw-limit-breaks($now-sizes, $max-size, $now-break, $next-break) {
  $basic-sizes:  vw-sizes($now-sizes, $now-break);
  $limit-breaks: ();

  @each $basic-size in $basic-sizes {
    $limit-break: if(is-vw($basic-size) and $basic-size != 0vw,
                     vw-limit-break($basic-size, $max-size),
                     $next-break);
    $limit-breaks: append($limit-breaks, $limit-break);
  }
  @return $limit-breaks;
}

// -- Get Limit Types --
@function fluid-limit-type($first, $last, $now-break, $next-break,
                           $limit-break, $type: 'size') {
  @if $type == 'break' { // only Limit: break
    @if $first {
      @return 'pass';
    }

    @if $limit-break <= $now-break {
      @return 'substitution';
    }
    @if $now-break < $limit-break and $limit-break < $next-break {
      @return 'add';
    }
  }

  @if $last and $now-break < $limit-break { // Reach Max Size
    @return 'add';
  }
  @else {
    @return 'pass';
  }
}
@function fluid-limit-types($now-key, $last-key, $now-break, $next-break,
                            $limit-breaks, $type: 'size') {
  $first: $now-key == $FIRST-KEY;
  $last:  $now-key == $last-key;

  $limit-types: ();
  @each $limit-break in $limit-breaks {
    $limit-type:  fluid-limit-type($first, $last, $now-break, $next-break, $limit-break, $type);
    $limit-types: append($limit-types, $limit-type);
  }
  @return $limit-types;
}

// -- Limit --
@mixin fluid-add-break($property,  $next-sizes, $fluid-sizes,  $max-size,
                       $now-break, $next-break, $limit-breaks, $limit-types,
                       $class, $is-iframe, $sorted-index, $increase) {
  $decrease: not $increase;

  // now-break: increase => pass, decrease => subtitution
  @if $decrease {
    @each $i in range($limit-breaks) {
      $index: nth($sorted-index, $i);
      @if nth($limit-types, $index) == 'add' {
        $fluid-sizes: replace-size($fluid-sizes, $index, $max-size);
      }
    }
  }
  @include fluid-option-media($class, $now-break, $is-iframe) {
    #{$property}: $fluid-sizes;
  }

  // add-break: increase => subtitution, decrease => create fluid-size(max, next-size)
  $new-fluid-size: null; // only use decrease
  $prev-fluid-sizes: null;
  $break-size: length($limit-breaks);
  @each $i in range($limit-breaks) {
    $now-index:  nth($sorted-index, $i);
    $next-index: if($i < $break-size, nth($sorted-index, $i + 1), null);

    $now-type:   nth($limit-types,  $now-index);
    $now-limit:  nth($limit-breaks, $now-index);
    $next-size:  if(is-null($next-sizes), null, nth($next-sizes,   $now-index ));
    $next-limit: if(is-null($next-index), null, nth($limit-breaks, $next-index));

    // now-limit == next-limit => only substitution
    // next-limit is null || now-limit != next-limit => substitution && apply
    @if $now-type == 'add' {
      $first: $i == 1;
      $last:  is-null($next-limit);
      $continuous: $now-limit == $next-limit;

      @if $decrease and ($first or not $last or not $continuous) {
        $new-fluid-size: fluid-size($max-size, $next-size, $now-limit, $next-break);
      }

      @if $continuous {
        $subtitue-size: if($increase, $max-size, $new-fluid-size);
        $fluid-sizes: replace-size($fluid-sizes, $now-index, $subtitue-size);
      }
      @else {
        $prev-fluid-sizes: $fluid-sizes;
        $subtitue-size: if($increase, $max-size, $new-fluid-size);
        $fluid-sizes: replace-size($fluid-sizes, $now-index, $subtitue-size);

        @if $last or $fluid-sizes != $prev-fluid-sizes {
          @include fluid-option-media($class, $now-limit, $is-iframe) {
            #{$property}: $fluid-sizes;
          }
        }
      }
    }
  }
}

@mixin fluid-media-limit($property, $now-sizes, $next-sizes, $fluid-sizes,
                         $max-size, $now-break, $next-break, $limit-breaks,
                         $limit-types, $is-fluid, $class, $is-iframe) {
  $exist-sub: contain($limit-types, 'substitution');
  $exist-add: contain($limit-types, 'add');
  $sorted-index: null;
  @if (not $exist-sub) and (not $exist-add) {
    //all pass
    @include fluid-option-media($class, $now-break, $is-iframe) {
      #{$property}: $fluid-sizes;
    }
  }
  @else {
    $sorted-index: list-sort-index($limit-breaks);
  }

  // subtitution
  @if $exist-sub {
    @each $i in range($limit-breaks) {
      $index: nth($sorted-index, $i);
      @if nth($limit-types, $index) == 'substitution' {
        $fluid-sizes: replace-size($fluid-sizes, $index, $max-size);
      }
    }
    @include fluid-option-media($class, $now-break, $is-iframe) {
      #{$property}: $fluid-sizes;
    }
  }

  // add break
  @if $exist-add {
    $increase: not $is-fluid or check-sizes($now-sizes, $next-sizes);

    // increase: next-size > now-size > max-size
    // decrease: next-size > max-size > now-size

    // increase => now-break(pass),        add-break(subtitution)
    // decrease => now-break(subtitution), add-break(create fluide-size)
    @include fluid-add-break($property,  $next-sizes, $fluid-sizes,  $max-size,
                             $now-break, $next-break, $limit-breaks, $limit-types,
                             $class, $is-iframe, $sorted-index, $increase);
  }
}

// -- Option::Class ------------------------------------------------------------
@mixin fluid-class-media($fls-class, $break) {
  @if $fls-class == true {
    @include media(">=#{$break}") {
      .fluid-class &,
      &.fluid-class {
        @content;
      }
    }
  }
  @else {
    @include media(">=#{$break}") {
      @content;
    }
  }
}

// -- Option -------------------------------------------------------------------
@mixin fluid-option-media($fls-class, $break, $is-iframe) {
  @include fluid-class-media($fls-class, $break) {
    @content;

    @if $is-iframe {
      @include safari-iframe-resize-fix;
    }
  }
}


// == Medias ===================================================================
@mixin fluid-media-apply($property, $fit-sizes, $fluid-breakpoints, $i, $last-key,
                         $fluid-mode, $fluid-unit, $breakunit,
                         $min-size, $max-size, $limit, $class, $iframe-fix) {
  // Get key and Sizes
  $now-key:  map-nth($fluid-breakpoints, $i);
  $next-key: map-nth($fluid-breakpoints, $i + 1);

  $now-sizes:  map-get($fit-sizes, $now-key );
  $next-sizes: map-get($fit-sizes, $next-key);
  $now-break:  map-get($fluid-breakpoints, $now-key );
  $next-break: map-get($fluid-breakpoints, $next-key);

  $first: $now-key == $FIRST-KEY;
  $prev-key:   if($first, null, map-nth($fluid-breakpoints, $i - 1));
  $prev-sizes: if(is-null($prev-key), null, map-get($fit-sizes, $prev-key));

  // Apply Options
  $is-fit:    $fluid-mode == 'fit';
  $is-fluid:  $fluid-mode == 'fluid';
  $is-vw:     $fluid-mode == 'vw-only';
  $is-last:   if($is-fluid, $now-key == $last-key, $next-key == $last-key);
  $is-iframe: (not $is-fit) and $iframe-fix;

  @if (not is-null($min-size) or not is-null($max-size)) and
      ($is-fit                or $limit == 'size') {
    $now-sizes:  limit-sizes($now-sizes,  $min-size, $max-size);
    $next-sizes: limit-sizes($next-sizes, $min-size, $max-size);
  }

  // for fluid mode
  $limit-breaks: null;
  $limit-types:  null;
  $fluid-sizes:  null;
  @if not $is-fit and not is-null($max-size) {
    $limit-breaks: if($is-fluid,
                      fluid-limit-breaks($now-sizes,  $next-sizes, $max-size,
                                         $now-break, $next-break),
                      vw-limit-breaks($now-sizes, $max-size, $now-break, $next-break));
    $limit-types: fluid-limit-types($now-key, $last-key, $now-break, $next-break,
                                    $limit-breaks, $limit);
    $max-size: to-unit-data($max-size, $fluid-unit);
  }

  // Set size
  $now-sizes:  to-unit-data($now-sizes,  $fluid-unit);
  $next-sizes: to-unit-data($next-sizes, $fluid-unit);
  @if not $is-fit {
    $fluid-sizes: if($is-fluid,
                     fluid-sizes($now-sizes, $next-sizes, $now-break, $next-break, $fluid-unit),
                     vw-sizes($now-sizes, $now-break));
  }

  // Set Break Units
  $now-break:  to-unit-data($now-break,  $breakunit);
  $next-break: to-unit-data($next-break, $breakunit);

  // Apply Media
  $not-continuous: $now-sizes != $prev-sizes;

  @if $first {
    #{$property}: $now-sizes;
  }
  @else if not $is-fit and not is-null($max-size) and $not-continuous {
    @include fluid-media-limit($property, $now-sizes, $next-sizes, $fluid-sizes,
                               $max-size, $now-break, $next-break, $limit-breaks,
                               $limit-types, $is-fluid, $class, $is-iframe);

    @if $is-vw and $is-last and $now-sizes != $next-sizes {
      $limit-breaks: vw-limit-breaks($next-sizes, $max-size, $next-break, $next-break);
      $limit-types:  fluid-limit-types($next-key, $last-key, $next-break, $next-break,
                                       $limit-breaks, $limit);
      $fluid-sizes: vw-sizes($next-sizes, $next-break);
      $last-sizes: null;

      @include fluid-media-limit($property, $next-sizes, $last-sizes, $fluid-sizes,
                                 $max-size, $next-break, $next-break, $limit-breaks,
                                 $limit-types, $is-fluid, $class, $is-iframe);
    }
  }
  @else if $not-continuous {
    $now-values: if(not $is-fit, $fluid-sizes, $now-sizes);
    @include fluid-option-media($class, $now-break, $is-iframe) {
      #{$property}: $now-values;
    }

    // Last at fit, vw-only mode
    $next-values: if($is-fit, $next-sizes, vw-sizes($next-sizes, $next-break));
    @if not $is-fluid and $is-last and $now-sizes != $next-sizes {
      @include fluid-option-media($class, $next-break, $is-iframe) {
        #{$property}: $next-values;
      }
    }
  }
}

@mixin fluid-media($property, $devices-sizes, $options: empty-map()) {
  // Basics
  $fit-sizes:         to-unit-data($devices-sizes, px);
  $fluid-breakpoints: map-merge(($FIRST-KEY: $FIRST-BREAK), to-unit-map($breakpoints, px));
  $fluid-breakpoints: map-sort-values($fluid-breakpoints);
  @if not map-has-key($fit-sizes, $FIRST-KEY) {
    $default-map: ($FIRST-KEY: $DEAFULT-SIZE);
    $fit-sizes: map-merge($default-map, $fit-sizes);
  }
  $fluid-basics: ($property, $fit-sizes, $fluid-breakpoints);

  // Get Options
  $fluid-mode: get-option($options,  mode);
  $fluid-unit: get-option($options,  unit);
  $breakunit:  get-option($options, breakunit);
  $min-size:   get-option($options,   min);
  $max-size:   get-option($options,   max);
  $limit:      get-option($options, limit);
  $class:      get-option($options, class);
  $iframe-fix: get-option($options, iframe-fix);
  $fluid-options: ($fluid-mode, $fluid-unit, $breakunit,
                   $min-size, $max-size, $limit, $class, $iframe-fix);

  // Apply media
  $last-size: length($fluid-breakpoints) - 1;
  $last-key:  map-nth($fluid-breakpoints, if($fluid-mode == 'fluid',
                                             $last-size, $last-size + 1));
  @each $i in range($last-size) {
    $fluid-context: ($i, $last-key);
    $args: flatten($fluid-basics, $fluid-context, $fluid-options);

    @include fluid-media-apply($args...);
  }
}

// -- Wrapper --
@mixin fluid($property, $sizes, $options: empty-map(), $type: 'font') {
  // Preprocessing for $options
  @if not is-map($options) {
    $options: (max-size: $options);
  }
  @if      get-option($options, unit) == each and $type == 'font' {
    $options: map-merge($options, (unit: rem));
  }
  @else if get-option($options, unit) == each and $type ==  'box' {
    $options: map-merge($options, (unit:  px));
  }

  // Validate
  $validate-breakpoints: validate-breakpoints();
  $validate-options:     validate-options($options);

  // Set Unit Scoped Option
  $temp-root: $root-font-size;
  $temp-base: $base-font-size;
  $root-font-size: get-option($options, root-size) !global;
  $base-font-size: get-option($options, base-size) !global;

  // Single value to List processing
  @if not is-list($sizes) {
    $sizes: ($sizes, );
  }

  $fit-sizes:     fit-size($sizes, $options);
  $keys:          join($FIRST-KEY, map-keys($breakpoints));
  $values:        join(($sizes,), $fit-sizes);
  $devices-sizes: to-map($keys, $values);

  @include fluid-media($property, $devices-sizes, $options);

  // Set to Original
  $root-font-size: $temp-root !global;
  $base-font-size: $temp-base !global;
}

// == Interface ================================================================
// -- Font --
@mixin font-size($sizes, $options: empty-map()) {
  @include fluid(font-size, $sizes, $options);
}
@mixin line-height($sizes, $options: empty-map()) {
  @include fluid(line-height, $sizes, $options);
}
@mixin text-indent($sizes, $options: empty-map()) {
  @include fluid(text-indent, $sizes, $options);
}
@mixin letter-spacing($sizes, $options: empty-map()) {
  @include fluid(letter-spacing, $sizes, $options);
}
@mixin word-spacing($sizes, $options: empty-map()) {
  @include fluid(word-spacing, $sizes, $options);
}
@mixin tab-size($sizes, $options: empty-map()) {
  @include fluid(tab-size, $sizes, $options);
}

// -- Box --
@mixin width($sizes, $options: empty-map()) {
  @include fluid(width, $sizes, $options, 'box');
}
@mixin height($sizes, $options: empty-map()) {
  @include fluid(height, $sizes, $options, 'box');
}
@mixin border-width($sizes, $options: empty-map()) {
  @include fluid(border-width, $sizes, $options, 'box');
}
@mixin margin($sizes, $options: empty-map()) {
  @include fluid(margin, $sizes, $options, 'box');
}
@mixin margin-top($sizes, $options: empty-map()) {
  @include fluid(margin-top, $sizes, $options, 'box');
}
@mixin margin-bottom($sizes, $options: empty-map()) {
  @include fluid(margin-bottom, $sizes, $options, 'box');
}
@mixin margin-left($sizes, $options: empty-map()) {
  @include fluid(margin-left, $sizes, $options, 'box');
}
@mixin margin-right($sizes, $options: empty-map()) {
  @include fluid(margin-right, $sizes, $options, 'box');
}
@mixin padding($sizes, $options: empty-map()) {
  @include fluid(padding, $sizes, $options, 'box');
}
@mixin padding-top($sizes, $options: empty-map()) {
  @include fluid(padding-top, $sizes, $options, 'box');
}
@mixin padding-bottom($sizes, $options: empty-map()) {
  @include fluid(padding-bottom, $sizes, $options, 'box');
}
@mixin padding-left($sizes, $options: empty-map()) {
  @include fluid(padding-left, $sizes, $options, 'box');
}
@mixin padding-right($sizes, $options: empty-map()) {
  @include fluid(padding-right, $sizes, $options, 'box');
}
