Motion
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © cryptolinx - jango_blockchained - open-source 💙

// https://www.tradingview.com/x/DiuimTu4/

// ———— Primary Functions {
//
// ▸ transition()
// ▸ iteration()
// }

// ———— Simplified Functions {
//
// ▸ marquee()                  ▸ slideInLeft()             ▸ slideInRight()
// ▸ blink()                    ▸ slideOutLeft()            ▸ slideOutRight()
// }

// ———— Secondary Functions 
//                              
// ▸ start()                    ▸ reset()                   ▸ next()
// ▸ stop()                     ▸ reverse()                 ▸ prev()
// ▸ pause()                    ▸ toggle()                  
// }

// ———— Utility Functions 
//                              
// ▸ _onFirst()                 ▸ _onOpen()                 ▸ _setupTransition()        ▸ _controller()
// ▸ _onUpdate()                ▸ _onClose()                ▸ _setupTimer()             ▸ nz()
// ▸ _onSetup()                 ▸ _onProcess()              ▸ _updateKeyframe()         ▸ __()
// }

// ———— Types {
//
// ▸ <keyframe>
// ▸ <transition>
// ▸ <timer>
// ▸ <brackets>
// }


//@version=5
library("Motion", overlay = true)


//  ▪ TYPES ────
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

// ———— Type Transition {
//
// @type transition A transition object.
// @param seq Set the sequence.
// @param fx Set the effect.
// @param max_loops Set the maximum loops.
// @param ws Set the amount of trailing white spaces.
// @param sub_start Set the subsequence start.
// @param sub_length Set the subsequence length.
// @param refill Set the refill state.
// @param reset_on_every_bar Set the reset state.
// @param prefix Set the prefix.
// @param suffix Set the suffix.
// @param seq_arr Set the sequence array.
// @param action Stores the action.
// @param dir Stores the direction.
// @param side Stores the side.
// @param init_index Stores the initial index.
// @param length The sequence length.
// --
export type transition
    // --
    string seq = na
    string fx = 'marquee'
    int max_loops = 0
    int ws = 0
    int sub_start = 0
    int sub_length = 0
    bool refill = true
    bool reset_on_every_bar = false
    // --
    string prefix
    string suffix
    array <string> seq_arr = na
    //--
    string action
    string dir
    string side
    // -- 
    int init_index = bar_index
    int length = 0
// }

// ----------------------------------------------------------------------------------------------

// ———— Type Timer {
//
// @type timer A timer object.
// @param type Set the type `on_time` represents tick based and time filtered  on_tick represents tick-based calc.
// @param mu Set the time unit.
// @param mode Set the mode.
// @param start Set the starting point value (timenow/bar_index).
// --
export type timer
    // --
    string type = 'on_tick' // represents tick based and time filtered calc. | on_tick represents tick-based calc.
    float mu = 1000
    string mode_start = 'now' // on_open | on_close
    int mode_calc = 0 // 0 = relative, 1 = absolute < todo
    // --
    int start = 0
// }

// ----------------------------------------------------------------------------------------------

// ———— Type Keyframe {
//
// @type keyframe A keyframe object.
// @param intv Set the interval (Δt). [0;∞[
// @param step Set the steps per execution.
// @param ltr Set the direction.
// @param update_no Set the update number.
// @param frame_no Set the frame number.
// @param seq_no Set the sequence number.
// @param loop_no Set the loop number.
// @param pointer Set the pointer.
// @param offset Set the offset.
// @param setup Set the setup state.
// @param started Set the started state.
// @param execution Start/Stop motion effect.
// @param __transition Set the transition.
// @param __timer Set the timer.
// @param output Set the output.
// --
export type keyframe
    // --
    int intv = 0
    int steps = 1
    bool ltr = na
    // --
    int update_no = 0
    int frame_no = 0
    int seq_no = 0
    int loop_no = 0
    int pointer = 0
    int offset = 0
    // --
    bool setup = na
    bool started = na
    bool execution = na
    // --
    transition __transition
    timer __timer
    // --
    string state = na
    int index = na
    // --
    string output
// }

// ----------------------------------------------------------------------------------------------

// ———— Type Brackets {
//
// @type brackets A brackets object.
// @param sws Set secured white spaces.
// @param us Set underscore.
// @param vd Set vertical divider.
// @param mu Set mu.
// @param empty Set empty.
// @param none Set none.
// --
type brackets
    // --
    string sws // needs to be initialized. WHite spaces in default arguments gets removed.
    string us = '_' // underscore
    string vd = '|' // vertical divider
    string mu = 'µ' // mu
    string empty = ''
    string none = na
// }


//  ▪ UTILITY FUNCTIONS ────
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

