From 475508baa9f0b21087eb85048d51af342aa09692 Mon Sep 17 00:00:00 2001 From: Fulup Ar Foll Date: Wed, 16 Dec 2015 17:15:43 +0100 Subject: Add the app!!! --- .../widgets/Buttons/SubmitButtons/SubmitButtons.js | 52 ++++++ .../Buttons/SubmitButtons/SubmitButtons.scss | 22 +++ .../app/Frontend/widgets/FormInput/FormInput.scss | 69 ++++++++ .../Frontend/widgets/FormInput/InputPassword.js | 79 +++++++++ .../app/Frontend/widgets/FormInput/InputText.js | 179 +++++++++++++++++++++ .../app/Frontend/widgets/FormInput/UploadFile.js | 113 +++++++++++++ .../app/Frontend/widgets/Navigation/LinkButton.js | 57 +++++++ .../Frontend/widgets/Navigation/Navigation.scss | 26 +++ .../widgets/Notifications/ModalNotification.js | 85 ++++++++++ .../widgets/Notifications/Notifications.scss | 47 ++++++ 10 files changed, 729 insertions(+) create mode 100644 afb-client/app/Frontend/widgets/Buttons/SubmitButtons/SubmitButtons.js create mode 100644 afb-client/app/Frontend/widgets/Buttons/SubmitButtons/SubmitButtons.scss create mode 100644 afb-client/app/Frontend/widgets/FormInput/FormInput.scss create mode 100644 afb-client/app/Frontend/widgets/FormInput/InputPassword.js create mode 100644 afb-client/app/Frontend/widgets/FormInput/InputText.js create mode 100644 afb-client/app/Frontend/widgets/FormInput/UploadFile.js create mode 100644 afb-client/app/Frontend/widgets/Navigation/LinkButton.js create mode 100644 afb-client/app/Frontend/widgets/Navigation/Navigation.scss create mode 100644 afb-client/app/Frontend/widgets/Notifications/ModalNotification.js create mode 100644 afb-client/app/Frontend/widgets/Notifications/Notifications.scss (limited to 'afb-client/app/Frontend/widgets') diff --git a/afb-client/app/Frontend/widgets/Buttons/SubmitButtons/SubmitButtons.js b/afb-client/app/Frontend/widgets/Buttons/SubmitButtons/SubmitButtons.js new file mode 100644 index 0000000..1b94e25 --- /dev/null +++ b/afb-client/app/Frontend/widgets/Buttons/SubmitButtons/SubmitButtons.js @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Bugs: Input with Callback SHOULD BE get 'required' class + */ + +(function () { + 'use strict'; + + var tmpl = '
' + + '' + + '{{label}}' + + '
'; + + angular.module('SubmitButton', []) + .directive('submitButton', function () { + + function mymethods(scope, elem, attrs) { + + // ajust icon or use default + scope.icon = attrs.icon || 'fi-foot'; + scope.label = attrs.label || 'Next'; + + // add label as class + elem.addClass (scope.label.toLowerCase()); + + // note: clicked in imported and when template is clicked + // it will call clicked method passed in param. + } + + return { + restrict: 'E', + template: tmpl, + link: mymethods, + scope: {clicked : '='} + }; + }); +})(); diff --git a/afb-client/app/Frontend/widgets/Buttons/SubmitButtons/SubmitButtons.scss b/afb-client/app/Frontend/widgets/Buttons/SubmitButtons/SubmitButtons.scss new file mode 100644 index 0000000..2150e4d --- /dev/null +++ b/afb-client/app/Frontend/widgets/Buttons/SubmitButtons/SubmitButtons.scss @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +@import "app/ibz-mixins"; + +// place here your submit buttons customization + diff --git a/afb-client/app/Frontend/widgets/FormInput/FormInput.scss b/afb-client/app/Frontend/widgets/FormInput/FormInput.scss new file mode 100644 index 0000000..37519fd --- /dev/null +++ b/afb-client/app/Frontend/widgets/FormInput/FormInput.scss @@ -0,0 +1,69 @@ +/* + Sample of style overload for a specific widget + + Note: this SCSS is injected with main HTML page, it scope should be reduce + to a specific widget or it value will be propagated at a global level. +*/ + +@import "app/ibz-mixins"; + +upload-file { + height: 5rem; + display: inline-block; + float: right; + img { height: inherit;} +} + +input-text { + + alert {@include ibz-input-alert(darkblue, rgba(200, 200, 200, 0.6))}; + + input { + margin-bottom: .5rem !important; + } + + label { + margin-top: 1rem !important; + } + + .required { + color: blue; + float: right; + color: lightskyblue; + } + .required.valid { + color: green; + } + + .required.invalid { + color: plum; + } + + .status-untouch { + border-color: rgba(200, 200, 200, 0.6) !important; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(200, 200, 200, 0.6) !important; + color: #696969 !important; + } + + input:focus { + border-color: rgba(82,168,236,0.8) ; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(82,168,236,0.8) !important; + transition: border 0.2s linear 0s, box-shadow 0.2s linear 0s; + color: darkslateblue !important; + @extend shadow-transition; + } + + .status-valid { + border-color: rgba(154, 205, 50, 0.6)!important; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(154, 205, 50, 0.6)!important; + @extend shadow-transition; + } + + .status-invalid { + border-color: rgba(154, 17, 69, 0.6); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(154, 17, 69, 0.6)!important; + color: rgb(154, 17, 69); + @extend shadow-transition; + } + +} diff --git a/afb-client/app/Frontend/widgets/FormInput/InputPassword.js b/afb-client/app/Frontend/widgets/FormInput/InputPassword.js new file mode 100644 index 0000000..157009c --- /dev/null +++ b/afb-client/app/Frontend/widgets/FormInput/InputPassword.js @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +(function() { +'use strict'; + +var tmpl = '' + + '' + + ' '+ + ''; + +angular.module('InputPassword',[]) + +.directive('inputPassword', function() { + function mymethods(scope, elem, attrs) { + + scope.valid1 = function (name, value) { + console.log ("Clicked InputPassword1 name=%s value=%s", name, value); + scope.firstpwd = value; + }; + + scope.valid2 = function (name, value, done) { + console.log ("Clicked InputPassword2 name=%s value=%s", name, value); + + // if both passwd equal then call form CB + if (scope.firstpwd !== value) { + done({valid: false, status: 'invalid', errmsg: "both password should match"}); + } else { + scope.callback (attrs.name, value); + } + + }; + + // this method can be called from controller to update widget status + scope.done=function (data) { + console.log ("Text-Input Callback ID="+ attrs.name + " data=", data); + for (var i in data) scope[i] = data[i]; + }; + + // Export some attributes within directive scope for template + scope.name = attrs.name; + scope.label1 = attrs.label || 'Password'; + scope.label2 = attrs.label || 'Password Verification'; + scope.place1 = attrs.placeholder1 || 'User Password'; + scope.tip1 = attrs.tip || 'Choose a Password'; + scope.place2 = attrs.placeholder1 || 'Password Verification'; + scope.tip2 = attrs.tip || 'Confirme your Password'; + scope.minlen = attrs.minlen || 10; + + if ("required" in attrs) scope.required = 'required'; + + } + + return { + restrict: 'E', + template: tmpl, + link: mymethods, + scope: { + callback : '=', + } + }; +}); + +console.log ("InputPassword Loaded"); +})(); diff --git a/afb-client/app/Frontend/widgets/FormInput/InputText.js b/afb-client/app/Frontend/widgets/FormInput/InputText.js new file mode 100644 index 0000000..2653175 --- /dev/null +++ b/afb-client/app/Frontend/widgets/FormInput/InputText.js @@ -0,0 +1,179 @@ + +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details.. + */ + + + +(function() { +'use strict'; + +var tmpl = '' + + ''+ + '{{errmsg}}'; + +var emailpatern = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; + +angular.module('InputText',['JQueryEmu']) + +.directive('inputText', function(JQemu) { + function mymethods(scope, elem, attrs) { + + // default value at 1st rendering + scope.error = false; + scope.valid = false; + scope.status = 'untouch'; + + scope.input = elem.find ("input"); + scope.required = 0; + + // requirer is use to increment requested counter + if ("required" in attrs) { + scope.required = 1; + elem.addClass ("required"); + } + + // user enter input reset error status + scope.selected = function () { + scope.error=false; + scope.errmsg=false; + scope.status = 'touch'; + }; + + scope.validate = function () { + + // get value from input field bypassing Angular ng-model + console.log ("Clicked InputText name=%s value=%s valid=%s", scope.name, scope.value, scope.valid); + + // form is not untouched anymore + scope.parent.removeClass ("ng-pristine"); + + // if value not null clean up string + if (scope.value) { + scope.error=false; + // remove leading and trailling space + scope.value = scope.value.trim(); + + // remove any space is not allowed + if ('nospace' in attrs) { + scope.value=scope.value.replace(/\s/g, ''); + } + + if ('lowercase' in attrs) { + scope.value = scope.value.toLowerCase(); + } + + // check minimum lenght + if ("minlen" in attrs) { + if (scope.value.length < attrs.minlen) { + scope.status='invalid'; + scope.errmsg=scope.name + ': Mininum Lengh= ' + attrs.minlen + ' Characters'; + scope.error=true; + } + } + + if ('email' in attrs) { + if (!emailpatern.test (scope.value)) { + scope.status='invalid'; + scope.errmsg='invalid email address'; + scope.error=true; + } + } + + } else { + if (scope.required) { + scope.status='invalid'; + scope.errmsg=scope.name + ': Required Attribute'; + scope.error=true; + } + } + + // If local control fail let's refuse input + if (scope.error) { + if (scope.required && scope.valid) { + scope.valid = false; + if (scope.l4acounter.validated > 0) scope.l4acounter.validated --; + } + // use call to update form scope on form completeness + scope.callback (attrs.name, null, scope.done); + } else { + // localcheck is OK backup may nevertheless change status to false + if (scope.required && !scope.valid) scope.l4acounter.validated ++; + scope.status='valid'; + scope.valid=true; + scope.callback (attrs.name, scope.value, scope.done); + } + + }; + + // this method can be called from controller to update widget status + scope.done=function (data) { + console.log ("Text-Input Callback ID="+ attrs.name + " data=", data); + for (var i in data) scope[i] = data[i]; + }; + + // Export some attributes within directive scope for template + scope.label = attrs.label; + scope.name = attrs.name; + scope.placeholder = attrs.placeholder; + scope.type = attrs.type || "text"; + scope.tip = attrs.tip; + + // search for form within parent elemnts + scope.parent = JQemu.parent (elem, "FORM"); + + // email enforce lowercase and nospace + if ("email" in attrs) { + attrs.lowercase=true; + attrs.nospace=true; + attrs.minlen=6; + } + + if (scope.required) { + scope.l4acounter = scope.parent.data ("l4acounter"); + if (!scope.l4acounter) { + scope.l4acounter = {required:1, validated:0}; + console.log("Field "+scope.name+" is required (1st)"); + scope.parent.data ("l4acounter", scope.l4acounter); + } else { + console.log("Field "+scope.name+" is required"); + scope.l4acounter.required ++; + } + } + + // refresh validation each time controler update value + scope.$watch ('value', function(){ + if(scope.value) scope.validate(); } + ); + + } + + return { + restrict: 'E', + template: tmpl, + link: mymethods, + scope: { + callback : '=', + value: '=' + } + }; +}); + +console.log ("InputText Loaded"); +})(); diff --git a/afb-client/app/Frontend/widgets/FormInput/UploadFile.js b/afb-client/app/Frontend/widgets/FormInput/UploadFile.js new file mode 100644 index 0000000..9a2f031 --- /dev/null +++ b/afb-client/app/Frontend/widgets/FormInput/UploadFile.js @@ -0,0 +1,113 @@ + +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details.. + * + * Reference: + * https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications#Using_hidden_file_input_elements_using_the_click%28%29_method + * https://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs + * https://www.terlici.com/2015/05/16/uploading-files-locally.html + * https://github.com/nervgh/angular-file-upload/blob/master/src/services/FileUploader.js + */ + + +function changeInput() { + console.log ('input imgClicked'); +} + +(function() { +'use strict'; + +// WARNING: Angular ng-change does not work on input/file. Let's hook our callback through standard JS function +var tmpl = '
'+ + ''+ + ' ' + + '
' + + '' ; + +function basename(path) { + return path.split('/').reverse()[0]; +} + +angular.module('UploadFile',['ConfigApp']) + +.directive('uploadFile', function(ConfigApp, $http, JQemu) { + function mymethods(scope, elem, attrs) { + + // get widget image handle from template + scope.imgElem = elem.find('img'); + scope.inputElem = elem.find('input'); + scope.submitElem = JQemu.findByType (elem.children(), "submit"); + + + // Image was ckick let's simulate an input (file) click + scope.imgClicked = function () { + scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!! + }; + + // upload file to server + scope.UpLoadFile= function(files) { + + + for (var i = 0; i < files.length; i++) { + var file = files[i]; + console.log ("Selected file=" + file.name + " size="+ file.size/1024); + var mimeType = /image.*/; // build regular expression from Mime + if (!file.type.match(mimeType)) { + continue; + } + + if (file.size > scope.sizemax*1024) { + scope.imagepath = scope.istoobig; // warning is path is wrong nothing happen + scope.$apply('imagepath'); // we short-circuit Angular resync Image + } else { + + scope.basename=basename(file.name); + scope.imgElem[0].file = file; + + var reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = function (upload) { + scope.imagepath = upload.target.result; + scope.$apply('imagepath'); // we short-circuit Angular resync image + scope.submitElem[0].click(); // Warning Angular TriggerEvent does not work!!! + }; + } + } + }; + + // Initiallize default values from attributes values + if (attrs.icon) scope.imagepath= ConfigApp.paths[attrs.category] + attrs.icon; + else scope.imagepath=ConfigApp.paths.avatars + 'tux-bzh.png'; + + if (attrs.istoobig) scope.istoobig= ConfigApp.paths[attrs.category] + attrs.istoobig; + else scope.istoobig=ConfigApp.paths.avatars + 'istoobig.jpg'; + + scope.name= attrs.name || 'avatar'; + scope.mime= attrs.mime || 'image'; + scope.sizemax= attrs.sizemax || 100; // default max size 100KB + + } + + return { + restrict: 'E', + template: tmpl, + link: mymethods, + scope: { + callback : '=' + } + }; +}); + +console.log ("UploadFile Loaded"); +})(); diff --git a/afb-client/app/Frontend/widgets/Navigation/LinkButton.js b/afb-client/app/Frontend/widgets/Navigation/LinkButton.js new file mode 100644 index 0000000..3e83425 --- /dev/null +++ b/afb-client/app/Frontend/widgets/Navigation/LinkButton.js @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Bugs: Input with Callback SHOULD BE get 'required' class + */ + +(function () { + 'use strict'; + + var tmpl = '' + + '' + + '{{label}}' + + ''; + + + angular.module('LinkButton', []) + .directive('linkButton', function ($location) { + + function mymethods(scope, elem, attrs) { + + scope.clicked = function () { + + if (!attrs.query) $location.path(attrs.href); + else $location.path(attrs.href).search(attrs.query); + }; + + // ajust icon or use default + scope.icon = attrs.icon || 'fi-link'; + scope.label = attrs.label || 'Jump'; + scope.href = attrs.href || '/home'; + + // add label as class + elem.addClass (scope.label.toLowerCase()); + } + + return { + restrict: 'E', + template: tmpl, + link: mymethods, + scope: {} + }; + }); +})(); diff --git a/afb-client/app/Frontend/widgets/Navigation/Navigation.scss b/afb-client/app/Frontend/widgets/Navigation/Navigation.scss new file mode 100644 index 0000000..2babf24 --- /dev/null +++ b/afb-client/app/Frontend/widgets/Navigation/Navigation.scss @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +@import "app/ibz-mixins"; + + +link-button {@include ibz-button(#3366ff,1rem)}; + +//pale blue for secondaty link +link-button.secondary {@include ibz-button(#99b3ff,1rem)}; + diff --git a/afb-client/app/Frontend/widgets/Notifications/ModalNotification.js b/afb-client/app/Frontend/widgets/Notifications/ModalNotification.js new file mode 100644 index 0000000..37ba047 --- /dev/null +++ b/afb-client/app/Frontend/widgets/Notifications/ModalNotification.js @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Bugs: Input with Callback SHOULD BE get 'required' class + * + * ref: https://developer.mozilla.org/en-US/docs/Web/Events/mouseover + * + * usage: + * + * tipModal: listen event from elem.parent() to display tip-modal + *
+ * + * + *
+ * + * Note: use CSS.visibility to avoid display flickering at initial display. + */ + +(function () { + 'use strict'; + + var tmpl = '
' + + '' + + '{{tip}}' + + '
' ; + + angular.module('ModalNotification', []) + .directive('tipModal', function ($timeout) { + + function mymethods(scope, elem, attrs) { + scope.parent = elem.parent(); + scope.modal = elem.find("div"); + + + // delay tip display to avoid blinking when moving mouse fast + function display () { + function action() { + if (scope.show) scope.modal.css({opacity: 1, visibility:'visible'}); + } + scope.show = true; + scope.timeout = $timeout(action, scope.delay); + } + + function close () { + scope.show = false; + scope.modal.css({opacity: 0, visibility:'hidden'}); + } + + + // ajust icon or use default + scope.icon = attrs.icon || 'fi-lightbulb'; + + // Update Parent element to get mouse event + scope.parent.addClass ('as-modal-tip'); + scope.parent.bind('click', close); + scope.parent.bind('focus', display); + scope.parent.bind('mouseover', display); + scope.parent.bind('mouseleave', close); + scope.parent.bind('blur', close); + + scope.delay = attrs.delay || 1000; // wait 1s before displaying tip + } + + return { + restrict: 'E', + template: tmpl, + link: mymethods, + scope: {tip: "="} // tip may not be defined when widget is display + }; + }); +})(); diff --git a/afb-client/app/Frontend/widgets/Notifications/Notifications.scss b/afb-client/app/Frontend/widgets/Notifications/Notifications.scss new file mode 100644 index 0000000..5d42d2a --- /dev/null +++ b/afb-client/app/Frontend/widgets/Notifications/Notifications.scss @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Reference: http://www.greywyvern.com/?post=337 + */ + +@import "app/ibz-mixins"; + + +link-button {@include ibz-button(#3366ff,1rem)}; + +// Modal should be relative and tip-modal-popup absolute +tip-modal { + position:relative; +} + +.tip-modal-popup { + //visibility: hidden; + width: 20rem; + position:absolute; + top:1em; + padding: 0.2em 0.6em; + border:1px solid #996633; + background-color:#e5ffff; + color:#000; + opacity:0; + transition:visibility .5s linear 1s,opacity 1s linear; + border-radius: 5px; + i { + margin: 0 .3rem 0 0; + display: inline; + } +} -- cgit