Merge branch 'master' into generic_id

pull/1/head
eric 2012-01-10 15:40:15 -05:00
commit d7e5e6e1ed
19 changed files with 425 additions and 13 deletions

View File

@ -201,3 +201,22 @@ class EmailShareForm(forms.Form):
# we can't rely on POST or GET since the emailshare view handles both # we can't rely on POST or GET since the emailshare view handles both
# and may iterate several times as it catches user errors, losing URL info # and may iterate several times as it catches user errors, losing URL info
next = forms.CharField(widget=forms.HiddenInput()) next = forms.CharField(widget=forms.HiddenInput())
class FeedbackForm(forms.Form):
sender = forms.EmailField(widget=forms.TextInput(attrs={'size':50}))
subject = forms.CharField(max_length=500, widget=forms.TextInput(attrs={'size':50}))
message = forms.CharField(widget=forms.Textarea())
page = forms.CharField(widget=forms.HiddenInput())
notarobot = forms.IntegerField(label="Please prove you're not a robot")
answer = forms.IntegerField(widget=forms.HiddenInput())
num1 = forms.IntegerField(widget=forms.HiddenInput())
num2 = forms.IntegerField(widget=forms.HiddenInput())
def clean(self):
cleaned_data = self.cleaned_data
notarobot = str(cleaned_data.get("notarobot"))
answer = str(cleaned_data.get("answer"))
if notarobot!=answer:
raise forms.ValidationError(_("Whoops, try that sum again."))
return cleaned_data

View File

@ -25,6 +25,9 @@
<body> <body>
<div id="feedback">
<p><a href="/feedback/?page={{request.build_absolute_uri|urlencode:""}}">Feedback</a></p>
</div>
<div id="js-page-wrap"> <div id="js-page-wrap">
<div id="js-header"> <div id="js-header">
<div class="js-main"> <div class="js-main">

View File

@ -0,0 +1,31 @@
{% extends "basedocumentation.html" %}
{% block title %}Feedback{% endblock %}
{% block doccontent %}
<p>Love something? Hate something? Found something broken or confusing? We're so glad you're telling us!</p>
To: support@gluejar.com<br />
<form method="POST" action="/feedback/">
{% csrf_token %}
{{ form.sender.label_tag }}</br>
{{ form.sender.errors }}
{{ form.sender }}</br>
{{ form.subject.label_tag }}</br>
{{ form.subject.errors }}
{{ form.subject }}</br>
{{ form.message.label_tag }}</br>
{{ form.message.errors }}
{{ form.message }}</br>
{{ form.notarobot.errors }}
{{ form.non_field_errors }}
Please prove you're not a robot. {{num1}} + {{num2}} =
{{ form.notarobot }}</br />
{{ form.answer }}
{{ form.num1 }}
{{ form.num2 }}
{{ form.page }}
<input type="submit" value="Submit" />
</form>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends "basedocumentation.html" %}
{% block title %}Pledge Cancelled{% endblock %}
{% block extra_extra_head %}
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
{% endblock %}
{% block doccontent %}
<div class="thank-you">Would you consider pledging in the future?</div>
<div>{{output}}</div>
{% endblock %}

View File

@ -0,0 +1,17 @@
{% extends "basedocumentation.html" %}
{% block title %}Pledge Completed{% endblock %}
{% block extra_extra_head %}
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
{% endblock %}
{% block doccontent %}
<div class="thank-you">Thank you!</div>
<div>{{output}}</div>
{% endblock %}

View File