// ———— Shadowing Build-In Function nz() {
//
// @function nz Replace `na` with a default value.
// @param _src Set source value.
// @param _replacement Set replacement value.
// @returns string, transition, timer, keyframe, array <string>
// --
nz(string _src, string _replacement) => na(_src) or _src == string(na) ? _replacement : _src
nz(transition _src, transition _replacement) => na(_src) ? _replacement : _src
nz(timer _src, timer _replacement) => na(_src) ? _replacement : _src
nz(keyframe _src, keyframe _replacement) => na(_src) ? _replacement : _src
nz(array <string> _src, array <string> _replacement) => na(_src) ? _replacement : _src
// }

// ----------------------------------------------------------------------------------------------

// ———— Get Mono-Spaced String {
//
// @function _ws Get a mono-spaced string.
// @param _str Used string.
// @param _len Length of the string.
// @returns string
// --
__(int _len, string _str) =>
    // >>
    array.join(array.new<string>(_len, _str))
// }

// ----------------------------------------------------------------------------------------------

// ———— Set State {
//
// @function _setState Set the state. []
// @param this Main `<keyframe>` object.
// @param _len Length of the string.
// @returns string
// --
_setState(keyframe this, // *required
 string state = na, int index = bar_index) =>
    // >>
    this.state := nz(state, this.state), this.index := nz(index, this.index)
// }

// ----------------------------------------------------------------------------------------------

// ———— On Setup {   << [deprecated]
//
// @function _onSetup Executed only once, at start, per keyframe setup.
// @param this Main `<keyframe>` object.
// @returns bool
// --
_onSetup(keyframe this) =>
    // >>
    barstate.isnew and not this.setup
// }

// ———— On Process {
//
// @function _onProcess Executed on every bar update.
// @param this Main `<keyframe>` object.
// @returns bool
// --
_onProcess(keyframe this) =>
    // >>
    (this.intv == 0 or this.update_no % this.intv == 0) ? barstate.islast : false
// }

// ———— On Open {
//
// @function _onOpen Executed only once on bar open
// @param this Main `<keyframe>` object.
// @returns bool
// --
_onOpen() =>
    // >>
    barstate.islast and barstate.isnew // varip 
// }

// ———— On Close {
//
// @function _onClose Executed only once on bar close.
// @param this Main `<keyframe>` object.
// @returns bool
// --
_onClose() =>
    // >>
    barstate.islast and barstate.isconfirmed // varip
// }

// ----------------------------------------------------------------------------------------------

// ———— On Start {
//
// @function _onStart Executed on event.
// @param this Main `<keyframe>` object.
// @param _event Set the event.
// @returns bool
// --
_onStart(keyframe this) =>
    // >>
    if barstate.islast and not this.started
        // --
        this.__timer.mode_start == 'on_open' ? _onOpen() : this.__timer.mode_start == 'on_close' ? _onClose() : true

// }

// ----------------------------------------------------------------------------------------------

// ———— On Index {
//
// @function _getIndex Returns the next `update_no` from an interval per bar.
// @param this Main `<keyframe>` object.
// @returns initial
// --
_getIndex(this) =>
    // >>
    bar_index - this.__timer.start
// }

// ———— On Time {
//
// @function _getTime Returns the next `update_no` from a tick based time interval.
// @param this Main `<keyframe>` object.
// @returns int
// --
_getTime(this) =>
    // >>
    int((timenow - this.__timer.start) / this.__timer.mu)
// }

// ———— On Time {
//
// @function _getTime Returns the next `update_no` from a tick based interval.
// @param this Main `<keyframe>` object.
// @returns int
// --
_getTick(this) =>
    // >>
    this.update_no + 1
// }

// ----------------------------------------------------------------------------------------------

// ———— Function Get Update No {
//
// @function _getUpdateNumber Get the update number.
// @param this Main `<keyframe>` object.
// @returns int
// --
_getUpdateNumber(keyframe this) =>
    // >>
    this.update_no := switch this.__timer.type
        // --
        'on_index' => _getIndex(this)
        'on_time' => _getTime(this)
        // --
        => _getTick(this)
// }

// ----------------------------------------------------------------------------------------------

// ———— Function Setup Timer {
//
// @function _setupTimer Setup the timer object.
// @param this Main `<keyframe>` object.
// @returns bool
// --
_setupTimer(keyframe this) =>
    // >>
    this.__timer.start := if this.__timer.mode_calc == 1
        switch this.__timer.type
            // >>
            'on_time' => timenow
            => bar_index // 'on_index' and 'on_tick'
    else
        0
// }

