diff --git a/IPython/frontend/html/notebook/static/css/notebook.css b/IPython/frontend/html/notebook/static/css/notebook.css
index 88f9259..b3d4f97 100644
--- a/IPython/frontend/html/notebook/static/css/notebook.css
+++ b/IPython/frontend/html/notebook/static/css/notebook.css
@@ -273,6 +273,13 @@ div.text_cell_render {
     font-family: monospace;
 }
 
+option.context {
+  background-color: #DEF7FF;
+}
+option.introspection {
+  background-color: #EBF4EB;
+}
+
 @-moz-keyframes fadeIn {
     from {opacity:0;}
     to {opacity:1;}
diff --git a/IPython/frontend/html/notebook/static/js/codecell.js b/IPython/frontend/html/notebook/static/js/codecell.js
index 5119461..b29b6ed 100644
--- a/IPython/frontend/html/notebook/static/js/codecell.js
+++ b/IPython/frontend/html/notebook/static/js/codecell.js
@@ -10,7 +10,6 @@
 //============================================================================
 
 var IPython = (function (IPython) {
-
     var utils = IPython.utils;
 
     var CodeCell = function (notebook) {
@@ -23,6 +22,8 @@ var IPython = (function (IPython) {
         this.tooltip_timeout = null;
         this.clear_out_timeout = null;
         IPython.Cell.apply(this, arguments);
+        var that = this;
+        this.ccc = new IPython.Completer(function(ed, callback){that.requestCompletion(ed, callback)});
     };
 
 
@@ -40,8 +41,11 @@ var IPython = (function (IPython) {
             mode: 'python',
             theme: 'ipython',
             readOnly: this.read_only,
-            onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
+            onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this),
         });
+        var that = this;
+        ccm = this.code_mirror;
+        ccc = this.ccc;
         input.append(input_area);
         var output = $('<div></div>').addClass('output vbox');
         cell.append(input).append(output);
@@ -129,13 +133,9 @@ var IPython = (function (IPython) {
                 // Prevent CodeMirror from handling the tab.
                 return true;
             } else {
-                pre_cursor.trim();
-                // Autocomplete the current line.
                 event.stop();
-                var line = editor.getLine(cur.line);
-                this.is_completing = true;
-                this.completion_cursor = cur;
-                IPython.notebook.complete_cell(this, line, cur.ch);
+                this.ccc.startCompletionFor(this.code_mirror);
+
                 return true;
             };
         } else if (event.keyCode === 8 && event.type == 'keydown') {
@@ -263,285 +263,39 @@ var IPython = (function (IPython) {
     };
 
     // As you type completer
-    CodeCell.prototype.finish_completing = function (matched_text, matches) {
-        if(matched_text[0]=='%'){
-            completing_from_magic = true;
-            completing_to_magic = false;
-        } else {
-            completing_from_magic = false;
-            completing_to_magic = false;
-        }
-        //return if not completing or nothing to complete
-        if (!this.is_completing || matches.length === 0) {return;}
-
-        // for later readability
-        var key = { tab:9,
-                    esc:27,
-                    backspace:8,
-                    space:32,
-                    shift:16,
-                    enter:13,
-                    // _ is 95
-                    isCompSymbol : function (code)
-                        {
-                        return (code > 64 && code <= 90)
-                            || (code >= 97 && code <= 122)
-                            || (code == 95)
-                        },
-                    dismissAndAppend : function (code)
-                        {
-                        chararr = '()[]+-/\\. ,=*'.split("");
-                        codearr = chararr.map(function(x){return x.charCodeAt(0)});
-                        return jQuery.inArray(code, codearr) != -1;
-                        }
-
-                    }
-
-        // smart completion, sort kwarg ending with '='
-        var newm = new Array();
-        if(this.notebook.smart_completer)
-        {
-            kwargs = new Array();
-            other = new Array();
-            for(var i = 0 ; i<matches.length ; ++i){
-                if(matches[i].substr(-1) === '='){
-                    kwargs.push(matches[i]);
-                }else{other.push(matches[i]);}
-            }
-            newm = kwargs.concat(other);
-            matches = newm;
-        }
-        // end sort kwargs
-
-        // give common prefix of a array of string
-        function sharedStart(A){
-            shared='';
-            if(A.length == 1){shared=A[0]}
-            if(A.length > 1 ){
-                var tem1, tem2, s, A = A.slice(0).sort();
-                tem1 = A[0];
-                s = tem1.length;
-                tem2 = A.pop();
-                while(s && tem2.indexOf(tem1) == -1){
-                    tem1 = tem1.substring(0, --s);
-                }
-                shared = tem1;
-            }
-            if (shared[0] == '%' && !completing_from_magic)
-            {
-                shared = shared.substr(1);
-                return [shared, true];
-            } else {
-                return [shared, false];
-            }
-        }
-
-
-        //try to check if the user is typing tab at least twice after a word
-        // and completion is "done"
-        fallback_on_tooltip_after = 2
-        if(matches.length == 1 && matched_text === matches[0])
-        {
-            if(this.npressed >fallback_on_tooltip_after  && this.prevmatch==matched_text)
-            {
-                this.request_tooltip_after_time(matched_text+'(',0);
-                return;
-            }
-            this.prevmatch = matched_text
-            this.npressed = this.npressed+1;
-        }
-        else
-        {
-            this.prevmatch = "";
-            this.npressed = 0;
-        }
-        // end fallback on tooltip
-        //==================================
-        // Real completion logic start here
-        var that = this;
-        var cur = this.completion_cursor;
-        var done = false;
-
-        // call to dismmiss the completer
-        var close = function () {
-            if (done) return;
-            done = true;
-            if (complete != undefined)
-            {complete.remove();}
-            that.is_completing = false;
-            that.completion_cursor = null;
-        };
-
-        // update codemirror with the typed text
-        prev = matched_text
-        var update = function (inserted_text, event) {
-            that.code_mirror.replaceRange(
-                inserted_text,
-                {line: cur.line, ch: (cur.ch-matched_text.length)},
-                {line: cur.line, ch: (cur.ch+prev.length-matched_text.length)}
-            );
-            prev = inserted_text
-            if(event != null){
-                event.stopPropagation();
-                event.preventDefault();
-            }
-        };
-        // insert the given text and exit the completer
-        var insert = function (selected_text, event) {
-            update(selected_text)
-            close();
-            setTimeout(function(){that.code_mirror.focus();}, 50);
-        };
-
-        // insert the curent highlited selection and exit
-        var pick = function () {
-            insert(select.val()[0],null);
-        };
-
+    CodeCell.prototype.requestCompletion= function(ed,callback)
+    {
+        this._compcallback = callback;
+        this._editor = ed;
+        var cur = ed.getCursor();
+        var pre_cursor = this.code_mirror.getRange({line:cur.line,ch:0},cur);
+        pre_cursor.trim();
+        // Autocomplete the current line.
+        var line = this.code_mirror.getLine(cur.line);
+        this.is_completing = true;
+        this.completion_cursor = cur;
+        IPython.notebook.complete_cell(this, line, cur.ch);
+    }
 
-        // Define function to clear the completer, refill it with the new
-        // matches, update the pseuso typing field. autopick insert match if
-        // only one left, in no matches (anymore) dismiss itself by pasting
-        // what the user have typed until then
-        var complete_with = function(matches,typed_text,autopick,event)
+    CodeCell.prototype.finish_completing = function (matched_text, matches) {
+        // let's build a function that wrap all that stuff into what is needed for the
+        // new completer:
+        //
+        var cur = this._editor.getCursor();
+        res = CodeMirror.contextHint(this._editor);
+        for( i=0; i< matches.length ; i++)
         {
-            // If autopick an only one match, past.
-            // Used to 'pick' when pressing tab
-            var prefix = '';
-            if(completing_to_magic && !completing_from_magic)
-            {
-                prefix='%';
-            }
-            if (matches.length < 1) {
-                insert(prefix+typed_text,event);
-                if(event != null){
-                event.stopPropagation();
-                event.preventDefault();
-                }
-            } else if (autopick && matches.length == 1) {
-                insert(matches[0],event);
-                if(event != null){
-                event.stopPropagation();
-                event.preventDefault();
-                }
-                return;
-            }
-            //clear the previous completion if any
-            update(prefix+typed_text,event);
-            complete.children().children().remove();
-            $('#asyoutype').html("<b>"+prefix+matched_text+"</b>"+typed_text.substr(matched_text.length));
-            select = $('#asyoutypeselect');
-            for (var i = 0; i<matches.length; ++i) {
-                    select.append($('<option/>').html(matches[i]));
-            }
-            select.children().first().attr('selected','true');
-        }
-
-        // create html for completer
-        var complete = $('<div/>').addClass('completions');
-            complete.attr('id','complete');
-        complete.append($('<p/>').attr('id', 'asyoutype').html('<b>fixed part</b>user part'));//pseudo input field
-
-        var select = $('<select/>').attr('multiple','true');
-            select.attr('id', 'asyoutypeselect')
-            select.attr('size',Math.min(10,matches.length));
-        var pos = this.code_mirror.cursorCoords();
-
-        // TODO: I propose to remove enough horizontal pixel
-        // to align the text later
-        complete.css('left',pos.x+'px');
-        complete.css('top',pos.yBot+'px');
-        complete.append(select);
-
-        $('body').append(complete);
-
-        // So a first actual completion.  see if all the completion start wit
-        // the same letter and complete if necessary
-        ff = sharedStart(matches)
-        fastForward = ff[0];
-        completing_to_magic = ff[1];
-        typed_characters = fastForward.substr(matched_text.length);
-        complete_with(matches,matched_text+typed_characters,true,null);
-        filterd = matches;
-        // Give focus to select, and make it filter the match as the user type
-        // by filtering the previous matches. Called by .keypress and .keydown
-        var downandpress = function (event,press_or_down) {
-            var code = event.which;
-            var autopick = false; // auto 'pick' if only one match
-            if (press_or_down === 0){
-                press = true; down = false; //Are we called from keypress or keydown
-            } else if (press_or_down == 1){
-                press = false; down = true;
-            }
-            if (code === key.shift) {
-                // nothing on Shift
-                return;
-            }
-            if (key.dismissAndAppend(code) && press) {
-                var newchar = String.fromCharCode(code);
-                typed_characters = typed_characters+newchar;
-                insert(matched_text+typed_characters,event);
-                return
-            }
-            if (code === key.enter) {
-                // Pressing ENTER will cause a pick
-                event.stopPropagation();
-                event.preventDefault();
-                pick();
-            } else if (code === 38 || code === 40) {
-                // We don't want the document keydown handler to handle UP/DOWN,
-                // but we want the default action.
-                event.stopPropagation();
-            } else if ( (code == key.backspace)||(code == key.tab && down) || press  || key.isCompSymbol(code)){
-                if( key.isCompSymbol(code) && press)
+            res.push(
                 {
-                    var newchar = String.fromCharCode(code);
-                    typed_characters = typed_characters+newchar;
-                } else if (code == key.tab) {
-                    ff = sharedStart(matches)
-                    fastForward = ff[0];
-                    completing_to_magic = ff[1];
-                    ffsub = fastForward.substr(matched_text.length+typed_characters.length);
-                    typed_characters = typed_characters+ffsub;
-                    autopick = true;
-                } else if (code == key.backspace && down) {
-                    // cancel if user have erase everything, otherwise decrease
-                    // what we filter with
-                    event.preventDefault();
-                    if (typed_characters.length <= 0)
-                    {
-                        insert(matched_text,event)
-                        return
-                    }
-                    typed_characters = typed_characters.substr(0,typed_characters.length-1);
-                } else if (press && code != key.backspace && code != key.tab && code != 0){
-                    insert(matched_text+typed_characters,event);
-                    return
-                } else {
-                    return
+                    str  : matches[i],
+                    type : "introspection",
+                    from : {line: cur.line, ch: cur.ch-matched_text.length},
+                    to   : {line: cur.line, ch: cur.ch}
                 }
-                re = new RegExp("^"+"\%?"+matched_text+typed_characters,"");
-                filterd = matches.filter(function(x){return re.test(x)});
-                ff = sharedStart(filterd);
-                completing_to_magic = ff[1];
-                complete_with(filterd,matched_text+typed_characters,autopick,event);
-            } else if (code == key.esc) {
-                // dismiss the completer and go back to before invoking it
-                insert(matched_text,event);
-            } else if (press) { // abort only on .keypress or esc
-            }
+            )
         }
-        select.keydown(function (event) {
-            downandpress(event,1)
-        });
-        select.keypress(function (event) {
-            downandpress(event,0)
-        });
-        // Double click also causes a pick.
-        // and bind the last actions.
-        select.dblclick(pick);
-        select.blur(close);
-        select.focus();
+        this._compcallback(res);
+
     };
 
 
diff --git a/IPython/frontend/html/notebook/static/js/completer.js b/IPython/frontend/html/notebook/static/js/completer.js
new file mode 100644
index 0000000..8c05cad
--- /dev/null
+++ b/IPython/frontend/html/notebook/static/js/completer.js
@@ -0,0 +1,233 @@
+// function completer.
+//
+// completer should be a class that take an editor instance, and a list of
+// function to call to get the list of completion.
+//
+// the function that send back the list of completion should received the
+// editor handle as sole argument, and should return a json object with the
+// following structure
+
+// {list: clist,     # list of n string containing the completions
+//  type : rp,       # list of n string containingtype/ origin of the completion 
+//                   # (will be set as the class of the <option> to be able to style
+//                   # them according to the origin of the completion)
+//  from: {line: cur.line, ch: token.start}, 
+//    to: {line: cur.line, ch: token.end}
+//  };
+//
+
+var IPython = (function(IPython ) {
+    // that will prevent us froom missspelling
+    "use strict";
+
+    // easyier key mapping
+    var key = { tab:9,
+                esc:27,
+                backspace:8,
+                space:32,
+                shift:16,
+                enter:13,
+                upArrow:38, // check with keyDown..
+                downArrow :40 // check with keyUp
+    };
+    
+    // what is the common start of all completions
+    function sharedStart(B){
+            if(B.length == 1){return B[0]}
+            var A = new Array()
+            for(i=0; i< B.length; i++)
+            {
+               A.push(B[i].str); 
+            }
+            if(A.length > 1 ){
+                var tem1, tem2, s, A = A.slice(0).sort();
+                tem1 = A[0];
+                s = tem1.length;
+                tem2 = A.pop();
+                while(s && tem2.indexOf(tem1) == -1){
+                    tem1 = tem1.substring(0, --s);
+                }
+                return { str  : tem1,
+                         type : "computed",
+                         from : B[0].from,
+                         to   : B[0].to
+                };
+            }
+            return null;
+        }
+
+    // user to nsert the given completion
+    var Completer = function(getHints) {
+
+        this.hintfunc = getHints;
+        // if last caractere before cursor is not in this, we stop completing
+        this.reg = /[A-Za-z.]/;
+    }
+
+    Completer.prototype.startCompletionFor = function(ed)
+    {
+        // call for a 'first' completion, that will set the editor and do some 
+        // special behaviour like autopicking if only one completion availlable
+        //
+        this.editor = ed;
+        if (this.editor.somethingSelected()) return;
+        this.done = false;
+        // use to get focus back on opera
+        this.carryOnCompletion(true);
+    }
+
+    Completer.prototype.carryOnCompletion = function(ff)
+    {
+        // pass true as parameter if you want the commpleter to autopick 
+        // when only one completion
+        // as this function is auto;atically reinvoked at each keystroke with 
+        // ff = false
+        var cur = this.editor.getCursor();
+        var pre_cursor = this.editor.getRange({line:cur.line,ch:cur.ch-1},cur);
+
+        // we nned to check that we are still on a word boundary
+        // because while typing the completer is still reinvoking itself
+        if(!this.reg.test(pre_cursor)){ this.close(); return;}
+       
+        this.autopick = false;
+        if( ff != 'undefined' && ff==true)
+        {
+            this.autopick=true;
+        }
+        // We want a single cursor position.
+        if (this.editor.somethingSelected()) return;
+
+        // there we will need to gather the results for all the function (and merge them ?)
+        // lets assume for now only one source
+        //
+        var that = this;
+        this.hintfunc(this.editor,function(result){that._resume_completion(result)});
+    }
+    Completer.prototype._resume_completion = function(results)
+    {
+        this.raw_result = results;
+
+        // if empty result return
+        if (!this.raw_result || !this.raw_result.length) return;
+
+
+
+        // When there is only one completion, use it directly.
+        if (this.autopick == true && this.raw_result.length == 1)
+            {
+                this.insert(this.raw_result[0]);
+                return true;
+            }
+
+        if (this.raw_result.length == 1)
+        {
+        // test if first and only completion totally matches 
+        // what is typed, in this case dismiss
+        var str = this.raw_result[0].str
+        var cur = this.editor.getCursor();
+        var pre_cursor = this.editor.getRange({line:cur.line,ch:cur.ch-str.length},cur);
+        if(pre_cursor == str){
+            this.close();
+            return ;
+            }
+        }
+
+        this.complete = $('<div/>').addClass('completions');
+        this.complete.attr('id','complete');
+
+        this.sel = $('<select/>').attr('multiple','true');
+        var pos = this.editor.cursorCoords();
+
+        // TODO: I propose to remove enough horizontal pixel
+        // to align the text later
+        this.complete.css('left',pos.x+'px');
+        this.complete.css('top',pos.yBot+'px');
+        this.complete.append(this.sel);
+
+        $('body').append(this.complete);
+        //build the container
+        var that = this;
+        this.sel.dblclick(function(){that.pick()});
+        this.sel.blur(this.close);
+        this.sel.keydown(function(event){that.keydown(event)});
+
+        this.build_gui_list(this.raw_result);
+        var that=this; 
+        //CodeMirror.connect(that.sel, "dblclick", function(){that.pick()});
+
+        this.sel.focus();
+        // Opera sometimes ignores focusing a freshly created node
+        if (window.opera) setTimeout(function(){if (!this.done) this.sel.focus();}, 100);
+        // why do we return true ?
+        return true;
+    }
+
+    Completer.prototype.insert = function(completion) {
+        this.editor.replaceRange(completion.str, completion.from, completion.to);
+        }
+
+    Completer.prototype.build_gui_list = function(completions){
+        // Need to clear the all list
+        for (var i = 0; i < completions.length; ++i) {
+            var opt = $('<option/>')
+                .text(completions[i].str)
+                .addClass(completions[i].type);
+            this.sel.append(opt);
+        }
+        this.sel.children().first().attr('selected','true');
+        
+        //sel.size = Math.min(10, completions.length);
+        // Hack to hide the scrollbar.
+        //if (completions.length <= 10)
+        //this.complete.style.width = (this.sel.clientWidth - 1) + "px";
+    }
+
+    Completer.prototype.close = function() {
+            if (this.done) return;
+            this.done = true;
+            $('.completions').remove();
+        }
+
+    Completer.prototype.pick = function(){
+        this.insert(this.raw_result[this.sel[0].selectedIndex]);
+        this.close();
+        var that = this;
+        setTimeout(function(){that.editor.focus();}, 50);
+        }
+
+                
+    Completer.prototype.keydown = function(event) {
+      var code = event.keyCode;
+      // Enter
+      if (code == key.enter) {CodeMirror.e_stop(event); this.pick();}
+      // Escape or backspace
+      else if (code == key.esc ) {CodeMirror.e_stop(event); this.close(); this.editor.focus();}
+      else if (code == key.space || code == key.backspace) {this.close(); this.editor.focus();}
+      else if (code == key.tab){
+            //all the fastforwarding operation, 
+            this.insert(sharedStart(this.raw_result));
+            this.close(); 
+            CodeMirror.e_stop(event);
+            this.editor.focus();
+            //reinvoke self
+            var that = this;
+            setTimeout(function(){that.carryOnCompletion();}, 50);
+      }
+      else if (code == key.upArrow || code == key.downArrow) {
+        // need to do that to be able to move the arrow 
+        // when on the first or last line ofo a code cell
+        event.stopPropagation();
+      }
+      else if (code != key.upArrow && code != key.downArrow) {
+        this.close(); this.editor.focus();
+        //we give focus to the editor immediately and call sell in 50 ms
+        var that = this;
+        setTimeout(function(){that.carryOnCompletion();}, 50);
+      }
+    }
+
+
+    IPython.Completer = Completer;
+
+    return IPython;
+}(IPython));
diff --git a/IPython/frontend/html/notebook/static/js/context-hint.js b/IPython/frontend/html/notebook/static/js/context-hint.js
new file mode 100644
index 0000000..ddde98a
--- /dev/null
+++ b/IPython/frontend/html/notebook/static/js/context-hint.js
@@ -0,0 +1,89 @@
+// highly adapted for codemiror jshint
+
+(function () {
+  function forEach(arr, f) {
+    for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
+  }
+  
+  function arrayContains(arr, item) {
+    if (!Array.prototype.indexOf) {
+      var i = arr.length;
+      while (i--) {
+        if (arr[i] === item) {
+          return true;
+        }
+      }
+      return false;
+    }
+    return arr.indexOf(item) != -1;
+  }
+  
+  CodeMirror.contextHint = function(editor) {
+    // Find the token at the cursor
+    var cur = editor.getCursor(), token = editor.getTokenAt(cur), tprop = token;
+    // If it's not a 'word-style' token, ignore the token.
+    // If it is a property, find out what it is a property of.
+
+    var list  = new Array();
+    var clist = getCompletions(token,editor) ;
+    for( var i = 0 ; i < clist.length ; i++)    
+    {
+        list.push(
+                {
+                    str  : clist[i],
+                    type : "context",
+                    from : {line: cur.line, ch: token.start},
+                    to   : {line: cur.line, ch: token.end}
+                }
+            )
+
+    }
+    return list;
+  }
+
+  // find all 'words' of current cell
+  function getAllTokens(editor)
+  {
+    var found = [];
+    // get all text remove and split it before dot and at space
+    // keep the dot for completing token that also start with dot
+    var candidates = editor.getValue()
+        .replace(/[. ]/g,"\n") 
+        .split('\n'); 
+    // append to arry if not already (the function)
+    function maybeAdd(str) {
+      if (!arrayContains(found, str)) found.push(str);
+    }
+
+    // append to arry if not already 
+    // (here we do it )
+    for( c in candidates )
+    {
+        if(candidates[c].length >= 1){
+        maybeAdd(candidates[c]);}
+    }
+    return found;
+
+  }
+
+  function getCompletions(token,editor) 
+  {
+    var candidates = getAllTokens(editor);
+    // filter all token that have a common start (but nox exactly) the lenght of the current token
+    var prependchar ='';
+    if(token.string.indexOf('.') == 0)
+    {
+        prependchar = '.'
+    }
+    var lambda = function(x){ 
+        x = prependchar+x;
+        return (x.indexOf(token.string)==0 && x != token.string)};
+    var filterd = candidates.filter(lambda);
+    for( var i in filterd)
+    {
+        // take care of reappending '.' at the beginning
+        filterd[i] = prependchar+filterd[i];
+    }
+    return filterd;
+  }
+})();
diff --git a/IPython/frontend/html/notebook/templates/notebook.html b/IPython/frontend/html/notebook/templates/notebook.html
index 89d2ab7..f7ba06d 100644
--- a/IPython/frontend/html/notebook/templates/notebook.html
+++ b/IPython/frontend/html/notebook/templates/notebook.html
@@ -1,5 +1,4 @@
 {% extends page.html %}
-
 {% block stylesheet %}
 
 {% if mathjax_url %}
@@ -220,6 +219,7 @@ data-notebook-id={{notebook_id}}
 <script src="{{ static_url("js/initmathjax.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
+<script src="{{ static_url("js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
@@ -231,5 +231,8 @@ data-notebook-id={{notebook_id}}
 <script src="{{ static_url("js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/notebookmain.js") }}" type="text/javascript" charset="utf-8"></script>
 
+<script src="{{ static_url("js/context-hint.js") }} charset="utf-8"></script>
+<script src="{{ static_url("codemirror/lib/util/simple-hint.js") }} charset="utf-8"></script>
+
 {% end %}