@ -0,0 +1,75 @@
{% extends "basedocumentation.html" %}
{% block doccontent %}
<h1>Press</h1>
<div class="presstoc">
<div>
<a href="#overview">Overview</a><br />
<a href="#press">Press Coverage</a><br />
<a href="#blogs">Blog Coverage (Highlights)</a><br />
</div>
<div>
<a href="#video">Video</a><br />
<a href="#newsletters">Newsletters</a><br />
<a href="#images">Logos &amp; Icons</a><br />
</div>
<div class="pressemail">
Additional press questions? Please email <a href="mailto:press@gluejar.com">press@gluejar.com</a>.
</div>
<div style="clear:both; height:7px;"></div>
<div class="pressemail">
Thanks for your interest! As of January 2011 Unglue.It is in alpha release. Things are mostly working but they're rough around the edges and may change without notice.
</div>
</div>
<a name="overview"></a><h2>Overview</h2>
<dl>
<dt>What?</dt>
<dd>Unglue.It offers a win-win solution to readers, who want to read and share their favorite books conveniently, and rights holders, who want to be rewarded for their work.<br /><br />
We run [TBA:link to wikipedia?]crowdfunding campaigns to raise money for specific, already-published books. When we reach a goal set by the rights holder, we pay them to unglue their work. They issue an electronic edition with a <a href="http://creativecommons.org"</a>Creative Commons</a> license. This license makes the edition free and legal for everyone to read, copy, and share, noncommercially, worldwide.</dd>
<dt>Why?</dt>
<dd>As ereaders proliferate, more and more people are enjoying the ereading experience. However, their favorite books may not be available as ebooks. They may come with DRM which makes them unreadable on certain devices, and difficult or impossible for individuals and libraries to lend. Or they may not be able to tell if they have the legal right to use the book as they'd like.<br /><br />
When books have a clear, established legal license which promotes use, they can be read more widely, leading to enjoyment, scholarship, and innovation. By raising money to compensate authors and publishers up front, Unglue.It encourages the benefits of openness while ensuring sustainability for creators.</dd>
<dt>Who?</dt>
<dd>Unglue.It is a service of Gluejar, Inc. We are <a href="http://go-to-hellman.blogspot.com">Eric Hellman</a>, <a href="http://www.ameckeco.com/">Amanda Mecke</a>, <a href="http://raymondyee.net/">Raymond Yee</a>, and <a href="http://andromedayelton.com">Andromeda Yelton</a>. (TBA: include contractors?) We come from the worlds of entrepreneurship, linked data, physics, publishing, education, and library science, to name a few. You can learn more about us at our personal home pages (linked above) or <a href="http://gluejar.com/team">the team page</a> of our corporate site.</dd>
<dt>When?</dt>
<dd>Unglue.It is in alpha -- a limited release for testing purposes -- as of January 2011. If you <a href="http://eepurl.com/fKLfI">sign up for our newsletter</a>, we'll tell you the moment we're in beta. At that point we'll have active campaigns and be welcoming account signups from everyone.</dd>
<dt>Where?</dt>
<dd>Gluejar is a New Jersey corporation, but its employees and contractors live and work across North America. The best way to contact us is by email, <a mailto="press@gluejar.com">press@gluejar.com</a>.</dd>
<dt>What does it cost?</dt>
<dd>Unglue.It is free to join and explore. Supporters pay only if they choose to support campaigns, and the amount is up to them. Unglue.It takes a small percentage from successful campaigns, with the remainder going to the rights holders.</dd>
<dt>What's your technology?</dt>
<dd>Unglue.It is built using Python and the Django framework. [TBA: links] We use data from the Google Books, OpenLibrary, LibraryThing, and GoodReads APIs [TBA: public? "Open Library"]; we appreciate that they've made these APIs available, and we'll be returning the favor with <a href="http://0.0.0.0:8000/api/help">our own API</a>. We use the Less framework [TBA:link] to organize our CSS. We collaborate on our code at <a href="https://github.com/">github</a>[TBA: capitalization?] and deploy to Amazon EC2 [TBA: throw in "cloud" here somehow, look up what EC2 is exactly].</dd>
<dt>I have more questions...</dt>
<dd>Please consult our <a href="/faq/">FAQ</a> (sidebar at left); join the site and explore its features for yourself; or email us, <a href="press@gluejar.com">press@gluejar.com</a>.</dd>
<a name="press"></a><h2>Press Coverage</h2>
<a name="blogs"></a><h2>Blog Coverage (Highlights)</h2>
<a name="video"></a><h2>Video</h2>
<a name="newsletters"></a><h2>Newsletters</h2>
<a name="images"></a><h2>Logos &amp; Icons</h2>
<div class="pressimages">
<div class="outer">
<div><img src="/static/images/logo.png"></div>
<div><p>(161 x 70)</p></div>
</div>
<div class="outer">
<div><img src="/static/images/unglued.png"></div>
<div><p>(44 x 30)</p></div>
</div>
</div>
<br /><br /><br /><br /><br /><br />
<div>
logos at
Low Resolution Logo (350 x 150)
Standard Resolution Logo (1400 x 600)
High Resolution Logo (5600 x 2400)
black background and white background (color versions only)
icons on black, dark grey, white; 1(?) p grey border, rounded corners (at some surprisingly large border radius), 48px square
screencaps
look through mail for different sizes of icon and B&W -- ping stefan, ask if this looks like enough, & if not can he send more
</div>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends "basedocumentation.html" %}
{% block title %}Feedback{% endblock %}
{% block doccontent %}
<p>Thanks for helping us make Unglue.It better!</p>
<p>Would you like to <a href="{{ page }}">go back to the page you were on</a>?
{% endblock %}