// ———— Function Setup Keyframe {
//
// @function _setupTransition Setup the keyframe object.
// @param this Main `<keyframe>` object.
// @returns bool
// --
_setupTransition(keyframe this) =>
    // --
    var __ = brackets.new(' ')
    // --
    transition t = this.__transition // ref
    array <string> transProp = str.split(t.fx, __.us)
    t.action := array.get(transProp, 0)
    t.dir := array.size(transProp) >= 2 ? array.get(transProp, 1) : t.dir
    t.side := array.size(transProp) == 3 ? array.get(transProp, 2) : t.side
    // --
    t.sub_length := t.sub_length == 0 ? t.length : t.sub_length
    // >>
    this.setup := true
// }

// ----------------------------------------------------------------------------------------------

// ———— Function Update Keyframe {
//
// @function _updateKeyframe Setup the keyframe object.
// @param this Main `<keyframe>` object.
// @returns bool
// --
_updateKeyframe(keyframe this) =>
    // --
    transition t = this.__transition // ref
    // --
    this.frame_no := math.floor(this.update_no / this.intv)
    this.loop_no := math.floor(this.frame_no / t.length)
    // --
    if _onOpen() and this.__transition.reset_on_every_bar
        // --
        this.update_no := 0, this.frame_no := 0, this.loop_no := 0, this.pointer := 0, t.init_index := bar_index
        _setupTimer(this)
    // --
    this.setup and this.started and this.execution
// }

// ----------------------------------------------------------------------------------------------

// ———— Execution Controller {
//
// @function _controller Controller.
// @param this Main `<keyframe>` object.
// @returns bool
// --
_controller(keyframe this) => // *required
    // --
    _getUpdateNumber(this)
    // --
    switch
        // --
        _onSetup(this) => _setupTimer(this), _setupTransition(this), false
        _onStart(this) => this.started := true, false
        _onProcess(this) => _updateKeyframe(this)
        => false
// }


//  ▪ PRIMARY FUNCTIONS ────
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

// ———— Function Start Execution {
//
// @function start Starts execution.
// @param this Main `<keyframe>` object.
// @returns bool
// --
export start(keyframe this) => this.execution := true, this.started := true
// }

// ———— Function Stop Execution {
//
// @function stop Stops execution. 
// @param this Main `<keyframe>` object.
// @returns bool
// --
export stop(keyframe this) => this.execution := false
// }

// ———— Function Reset Metadata {
//
// @function stop executions/Starts execution.
// @param this Main `<keyframe>` object.
// @returns int
// --
export reset(keyframe this) =>
    // --
    this.__timer.start := 0, this.update_no := 0, this.frame_no := 0, this.loop_no := 0
    this.pointer := 0, this.__transition.init_index := bar_index
    // --
    _setupTimer(this)
// }

// ----------------------------------------------------------------------------------------------

