Convert htmlbuilder.py to use Template() strings for css

- Changed strings to use Template() instead of format() for css building
- Fix htmlbuilder strings tests
- Cleanup template variables and fix tests to match
- bug 1590386 - string format error in editbibledialog.py
- bug 1590475 - string format error in mediaitem.py
- Function call error in firsttimeform.py (missed label text variable)

--------------------------------
lp:~alisonken1/openlp/strings-templates2 (revision 2679)
[SUCCESS] ...

bzr-revno: 2675
Fixes: https://launchpad.net/bugs/1590386, https://launchpad.net/bugs/1590475
This commit is contained in:
Ken Roberts 2016-06-14 22:05:51 +01:00 committed by Tim Bentley
commit f20499b1af
5 changed files with 400 additions and 389 deletions

View File

@ -390,163 +390,207 @@ is the function which has to be called from outside. The generated and returned
import logging
from PyQt5 import QtWebKit
from string import Template
from openlp.core.common import Settings
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, VerticalType, HorizontalType
log = logging.getLogger(__name__)
# TODO: Verify where this is used before converting to python3
HTMLSRC = """
<!DOCTYPE html>
<html>
<head>
<title>OpenLP Display</title>
<style>
*{
HTML_SRC = Template("""
<!DOCTYPE html>
<html>
<head>
<title>OpenLP Display</title>
<style>
*{
margin: 0;
padding: 0;
border: 0;
overflow: hidden;
-webkit-user-select: none;
}
body {
${bg_css};
}
.size {
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
}
#black {
z-index: 8;
background-color: black;
display: none;
}
#bgimage {
z-index: 1;
}
#image {
z-index: 2;
}
${css_additions}
#footer {
position: absolute;
z-index: 6;
${footer_css}
}
/* lyric css */${lyrics_css}
sup {
font-size: 0.6em;
vertical-align: top;
position: relative;
top: -0.3em;
}
</style>
<script>
var timer = null;
var transition = ${transitions};
${js_additions}
function show_image(src){
var img = document.getElementById('image');
img.src = src;
if(src == '')
img.style.display = 'none';
else
img.style.display = 'block';
}
function show_blank(state){
var black = 'none';
var lyrics = '';
switch(state){
case 'theme':
lyrics = 'hidden';
break;
case 'black':
black = 'block';
break;
case 'desktop':
break;
}
document.getElementById('black').style.display = black;
document.getElementById('lyricsmain').style.visibility = lyrics;
document.getElementById('image').style.visibility = lyrics;
document.getElementById('footer').style.visibility = lyrics;
}
function show_footer(footertext){
document.getElementById('footer').innerHTML = footertext;
}
function show_text(new_text){
var match = /-webkit-text-fill-color:[^;\"]+/gi;
if(timer != null)
clearTimeout(timer);
/*
QtWebkit bug with outlines and justify causing outline alignment
problems. (Bug 859950) Surround each word with a <span> to workaround,
but only in this scenario.
*/
var txt = document.getElementById('lyricsmain');
if(window.getComputedStyle(txt).textAlign == 'justify'){
if(window.getComputedStyle(txt).webkitTextStrokeWidth != '0px'){
new_text = new_text.replace(/(\s|&nbsp;)+(?![^<]*>)/g,
function(match) {
return '</span>' + match + '<span>';
});
new_text = '<span>' + new_text + '</span>';
}
}
text_fade('lyricsmain', new_text);
}
function text_fade(id, new_text){
/*
Show the text.
*/
var text = document.getElementById(id);
if(text == null) return;
if(!transition){
text.innerHTML = new_text;
return;
}
// Fade text out. 0.1 to minimize the time "nothing" is shown on the screen.
text.style.opacity = '0.1';
// Fade new text in after the old text has finished fading out.
timer = window.setTimeout(function(){_show_text(text, new_text)}, 400);
}
function _show_text(text, new_text) {
/*
Helper function to show the new_text delayed.
*/
text.innerHTML = new_text;
text.style.opacity = '1';
// Wait until the text is completely visible. We want to save the timer id, to be able to call
// clearTimeout(timer) when the text has changed before finishing fading.
timer = window.setTimeout(function(){timer = null;}, 400);
}
function show_text_completed(){
return (timer == null);
}
</script>
</head>
<body>
<img id="bgimage" class="size" ${bg_image} />
<img id="image" class="size" ${image} />
${html_additions}
<div class="lyricstable"><div id="lyricsmain" style="opacity:1" class="lyricscell lyricsmain"></div></div>
<div id="footer" class="footer"></div>
<div id="black" class="size"></div>
</body>
</html>
""")
LYRICS_SRC = Template("""
.lyricstable {
z-index: 5;
position: absolute;
display: table;
${stable}
}
.lyricscell {
display: table-cell;
word-wrap: break-word;
-webkit-transition: opacity 0.4s ease;
${lyrics}
}
.lyricsmain {
${main}
}
""")
FOOTER_SRC = Template("""
left: ${left}px;
bottom: ${bottom}px;
width: ${width}px;
font-family: ${family};
font-size: ${size}pt;
color: ${color};
text-align: left;
white-space: ${space};
""")
LYRICS_FORMAT_SRC = Template("""
${justify}word-wrap: break-word;
text-align: ${align};
vertical-align: ${valign};
font-family: ${font};
font-size: ${size}pt;
color: ${color};
line-height: ${line}%;
margin: 0;
padding: 0;
border: 0;
overflow: hidden;
-webkit-user-select: none;
}
body {
%s;
}
.size {
position: absolute;
left: 0px;
top: 0px;
width: 100%%;
height: 100%%;
}
#black {
z-index: 8;
background-color: black;
display: none;
}
#bgimage {
z-index: 1;
}
#image {
z-index: 2;
}
%s
#footer {
position: absolute;
z-index: 6;
%s
}
/* lyric css */
%s
sup {
font-size: 0.6em;
vertical-align: top;
position: relative;
top: -0.3em;
}
</style>
<script>
var timer = null;
var transition = %s;
%s
function show_image(src){
var img = document.getElementById('image');
img.src = src;
if(src == '')
img.style.display = 'none';
else
img.style.display = 'block';
}
function show_blank(state){
var black = 'none';
var lyrics = '';
switch(state){
case 'theme':
lyrics = 'hidden';
break;
case 'black':
black = 'block';
break;
case 'desktop':
break;
}
document.getElementById('black').style.display = black;
document.getElementById('lyricsmain').style.visibility = lyrics;
document.getElementById('image').style.visibility = lyrics;
document.getElementById('footer').style.visibility = lyrics;
}
function show_footer(footertext){
document.getElementById('footer').innerHTML = footertext;
}
function show_text(new_text){
var match = /-webkit-text-fill-color:[^;\"]+/gi;
if(timer != null)
clearTimeout(timer);
/*
QtWebkit bug with outlines and justify causing outline alignment
problems. (Bug 859950) Surround each word with a <span> to workaround,
but only in this scenario.
*/
var txt = document.getElementById('lyricsmain');
if(window.getComputedStyle(txt).textAlign == 'justify'){
if(window.getComputedStyle(txt).webkitTextStrokeWidth != '0px'){
new_text = new_text.replace(/(\s|&nbsp;)+(?![^<]*>)/g,
function(match) {
return '</span>' + match + '<span>';
});
new_text = '<span>' + new_text + '</span>';
}
}
text_fade('lyricsmain', new_text);
}
function text_fade(id, new_text){
/*
Show the text.
*/
var text = document.getElementById(id);
if(text == null) return;
if(!transition){
text.innerHTML = new_text;
return;
}
// Fade text out. 0.1 to minimize the time "nothing" is shown on the screen.
text.style.opacity = '0.1';
// Fade new text in after the old text has finished fading out.
timer = window.setTimeout(function(){_show_text(text, new_text)}, 400);
}
function _show_text(text, new_text) {
/*
Helper function to show the new_text delayed.
*/
text.innerHTML = new_text;
text.style.opacity = '1';
// Wait until the text is completely visible. We want to save the timer id, to be able to call
// clearTimeout(timer) when the text has changed before finishing fading.
timer = window.setTimeout(function(){timer = null;}, 400);
}
function show_text_completed(){
return (timer == null);
}
</script>
</head>
<body>
<img id="bgimage" class="size" %s />
<img id="image" class="size" %s />
%s
<div class="lyricstable"><div id="lyricsmain" style="opacity:1" class="lyricscell lyricsmain"></div></div>
<div id="footer" class="footer"></div>
<div id="black" class="size"></div>
</body>
</html>
"""
padding-bottom: ${bottom};
padding-left: ${left}px;
width: ${width}px;
height: ${height}px;${font_style}${font_weight}
""")
def build_html(item, screen, is_live, background, image=None, plugins=None):
@ -582,18 +626,17 @@ def build_html(item, screen, is_live, background, image=None, plugins=None):
css_additions += plugin.get_display_css()
js_additions += plugin.get_display_javascript()
html_additions += plugin.get_display_html()
html = HTMLSRC % (
build_background_css(item, width),
css_additions,
build_footer_css(item, height),
build_lyrics_css(item),
'true' if theme_data and theme_data.display_slide_transition and is_live else 'false',
js_additions,
bgimage_src,
image_src,
html_additions
)
return html
return HTML_SRC.substitute(bg_css=build_background_css(item, width),
css_additions=css_additions,
footer_css=build_footer_css(item, height),
lyrics_css=build_lyrics_css(item),
transitions='true' if (theme_data and
theme_data.display_slide_transition and
is_live) else 'false',
js_additions=js_additions,
bg_image=bgimage_src,
image=image_src,
html_additions=html_additions)
def webkit_version():
@ -650,24 +693,6 @@ def build_lyrics_css(item):
:param item: Service Item containing theme and location information
"""
# TODO: Verify this before converting to python3
style = """
.lyricstable {
z-index: 5;
position: absolute;
display: table;
%s
}
.lyricscell {
display: table-cell;
word-wrap: break-word;
-webkit-transition: opacity 0.4s ease;
%s
}
.lyricsmain {
%s
}
"""
theme_data = item.theme_data
lyricstable = ''
lyrics = ''
@ -680,8 +705,7 @@ def build_lyrics_css(item):
lyricsmain += ' text-shadow: {theme} {shadow}px ' \
'{shadow}px;'.format(theme=theme_data.font_main_shadow_color,
shadow=theme_data.font_main_shadow_size)
lyrics_css = style % (lyricstable, lyrics, lyricsmain)
return lyrics_css
return LYRICS_SRC.substitute(stable=lyricstable, lyrics=lyrics, main=lyricsmain)
def build_lyrics_outline_css(theme_data):
@ -710,38 +734,23 @@ def build_lyrics_format_css(theme_data, width, height):
"""
align = HorizontalType.Names[theme_data.display_horizontal_align]
valign = VerticalType.Names[theme_data.display_vertical_align]
if theme_data.font_main_outline:
left_margin = int(theme_data.font_main_outline_size) * 2
else:
left_margin = 0
justify = 'white-space:pre-wrap;'
left_margin = (int(theme_data.font_main_outline_size) * 2) if theme_data.font_main_outline else 0
# fix tag incompatibilities
if theme_data.display_horizontal_align == HorizontalType.Justify:
justify = ''
if theme_data.display_vertical_align == VerticalType.Bottom:
padding_bottom = '0.5em'
else:
padding_bottom = '0'
lyrics = '{justify} word-wrap: break-word; ' \
'text-align: {align}; vertical-align: {valign}; font-family: {font}; ' \
'font-size: {size}pt; color: {color}; line-height: {line:d}%; margin: 0;' \
'padding: 0; padding-bottom: {bottom}; padding-left: {left}px; width: {width}px; ' \
'height: {height}px; '.format(justify=justify,
align=align,
valign=valign,
font=theme_data.font_main_name,
size=theme_data.font_main_size,
color=theme_data.font_main_color,
line=100 + int(theme_data.font_main_line_adjustment),
bottom=padding_bottom,
left=left_margin,
width=width,
height=height)
if theme_data.font_main_italics:
lyrics += 'font-style:italic; '
if theme_data.font_main_bold:
lyrics += 'font-weight:bold; '
return lyrics
justify = '' if (theme_data.display_horizontal_align == HorizontalType.Justify) else ' white-space: pre-wrap;\n'
padding_bottom = '0.5em' if (theme_data.display_vertical_align == VerticalType.Bottom) else '0'
return LYRICS_FORMAT_SRC.substitute(justify=justify,
align=align,
valign=valign,
font=theme_data.font_main_name,
size=theme_data.font_main_size,
color=theme_data.font_main_color,
line='{line:d}'.format(line=100 + int(theme_data.font_main_line_adjustment)),
bottom=padding_bottom,
left=left_margin,
width=width,
height=height,
font_style='\n font-style: italic;' if theme_data.font_main_italics else '',
font_weight='\n font-weight: bold;' if theme_data.font_main_bold else '')
def build_footer_css(item, height):
@ -751,22 +760,11 @@ def build_footer_css(item, height):
:param item: Service Item to be processed.
:param height:
"""
style = """
left: {left}px;
bottom: {bottom}px;
width: {width}px;
font-family: {family};
font-size: {size}pt;
color: {color};
text-align: left;
white-space: {space};
"""
theme = item.theme_data
if not theme or not item.footer:
return ''
bottom = height - int(item.footer.y()) - int(item.footer.height())
whitespace = 'normal' if Settings().value('themes/wrap footer') else 'nowrap'
lyrics_html = style.format(left=item.footer.x(), bottom=bottom, width=item.footer.width(),
family=theme.font_footer_name, size=theme.font_footer_size,
color=theme.font_footer_color, space=whitespace)
return lyrics_html
return FOOTER_SRC.substitute(left=item.footer.x(), bottom=bottom, width=item.footer.width(),
family=theme.font_footer_name, size=theme.font_footer_size,
color=theme.font_footer_color, space=whitespace)

View File

@ -565,13 +565,13 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
if self.has_run_wizard:
text = translate('OpenLP.FirstTimeWizard',
'Download complete. Click the {button} button to return to OpenLP.'
).format(text=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton)))
).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton)))
self.progress_label.setText(text)
else:
text = translate('OpenLP.FirstTimeWizard',
'Download complete. Click the {button} button to start OpenLP.'
).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton)))
self.progress_label.setText()
self.progress_label.setText(text)
else:
if self.has_run_wizard:
text = translate('OpenLP.FirstTimeWizard',
@ -582,7 +582,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
text = translate('OpenLP.FirstTimeWizard',
'Click the {button} button to start OpenLP.'
).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton)))
self.progress_label.setText()
self.progress_label.setText(text)
self.finish_button.setVisible(True)
self.finish_button.setEnabled(True)
self.cancel_button.setVisible(False)

View File

@ -104,7 +104,7 @@ class Ui_EditBibleDialog(object):
for book in BiblesResourcesDB.get_books():
self.book_name_label[book['abbreviation']] = QtWidgets.QLabel(self.book_name_widget)
self.book_name_label[book['abbreviation']].setObjectName(
'book_name_label[{name}]'.format(book=book['abbreviation']))
'book_name_label[{book}]'.format(book=book['abbreviation']))
self.book_name_edit[book['abbreviation']] = QtWidgets.QLineEdit(self.book_name_widget)
self.book_name_edit[book['abbreviation']].setObjectName(
'book_name_edit[{name}]'.format(name=book['abbreviation']))

View File

@ -603,7 +603,7 @@ class SongMediaItem(MediaManagerItem):
else:
verse_index = VerseType.from_tag(verse[0]['type'])
verse_tag = VerseType.translated_tags[verse_index]
verse_def = '{tag}{label}'.format(tzg=verse_tag, text=verse[0]['label'])
verse_def = '{tag}{text}'.format(tag=verse_tag, text=verse[0]['label'])
service_item.add_from_text(verse[1], verse_def)
service_item.title = song.title
author_list = self.generate_footer(service_item, song)

View File

@ -14,177 +14,190 @@ from tests.functional import MagicMock, patch
from tests.helpers.testmixin import TestMixin
HTML = """
<!DOCTYPE html>
<html>
<head>
<title>OpenLP Display</title>
<style>
*{
margin: 0;
padding: 0;
border: 0;
overflow: hidden;
-webkit-user-select: none;
}
body {
;
}
.size {
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
}
#black {
z-index: 8;
background-color: black;
display: none;
}
#bgimage {
z-index: 1;
}
#image {
z-index: 2;
}
plugin CSS
#footer {
position: absolute;
z-index: 6;
dummy: dummy;
}
/* lyric css */
sup {
font-size: 0.6em;
vertical-align: top;
position: relative;
top: -0.3em;
}
</style>
<script>
var timer = null;
var transition = false;
plugin JS
function show_image(src){
var img = document.getElementById('image');
img.src = src;
if(src == '')
img.style.display = 'none';
else
img.style.display = 'block';
<!DOCTYPE html>
<html>
<head>
<title>OpenLP Display</title>
<style>
*{
margin: 0;
padding: 0;
border: 0;
overflow: hidden;
-webkit-user-select: none;
}
body {
;
}
.size {
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
}
#black {
z-index: 8;
background-color: black;
display: none;
}
#bgimage {
z-index: 1;
}
#image {
z-index: 2;
}
plugin CSS
#footer {
position: absolute;
z-index: 6;
dummy: dummy;
}
/* lyric css */
sup {
font-size: 0.6em;
vertical-align: top;
position: relative;
top: -0.3em;
}
</style>
<script>
var timer = null;
var transition = false;
plugin JS
function show_blank(state){
var black = 'none';
var lyrics = '';
switch(state){
case 'theme':
lyrics = 'hidden';
break;
case 'black':
black = 'block';
break;
case 'desktop':
break;
function show_image(src){
var img = document.getElementById('image');
img.src = src;
if(src == '')
img.style.display = 'none';
else
img.style.display = 'block';
}
document.getElementById('black').style.display = black;
document.getElementById('lyricsmain').style.visibility = lyrics;
document.getElementById('image').style.visibility = lyrics;
document.getElementById('footer').style.visibility = lyrics;
}
function show_footer(footertext){
document.getElementById('footer').innerHTML = footertext;
}
function show_text(new_text){
var match = /-webkit-text-fill-color:[^;"]+/gi;
if(timer != null)
clearTimeout(timer);
/*
QtWebkit bug with outlines and justify causing outline alignment
problems. (Bug 859950) Surround each word with a <span> to workaround,
but only in this scenario.
*/
var txt = document.getElementById('lyricsmain');
if(window.getComputedStyle(txt).textAlign == 'justify'){
if(window.getComputedStyle(txt).webkitTextStrokeWidth != '0px'){
new_text = new_text.replace(/(\s|&nbsp;)+(?![^<]*>)/g,
function(match) {
return '</span>' + match + '<span>';
});
new_text = '<span>' + new_text + '</span>';
function show_blank(state){
var black = 'none';
var lyrics = '';
switch(state){
case 'theme':
lyrics = 'hidden';
break;
case 'black':
black = 'block';
break;
case 'desktop':
break;
}
document.getElementById('black').style.display = black;
document.getElementById('lyricsmain').style.visibility = lyrics;
document.getElementById('image').style.visibility = lyrics;
document.getElementById('footer').style.visibility = lyrics;
}
text_fade('lyricsmain', new_text);
}
function text_fade(id, new_text){
/*
Show the text.
*/
var text = document.getElementById(id);
if(text == null) return;
if(!transition){
function show_footer(footertext){
document.getElementById('footer').innerHTML = footertext;
}
function show_text(new_text){
var match = /-webkit-text-fill-color:[^;"]+/gi;
if(timer != null)
clearTimeout(timer);
/*
QtWebkit bug with outlines and justify causing outline alignment
problems. (Bug 859950) Surround each word with a <span> to workaround,
but only in this scenario.
*/
var txt = document.getElementById('lyricsmain');
if(window.getComputedStyle(txt).textAlign == 'justify'){
if(window.getComputedStyle(txt).webkitTextStrokeWidth != '0px'){
new_text = new_text.replace(/(\s|&nbsp;)+(?![^<]*>)/g,
function(match) {
return '</span>' + match + '<span>';
});
new_text = '<span>' + new_text + '</span>';
}
}
text_fade('lyricsmain', new_text);
}
function text_fade(id, new_text){
/*
Show the text.
*/
var text = document.getElementById(id);
if(text == null) return;
if(!transition){
text.innerHTML = new_text;
return;
}
// Fade text out. 0.1 to minimize the time "nothing" is shown on the screen.
text.style.opacity = '0.1';
// Fade new text in after the old text has finished fading out.
timer = window.setTimeout(function(){_show_text(text, new_text)}, 400);
}
function _show_text(text, new_text) {
/*
Helper function to show the new_text delayed.
*/
text.innerHTML = new_text;
return;
text.style.opacity = '1';
// Wait until the text is completely visible. We want to save the timer id, to be able to call
// clearTimeout(timer) when the text has changed before finishing fading.
timer = window.setTimeout(function(){timer = null;}, 400);
}
// Fade text out. 0.1 to minimize the time "nothing" is shown on the screen.
text.style.opacity = '0.1';
// Fade new text in after the old text has finished fading out.
timer = window.setTimeout(function(){_show_text(text, new_text)}, 400);
}
function _show_text(text, new_text) {
/*
Helper function to show the new_text delayed.
*/
text.innerHTML = new_text;
text.style.opacity = '1';
// Wait until the text is completely visible. We want to save the timer id, to be able to call
// clearTimeout(timer) when the text has changed before finishing fading.
timer = window.setTimeout(function(){timer = null;}, 400);
}
function show_text_completed(){
return (timer == null);
}
</script>
</head>
<body>
<img id="bgimage" class="size" style="display:none;" />
<img id="image" class="size" style="display:none;" />
plugin HTML
<div class="lyricstable"><div id="lyricsmain" style="opacity:1" class="lyricscell lyricsmain"></div></div>
<div id="footer" class="footer"></div>
<div id="black" class="size"></div>
</body>
</html>
"""
function show_text_completed(){
return (timer == null);
}
</script>
</head>
<body>
<img id="bgimage" class="size" style="display:none;" />
<img id="image" class="size" style="display:none;" />
plugin HTML
<div class="lyricstable"><div id="lyricsmain" style="opacity:1" class="lyricscell lyricsmain"></div></div>
<div id="footer" class="footer"></div>
<div id="black" class="size"></div>
</body>
</html>
"""
BACKGROUND_CSS_RADIAL = 'background: -webkit-gradient(radial, 5 50%, 100, 5 50%, 5, from(#000000), to(#FFFFFF)) fixed'
LYRICS_CSS = """
.lyricstable {
z-index: 5;
position: absolute;
display: table;
left: 10px; top: 20px;
}
.lyricscell {
display: table-cell;
word-wrap: break-word;
-webkit-transition: opacity 0.4s ease;
lyrics_format_css
}
.lyricsmain {
text-shadow: #000000 5px 5px;
}
"""
.lyricstable {
z-index: 5;
position: absolute;
display: table;
left: 10px; top: 20px;
}
.lyricscell {
display: table-cell;
word-wrap: break-word;
-webkit-transition: opacity 0.4s ease;
lyrics_format_css
}
.lyricsmain {
text-shadow: #000000 5px 5px;
}
"""
LYRICS_OUTLINE_CSS = ' -webkit-text-stroke: 0.125em #000000; -webkit-text-fill-color: #FFFFFF; '
LYRICS_FORMAT_CSS = ' word-wrap: break-word; text-align: justify; vertical-align: bottom; ' + \
'font-family: Arial; font-size: 40pt; color: #FFFFFF; line-height: 108%; margin: 0;padding: 0; ' + \
'padding-bottom: 0.5em; padding-left: 2px; width: 1580px; height: 810px; font-style:italic; font-weight:bold; '
LYRICS_FORMAT_CSS = """
word-wrap: break-word;
text-align: justify;
vertical-align: bottom;
font-family: Arial;
font-size: 40pt;
color: #FFFFFF;
line-height: 108%;
margin: 0;
padding: 0;
padding-bottom: 0.5em;
padding-left: 2px;
width: 1580px;
height: 810px;
font-style: italic;
font-weight: bold;
"""
FOOTER_CSS_BASE = """
left: 10px;
bottom: 0px;