View File

@ -79,7 +79,13 @@ jQuery(document).ready(function(){
<h3 class="book-author">{{ work.author }}</h3> <h3 class="book-author">{{ work.author }}</h3>
<h3 class="book-year">{{ pubdate }}</h3> <h3 class="book-year">{{ pubdate }}</h3>
</div> </div>
{% if status == 'ACTIVE' %}<div class="btn_support"><form action="{% url pledge work_id=work.id %}" method="get"><input type="submit" value="Support"/></form></div>{% endif %} {% if status == 'ACTIVE' %}
{% if pledged %}
<div class="btn_support modify"><form action="/stub/modify_pledge" method="get"><input type="submit" value="Change Pledge"/></form></div>
{% else %}
<div class="btn_support"><form action="{% url pledge work_id=work.id %}" method="get"><input type="submit" value="Support"/></form></div>
{% endif %}
{% endif %}
</div> </div>
</div> </div>
<div class="find-book"> <div class="find-book">

View File

@ -5,7 +5,7 @@ from django.views.generic import ListView, DetailView
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from regluit.core.models import Campaign from regluit.core.models import Campaign
from regluit.frontend.views import CampaignFormView, GoodreadsDisplayView, LibraryThingView, PledgeView, FAQView from regluit.frontend.views import CampaignFormView, GoodreadsDisplayView, LibraryThingView, PledgeView, PledgeCompleteView, PledgeCancelView, FAQView
from regluit.frontend.views import CampaignListView, DonateView, WorkListView from regluit.frontend.views import CampaignListView, DonateView, WorkListView
urlpatterns = patterns( urlpatterns = patterns(
@ -48,6 +48,8 @@ urlpatterns = patterns(
#may want to deprecate the following #may want to deprecate the following
url(r"^setup/work/(?P<work_id>\d+)/$", "work", {'action':'setup_campaign'}, name="setup_campaign"), url(r"^setup/work/(?P<work_id>\d+)/$", "work", {'action':'setup_campaign'}, name="setup_campaign"),
url(r"^pledge/(?P<work_id>\d+)/$", login_required(PledgeView.as_view()), name="pledge"), url(r"^pledge/(?P<work_id>\d+)/$", login_required(PledgeView.as_view()), name="pledge"),
url(r"^pledge/cancel/$", PledgeCancelView.as_view(), name="pledge_cancel"),
url(r"^pledge/complete/$", PledgeCompleteView.as_view(), name="pledge_complete"),
url(r"^celery/clear/$","clear_celery_tasks", name="clear_celery_tasks"), url(r"^celery/clear/$","clear_celery_tasks", name="clear_celery_tasks"),
url(r"^subjects/$", "subjects", name="subjects"), url(r"^subjects/$", "subjects", name="subjects"),
url(r"^librarything/$", LibraryThingView.as_view(), name="librarything"), url(r"^librarything/$", LibraryThingView.as_view(), name="librarything"),
@ -57,4 +59,8 @@ urlpatterns = patterns(
url('^500testing/$', direct_to_template, {'template': '500.html'}), url('^500testing/$', direct_to_template, {'template': '500.html'}),
url('^robots.txt$', direct_to_template, {'template': 'robots.txt'}), url('^robots.txt$', direct_to_template, {'template': 'robots.txt'}),
url(r"^emailshare/$", "emailshare", name="emailshare"), url(r"^emailshare/$", "emailshare", name="emailshare"),
url(r"^feedback/$", "feedback", name="feedback"),
url(r"^feedback/thanks/$", TemplateView.as_view(template_name="thanks.html")),
url(r"^press/$", TemplateView.as_view(template_name="press.html"),
name="press"),
) )

View File

@ -4,6 +4,7 @@ import json
import urllib import urllib
import logging import logging
import datetime import datetime
from random import randint
from re import sub from re import sub
from itertools import islice from itertools import islice
from decimal import Decimal as D from decimal import Decimal as D
@ -39,7 +40,7 @@ from regluit.core.search import gluejar_search
from regluit.core.goodreads import GoodreadsClient from regluit.core.goodreads import GoodreadsClient
from regluit.frontend.forms import UserData, ProfileForm, CampaignPledgeForm, GoodreadsShelfLoadingForm from regluit.frontend.forms import UserData, ProfileForm, CampaignPledgeForm, GoodreadsShelfLoadingForm
from regluit.frontend.forms import RightsHolderForm, UserClaimForm, LibraryThingForm, OpenCampaignForm from regluit.frontend.forms import RightsHolderForm, UserClaimForm, LibraryThingForm, OpenCampaignForm
from regluit.frontend.forms import ManageCampaignForm, DonateForm, CampaignAdminForm, EmailShareForm from regluit.frontend.forms import ManageCampaignForm, DonateForm, CampaignAdminForm, EmailShareForm, FeedbackForm
from regluit.payment.manager import PaymentManager from regluit.payment.manager import PaymentManager
from regluit.payment.parameters import TARGET_TYPE_CAMPAIGN, TARGET_TYPE_DONATION from regluit.payment.parameters import TARGET_TYPE_CAMPAIGN, TARGET_TYPE_DONATION
from regluit.payment.paypal import Preapproval, IPN_PAY_STATUS_ACTIVE, IPN_PAY_STATUS_INCOMPLETE, IPN_PAY_STATUS_COMPLETED, IPN_PAY_STATUS_CANCELED from regluit.payment.paypal import Preapproval, IPN_PAY_STATUS_ACTIVE, IPN_PAY_STATUS_INCOMPLETE, IPN_PAY_STATUS_COMPLETED, IPN_PAY_STATUS_CANCELED
@ -81,6 +82,10 @@ def work(request, work_id, action='display'):
work = get_object_or_404(models.Work, id=work_id) work = get_object_or_404(models.Work, id=work_id)
editions = work.editions.all().order_by('-publication_date') editions = work.editions.all().order_by('-publication_date')
campaign = work.last_campaign() campaign = work.last_campaign()
try:
pledged = campaign.transactions().filter(user=request.user, status="ACTIVE")
except:
pledged = None
try: try:
pubdate = work.editions.all()[0].publication_date[:4] pubdate = work.editions.all()[0].publication_date[:4]
except IndexError: except IndexError:
@ -111,6 +116,7 @@ def work(request, work_id, action='display'):
'base_url': base_url, 'base_url': base_url,
'editions': editions, 'editions': editions,
'pubdate': pubdate, 'pubdate': pubdate,
'pledged':pledged,
}) })
def manage_campaign(request, id): def manage_campaign(request, id):
@ -295,6 +301,37 @@ class PledgeView(FormView):
logger.info("PledgeView paypal: Error " + str(t.reference)) logger.info("PledgeView paypal: Error " + str(t.reference))
return HttpResponse(response) return HttpResponse(response)
class PledgeCompleteView(TemplateView):
"""A callback for PayPal to tell unglue.it that a payment transaction has completed successfully"""
template_name="pledge_complete.html"
def get_context_data(self, **kwargs):
# pick up all get and post parameters and display
context = super(PledgeCompleteView, self).get_context_data(**kwargs)
output = "pledge complete"
output += self.request.method + "\n" + str(self.request.REQUEST.items())
context["output"] = output
return context
class PledgeCancelView(TemplateView):
"""A callback for PayPal to tell unglue.it that a payment transaction has been canceled by the user"""
template_name="pledge_cancel.html"
def get_context_data(self, **kwargs):
# pick up all get and post parameters and display
context = super(PledgeCancelView, self).get_context_data(**kwargs)
output = "pledge cancel"
output += self.request.method + "\n" + str(self.request.REQUEST.items())
context["output"] = output
return context
class DonateView(FormView): class DonateView(FormView):
template_name="donate.html" template_name="donate.html"
form_class = DonateForm form_class = DonateForm
@ -1044,3 +1081,39 @@ def emailshare(request):
form = EmailShareForm(initial={'next':next, 'message':"I'm ungluing books at unglue.it. Here's one of my favorites: "+next, "sender":sender}) form = EmailShareForm(initial={'next':next, 'message':"I'm ungluing books at unglue.it. Here's one of my favorites: "+next, "sender":sender})
return render(request, "emailshare.html", {'form':form}) return render(request, "emailshare.html", {'form':form})
def feedback(request):
num1 = randint(0,10)
num2 = randint(0,10)
sum = num1 + num2
if request.method == 'POST':
form=FeedbackForm(request.POST)
if form.is_valid():
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
sender = form.cleaned_data['sender']
recipient = 'support@gluejar.com'
page = form.cleaned_data['page']
message = "<<<This feedback is about "+page+". Original user message follows: >>>"+message
send_mail(subject, message, sender, [recipient])
return render(request, "thanks.html", {"page":page})
else:
num1 = request.POST['num1']
num2 = request.POST['num2']
else:
if request.user.is_authenticated():
sender=user.email;
else:
sender=''
try:
page = request.GET['page']
except:
page='/'
form = FeedbackForm(initial={"sender":sender, "subject": "Feedback on page "+page, "page":page, "num1":num1, "num2":num2, "answer":sum})
return render(request, "feedback.html", {'form':form, 'num1':num1, 'num2':num2})