// ———— Function Transition Effect {
// 
// @function marquee Generates a text effect.
// @param this Main `<keyframe>` object.
// @param _seq Optional sequence.
// @param _fx Optional effect name. If not set, the default marquee effect will be used.
// @param _ws wss between the input source.
// @param _maxLoops Optional maximum loops.
// @param _subLen Optional substring length.
// @param _subStart Optional substring start.
// @param _ltr Set the direction to left to right.
// @param _refill Optional refill.
// @param _resetOnEveryBar Optional reset on every bar.
// @param _autoplay Optional autoplay. 
// @param _prefix Optional prefix.
// @param _suffix Optional suffix.
// @param _timerType Optional timer type.
// @param _timerMu Optional timer mu.
// @param _timerMode Optional timer calculation mode.
// @returns string
// --
export transition(keyframe this, // *required
 string _seq = na, string _fx = 'marquee', int _ws = 0, int _maxLoops = 0, int _subLen = 0, int _subStart = 0,
 bool _ltr = false, bool _refill = true, bool _resetOnEveryBar = false, bool _autoplay = true, string _prefix = na,
 string _suffix = na, string _timerType = 'on_tick', float _timerMu = 1000, string _timerStart = 'now', int _timerMode = 1) =>
    // --
    var __ = brackets.new(' ')
    // -- init once.
    this.__timer := nz(this.__timer, timer.new(_timerType, _timerMu, _timerStart, _timerMode))
    this.__transition := nz(this.__transition, transition.new(fx = _fx, max_loops = _maxLoops, ws = _ws, 
     sub_start = _subStart, sub_length = _subLen, refill = _refill, reset_on_every_bar = _resetOnEveryBar,
     prefix = nz(_prefix, __.empty), suffix = nz(_suffix, __.empty)))
    transition t = this.__transition // -- short-hand (object reference <3)
    t.length := str.length(nz(_seq, t.seq)) + t.ws
    this.ltr := nz(this.ltr, _ltr), this.execution := nz(this.execution, _autoplay) // -- keyframe first
    // --
    if _controller(this)
        // --
        this.ltr := t.fx == 'marquee' ? this.ltr 
         : t.action == 'slide' and ((t.dir == 'in' and t.side == 'left') or (t.dir == 'out' and t.side == 'right')) ? true
         : t.action == 'blend' and ((t.dir == 'out' and t.side == 'left') or (t.dir == 'in' and t.side == 'right')) ? true
         : false
        this.pointer := math.abs((this.ltr ? t.length - 1 : 0)
         - (((this.frame_no + this.offset) * this.steps) % t.length)) + (t.side == 'right' ? 1 : 0)
    // >>
    // this.output := 
    t.prefix + str.substring(
       // -- marquee effect
      (t.action == 'marquee' ? str.substring(nz(_seq, t.seq) + __(t.ws, __.sws), this.pointer, t.length) + str.substring(nz(_seq, t.seq) + __(t.ws, __.sws), 0, this.pointer)
       // -- slide effect without placeholder (dynamic string length)
     : t.action == 'slide' and not t.refill ? str.substring(nz(_seq, t.seq) + __(t.ws, __.sws), t.side == 'left' ? this.pointer : 0, t.side == 'left' ? t.length : this.pointer)
       // -- slide effect with placeholder (static string length)
     : t.action == 'slide' and t.refill ? str.substring(__(t.side == 'right' ? t.length : 0, __.sws) + nz(_seq, t.seq) + __(t.ws, __.sws)
       + __(t.side == 'left' ? t.length : 0, __.sws), this.pointer, this.pointer + t.length)
       // -- blend effect without placeholder (dynamic string length)
     : t.action == 'blend' and not t.refill ? str.substring(nz(_seq, t.seq) + __(t.ws, __.sws), t.side == 'left' ? 0 : this.pointer, t.side == 'left' ? this.pointer : t.length)
       // -- blend effect with placeholder (static string length)
     : t.action == 'blend' and t.refill ? __(t.side == 'right' ? this.pointer : 0, __.sws) + str.substring(nz(_seq, t.seq)
       + __(t.ws, __.sws),  t.side == 'left' ? 0 : this.pointer, t.side == 'left' ? this.pointer : t.length) + __(t.side == 'left' ?  t.length - this.pointer : 0, __.sws)
       // -- blink
     : t.action == 'blink' ? str.substring(nz(_seq, t.seq) + __(t.ws, __.sws) + __(t.length, __.sws), this.pointer % 2 ? 0 : t.length, this.pointer % 2 ? t.length : t.length * 2)
       // -- default
     : nz(_seq, t.seq) + __(t.ws, __.sws)
      ), t.sub_start, t.sub_start + t.sub_length) + t.suffix
// }

// ----------------------------------------------------------------------------------------------

// ———— Function Iteration Effect {
// 
// @function iteration Generates a text effect.
// @param this Main `<keyframe>` object.
// @param _seq Optional sequence.
// @param _ws wss between the input source.
// @param _maxLoops Optional maximum loops.
// @param _subLen Optional substring length.
// @param _subStart Optional substring start.
// @param _ltr Set the direction to left to right.
// @param _prefix Optional prefix.
// @param _suffix Optional suffix.
// @param _seqArr Optional sequence array.
// @param _autoplay Optional autoplay. 
// @param _resetOnEveryBar Optional reset on every bar.
// @param _timerType Optional timer type.
// @param _timerMu Optional timer mu.
// @param _timerMode Optional timer mode.
// @param _delimiter Optional One or more characters that separates the seq string.
// @returns string
// --
export iteration(keyframe this, // * required
 string _seq = na, int _ws = 0, int _maxLoops = 0, int _subLen = 0, int _subStart = 0, bool _refill = true, bool _ltr = false,
 string _prefix = na, string _suffix = na, array <string> _seqArr = na, bool _autoplay = true, bool _resetOnEveryBar = false,
 string _timerType = 'on_tick', float _timerMu = 1000, string _timerStart = 'now', int _timerMode = 1, string _delimiter = na) =>
    // --
    var __ = brackets.new(' '), int maxLength = 0
    // -- init once
    this.__timer := nz(this.__timer, timer.new(_timerType, _timerMu, _timerStart, _timerMode))
    this.__transition := nz(this.__transition, transition.new(seq = _seq, seq_arr = _seqArr, max_loops = _maxLoops,
     ws = _ws, sub_start = _subStart, sub_length = _subLen, refill = _refill, prefix = nz(_prefix, __.empty),
     suffix = nz(_suffix, __.empty), reset_on_every_bar = _resetOnEveryBar, init_index = bar_index))
    this.ltr := nz(this.ltr, _ltr), this.execution := nz(this.execution, _autoplay) // keyframe first
    transition t = this.__transition
    // --
    t.seq_arr := nz(t.seq_arr, str.split(_seq, nz(_delimiter, __.empty)))
    // --
    for [idx_, content_] in t.seq_arr
        maxLength := math.max(maxLength, str.length(content_))
    t.length := array.size(t.seq_arr)
    // --
    if _controller(this)
    // --
        this.pointer := math.abs((this.ltr ? t.length - 1 : 0) - (((this.frame_no + this.offset) * this.steps) % t.length))
    // --
    int currentLength = str.length(array.get(t.seq_arr, this.pointer))
    t.sub_length := _subLen > 0 ? _subLen : currentLength
    // >> fix here
    this.output := t.prefix + str.substring(array.get(t.seq_arr, this.pointer), t.sub_start, t.sub_start + t.sub_length)
     + __(t.refill ? maxLength - currentLength : 0, __.sws) + __(t.ws, __.sws) + t.suffix

// }

// ----------------------------------------------------------------------------------------------

// ———— Function Color Gradient Effect {
// 
// @function color_gradient Generates a color gradient effect.
// @param this Main `<keyframe>` object.
// @param _from Set Color from.
// @param _to Set Color to.
// @param _steps Optional steps.
// @param _maxLoops Optional maximum loops.
// @param _resetOnEveryBar Optional reset on every bar.
// @param _autoplay Optional autoplay. 
// @param _timerType Optional timer type.
// @param _timerMu Optional timer mu.
// @param _timerMode Optional timer calculation mode.
// @returns string
// --
export color_gradient(keyframe this, // *required
 color _color_from, color _color_to, int _transp_from = 0, int _transp_to = 0,
 int _steps = 6, int _maxLoops = 0, bool _resetOnEveryBar = false, bool _autoplay = true, bool _cycle = true,
 string _timerType = 'on_tick', float _timerMu = 1000, string _timerStart = 'now', int _timerMode = 1) =>
    // -- init once
    this.__timer := nz(this.__timer, timer.new(_timerType, _timerMu, _timerStart, _timerMode))
    this.__transition := nz(this.__transition, transition.new(fx = 'color', max_loops = _maxLoops, length = _steps,
     reset_on_every_bar = _resetOnEveryBar))
    this.execution := nz(this.execution, _autoplay) // -- keyframe first
    // --
    if _controller(this)
        // --
        this.ltr := _cycle and this.loop_no % 2 ? false : true // oscillating color
        this.pointer := math.abs((this.ltr ? _steps : 0) - (((this.frame_no + this.offset) * this.steps) % _steps))
    // >>
    color.from_gradient(value = this.pointer, bottom_value = 0, top_value = _steps,
     bottom_color = color.new(color = _color_from, transp = _transp_from), top_color = color.new(color = _color_to, transp = _transp_to)) 
// }


//  ▪ SIMPLIFIED FUNCTIONS ────
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

// ———— Function String Marquee Effect {
// 
// @function marquee Generates a marquee effect.
// @param this Main `<keyframe>` object.
// @param _seq Optional sequence.
// @param _ws White space between the input source.
// @param _maxLoops Optional maximum loops.
// @param _ltr Set the direction to left to right.
// @returns string
// --
export marquee(keyframe this, string _seq, // *required
 int _ws = 1, int _maxLoops = 0, bool _ltr = false, string _timerType = 'on_tick') =>
    // >>
    this.output := transition(this, _seq = _seq, _fx = 'marquee', _ws = _ws, _maxLoops = _maxLoops,
     _ltr = _ltr, _timerType = _timerType)
// }

// ----------------------------------------------------------------------------------------------

// ———— Function String Blink Effect {
// 
// @function blink Generates a blink effect.
// @param this Main `<keyframe>` object.
// @param _seq Optional sequence.
// @param _ws White space between the input source.
// @param _maxLoops Optional maximum loops.
// @returns string
// --
export blink(keyframe this, string _seq, // *required
 int _ws = 0, int _maxLoops = 0, string _timerType = 'on_tick') =>
    // >>
    this.output := transition(this, _seq = _seq, _fx = 'blink', _ws = _ws, _maxLoops = _maxLoops, _timerType = _timerType)
// }

// ----------------------------------------------------------------------------------------------