View File

@ -130,6 +130,9 @@
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
} }
.book-detail-info > div.layout div.btn_support.modify input {
background: url("/static/images/btn_bg_grey.png") 0 0 no-repeat;
}
.book-detail-info .btn_wishlist span { .book-detail-info .btn_wishlist span {
text-align: right; text-align: right;
} }

View File

@ -188,6 +188,7 @@ dt {
} }
dd { dd {
margin: 0; margin: 0;
padding-bottom: 7px;
} }
.doc ol li { .doc ol li {
margin-bottom: 7px; margin-bottom: 7px;
@ -198,3 +199,36 @@ dd {
.collapse ul { .collapse ul {
display: none; display: none;
} }
/* items on press page */
.presstoc {
overflow: auto;
clear: both;
padding-bottom: 10px;
}
.presstoc div {
float: left;
padding-right: 15px;
}
.presstoc div.pressemail {
border: solid 2px #3d4e53;
padding: 5px;
-moz-border-radius: 7px 7px 7px 7px;
-webkit-border-radius: 7px 7px 7px 7px;
border-radius: 7px 7px 7px 7px;
-moz-border-radius: 7px 7px 7px 7px;
-webkit-border-radius: 7px 7px 7px 7px;
border-radius: 7px 7px 7px 7px;
}
.pressimages {
clear: both;
}
.pressimages .outer {
clear: both;
}
.pressimages .outer div {
float: left;
width: 25%;
padding-bottom: 10px;
display: table-cell;
margin: auto 0;
}

View File

@ -141,14 +141,19 @@
height: 24px; height: 24px;
line-height: 24px; line-height: 24px;
width: 24px; width: 24px;
-moz-border-radius: 12px 12px 12px 12px; -moz-border-radius: 24px 24px 24px 24px;
-webkit-border-radius: 12px 12px 12px 12px; -webkit-border-radius: 24px 24px 24px 24px;
border-radius: 12px 12px 12px 12px; border-radius: 24px 24px 24px 24px;
-moz-border-radius: 12px 12px 12px 12px; -moz-border-radius: 24px 24px 24px 24px;
-webkit-border-radius: 12px 12px 12px 12px; -webkit-border-radius: 24px 24px 24px 24px;
border-radius: 12px 12px 12px 12px; border-radius: 24px 24px 24px 24px;
border: solid 4px #3d4e53; -moz-box-shadow: -1px 1px #3d4e53;
-webkit-box-shadow: -1px 1px #3d4e53;
box-shadow: -1px 1px #3d4e53;
border: solid 3px white;
text-align: center; text-align: center;
color: white;
background: #3d4e53;
font-size: 17px; font-size: 17px;
z-index: 5000; z-index: 5000;
margin-top: -12px; margin-top: -12px;

View File

@ -35,6 +35,42 @@ body {
font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Arial, Helvetica, sans-serif; font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Arial, Helvetica, sans-serif;
color: #3d4e53; color: #3d4e53;
} }
#feedback {
/* remove after alpha? */
position: fixed;
top: 50%;
left: 0;
z-index: 500;
}
#feedback p {
/* see http://scottgale.com/blog/css-vertical-text/2010/03/01/ */
writing-mode: tb-rl;
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-o-transform: rotate(90deg);
white-space: nowrap;
display: block;
bottom: 0;
width: 160px;
height: 26px;
-moz-border-radius: 32px 32px 32px 32px;
-webkit-border-radius: 32px 32px 32px 32px;
border-radius: 32px 32px 32px 32px;
background: #8dc63f;
margin-bottom: 0;
text-align: center;
margin-left: -67px;
}
#feedback p a {
color: white;
font-size: 24px;
font-weight: normal;
}
#feedback p a:hover {
color: #3d4e53;
}
a { a {
font-weight: bold; font-weight: bold;
font-size: 13px; font-size: 13px;

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -151,6 +151,10 @@
padding:0; padding:0;
cursor:pointer; cursor:pointer;
} }
&.modify input {
background:url("@{image-base}btn_bg_grey.png") 0 0 no-repeat;
}
} }
} }