// ———— Function Color Fade In Effect {
// 
// @function fadeIn Generates a fade in effect.
// @param this Main `<keyframe>` object.
// @param _color_from Set Color from.
// @param _color_to Set Color to.
// @param _steps Optional steps.
// @param _transp_from Optional transparency from.
// @param _transp_to Optional transparency to.
// @param _maxLoops Optional maximum loops.
// @param _timerType Optional timer type.
// @returns color
// --
export fadeIn(keyframe this, color _color_from, color _color_to, // *required
 int _steps = 5, int _transp_from = 100, int _transp_to = 0, int _maxLoops = 0, string _timerType = 'on_tick') =>
    // >>
    // this.output_color :=
    color_gradient(this, _color_from = _color_from, _color_to = _color_to, _steps = _steps,
     _transp_from = _transp_from, _transp_to = _transp_to, _maxLoops = _maxLoops, _timerType = _timerType)
// }

// ———— Function Color Fade Out Effect {
// 
// @function fadeOut Generates a fade out effect.
// @param this Main `<keyframe>` object.
// @param _color_from Set Color from.
// @param _color_to Set Color to.
// @param _steps Optional steps.
// @param _transp_from Optional transparency from.
// @param _transp_to Optional transparency to.
// @param _maxLoops Optional maximum loops.
// @param _timerType Optional timer type.
// @returns color
// --
export fadeOut(keyframe this, color _color_from, color _color_to, // *required
 int _steps = 5, int _transp_from = 0, int _transp_to = 100, int _maxLoops = 0, string _timerType = 'on_tick') =>
    // >>
    // this.output_color := 
    color_gradient(this, _steps = _steps, _color_from = _color_from, _color_to = _color_to,
     _transp_from = _transp_from, _transp_to = _transp_to, _maxLoops = _maxLoops, _timerType = _timerType)
// }

// ----------------------------------------------------------------------------------------------

// ———— Function String SLide In Left Effect {
// 
// @function slideInLeft Generates a slide in left effect.
// @param this Main `<keyframe>` object.
// @param _seq Optional sequence.
// @param _ws White space between the input source.
// @param _maxLoops Optional maximum loops.
// @param _refill Optional refill.
// @returns string
// --
export slideInLeft(keyframe this, string _seq, // *required
 int _ws = 0, int _maxLoops = 0, bool _refill = true, string _timerType = 'on_tick') =>
    // >>
    this.output := transition(this, _seq = _seq, _fx = 'slide_in_left', _ws = _ws, _maxLoops = _maxLoops,
     _refill = _refill, _timerType = _timerType)
// }

// ———— Function String Slide Out Left Effect {
// 
// @function slideOutLeft Generates a slide out left effect.
// @param this Main `<keyframe>` object.
// @param _seq Optional sequence.
// @param _ws White space between the input source.
// @param _maxLoops Optional maximum loops.
// @returns string
// --
export slideOutLeft(keyframe this, string _seq, // *required
 int _ws = 0, int _maxLoops = 0, bool _refill = true, string _timerType = 'on_tick') =>
    // >>
    this.output := transition(this, _seq = _seq, _fx = 'slide_out_left', _ws = _ws, _maxLoops = _maxLoops,
     _refill = _refill, _timerType = _timerType)
// }

// ———— Function String Slide In Right Effect {
// 
// @function slideInRight Generates a slide in right effect.
// @param this Main `<keyframe>` object.
// @param _seq Optional sequence.
// @param _ws White space between the input source.
// @param _maxLoops Optional maximum loops.
// @param _refill Optional refill.
// @returns string
// --
export slideInRight(keyframe this, string _seq, // *required
 int _ws = 0, int _maxLoops = 0, bool _refill = true, string _timerType = 'on_tick') =>
    // >>
    this.output := transition(this, _seq = _seq, _fx = 'slide_in_right', _ws = _ws, _maxLoops = _maxLoops,
     _refill = _refill, _timerType = _timerType)
// }

// ———— Function String Slide Out Right Effect {
// 
// @function slideOutRight Generates a slide out right effect.
// @param this Main `<keyframe>` object.
// @param _seq Optional sequence.
// @param _ws White space between the input source.
// @param _maxLoops Optional maximum loops.
// @param _refill Optional refill.
// @returns string
// --
export slideOutRight(keyframe this, string _seq, // *required
 int _ws = 0, int _maxLoops = 0, bool _refill = true, string _timerType = 'on_tick') =>
    // >>
    this.output := transition(this, _seq = _seq, _fx = 'slide_out_right', _ws = _ws, _maxLoops = _maxLoops,
     _refill = _refill, _timerType = _timerType)
// }

// ----------------------------------------------------------------------------------------------

// ———— Function String Blend In Left Effect {
// 
// @function blendInLeft Generates a blend in left effect.
// @param this Main `<keyframe>` object.
// @param _seq Optional sequence.
// @param _ws White space between the input source.
// @param _maxLoops Optional maximum loops.
// @param _refill Optional refill.
// @returns string
// --
export blendInLeft(keyframe this, string _seq, // *required
 int _ws = 0, int _maxLoops = 0, bool _refill = true, string _timerType = 'on_tick') =>
    // >>
    this.output := transition(this, _seq = _seq, _fx = 'blend_in_left', _ws = _ws, _maxLoops = _maxLoops,
     _refill = _refill, _timerType = _timerType)
// }

// ———— Function String Blend Out Left Effect {
// 
// @function blendInLeft Generates a blend out left effect.
// @param this Main `<keyframe>` object.
// @param _seq Optional sequence.
// @param _ws White space between the input source.
// @param _maxLoops Optional maximum loops.
// @param _refill Optional refill.
// @returns string
// --
export blendOutLeft(keyframe this, string _seq, // *required
 int _ws = 0, int _maxLoops = 0, bool _refill = true, string _timerType = 'on_tick') =>
    // >>
    this.output := transition(this, _seq = _seq, _fx = 'blend_out_left', _ws = _ws, _maxLoops = _maxLoops,
     _refill = _refill, _timerType = _timerType)
// }

// ———— Function String Blend In Right Effect {
// 
// @function blendInRight Generates a blend in right effect.
// @param this Main `<keyframe>` object.
// @param _seq Optional sequence.
// @param _ws White space between the input source.
// @param _maxLoops Optional maximum loops.
// @param _refill Optional refill.
// @returns string
// --
export blendInRight(keyframe this, string _seq, // *required
 int _ws = 0, int _maxLoops = 0, bool _refill = true, string _timerType = 'on_tick') =>
    // >>
    this.output := transition(this, _seq = _seq, _fx = 'blend_in_right', _ws = _ws, _maxLoops = _maxLoops,
     _refill = _refill, _timerType = _timerType)
// }

// ———— Function String Blend Out Right Effect {
// 
// @function blendInLeft Generates a blend out right effect.
// @param this Main `<keyframe>` object.
// @param _seq Optional sequence.
// @param _ws White space between the input source.
// @param _maxLoops Optional maximum loops.
// @param _refill Optional refill.
// @returns string
// --
export blendOutRight(keyframe this, string _seq, // *required
 int _ws = 0, int _maxLoops = 0, bool _refill = true, string _timerType = 'on_tick') =>
    // >>
    this.output := transition(this, _seq = _seq, _fx = 'blend_out_right', _ws = _ws, _maxLoops = _maxLoops,
     _refill = _refill, _timerType = _timerType)
// }

//  ▪ EXAMPLE ────
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

/// ———— User Input {
//
// ⚠️ IMPORTANT: Example Type. NOT REQUIRED.
//
type user_input
    // --
    string fx
    bool play
    string timing
    int intv
    int mu
    int step
    bool reset
    int max_loops
    int sub_start
    int sub_length
    bool ltr
    bool refill
    string prefix
    string txt
    int ws
    string suffix
    int color_steps
    bool color_cycle
    color color_from
    color color_to
    string mode_start
    int mode_calc
// --
var __settings = user_input.new(
 fx = input.string('marquee', 'Transition', options = ['marquee', 'blink', 'blend_in_right', 'blend_out_right', 'blend_in_left', 'blend_out_left', 'slide_in_left', 'slide_in_right', 'slide_out_left', 'slide_out_right'], group = 'ACTION'),
 play = input.bool(true, 'Play', group = 'ACTION'),
 timing = input.string('on_time', 'Event Producer', options = ['on_tick', 'on_time', 'on_index'], group = 'TIMING'),
 intv = input.int(1, title = 'Interval (Δt)', step = 1, minval = 1, maxval = 100, tooltip = 'Delta Time [1;∞[', group = 'TIMING'),
 mu = input.int(1000, title = 'onTime - Fineness (μ)', step = 50, minval = 50, maxval = 2000, tooltip = '[50-2000] 1000 = default. lower value = higher freq.', group = 'TIMING'),
 step = input.int(1, title = 'Steps Per Execution', step = 1, minval = 1, maxval = 20, group = 'PREFERENCES'),
 reset = input.bool(false, title = 'Reset On Every Bar', group = 'PREFERENCES'),
 max_loops = input.int(0, title = 'Max. Loops', step = 1, minval = 0, maxval = 100, tooltip = '0 = infinite', group = 'PREFERENCES'),
 sub_start = input.int(0, title = 'Subsequence Start', step = 1, minval = 0, maxval = 20, tooltip = '[ABCDEF]GHI...\n|      |\n0     5', group = 'PREFERENCES'),
 sub_length = input.int(0, title = 'Subsequence Length', step = 1, minval = 0, maxval = 20, group = 'PREFERENCES'),
 refill = input.bool(true, 'Placeholder String', group = 'PREFERENCES', tooltip = 'true = default; false = slide effect without placeholder (+/- string length)'),
 ltr = input.bool(false, title = 'Left To Right', group = 'ONLY MARQUEE'),
 prefix = input.string('', 'Prefix', group = 'CONTENT'),
 txt = input.string('ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789', 'Text', group = 'CONTENT'),
 ws = input.int(0, title = 'Trailing White Space', step = 1, minval = 0, maxval = 10, group = 'CONTENT'),
 suffix = input.string('', 'Suffix', group = 'CONTENT'),
 color_steps = input.int(6, title = 'Gradient Steps', step = 1, minval = 1, maxval = 20, group = 'COLOR'),
 color_cycle = input.bool(true, title = 'Color Cycle', group = 'COLOR'),
 color_from = input.color(color.rgb(0, 57, 212), 'Color From', group = 'COLOR'),
 color_to = input.color(color.rgb(59, 209, 255), 'Color To', group = 'COLOR'),
 mode_start = 'now', // input.string('now', 'Effect Start', options = ['now', 'on_open', 'on_close'], group = 'ADVANCED/DEVELOPMENT'),
 mode_calc = 1) // input.int(1, 'Calculation Mode', minval  = 0, maxval = 1, step = 1, tooltip = '0 = relative; 1 = absolute', group = 'ADVANCED/DEVELOPMENT'))