View File

@ -124,6 +124,7 @@ dt {
dd { dd {
margin: 0; margin: 0;
padding-bottom: 7px;
} }
.doc ol li { .doc ol li {
@ -137,3 +138,35 @@ dd {
.collapse ul { .collapse ul {
display: none; display: none;
} }
/* items on press page */
.presstoc {
div {
float: left;
padding-right: 15px;
&.pressemail {
border: solid 2px @text-blue;
padding: 5px;
.border-radius(7px, 7px, 7px, 7px);
}
}
overflow: auto;
clear: both;
padding-bottom: 10px;
}
.pressimages {
.outer {
clear: both;
div {
float: left;
width: 25%;
padding-bottom: 10px;
display: table-cell;
margin: auto 0;
}
}
clear: both;
}

View File

@ -92,9 +92,14 @@
float: right; float: right;
.height(24px); .height(24px);
width: 24px; width: 24px;
.border-radius(12px, 12px, 12px, 12px); .border-radius(24px, 24px, 24px, 24px);
border: solid 4px @text-blue; -moz-box-shadow: -1px 1px @text-blue;
-webkit-box-shadow: -1px 1px @text-blue;
box-shadow: -1px 1px @text-blue;
border: solid 3px white;
text-align: center; text-align: center;
color: white;
background: @text-blue;
font-size: 17px; font-size: 17px;
z-index:5000; z-index:5000;
margin-top: -12px; margin-top: -12px;

View File

@ -32,6 +32,41 @@ body{
color:@text-blue; color:@text-blue;
} }
#feedback {
/* remove after alpha? */
position: fixed;
top: 50%;
left: 0;
z-index:500;
p {
/* see http://scottgale.com/blog/css-vertical-text/2010/03/01/ */
a {
color:white;
font-size:24px;
font-weight:normal;
&:hover {
color: @text-blue;
}
}
writing-mode:tb-rl;
-webkit-transform:rotate(90deg);
-moz-transform:rotate(90deg);
-o-transform: rotate(90deg);
white-space:nowrap;
display:block;
bottom:0;
width:160px;
height:26px;
.border-radius(32px, 32px, 32px, 32px);
background: @call-to-action;
margin-bottom: 0;
text-align: center;
margin-left: -67px;
}
}
a { a {
font-weight:bold; font-weight:bold;
font-size:13px; font-size:13px;