// }

// ———— Motion 🪄 {
//
// ⚠️ IMPORTANT:  The keyframe object must be created by using the `varip` keyword. 
//
varip transitionKf = keyframe.new(intv = __settings.intv, steps = __settings.step, execution = __settings.play) // create keyframe
string transitionString = transition(transitionKf, _seq = __settings.txt, _fx = __settings.fx, _ws = __settings.ws, _maxLoops = __settings.max_loops,
 _subLen = __settings.sub_length, _subStart = __settings.sub_start, _ltr = __settings.ltr, _resetOnEveryBar = __settings.reset, _autoplay = __settings.play,
 _refill = __settings.refill, _timerType = __settings.timing, _timerMu = __settings.mu, _timerMode = __settings.mode_calc, _prefix = __settings.prefix, _suffix = __settings.suffix)
// --
varip iterationKf = keyframe.new(intv = 3, steps = __settings.step, execution = __settings.play)
iteration(iterationKf, _seqArr = array.from('THIS','IS','AN','ITERATION','EFFECT!'), _ws = __settings.ws,
 _subLen = __settings.sub_length, _subStart = __settings.sub_start, _ltr = __settings.ltr, _resetOnEveryBar = __settings.reset, _autoplay = false, //__settings.play,
 _refill = __settings.refill, _timerType = __settings.timing, _timerMu = __settings.mu, _timerMode = __settings.mode_calc, _prefix = __settings.prefix, _suffix = __settings.suffix)
// --
varip colorKf = keyframe.new(intv = __settings.intv, steps = __settings.step, execution = __settings.play)
color gradient = fadeOut(colorKf, color.red, color.yellow)//color_gradient(colorKf, __settings.color_from, __settings.color_to, _steps = __settings.color_steps, _cycle = __settings.color_cycle, _timerType = __settings.timing)
// }

// ———— Label {
//
var select = label.new(bar_index, 750, text = 'Double click, to select a transition effect from input:', textcolor = color.white, color = #ffffff0b, size = size.normal, style = label.style_label_center)
var fx = label.new(bar_index, 700, text = __settings.fx, textcolor = #AAFF00, color = #ffffff0b, size = size.normal, style = label.style_label_center, textalign = text.align_center, text_font_family = font.family_default)
var openSource = label.new(bar_index, 300, text = 'open 💙 source', textcolor = color.white, color = #ffffff0b, size = size.normal, style = label.style_label_center)
var example = label.new(bar_index, 600, text = '', textcolor = color.white, color = #ffffff0b, size = size.huge, style = label.style_label_center, textalign = text.align_center, text_font_family = font.family_monospace)
var itr = label.new(bar_index, 450, text = '', textcolor = color.white, color = #ffffff0b, size = size.normal, style = label.style_label_center, textalign = text.align_center, text_font_family = font.family_monospace)
// --

label.set_x(select, bar_index), label.set_x(fx, bar_index), label.set_x(example, bar_index), label.set_x(openSource, bar_index), label.set_x(itr, bar_index),
label.set_text(fx, __settings.fx), label.set_text(example, transitionString), label.set_text(itr, iterationKf.output)
label.set_textcolor(itr, gradient)
// }


// } #EOF